NotificationListItem
DomainSingle notification row. Includes per-kind icon tones (order/message/system/alert/success/social), read/unread emphasis, relative time, and an optional "Mark read" button.
Order #1042 has been shipped
Your package is on the way and should arrive by Friday.
New message from Sarah
Hey! Just wanted to follow up on the proposal we discussed last week.
Backup completed successfully
Unusual sign-in attempt blocked
A sign-in attempt from a new device was prevented.
<%- include('modules/domain/common/notification/NotificationListItem', {
kind: 'order',
title: 'Order #1042 has been shipped',
body: 'Your package is on the way and should arrive by Friday.',
createdAt: new Date(Date.now() - 5 * 60 * 1000),
read: false,
href: '/orders/1042'
}) %>
Scheduled maintenance tonight at 02:00 UTC
The dashboard will be briefly unavailable for ~10 minutes.
<%- include('modules/domain/common/notification/NotificationListItem', {
kind: 'system',
title: 'Scheduled maintenance tonight at 02:00 UTC',
body: 'The dashboard will be briefly unavailable for ~10 minutes.',
createdAt: new Date(),
read: false,
onMarkRead: 'function(){ /* mark read */ }'
}) %>
<%
var _kind = locals.kind || 'system';
var _title = locals.title || '';
var _body = locals.body || '';
var _createdAt = locals.createdAt || new Date();
var _read = !!locals.read;
var _href = locals.href || '';
var _className = locals.className || '';
var _onMarkRead = locals.onMarkRead || ''; // optional JS handler string
var KIND_META = {
order: { icon: 'fa-bag-shopping', tone: 'bg-primary-subtle text-primary' },
message: { icon: 'fa-message', tone: 'bg-info-subtle text-info' },
system: { icon: 'fa-bell', tone: 'bg-surface-overlay text-text-secondary' },
alert: { icon: 'fa-triangle-exclamation', tone: 'bg-warning-subtle text-warning' },
success: { icon: 'fa-circle-check', tone: 'bg-success-subtle text-success' },
social: { icon: 'fa-user', tone: 'bg-secondary/10 text-secondary' },
};
var meta = KIND_META[_kind] || KIND_META.system;
function timeAgo(date) {
var d = (date instanceof Date) ? date : new Date(date);
var diff = Date.now() - d.getTime();
var mins = Math.floor(diff / 60000);
if (mins < 1) return 'just now';
if (mins < 60) return mins + 'm ago';
var hrs = Math.floor(mins / 60);
if (hrs < 24) return hrs + 'h ago';
var days = Math.floor(hrs / 24);
if (days < 7) return days + 'd ago';
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
var _dateIso = (function () {
try { return new Date(_createdAt).toISOString(); } catch (e) { return ''; }
})();
var _timeLabel = timeAgo(_createdAt);
var rootCls = 'group relative flex items-start gap-3 border-b border-border last:border-b-0 px-4 py-3 transition-colors';
if (!_read) rootCls += ' bg-primary-subtle/30';
if (_href) rootCls += ' hover:bg-surface-overlay focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-border-focus';
if (_className) rootCls += ' ' + _className;
%>
<% if (_href) { %>
<% } else { %>
<% } %>
<% if (!_read) { %>
<% } %>
<%= _title %>
<% if (_body) { %>
<%= _body %>
<% } %>
<% if (!_read && _onMarkRead) { %>
<% } %>
<% if (_href) { %>
<% } else { %>
<% } %>