@lessly/ui 0.14.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1352,6 +1352,15 @@ interface NotificationProduct {
1352
1352
  }
1353
1353
  declare const severityLabels: Record<NotificationSeverity, string>;
1354
1354
  declare function severityBadgeVariant(severity: NotificationSeverity): 'destructive' | 'warning' | 'secondary';
1355
+ /** Per-severity dwell in seconds. `Infinity` means the toast stays until dismissed. */
1356
+ type NotificationToastDwell = Record<NotificationSeverity, number>;
1357
+ /**
1358
+ * Toasts peek, then tuck away — the record behind the bell is the durable copy, so
1359
+ * nothing is lost when a card leaves. Critical is the exception: it stays in the
1360
+ * corner until it is handled, because for an incident the persistent toast IS the
1361
+ * escalation. Timing lives in the toaster; this is only the default table.
1362
+ */
1363
+ declare const TOAST_DWELL: NotificationToastDwell;
1355
1364
  /**
1356
1365
  * Reads a stamp as a relative age. `now` is injectable so the components stay
1357
1366
  * testable and consumers can substitute an absolute or localized formatter; the
@@ -1372,9 +1381,8 @@ interface NotificationBellProps {
1372
1381
  /** Rows shown in the popover. Default 5. */
1373
1382
  maxItems?: number;
1374
1383
  /**
1375
- * Reported when the consumer's own row affordance marks a single item read.
1376
- * The default row surfaces the item through `onItemClick`; this exists so a
1377
- * host that renders its own control has the callback the model expects.
1384
+ * Marks a single item read. Supplying it puts a quiet check control on every
1385
+ * unread row "Read all" and clicking through are no longer the only ways.
1378
1386
  */
1379
1387
  onMarkRead?(id: string): void;
1380
1388
  onMarkAllRead?(): void;
@@ -1406,6 +1414,11 @@ interface NotificationCenterProps {
1406
1414
  onDeepLinkClick?(item: NotificationItem): void;
1407
1415
  /** "Read all". Rendered only when supplied. */
1408
1416
  onMarkAllRead?(): void;
1417
+ /**
1418
+ * Marks a single item read. Supplying it puts a quiet check control on every
1419
+ * unread row — "Read all" and clicking through are no longer the only ways.
1420
+ */
1421
+ onMarkRead?(id: string): void;
1409
1422
  /** Override the default relative-age label. */
1410
1423
  formatAge?(createdAt: Date): string;
1411
1424
  className?: string;
@@ -1428,4 +1441,40 @@ interface NotificationSettingsProps {
1428
1441
  }
1429
1442
  declare const NotificationSettings: React$1.ForwardRefExoticComponent<NotificationSettingsProps & React$1.RefAttributes<HTMLDivElement>>;
1430
1443
 
1431
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppShell, type AppShellProps, AppSidebar, type AppSidebarProps, AspectRatio, AuthenticatedLayout, Avatar, AvatarFallback, AvatarImage, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, type CarouselApi, CarouselContent, type CarouselContextProps, CarouselItem, CarouselNext, type CarouselOptions, type CarouselPlugin, CarouselPrevious, type CarouselProps, Checkbox, Code, CodeBlock, type CodeBlockProps, type CodeLang, type CodeProps, Col, type ColProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ConfirmDialog, type ConfirmDialogProps, type ConnectionAuth, ConnectionCard, type ConnectionCardProps, type ConnectionScope, type ConnectionStatus, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, type CopyButtonProps, type CreateProductSubmit, CreateProductWindow, type CreateProductWindowOrganization, type CreateProductWindowProduct, type CreateProductWindowProps, DatePicker, type DatePickerProps, type DeltaDirection, type DeltaTone, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DocsLink, type DocsLinkProps, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, ExtensionIcon, type ExtensionIconProps, ExtensionLink, type ExtensionLinkProps, type ExtensionLinkUiMode, FeedbackButton, type FeedbackButtonLabels, type FeedbackButtonProps, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Grid, GridOverlay, type GridOverlayProps, type GridProps, HoverCard, HoverCardContent, HoverCardTrigger, InlineError, type InlineErrorProps, Input, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, type InputProps, Label, MenuDivider, MenuPopup, type MenuPopupProps, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, type NavItem, type NavLinkComponent, NavRow, type NavRowProps, type NavRowVariant, NavSectionLabel, type NavSectionLabelProps, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, type NotifClass, type NotificationAction, NotificationBell, type NotificationBellProps, NotificationCenter, type NotificationCenterProps, type NotificationClass, type NotificationDeepLink, type NotificationItem, type NotificationMute, type NotificationPreferences, type NotificationProduct, NotificationSettings, type NotificationSettingsProps, type NotificationSeverity, type NotificationSubscription, type NotificationThreshold, OrgProductSwitcher, type OrgProductSwitcherAction, type OrgProductSwitcherItem, type OrgProductSwitcherProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, type PaginationLinkProps, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, RequestRow, type RequestRowProps, type RequestState, type RequestSurface, type ResolvedTheme, type Responsive, ScrollArea, ScrollBar, SectionIntro, type SectionIntroProps, SegmentChip, type SegmentChipProps, Select, type SelectOption, type SelectProps, Separator, type Severity, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarBackHeader, type SidebarBackHeaderProps, SidebarNav, SidebarNavLink, type SidebarNavLinkProps, type SidebarNavProps, SidebarProductHeader, type SidebarProductHeaderProps, SignInScreen, type SignInScreenProps, type SignInScreenState, Skeleton, Slider, StatCard, type StatCardProps, StatusDot, type StatusDotProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, type TextareaProps, type Theme, ThemeToggle, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UseSidebarOptions, type UseSidebarResult, UserMenu, type UserMenuItem, type UserMenuProps, type UserMenuUser, alertVariants, badgeVariants, buttonVariants, cn, formatRelativeAge, isKnownExtensionIcon, isNotificationSettled, navigationMenuTriggerStyle, segmentChipVariants, severityBadgeVariant, severityLabels, statusDotVariants, toggleVariants, useCarousel, useFormField, useIsMobile, useSidebar, useTheme };
1444
+ interface NotificationToastProps {
1445
+ item: NotificationItem;
1446
+ /** Provenance shown once per run of same-source cards. Default `true`. */
1447
+ showSource?: boolean;
1448
+ /** A short state line under the body, e.g. "Awaiting your decision — expires in 20s." */
1449
+ note?: React$1.ReactNode;
1450
+ onItemClick?(item: NotificationItem): void;
1451
+ onAction?(action: NotificationAction, item: NotificationItem): void;
1452
+ onDismiss?(item: NotificationItem): void;
1453
+ /** Accessible label for the close button. Default `"Dismiss"`. */
1454
+ dismissLabel?: string;
1455
+ className?: string;
1456
+ }
1457
+ declare const NotificationToast: React$1.ForwardRefExoticComponent<NotificationToastProps & React$1.RefAttributes<HTMLDivElement>>;
1458
+
1459
+ type NotificationToasterPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
1460
+ interface NotificationToasterProps {
1461
+ /** The live queue. The consumer decides what becomes a toast at all. */
1462
+ items: NotificationItem[];
1463
+ /** Per-severity dwell in seconds, merged over `TOAST_DWELL`. `Infinity` never hides. */
1464
+ dwell?: Partial<NotificationToastDwell>;
1465
+ /** Most cards on screen at once. Default `4`. */
1466
+ max?: number;
1467
+ position?: NotificationToasterPosition;
1468
+ /** Freeze every countdown while the pointer is over the stack. Default `true`. */
1469
+ pauseOnHover?: boolean;
1470
+ /** Fires on auto-hide and on the close button alike — the item leaves the same way. */
1471
+ onDismiss?(id: string): void;
1472
+ onItemClick?(item: NotificationItem): void;
1473
+ onAction?(action: NotificationAction, item: NotificationItem): void;
1474
+ /** Per-item state line, e.g. a TTL countdown the consumer already tracks. */
1475
+ renderNote?(item: NotificationItem): React$1.ReactNode;
1476
+ className?: string;
1477
+ }
1478
+ declare const NotificationToaster: React$1.ForwardRefExoticComponent<NotificationToasterProps & React$1.RefAttributes<HTMLDivElement>>;
1479
+
1480
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppShell, type AppShellProps, AppSidebar, type AppSidebarProps, AspectRatio, AuthenticatedLayout, Avatar, AvatarFallback, AvatarImage, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, type CarouselApi, CarouselContent, type CarouselContextProps, CarouselItem, CarouselNext, type CarouselOptions, type CarouselPlugin, CarouselPrevious, type CarouselProps, Checkbox, Code, CodeBlock, type CodeBlockProps, type CodeLang, type CodeProps, Col, type ColProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ConfirmDialog, type ConfirmDialogProps, type ConnectionAuth, ConnectionCard, type ConnectionCardProps, type ConnectionScope, type ConnectionStatus, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, type CopyButtonProps, type CreateProductSubmit, CreateProductWindow, type CreateProductWindowOrganization, type CreateProductWindowProduct, type CreateProductWindowProps, DatePicker, type DatePickerProps, type DeltaDirection, type DeltaTone, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DocsLink, type DocsLinkProps, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, ExtensionIcon, type ExtensionIconProps, ExtensionLink, type ExtensionLinkProps, type ExtensionLinkUiMode, FeedbackButton, type FeedbackButtonLabels, type FeedbackButtonProps, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Grid, GridOverlay, type GridOverlayProps, type GridProps, HoverCard, HoverCardContent, HoverCardTrigger, InlineError, type InlineErrorProps, Input, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, type InputProps, Label, MenuDivider, MenuPopup, type MenuPopupProps, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, type NavItem, type NavLinkComponent, NavRow, type NavRowProps, type NavRowVariant, NavSectionLabel, type NavSectionLabelProps, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, type NotifClass, type NotificationAction, NotificationBell, type NotificationBellProps, NotificationCenter, type NotificationCenterProps, type NotificationClass, type NotificationDeepLink, type NotificationItem, type NotificationMute, type NotificationPreferences, type NotificationProduct, NotificationSettings, type NotificationSettingsProps, type NotificationSeverity, type NotificationSubscription, type NotificationThreshold, NotificationToast, type NotificationToastDwell, type NotificationToastProps, NotificationToaster, type NotificationToasterPosition, type NotificationToasterProps, OrgProductSwitcher, type OrgProductSwitcherAction, type OrgProductSwitcherItem, type OrgProductSwitcherProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, type PaginationLinkProps, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, RequestRow, type RequestRowProps, type RequestState, type RequestSurface, type ResolvedTheme, type Responsive, ScrollArea, ScrollBar, SectionIntro, type SectionIntroProps, SegmentChip, type SegmentChipProps, Select, type SelectOption, type SelectProps, Separator, type Severity, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarBackHeader, type SidebarBackHeaderProps, SidebarNav, SidebarNavLink, type SidebarNavLinkProps, type SidebarNavProps, SidebarProductHeader, type SidebarProductHeaderProps, SignInScreen, type SignInScreenProps, type SignInScreenState, Skeleton, Slider, StatCard, type StatCardProps, StatusDot, type StatusDotProps, Switch, type SwitchProps, TOAST_DWELL, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, type TextareaProps, type Theme, ThemeToggle, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UseSidebarOptions, type UseSidebarResult, UserMenu, type UserMenuItem, type UserMenuProps, type UserMenuUser, alertVariants, badgeVariants, buttonVariants, cn, formatRelativeAge, isKnownExtensionIcon, isNotificationSettled, navigationMenuTriggerStyle, segmentChipVariants, severityBadgeVariant, severityLabels, statusDotVariants, toggleVariants, useCarousel, useFormField, useIsMobile, useSidebar, useTheme };
package/dist/index.js CHANGED
@@ -4285,6 +4285,11 @@ var severityBadgeVariants = {
4285
4285
  function severityBadgeVariant(severity) {
4286
4286
  return severityBadgeVariants[severity];
4287
4287
  }
4288
+ var TOAST_DWELL = {
4289
+ fyi: 6,
4290
+ important: 8,
4291
+ critical: Infinity
4292
+ };
4288
4293
  function formatRelativeAge(createdAt, now = /* @__PURE__ */ new Date()) {
4289
4294
  const then = createdAt instanceof Date ? createdAt : new Date(createdAt);
4290
4295
  const seconds = Math.max(0, Math.floor((now.getTime() - then.getTime()) / 1e3));
@@ -4301,19 +4306,33 @@ function isNotificationSettled(item) {
4301
4306
 
4302
4307
  // src/components/notification-bell.tsx
4303
4308
  import * as React61 from "react";
4304
- import { BellRing, Settings } from "lucide-react";
4309
+ import { BellRing, Check as Check7, Settings } from "lucide-react";
4305
4310
  import { jsx as jsx74, jsxs as jsxs41 } from "react/jsx-runtime";
4311
+ function MarkReadButton({ title, onClick }) {
4312
+ return /* @__PURE__ */ jsx74(
4313
+ "button",
4314
+ {
4315
+ type: "button",
4316
+ "aria-label": `Mark "${title}" as read`,
4317
+ onClick,
4318
+ className: "flex size-5 shrink-0 items-center justify-center rounded text-text-tertiary opacity-0 transition-opacity hover:text-text-primary focus-visible:opacity-100 group-hover:opacity-100",
4319
+ children: /* @__PURE__ */ jsx74(Check7, { className: "size-3.5", "aria-hidden": "true" })
4320
+ }
4321
+ );
4322
+ }
4306
4323
  function BellRow({
4307
4324
  item,
4308
4325
  onItemClick,
4309
4326
  onActionClick,
4327
+ onMarkRead,
4310
4328
  formatAge
4311
4329
  }) {
4312
4330
  const settled = isNotificationSettled(item);
4313
4331
  const unread = !settled && !item.read;
4332
+ const markable = Boolean(onMarkRead) && unread && item.cls !== "ephemeral";
4314
4333
  const actions = settled ? [] : item.actions ?? [];
4315
4334
  const age = item.createdAt ? formatAge(item.createdAt instanceof Date ? item.createdAt : new Date(item.createdAt)) : null;
4316
- return /* @__PURE__ */ jsxs41("div", { className: cn("rounded-lg bg-bg-surface px-3 py-2.5", settled && "opacity-45"), children: [
4335
+ return /* @__PURE__ */ jsxs41("div", { className: cn("group rounded-lg bg-bg-surface px-3 py-2.5", settled && "opacity-45"), children: [
4317
4336
  /* @__PURE__ */ jsxs41("div", { className: "flex items-start gap-2.5", children: [
4318
4337
  /* @__PURE__ */ jsxs41("div", { className: "min-w-0 flex-1", children: [
4319
4338
  /* @__PURE__ */ jsx74(
@@ -4333,7 +4352,10 @@ function BellRow({
4333
4352
  item.receipt ? `, ${item.receipt}` : ""
4334
4353
  ] })
4335
4354
  ] }),
4336
- age && /* @__PURE__ */ jsx74("span", { className: "mt-px shrink-0 text-xs text-text-tertiary", children: age })
4355
+ (age || markable) && /* @__PURE__ */ jsxs41("div", { className: "mt-px flex shrink-0 items-center gap-1", children: [
4356
+ age && /* @__PURE__ */ jsx74("span", { className: "text-xs text-text-tertiary", children: age }),
4357
+ markable && /* @__PURE__ */ jsx74(MarkReadButton, { title: item.title, onClick: () => onMarkRead(item.id) })
4358
+ ] })
4337
4359
  ] }),
4338
4360
  (actions.length > 0 || item.deepLink) && /* @__PURE__ */ jsxs41("div", { className: "mt-2 flex flex-wrap items-center gap-x-4 gap-y-2", children: [
4339
4361
  actions.map((action) => {
@@ -4370,6 +4392,7 @@ var NotificationBell = React61.forwardRef(
4370
4392
  items,
4371
4393
  unreadCount,
4372
4394
  maxItems = 5,
4395
+ onMarkRead,
4373
4396
  onMarkAllRead,
4374
4397
  onItemClick,
4375
4398
  onActionClick,
@@ -4438,6 +4461,7 @@ var NotificationBell = React61.forwardRef(
4438
4461
  item,
4439
4462
  onItemClick,
4440
4463
  onActionClick,
4464
+ onMarkRead,
4441
4465
  formatAge: age
4442
4466
  },
4443
4467
  item.id
@@ -4459,24 +4483,41 @@ NotificationBell.displayName = "NotificationBell";
4459
4483
 
4460
4484
  // src/components/notification-center.tsx
4461
4485
  import * as React62 from "react";
4462
- import { ArrowUpRight, Check as Check7, Search as Search2 } from "lucide-react";
4486
+ import { ArrowUpRight, Check as Check8, Search as Search2 } from "lucide-react";
4463
4487
  import { jsx as jsx75, jsxs as jsxs42 } from "react/jsx-runtime";
4464
4488
  var chipBase = "rounded-lg px-3 py-1.5 text-sm font-medium transition-colors";
4465
4489
  var chipOn = "bg-bg-brand-subtle text-text-brand";
4466
4490
  var chipOff = "bg-bg-elevated text-text-secondary hover:text-text-primary";
4491
+ function MarkReadButton2({ title, onClick }) {
4492
+ return /* @__PURE__ */ jsx75(
4493
+ "button",
4494
+ {
4495
+ type: "button",
4496
+ "aria-label": `Mark "${title}" as read`,
4497
+ onClick,
4498
+ className: "flex size-5 shrink-0 items-center justify-center rounded text-text-tertiary opacity-0 transition-opacity hover:text-text-primary focus-visible:opacity-100 group-hover:opacity-100",
4499
+ children: /* @__PURE__ */ jsx75(Check8, { className: "size-3.5", "aria-hidden": "true" })
4500
+ }
4501
+ );
4502
+ }
4467
4503
  function CenterRow({
4468
4504
  item,
4469
4505
  onItemClick,
4470
4506
  onDeepLinkClick,
4507
+ onMarkRead,
4471
4508
  formatAge
4472
4509
  }) {
4473
4510
  const unread = !item.read;
4511
+ const markable = Boolean(onMarkRead) && unread && item.cls !== "ephemeral";
4474
4512
  const age = item.createdAt ? formatAge(item.createdAt instanceof Date ? item.createdAt : new Date(item.createdAt)) : null;
4475
- return /* @__PURE__ */ jsxs42("article", { className: "rounded-lg bg-bg-elevated px-4 py-3", children: [
4513
+ return /* @__PURE__ */ jsxs42("article", { className: "group rounded-lg bg-bg-elevated px-4 py-3", children: [
4476
4514
  /* @__PURE__ */ jsxs42("div", { className: "flex items-center gap-2", children: [
4477
4515
  /* @__PURE__ */ jsx75("span", { className: "text-xs font-semibold uppercase tracking-[0.09em] text-text-secondary", children: item.source }),
4478
4516
  /* @__PURE__ */ jsx75(Badge, { variant: severityBadgeVariant(item.severity), children: severityLabels[item.severity] }),
4479
- age && /* @__PURE__ */ jsx75("span", { className: "ml-auto shrink-0 text-xs tabular-nums text-text-tertiary", children: age })
4517
+ (age || markable) && /* @__PURE__ */ jsxs42("div", { className: "ml-auto flex shrink-0 items-center gap-1.5", children: [
4518
+ age && /* @__PURE__ */ jsx75("span", { className: "text-xs tabular-nums text-text-tertiary", children: age }),
4519
+ markable && /* @__PURE__ */ jsx75(MarkReadButton2, { title: item.title, onClick: () => onMarkRead(item.id) })
4520
+ ] })
4480
4521
  ] }),
4481
4522
  /* @__PURE__ */ jsx75(
4482
4523
  "button",
@@ -4493,7 +4534,7 @@ function CenterRow({
4493
4534
  item.body && /* @__PURE__ */ jsx75("p", { className: "mt-0.5 text-sm leading-snug text-text-secondary", children: item.body }),
4494
4535
  (item.receipt || item.deepLink) && /* @__PURE__ */ jsxs42("div", { className: "mt-2 flex flex-wrap items-center gap-x-4 gap-y-1.5", children: [
4495
4536
  item.receipt && /* @__PURE__ */ jsxs42("span", { className: "inline-flex items-center gap-1.5 text-sm text-text-tertiary", children: [
4496
- /* @__PURE__ */ jsx75(Check7, { className: "size-3 shrink-0 text-text-success", "aria-hidden": "true" }),
4537
+ /* @__PURE__ */ jsx75(Check8, { className: "size-3 shrink-0 text-text-success", "aria-hidden": "true" }),
4497
4538
  item.receipt
4498
4539
  ] }),
4499
4540
  item.deepLink && /* @__PURE__ */ jsxs42(
@@ -4523,6 +4564,7 @@ var NotificationCenter = React62.forwardRef(
4523
4564
  onItemClick,
4524
4565
  onDeepLinkClick,
4525
4566
  onMarkAllRead,
4567
+ onMarkRead,
4526
4568
  formatAge,
4527
4569
  className
4528
4570
  }, ref) => {
@@ -4644,6 +4686,7 @@ var NotificationCenter = React62.forwardRef(
4644
4686
  item,
4645
4687
  onItemClick,
4646
4688
  onDeepLinkClick,
4689
+ onMarkRead,
4647
4690
  formatAge: age
4648
4691
  },
4649
4692
  item.id
@@ -4871,6 +4914,184 @@ var NotificationSettings = React63.forwardRef(
4871
4914
  }
4872
4915
  );
4873
4916
  NotificationSettings.displayName = "NotificationSettings";
4917
+
4918
+ // src/components/notification-toast.tsx
4919
+ import * as React64 from "react";
4920
+ import { X as X3 } from "lucide-react";
4921
+ import { jsx as jsx77, jsxs as jsxs44 } from "react/jsx-runtime";
4922
+ var chipBase2 = "rounded-lg px-2.5 py-1 text-sm font-medium transition-colors";
4923
+ var chipPrimary = "bg-bg-brand text-text-on-brand hover:bg-bg-brand-hover";
4924
+ var chipDestructive = "bg-bg-elevated text-text-danger hover:bg-bg-surface";
4925
+ var linkAction = "text-sm font-semibold text-text-brand transition-colors hover:text-text-primary";
4926
+ var NotificationToast = React64.forwardRef(
4927
+ ({
4928
+ item,
4929
+ showSource = true,
4930
+ note,
4931
+ onItemClick,
4932
+ onAction,
4933
+ onDismiss,
4934
+ dismissLabel = "Dismiss",
4935
+ className
4936
+ }, ref) => {
4937
+ const actions = item.actions ?? [];
4938
+ return /* @__PURE__ */ jsxs44(
4939
+ "div",
4940
+ {
4941
+ ref,
4942
+ role: "status",
4943
+ "aria-live": item.severity === "critical" ? "assertive" : "polite",
4944
+ className: cn(
4945
+ "rounded-2xl border border-border-subtle bg-bg-elevated p-4 shadow-(--menu-shadow)",
4946
+ className
4947
+ ),
4948
+ children: [
4949
+ /* @__PURE__ */ jsxs44("div", { className: "flex items-center gap-2", children: [
4950
+ showSource && /* @__PURE__ */ jsx77("span", { className: "text-xs font-semibold uppercase tracking-[0.09em] text-text-secondary", children: item.source }),
4951
+ /* @__PURE__ */ jsx77(Badge, { variant: severityBadgeVariant(item.severity), children: severityLabels[item.severity] }),
4952
+ onDismiss && /* @__PURE__ */ jsx77(
4953
+ "button",
4954
+ {
4955
+ type: "button",
4956
+ "aria-label": dismissLabel,
4957
+ onClick: () => onDismiss(item),
4958
+ className: "-mr-1 ml-auto flex size-5 shrink-0 items-center justify-center rounded text-text-tertiary transition-colors hover:bg-bg-surface hover:text-text-primary",
4959
+ children: /* @__PURE__ */ jsx77(X3, { className: "size-3.5", "aria-hidden": "true" })
4960
+ }
4961
+ )
4962
+ ] }),
4963
+ onItemClick ? /* @__PURE__ */ jsx77(
4964
+ "button",
4965
+ {
4966
+ type: "button",
4967
+ onClick: () => onItemClick(item),
4968
+ className: "mt-1 block text-left text-sm font-semibold leading-snug text-text-primary underline-offset-2 hover:underline",
4969
+ children: item.title
4970
+ }
4971
+ ) : /* @__PURE__ */ jsx77("p", { className: "mt-1 text-sm font-semibold leading-snug text-text-primary", children: item.title }),
4972
+ item.body && /* @__PURE__ */ jsx77("p", { className: "mt-0.5 text-sm leading-snug text-text-secondary", children: item.body }),
4973
+ note && /* @__PURE__ */ jsx77("p", { className: "mt-1.5 text-xs font-medium text-text-warning", children: note }),
4974
+ actions.length > 0 && /* @__PURE__ */ jsx77("div", { className: "mt-2.5 flex flex-wrap items-center gap-2", children: actions.map((action) => {
4975
+ const variant = action.variant ?? "link";
4976
+ return /* @__PURE__ */ jsx77(
4977
+ "button",
4978
+ {
4979
+ type: "button",
4980
+ onClick: () => onAction?.(action, item),
4981
+ className: variant === "primary" ? cn(chipBase2, chipPrimary) : variant === "destructive" ? cn(chipBase2, chipDestructive) : linkAction,
4982
+ children: action.label
4983
+ },
4984
+ action.id
4985
+ );
4986
+ }) })
4987
+ ]
4988
+ }
4989
+ );
4990
+ }
4991
+ );
4992
+ NotificationToast.displayName = "NotificationToast";
4993
+
4994
+ // src/components/notification-toaster.tsx
4995
+ import * as React65 from "react";
4996
+ import { jsx as jsx78 } from "react/jsx-runtime";
4997
+ var positions = {
4998
+ "bottom-right": "bottom-6 right-6 flex-col",
4999
+ "bottom-left": "bottom-6 left-6 flex-col",
5000
+ "top-right": "top-6 right-6 flex-col-reverse",
5001
+ "top-left": "top-6 left-6 flex-col-reverse"
5002
+ };
5003
+ var NotificationToaster = React65.forwardRef(
5004
+ ({
5005
+ items,
5006
+ dwell,
5007
+ max = 4,
5008
+ position = "bottom-right",
5009
+ pauseOnHover = true,
5010
+ onDismiss,
5011
+ onItemClick,
5012
+ onAction,
5013
+ renderNote,
5014
+ className
5015
+ }, ref) => {
5016
+ const shown = items.slice(0, max);
5017
+ const [paused, setPaused] = React65.useState(false);
5018
+ const onDismissRef = React65.useRef(onDismiss);
5019
+ onDismissRef.current = onDismiss;
5020
+ const fyiDwell = dwell?.fyi;
5021
+ const importantDwell = dwell?.important;
5022
+ const criticalDwell = dwell?.critical;
5023
+ const remaining = React65.useRef(/* @__PURE__ */ new Map());
5024
+ const liveIds = shown.map((item) => `${item.id}:${item.severity}`).join(" ");
5025
+ React65.useEffect(() => {
5026
+ const owedById = remaining.current;
5027
+ const dwellSeconds = {
5028
+ fyi: fyiDwell ?? TOAST_DWELL.fyi,
5029
+ important: importantDwell ?? TOAST_DWELL.important,
5030
+ critical: criticalDwell ?? TOAST_DWELL.critical
5031
+ };
5032
+ const live = liveIds === "" ? [] : liveIds.split(" ");
5033
+ const ids = new Set(live.map((entry) => entry.slice(0, entry.lastIndexOf(":"))));
5034
+ for (const id of owedById.keys()) {
5035
+ if (!ids.has(id)) owedById.delete(id);
5036
+ }
5037
+ for (const entry of live) {
5038
+ const at = entry.lastIndexOf(":");
5039
+ const id = entry.slice(0, at);
5040
+ const severity = entry.slice(at + 1);
5041
+ if (!owedById.has(id)) {
5042
+ owedById.set(id, dwellSeconds[severity] * 1e3);
5043
+ }
5044
+ }
5045
+ if (paused) return;
5046
+ const startedAt = Date.now();
5047
+ const timers = [];
5048
+ for (const [id, owed] of owedById) {
5049
+ if (!Number.isFinite(owed)) continue;
5050
+ timers.push(
5051
+ setTimeout(() => {
5052
+ owedById.delete(id);
5053
+ onDismissRef.current?.(id);
5054
+ }, owed)
5055
+ );
5056
+ }
5057
+ return () => {
5058
+ timers.forEach(clearTimeout);
5059
+ const elapsed = Date.now() - startedAt;
5060
+ for (const [id, owed] of owedById) {
5061
+ if (Number.isFinite(owed)) owedById.set(id, Math.max(0, owed - elapsed));
5062
+ }
5063
+ };
5064
+ }, [liveIds, paused, fyiDwell, importantDwell, criticalDwell]);
5065
+ if (shown.length === 0) return null;
5066
+ return /* @__PURE__ */ jsx78(
5067
+ "div",
5068
+ {
5069
+ ref,
5070
+ role: "region",
5071
+ "aria-label": "Notifications",
5072
+ onMouseEnter: pauseOnHover ? () => setPaused(true) : void 0,
5073
+ onMouseLeave: pauseOnHover ? () => setPaused(false) : void 0,
5074
+ className: cn(
5075
+ "pointer-events-none fixed z-50 flex w-[350px] gap-3",
5076
+ positions[position],
5077
+ className
5078
+ ),
5079
+ children: shown.map((item, i) => /* @__PURE__ */ jsx78("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx78(
5080
+ NotificationToast,
5081
+ {
5082
+ item,
5083
+ showSource: i === 0 || shown[i - 1].source !== item.source,
5084
+ note: renderNote?.(item),
5085
+ onItemClick,
5086
+ onAction,
5087
+ onDismiss: onDismiss ? () => onDismiss(item.id) : void 0
5088
+ }
5089
+ ) }, item.id))
5090
+ }
5091
+ );
5092
+ }
5093
+ );
5094
+ NotificationToaster.displayName = "NotificationToaster";
4874
5095
  export {
4875
5096
  Accordion,
4876
5097
  AccordionContent,
@@ -5044,6 +5265,8 @@ export {
5044
5265
  NotificationBell,
5045
5266
  NotificationCenter,
5046
5267
  NotificationSettings,
5268
+ NotificationToast,
5269
+ NotificationToaster,
5047
5270
  OrgProductSwitcher,
5048
5271
  Pagination,
5049
5272
  PaginationContent,
@@ -5086,6 +5309,7 @@ export {
5086
5309
  StatCard,
5087
5310
  StatusDot,
5088
5311
  Switch,
5312
+ TOAST_DWELL,
5089
5313
  Table,
5090
5314
  TableBody,
5091
5315
  TableCaption,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessly/ui",
3
- "version": "0.14.1",
3
+ "version": "0.16.0",
4
4
  "description": "Lessly design system — shared UI primitives, tokens, and theme",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.30.3",