@cl-light-ds/ui 0.4.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 (91) hide show
  1. package/package.json +19 -17
  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/card/CHANGELOG.md +9 -0
  9. package/src/components/card/Card.astro +22 -0
  10. package/src/components/combobox/CHANGELOG.md +14 -0
  11. package/src/components/combobox/ComboboxTrigger.astro +31 -1
  12. package/src/components/command-search/CHANGELOG.md +27 -0
  13. package/src/components/command-search/CommandSearch.astro +515 -0
  14. package/src/components/command-search/CommandSearchGroup.astro +32 -0
  15. package/src/components/command-search/CommandSearchItem.astro +64 -0
  16. package/src/components/command-search/classes.ts +18 -0
  17. package/src/components/command-search/index.ts +12 -0
  18. package/src/components/command-search/nothing-matches.svg +42 -0
  19. package/src/components/dialog/CHANGELOG.md +13 -0
  20. package/src/components/dialog/DialogClose.astro +26 -12
  21. package/src/components/dropdown-menu/CHANGELOG.md +7 -0
  22. package/src/components/dropdown-menu/dropdown-menu-item-variants.ts +1 -1
  23. package/src/components/empty-state/CHANGELOG.md +18 -0
  24. package/src/components/empty-state/EmptyState.astro +157 -0
  25. package/src/components/empty-state/illustrations/AllCaughtUp.astro +52 -0
  26. package/src/components/empty-state/illustrations/EmptyWorkspaces.astro +56 -0
  27. package/src/components/empty-state/illustrations/LoadError.astro +61 -0
  28. package/src/components/empty-state/illustrations/NoResults.astro +60 -0
  29. package/src/components/empty-state/illustrations/NothingMatches.astro +66 -0
  30. package/src/components/empty-state/illustrations/index.ts +27 -0
  31. package/src/components/empty-state/index.ts +7 -0
  32. package/src/components/favorite-toggle/CHANGELOG.md +13 -0
  33. package/src/components/favorite-toggle/FavoriteToggle.astro +94 -0
  34. package/src/components/favorite-toggle/index.ts +1 -0
  35. package/src/components/input-group/CHANGELOG.md +7 -0
  36. package/src/components/input-group/InputGroup.astro +2 -0
  37. package/src/components/loading-overlay/CHANGELOG.md +25 -0
  38. package/src/components/loading-overlay/LoadingOverlay.astro +179 -0
  39. package/src/components/loading-overlay/index.ts +8 -0
  40. package/src/components/notifications-menu/CHANGELOG.md +22 -0
  41. package/src/components/notifications-menu/NotificationsMenu.astro +374 -0
  42. package/src/components/notifications-menu/NotificationsMenuItem.astro +129 -0
  43. package/src/components/notifications-menu/all-caught-up.svg +28 -0
  44. package/src/components/notifications-menu/index.ts +3 -0
  45. package/src/components/notifications-menu/types.ts +32 -0
  46. package/src/components/option-card/CHANGELOG.md +16 -0
  47. package/src/components/option-card/OptionCard.astro +135 -0
  48. package/src/components/option-card/index.ts +1 -0
  49. package/src/components/organisms/top-bar/CHANGELOG.md +21 -0
  50. package/src/components/organisms/top-bar/TopBar.astro +264 -0
  51. package/src/components/organisms/top-bar/index.ts +2 -0
  52. package/src/components/organisms/top-bar/types.ts +30 -0
  53. package/src/components/organisms/workspace/CHANGELOG.md +73 -0
  54. package/src/components/organisms/workspace/OrganisationOverview.astro +62 -0
  55. package/src/components/organisms/workspace/RecentWorkspaceCard.astro +235 -0
  56. package/src/components/organisms/workspace/RecentWorkspaces.astro +93 -0
  57. package/src/components/organisms/workspace/WorkspaceAvatar.astro +144 -0
  58. package/src/components/organisms/workspace/WorkspaceCard.astro +172 -0
  59. package/src/components/organisms/workspace/WorkspaceGlyph.astro +62 -0
  60. package/src/components/organisms/workspace/WorkspaceIcon.astro +42 -0
  61. package/src/components/organisms/workspace/WorkspaceIconPicker.astro +288 -0
  62. package/src/components/organisms/workspace/WorkspaceIconPickerGlyph.astro +44 -0
  63. package/src/components/organisms/workspace/icons.ts +98 -0
  64. package/src/components/organisms/workspace/index.ts +35 -0
  65. package/src/components/organisms/workspace/labels.ts +54 -0
  66. package/src/components/organisms/workspace/recent-card-rings-strong.png +0 -0
  67. package/src/components/organisms/workspace/types.ts +30 -0
  68. package/src/components/profile-menu/CHANGELOG.md +22 -0
  69. package/src/components/profile-menu/ProfileMenu.astro +236 -0
  70. package/src/components/profile-menu/index.ts +2 -0
  71. package/src/components/profile-menu/types.ts +27 -0
  72. package/src/components/search-field/CHANGELOG.md +14 -0
  73. package/src/components/search-field/SearchField.astro +163 -0
  74. package/src/components/search-field/index.ts +1 -0
  75. package/src/components/table/CHANGELOG.md +9 -0
  76. package/src/components/table/TableCell.astro +6 -0
  77. package/src/components/table/TableHead.astro +5 -1
  78. package/src/components/table/TableRow.astro +3 -1
  79. package/src/components/table/table-cell-variants.ts +2 -0
  80. package/src/components/toast/CHANGELOG.md +32 -0
  81. package/src/components/toast/Toast.astro +145 -0
  82. package/src/components/toast/Toaster.astro +322 -0
  83. package/src/components/toast/index.ts +9 -0
  84. package/src/components/toggle/CHANGELOG.md +7 -0
  85. package/src/lib/command-search.ts +73 -0
  86. package/src/lib/loading-overlay.ts +174 -0
  87. package/src/lib/toast.ts +127 -0
  88. package/src/lib/toggle-shared.ts +4 -1
  89. package/src/lib/workspace-palette.ts +119 -0
  90. package/src/styles/CHANGELOG.md +22 -0
  91. package/src/styles/globals.css +87 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cl-light-ds/ui",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "ClickLearn Lightning Design System — Astro UI component library.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -34,22 +34,24 @@
34
34
  "typescript": "^5.9.3"
35
35
  },
36
36
  "dependencies": {
37
- "@data-slot/accordion": "^0.2.166",
38
- "@data-slot/collapsible": "^0.2.166",
39
- "@data-slot/combobox": "^0.2.166",
40
- "@data-slot/dialog": "^0.2.166",
41
- "@data-slot/dropdown-menu": "^0.2.166",
42
- "@data-slot/hover-card": "^0.2.166",
43
- "@data-slot/navigation-menu": "^0.2.166",
44
- "@data-slot/popover": "^0.2.166",
45
- "@data-slot/radio-group": "^0.2.166",
46
- "@data-slot/select": "^0.2.166",
47
- "@data-slot/slider": "^0.2.166",
48
- "@data-slot/switch": "^0.2.166",
49
- "@data-slot/tabs": "^0.2.166",
50
- "@data-slot/toggle": "^0.2.166",
51
- "@data-slot/toggle-group": "^0.2.166",
52
- "@data-slot/tooltip": "^0.2.166",
37
+ "@data-slot/accordion": "^1.1.2",
38
+ "@data-slot/collapsible": "^1.1.2",
39
+ "@data-slot/combobox": "^1.1.2",
40
+ "@data-slot/command": "^1.1.2",
41
+ "@data-slot/dialog": "^1.1.2",
42
+ "@data-slot/dropdown-menu": "^1.1.2",
43
+ "@data-slot/hover-card": "^1.1.2",
44
+ "@data-slot/navigation-menu": "^1.1.2",
45
+ "@data-slot/popover": "^1.1.2",
46
+ "@data-slot/radio-group": "^1.1.2",
47
+ "@data-slot/select": "^1.1.2",
48
+ "@data-slot/slider": "^1.1.2",
49
+ "@data-slot/switch": "^1.1.2",
50
+ "@data-slot/tabs": "^1.1.2",
51
+ "@data-slot/toast": "^1.1.2",
52
+ "@data-slot/toggle": "^1.1.2",
53
+ "@data-slot/toggle-group": "^1.1.2",
54
+ "@data-slot/tooltip": "^1.1.2",
53
55
  "@lucide/astro": "^1.14.0",
54
56
  "bejamas": "^0.4.1",
55
57
  "class-variance-authority": "^0.7.1",
@@ -24,6 +24,10 @@
24
24
  * <Alert variant="success" icon={CircleCheckIcon}>
25
25
  * <AlertTitle>Payment successful.</AlertTitle>
26
26
  * </Alert>
27
+ *
28
+ * <Alert variant="warning" icon={TriangleAlertIcon}>
29
+ * <AlertTitle>Linked recordings won't be included.</AlertTitle>
30
+ * </Alert>
27
31
  * </div>
28
32
  *
29
33
  * @usage
@@ -45,7 +49,7 @@ import type { AstroComponent } from "@lucide/astro";
45
49
  import { cn } from "@cl-light-ds/ui/lib/utils";
46
50
  import type { HTMLAttributes } from "astro/types";
47
51
 
48
- type AlertVariant = "default" | "destructive" | "success";
52
+ type AlertVariant = "default" | "destructive" | "success" | "warning";
49
53
  type AlertSize = "default" | "large";
50
54
 
51
55
  interface Props extends HTMLAttributes<"div"> {
@@ -77,6 +81,7 @@ const alertVariants = cva(
77
81
  default: "bg-popover",
78
82
  destructive: "bg-card",
79
83
  success: "bg-card",
84
+ warning: "bg-callout-warning border-warning/40",
80
85
  },
81
86
  size: {
82
87
  default: "items-center gap-2 p-2",
@@ -98,6 +103,7 @@ const iconVariants = cva(
98
103
  default: "bg-muted text-foreground",
99
104
  destructive: "bg-destructive text-destructive-foreground",
100
105
  success: "bg-success text-success-foreground",
106
+ warning: "bg-warning text-warning-foreground",
101
107
  },
102
108
  },
103
109
  defaultVariants: {
@@ -157,7 +163,7 @@ const contentVariants = cva("flex flex-1 text-sm", {
157
163
  <button
158
164
  type="button"
159
165
  data-slot="alert-dismiss"
160
- class="flex shrink-0 items-center justify-center gap-0.5 rounded-lg bg-background p-2 [&>svg]:size-4"
166
+ class="flex shrink-0 items-center justify-center gap-0.5 rounded-lg bg-background p-2 group-data-[variant=warning]/alert:bg-transparent [&>svg]:size-4"
161
167
  >
162
168
  <XIcon />
163
169
  <span class="sr-only">Dismiss</span>
@@ -2,4 +2,13 @@
2
2
 
3
3
  All notable changes to the `Alert` component will be documented in this file.
4
4
 
5
+ ## Date 15th Oct 2026
6
+ ## Workspace selection handover (N19, gap G13)
7
+
8
+ ### Variants
9
+
10
+ - Added a `warning` variant (`variant: "default" | "destructive" | "success" | "warning"`). Background `bg-callout-warning` (`--callout-background-warning`, Figma Yellow/50), border `warning` at 40%, icon in a solid `warning` circle with `warning-foreground`. Replaces the workspace-selection screen's hand-styled `Alert` (`bg-badge-warning/10 border-badge-warning/40`).
11
+ - The dismiss button is transparent on `warning`, so no white square shows on the yellow background.
12
+
13
+
5
14
  ## Unreleased
@@ -95,6 +95,10 @@
95
95
  * <Badge slot="badge" variant="secondary" shape="sharp" size="sm">Value</Badge>
96
96
  * </Button>
97
97
  *
98
+ * ### Loading
99
+ *
100
+ * <Button variant="destructive" loading loadingText="Deleting…">Delete workspace</Button>
101
+ *
98
102
  * ### Destructive
99
103
  *
100
104
  * <Button variant="destructive">Click me</Button>
@@ -125,6 +129,7 @@ import type { HTMLAttributes } from "astro/types";
125
129
  import { cva, type VariantProps } from "class-variance-authority";
126
130
 
127
131
  import { cn } from "@cl-light-ds/ui/lib/utils";
132
+ import { Spinner } from "../spinner";
128
133
 
129
134
  const buttonVariants = cva(
130
135
  "group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:border-transparent disabled:bg-muted disabled:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0",
@@ -143,6 +148,7 @@ const buttonVariants = cva(
143
148
  sm: "h-8 gap-1 px-2 text-xs [&_svg]:size-3.5",
144
149
  lg: "h-10 gap-1 px-2.5 text-sm [&_svg]:size-5",
145
150
  icon: "size-9 [&_svg]:size-4",
151
+ "icon-xs": "size-6 [&_svg]:size-3.5",
146
152
  "icon-sm": "size-8 [&_svg]:size-3.5",
147
153
  "icon-lg": "size-10 [&_svg]:size-5",
148
154
  },
@@ -168,6 +174,15 @@ type ButtonOwnProps = ButtonVariantProps & {
168
174
  leadIcon?: boolean;
169
175
  tailIcon?: boolean;
170
176
  badge?: boolean;
177
+ /**
178
+ * Busy: disabled, with a spinner and `loadingText` in the label's place. The
179
+ * label and the loading content share one grid cell, so the width holds.
180
+ * Passing `loading` (true or false) renders that structure; a script can then
181
+ * switch it with `data-loading="true"` plus `disabled`.
182
+ */
183
+ loading?: boolean;
184
+ /** Text beside the spinner while loading, e.g. "Deleting…". Spinner only when omitted. */
185
+ loadingText?: string;
171
186
  };
172
187
 
173
188
  export type Props = (ButtonAsButton | ButtonAsAnchor) & ButtonOwnProps;
@@ -183,9 +198,21 @@ const {
183
198
  leadIcon = hasLeadIconSlot,
184
199
  tailIcon = hasTailIconSlot,
185
200
  badge = hasBadgeSlot,
201
+ loading,
202
+ loadingText,
186
203
  class: className = "",
187
204
  ...rest
188
205
  } = Astro.props as Props;
206
+
207
+ // Loading (N21): only buttons given `loading` or `loadingText` get the stacked label.
208
+ const hasLoading = loading !== undefined || loadingText !== undefined;
209
+ const loadingAttrs = hasLoading
210
+ ? {
211
+ "data-loading": loading ? "true" : "false",
212
+ "aria-busy": loading ? "true" : undefined,
213
+ ...(loading ? (Tag === "a" ? { "aria-disabled": "true" } : { disabled: true }) : {}),
214
+ }
215
+ : {};
189
216
  ---
190
217
 
191
218
  {
@@ -202,32 +229,64 @@ const {
202
229
  data-variant={variant}
203
230
  data-size={size}
204
231
  class={cn(
205
- "focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-chart-1 in-data-[slot=button-group]:hover:bg-background-hover in-data-[slot=button-group]:hover:border-badge-accent aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border border-transparent bg-clip-padding font-semibold aria-invalid:ring-3 ease-out duration-150 active:scale-98",
232
+ "focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-chart-1 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border border-transparent bg-clip-padding font-semibold aria-invalid:ring-3 ease-out duration-150 active:scale-98",
206
233
  buttonVariants({ variant, size }),
207
234
  className,
208
235
  )}
209
236
  {...rest}
237
+ {...loadingAttrs}
210
238
  >
211
239
  {
212
- leadIcon && (
213
- <span data-slot="button-lead-icon" class="inline-flex shrink-0 items-center">
214
- <slot name="lead-icon" />
215
- </span>
216
- )
217
- }
218
- <slot />
219
- {
220
- badge && (
221
- <span data-slot="button-badge" class="inline-flex shrink-0 items-center">
222
- <slot name="badge" />
223
- </span>
224
- )
225
- }
226
- {
227
- tailIcon && (
228
- <span data-slot="button-tail-icon" class="inline-flex shrink-0 items-center">
229
- <slot name="tail-icon" />
240
+ hasLoading ? (
241
+ <span data-slot="button-loading-stack" class="grid">
242
+ <span
243
+ data-slot="button-content"
244
+ class="col-start-1 row-start-1 inline-flex items-center justify-center gap-1 group-data-[loading=true]/button:invisible"
245
+ >
246
+ {leadIcon && (
247
+ <span data-slot="button-lead-icon" class="inline-flex shrink-0 items-center">
248
+ <slot name="lead-icon" />
249
+ </span>
250
+ )}
251
+ <slot />
252
+ {badge && (
253
+ <span data-slot="button-badge" class="inline-flex shrink-0 items-center">
254
+ <slot name="badge" />
255
+ </span>
256
+ )}
257
+ {tailIcon && (
258
+ <span data-slot="button-tail-icon" class="inline-flex shrink-0 items-center">
259
+ <slot name="tail-icon" />
260
+ </span>
261
+ )}
262
+ </span>
263
+ <span
264
+ data-slot="button-loading"
265
+ class="invisible col-start-1 row-start-1 inline-flex items-center justify-center gap-2 group-data-[loading=true]/button:visible"
266
+ >
267
+ <Spinner aria-hidden="true" role="presentation" />
268
+ {loadingText}
269
+ </span>
230
270
  </span>
271
+ ) : (
272
+ <>
273
+ {leadIcon && (
274
+ <span data-slot="button-lead-icon" class="inline-flex shrink-0 items-center">
275
+ <slot name="lead-icon" />
276
+ </span>
277
+ )}
278
+ <slot />
279
+ {badge && (
280
+ <span data-slot="button-badge" class="inline-flex shrink-0 items-center">
281
+ <slot name="badge" />
282
+ </span>
283
+ )}
284
+ {tailIcon && (
285
+ <span data-slot="button-tail-icon" class="inline-flex shrink-0 items-center">
286
+ <slot name="tail-icon" />
287
+ </span>
288
+ )}
289
+ </>
231
290
  )
232
291
  }
233
292
  </Tag>
@@ -2,6 +2,28 @@
2
2
 
3
3
  All notable changes to the `Button` component will be documented in this file.
4
4
 
5
+ ## Date 15th Oct 2026
6
+ ## Workspace selection handover (N21)
7
+
8
+ ### Props
9
+
10
+ - Added `loading` and `loadingText`. While loading the button is disabled (`aria-disabled` on `as="a"`), `aria-busy="true"` and `data-loading="true"`, and shows a `Spinner` with `loadingText` in place of the label. The label and the loading content are stacked in one grid cell (the label goes `invisible`), so the button keeps its width (D178). Without `loadingText` only the spinner shows.
11
+ - Only buttons given `loading` (true or false) or `loadingText` get the extra markup (`button-loading-stack` > `button-content` + `button-loading`); every other button renders exactly as before. A script can switch a `loading={false}` button with `data-loading="true"` plus `disabled`.
12
+ - Storybook: "Loading" preview, with a scripted example.
13
+
14
+ ## Date 15th Oct 2026
15
+ ## Workspace selection handover (C2, C3, C25)
16
+
17
+ ### Sizes
18
+
19
+ - Added `icon-xs` (24px, 14px icon) for small icon buttons such as a notification's dismiss ✕. Was `icon-sm` resized with `size-6`.
20
+
21
+ ### States: inside a `ButtonGroup` (gap G9)
22
+
23
+ - **Removed** the group-wide hover override (`in-data-[slot=button-group]:hover:bg-background-hover hover:border-badge-accent`). Every variant now keeps its own hover inside a group: a primary button hovers `primary/80` (it turned grey), an outline button keeps its `border` (it turned light blue, unlike the same button outside a group).
24
+ - **Figma:** this reverses the grouped Hover state from the 8th Sep alignment (`bg-background-hover` + `accent` border, master symbols node `114:11217`). Agreed with the designer (D71, D152); Figma's Button Group states will be updated to match.
25
+
26
+
5
27
  ## Date 8th Sep 2026
6
28
  ## Figma alignment (node 114-6897, prop table 7185-13418)
7
29
 
@@ -102,14 +102,25 @@ const {
102
102
  } = Astro.props as Props;
103
103
 
104
104
  export const buttonGroupVariants = cva(
105
- "flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-e-lg [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
105
+ [
106
+ "flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10",
107
+ // A primary button beside a separator: a light blue separator, and no transparent end border
108
+ // (it isn't painted, so the page showed through as a white strip).
109
+ "[&>[data-slot=button][data-variant=default]+[data-slot=button-group-separator]]:bg-chart-2",
110
+ // A field joined to a button (search + Search): while the field has focus, one ring goes round
111
+ // both. The field's own ring is off and the button's border takes the ring colour.
112
+ "has-[>[data-slot=input-group]_[data-slot=input-group-control]:focus-visible]:rounded-lg has-[>[data-slot=input-group]_[data-slot=input-group-control]:focus-visible]:ring-3 has-[>[data-slot=input-group]_[data-slot=input-group-control]:focus-visible]:ring-ring/50 transition-shadow",
113
+ "[&>[data-slot=input-group]]:has-[[data-slot=input-group-control]:focus-visible]:ring-0",
114
+ "has-[>[data-slot=input-group]_[data-slot=input-group-control]:focus-visible]:[&>[data-slot=button]]:border-ring",
115
+ "has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-e-lg [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
116
+ ],
106
117
  {
107
118
  variants: {
108
119
  orientation: {
109
120
  horizontal:
110
- "*:data-slot:rounded-e-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-e-lg! [&>[data-slot]~[data-slot]]:border-s-0 [&>[data-slot]~[data-slot]]:rounded-s-none [&>[data-slot]~[data-slot]_[data-slot]]:border-s-0 [&>[data-slot]~[data-slot]_[data-slot]]:rounded-s-none",
121
+ "[&>[data-slot]:has(+[data-slot=button-group-separator])]:border-e-0 *:data-slot:rounded-e-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-e-lg! [&>[data-slot]~[data-slot]]:border-s-0 [&>[data-slot]~[data-slot]]:rounded-s-none [&>[data-slot]~[data-slot]_[data-slot]]:border-s-0 [&>[data-slot]~[data-slot]_[data-slot]]:rounded-s-none",
111
122
  vertical:
112
- "flex-col *:data-slot:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-lg! [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0 [&>[data-slot]~[data-slot]_[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]_[data-slot]]:border-t-0",
123
+ "flex-col [&>[data-slot]:has(+[data-slot=button-group-separator])]:border-b-0 *:data-slot:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-lg! [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0 [&>[data-slot]~[data-slot]_[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]_[data-slot]]:border-t-0",
113
124
  },
114
125
  },
115
126
  defaultVariants: {
@@ -2,6 +2,23 @@
2
2
 
3
3
  All notable changes to the `ButtonGroup` component will be documented in this file.
4
4
 
5
+ ## Date 15th Oct 2026
6
+ ## Workspace selection handover (C2, C4)
7
+
8
+ ### Split button (C2)
9
+
10
+ - A button followed by a `ButtonGroupSeparator` has no end border (no bottom border when vertical). Its transparent border isn't painted (`bg-clip-padding`), so the page showed through as a white strip beside the separator.
11
+ - A separator right after a primary (`variant="default"`) button is `chart-2` (light blue) instead of `input` grey.
12
+
13
+ ### Field joined to a button (C4)
14
+
15
+ - When a `ButtonGroup` holds an `InputGroup` and its field has keyboard focus, one ring (`ring-3 ring-ring/50`) goes round the whole group: the field's own ring is off and the buttons' border turns `ring`. Tab on to the button and it shows its own ring alone.
16
+
17
+ ### Storybook
18
+
19
+ - Added "Split button" and "Field joined to a button" previews.
20
+
21
+
5
22
  ## Date 8th Sep 2026
6
23
  ## Figma alignment (node 114-9882)
7
24
 
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to the `Card` component will be documented in this file.
4
4
 
5
+ ## Date 15th Oct 2026
6
+ ## Workspace selection handover (C14)
7
+
8
+ ### Added
9
+
10
+ - `interactive` prop (default `false`): for cards that open something, the card lifts 4px (`-translate-y-1`) with `shadow-lg` on hover and on keyboard focus, on the card itself (`as="a"`) or inside it (`has-focus-visible`, for a stretched link with other controls beside it). Only keyboard focus lifts it, so a mouse click on a control in the card doesn't leave it raised. No lift with `prefers-reduced-motion` (the shadow still changes). Sets `data-interactive`.
11
+ - Opt-in rather than always on: most cards are static, and lifting them would suggest they can be clicked.
12
+ - Storybook: an "Interactive" preview.
13
+
5
14
  ## Date 9th Sep 2026
6
15
  ## Figma alignment (node 54-1310, prop table 7350-1279)
7
16
 
@@ -102,6 +102,16 @@
102
102
  * Pass `as="a"` (with `href`) to render the whole card as a real, natively focusable anchor — this is how to make Figma's Focus state reachable via keyboard, since a plain `<div>` can't receive `:focus-visible`.
103
103
  *
104
104
  * <Card as="a" href="#">...</Card>
105
+ *
106
+ * ### Interactive (hover lift)
107
+ *
108
+ * `interactive` is for cards that open something (the whole card is a link,
109
+ * either `as="a"` or a stretched link inside it): the card lifts 4px with a
110
+ * larger shadow on hover and while it, or anything in it, has keyboard focus.
111
+ * No lift with reduced motion (the shadow still changes). Off by default, so
112
+ * static cards don't suggest they can be clicked.
113
+ *
114
+ * <Card as="a" href="/workspace" interactive>...</Card>
105
115
  */
106
116
 
107
117
  import { cva } from "class-variance-authority";
@@ -124,6 +134,8 @@ type CardOwnProps = {
124
134
  variant?: CardVariant;
125
135
  size?: CardSize;
126
136
  orientation?: CardOrientation;
137
+ /** Lift 4px with a larger shadow on hover and keyboard focus, for cards that open something. */
138
+ interactive?: boolean;
127
139
  };
128
140
 
129
141
  export type Props = (CardAsDiv | CardAsAnchor) & CardOwnProps;
@@ -134,9 +146,17 @@ const {
134
146
  variant = "default",
135
147
  size = "default",
136
148
  orientation = "vertical",
149
+ interactive = false,
137
150
  ...rest
138
151
  } = Astro.props as Props;
139
152
 
153
+ /**
154
+ * C14 — the hover lift. Keyboard focus only (`focus-visible`, on the card or
155
+ * inside it): a mouse click on a control in the card doesn't keep it lifted.
156
+ */
157
+ const interactiveClasses =
158
+ "transition-[translate,box-shadow] duration-200 hover:shadow-lg focus-visible:shadow-lg has-focus-visible:shadow-lg motion-safe:hover:-translate-y-1 motion-safe:focus-visible:-translate-y-1 motion-safe:has-focus-visible:-translate-y-1";
159
+
140
160
  const cardVariants = cva(
141
161
  "group/card relative flex overflow-clip rounded-lg text-sm text-card-foreground outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-chart-1",
142
162
  {
@@ -185,11 +205,13 @@ const cardVariants = cva(
185
205
  data-variant={variant}
186
206
  data-size={size}
187
207
  data-orientation={orientation}
208
+ data-interactive={interactive ? "" : undefined}
188
209
  class={cn(
189
210
  cardVariants({ variant, size, orientation }),
190
211
  "*:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg",
191
212
  variant === "overlay" &&
192
213
  "[&_[data-slot=card-media]]:absolute [&_[data-slot=card-media]]:inset-0 [&_[data-slot=card-media]]:z-0 [&_[data-slot=card-media]]:rounded-lg [&_[data-slot=card-media]]:p-0 [&_[data-slot=card-media]_img]:rounded-lg",
214
+ interactive && interactiveClasses,
193
215
  className,
194
216
  )}
195
217
  >
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to the `Combobox` component family (`Combobox`, `ComboboxInput`, `ComboboxContent`, `ComboboxList`, `ComboboxItem`, `ComboboxGroup`, `ComboboxLabel`, `ComboboxSeparator`, `ComboboxEmpty`, `ComboboxTrigger`, `ComboboxValue`) are documented in this file.
4
4
 
5
+ ## Date 15th Oct 2026
6
+ ## Workspace selection handover (C6; gaps G12, G17)
7
+
8
+ ### `ComboboxTrigger` (C6)
9
+
10
+ - **Styled as a field:** the Input's border, `bg-card`, shadow and focus ring, sized by its `size` prop (`sm` 32px, `default` 36px, `lg` 40px; padding and text as `Input`). It was unstyled and `size` did nothing.
11
+ - **In the Tab order:** `tabindex` defaults to `0`. The runtime takes a trigger without one out of the Tab order, so the "search in popup" field couldn't be reached by keyboard. Pass `tabindex={-1}` to opt out.
12
+ - **Named by its label:** the runtime names every trigger "Toggle". When the trigger has an `id` and a `<label for>` points at it, it gets `aria-labelledby` the label and its value ("Language English"). An author's own `aria-labelledby` is left alone.
13
+
14
+ ### Storybook
15
+
16
+ - "Search in popup, labelled, in sizes" preview.
17
+
18
+
5
19
  ## 2026-09-08 — Figma alignment (ClickLearn Lightning Design Library, node `5455-12863`)
6
20
 
7
21
  Styling-only pass — no props, components, or behavior were added or removed. The implementation (including the shared `InputGroup`/`InputGroupInput` it delegates trigger styling to) was already close to Figma; this fixed the remaining concrete mismatches found by comparing computed styles against the Figma spec.
@@ -10,17 +10,30 @@ interface Props extends HTMLAttributes<"button"> {
10
10
  const {
11
11
  class: className = "",
12
12
  size = "default",
13
+ tabindex = 0,
13
14
  ...rest
14
15
  } = Astro.props as Props;
15
16
  ---
16
17
 
18
+ {
19
+ /*
20
+ The "search in popup" field: it shows the committed value and is the control
21
+ people Tab to, so it has the Input's field styling and stays in the Tab order
22
+ (the runtime takes a trigger without a tabindex out of it). The runtime also
23
+ names every trigger "Toggle"; the script below names it by its <label for>
24
+ and its value instead.
25
+ */
26
+ }
17
27
  <button
18
28
  {...rest}
19
29
  type="button"
30
+ tabindex={tabindex}
20
31
  data-slot="combobox-trigger"
21
32
  data-size={size}
22
33
  class={cn(
23
- "[&_svg:not([class*='size-'])]:size-4 rounded-lg flex w-full items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=combobox-value]:line-clamp-1 *:data-[slot=combobox-value]:flex *:data-[slot=combobox-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
34
+ "[&_svg:not([class*='size-'])]:size-4 rounded-lg flex w-full items-center justify-between gap-2 whitespace-nowrap text-left outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=combobox-value]:line-clamp-1 *:data-[slot=combobox-value]:flex *:data-[slot=combobox-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
35
+ "border-input bg-card focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 aria-invalid:border-destructive border shadow-xs transition-[color,box-shadow] focus-visible:ring-3 aria-invalid:ring-3",
36
+ "data-[size=sm]:h-8 data-[size=sm]:px-2.5 data-[size=sm]:text-xs data-[size=default]:h-9 data-[size=default]:px-2.5 data-[size=default]:text-sm data-[size=lg]:h-10 data-[size=lg]:px-3.5 data-[size=lg]:text-base",
24
37
  className,
25
38
  )}
26
39
  >
@@ -28,3 +41,20 @@ const {
28
41
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="text-muted-foreground size-4 pointer-events-none"
29
42
  data-slot="combobox-icon"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m6 9l6 6l6-6"/></svg>
30
43
  </button>
44
+
45
+ <script>
46
+ // Name each trigger by its label and current value ("Language English"),
47
+ // unless the author already named it with aria-labelledby.
48
+ let uid = 0;
49
+ const ensureId = (el: Element, prefix: string) => (el.id ||= `${prefix}-${++uid}`);
50
+
51
+ for (const trigger of document.querySelectorAll<HTMLButtonElement>("[data-slot='combobox-trigger']")) {
52
+ if (!trigger.id || trigger.hasAttribute("aria-labelledby")) continue;
53
+ const label = document.querySelector(`label[for="${CSS.escape(trigger.id)}"]`);
54
+ if (!label) continue;
55
+ const value = trigger.querySelector("[data-slot='combobox-value']");
56
+ const ids = [ensureId(label, "combobox-label")];
57
+ if (value) ids.push(ensureId(value, "combobox-value"));
58
+ trigger.setAttribute("aria-labelledby", ids.join(" "));
59
+ }
60
+ </script>
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `CommandSearch` component family (`CommandSearch`, `CommandSearchGroup`, `CommandSearchItem`, and the helpers in `@cl-light-ds/ui/lib/command-search`) are documented in this file.
4
+
5
+ ## Date 15th Oct 2026
6
+ ## Workspace selection handover (N11)
7
+
8
+ New component, replacing the workspace-selection screen's hand-composed `CommandPalette.astro` and its palette code in `interactions.ts` (gap G8).
9
+
10
+ ### `CommandSearch`
11
+
12
+ - Trigger (D63): a 32px outline `Button` "Search everything…" with a search icon and a `Kbd` showing ⌘K on a Mac, Ctrl K elsewhere. ⌘K / Ctrl K opens the first palette on the page (`shortcut`, default on). `hideTrigger` to open it from your own control (`openCommandSearch()` or `dialog:set`).
13
+ - Palette: the DS `Dialog` (672px, `sm:max-w-2xl`), a large search field (`InputGroup` size lg) with a secondary Search button, results, and a footer with ↑ ↓ / ↵ / esc hints (replaceable via the `footer` slot).
14
+ - Searching (D116, D122, D142): nothing is listed until a search runs; a hint says how. Enter or Search runs it; typing alone changes nothing, and emptying the field goes back to the hint. While it runs: a spinner and "Searching everything…". Enter again chooses the highlighted item.
15
+ - Results grouped (`CommandSearchGroup`, heading in small caps with the number of matches beside it, D67); rows (`CommandSearchItem`) with optional `media` and `meta` slots, highlighted while active. Choosing fires `command-search:select` (cancelable), closes the palette, and follows `href` if given.
16
+ - "Nothing matches" (D187): the 184×124 illustration (`nothing-matches.svg`), a title and a line of text.
17
+ - Two modes: `filter` (default) filters the items you put in, by label and keywords, case-insensitive substring, keeping your order; `delay` shows the loading state first. `remote` fires `command-search:search` with the query and waits for `setCommandSearchResults(root, groups, { query })` (stale answers are ignored), rendering the groups itself, or keeping rows you added.
18
+ - A11y: the runtime makes the field a combobox over a listbox (`aria-activedescendant`, options, groups labelled by their heading); a polite live region announces "Searching everything…", the number of results or "Nothing matches".
19
+
20
+ ### Built on `@data-slot/command`
21
+
22
+ - Used: item and group registration, `role`s, active item, arrow / Home / End / Alt+arrow navigation, Enter and click selection, hover highlight, hiding groups with no matches, the empty element, `setSearch` / `select`.
23
+ - Added on top: search on Enter or the button instead of as you type (the field's `input` event is stopped before the runtime sees it), the idle hint and loading states, a substring filter that keeps the given order, match counts, the remote mode and its result rendering, `href` navigation and closing on select, the platform shortcut label and the global shortcut. The runtime's Ctrl+J/K/N/P bindings are off (Ctrl K opens the palette).
24
+
25
+ ### Storybook
26
+
27
+ - "Command Search" section: filter mode with workspaces, recordings and assets; remote mode with a simulated server.