style: ui improvement to the notification cards.

This commit is contained in:
Nathnael
2026-07-06 10:54:43 +00:00
parent ca3a6072d8
commit 298ad6227b
11 changed files with 211 additions and 109 deletions

View File

@@ -9,11 +9,13 @@ import {
ScrollArea,
Stack,
Text,
ThemeIcon,
} from "@mantine/core";
import { CheckCheck, Inbox, X } from "lucide-react";
import { Bell, CheckCheck, Inbox, X } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { NotificationItem } from "./NotificationItem";
import { edr } from "./PriorityTag";
import type {
NotificationItemData,
ResolveNotificationVisual,
@@ -73,36 +75,39 @@ function Sentinel({
}
function SectionLabel({
children,
right,
label,
count,
}: {
children: React.ReactNode;
right?: React.ReactNode;
label: string;
count?: number;
}) {
return (
<Group
justify="space-between"
gap={8}
px="md"
py={6}
py={8}
wrap="nowrap"
style={{
position: "sticky",
top: 0,
zIndex: 1,
background: "var(--mantine-color-body)",
backdropFilter: "blur(6px)",
background: edr.card,
borderBottom: `1px solid ${edr.border}`,
}}
>
<Text
size="xs"
fw={700}
tt="uppercase"
c="dimmed"
style={{ letterSpacing: 0.6 }}
style={{ color: edr.muted, letterSpacing: 0.6 }}
>
{children}
{label}
</Text>
{right}
{typeof count === "number" && count > 0 && (
<Text size="xs" fw={600} style={{ color: edr.muted }}>
{count}
</Text>
)}
</Group>
);
}
@@ -110,15 +115,16 @@ function SectionLabel({
function LoadingRow() {
return (
<Group justify="center" py="md">
<Loader size="xs" />
<Loader size="xs" color="edr-green" />
</Group>
);
}
/**
* Right slide-in notification center: an "Unread" section over an "Earlier"
* (read) section, each with its own infinite-scroll trigger, in one scroll
* surface. Presentational — the host wires data, paging, and the visual registry.
* Right slide-in notification center, styled to the EDR design system: a
* branded header, an "Unread" section over an "Earlier" (read) section, each
* with its own infinite-scroll trigger in one scroll surface. Presentational —
* the host wires data, paging, and the visual registry.
*/
export function NotificationDrawer({
opened,
@@ -138,7 +144,7 @@ export function NotificationDrawer({
resolveVisual,
emptyLabel = "You're all caught up",
title = "Notifications",
width = 420,
width = 424,
}: NotificationDrawerProps) {
const [viewport, setViewport] = useState<HTMLElement | null>(null);
const viewportRef = useRef<HTMLDivElement>(null);
@@ -168,10 +174,14 @@ export function NotificationDrawer({
size={width}
padding={0}
withCloseButton={false}
overlayProps={{ backgroundOpacity: 0.35, blur: 2 }}
transitionProps={{ transition: "slide-left" }}
overlayProps={{ backgroundOpacity: 0.45, blur: 3 }}
transitionProps={{ transition: "slide-left", duration: 220 }}
styles={{
content: { display: "flex", flexDirection: "column" },
content: {
display: "flex",
flexDirection: "column",
background: edr.card,
},
body: {
flex: 1,
minHeight: 0,
@@ -184,18 +194,24 @@ export function NotificationDrawer({
<Group
justify="space-between"
px="md"
py="sm"
py="md"
wrap="nowrap"
style={{
borderBottom: "1px solid var(--mantine-color-default-border)",
}}
style={{ borderBottom: `1px solid ${edr.border}` }}
>
<Group gap="xs" wrap="nowrap">
<Text fw={700} size="md">
{title}
</Text>
<Group gap="sm" wrap="nowrap">
<ThemeIcon variant="light" color="edr-green" radius="md" size={36}>
<Bell size={18} strokeWidth={2} />
</ThemeIcon>
<Box>
<Text fw={700} size="md" style={{ color: edr.text, lineHeight: 1.2 }}>
{title}
</Text>
<Text size="xs" style={{ color: edr.muted }}>
{totalUnread > 0 ? `${totalUnread} unread` : "All caught up"}
</Text>
</Box>
{totalUnread > 0 && (
<Badge color="red" variant="filled" size="sm" radius="sm">
<Badge color="red" variant="filled" size="sm" radius="xl">
{totalUnread > 99 ? "99+" : totalUnread}
</Badge>
)}
@@ -205,7 +221,7 @@ export function NotificationDrawer({
<Button
variant="subtle"
size="compact-xs"
color="gray"
color="edr-green"
leftSection={<CheckCheck size={13} />}
onClick={onMarkAllRead}
>
@@ -233,21 +249,23 @@ export function NotificationDrawer({
{loading && unread.length === 0 && read.length === 0 ? (
<LoadingRow />
) : isEmpty ? (
<Stack align="center" gap="xs" py={64} px="lg">
<Inbox
size={40}
strokeWidth={1.4}
color="var(--mantine-color-dimmed)"
/>
<Text c="dimmed" size="sm" ta="center">
<Stack align="center" gap="sm" py={72} px="lg">
<ThemeIcon variant="light" color="edr-green" radius="xl" size={56}>
<Inbox size={26} strokeWidth={1.6} />
</ThemeIcon>
<Text size="sm" fw={600} style={{ color: edr.text }}>
{emptyLabel}
</Text>
<Text size="xs" ta="center" style={{ color: edr.muted, maxWidth: 240 }}>
New notifications about your bookings, clearances and invoices will
show up here.
</Text>
</Stack>
) : (
<Box pb="md">
{unread.length > 0 && (
<>
<SectionLabel>Unread</SectionLabel>
<SectionLabel label="Unread" count={totalUnread} />
<Stack gap={0}>{unread.map(renderItem)}</Stack>
{hasMoreUnread && (
<Sentinel
@@ -261,7 +279,7 @@ export function NotificationDrawer({
{read.length > 0 && (
<>
<SectionLabel>Earlier</SectionLabel>
<SectionLabel label="Earlier" />
<Stack gap={0}>{read.map(renderItem)}</Stack>
{hasMoreRead && (
<Sentinel