$store.announcements$store.announcementModalAlpine.store('viewedAnnouncements'){
"announcements": [
{
"id": "Announcement Id",
"title": "Sample title",
"description": "<p>Sample description</p>\n",
"buttonPrimaryText": "Sample button text",
"buttonPrimaryLink": "/sample",
"isRead": false,
"image": {
"id": "Image Id",
"galleryId": "Gallery Id",
"link": "https://example.com/sample",
"mediaType": "Image"
}
}
],
"name": "Announcement",
"view": "Default",
"section": "SectionHeader",
"settings": {
"id": "Component Id",
"section": "SectionHeader",
"type": "NoirAnnouncement",
"name": "Announcement",
"configuredInContentApi": true,
"view": "Default",
"displayName": "",
"cssClass": "",
"header": "",
"alignment": "Left"
},
"translations": {
"closeAnnouncementsModalMessage": "Sample translation",
"notifications": "Sample translation",
"noNotificationsFound": "Sample translation",
"...": "..."
}
}settings.idcomp-{{ id }}settings.cssClass(UNDEFINED).announcements[]id (string)title (string)description (string, optional HTML)isRead (boolean)!isRead announcements, and read state can be persisted server-side via setReadAnnouncementIds(...).Note
For authenticated users,isReadis the server-provided read flag.
For guest users, read/unread is tracked client-side via theviewedAnnouncementsstore.
x-data="announcementdefault.initComponent(announcements)"initComponent(...) returns the Alpine component state + methods.initComponent(announcements)announcements (Liquid-serialized data) and returns an Alpine object containing:announcements: the list rendered by the templateheight: a dynamic drawer height so it fits under the headerinit()isAnnouncementViewed, calculateHeight)initComponent(announcements) stable, since it’s a contract with the Liquid template.init()calculateHeight() immediately to initialize the drawer height.resize → recalculates heightscroll → recalculates heightlistenersBound), orcalculateHeight() for performance.isAnnouncementViewed(id)id is already marked as viewed in the client store:Alpine.store("viewedAnnouncements").isViewed(id)isRead) and can differ from the client store. Keep the responsibilities clear:calculateHeight()<header> element:document.querySelector("header")header.offsetHeight (or 0 if not found)window.innerHeight - headerHeightthis.height = "...px"<header> element, the computed height becomes full viewport height. If that happens:header.site-header), orx-ref from the layout.offsetHeight includes borders (not margins), which is typically what you want for “fit under fixed header” behavior.$store.announcements (drawer state)x-show="$store.announcements.visible"@click.outside="$store.announcements.close()"@click="$store.announcements.close()"$store.announcements.open() (or sets visible = true), the drawer becomes visible.close() is called and the drawer hides.Note
The store is responsible for the “global UI state” (open/close), so the drawer can be triggered from anywhere (e.g. header icon).
$store.announcementModal (modal state)$store.announcementModal.open(announcement.id)Alpine.store('viewedAnnouncements') (client “viewed/unviewed” tracking)!isAnnouncementViewed(announcement.id)Alpine.store('viewedAnnouncements').addViewedId(announcement.id)viewedAnnouncements is the main mechanism used to track unread/read on the client (usually via local storage).isRead flag and can be persisted with API calls (done by the modal logic).viewedAnnouncements.addViewedId(id)$dispatch('update-remaining')$store.announcements.close()$store.announcementModal.open(id)$store.announcements$store.announcementModalAlpine.store('viewedAnnouncements')description is rendered using x-html, so it is treated as trusted HTML (ensure server sanitization where applicable).$store.announcements or $store.announcementModal are missing, the drawer/modal flow won’t work.x-show="$store.announcements.visible"@click.outside)announcements.length > 0, it renders a list of clickable rows.announcements.length == 0, it renders a “No notifications found” message.isAnnouncementViewed(announcement.id)Alpine.store('viewedAnnouncements').addViewedId(...)update-remaining$store.announcements.close()$store.announcementModal.open(announcement.id)