@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.
- package/package.json +19 -17
- package/src/components/alert/Alert.astro +8 -2
- package/src/components/alert/CHANGELOG.md +9 -0
- package/src/components/button/Button.astro +78 -19
- package/src/components/button/CHANGELOG.md +22 -0
- package/src/components/button-group/ButtonGroup.astro +14 -3
- package/src/components/button-group/CHANGELOG.md +17 -0
- package/src/components/card/CHANGELOG.md +9 -0
- package/src/components/card/Card.astro +22 -0
- package/src/components/combobox/CHANGELOG.md +14 -0
- package/src/components/combobox/ComboboxTrigger.astro +31 -1
- package/src/components/command-search/CHANGELOG.md +27 -0
- package/src/components/command-search/CommandSearch.astro +515 -0
- package/src/components/command-search/CommandSearchGroup.astro +32 -0
- package/src/components/command-search/CommandSearchItem.astro +64 -0
- package/src/components/command-search/classes.ts +18 -0
- package/src/components/command-search/index.ts +12 -0
- package/src/components/command-search/nothing-matches.svg +42 -0
- package/src/components/dialog/CHANGELOG.md +13 -0
- package/src/components/dialog/DialogClose.astro +26 -12
- package/src/components/dropdown-menu/CHANGELOG.md +7 -0
- package/src/components/dropdown-menu/dropdown-menu-item-variants.ts +1 -1
- package/src/components/empty-state/CHANGELOG.md +18 -0
- package/src/components/empty-state/EmptyState.astro +157 -0
- package/src/components/empty-state/illustrations/AllCaughtUp.astro +52 -0
- package/src/components/empty-state/illustrations/EmptyWorkspaces.astro +56 -0
- package/src/components/empty-state/illustrations/LoadError.astro +61 -0
- package/src/components/empty-state/illustrations/NoResults.astro +60 -0
- package/src/components/empty-state/illustrations/NothingMatches.astro +66 -0
- package/src/components/empty-state/illustrations/index.ts +27 -0
- package/src/components/empty-state/index.ts +7 -0
- package/src/components/favorite-toggle/CHANGELOG.md +13 -0
- package/src/components/favorite-toggle/FavoriteToggle.astro +94 -0
- package/src/components/favorite-toggle/index.ts +1 -0
- package/src/components/input-group/CHANGELOG.md +7 -0
- package/src/components/input-group/InputGroup.astro +2 -0
- package/src/components/loading-overlay/CHANGELOG.md +25 -0
- package/src/components/loading-overlay/LoadingOverlay.astro +179 -0
- package/src/components/loading-overlay/index.ts +8 -0
- package/src/components/notifications-menu/CHANGELOG.md +22 -0
- package/src/components/notifications-menu/NotificationsMenu.astro +374 -0
- package/src/components/notifications-menu/NotificationsMenuItem.astro +129 -0
- package/src/components/notifications-menu/all-caught-up.svg +28 -0
- package/src/components/notifications-menu/index.ts +3 -0
- package/src/components/notifications-menu/types.ts +32 -0
- package/src/components/option-card/CHANGELOG.md +16 -0
- package/src/components/option-card/OptionCard.astro +135 -0
- package/src/components/option-card/index.ts +1 -0
- package/src/components/organisms/top-bar/CHANGELOG.md +21 -0
- package/src/components/organisms/top-bar/TopBar.astro +264 -0
- package/src/components/organisms/top-bar/index.ts +2 -0
- package/src/components/organisms/top-bar/types.ts +30 -0
- package/src/components/organisms/workspace/CHANGELOG.md +73 -0
- package/src/components/organisms/workspace/OrganisationOverview.astro +62 -0
- package/src/components/organisms/workspace/RecentWorkspaceCard.astro +235 -0
- package/src/components/organisms/workspace/RecentWorkspaces.astro +93 -0
- package/src/components/organisms/workspace/WorkspaceAvatar.astro +144 -0
- package/src/components/organisms/workspace/WorkspaceCard.astro +172 -0
- package/src/components/organisms/workspace/WorkspaceGlyph.astro +62 -0
- package/src/components/organisms/workspace/WorkspaceIcon.astro +42 -0
- package/src/components/organisms/workspace/WorkspaceIconPicker.astro +288 -0
- package/src/components/organisms/workspace/WorkspaceIconPickerGlyph.astro +44 -0
- package/src/components/organisms/workspace/icons.ts +98 -0
- package/src/components/organisms/workspace/index.ts +35 -0
- package/src/components/organisms/workspace/labels.ts +54 -0
- package/src/components/organisms/workspace/recent-card-rings-strong.png +0 -0
- package/src/components/organisms/workspace/types.ts +30 -0
- package/src/components/profile-menu/CHANGELOG.md +22 -0
- package/src/components/profile-menu/ProfileMenu.astro +236 -0
- package/src/components/profile-menu/index.ts +2 -0
- package/src/components/profile-menu/types.ts +27 -0
- package/src/components/search-field/CHANGELOG.md +14 -0
- package/src/components/search-field/SearchField.astro +163 -0
- package/src/components/search-field/index.ts +1 -0
- package/src/components/table/CHANGELOG.md +9 -0
- package/src/components/table/TableCell.astro +6 -0
- package/src/components/table/TableHead.astro +5 -1
- package/src/components/table/TableRow.astro +3 -1
- package/src/components/table/table-cell-variants.ts +2 -0
- package/src/components/toast/CHANGELOG.md +32 -0
- package/src/components/toast/Toast.astro +145 -0
- package/src/components/toast/Toaster.astro +322 -0
- package/src/components/toast/index.ts +9 -0
- package/src/components/toggle/CHANGELOG.md +7 -0
- package/src/lib/command-search.ts +73 -0
- package/src/lib/loading-overlay.ts +174 -0
- package/src/lib/toast.ts +127 -0
- package/src/lib/toggle-shared.ts +4 -1
- package/src/lib/workspace-palette.ts +119 -0
- package/src/styles/CHANGELOG.md +22 -0
- 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.
|
|
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": "^
|
|
38
|
-
"@data-slot/collapsible": "^
|
|
39
|
-
"@data-slot/combobox": "^
|
|
40
|
-
"@data-slot/
|
|
41
|
-
"@data-slot/
|
|
42
|
-
"@data-slot/
|
|
43
|
-
"@data-slot/
|
|
44
|
-
"@data-slot/
|
|
45
|
-
"@data-slot/
|
|
46
|
-
"@data-slot/
|
|
47
|
-
"@data-slot/
|
|
48
|
-
"@data-slot/
|
|
49
|
-
"@data-slot/
|
|
50
|
-
"@data-slot/
|
|
51
|
-
"@data-slot/
|
|
52
|
-
"@data-slot/
|
|
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
|
|
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
|
-
|
|
213
|
-
<span data-slot="button-
|
|
214
|
-
<
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
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.
|