@cl-light-ds/ui 0.4.0 → 0.6.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.
Files changed (127) hide show
  1. package/package.json +19 -17
  2. package/src/components/accordion/AccordionTrigger.astro +5 -1
  3. package/src/components/accordion/CHANGELOG.md +9 -0
  4. package/src/components/alert/Alert.astro +42 -3
  5. package/src/components/alert/CHANGELOG.md +19 -0
  6. package/src/components/button/Button.astro +78 -19
  7. package/src/components/button/CHANGELOG.md +22 -0
  8. package/src/components/button-group/ButtonGroup.astro +14 -3
  9. package/src/components/button-group/CHANGELOG.md +17 -0
  10. package/src/components/card/CHANGELOG.md +9 -0
  11. package/src/components/card/Card.astro +22 -0
  12. package/src/components/collapsible/CHANGELOG.md +9 -0
  13. package/src/components/collapsible/CollapsibleTrigger.astro +5 -1
  14. package/src/components/combobox/CHANGELOG.md +27 -0
  15. package/src/components/combobox/Combobox.astro +18 -1
  16. package/src/components/combobox/ComboboxTrigger.astro +32 -2
  17. package/src/components/command-search/CHANGELOG.md +57 -0
  18. package/src/components/command-search/CommandSearch.astro +594 -0
  19. package/src/components/command-search/CommandSearchGroup.astro +43 -0
  20. package/src/components/command-search/CommandSearchItem.astro +64 -0
  21. package/src/components/command-search/classes.ts +18 -0
  22. package/src/components/command-search/index.ts +12 -0
  23. package/src/components/command-search/nothing-matches.svg +42 -0
  24. package/src/components/dialog/CHANGELOG.md +50 -0
  25. package/src/components/dialog/Dialog.astro +14 -3
  26. package/src/components/dialog/DialogClose.astro +28 -12
  27. package/src/components/dialog/DialogContent.astro +19 -1
  28. package/src/components/dialog/DialogTrigger.astro +5 -1
  29. package/src/components/dropdown-menu/CHANGELOG.md +32 -0
  30. package/src/components/dropdown-menu/DropdownMenu.astro +13 -3
  31. package/src/components/dropdown-menu/DropdownMenuRadioGroup.astro +17 -2
  32. package/src/components/dropdown-menu/DropdownMenuTrigger.astro +7 -1
  33. package/src/components/dropdown-menu/dropdown-menu-item-variants.ts +1 -1
  34. package/src/components/empty-state/CHANGELOG.md +18 -0
  35. package/src/components/empty-state/EmptyState.astro +157 -0
  36. package/src/components/empty-state/illustrations/AllCaughtUp.astro +52 -0
  37. package/src/components/empty-state/illustrations/EmptyWorkspaces.astro +56 -0
  38. package/src/components/empty-state/illustrations/LoadError.astro +61 -0
  39. package/src/components/empty-state/illustrations/NoResults.astro +60 -0
  40. package/src/components/empty-state/illustrations/NothingMatches.astro +66 -0
  41. package/src/components/empty-state/illustrations/index.ts +27 -0
  42. package/src/components/empty-state/index.ts +7 -0
  43. package/src/components/favorite-toggle/CHANGELOG.md +13 -0
  44. package/src/components/favorite-toggle/FavoriteToggle.astro +94 -0
  45. package/src/components/favorite-toggle/index.ts +1 -0
  46. package/src/components/field/CHANGELOG.md +18 -0
  47. package/src/components/field/Field.astro +18 -0
  48. package/src/components/field/FieldError.astro +13 -1
  49. package/src/components/field/FieldGroup.astro +12 -3
  50. package/src/components/hover-card/CHANGELOG.md +9 -0
  51. package/src/components/hover-card/HoverCardTrigger.astro +5 -1
  52. package/src/components/input-group/CHANGELOG.md +7 -0
  53. package/src/components/input-group/InputGroup.astro +2 -0
  54. package/src/components/loading-overlay/CHANGELOG.md +25 -0
  55. package/src/components/loading-overlay/LoadingOverlay.astro +179 -0
  56. package/src/components/loading-overlay/index.ts +8 -0
  57. package/src/components/notifications-menu/CHANGELOG.md +29 -0
  58. package/src/components/notifications-menu/NotificationsMenu.astro +375 -0
  59. package/src/components/notifications-menu/NotificationsMenuItem.astro +130 -0
  60. package/src/components/notifications-menu/all-caught-up.svg +28 -0
  61. package/src/components/notifications-menu/index.ts +3 -0
  62. package/src/components/notifications-menu/types.ts +32 -0
  63. package/src/components/option-card/CHANGELOG.md +26 -0
  64. package/src/components/option-card/OptionCard.astro +143 -0
  65. package/src/components/option-card/index.ts +1 -0
  66. package/src/components/organisms/top-bar/CHANGELOG.md +27 -0
  67. package/src/components/organisms/top-bar/TopBar.astro +257 -0
  68. package/src/components/organisms/top-bar/index.ts +2 -0
  69. package/src/components/organisms/top-bar/types.ts +30 -0
  70. package/src/components/organisms/workspace/CHANGELOG.md +80 -0
  71. package/src/components/organisms/workspace/OrganisationOverview.astro +62 -0
  72. package/src/components/organisms/workspace/RecentWorkspaceCard.astro +235 -0
  73. package/src/components/organisms/workspace/RecentWorkspaces.astro +93 -0
  74. package/src/components/organisms/workspace/WorkspaceAvatar.astro +144 -0
  75. package/src/components/organisms/workspace/WorkspaceCard.astro +172 -0
  76. package/src/components/organisms/workspace/WorkspaceGlyph.astro +62 -0
  77. package/src/components/organisms/workspace/WorkspaceIcon.astro +42 -0
  78. package/src/components/organisms/workspace/WorkspaceIconPicker.astro +288 -0
  79. package/src/components/organisms/workspace/WorkspaceIconPickerGlyph.astro +44 -0
  80. package/src/components/organisms/workspace/icons.ts +98 -0
  81. package/src/components/organisms/workspace/index.ts +35 -0
  82. package/src/components/organisms/workspace/labels.ts +54 -0
  83. package/src/components/organisms/workspace/recent-card-rings-strong.png +0 -0
  84. package/src/components/organisms/workspace/types.ts +30 -0
  85. package/src/components/popover/CHANGELOG.md +14 -0
  86. package/src/components/popover/PopoverClose.astro +2 -1
  87. package/src/components/popover/PopoverTrigger.astro +7 -1
  88. package/src/components/profile-menu/CHANGELOG.md +22 -0
  89. package/src/components/profile-menu/ProfileMenu.astro +236 -0
  90. package/src/components/profile-menu/index.ts +2 -0
  91. package/src/components/profile-menu/types.ts +27 -0
  92. package/src/components/search-field/CHANGELOG.md +14 -0
  93. package/src/components/search-field/SearchField.astro +163 -0
  94. package/src/components/search-field/index.ts +1 -0
  95. package/src/components/select/CHANGELOG.md +8 -0
  96. package/src/components/select/SelectTrigger.astro +1 -1
  97. package/src/components/table/CHANGELOG.md +15 -0
  98. package/src/components/table/TableCell.astro +6 -0
  99. package/src/components/table/TableEditableCell.astro +2 -2
  100. package/src/components/table/TableHead.astro +5 -1
  101. package/src/components/table/TablePagination.astro +4 -4
  102. package/src/components/table/TableRow.astro +3 -1
  103. package/src/components/table/table-cell-variants.ts +2 -0
  104. package/src/components/tabs/CHANGELOG.md +16 -0
  105. package/src/components/tabs/Tabs.astro +26 -0
  106. package/src/components/tabs/TabsList.astro +30 -3
  107. package/src/components/toast/CHANGELOG.md +32 -0
  108. package/src/components/toast/Toast.astro +145 -0
  109. package/src/components/toast/Toaster.astro +322 -0
  110. package/src/components/toast/index.ts +9 -0
  111. package/src/components/toggle/CHANGELOG.md +7 -0
  112. package/src/components/toggle-group/CHANGELOG.md +11 -0
  113. package/src/components/toggle-group/ToggleGroup.astro +19 -2
  114. package/src/components/toggle-group/ToggleGroupItem.astro +10 -0
  115. package/src/components/tooltip/CHANGELOG.md +26 -0
  116. package/src/components/tooltip/Tooltip.astro +31 -3
  117. package/src/components/tooltip/TooltipTrigger.astro +5 -1
  118. package/src/lib/command-search.ts +78 -0
  119. package/src/lib/dialog-focus.ts +73 -0
  120. package/src/lib/dropdown-menu-radio-groups.ts +79 -0
  121. package/src/lib/loading-overlay.ts +174 -0
  122. package/src/lib/scroll-lock.ts +97 -0
  123. package/src/lib/toast.ts +127 -0
  124. package/src/lib/toggle-shared.ts +9 -1
  125. package/src/lib/workspace-palette.ts +119 -0
  126. package/src/styles/CHANGELOG.md +35 -0
  127. package/src/styles/globals.css +109 -0
@@ -0,0 +1,130 @@
1
+ ---
2
+ /**
3
+ * @component NotificationsMenuItem
4
+ * @title Notifications Menu Item
5
+ * @description One row of a NotificationsMenu: avatar or icon, title, text, a "context · time" line, a blue "new" dot and a ✕ to dismiss it.
6
+ *
7
+ * @usage
8
+ *
9
+ * ```astro nocollapse
10
+ * <NotificationsMenuItem id="n1" title="Anna commented" text="Looks good to me" context="Northwind" time="2 min ago" initials="AL" new />
11
+ *
12
+ * <NotificationsMenuItem id="n2" title="Your export is ready" icon={CircleCheck} tone="success" time="Just now" new>
13
+ * <Button as="a" href="/export.zip" download variant="outline" size="sm">Download</Button>
14
+ * </NotificationsMenuItem>
15
+ * ```
16
+ *
17
+ * Use it inside `NotificationsMenu`'s default slot, or let `NotificationsMenu` render it from its `notifications` prop.
18
+ * The default slot holds extra content under the text (e.g. an action button); the `media` slot replaces the avatar / icon.
19
+ */
20
+
21
+ import type { HTMLAttributes } from "astro/types";
22
+ import { X } from "@lucide/astro";
23
+ import { cva } from "class-variance-authority";
24
+ import { cn } from "@cl-light-ds/ui/lib/utils";
25
+ import { Avatar, AvatarFallback, AvatarImage } from "../avatar";
26
+ import { Button } from "../button";
27
+ import type { NotificationsMenuItemData } from "./types";
28
+
29
+ type Props = Omit<HTMLAttributes<"li">, "id" | "title"> &
30
+ NotificationsMenuItemData & {
31
+ /** Accessible name of the ✕. `{title}` is replaced by the notification's title. */
32
+ dismissLabel?: string;
33
+ /** Screen-reader text after a new notification. */
34
+ newLabel?: string;
35
+ class?: string;
36
+ };
37
+
38
+ const {
39
+ id,
40
+ title,
41
+ text,
42
+ context,
43
+ time,
44
+ datetime,
45
+ initials,
46
+ avatarSrc,
47
+ icon: Icon,
48
+ tone = "default",
49
+ new: isNew = false,
50
+ dismissLabel = "Dismiss: {title}",
51
+ newLabel = "(new)",
52
+ class: className = "",
53
+ ...rest
54
+ } = Astro.props as Props;
55
+
56
+ const hasMedia = Astro.slots.has("media");
57
+ const hasExtra = Astro.slots.has("default");
58
+
59
+ const iconChipVariants = cva(
60
+ "bg-muted flex size-6 shrink-0 items-center justify-center rounded-full [&_svg]:size-3.5",
61
+ {
62
+ variants: {
63
+ tone: {
64
+ default: "text-muted-foreground",
65
+ success: "text-success",
66
+ warning: "text-warning",
67
+ destructive: "text-destructive",
68
+ },
69
+ },
70
+ defaultVariants: { tone: "default" },
71
+ },
72
+ );
73
+ ---
74
+
75
+ <li
76
+ {...rest}
77
+ data-slot="notifications-menu-item"
78
+ data-notification={id}
79
+ data-new={isNew ? "true" : "false"}
80
+ class={cn(
81
+ "group/note relative flex items-start gap-3 border-b border-border p-3 pe-9 transition-colors last:border-b-0",
82
+ className,
83
+ )}
84
+ >
85
+ {
86
+ hasMedia ? (
87
+ <slot name="media" />
88
+ ) : initials || avatarSrc ? (
89
+ <Avatar size="sm" variant={avatarSrc ? "image" : "initials"}>
90
+ {avatarSrc && <AvatarImage src={avatarSrc} alt="" />}
91
+ <AvatarFallback>{initials}</AvatarFallback>
92
+ </Avatar>
93
+ ) : Icon ? (
94
+ <span class={iconChipVariants({ tone })} aria-hidden="true">
95
+ <Icon />
96
+ </span>
97
+ ) : null
98
+ }
99
+ <span class={cn("flex min-w-0 flex-1 flex-col", hasExtra ? "gap-1" : "gap-0.5")}>
100
+ <span data-slot="notifications-menu-item-title" class="text-foreground text-sm font-medium">{title}</span>
101
+ {text && <span class="text-muted-foreground text-xs">{text}</span>}
102
+ {hasExtra && <span data-slot="notifications-menu-item-extra"><slot /></span>}
103
+ {
104
+ (context || time) && (
105
+ <span class="text-muted-foreground/70 text-xs">
106
+ {context}
107
+ {context && time && " · "}
108
+ {time && (datetime ? <time datetime={datetime}>{time}</time> : time)}
109
+ </span>
110
+ )
111
+ }
112
+ </span>
113
+ {/* The "new" dot: centred vertically, clear of the ✕ (D154). Never alone: sr-only text too. */}
114
+ <span
115
+ data-slot="notifications-menu-item-dot"
116
+ class="bg-primary absolute top-1/2 right-3.5 hidden size-2 -translate-y-1/2 rounded-full group-focus-within/note:hidden group-hover/note:hidden group-data-[new=true]/note:block"
117
+ aria-hidden="true"
118
+ ></span>
119
+ <span class="sr-only group-data-[new=false]/note:hidden">{newLabel}</span>
120
+ <Button
121
+ type="button"
122
+ variant="ghost"
123
+ size="icon-xs"
124
+ class="absolute top-2 right-2 opacity-0 group-focus-within/note:opacity-100 group-hover/note:opacity-100 pointer-coarse:opacity-100"
125
+ aria-label={dismissLabel.replace("{title}", title)}
126
+ data-notifications-dismiss
127
+ >
128
+ <X />
129
+ </Button>
130
+ </li>
@@ -0,0 +1,28 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="144" height="104" viewBox="-12 -12 144 104" fill="none" xmlns:c2pa="http://c2pa.org/manifest"><metadata><c2pa:manifest>AAAWgmp1bWIAAAAeanVtZGMycGEAEQAQgAAAqgA4m3EDYzJwYQAAABZcanVtYgAAAEdqdW1kYzJtYQARABCAAACqADibcQN1cm46YzJwYTo1ZTFjODE4MC05NTYxLTQ3YWYtOGYwMy0zNWJmODU4YjViN2EAAAADl2p1bWIAAAApanVtZGMyYXMAEQAQgAAAqgA4m3EDYzJwYS5hc3NlcnRpb25zAAAAALxqdW1iAAAARGp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuaW5ncmVkaWVudC52MwAAAAAYYzJzaC+NPxB7N/p9adnc2qsfPxgAAABwY2JvcqNpZGM6Zm9ybWF0bWltYWdlL3N2Zyt4bWxqaW5zdGFuY2VJRHgseG1wOmlpZDpjYjNhMTNlZS1kN2NlLTQyNGYtODBiYi1mM2E3MjEwNjRhZmZscmVsYXRpb25zaGlwaHBhcmVudE9mAAAB4mp1bWIAAABBanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5hY3Rpb25zLnYyAAAAABhjMnNotRHSjZb1m13eqMxg3CrdWwAAAZljYm9yomdhY3Rpb25zgqJmYWN0aW9ua2MycGEub3BlbmVkanBhcmFtZXRlcnOha2luZ3JlZGllbnRzgaJjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFggaByci08iEbo8a7fyckB98mnsuZspV6glutU6CRYHq7ikZmFjdGlvbngdY29tLmFudGhyb3BpYy5jbGF1ZGUucHJvdmlkZWRqcGFyYW1ldGVyc6F4H2NvbS5hbnRocm9waWMub3JpZ2luLWNvbmZpZGVuY2VndW5rbm93bmtkZXNjcmlwdGlvbnhmQ2xhdWRlIHByb3ZpZGVkIHRoaXMgZmlsZSBhdCB0aGUgcmVxdWVzdCBvZiBhIHVzZXIgYW5kIG1heSBoYXZlIGNyZWF0ZWQgb3IgbW9kaWZpZWQgdGhlIGZpbGUgY29udGVudHMubXNvZnR3YXJlQWdlbnShZG5hbWVmQ2xhdWRlcmFsbEFjdGlvbnNJbmNsdWRlZPUAAADIanVtYgAAAEBqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmhhc2guZGF0YQAAAAAYYzJzaCE4XFoYfQdK2oaIn4KqoaMAAACAY2JvcqVjYWxnZnNoYTI1NmNwYWRNAAAAAAAAAAAAAAAAAGRoYXNoWCBwyJmeK42jGvzuOwLqXoDrEDyRkMXdC7jb6T0G7aNtl2RuYW1lbmp1bWJmIG1hbmlmZXN0amV4Y2x1c2lvbnOBomVzdGFydBimZmxlbmd0aBkeBAAAAj5qdW1iAAAAJ2p1bWRjMmNsABEAEIAAAKoAOJtxA2MycGEuY2xhaW0udjIAAAACD2Nib3KlY2FsZ2ZzaGEyNTZpc2lnbmF0dXJleE1zZWxmI2p1bWJmPS9jMnBhL3VybjpjMnBhOjVlMWM4MTgwLTk1NjEtNDdhZi04ZjAzLTM1YmY4NThiNWI3YS9jMnBhLnNpZ25hdHVyZWppbnN0YW5jZUlEeCx4bXA6aWlkOjQwNzRmMDY1LWM3ZGEtNDE5My04MmJkLTE5MTI2NjA1M2NkMnJjcmVhdGVkX2Fzc2VydGlvbnODomN1cmx4LXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaW5ncmVkaWVudC52M2RoYXNoWCBoHJyLTyIRujxrt/JyQH3yaey5mylXqCW61ToJFgeruKJjdXJseCpzZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmFjdGlvbnMudjJkaGFzaFggDoC/w970Rz1Igqx1EVS7RMxcJhZaAy+qJS5QZcSbvbqiY3VybHgpc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5oYXNoLmRhdGFkaGFzaFggNp2qc1mvgSvJEClX2ZNnwW8h6YEISoyFzEQlrAgN7hl0Y2xhaW1fZ2VuZXJhdG9yX2luZm+jZG5hbWVvQW50aHJvcGljIEZpbGVzZ3ZlcnNpb25lMS4wLjBrc3BlY1ZlcnNpb25lMi40LjAAABA4anVtYgAAAChqdW1kYzJjcwARABCAAACqADibcQNjMnBhLnNpZ25hdHVyZQAAABAIY2JvctKEWQISogEmGCFZAgowggIGMIIBjaADAgECAhRA5aAK7sI50L64g/oGQgU9Z1UTADAKBggqhkjOPQQDAzBJMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEuMCwGA1UEAxMlQW50aHJvcGljIENvbnRlbnQgQ3JlZGVudGlhbHMgUm9vdCBDQTAeFw0yNjA4MDcxODQzNTZaFw0yODA4MDYxOTQzNTZaMEQxFzAVBgNVBAoTDkFudGhyb3BpYywgUEJDMSkwJwYDVQQDEyBBbnRocm9waWMgQ2xhdWRlIENvbnRlbnQgU2lnbmluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJh6CmvLUBgFFNU0vUKlOVtE6djd17L5SuwX0LemFisBM3dkd/3cyjxFA3Qo5S46fX0/ihY0VZ7mfb9KF703t5OjWDBWMA4GA1UdDwEB/wQEAwIHgDAVBgNVHSUEDjAMBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUzlHiBIFOZFsj+OPEz5o+nMHXXMIwCgYIKoZIzj0EAwMDZwAwZAIwMXMdFJ4BetLLVY7ORuE9noqbbAZOZn/aArXyTwFAZfKrPzxF2vPoJNf1+UCdg1XGAjBwX1zd9WGqYkqmL5SFqw1QySjr1zJfpJM9+1rdDwSPLMOPOjKuiXjoU/pUUeG9RwmhY3BhZFkNngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPZYQOsPa7KlN7A+bxSZMbiwEHyIQO5N1z1/LriHBGTnTLVR2U8DysV/pK+fSfYixl2JUMqvt8cAN/cU5VG46RwGmuI=</c2pa:manifest></metadata>
2
+ <defs>
3
+ <filter id="sBack" x="-30%" y="-40%" width="160%" height="190%" color-interpolation-filters="sRGB">
4
+ <feDropShadow dx="0" dy="1.5" stdDeviation="2" flood-color="#051729" flood-opacity="0.05"></feDropShadow>
5
+ </filter>
6
+ <filter id="sFront" x="-40%" y="-40%" width="180%" height="190%" color-interpolation-filters="sRGB">
7
+ <feDropShadow dx="0" dy="5" stdDeviation="6" flood-color="#051729" flood-opacity="0.10"></feDropShadow>
8
+ </filter>
9
+ <filter id="sCheck" x="-60%" y="-60%" width="220%" height="220%" color-interpolation-filters="sRGB">
10
+ <feDropShadow dx="0" dy="3" stdDeviation="3" flood-color="#3785E0" flood-opacity="0.32"></feDropShadow>
11
+ </filter>
12
+ </defs>
13
+ <circle cx="60" cy="40" r="35" stroke="#F5F9FE" stroke-width="10"></circle>
14
+ <circle cx="60" cy="40" r="19" stroke="#EBF3FC" stroke-width="8"></circle>
15
+ <g filter="url(#sBack)"><rect x="22" y="12" width="76" height="18" rx="6" fill="#FFFFFF"></rect></g>
16
+ <rect x="22.5" y="12.5" width="75" height="17" rx="5.5" stroke="#09090B" stroke-opacity="0.06"></rect>
17
+ <circle cx="31" cy="21" r="4" fill="#F4F4F5"></circle>
18
+ <rect x="39" y="19" width="30" height="4" rx="2" fill="#F4F4F5"></rect>
19
+ <g filter="url(#sFront)"><rect x="16" y="34" width="88" height="24" rx="7" fill="#FFFFFF"></rect></g>
20
+ <rect x="16.5" y="34.5" width="87" height="23" rx="6.5" stroke="#09090B" stroke-opacity="0.06"></rect>
21
+ <circle cx="28" cy="46" r="6" fill="#EBF3FC"></circle>
22
+ <path d="M25.5 45.5 a2.5 2.5 0 0 1 5 0 c0 2.2 1 3 1 3 h-7 s1 -0.8 1 -3 Z M27.2 50.2 a1 1 0 0 0 1.6 0" stroke="#3785E0" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"></path>
23
+ <rect x="39" y="41" width="40" height="4" rx="2" fill="#E4E4E7"></rect>
24
+ <rect x="39" y="48.5" width="26" height="3.5" rx="1.75" fill="#F4F4F5"></rect>
25
+ <g filter="url(#sCheck)"><circle cx="100" cy="58" r="11" fill="#3785E0"></circle></g>
26
+ <circle cx="100" cy="58" r="11" stroke="#FFFFFF" stroke-width="2.5"></circle>
27
+ <path d="M95.5 58.2 l3 3 l6 -6" stroke="#FFFFFF" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"></path>
28
+ </svg>
@@ -0,0 +1,3 @@
1
+ export { default as NotificationsMenu } from "./NotificationsMenu.astro";
2
+ export { default as NotificationsMenuItem } from "./NotificationsMenuItem.astro";
3
+ export * from "./types";
@@ -0,0 +1,32 @@
1
+ import type { AstroComponent } from "@lucide/astro";
2
+
3
+ /** Colour of a notification's icon chip. */
4
+ export type NotificationTone = "default" | "success" | "warning" | "destructive";
5
+
6
+ /**
7
+ * One notification. Everything except `icon` is JSON-serialisable, so a page
8
+ * can pass an API response straight into `<NotificationsMenu notifications={…} />`.
9
+ */
10
+ export interface NotificationsMenuItemData {
11
+ /** Stable id. Sent back in `notifications:dismiss` / `notifications:seen`. */
12
+ id: string;
13
+ title: string;
14
+ /** Second line, muted. */
15
+ text?: string;
16
+ /** Where it happened (a workspace, project, team…). Shown as "context · time". */
17
+ context?: string;
18
+ /** Display time, e.g. "2 min ago". */
19
+ time?: string;
20
+ /** Machine-readable time for the `<time>` element (ISO 8601). */
21
+ datetime?: string;
22
+ /** Initials for an Avatar. Takes precedence over `icon`. */
23
+ initials?: string;
24
+ /** Avatar image (with `initials` as its fallback). */
25
+ avatarSrc?: string;
26
+ /** A Lucide icon, shown in a round chip when there is no avatar. */
27
+ icon?: AstroComponent;
28
+ /** Colour of the icon chip. Default `"default"` (muted). */
29
+ tone?: NotificationTone;
30
+ /** Unseen: shows the blue dot and counts towards the bell's red dot. */
31
+ new?: boolean;
32
+ }
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `OptionCard` component will be documented in this file.
4
+
5
+ ## Date 15th Oct 2026
6
+ ## Workspace selection handover (C19)
7
+
8
+ ### Fixed
9
+
10
+ - Accent badges inside an `OptionCard` (the `badge` slot and the nested content) now get a `chart-1` border from the card itself (`[&_[data-slot=badge][data-variant=accent]]:border-chart-1`). The accent badge is bordered in its own fill, the same pale blue as a chosen card's tint, so "Recommended" and "Linked" disappeared on a chosen card (D109). Consumers no longer add `class="border-chart-1"`; it still works if left in.
11
+ - Not opt-in: it only touches accent badges inside an option card, and only their border colour, to keep them visible. Badges elsewhere, and other badge variants, are unchanged.
12
+ - Always on, not only while the card is chosen: the reference gives these badges the darker edge on every card, and the badge doesn't change as the choice moves between cards.
13
+ - Storybook: the badges in the previews no longer carry the class.
14
+
15
+ ## Date 15th Oct 2026
16
+ ## Workspace selection handover (N18)
17
+
18
+ ### Added
19
+
20
+ - `OptionCard`: a radio choice as a card, for use inside `RadioGroup` (which owns the value, roving focus, arrow keys, `role="radio"`/`aria-checked` and the hidden form input).
21
+ - Radio, bold title, optional `badge` slot beside it, muted description. `title`/`description` props, or `title`/`description` slots for rich text.
22
+ - Selected (`data-checked` on its radio): `border-primary` on `badge-accent` at 50%. No JavaScript of its own; the state is read with `has-[]` from the runtime's attributes.
23
+ - Optional nested content (default slot), shown only while the card is chosen, outside the `<label>` so it can hold its own controls. `content="inset"` (notes under the description after a divider, as Duplicate's link details), `"panel"` (white bordered panel, as Export's checklist) or `"plain"`.
24
+ - `disabled` dims the card and disables its radio; `id` goes on the radio item.
25
+ - Accent badges on a chosen card blend into its tint; give them `class="border-chart-1"` (C19). Not applied automatically, since `Badge` exposes no variant hook.
26
+ - Storybook: "Option Card" under Forms.
@@ -0,0 +1,143 @@
1
+ ---
2
+ /**
3
+ * @component OptionCard
4
+ * @title Option Card
5
+ * @description A radio choice drawn as a card: radio, bold title (with an optional badge), description. The chosen card has a primary border on the accent tint, and can open more content inside it. Goes inside a `RadioGroup`, which owns selection, keyboard and ARIA.
6
+ *
7
+ * @preview
8
+ *
9
+ * <RadioGroup name="mode" defaultValue="copy" aria-label="How content is brought across" class="gap-3">
10
+ * <OptionCard value="copy" title="Make independent copies" description="The new workspace gets its own copies.">
11
+ * <Badge slot="badge" variant="accent" size="sm">Recommended</Badge>
12
+ * </OptionCard>
13
+ * <OptionCard value="link" title="Link to the original" description="Any change made in either workspace appears in both." />
14
+ * </RadioGroup>
15
+ *
16
+ * @usage
17
+ *
18
+ * ```astro nocollapse
19
+ * ---
20
+ * import { RadioGroup } from '@cl-light-ds/ui/components/radio-group';
21
+ * import { OptionCard } from '@cl-light-ds/ui/components/option-card';
22
+ * ---
23
+ *
24
+ * <RadioGroup name="export" defaultValue="full" aria-label="What to export" class="gap-3">
25
+ * <OptionCard value="full" title="Full workspace" description="Recordings, settings and team members." />
26
+ * <OptionCard value="choose" title="Choose what to include" description="Pick exactly which parts to export." content="panel">
27
+ * <!-- shown only while this option is chosen -->
28
+ * </OptionCard>
29
+ * </RadioGroup>
30
+ * ```
31
+ *
32
+ * The title, badge and description are the radio's `<label>`, so the whole
33
+ * top of the card selects it. The default slot is outside the label (it may
34
+ * hold its own controls, e.g. checkboxes) and is shown only while the card is
35
+ * chosen:
36
+ *
37
+ * - `content="inset"`: lined up under the description, after a divider, small muted text (notes).
38
+ * - `content="panel"`: a white bordered panel inset in the card (a checklist).
39
+ * - `content="plain"`: no styling, only the show/hide.
40
+ *
41
+ * Accent badges anywhere in the card (the `badge` slot, nested notes) get a
42
+ * `chart-1` edge, so they don't disappear on the chosen card's accent tint
43
+ * (C19, D109). No class needed on the badge.
44
+ *
45
+ * Listen for `radio-group:change` on the `RadioGroup` for the value.
46
+ */
47
+
48
+ import type { HTMLAttributes } from "astro/types";
49
+ import { cva } from "class-variance-authority";
50
+ import { cn } from "@cl-light-ds/ui/lib/utils";
51
+ import { RadioGroupItem } from "../radio-group";
52
+
53
+ type OptionCardContent = "inset" | "panel" | "plain";
54
+
55
+ interface Props extends Omit<HTMLAttributes<"div">, "class" | "title"> {
56
+ /** The radio value. */
57
+ value: string;
58
+ /** Bold title. Or use the `title` slot. */
59
+ title?: string;
60
+ /** Muted text under the title. Or use the `description` slot. */
61
+ description?: string;
62
+ /** How the nested (default slot) content is framed while chosen. */
63
+ content?: OptionCardContent;
64
+ disabled?: boolean;
65
+ /** id for the radio item. */
66
+ id?: string;
67
+ class?: string;
68
+ }
69
+
70
+ const {
71
+ value,
72
+ title,
73
+ description,
74
+ content = "inset",
75
+ disabled = false,
76
+ id,
77
+ class: className = "",
78
+ ...rest
79
+ } = Astro.props as Props;
80
+
81
+ const hasBadge = Astro.slots.has("badge");
82
+ const hasTitleSlot = Astro.slots.has("title");
83
+ const hasDescriptionSlot = Astro.slots.has("description");
84
+ const hasContent = Astro.slots.has("default");
85
+
86
+ const optionCardContentVariants = cva(
87
+ // Hidden until this card's own radio (not one nested in the content) is checked.
88
+ "hidden group-has-[>[data-slot=option-card-label]>[data-checked]]/option-card:block",
89
+ {
90
+ variants: {
91
+ content: {
92
+ inset: "-mt-2 mr-4 mb-4 ml-11 border-t pt-3 text-xs text-muted-foreground",
93
+ panel: "mx-3 mb-3 rounded-lg border bg-background",
94
+ plain: "",
95
+ },
96
+ },
97
+ defaultVariants: { content: "inset" },
98
+ },
99
+ );
100
+ ---
101
+
102
+ <div
103
+ {...rest}
104
+ data-slot="option-card"
105
+ data-value={value}
106
+ class={cn(
107
+ "group/option-card flex flex-col rounded-lg border bg-card transition-colors",
108
+ "has-[>[data-slot=option-card-label]>[data-checked]]:border-primary has-[>[data-slot=option-card-label]>[data-checked]]:bg-badge-accent/50",
109
+ "has-[>[data-slot=option-card-label]>[data-disabled]]:opacity-50",
110
+ // C19, D109 — an accent badge is bordered in its own fill, the same pale
111
+ // blue as the chosen tint; a chart-1 edge keeps it visible. Always on (not
112
+ // only while chosen), so the badge doesn't change as the choice moves.
113
+ "[&_[data-slot=badge][data-variant=accent]]:border-chart-1",
114
+ className,
115
+ )}
116
+ >
117
+ <label
118
+ data-slot="option-card-label"
119
+ class="flex cursor-pointer items-start gap-3 p-4 text-sm text-foreground has-[>[data-disabled]]:cursor-not-allowed"
120
+ >
121
+ <RadioGroupItem value={value} id={id} disabled={disabled} class="mt-0.5" />
122
+ <span class="flex min-w-0 flex-col gap-1">
123
+ <span data-slot="option-card-title" class="flex flex-wrap items-center gap-2 font-semibold">
124
+ {hasTitleSlot ? <slot name="title" /> : title}
125
+ {hasBadge && <slot name="badge" />}
126
+ </span>
127
+ {
128
+ (hasDescriptionSlot || description) && (
129
+ <span data-slot="option-card-description" class="text-muted-foreground">
130
+ {hasDescriptionSlot ? <slot name="description" /> : description}
131
+ </span>
132
+ )
133
+ }
134
+ </span>
135
+ </label>
136
+ {
137
+ hasContent && (
138
+ <div data-slot="option-card-content" class={optionCardContentVariants({ content })}>
139
+ <slot />
140
+ </div>
141
+ )
142
+ }
143
+ </div>
@@ -0,0 +1 @@
1
+ export { default as OptionCard } from "./OptionCard.astro";
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to the top bar organism (`organisms/top-bar`) will be documented in this file.
4
+
5
+ ## Date 15th Oct 2026
6
+ ## Normal breadcrumb (C1 reverted)
7
+
8
+ - The bar uses the generic `Breadcrumb` as it is (36px crumbs, the normal spacing) instead of the compact 32px crumbs. To stay 41px with its border, the bar's vertical padding is 2px (was 4px); the 32px controls stay centred.
9
+
10
+
11
+ ## Date 15th Oct 2026
12
+ ## Workspace selection handover (N12, C1)
13
+
14
+ ### Added
15
+
16
+ - `TopBar` (N12): the organisation-level application bar, 41px including its bottom border (D23): 4px above and below the 32px controls. Sticky by default (`StickySurface`; `sticky={false}` for a plain bar).
17
+ - Left: a breadcrumb from `crumbs` (`{ label, href? }[]`, the last one current, crumbs without `href` as plain text), or the `breadcrumb` slot. It doesn't wrap; the current crumb truncates.
18
+ - Right, in order: the `search` slot (e.g. `CommandSearch`), the New workspace split button, the `notifications` slot (e.g. `NotificationsMenu`), the `profile` slot (e.g. `ProfileMenu`).
19
+ - Split button: primary `primaryLabel` ("New workspace", `primaryShortLabel` "New" on phones) + separator + chevron `DropdownMenu` with `menuItems` (default Import workspace, `id: "import"`). `splitButton={false}` hides it, the `actions` slot replaces it, `menuItems={[]}` drops the chevron. Uses the C2 split-button styles already in `ButtonGroup`.
20
+ - Event `top-bar:action` (`{ action }`, bubbles, cancelable) on the primary button (`primaryAction`, default `"new-workspace"`) or a row (its `id`); a row with `href` navigates unless cancelled. The button and rows also carry `data-action`.
21
+ - Holds the toast stack (N9): a `Toaster` with `top` = `TOP_BAR_HEIGHT` (41px), right-aligned with the bar's padding. The bar's surface is the containing block of the stack, so it hangs from the bar's right end and follows it. `toaster={false}` leaves it out; `toasterId`, `toasterOffset`.
22
+ - `types.ts`: `TopBarCrumb`, `TopBarMenuItem`, `TopBarActionDetail`, `TOP_BAR_HEIGHT`.
23
+ - Storybook: "Top Bar" under Organisms.
24
+
25
+ ### Breadcrumb inside the bar (C1)
26
+
27
+ - Crumbs are 32px tall (`py-1.5`, was the pill's `py-2`, 36px) with 6px either side of the chevron (`px-1.5`, was `px-3`), so the bar stays 41px (D23, D97). The first crumb is pulled back by its padding so its text lines up with the bar's edge. Applied inside the organism; the generic `Breadcrumb` is unchanged.