@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
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* One result row in a `CommandSearch`: optional media (an icon or avatar), the
|
|
4
|
+
* label, and optional muted text on the right (`meta` slot, e.g. "in Acme
|
|
5
|
+
* Finance"). Highlighted while active (arrow keys or hover); Enter or a click
|
|
6
|
+
* chooses it.
|
|
7
|
+
*
|
|
8
|
+
* Searching matches `label` and `keywords` (case-insensitive, anywhere in the
|
|
9
|
+
* text), so give every item a `label`. `value` identifies it in
|
|
10
|
+
* `command-search:select`; it defaults to the label, so pass one when two items
|
|
11
|
+
* can share a name.
|
|
12
|
+
*/
|
|
13
|
+
import { cn } from "@cl-light-ds/ui/lib/utils";
|
|
14
|
+
import type { HTMLAttributes } from "astro/types";
|
|
15
|
+
|
|
16
|
+
import { commandSearchItemClasses, commandSearchLabelClasses, commandSearchMetaClasses } from "./classes";
|
|
17
|
+
|
|
18
|
+
interface Props extends HTMLAttributes<"div"> {
|
|
19
|
+
/** The text searched and shown (the default slot can replace what is shown). */
|
|
20
|
+
label: string;
|
|
21
|
+
/** Unique value, sent with `command-search:select`. Defaults to `label`. */
|
|
22
|
+
value?: string;
|
|
23
|
+
/** More terms that find this item. */
|
|
24
|
+
keywords?: string[];
|
|
25
|
+
/** Navigate here when chosen. */
|
|
26
|
+
href?: string;
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
class?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
label,
|
|
33
|
+
value = label,
|
|
34
|
+
keywords = [],
|
|
35
|
+
href,
|
|
36
|
+
disabled = false,
|
|
37
|
+
class: className = "",
|
|
38
|
+
...rest
|
|
39
|
+
} = Astro.props as Props;
|
|
40
|
+
|
|
41
|
+
const hasMedia = Astro.slots.has("media");
|
|
42
|
+
const hasMeta = Astro.slots.has("meta");
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
<div
|
|
46
|
+
data-slot="command-item"
|
|
47
|
+
data-value={value}
|
|
48
|
+
data-label={label}
|
|
49
|
+
data-keywords={keywords.length ? keywords.join(",") : undefined}
|
|
50
|
+
data-href={href}
|
|
51
|
+
data-disabled={disabled ? "true" : undefined}
|
|
52
|
+
class={cn(commandSearchItemClasses, className)}
|
|
53
|
+
{...rest}
|
|
54
|
+
>
|
|
55
|
+
{hasMedia && <slot name="media" />}
|
|
56
|
+
<span class={commandSearchLabelClasses}><slot>{label}</slot></span>
|
|
57
|
+
{
|
|
58
|
+
hasMeta && (
|
|
59
|
+
<span class={commandSearchMetaClasses}>
|
|
60
|
+
<slot name="meta" />
|
|
61
|
+
</span>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Class strings shared by the Astro parts and the results the script renders
|
|
3
|
+
* from `setCommandSearchResults()`, so both look the same.
|
|
4
|
+
*/
|
|
5
|
+
export const commandSearchGroupClasses = "flex flex-col gap-1";
|
|
6
|
+
|
|
7
|
+
export const commandSearchHeadingClasses =
|
|
8
|
+
"text-muted-foreground flex items-baseline gap-2 px-2 pt-1 text-xs font-semibold tracking-wide uppercase";
|
|
9
|
+
|
|
10
|
+
export const commandSearchCountClasses = "font-normal tabular-nums";
|
|
11
|
+
|
|
12
|
+
export const commandSearchItemClasses =
|
|
13
|
+
"hover:bg-background-hover data-[selected=true]:bg-background-hover aria-disabled:pointer-events-none aria-disabled:opacity-50 flex cursor-pointer items-center gap-3 rounded-md px-2 py-2 text-sm transition-colors outline-none";
|
|
14
|
+
|
|
15
|
+
export const commandSearchLabelClasses = "min-w-0 flex-1 truncate";
|
|
16
|
+
|
|
17
|
+
export const commandSearchMetaClasses =
|
|
18
|
+
"text-muted-foreground flex max-w-56 min-w-0 shrink-0 items-center gap-1.5 truncate text-xs";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { default as CommandSearch } from "./CommandSearch.astro";
|
|
2
|
+
export { default as CommandSearchGroup } from "./CommandSearchGroup.astro";
|
|
3
|
+
export { default as CommandSearchItem } from "./CommandSearchItem.astro";
|
|
4
|
+
export {
|
|
5
|
+
openCommandSearch,
|
|
6
|
+
closeCommandSearch,
|
|
7
|
+
setCommandSearchResults,
|
|
8
|
+
} from "../../lib/command-search";
|
|
9
|
+
export type {
|
|
10
|
+
CommandSearchResultGroup,
|
|
11
|
+
CommandSearchResultItem,
|
|
12
|
+
} from "../../lib/command-search";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="184" height="124" viewBox="-12 -12 184 124" fill="none" xmlns:c2pa="http://c2pa.org/manifest"><metadata><c2pa:manifest>AAAWgmp1bWIAAAAeanVtZGMycGEAEQAQgAAAqgA4m3EDYzJwYQAAABZcanVtYgAAAEdqdW1kYzJtYQARABCAAACqADibcQN1cm46YzJwYTplODIyOThlNC1jNGQ2LTQwYTQtOGU3Ny1hYWY2OTcxYjM2OTIAAAADl2p1bWIAAAApanVtZGMyYXMAEQAQgAAAqgA4m3EDYzJwYS5hc3NlcnRpb25zAAAAALxqdW1iAAAARGp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuaW5ncmVkaWVudC52MwAAAAAYYzJzaKqugmJMsqYC8viyOBbRbhIAAABwY2JvcqNpZGM6Zm9ybWF0bWltYWdlL3N2Zyt4bWxqaW5zdGFuY2VJRHgseG1wOmlpZDo0MWIxMTE5NC02ZTQ0LTRlMzUtOTU5MS0xYjY1M2ViNWUyNTJscmVsYXRpb25zaGlwaHBhcmVudE9mAAAB4mp1bWIAAABBanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5hY3Rpb25zLnYyAAAAABhjMnNouXZVAYt+XHRTFpV6GfvZOgAAAZljYm9yomdhY3Rpb25zgqJmYWN0aW9ua2MycGEub3BlbmVkanBhcmFtZXRlcnOha2luZ3JlZGllbnRzgaJjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFggVgKr/4T9xsUXed1EvB+dGw4hkEVHK5LpA88frFSdO46kZmFjdGlvbngdY29tLmFudGhyb3BpYy5jbGF1ZGUucHJvdmlkZWRqcGFyYW1ldGVyc6F4H2NvbS5hbnRocm9waWMub3JpZ2luLWNvbmZpZGVuY2VndW5rbm93bmtkZXNjcmlwdGlvbnhmQ2xhdWRlIHByb3ZpZGVkIHRoaXMgZmlsZSBhdCB0aGUgcmVxdWVzdCBvZiBhIHVzZXIgYW5kIG1heSBoYXZlIGNyZWF0ZWQgb3IgbW9kaWZpZWQgdGhlIGZpbGUgY29udGVudHMubXNvZnR3YXJlQWdlbnShZG5hbWVmQ2xhdWRlcmFsbEFjdGlvbnNJbmNsdWRlZPUAAADIanVtYgAAAEBqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmhhc2guZGF0YQAAAAAYYzJzaNb7nH0x+zMDR8g/a81tYxUAAACAY2JvcqVjYWxnZnNoYTI1NmNwYWRNAAAAAAAAAAAAAAAAAGRoYXNoWCB8IybuiUW/D1GR1OoeQ940FnLg0RBFnD1vqhoG+GGrd2RuYW1lbmp1bWJmIG1hbmlmZXN0amV4Y2x1c2lvbnOBomVzdGFydBimZmxlbmd0aBkeBAAAAj5qdW1iAAAAJ2p1bWRjMmNsABEAEIAAAKoAOJtxA2MycGEuY2xhaW0udjIAAAACD2Nib3KlY2FsZ2ZzaGEyNTZpc2lnbmF0dXJleE1zZWxmI2p1bWJmPS9jMnBhL3VybjpjMnBhOmU4MjI5OGU0LWM0ZDYtNDBhNC04ZTc3LWFhZjY5NzFiMzY5Mi9jMnBhLnNpZ25hdHVyZWppbnN0YW5jZUlEeCx4bXA6aWlkOjExNmJiZmVjLWQxNjEtNDM1YS1iNzkxLTM1YjRlNTM4MzljYXJjcmVhdGVkX2Fzc2VydGlvbnODomN1cmx4LXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaW5ncmVkaWVudC52M2RoYXNoWCBWAqv/hP3GxRd53US8H50bDiGQRUcrkukDzx+sVJ07jqJjdXJseCpzZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmFjdGlvbnMudjJkaGFzaFggWNJwmATXeq3w/IjLwhFkIQQ1fvC8gXIQ/CRzxbJAz4aiY3VybHgpc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5oYXNoLmRhdGFkaGFzaFgg3x7o2h9I/3508c0ufWUiNo2ld5HoEd8e5vBDPLAJa0t0Y2xhaW1fZ2VuZXJhdG9yX2luZm+jZG5hbWVvQW50aHJvcGljIEZpbGVzZ3ZlcnNpb25lMS4wLjBrc3BlY1ZlcnNpb25lMi40LjAAABA4anVtYgAAAChqdW1kYzJjcwARABCAAACqADibcQNjMnBhLnNpZ25hdHVyZQAAABAIY2JvctKEWQISogEmGCFZAgowggIGMIIBjaADAgECAhRA5aAK7sI50L64g/oGQgU9Z1UTADAKBggqhkjOPQQDAzBJMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEuMCwGA1UEAxMlQW50aHJvcGljIENvbnRlbnQgQ3JlZGVudGlhbHMgUm9vdCBDQTAeFw0yNjA4MDcxODQzNTZaFw0yODA4MDYxOTQzNTZaMEQxFzAVBgNVBAoTDkFudGhyb3BpYywgUEJDMSkwJwYDVQQDEyBBbnRocm9waWMgQ2xhdWRlIENvbnRlbnQgU2lnbmluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJh6CmvLUBgFFNU0vUKlOVtE6djd17L5SuwX0LemFisBM3dkd/3cyjxFA3Qo5S46fX0/ihY0VZ7mfb9KF703t5OjWDBWMA4GA1UdDwEB/wQEAwIHgDAVBgNVHSUEDjAMBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUzlHiBIFOZFsj+OPEz5o+nMHXXMIwCgYIKoZIzj0EAwMDZwAwZAIwMXMdFJ4BetLLVY7ORuE9noqbbAZOZn/aArXyTwFAZfKrPzxF2vPoJNf1+UCdg1XGAjBwX1zd9WGqYkqmL5SFqw1QySjr1zJfpJM9+1rdDwSPLMOPOjKuiXjoU/pUUeG9RwmhY3BhZFkNngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPZYQBQ6fFVaIySaTkZAvfiIN0Vu6miBN8tscscJnyxn5eEUXN2x49nEHZ+/F/hQydOi3j5/kryWAn8JzV93uuuUbDg=</c2pa:manifest></metadata>
|
|
2
|
+
<defs>
|
|
3
|
+
<filter id="sBack" x="-30%" y="-30%" width="160%" height="160%" color-interpolation-filters="sRGB">
|
|
4
|
+
<feDropShadow dx="0" dy="1.5" stdDeviation="2" flood-color="#051729" flood-opacity="0.05"></feDropShadow>
|
|
5
|
+
</filter>
|
|
6
|
+
<filter id="sFront" x="-40%" y="-30%" width="180%" height="170%" color-interpolation-filters="sRGB">
|
|
7
|
+
<feDropShadow dx="0" dy="5" stdDeviation="6" flood-color="#051729" flood-opacity="0.10"></feDropShadow>
|
|
8
|
+
</filter>
|
|
9
|
+
<filter id="sLens" x="-60%" y="-60%" width="220%" height="220%" color-interpolation-filters="sRGB">
|
|
10
|
+
<feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#3785E0" flood-opacity="0.28"></feDropShadow>
|
|
11
|
+
</filter>
|
|
12
|
+
</defs>
|
|
13
|
+
<circle cx="80" cy="50" r="44" stroke="#F5F9FE" stroke-width="12"></circle>
|
|
14
|
+
<circle cx="80" cy="50" r="24" stroke="#EBF3FC" stroke-width="10"></circle>
|
|
15
|
+
<g transform="rotate(-12 44 52)">
|
|
16
|
+
<g filter="url(#sBack)"><rect x="22" y="26" width="44" height="52" rx="8" fill="#FFFFFF"></rect></g>
|
|
17
|
+
<rect x="22.5" y="26.5" width="43" height="51" rx="7.5" stroke="#09090B" stroke-opacity="0.06"></rect>
|
|
18
|
+
<rect x="28" y="32" width="14" height="14" rx="4" fill="#D7E7F9"></rect>
|
|
19
|
+
<rect x="28" y="54" width="24" height="4" rx="2" fill="#E4E4E7"></rect>
|
|
20
|
+
<rect x="28" y="62" width="16" height="4" rx="2" fill="#F4F4F5"></rect>
|
|
21
|
+
</g>
|
|
22
|
+
<g transform="rotate(12 116 52)">
|
|
23
|
+
<g filter="url(#sBack)"><rect x="94" y="26" width="44" height="52" rx="8" fill="#FFFFFF"></rect></g>
|
|
24
|
+
<rect x="94.5" y="26.5" width="43" height="51" rx="7.5" stroke="#09090B" stroke-opacity="0.06"></rect>
|
|
25
|
+
<rect x="100" y="34" width="28" height="4" rx="2" fill="#E4E4E7"></rect>
|
|
26
|
+
<rect x="100" y="42" width="32" height="4" rx="2" fill="#F4F4F5"></rect>
|
|
27
|
+
<rect x="100" y="50" width="22" height="4" rx="2" fill="#F4F4F5"></rect>
|
|
28
|
+
<rect x="100" y="58" width="28" height="4" rx="2" fill="#F4F4F5"></rect>
|
|
29
|
+
</g>
|
|
30
|
+
<g filter="url(#sFront)"><rect x="56" y="18" width="48" height="58" rx="9" fill="#FFFFFF"></rect></g>
|
|
31
|
+
<rect x="56.5" y="18.5" width="47" height="57" rx="8.5" stroke="#09090B" stroke-opacity="0.06"></rect>
|
|
32
|
+
<rect x="62" y="24" width="36" height="22" rx="5" fill="#EBF3FC"></rect>
|
|
33
|
+
<path d="M77 30.5 L84.5 35 L77 39.5 Z" fill="#87B6EC" stroke="#87B6EC" stroke-width="1.5" stroke-linejoin="round"></path>
|
|
34
|
+
<rect x="62" y="53" width="28" height="4" rx="2" fill="#E4E4E7"></rect>
|
|
35
|
+
<rect x="62" y="61" width="18" height="4" rx="2" fill="#F4F4F5"></rect>
|
|
36
|
+
<g filter="url(#sLens)">
|
|
37
|
+
<line x1="113.5" y1="79.5" x2="122" y2="88" stroke="#1F5FAA" stroke-width="6" stroke-linecap="round"></line>
|
|
38
|
+
<circle cx="104" cy="70" r="13" fill="#FFFFFF" stroke="#3785E0" stroke-width="4"></circle>
|
|
39
|
+
</g>
|
|
40
|
+
<line x1="100" y1="66" x2="108" y2="74" stroke="#87B6EC" stroke-width="2.2" stroke-linecap="round"></line>
|
|
41
|
+
<line x1="108" y1="66" x2="100" y2="74" stroke="#87B6EC" stroke-width="2.2" stroke-linecap="round"></line>
|
|
42
|
+
</svg>
|
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the `Dialog` component family (`Dialog`, `DialogTrigger`, `DialogContent`, `DialogOverlay`, `DialogHeader`, `DialogFooter`, `DialogTitle`, `DialogDescription`, `DialogClose`) are documented in this file.
|
|
4
4
|
|
|
5
|
+
## Date 15th Oct 2026
|
|
6
|
+
## Workspace selection handover (C7, gap G17)
|
|
7
|
+
|
|
8
|
+
### Popovers inside a dialog
|
|
9
|
+
|
|
10
|
+
- The popover runtime moves open content to `<body>`, out of the dialog, so the dialog's focus trap pulled focus back and Tab walked out of the popover. Now (`lib/dialog-focus.ts`, installed by `Dialog`) Tab from a popover's trigger goes into it, Tab and Shift+Tab go round its controls, and Escape returns to the trigger. Tied to the dialog through the trigger's `aria-controls`. A workaround for a runtime bug (bejamas/data-slot).
|
|
11
|
+
- Storybook: "Popover and combobox inside a dialog" preview.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## Date 15th Oct 2026
|
|
15
|
+
## Workspace selection handover (C8, gap G5)
|
|
16
|
+
|
|
17
|
+
### Scroll lock
|
|
18
|
+
|
|
19
|
+
- `lockScroll` no longer uses the `@data-slot/dialog` runtime's lock (`overflow: hidden` + `scrollbar-gutter: stable` on `<html>`), which left an undimmed white band where the scrollbar was. `lib/scroll-lock.ts` locks instead: the scrollbar stays and nothing moves; the wheel, touch and paging keys don't scroll the page; scrollable areas under the pointer (a long list in the dialog) still scroll; a dragged scrollbar is put back. `lockScroll={false}` still turns it off. A workaround for a runtime bug (bejamas/data-slot); remove it if the runtime fixes its lock.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## Date 15th Oct 2026
|
|
23
|
+
## Handover gaps: dialog widths (C24)
|
|
24
|
+
|
|
25
|
+
### `DialogContent`
|
|
26
|
+
|
|
27
|
+
- **`size` prop** (`"sm" | "md" | "lg" | "xl"`, default `"sm"`): the panel's width from the `sm` breakpoint. `sm` 448px (`sm:max-w-md`, today's default, unchanged), `md` 512px (`sm:max-w-lg`, the delete, duplicate and export confirmations, D111), `lg` 672px (`sm:max-w-2xl`, command search), `xl` 1024px (`sm:max-w-5xl`, New workspace, D89). Below `sm` the panel is still the viewport minus 2rem. Sets `data-size`. A `sm:max-w-*` class you pass still wins.
|
|
28
|
+
|
|
29
|
+
### Storybook
|
|
30
|
+
|
|
31
|
+
- "Sizes" preview with all four; the "Cancel with DialogClose asChild" preview uses `size="md"` instead of a class.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## Handover gaps: triggers don't submit forms (C29, gap G18)
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- **`DialogClose` renders `type="button"`** unless you pass a `type` (including the ✕ that `DialogContent` adds). It renders a `Button`, which has no type, so a dialog whose content is a `<form>` was submitted by its own close button.
|
|
39
|
+
- `DialogTrigger` already rendered `type="button"`; no change.
|
|
40
|
+
- **`asChild` (`DialogTrigger`, `DialogClose`):** 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 props' JSDoc).
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
## Workspace selection handover (C9, C16; gap G15)
|
|
44
|
+
|
|
45
|
+
### `DialogClose`
|
|
46
|
+
|
|
47
|
+
- **`asChild` (C9):** `<DialogClose asChild><Button variant="outline">Cancel</Button></DialogClose>` closes the dialog with your own button. The wrapper is `display: contents`. Without it, wrapping a Button nested a button in a button.
|
|
48
|
+
- **✕ hover (C16):** `hover:bg-background-button-hover` (zinc-200). The ghost hover (zinc-100) didn't show on light grey surfaces.
|
|
49
|
+
|
|
50
|
+
### Storybook
|
|
51
|
+
|
|
52
|
+
- "Cancel with DialogClose asChild" preview.
|
|
53
|
+
|
|
54
|
+
|
|
5
55
|
## Earlier (commit `c3cec7d`, undocumented at the time)
|
|
6
56
|
|
|
7
57
|
- `DialogContent`: `rounded-xl` → `rounded-lg` (Figma binds 8px radius).
|
|
@@ -189,8 +189,19 @@ const {
|
|
|
189
189
|
|
|
190
190
|
<script>
|
|
191
191
|
import { createDialog } from "@data-slot/dialog";
|
|
192
|
+
import { lockScrollWhileOpen } from "@cl-light-ds/ui/lib/scroll-lock";
|
|
193
|
+
import { trapTabInPopoversInDialogs } from "@cl-light-ds/ui/lib/dialog-focus";
|
|
192
194
|
|
|
193
|
-
document
|
|
194
|
-
|
|
195
|
-
|
|
195
|
+
document.querySelectorAll<HTMLElement>('[data-slot="dialog"]').forEach((el) => {
|
|
196
|
+
// The runtime's own lock reserves a scrollbar gutter the overlay doesn't
|
|
197
|
+
// cover; lib/scroll-lock keeps the scrollbar and blocks scrolling instead.
|
|
198
|
+
const dialog = createDialog(el, { lockScroll: false });
|
|
199
|
+
if (el.dataset.lockScroll !== "false") {
|
|
200
|
+
lockScrollWhileOpen(el, "dialog:change", () => dialog.isOpen);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Tab stays inside a popover opened from a dialog (the runtime moves the
|
|
205
|
+
// popover to <body>, out of the dialog's focus trap).
|
|
206
|
+
trapTabInPopoversInDialogs();
|
|
196
207
|
</script>
|
|
@@ -5,19 +5,35 @@ import Button from "../button/Button.astro";
|
|
|
5
5
|
|
|
6
6
|
interface Props extends HTMLAttributes<"button"> {
|
|
7
7
|
class?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Radix-like: render a wrapper so your own element closes the dialog, e.g. a
|
|
10
|
+
* Cancel `<Button>`. The wrapper is `display: contents`, so the child lays out
|
|
11
|
+
* as if it were placed directly. The wrapper is not a button: inside a
|
|
12
|
+
* `<form>`, give the child `type="button"` or it submits the form.
|
|
13
|
+
*/
|
|
14
|
+
asChild?: boolean;
|
|
8
15
|
}
|
|
9
16
|
|
|
10
|
-
const { class: className = "", ...rest } = Astro.props as Props;
|
|
17
|
+
const { class: className = "", asChild = false, type = "button", ...rest } = Astro.props as Props;
|
|
11
18
|
---
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
{
|
|
21
|
+
asChild ? (
|
|
22
|
+
<div data-slot="dialog-close" data-dialog-close data-as-child class={cn("contents", className)}>
|
|
23
|
+
<slot />
|
|
24
|
+
</div>
|
|
25
|
+
) : (
|
|
26
|
+
<Button
|
|
27
|
+
{...rest}
|
|
28
|
+
type={type}
|
|
29
|
+
data-slot="dialog-close"
|
|
30
|
+
data-dialog-close
|
|
31
|
+
variant="ghost"
|
|
32
|
+
size="icon-sm"
|
|
33
|
+
class={cn("absolute top-2 right-2 hover:bg-background-button-hover", className)}
|
|
34
|
+
>
|
|
35
|
+
<slot />
|
|
36
|
+
{!Astro.slots.has("default") && <span class="sr-only">Close</span>}
|
|
37
|
+
</Button>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -5,12 +5,28 @@ import type { HTMLAttributes } from "astro/types";
|
|
|
5
5
|
import DialogClose from "./DialogClose.astro";
|
|
6
6
|
import DialogOverlay from "./DialogOverlay.astro";
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Widths from the `sm` breakpoint (below it the panel is the viewport minus
|
|
10
|
+
* 2rem): `sm` 448px (default), `md` 512px (delete, duplicate and export
|
|
11
|
+
* confirmations, D111), `lg` 672px (command search), `xl` 1024px (New
|
|
12
|
+
* workspace, D89).
|
|
13
|
+
*/
|
|
14
|
+
const sizes = {
|
|
15
|
+
sm: "sm:max-w-md",
|
|
16
|
+
md: "sm:max-w-lg",
|
|
17
|
+
lg: "sm:max-w-2xl",
|
|
18
|
+
xl: "sm:max-w-5xl",
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
8
21
|
interface Props extends HTMLAttributes<"div"> {
|
|
9
22
|
showCloseButton?: boolean;
|
|
23
|
+
/** Panel width: `sm` 448px (default), `md` 512px, `lg` 672px, `xl` 1024px. */
|
|
24
|
+
size?: keyof typeof sizes;
|
|
10
25
|
}
|
|
11
26
|
|
|
12
27
|
const {
|
|
13
28
|
showCloseButton = true,
|
|
29
|
+
size = "sm",
|
|
14
30
|
class: className = "",
|
|
15
31
|
...rest
|
|
16
32
|
} = Astro.props as Props;
|
|
@@ -21,8 +37,10 @@ const {
|
|
|
21
37
|
<div
|
|
22
38
|
data-slot="dialog-content"
|
|
23
39
|
hidden
|
|
40
|
+
data-size={size}
|
|
24
41
|
class={cn(
|
|
25
|
-
"bg-card border-border data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 max-w-[calc(100%-2rem)] rounded-lg border shadow-lg duration-100
|
|
42
|
+
"bg-card border-border data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 max-w-[calc(100%-2rem)] rounded-lg border shadow-lg duration-100 fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none",
|
|
43
|
+
sizes[size] ?? sizes.sm,
|
|
26
44
|
className,
|
|
27
45
|
)}
|
|
28
46
|
{...rest}
|
|
@@ -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,38 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the `DropdownMenu` component will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## Date 15th Oct 2026
|
|
6
|
+
## Workspace selection handover (C10, gap G16)
|
|
7
|
+
|
|
8
|
+
### Several radio groups in one menu
|
|
9
|
+
|
|
10
|
+
- `DropdownMenuRadioGroup` takes `name` (and `defaultValue`). A named group has its own value, so one menu can hold "Sort by" and "Order". The `@data-slot/dropdown-menu` runtime holds one radio value per menu, so a named group's items run on its checkbox values (`lib/dropdown-menu-radio-groups.ts`): exactly one stays checked, picking the checked one again doesn't untick it, and they're still announced as `menuitemradio`. The group mirrors its value in `data-value` and the root emits `dropdown-menu:group-value-change` (`{ name, value, previousValue }`). Item values must be unique within the menu. Unnamed groups behave as before. A workaround until the runtime supports radio groups (bejamas/data-slot).
|
|
11
|
+
- Storybook: "Two radio groups in one menu" preview.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## Date 15th Oct 2026
|
|
15
|
+
## Workspace selection handover (C8, gap G5)
|
|
16
|
+
|
|
17
|
+
### Scroll lock
|
|
18
|
+
|
|
19
|
+
- `lockScroll` uses `lib/scroll-lock.ts` instead of the `@data-slot/dropdown-menu` runtime's lock, which added a scrollbar gutter and made short pages jump sideways when a menu opened. Nothing about the layout changes now. A workaround for a runtime bug (bejamas/data-slot).
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## Date 15th Oct 2026
|
|
23
|
+
## Handover gaps: triggers don't submit forms (C29, gap G18)
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **`DropdownMenuTrigger` renders `type="button"`** unless you pass a `type`. It renders a `Button`, which has no type, so inside a `<form>` opening the menu submitted the form. Plain `Button` is unchanged; this also covers `TableColumnMenu`'s trigger.
|
|
28
|
+
- **`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).
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## Workspace selection handover (C17, gap G7)
|
|
32
|
+
|
|
33
|
+
### States
|
|
34
|
+
|
|
35
|
+
- `destructive` item hover is now `bg-callout-error` (`--callout-background-error`, Figma Red/50), was the neutral `background-hover`. The workspace-selection screen overrode it with `destructive/10`, which this replaces. Dark mode keeps `destructive/20`.
|
|
36
|
+
|
|
5
37
|
## Date 10th Sep 2026
|
|
6
38
|
## Figma alignment (node 2329-6521, prop table 7392-10142)
|
|
7
39
|
|
|
@@ -220,7 +220,17 @@ const defaultValuesJson =
|
|
|
220
220
|
|
|
221
221
|
<script>
|
|
222
222
|
import { createDropdownMenu } from "@data-slot/dropdown-menu";
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
import { lockScrollWhileOpen } from "@cl-light-ds/ui/lib/scroll-lock";
|
|
224
|
+
import { bindRadioGroups, prepareRadioGroups } from "@cl-light-ds/ui/lib/dropdown-menu-radio-groups";
|
|
225
|
+
|
|
226
|
+
document.querySelectorAll<HTMLElement>('[data-slot="dropdown-menu"]').forEach((el) => {
|
|
227
|
+
prepareRadioGroups(el);
|
|
228
|
+
// The runtime's own lock adds a scrollbar gutter, so short pages jump
|
|
229
|
+
// sideways when a menu opens; lib/scroll-lock doesn't touch the layout.
|
|
230
|
+
const menu = createDropdownMenu(el, { lockScroll: false });
|
|
231
|
+
bindRadioGroups(el, menu);
|
|
232
|
+
if (el.dataset.lockScroll !== "false") {
|
|
233
|
+
lockScrollWhileOpen(el, "dropdown-menu:open-change", () => menu.isOpen);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
226
236
|
</script>
|
|
@@ -3,11 +3,26 @@ import type { HTMLAttributes } from "astro/types";
|
|
|
3
3
|
|
|
4
4
|
interface Props extends HTMLAttributes<"div"> {
|
|
5
5
|
class?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Gives the group its own value, so one menu can hold several radio groups
|
|
8
|
+
* (e.g. "Sort by" and "Order"). Without a name, the group's items share the
|
|
9
|
+
* menu's single radio value, as before. Item values must be unique in the menu.
|
|
10
|
+
*/
|
|
11
|
+
name?: string;
|
|
12
|
+
/** The checked item of a named group. */
|
|
13
|
+
defaultValue?: string;
|
|
6
14
|
}
|
|
7
15
|
|
|
8
|
-
const { class: className = "", ...rest } = Astro.props as Props;
|
|
16
|
+
const { class: className = "", name, defaultValue, ...rest } = Astro.props as Props;
|
|
9
17
|
---
|
|
10
18
|
|
|
11
|
-
<div
|
|
19
|
+
<div
|
|
20
|
+
{...rest}
|
|
21
|
+
role="group"
|
|
22
|
+
data-slot="dropdown-menu-radio-group"
|
|
23
|
+
data-name={name}
|
|
24
|
+
data-default-value={name ? defaultValue : undefined}
|
|
25
|
+
class={className}
|
|
26
|
+
>
|
|
12
27
|
<slot />
|
|
13
28
|
</div>
|
|
@@ -6,7 +6,11 @@ import type { Props as ButtonProps } from "../button/Button.astro";
|
|
|
6
6
|
|
|
7
7
|
type Props = ButtonProps & {
|
|
8
8
|
class?: string;
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Radix-like: render wrapper so the child can be the interactive element.
|
|
11
|
+
* The wrapper is not a button, so the child sets its own type: give a
|
|
12
|
+
* `<button>`/`<Button>` child `type="button"` inside a `<form>` or it submits it.
|
|
13
|
+
*/
|
|
10
14
|
asChild?: boolean;
|
|
11
15
|
};
|
|
12
16
|
|
|
@@ -16,6 +20,7 @@ const {
|
|
|
16
20
|
class: className = "",
|
|
17
21
|
asChild = false,
|
|
18
22
|
variant = "outline",
|
|
23
|
+
type = "button",
|
|
19
24
|
tailIcon = true,
|
|
20
25
|
...rest
|
|
21
26
|
} = Astro.props as Props;
|
|
@@ -33,6 +38,7 @@ const {
|
|
|
33
38
|
variant={variant}
|
|
34
39
|
tailIcon={tailIcon}
|
|
35
40
|
{...rest as any}
|
|
41
|
+
type={(rest as any).as === "a" ? undefined : type}
|
|
36
42
|
>
|
|
37
43
|
<slot />
|
|
38
44
|
{tailIcon &&
|
|
@@ -11,7 +11,7 @@ export const itemVariants = cva(
|
|
|
11
11
|
variant: {
|
|
12
12
|
default: "p-2 text-xs font-semibold text-foreground hover:bg-background-hover hover:text-foreground",
|
|
13
13
|
destructive:
|
|
14
|
-
"p-2 text-xs font-semibold text-destructive hover:bg-
|
|
14
|
+
"p-2 text-xs font-semibold text-destructive hover:bg-callout-error hover:text-destructive dark:hover:bg-destructive/20 [&_svg]:text-destructive",
|
|
15
15
|
category: "pointer-events-none gap-1 pt-1 pr-1 pb-0 pl-1 text-xs font-medium text-muted-foreground",
|
|
16
16
|
},
|
|
17
17
|
},
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the `EmptyState` component will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## Date 15th Oct 2026
|
|
6
|
+
## Workspace selection handover (N15)
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- `EmptyState`: illustration (or icon), title, text and an optional action, centred.
|
|
11
|
+
- `size="page"`: 280×200 illustrations, 18px semibold title (`h2`), 14px text, 16px gaps, `py-12`.
|
|
12
|
+
- `size="menu"`: 144×104 / 184×124 illustrations, 14px medium title (`p`), 12px text, 8px gaps, `py-10`.
|
|
13
|
+
- `variant="panel"`: on a white (`bg-background`) dashed, rounded panel, for card grids (D188, D202). `plain` (default) has no frame, e.g. inside `TableEmpty` or a popover.
|
|
14
|
+
- `illustration`: built-in name; `icon`: a Lucide component in a 48px muted circle (the reference's "no favourites" state); `illustration` slot for custom artwork.
|
|
15
|
+
- `title` / `description` props or slots, `titleAs` to change the heading element, `action` slot for buttons. Other attributes pass through (e.g. `role="alert"` for a load error).
|
|
16
|
+
- Illustrations (`illustrations/`), from the designer's handover SVGs: `empty`, `no-results`, `error` (280×200), `all-caught-up` (144×104), `nothing-matches` (184×124). Converted to inline Astro components so their colours are design tokens (`background`, `foreground`, `muted`, `border`, `primary`, `primary-50/100/300/700`, `destructive`) and follow the theme; the C2PA metadata is stripped and filter ids are unique per instance. Exceptions without a token: two neutral greys (`zinc-400`, `zinc-300`, via Tailwind's variables with hex fallbacks), the lightest ring (a mix of `primary-50` and `background`), and the drop shadows' `#051729` (the same colour as `shadow-card-light`).
|
|
17
|
+
- `emptyStateIllustrations`, `EMPTY_STATE_ILLUSTRATIONS`, `EmptyStateIllustrationName` exported.
|
|
18
|
+
- Storybook: "Empty State" under Feedback.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* @component EmptyState
|
|
4
|
+
* @title Empty State
|
|
5
|
+
* @description A message for when there is nothing to show, or something failed: an illustration, a title, a line of text and an optional action. Page and menu sizes, optionally on a white dashed panel.
|
|
6
|
+
*
|
|
7
|
+
* @preview
|
|
8
|
+
*
|
|
9
|
+
* <EmptyState
|
|
10
|
+
* illustration="empty"
|
|
11
|
+
* title="You're not in any workspaces yet"
|
|
12
|
+
* description="Create one to get started, or ask a colleague to invite you to theirs."
|
|
13
|
+
* variant="panel"
|
|
14
|
+
* >
|
|
15
|
+
* <Button slot="action">New workspace</Button>
|
|
16
|
+
* </EmptyState>
|
|
17
|
+
*
|
|
18
|
+
* @usage
|
|
19
|
+
*
|
|
20
|
+
* ```astro nocollapse
|
|
21
|
+
* ---
|
|
22
|
+
* import { EmptyState } from '@cl-light-ds/ui/components/empty-state';
|
|
23
|
+
* import { Button } from '@cl-light-ds/ui/components/button';
|
|
24
|
+
* ---
|
|
25
|
+
*
|
|
26
|
+
* <EmptyState illustration="no-results" title="No results for “acme”" description="Check the spelling, or try a shorter name.">
|
|
27
|
+
* <Button slot="action" variant="outline">Clear search</Button>
|
|
28
|
+
* </EmptyState>
|
|
29
|
+
*
|
|
30
|
+
* <EmptyState size="menu" illustration="all-caught-up" title="You're all caught up" description="New notifications show up here." />
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* - Built-in illustrations (`illustration`): `empty`, `no-results`, `error`
|
|
34
|
+
* (280×200, for pages), `all-caught-up` (144×104) and `nothing-matches`
|
|
35
|
+
* (184×124, for menus). Drawn with design tokens, so they follow the theme.
|
|
36
|
+
* - Or an `icon` (a Lucide component) in a 48px muted circle, or your own
|
|
37
|
+
* artwork in the `illustration` slot.
|
|
38
|
+
* - For a failure that appears after an action, add `role="alert"` so it is
|
|
39
|
+
* announced; for results that change as you type, `role="status"`.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import type { HTMLAttributes, HTMLTag } from "astro/types";
|
|
43
|
+
import type { AstroComponent } from "@lucide/astro";
|
|
44
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
45
|
+
import { cn } from "@cl-light-ds/ui/lib/utils";
|
|
46
|
+
import { emptyStateIllustrations, type EmptyStateIllustrationName } from "./illustrations";
|
|
47
|
+
|
|
48
|
+
export const emptyStateVariants = cva("flex flex-col items-center text-center", {
|
|
49
|
+
variants: {
|
|
50
|
+
size: {
|
|
51
|
+
page: "gap-4 px-6 py-12",
|
|
52
|
+
menu: "gap-2 px-6 py-10",
|
|
53
|
+
},
|
|
54
|
+
variant: {
|
|
55
|
+
plain: "",
|
|
56
|
+
panel: "rounded-lg border border-dashed bg-background",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
defaultVariants: {
|
|
60
|
+
size: "page",
|
|
61
|
+
variant: "plain",
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
interface Props extends VariantProps<typeof emptyStateVariants>, Omit<HTMLAttributes<"div">, "class" | "title"> {
|
|
66
|
+
/** A built-in illustration. */
|
|
67
|
+
illustration?: EmptyStateIllustrationName;
|
|
68
|
+
/** Or a Lucide icon in a 48px muted circle (ignored when there is an illustration). */
|
|
69
|
+
icon?: AstroComponent;
|
|
70
|
+
/** The title. Or the `title` slot. */
|
|
71
|
+
title?: string;
|
|
72
|
+
/** The text under the title. Or the `description` slot. */
|
|
73
|
+
description?: string;
|
|
74
|
+
/** Element for the title: `h2` on pages, `p` in menus by default. */
|
|
75
|
+
titleAs?: HTMLTag;
|
|
76
|
+
class?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const {
|
|
80
|
+
illustration,
|
|
81
|
+
icon,
|
|
82
|
+
title,
|
|
83
|
+
description,
|
|
84
|
+
size = "page",
|
|
85
|
+
variant = "plain",
|
|
86
|
+
titleAs,
|
|
87
|
+
class: className = "",
|
|
88
|
+
...rest
|
|
89
|
+
} = Astro.props as Props;
|
|
90
|
+
|
|
91
|
+
const hasIllustrationSlot = Astro.slots.has("illustration");
|
|
92
|
+
const hasTitleSlot = Astro.slots.has("title");
|
|
93
|
+
const hasDescriptionSlot = Astro.slots.has("description");
|
|
94
|
+
const hasAction = Astro.slots.has("action");
|
|
95
|
+
|
|
96
|
+
const art = illustration ? emptyStateIllustrations[illustration] : undefined;
|
|
97
|
+
const Illustration = art?.component;
|
|
98
|
+
const Icon = icon;
|
|
99
|
+
const TitleTag = titleAs ?? (size === "menu" ? "p" : "h2");
|
|
100
|
+
const isMenu = size === "menu";
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
<div
|
|
104
|
+
{...rest}
|
|
105
|
+
data-slot="empty-state"
|
|
106
|
+
data-size={size}
|
|
107
|
+
data-variant={variant}
|
|
108
|
+
class={cn(emptyStateVariants({ size, variant }), className)}
|
|
109
|
+
>
|
|
110
|
+
{
|
|
111
|
+
hasIllustrationSlot ? (
|
|
112
|
+
<div data-slot="empty-state-illustration" class="flex justify-center">
|
|
113
|
+
<slot name="illustration" />
|
|
114
|
+
</div>
|
|
115
|
+
) : Illustration && art ? (
|
|
116
|
+
<Illustration data-slot="empty-state-illustration" class={cn("max-w-full shrink-0", art.pull)} />
|
|
117
|
+
) : (
|
|
118
|
+
Icon && (
|
|
119
|
+
<span
|
|
120
|
+
data-slot="empty-state-icon"
|
|
121
|
+
class="flex size-12 items-center justify-center rounded-full bg-muted text-muted-foreground"
|
|
122
|
+
aria-hidden="true"
|
|
123
|
+
>
|
|
124
|
+
<Icon class="size-6" />
|
|
125
|
+
</span>
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
<div class={cn("flex flex-col", isMenu ? "max-w-xs gap-2" : "max-w-md gap-1")}>
|
|
131
|
+
{
|
|
132
|
+
(hasTitleSlot || title) && (
|
|
133
|
+
<TitleTag
|
|
134
|
+
data-slot="empty-state-title"
|
|
135
|
+
class={cn("text-foreground", isMenu ? "text-sm font-medium" : "text-lg font-semibold")}
|
|
136
|
+
>
|
|
137
|
+
{hasTitleSlot ? <slot name="title" /> : title}
|
|
138
|
+
</TitleTag>
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
{
|
|
142
|
+
(hasDescriptionSlot || description) && (
|
|
143
|
+
<p data-slot="empty-state-description" class={cn("text-muted-foreground", isMenu ? "text-xs" : "text-sm")}>
|
|
144
|
+
{hasDescriptionSlot ? <slot name="description" /> : description}
|
|
145
|
+
</p>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
{
|
|
151
|
+
hasAction && (
|
|
152
|
+
<div data-slot="empty-state-action" class="flex flex-wrap items-center justify-center gap-2">
|
|
153
|
+
<slot name="action" />
|
|
154
|
+
</div>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
</div>
|