@cl-light-ds/ui 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +19 -17
- package/src/components/accordion/AccordionTrigger.astro +5 -1
- package/src/components/accordion/CHANGELOG.md +9 -0
- package/src/components/alert/Alert.astro +42 -3
- package/src/components/alert/CHANGELOG.md +19 -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/collapsible/CHANGELOG.md +9 -0
- package/src/components/collapsible/CollapsibleTrigger.astro +5 -1
- package/src/components/combobox/CHANGELOG.md +27 -0
- package/src/components/combobox/Combobox.astro +18 -1
- package/src/components/combobox/ComboboxTrigger.astro +32 -2
- package/src/components/command-search/CHANGELOG.md +57 -0
- package/src/components/command-search/CommandSearch.astro +594 -0
- package/src/components/command-search/CommandSearchGroup.astro +43 -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 +50 -0
- package/src/components/dialog/Dialog.astro +14 -3
- package/src/components/dialog/DialogClose.astro +28 -12
- package/src/components/dialog/DialogContent.astro +19 -1
- package/src/components/dialog/DialogTrigger.astro +5 -1
- package/src/components/dropdown-menu/CHANGELOG.md +32 -0
- package/src/components/dropdown-menu/DropdownMenu.astro +13 -3
- package/src/components/dropdown-menu/DropdownMenuRadioGroup.astro +17 -2
- package/src/components/dropdown-menu/DropdownMenuTrigger.astro +7 -1
- 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/field/CHANGELOG.md +18 -0
- package/src/components/field/Field.astro +18 -0
- package/src/components/field/FieldError.astro +13 -1
- package/src/components/field/FieldGroup.astro +12 -3
- package/src/components/hover-card/CHANGELOG.md +9 -0
- package/src/components/hover-card/HoverCardTrigger.astro +5 -1
- 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 +29 -0
- package/src/components/notifications-menu/NotificationsMenu.astro +375 -0
- package/src/components/notifications-menu/NotificationsMenuItem.astro +130 -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 +26 -0
- package/src/components/option-card/OptionCard.astro +143 -0
- package/src/components/option-card/index.ts +1 -0
- package/src/components/organisms/top-bar/CHANGELOG.md +27 -0
- package/src/components/organisms/top-bar/TopBar.astro +257 -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 +80 -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/popover/CHANGELOG.md +14 -0
- package/src/components/popover/PopoverClose.astro +2 -1
- package/src/components/popover/PopoverTrigger.astro +7 -1
- 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/select/CHANGELOG.md +8 -0
- package/src/components/select/SelectTrigger.astro +1 -1
- package/src/components/table/CHANGELOG.md +15 -0
- package/src/components/table/TableCell.astro +6 -0
- package/src/components/table/TableEditableCell.astro +2 -2
- package/src/components/table/TableHead.astro +5 -1
- package/src/components/table/TablePagination.astro +4 -4
- package/src/components/table/TableRow.astro +3 -1
- package/src/components/table/table-cell-variants.ts +2 -0
- package/src/components/tabs/CHANGELOG.md +16 -0
- package/src/components/tabs/Tabs.astro +26 -0
- package/src/components/tabs/TabsList.astro +30 -3
- 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/components/toggle-group/CHANGELOG.md +11 -0
- package/src/components/toggle-group/ToggleGroup.astro +19 -2
- package/src/components/toggle-group/ToggleGroupItem.astro +10 -0
- package/src/components/tooltip/CHANGELOG.md +26 -0
- package/src/components/tooltip/Tooltip.astro +31 -3
- package/src/components/tooltip/TooltipTrigger.astro +5 -1
- package/src/lib/command-search.ts +78 -0
- package/src/lib/dialog-focus.ts +73 -0
- package/src/lib/dropdown-menu-radio-groups.ts +79 -0
- package/src/lib/loading-overlay.ts +174 -0
- package/src/lib/scroll-lock.ts +97 -0
- package/src/lib/toast.ts +127 -0
- package/src/lib/toggle-shared.ts +9 -1
- package/src/lib/workspace-palette.ts +119 -0
- package/src/styles/CHANGELOG.md +35 -0
- package/src/styles/globals.css +109 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cl-light-ds/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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",
|
|
@@ -10,7 +10,11 @@ type IndicatorIcon = "plus-minus" | "chevron";
|
|
|
10
10
|
|
|
11
11
|
interface Props extends HTMLAttributes<"button"> {
|
|
12
12
|
class?: string;
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Radix-like: render wrapper so the child can be the interactive element.
|
|
15
|
+
* The wrapper is not a button, so the child sets its own type: give a
|
|
16
|
+
* `<button>`/`<Button>` child `type="button"` inside a `<form>` or it submits it.
|
|
17
|
+
*/
|
|
14
18
|
asChild?: boolean;
|
|
15
19
|
/** Leading icon rendered before the label (e.g. a lucide-astro icon). */
|
|
16
20
|
icon?: AstroComponent;
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the `Accordion` component family (`Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionContent`) are documented in this file.
|
|
4
4
|
|
|
5
|
+
## Date 15th Oct 2026
|
|
6
|
+
## Handover gaps: triggers don't submit forms (C29, gap G18)
|
|
7
|
+
|
|
8
|
+
### Docs
|
|
9
|
+
|
|
10
|
+
- `AccordionTrigger` already renders `type="button"` unless you pass a `type`; no change.
|
|
11
|
+
- **`asChild`:** the wrapper is a `<div>`, not a button, so it can't set a type. Inside a `<form>`, give your child `type="button"` (now in the prop's JSDoc).
|
|
12
|
+
|
|
13
|
+
|
|
5
14
|
## 2026-09-08 — Figma alignment (ClickLearn Lightning Design Library, node `133-7547`)
|
|
6
15
|
|
|
7
16
|
Aligned the component visually and functionally with Figma, while preserving the existing 4-part structure and all pre-existing props.
|
|
@@ -24,8 +24,26 @@
|
|
|
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
|
*
|
|
33
|
+
* ### Destructive as a tint
|
|
34
|
+
*
|
|
35
|
+
* `appearance="tint"` on the destructive variant draws it as a plain light-red
|
|
36
|
+
* note: `destructive` at 10% behind it, no border, the title in `destructive`
|
|
37
|
+
* and the description in `destructive` at 80% (D65). For a warning inside a
|
|
38
|
+
* dialog (delete), where the white card with a red icon reads too loud. Only
|
|
39
|
+
* the destructive variant has a tint (`warning` is always one); on the others
|
|
40
|
+
* `appearance` does nothing.
|
|
41
|
+
*
|
|
42
|
+
* <Alert variant="destructive" appearance="tint" size="large" dismissible={false}>
|
|
43
|
+
* <AlertTitle>This can't be undone</AlertTitle>
|
|
44
|
+
* <AlertDescription>Deleted workspaces and their recordings can't be restored.</AlertDescription>
|
|
45
|
+
* </Alert>
|
|
46
|
+
*
|
|
29
47
|
* @usage
|
|
30
48
|
*
|
|
31
49
|
* ```astro nocollapse
|
|
@@ -45,12 +63,15 @@ import type { AstroComponent } from "@lucide/astro";
|
|
|
45
63
|
import { cn } from "@cl-light-ds/ui/lib/utils";
|
|
46
64
|
import type { HTMLAttributes } from "astro/types";
|
|
47
65
|
|
|
48
|
-
type AlertVariant = "default" | "destructive" | "success";
|
|
66
|
+
type AlertVariant = "default" | "destructive" | "success" | "warning";
|
|
49
67
|
type AlertSize = "default" | "large";
|
|
68
|
+
type AlertAppearance = "default" | "tint";
|
|
50
69
|
|
|
51
70
|
interface Props extends HTMLAttributes<"div"> {
|
|
52
71
|
variant?: AlertVariant;
|
|
53
72
|
size?: AlertSize;
|
|
73
|
+
/** `"tint"`: the destructive variant as a light-red note, no border (C18, D65). */
|
|
74
|
+
appearance?: AlertAppearance;
|
|
54
75
|
icon?: AstroComponent;
|
|
55
76
|
dismissible?: boolean;
|
|
56
77
|
class?: string;
|
|
@@ -60,6 +81,7 @@ const {
|
|
|
60
81
|
class: className = "",
|
|
61
82
|
variant = "default",
|
|
62
83
|
size = "default",
|
|
84
|
+
appearance = "default",
|
|
63
85
|
icon,
|
|
64
86
|
dismissible = true,
|
|
65
87
|
...rest
|
|
@@ -77,15 +99,30 @@ const alertVariants = cva(
|
|
|
77
99
|
default: "bg-popover",
|
|
78
100
|
destructive: "bg-card",
|
|
79
101
|
success: "bg-card",
|
|
102
|
+
warning: "bg-callout-warning border-warning/40",
|
|
80
103
|
},
|
|
81
104
|
size: {
|
|
82
105
|
default: "items-center gap-2 p-2",
|
|
83
106
|
large: "items-start gap-4 p-4",
|
|
84
107
|
},
|
|
108
|
+
appearance: {
|
|
109
|
+
default: "",
|
|
110
|
+
tint: "",
|
|
111
|
+
},
|
|
85
112
|
},
|
|
113
|
+
compoundVariants: [
|
|
114
|
+
// C18, D65 — a plain light-red note: no border, red title, red text at 80%.
|
|
115
|
+
{
|
|
116
|
+
variant: "destructive",
|
|
117
|
+
appearance: "tint",
|
|
118
|
+
class:
|
|
119
|
+
"bg-destructive/10 border-transparent [&_[data-slot=alert-title]]:text-destructive [&_[data-slot=alert-description]]:text-destructive/80",
|
|
120
|
+
},
|
|
121
|
+
],
|
|
86
122
|
defaultVariants: {
|
|
87
123
|
variant: "default",
|
|
88
124
|
size: "default",
|
|
125
|
+
appearance: "default",
|
|
89
126
|
},
|
|
90
127
|
},
|
|
91
128
|
);
|
|
@@ -98,6 +135,7 @@ const iconVariants = cva(
|
|
|
98
135
|
default: "bg-muted text-foreground",
|
|
99
136
|
destructive: "bg-destructive text-destructive-foreground",
|
|
100
137
|
success: "bg-success text-success-foreground",
|
|
138
|
+
warning: "bg-warning text-warning-foreground",
|
|
101
139
|
},
|
|
102
140
|
},
|
|
103
141
|
defaultVariants: {
|
|
@@ -124,8 +162,9 @@ const contentVariants = cva("flex flex-1 text-sm", {
|
|
|
124
162
|
data-slot="alert"
|
|
125
163
|
data-variant={variant}
|
|
126
164
|
data-size={size}
|
|
165
|
+
data-appearance={appearance}
|
|
127
166
|
role="alert"
|
|
128
|
-
class={cn(alertVariants({ variant, size }), className)}
|
|
167
|
+
class={cn(alertVariants({ variant, size, appearance }), className)}
|
|
129
168
|
>
|
|
130
169
|
{
|
|
131
170
|
hasIcon && (
|
|
@@ -157,7 +196,7 @@ const contentVariants = cva("flex flex-1 text-sm", {
|
|
|
157
196
|
<button
|
|
158
197
|
type="button"
|
|
159
198
|
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"
|
|
199
|
+
class="flex shrink-0 items-center justify-center gap-0.5 rounded-lg bg-background p-2 group-data-[variant=warning]/alert:bg-transparent group-data-[variant=destructive]/alert:group-data-[appearance=tint]/alert:bg-transparent [&>svg]:size-4"
|
|
161
200
|
>
|
|
162
201
|
<XIcon />
|
|
163
202
|
<span class="sr-only">Dismiss</span>
|
|
@@ -2,4 +2,23 @@
|
|
|
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 (C18)
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- `appearance` prop (`"default" | "tint"`, default `"default"`), sets `data-appearance`. `variant="destructive" appearance="tint"` draws the destructive alert as a plain light-red note (D65): background `destructive` at 10%, no border, `AlertTitle` in `destructive` and `AlertDescription` in `destructive` at 80%; the dismiss button is transparent, as on `warning`. Replaces the workspace-selection delete dialog's `class="bg-destructive/10 border-transparent"` and its title/description colour classes.
|
|
11
|
+
- Uses `destructive` at 10% rather than `callout-error` (Red/50): D65 specifies the destructive red at 10%, which is a little stronger than Red/50, and the title and description in the destructive red (not `callout-foreground-error`), so the tint matches the reference exactly.
|
|
12
|
+
- Opt-in: the destructive alert's default look (white card, border, red icon) is unchanged. Only the destructive variant has a tint (`warning` is always tinted); on the other variants `appearance` does nothing.
|
|
13
|
+
- Storybook: a "Destructive as a tint" preview.
|
|
14
|
+
|
|
15
|
+
## Date 15th Oct 2026
|
|
16
|
+
## Workspace selection handover (N19, gap G13)
|
|
17
|
+
|
|
18
|
+
### Variants
|
|
19
|
+
|
|
20
|
+
- 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`).
|
|
21
|
+
- The dismiss button is transparent on `warning`, so no white square shows on the yellow background.
|
|
22
|
+
|
|
23
|
+
|
|
5
24
|
## 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,4 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the `Collapsible` component will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## Date 15th Oct 2026
|
|
6
|
+
## Handover gaps: triggers don't submit forms (C29, gap G18)
|
|
7
|
+
|
|
8
|
+
### Docs
|
|
9
|
+
|
|
10
|
+
- `CollapsibleTrigger` already renders `type="button"` unless you pass a `type`; no change.
|
|
11
|
+
- **`asChild`:** the wrapper is a `<div>`, not a button, so it can't set a type. Inside a `<form>`, give your child `type="button"` (now in the prop's JSDoc).
|
|
12
|
+
|
|
13
|
+
|
|
5
14
|
## Unreleased
|
|
@@ -4,7 +4,11 @@ import type { HTMLAttributes } from "astro/types";
|
|
|
4
4
|
|
|
5
5
|
interface Props extends HTMLAttributes<"button"> {
|
|
6
6
|
class?: string;
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Radix-like: render wrapper so the child can be the interactive element.
|
|
9
|
+
* The wrapper is not a button, so the child sets its own type: give a
|
|
10
|
+
* `<button>`/`<Button>` child `type="button"` inside a `<form>` or it submits it.
|
|
11
|
+
*/
|
|
8
12
|
asChild?: boolean;
|
|
9
13
|
}
|
|
10
14
|
|
|
@@ -2,6 +2,33 @@
|
|
|
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 (C7, gap G17)
|
|
7
|
+
|
|
8
|
+
- In "search in popup" mode, focus goes back to the trigger when the list closes (a pick or Escape), as with a native select. It dropped to the page, and out of a surrounding dialog. A workaround for a runtime bug (bejamas/data-slot).
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## Date 15th Oct 2026
|
|
12
|
+
## Handover gaps: triggers don't submit forms (C29, gap G18)
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **`ComboboxTrigger`: a `type` you pass now wins.** It always rendered `type="button"`, but after your attributes, so it overrode yours. The default is unchanged.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Workspace selection handover (C6; gaps G12, G17)
|
|
20
|
+
|
|
21
|
+
### `ComboboxTrigger` (C6)
|
|
22
|
+
|
|
23
|
+
- **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.
|
|
24
|
+
- **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.
|
|
25
|
+
- **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.
|
|
26
|
+
|
|
27
|
+
### Storybook
|
|
28
|
+
|
|
29
|
+
- "Search in popup, labelled, in sizes" preview.
|
|
30
|
+
|
|
31
|
+
|
|
5
32
|
## 2026-09-08 — Figma alignment (ClickLearn Lightning Design Library, node `5455-12863`)
|
|
6
33
|
|
|
7
34
|
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.
|
|
@@ -346,8 +346,25 @@ const {
|
|
|
346
346
|
<script>
|
|
347
347
|
import { createCombobox } from "@data-slot/combobox";
|
|
348
348
|
|
|
349
|
-
document.querySelectorAll('[data-slot="combobox"]').forEach((el) => {
|
|
349
|
+
document.querySelectorAll<HTMLElement>('[data-slot="combobox"]').forEach((el) => {
|
|
350
|
+
// Found before the runtime starts: it moves the content to <body> when it opens.
|
|
351
|
+
const content = el.querySelector('[data-slot="combobox-content"]');
|
|
352
|
+
const trigger = Array.from(el.querySelectorAll<HTMLElement>('[data-slot="combobox-trigger"]')).find(
|
|
353
|
+
(t) => !content?.contains(t),
|
|
354
|
+
);
|
|
355
|
+
|
|
350
356
|
createCombobox(el);
|
|
357
|
+
|
|
358
|
+
// "Search in popup": the search input lives in the list, which hides on a
|
|
359
|
+
// pick or Escape, so focus would drop to the page (and out of a dialog).
|
|
360
|
+
// Put it back on the trigger, as a native select does.
|
|
361
|
+
el.addEventListener("combobox:open-change", (event) => {
|
|
362
|
+
if ((event as CustomEvent<{ open: boolean }>).detail.open || !trigger) return;
|
|
363
|
+
requestAnimationFrame(() => {
|
|
364
|
+
const at = document.activeElement;
|
|
365
|
+
if (!at || at === document.body || content?.contains(at)) trigger.focus();
|
|
366
|
+
});
|
|
367
|
+
});
|
|
351
368
|
});
|
|
352
369
|
|
|
353
370
|
// Clear button delegation
|