@cl-light-ds/ui 0.3.0 → 0.5.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 (103) hide show
  1. package/package.json +20 -18
  2. package/src/components/alert/Alert.astro +8 -2
  3. package/src/components/alert/CHANGELOG.md +9 -0
  4. package/src/components/button/Button.astro +78 -19
  5. package/src/components/button/CHANGELOG.md +22 -0
  6. package/src/components/button-group/ButtonGroup.astro +14 -3
  7. package/src/components/button-group/CHANGELOG.md +17 -0
  8. package/src/components/callout/CHANGELOG.md +32 -0
  9. package/src/components/callout/Callout.astro +147 -0
  10. package/src/components/callout/CalloutDescription.astro +16 -0
  11. package/src/components/callout/CalloutTitle.astro +16 -0
  12. package/src/components/callout/index.ts +3 -0
  13. package/src/components/card/CHANGELOG.md +9 -0
  14. package/src/components/card/Card.astro +22 -0
  15. package/src/components/combobox/CHANGELOG.md +14 -0
  16. package/src/components/combobox/ComboboxTrigger.astro +31 -1
  17. package/src/components/command-search/CHANGELOG.md +27 -0
  18. package/src/components/command-search/CommandSearch.astro +515 -0
  19. package/src/components/command-search/CommandSearchGroup.astro +32 -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 +13 -0
  25. package/src/components/dialog/DialogClose.astro +26 -12
  26. package/src/components/dropdown-menu/CHANGELOG.md +7 -0
  27. package/src/components/dropdown-menu/dropdown-menu-item-variants.ts +1 -1
  28. package/src/components/empty-state/CHANGELOG.md +18 -0
  29. package/src/components/empty-state/EmptyState.astro +157 -0
  30. package/src/components/empty-state/illustrations/AllCaughtUp.astro +52 -0
  31. package/src/components/empty-state/illustrations/EmptyWorkspaces.astro +56 -0
  32. package/src/components/empty-state/illustrations/LoadError.astro +61 -0
  33. package/src/components/empty-state/illustrations/NoResults.astro +60 -0
  34. package/src/components/empty-state/illustrations/NothingMatches.astro +66 -0
  35. package/src/components/empty-state/illustrations/index.ts +27 -0
  36. package/src/components/empty-state/index.ts +7 -0
  37. package/src/components/favorite-toggle/CHANGELOG.md +13 -0
  38. package/src/components/favorite-toggle/FavoriteToggle.astro +94 -0
  39. package/src/components/favorite-toggle/index.ts +1 -0
  40. package/src/components/input/CHANGELOG.md +15 -0
  41. package/src/components/input/InputOTP.astro +211 -0
  42. package/src/components/input/index.ts +1 -1
  43. package/src/components/input-group/CHANGELOG.md +7 -0
  44. package/src/components/input-group/InputGroup.astro +2 -0
  45. package/src/components/loading-overlay/CHANGELOG.md +25 -0
  46. package/src/components/loading-overlay/LoadingOverlay.astro +179 -0
  47. package/src/components/loading-overlay/index.ts +8 -0
  48. package/src/components/logo/CHANGELOG.md +17 -0
  49. package/src/components/logo/Logo.astro +90 -0
  50. package/src/components/logo/index.ts +3 -0
  51. package/src/components/logo/logo-paths.ts +57 -0
  52. package/src/components/notifications-menu/CHANGELOG.md +22 -0
  53. package/src/components/notifications-menu/NotificationsMenu.astro +374 -0
  54. package/src/components/notifications-menu/NotificationsMenuItem.astro +129 -0
  55. package/src/components/notifications-menu/all-caught-up.svg +28 -0
  56. package/src/components/notifications-menu/index.ts +3 -0
  57. package/src/components/notifications-menu/types.ts +32 -0
  58. package/src/components/option-card/CHANGELOG.md +16 -0
  59. package/src/components/option-card/OptionCard.astro +135 -0
  60. package/src/components/option-card/index.ts +1 -0
  61. package/src/components/organisms/top-bar/CHANGELOG.md +21 -0
  62. package/src/components/organisms/top-bar/TopBar.astro +264 -0
  63. package/src/components/organisms/top-bar/index.ts +2 -0
  64. package/src/components/organisms/top-bar/types.ts +30 -0
  65. package/src/components/organisms/workspace/CHANGELOG.md +73 -0
  66. package/src/components/organisms/workspace/OrganisationOverview.astro +62 -0
  67. package/src/components/organisms/workspace/RecentWorkspaceCard.astro +235 -0
  68. package/src/components/organisms/workspace/RecentWorkspaces.astro +93 -0
  69. package/src/components/organisms/workspace/WorkspaceAvatar.astro +144 -0
  70. package/src/components/organisms/workspace/WorkspaceCard.astro +172 -0
  71. package/src/components/organisms/workspace/WorkspaceGlyph.astro +62 -0
  72. package/src/components/organisms/workspace/WorkspaceIcon.astro +42 -0
  73. package/src/components/organisms/workspace/WorkspaceIconPicker.astro +288 -0
  74. package/src/components/organisms/workspace/WorkspaceIconPickerGlyph.astro +44 -0
  75. package/src/components/organisms/workspace/icons.ts +98 -0
  76. package/src/components/organisms/workspace/index.ts +35 -0
  77. package/src/components/organisms/workspace/labels.ts +54 -0
  78. package/src/components/organisms/workspace/recent-card-rings-strong.png +0 -0
  79. package/src/components/organisms/workspace/types.ts +30 -0
  80. package/src/components/profile-menu/CHANGELOG.md +22 -0
  81. package/src/components/profile-menu/ProfileMenu.astro +236 -0
  82. package/src/components/profile-menu/index.ts +2 -0
  83. package/src/components/profile-menu/types.ts +27 -0
  84. package/src/components/search-field/CHANGELOG.md +14 -0
  85. package/src/components/search-field/SearchField.astro +163 -0
  86. package/src/components/search-field/index.ts +1 -0
  87. package/src/components/table/CHANGELOG.md +9 -0
  88. package/src/components/table/TableCell.astro +6 -0
  89. package/src/components/table/TableHead.astro +5 -1
  90. package/src/components/table/TableRow.astro +3 -1
  91. package/src/components/table/table-cell-variants.ts +2 -0
  92. package/src/components/toast/CHANGELOG.md +32 -0
  93. package/src/components/toast/Toast.astro +145 -0
  94. package/src/components/toast/Toaster.astro +322 -0
  95. package/src/components/toast/index.ts +9 -0
  96. package/src/components/toggle/CHANGELOG.md +7 -0
  97. package/src/lib/command-search.ts +73 -0
  98. package/src/lib/loading-overlay.ts +174 -0
  99. package/src/lib/toast.ts +127 -0
  100. package/src/lib/toggle-shared.ts +4 -1
  101. package/src/lib/workspace-palette.ts +119 -0
  102. package/src/styles/CHANGELOG.md +22 -0
  103. package/src/styles/globals.css +100 -1
@@ -0,0 +1,64 @@
1
+ ---
2
+ /**
3
+ * One result row in a `CommandSearch`: optional media (an icon or avatar), the
4
+ * label, and optional muted text on the right (`meta` slot, e.g. "in Acme
5
+ * Finance"). Highlighted while active (arrow keys or hover); Enter or a click
6
+ * chooses it.
7
+ *
8
+ * Searching matches `label` and `keywords` (case-insensitive, anywhere in the
9
+ * text), so give every item a `label`. `value` identifies it in
10
+ * `command-search:select`; it defaults to the label, so pass one when two items
11
+ * can share a name.
12
+ */
13
+ import { cn } from "@cl-light-ds/ui/lib/utils";
14
+ import type { HTMLAttributes } from "astro/types";
15
+
16
+ import { commandSearchItemClasses, commandSearchLabelClasses, commandSearchMetaClasses } from "./classes";
17
+
18
+ interface Props extends HTMLAttributes<"div"> {
19
+ /** The text searched and shown (the default slot can replace what is shown). */
20
+ label: string;
21
+ /** Unique value, sent with `command-search:select`. Defaults to `label`. */
22
+ value?: string;
23
+ /** More terms that find this item. */
24
+ keywords?: string[];
25
+ /** Navigate here when chosen. */
26
+ href?: string;
27
+ disabled?: boolean;
28
+ class?: string;
29
+ }
30
+
31
+ const {
32
+ label,
33
+ value = label,
34
+ keywords = [],
35
+ href,
36
+ disabled = false,
37
+ class: className = "",
38
+ ...rest
39
+ } = Astro.props as Props;
40
+
41
+ const hasMedia = Astro.slots.has("media");
42
+ const hasMeta = Astro.slots.has("meta");
43
+ ---
44
+
45
+ <div
46
+ data-slot="command-item"
47
+ data-value={value}
48
+ data-label={label}
49
+ data-keywords={keywords.length ? keywords.join(",") : undefined}
50
+ data-href={href}
51
+ data-disabled={disabled ? "true" : undefined}
52
+ class={cn(commandSearchItemClasses, className)}
53
+ {...rest}
54
+ >
55
+ {hasMedia && <slot name="media" />}
56
+ <span class={commandSearchLabelClasses}><slot>{label}</slot></span>
57
+ {
58
+ hasMeta && (
59
+ <span class={commandSearchMetaClasses}>
60
+ <slot name="meta" />
61
+ </span>
62
+ )
63
+ }
64
+ </div>
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Class strings shared by the Astro parts and the results the script renders
3
+ * from `setCommandSearchResults()`, so both look the same.
4
+ */
5
+ export const commandSearchGroupClasses = "flex flex-col gap-1";
6
+
7
+ export const commandSearchHeadingClasses =
8
+ "text-muted-foreground flex items-baseline gap-2 px-2 pt-1 text-xs font-semibold tracking-wide uppercase";
9
+
10
+ export const commandSearchCountClasses = "font-normal tabular-nums";
11
+
12
+ export const commandSearchItemClasses =
13
+ "hover:bg-background-hover data-[selected=true]:bg-background-hover aria-disabled:pointer-events-none aria-disabled:opacity-50 flex cursor-pointer items-center gap-3 rounded-md px-2 py-2 text-sm transition-colors outline-none";
14
+
15
+ export const commandSearchLabelClasses = "min-w-0 flex-1 truncate";
16
+
17
+ export const commandSearchMetaClasses =
18
+ "text-muted-foreground flex max-w-56 min-w-0 shrink-0 items-center gap-1.5 truncate text-xs";
@@ -0,0 +1,12 @@
1
+ export { default as CommandSearch } from "./CommandSearch.astro";
2
+ export { default as CommandSearchGroup } from "./CommandSearchGroup.astro";
3
+ export { default as CommandSearchItem } from "./CommandSearchItem.astro";
4
+ export {
5
+ openCommandSearch,
6
+ closeCommandSearch,
7
+ setCommandSearchResults,
8
+ } from "../../lib/command-search";
9
+ export type {
10
+ CommandSearchResultGroup,
11
+ CommandSearchResultItem,
12
+ } from "../../lib/command-search";
@@ -0,0 +1,42 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="184" height="124" viewBox="-12 -12 184 124" fill="none" xmlns:c2pa="http://c2pa.org/manifest"><metadata><c2pa:manifest>AAAWgmp1bWIAAAAeanVtZGMycGEAEQAQgAAAqgA4m3EDYzJwYQAAABZcanVtYgAAAEdqdW1kYzJtYQARABCAAACqADibcQN1cm46YzJwYTplODIyOThlNC1jNGQ2LTQwYTQtOGU3Ny1hYWY2OTcxYjM2OTIAAAADl2p1bWIAAAApanVtZGMyYXMAEQAQgAAAqgA4m3EDYzJwYS5hc3NlcnRpb25zAAAAALxqdW1iAAAARGp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuaW5ncmVkaWVudC52MwAAAAAYYzJzaKqugmJMsqYC8viyOBbRbhIAAABwY2JvcqNpZGM6Zm9ybWF0bWltYWdlL3N2Zyt4bWxqaW5zdGFuY2VJRHgseG1wOmlpZDo0MWIxMTE5NC02ZTQ0LTRlMzUtOTU5MS0xYjY1M2ViNWUyNTJscmVsYXRpb25zaGlwaHBhcmVudE9mAAAB4mp1bWIAAABBanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5hY3Rpb25zLnYyAAAAABhjMnNouXZVAYt+XHRTFpV6GfvZOgAAAZljYm9yomdhY3Rpb25zgqJmYWN0aW9ua2MycGEub3BlbmVkanBhcmFtZXRlcnOha2luZ3JlZGllbnRzgaJjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFggVgKr/4T9xsUXed1EvB+dGw4hkEVHK5LpA88frFSdO46kZmFjdGlvbngdY29tLmFudGhyb3BpYy5jbGF1ZGUucHJvdmlkZWRqcGFyYW1ldGVyc6F4H2NvbS5hbnRocm9waWMub3JpZ2luLWNvbmZpZGVuY2VndW5rbm93bmtkZXNjcmlwdGlvbnhmQ2xhdWRlIHByb3ZpZGVkIHRoaXMgZmlsZSBhdCB0aGUgcmVxdWVzdCBvZiBhIHVzZXIgYW5kIG1heSBoYXZlIGNyZWF0ZWQgb3IgbW9kaWZpZWQgdGhlIGZpbGUgY29udGVudHMubXNvZnR3YXJlQWdlbnShZG5hbWVmQ2xhdWRlcmFsbEFjdGlvbnNJbmNsdWRlZPUAAADIanVtYgAAAEBqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmhhc2guZGF0YQAAAAAYYzJzaNb7nH0x+zMDR8g/a81tYxUAAACAY2JvcqVjYWxnZnNoYTI1NmNwYWRNAAAAAAAAAAAAAAAAAGRoYXNoWCB8IybuiUW/D1GR1OoeQ940FnLg0RBFnD1vqhoG+GGrd2RuYW1lbmp1bWJmIG1hbmlmZXN0amV4Y2x1c2lvbnOBomVzdGFydBimZmxlbmd0aBkeBAAAAj5qdW1iAAAAJ2p1bWRjMmNsABEAEIAAAKoAOJtxA2MycGEuY2xhaW0udjIAAAACD2Nib3KlY2FsZ2ZzaGEyNTZpc2lnbmF0dXJleE1zZWxmI2p1bWJmPS9jMnBhL3VybjpjMnBhOmU4MjI5OGU0LWM0ZDYtNDBhNC04ZTc3LWFhZjY5NzFiMzY5Mi9jMnBhLnNpZ25hdHVyZWppbnN0YW5jZUlEeCx4bXA6aWlkOjExNmJiZmVjLWQxNjEtNDM1YS1iNzkxLTM1YjRlNTM4MzljYXJjcmVhdGVkX2Fzc2VydGlvbnODomN1cmx4LXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaW5ncmVkaWVudC52M2RoYXNoWCBWAqv/hP3GxRd53US8H50bDiGQRUcrkukDzx+sVJ07jqJjdXJseCpzZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmFjdGlvbnMudjJkaGFzaFggWNJwmATXeq3w/IjLwhFkIQQ1fvC8gXIQ/CRzxbJAz4aiY3VybHgpc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5oYXNoLmRhdGFkaGFzaFgg3x7o2h9I/3508c0ufWUiNo2ld5HoEd8e5vBDPLAJa0t0Y2xhaW1fZ2VuZXJhdG9yX2luZm+jZG5hbWVvQW50aHJvcGljIEZpbGVzZ3ZlcnNpb25lMS4wLjBrc3BlY1ZlcnNpb25lMi40LjAAABA4anVtYgAAAChqdW1kYzJjcwARABCAAACqADibcQNjMnBhLnNpZ25hdHVyZQAAABAIY2JvctKEWQISogEmGCFZAgowggIGMIIBjaADAgECAhRA5aAK7sI50L64g/oGQgU9Z1UTADAKBggqhkjOPQQDAzBJMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEuMCwGA1UEAxMlQW50aHJvcGljIENvbnRlbnQgQ3JlZGVudGlhbHMgUm9vdCBDQTAeFw0yNjA4MDcxODQzNTZaFw0yODA4MDYxOTQzNTZaMEQxFzAVBgNVBAoTDkFudGhyb3BpYywgUEJDMSkwJwYDVQQDEyBBbnRocm9waWMgQ2xhdWRlIENvbnRlbnQgU2lnbmluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJh6CmvLUBgFFNU0vUKlOVtE6djd17L5SuwX0LemFisBM3dkd/3cyjxFA3Qo5S46fX0/ihY0VZ7mfb9KF703t5OjWDBWMA4GA1UdDwEB/wQEAwIHgDAVBgNVHSUEDjAMBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUzlHiBIFOZFsj+OPEz5o+nMHXXMIwCgYIKoZIzj0EAwMDZwAwZAIwMXMdFJ4BetLLVY7ORuE9noqbbAZOZn/aArXyTwFAZfKrPzxF2vPoJNf1+UCdg1XGAjBwX1zd9WGqYkqmL5SFqw1QySjr1zJfpJM9+1rdDwSPLMOPOjKuiXjoU/pUUeG9RwmhY3BhZFkNngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPZYQBQ6fFVaIySaTkZAvfiIN0Vu6miBN8tscscJnyxn5eEUXN2x49nEHZ+/F/hQydOi3j5/kryWAn8JzV93uuuUbDg=</c2pa:manifest></metadata>
2
+ <defs>
3
+ <filter id="sBack" x="-30%" y="-30%" width="160%" height="160%" 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="-30%" width="180%" height="170%" 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="sLens" x="-60%" y="-60%" width="220%" height="220%" color-interpolation-filters="sRGB">
10
+ <feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#3785E0" flood-opacity="0.28"></feDropShadow>
11
+ </filter>
12
+ </defs>
13
+ <circle cx="80" cy="50" r="44" stroke="#F5F9FE" stroke-width="12"></circle>
14
+ <circle cx="80" cy="50" r="24" stroke="#EBF3FC" stroke-width="10"></circle>
15
+ <g transform="rotate(-12 44 52)">
16
+ <g filter="url(#sBack)"><rect x="22" y="26" width="44" height="52" rx="8" fill="#FFFFFF"></rect></g>
17
+ <rect x="22.5" y="26.5" width="43" height="51" rx="7.5" stroke="#09090B" stroke-opacity="0.06"></rect>
18
+ <rect x="28" y="32" width="14" height="14" rx="4" fill="#D7E7F9"></rect>
19
+ <rect x="28" y="54" width="24" height="4" rx="2" fill="#E4E4E7"></rect>
20
+ <rect x="28" y="62" width="16" height="4" rx="2" fill="#F4F4F5"></rect>
21
+ </g>
22
+ <g transform="rotate(12 116 52)">
23
+ <g filter="url(#sBack)"><rect x="94" y="26" width="44" height="52" rx="8" fill="#FFFFFF"></rect></g>
24
+ <rect x="94.5" y="26.5" width="43" height="51" rx="7.5" stroke="#09090B" stroke-opacity="0.06"></rect>
25
+ <rect x="100" y="34" width="28" height="4" rx="2" fill="#E4E4E7"></rect>
26
+ <rect x="100" y="42" width="32" height="4" rx="2" fill="#F4F4F5"></rect>
27
+ <rect x="100" y="50" width="22" height="4" rx="2" fill="#F4F4F5"></rect>
28
+ <rect x="100" y="58" width="28" height="4" rx="2" fill="#F4F4F5"></rect>
29
+ </g>
30
+ <g filter="url(#sFront)"><rect x="56" y="18" width="48" height="58" rx="9" fill="#FFFFFF"></rect></g>
31
+ <rect x="56.5" y="18.5" width="47" height="57" rx="8.5" stroke="#09090B" stroke-opacity="0.06"></rect>
32
+ <rect x="62" y="24" width="36" height="22" rx="5" fill="#EBF3FC"></rect>
33
+ <path d="M77 30.5 L84.5 35 L77 39.5 Z" fill="#87B6EC" stroke="#87B6EC" stroke-width="1.5" stroke-linejoin="round"></path>
34
+ <rect x="62" y="53" width="28" height="4" rx="2" fill="#E4E4E7"></rect>
35
+ <rect x="62" y="61" width="18" height="4" rx="2" fill="#F4F4F5"></rect>
36
+ <g filter="url(#sLens)">
37
+ <line x1="113.5" y1="79.5" x2="122" y2="88" stroke="#1F5FAA" stroke-width="6" stroke-linecap="round"></line>
38
+ <circle cx="104" cy="70" r="13" fill="#FFFFFF" stroke="#3785E0" stroke-width="4"></circle>
39
+ </g>
40
+ <line x1="100" y1="66" x2="108" y2="74" stroke="#87B6EC" stroke-width="2.2" stroke-linecap="round"></line>
41
+ <line x1="108" y1="66" x2="100" y2="74" stroke="#87B6EC" stroke-width="2.2" stroke-linecap="round"></line>
42
+ </svg>
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to the `Dialog` component family (`Dialog`, `DialogTrigger`, `DialogContent`, `DialogOverlay`, `DialogHeader`, `DialogFooter`, `DialogTitle`, `DialogDescription`, `DialogClose`) are documented in this file.
4
4
 
5
+ ## Date 15th Oct 2026
6
+ ## Workspace selection handover (C9, C16; gap G15)
7
+
8
+ ### `DialogClose`
9
+
10
+ - **`asChild` (C9):** `<DialogClose asChild><Button variant="outline">Cancel</Button></DialogClose>` closes the dialog with your own button. The wrapper is `display: contents`. Without it, wrapping a Button nested a button in a button.
11
+ - **✕ hover (C16):** `hover:bg-background-button-hover` (zinc-200). The ghost hover (zinc-100) didn't show on light grey surfaces.
12
+
13
+ ### Storybook
14
+
15
+ - "Cancel with DialogClose asChild" preview.
16
+
17
+
5
18
  ## Earlier (commit `c3cec7d`, undocumented at the time)
6
19
 
7
20
  - `DialogContent`: `rounded-xl` → `rounded-lg` (Figma binds 8px radius).
@@ -5,19 +5,33 @@ import Button from "../button/Button.astro";
5
5
 
6
6
  interface Props extends HTMLAttributes<"button"> {
7
7
  class?: string;
8
+ /**
9
+ * Radix-like: render a wrapper so your own element closes the dialog, e.g. a
10
+ * Cancel `<Button>`. The wrapper is `display: contents`, so the child lays out
11
+ * as if it were placed directly.
12
+ */
13
+ asChild?: boolean;
8
14
  }
9
15
 
10
- const { class: className = "", ...rest } = Astro.props as Props;
16
+ const { class: className = "", asChild = false, ...rest } = Astro.props as Props;
11
17
  ---
12
18
 
13
- <Button
14
- {...rest}
15
- data-slot="dialog-close"
16
- data-dialog-close
17
- variant="ghost"
18
- size="icon-sm"
19
- class={cn("absolute top-2 right-2", className)}
20
- >
21
- <slot />
22
- {!Astro.slots.has("default") && <span class="sr-only">Close</span>}
23
- </Button>
19
+ {
20
+ asChild ? (
21
+ <div data-slot="dialog-close" data-dialog-close data-as-child class={cn("contents", className)}>
22
+ <slot />
23
+ </div>
24
+ ) : (
25
+ <Button
26
+ {...rest}
27
+ data-slot="dialog-close"
28
+ data-dialog-close
29
+ variant="ghost"
30
+ size="icon-sm"
31
+ class={cn("absolute top-2 right-2 hover:bg-background-button-hover", className)}
32
+ >
33
+ <slot />
34
+ {!Astro.slots.has("default") && <span class="sr-only">Close</span>}
35
+ </Button>
36
+ )
37
+ }
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to the `DropdownMenu` component will be documented in this file.
4
4
 
5
+ ## Date 15th Oct 2026
6
+ ## Workspace selection handover (C17, gap G7)
7
+
8
+ ### States
9
+
10
+ - `destructive` item hover is now `bg-callout-error` (`--callout-background-error`, Figma Red/50), was the neutral `background-hover`. The workspace-selection screen overrode it with `destructive/10`, which this replaces. Dark mode keeps `destructive/20`.
11
+
5
12
  ## Date 10th Sep 2026
6
13
  ## Figma alignment (node 2329-6521, prop table 7392-10142)
7
14
 
@@ -11,7 +11,7 @@ export const itemVariants = cva(
11
11
  variant: {
12
12
  default: "p-2 text-xs font-semibold text-foreground hover:bg-background-hover hover:text-foreground",
13
13
  destructive:
14
- "p-2 text-xs font-semibold text-destructive hover:bg-background-hover hover:text-destructive dark:hover:bg-destructive/20 [&_svg]:text-destructive",
14
+ "p-2 text-xs font-semibold text-destructive hover:bg-callout-error hover:text-destructive dark:hover:bg-destructive/20 [&_svg]:text-destructive",
15
15
  category: "pointer-events-none gap-1 pt-1 pr-1 pb-0 pl-1 text-xs font-medium text-muted-foreground",
16
16
  },
17
17
  },
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `EmptyState` component will be documented in this file.
4
+
5
+ ## Date 15th Oct 2026
6
+ ## Workspace selection handover (N15)
7
+
8
+ ### Added
9
+
10
+ - `EmptyState`: illustration (or icon), title, text and an optional action, centred.
11
+ - `size="page"`: 280×200 illustrations, 18px semibold title (`h2`), 14px text, 16px gaps, `py-12`.
12
+ - `size="menu"`: 144×104 / 184×124 illustrations, 14px medium title (`p`), 12px text, 8px gaps, `py-10`.
13
+ - `variant="panel"`: on a white (`bg-background`) dashed, rounded panel, for card grids (D188, D202). `plain` (default) has no frame, e.g. inside `TableEmpty` or a popover.
14
+ - `illustration`: built-in name; `icon`: a Lucide component in a 48px muted circle (the reference's "no favourites" state); `illustration` slot for custom artwork.
15
+ - `title` / `description` props or slots, `titleAs` to change the heading element, `action` slot for buttons. Other attributes pass through (e.g. `role="alert"` for a load error).
16
+ - Illustrations (`illustrations/`), from the designer's handover SVGs: `empty`, `no-results`, `error` (280×200), `all-caught-up` (144×104), `nothing-matches` (184×124). Converted to inline Astro components so their colours are design tokens (`background`, `foreground`, `muted`, `border`, `primary`, `primary-50/100/300/700`, `destructive`) and follow the theme; the C2PA metadata is stripped and filter ids are unique per instance. Exceptions without a token: two neutral greys (`zinc-400`, `zinc-300`, via Tailwind's variables with hex fallbacks), the lightest ring (a mix of `primary-50` and `background`), and the drop shadows' `#051729` (the same colour as `shadow-card-light`).
17
+ - `emptyStateIllustrations`, `EMPTY_STATE_ILLUSTRATIONS`, `EmptyStateIllustrationName` exported.
18
+ - Storybook: "Empty State" under Feedback.
@@ -0,0 +1,157 @@
1
+ ---
2
+ /**
3
+ * @component EmptyState
4
+ * @title Empty State
5
+ * @description A message for when there is nothing to show, or something failed: an illustration, a title, a line of text and an optional action. Page and menu sizes, optionally on a white dashed panel.
6
+ *
7
+ * @preview
8
+ *
9
+ * <EmptyState
10
+ * illustration="empty"
11
+ * title="You're not in any workspaces yet"
12
+ * description="Create one to get started, or ask a colleague to invite you to theirs."
13
+ * variant="panel"
14
+ * >
15
+ * <Button slot="action">New workspace</Button>
16
+ * </EmptyState>
17
+ *
18
+ * @usage
19
+ *
20
+ * ```astro nocollapse
21
+ * ---
22
+ * import { EmptyState } from '@cl-light-ds/ui/components/empty-state';
23
+ * import { Button } from '@cl-light-ds/ui/components/button';
24
+ * ---
25
+ *
26
+ * <EmptyState illustration="no-results" title="No results for “acme”" description="Check the spelling, or try a shorter name.">
27
+ * <Button slot="action" variant="outline">Clear search</Button>
28
+ * </EmptyState>
29
+ *
30
+ * <EmptyState size="menu" illustration="all-caught-up" title="You're all caught up" description="New notifications show up here." />
31
+ * ```
32
+ *
33
+ * - Built-in illustrations (`illustration`): `empty`, `no-results`, `error`
34
+ * (280×200, for pages), `all-caught-up` (144×104) and `nothing-matches`
35
+ * (184×124, for menus). Drawn with design tokens, so they follow the theme.
36
+ * - Or an `icon` (a Lucide component) in a 48px muted circle, or your own
37
+ * artwork in the `illustration` slot.
38
+ * - For a failure that appears after an action, add `role="alert"` so it is
39
+ * announced; for results that change as you type, `role="status"`.
40
+ */
41
+
42
+ import type { HTMLAttributes, HTMLTag } from "astro/types";
43
+ import type { AstroComponent } from "@lucide/astro";
44
+ import { cva, type VariantProps } from "class-variance-authority";
45
+ import { cn } from "@cl-light-ds/ui/lib/utils";
46
+ import { emptyStateIllustrations, type EmptyStateIllustrationName } from "./illustrations";
47
+
48
+ export const emptyStateVariants = cva("flex flex-col items-center text-center", {
49
+ variants: {
50
+ size: {
51
+ page: "gap-4 px-6 py-12",
52
+ menu: "gap-2 px-6 py-10",
53
+ },
54
+ variant: {
55
+ plain: "",
56
+ panel: "rounded-lg border border-dashed bg-background",
57
+ },
58
+ },
59
+ defaultVariants: {
60
+ size: "page",
61
+ variant: "plain",
62
+ },
63
+ });
64
+
65
+ interface Props extends VariantProps<typeof emptyStateVariants>, Omit<HTMLAttributes<"div">, "class" | "title"> {
66
+ /** A built-in illustration. */
67
+ illustration?: EmptyStateIllustrationName;
68
+ /** Or a Lucide icon in a 48px muted circle (ignored when there is an illustration). */
69
+ icon?: AstroComponent;
70
+ /** The title. Or the `title` slot. */
71
+ title?: string;
72
+ /** The text under the title. Or the `description` slot. */
73
+ description?: string;
74
+ /** Element for the title: `h2` on pages, `p` in menus by default. */
75
+ titleAs?: HTMLTag;
76
+ class?: string;
77
+ }
78
+
79
+ const {
80
+ illustration,
81
+ icon,
82
+ title,
83
+ description,
84
+ size = "page",
85
+ variant = "plain",
86
+ titleAs,
87
+ class: className = "",
88
+ ...rest
89
+ } = Astro.props as Props;
90
+
91
+ const hasIllustrationSlot = Astro.slots.has("illustration");
92
+ const hasTitleSlot = Astro.slots.has("title");
93
+ const hasDescriptionSlot = Astro.slots.has("description");
94
+ const hasAction = Astro.slots.has("action");
95
+
96
+ const art = illustration ? emptyStateIllustrations[illustration] : undefined;
97
+ const Illustration = art?.component;
98
+ const Icon = icon;
99
+ const TitleTag = titleAs ?? (size === "menu" ? "p" : "h2");
100
+ const isMenu = size === "menu";
101
+ ---
102
+
103
+ <div
104
+ {...rest}
105
+ data-slot="empty-state"
106
+ data-size={size}
107
+ data-variant={variant}
108
+ class={cn(emptyStateVariants({ size, variant }), className)}
109
+ >
110
+ {
111
+ hasIllustrationSlot ? (
112
+ <div data-slot="empty-state-illustration" class="flex justify-center">
113
+ <slot name="illustration" />
114
+ </div>
115
+ ) : Illustration && art ? (
116
+ <Illustration data-slot="empty-state-illustration" class={cn("max-w-full shrink-0", art.pull)} />
117
+ ) : (
118
+ Icon && (
119
+ <span
120
+ data-slot="empty-state-icon"
121
+ class="flex size-12 items-center justify-center rounded-full bg-muted text-muted-foreground"
122
+ aria-hidden="true"
123
+ >
124
+ <Icon class="size-6" />
125
+ </span>
126
+ )
127
+ )
128
+ }
129
+
130
+ <div class={cn("flex flex-col", isMenu ? "max-w-xs gap-2" : "max-w-md gap-1")}>
131
+ {
132
+ (hasTitleSlot || title) && (
133
+ <TitleTag
134
+ data-slot="empty-state-title"
135
+ class={cn("text-foreground", isMenu ? "text-sm font-medium" : "text-lg font-semibold")}
136
+ >
137
+ {hasTitleSlot ? <slot name="title" /> : title}
138
+ </TitleTag>
139
+ )
140
+ }
141
+ {
142
+ (hasDescriptionSlot || description) && (
143
+ <p data-slot="empty-state-description" class={cn("text-muted-foreground", isMenu ? "text-xs" : "text-sm")}>
144
+ {hasDescriptionSlot ? <slot name="description" /> : description}
145
+ </p>
146
+ )
147
+ }
148
+ </div>
149
+
150
+ {
151
+ hasAction && (
152
+ <div data-slot="empty-state-action" class="flex flex-wrap items-center justify-center gap-2">
153
+ <slot name="action" />
154
+ </div>
155
+ )
156
+ }
157
+ </div>
@@ -0,0 +1,52 @@
1
+ ---
2
+ /**
3
+ * Illustration "all-caught-up" (144×104, with its own padding inside the viewBox).
4
+ * The designer's artwork; colours mapped to design tokens so it follows the theme.
5
+ * Decorative. Generated from the workspace selection handover assets (N15).
6
+ */
7
+ import type { HTMLAttributes } from "astro/types";
8
+
9
+ type Props = Omit<HTMLAttributes<"svg">, "width" | "height">;
10
+
11
+ const props = Astro.props as Props;
12
+ // Unique ids per instance, so several illustrations on one page don't share filters.
13
+ const uid = `es-all-caught-up-${Math.random().toString(36).slice(2, 8)}`;
14
+ ---
15
+
16
+ <svg
17
+ xmlns="http://www.w3.org/2000/svg"
18
+ width="144"
19
+ height="104"
20
+ viewBox="-12 -12 144 104"
21
+ fill="none"
22
+ aria-hidden="true"
23
+ focusable="false"
24
+ {...props}
25
+ >
26
+ <defs>
27
+ <filter id={`${uid}-sBack`} x="-30%" y="-40%" width="160%" height="190%" color-interpolation-filters="sRGB">
28
+ <feDropShadow dx="0" dy="1.5" stdDeviation="2" flood-color="#051729" flood-opacity="0.05"></feDropShadow>
29
+ </filter>
30
+ <filter id={`${uid}-sFront`} x="-40%" y="-40%" width="180%" height="190%" color-interpolation-filters="sRGB">
31
+ <feDropShadow dx="0" dy="5" stdDeviation="6" flood-color="#051729" flood-opacity="0.10"></feDropShadow>
32
+ </filter>
33
+ <filter id={`${uid}-sCheck`} x="-60%" y="-60%" width="220%" height="220%" color-interpolation-filters="sRGB">
34
+ <feDropShadow dx="0" dy="3" stdDeviation="3" flood-opacity="0.32" style="flood-color:var(--primary)"></feDropShadow>
35
+ </filter>
36
+ </defs>
37
+ <circle cx="60" cy="40" r="35" stroke-width="10" style="stroke:color-mix(in oklab, var(--primary-50) 50%, var(--background))"></circle>
38
+ <circle cx="60" cy="40" r="19" stroke-width="8" style="stroke:var(--primary-50)"></circle>
39
+ <g filter={`url(#${uid}-sBack)`}><rect x="22" y="12" width="76" height="18" rx="6" style="fill:var(--background)"></rect></g>
40
+ <rect x="22.5" y="12.5" width="75" height="17" rx="5.5" stroke-opacity="0.06" style="stroke:var(--foreground)"></rect>
41
+ <circle cx="31" cy="21" r="4" style="fill:var(--muted)"></circle>
42
+ <rect x="39" y="19" width="30" height="4" rx="2" style="fill:var(--muted)"></rect>
43
+ <g filter={`url(#${uid}-sFront)`}><rect x="16" y="34" width="88" height="24" rx="7" style="fill:var(--background)"></rect></g>
44
+ <rect x="16.5" y="34.5" width="87" height="23" rx="6.5" stroke-opacity="0.06" style="stroke:var(--foreground)"></rect>
45
+ <circle cx="28" cy="46" r="6" style="fill:var(--primary-50)"></circle>
46
+ <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-width="1.1" stroke-linecap="round" stroke-linejoin="round" style="stroke:var(--primary)"></path>
47
+ <rect x="39" y="41" width="40" height="4" rx="2" style="fill:var(--border)"></rect>
48
+ <rect x="39" y="48.5" width="26" height="3.5" rx="1.75" style="fill:var(--muted)"></rect>
49
+ <g filter={`url(#${uid}-sCheck)`}><circle cx="100" cy="58" r="11" style="fill:var(--primary)"></circle></g>
50
+ <circle cx="100" cy="58" r="11" stroke-width="2.5" style="stroke:var(--background)"></circle>
51
+ <path d="M95.5 58.2 l3 3 l6 -6" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" style="stroke:var(--background)"></path>
52
+ </svg>
@@ -0,0 +1,56 @@
1
+ ---
2
+ /**
3
+ * Illustration "empty-workspaces" (280×200, with its own padding inside the viewBox).
4
+ * The designer's artwork; colours mapped to design tokens so it follows the theme.
5
+ * Decorative. Generated from the workspace selection handover assets (N15).
6
+ */
7
+ import type { HTMLAttributes } from "astro/types";
8
+
9
+ type Props = Omit<HTMLAttributes<"svg">, "width" | "height">;
10
+
11
+ const props = Astro.props as Props;
12
+ // Unique ids per instance, so several illustrations on one page don't share filters.
13
+ const uid = `es-empty-workspaces-${Math.random().toString(36).slice(2, 8)}`;
14
+ ---
15
+
16
+ <svg
17
+ xmlns="http://www.w3.org/2000/svg"
18
+ width="280"
19
+ height="200"
20
+ viewBox="-20 -20 280 200"
21
+ fill="none"
22
+ aria-hidden="true"
23
+ focusable="false"
24
+ {...props}
25
+ >
26
+ <defs>
27
+ <filter id={`${uid}-sBack`} x="-20%" y="-30%" width="140%" height="160%" color-interpolation-filters="sRGB">
28
+ <feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="#051729" flood-opacity="0.05"></feDropShadow>
29
+ </filter>
30
+ <filter id={`${uid}-sFront`} x="-30%" y="-40%" width="160%" height="190%" color-interpolation-filters="sRGB">
31
+ <feDropShadow dx="0" dy="8" stdDeviation="10" flood-color="#051729" flood-opacity="0.10"></feDropShadow>
32
+ </filter>
33
+ <filter id={`${uid}-sBlue`} x="-60%" y="-60%" width="220%" height="240%" color-interpolation-filters="sRGB">
34
+ <feDropShadow dx="0" dy="4" stdDeviation="5" flood-opacity="0.35" style="flood-color:var(--primary)"></feDropShadow>
35
+ </filter>
36
+ </defs>
37
+ <circle cx="120" cy="80" r="69" stroke-width="20" style="stroke:color-mix(in oklab, var(--primary-50) 50%, var(--background))"></circle>
38
+ <circle cx="120" cy="80" r="40" stroke-width="16" style="stroke:var(--primary-50)"></circle>
39
+ <g transform="rotate(-9 94 70)" filter={`url(#${uid}-sBack)`}>
40
+ <rect x="34" y="34" width="120" height="72" rx="10" style="fill:var(--background)"></rect>
41
+ <rect x="34.5" y="34.5" width="119" height="71" rx="9.5" stroke-opacity="0.06" style="stroke:var(--foreground)"></rect>
42
+ </g>
43
+ <g transform="rotate(8 152 66)" filter={`url(#${uid}-sBack)`}>
44
+ <rect x="92" y="30" width="120" height="72" rx="10" style="fill:var(--background)"></rect>
45
+ <rect x="92.5" y="30.5" width="119" height="71" rx="9.5" stroke-opacity="0.06" style="stroke:var(--foreground)"></rect>
46
+ </g>
47
+ <g filter={`url(#${uid}-sFront)`}>
48
+ <rect x="50" y="52" width="140" height="84" rx="12" style="fill:var(--background)"></rect>
49
+ </g>
50
+ <rect x="50.5" y="52.5" width="139" height="83" rx="11.5" stroke-opacity="0.06" style="stroke:var(--foreground)"></rect>
51
+ <rect x="64" y="66" width="28" height="28" rx="8" filter={`url(#${uid}-sBlue)`} style="fill:var(--primary)"></rect>
52
+ <rect x="77" y="74" width="2" height="12" rx="1" style="fill:var(--background)"></rect>
53
+ <rect x="72" y="79" width="12" height="2" rx="1" style="fill:var(--background)"></rect>
54
+ <rect x="64" y="106" width="76" height="7" rx="3.5" style="fill:var(--primary-100)"></rect>
55
+ <rect x="64" y="119" width="48" height="7" rx="3.5" style="fill:var(--muted)"></rect>
56
+ </svg>
@@ -0,0 +1,61 @@
1
+ ---
2
+ /**
3
+ * Illustration "load-error" (280×200, with its own padding inside the viewBox).
4
+ * The designer's artwork; colours mapped to design tokens so it follows the theme.
5
+ * Decorative. Generated from the workspace selection handover assets (N15).
6
+ */
7
+ import type { HTMLAttributes } from "astro/types";
8
+
9
+ type Props = Omit<HTMLAttributes<"svg">, "width" | "height">;
10
+
11
+ const props = Astro.props as Props;
12
+ // Unique ids per instance, so several illustrations on one page don't share filters.
13
+ const uid = `es-load-error-${Math.random().toString(36).slice(2, 8)}`;
14
+ ---
15
+
16
+ <svg
17
+ xmlns="http://www.w3.org/2000/svg"
18
+ width="280"
19
+ height="200"
20
+ viewBox="-20 -20 280 200"
21
+ fill="none"
22
+ aria-hidden="true"
23
+ focusable="false"
24
+ {...props}
25
+ >
26
+ <defs>
27
+ <filter id={`${uid}-sBack`} x="-20%" y="-30%" width="140%" height="160%" color-interpolation-filters="sRGB">
28
+ <feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="#051729" flood-opacity="0.05"></feDropShadow>
29
+ </filter>
30
+ <filter id={`${uid}-sFront`} x="-30%" y="-40%" width="160%" height="190%" color-interpolation-filters="sRGB">
31
+ <feDropShadow dx="0" dy="8" stdDeviation="10" flood-color="#051729" flood-opacity="0.10"></feDropShadow>
32
+ </filter>
33
+ <filter id={`${uid}-sBadge`} x="-60%" y="-60%" width="220%" height="220%" color-interpolation-filters="sRGB">
34
+ <feDropShadow dx="0" dy="4" stdDeviation="5" flood-opacity="0.28" style="flood-color:var(--destructive)"></feDropShadow>
35
+ </filter>
36
+ </defs>
37
+ <circle cx="120" cy="80" r="69" stroke-width="20" style="stroke:color-mix(in oklab, var(--primary-50) 50%, var(--background))"></circle>
38
+ <circle cx="120" cy="80" r="40" stroke-width="16" style="stroke:var(--primary-50)"></circle>
39
+ <g transform="rotate(-9 94 70)" filter={`url(#${uid}-sBack)`}>
40
+ <rect x="34" y="34" width="120" height="72" rx="10" style="fill:var(--background)"></rect>
41
+ <rect x="34.5" y="34.5" width="119" height="71" rx="9.5" stroke-opacity="0.06" style="stroke:var(--foreground)"></rect>
42
+ </g>
43
+ <g transform="rotate(8 152 66)" filter={`url(#${uid}-sBack)`}>
44
+ <rect x="92" y="30" width="120" height="72" rx="10" style="fill:var(--background)"></rect>
45
+ <rect x="92.5" y="30.5" width="119" height="71" rx="9.5" stroke-opacity="0.06" style="stroke:var(--foreground)"></rect>
46
+ </g>
47
+ <g filter={`url(#${uid}-sFront)`}>
48
+ <rect x="50" y="52" width="140" height="84" rx="12" style="fill:var(--background)"></rect>
49
+ </g>
50
+ <rect x="50.5" y="52.5" width="139" height="83" rx="11.5" stroke-opacity="0.06" style="stroke:var(--foreground)"></rect>
51
+ <rect x="64" y="66" width="28" height="28" rx="8" style="fill:var(--muted)"></rect>
52
+ <path d="M71 80 h3 l2.5 -5 l4 10 l2.5 -5 h3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="stroke:var(--color-zinc-400, #a1a1aa)"></path>
53
+ <rect x="64" y="106" width="76" height="7" rx="3.5" style="fill:var(--border)"></rect>
54
+ <rect x="64" y="119" width="48" height="7" rx="3.5" style="fill:var(--muted)"></rect>
55
+ <g filter={`url(#${uid}-sBadge)`}>
56
+ <circle cx="180" cy="62" r="20" style="fill:var(--destructive)"></circle>
57
+ </g>
58
+ <circle cx="180" cy="62" r="20" stroke-width="4" style="stroke:var(--background)"></circle>
59
+ <rect x="178" y="51" width="4" height="14" rx="2" style="fill:var(--background)"></rect>
60
+ <circle cx="180" cy="71.5" r="2.4" style="fill:var(--background)"></circle>
61
+ </svg>