fix(web): show notification count as menu badge and mark read on open
ci / ts / ts (packages + web) (push) Successful in 2m52s
ci / ts / ts (packages + web) (push) Successful in 2m52s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
139dd226fa
commit
f77148df2c
4 files changed
+19
-3
No files matched your search
@@ -29,7 +29,7 @@ export function NotificationsDialog({ onClose, onOpen }: { onClose: () => void;
|
|||||||
{shown.map(event => {
|
{shown.map(event => {
|
||||||
const date = new Date(event.created_at);
|
const date = new Date(event.created_at);
|
||||||
return <article key={event.id} className={`${s.row} ${!event.read ? s.unread : ''}`}>
|
return <article key={event.id} className={`${s.row} ${!event.read ? s.unread : ''}`}>
|
||||||
<button type="button" className={s.open} aria-label={t('social.open_notification', { name: event.actor_name })} onClick={() => onOpen(event.actor_id)}>
|
<button type="button" className={s.open} aria-label={t('social.open_notification', { name: event.actor_name })} onClick={() => { if (!event.read) read.mutate(event.id); onOpen(event.actor_id); }}>
|
||||||
<span className={s.avatar} aria-hidden>{event.actor_name.slice(0, 1).toUpperCase()}</span>
|
<span className={s.avatar} aria-hidden>{event.actor_name.slice(0, 1).toUpperCase()}</span>
|
||||||
<span className={s.content}><strong>{event.actor_name}</strong><span>{t(`social.kind_${event.kind}`)}</span>
|
<span className={s.content}><strong>{event.actor_name}</strong><span>{t(`social.kind_${event.kind}`)}</span>
|
||||||
<time dateTime={event.created_at} title={date.toLocaleString(i18n.language)}>{date.toLocaleString(i18n.language, { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })}</time>
|
<time dateTime={event.created_at} title={date.toLocaleString(i18n.language)}>{date.toLocaleString(i18n.language, { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })}</time>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export function useSocialProfileMenu() {
|
|||||||
return {
|
return {
|
||||||
items: [
|
items: [
|
||||||
{ label: t(currentVisibility === 'nobody' ? 'social.status_hidden' : 'social.privacy_settings'), icon: currentVisibility === 'nobody' ? visibilityOffIcon : shieldIcon, onSelect: () => setDialog('privacy') },
|
{ label: t(currentVisibility === 'nobody' ? 'social.status_hidden' : 'social.privacy_settings'), icon: currentVisibility === 'nobody' ? visibilityOffIcon : shieldIcon, onSelect: () => setDialog('privacy') },
|
||||||
{ label: `${t('social.notifications')}${inbox.data?.unread ? ` ${inbox.data.unread}` : ''}`, icon: inboxIcon, onSelect: () => setDialog('notifications') },
|
{ label: t('social.notifications'), badge: inbox.data?.unread, icon: inboxIcon, onSelect: () => setDialog('notifications') },
|
||||||
],
|
],
|
||||||
dialogs: <>
|
dialogs: <>
|
||||||
{dialog === 'privacy' && <Dialog title={t('social.privacy_settings')} onClose={closeDialog} className={s.preferencesDialog}>
|
{dialog === 'privacy' && <Dialog title={t('social.privacy_settings')} onClose={closeDialog} className={s.preferencesDialog}>
|
||||||
|
|||||||
@@ -420,6 +420,21 @@
|
|||||||
background: var(--hover);
|
background: var(--hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menuBadge {
|
||||||
|
margin-left: auto;
|
||||||
|
min-width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 6px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--accent-fill);
|
||||||
|
color: var(--on-accent, #fff);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
.menuDanger {
|
.menuDanger {
|
||||||
color: var(--danger);
|
color: var(--danger);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ export function Dialog({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MenuItem = { label: string; icon?: string; danger?: boolean; onSelect: () => void };
|
export type MenuItem = { label: string; icon?: string; danger?: boolean; badge?: number; onSelect: () => void };
|
||||||
|
|
||||||
/** Выпадающее меню: открывается кнопкой-триггером, закрывается кликом снаружи и Esc. */
|
/** Выпадающее меню: открывается кнопкой-триггером, закрывается кликом снаружи и Esc. */
|
||||||
export function Menu({
|
export function Menu({
|
||||||
@@ -268,6 +268,7 @@ export function Menu({
|
|||||||
>
|
>
|
||||||
{it.icon && <Icon src={it.icon} size={18} />}
|
{it.icon && <Icon src={it.icon} size={18} />}
|
||||||
{it.label}
|
{it.label}
|
||||||
|
{it.badge ? <span className={s.menuBadge}>{it.badge > 99 ? '99+' : it.badge}</span> : null}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user