@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.
Files changed (127) hide show
  1. package/package.json +19 -17
  2. package/src/components/accordion/AccordionTrigger.astro +5 -1
  3. package/src/components/accordion/CHANGELOG.md +9 -0
  4. package/src/components/alert/Alert.astro +42 -3
  5. package/src/components/alert/CHANGELOG.md +19 -0
  6. package/src/components/button/Button.astro +78 -19
  7. package/src/components/button/CHANGELOG.md +22 -0
  8. package/src/components/button-group/ButtonGroup.astro +14 -3
  9. package/src/components/button-group/CHANGELOG.md +17 -0
  10. package/src/components/card/CHANGELOG.md +9 -0
  11. package/src/components/card/Card.astro +22 -0
  12. package/src/components/collapsible/CHANGELOG.md +9 -0
  13. package/src/components/collapsible/CollapsibleTrigger.astro +5 -1
  14. package/src/components/combobox/CHANGELOG.md +27 -0
  15. package/src/components/combobox/Combobox.astro +18 -1
  16. package/src/components/combobox/ComboboxTrigger.astro +32 -2
  17. package/src/components/command-search/CHANGELOG.md +57 -0
  18. package/src/components/command-search/CommandSearch.astro +594 -0
  19. package/src/components/command-search/CommandSearchGroup.astro +43 -0
  20. package/src/components/command-search/CommandSearchItem.astro +64 -0
  21. package/src/components/command-search/classes.ts +18 -0
  22. package/src/components/command-search/index.ts +12 -0
  23. package/src/components/command-search/nothing-matches.svg +42 -0
  24. package/src/components/dialog/CHANGELOG.md +50 -0
  25. package/src/components/dialog/Dialog.astro +14 -3
  26. package/src/components/dialog/DialogClose.astro +28 -12
  27. package/src/components/dialog/DialogContent.astro +19 -1
  28. package/src/components/dialog/DialogTrigger.astro +5 -1
  29. package/src/components/dropdown-menu/CHANGELOG.md +32 -0
  30. package/src/components/dropdown-menu/DropdownMenu.astro +13 -3
  31. package/src/components/dropdown-menu/DropdownMenuRadioGroup.astro +17 -2
  32. package/src/components/dropdown-menu/DropdownMenuTrigger.astro +7 -1
  33. package/src/components/dropdown-menu/dropdown-menu-item-variants.ts +1 -1
  34. package/src/components/empty-state/CHANGELOG.md +18 -0
  35. package/src/components/empty-state/EmptyState.astro +157 -0
  36. package/src/components/empty-state/illustrations/AllCaughtUp.astro +52 -0
  37. package/src/components/empty-state/illustrations/EmptyWorkspaces.astro +56 -0
  38. package/src/components/empty-state/illustrations/LoadError.astro +61 -0
  39. package/src/components/empty-state/illustrations/NoResults.astro +60 -0
  40. package/src/components/empty-state/illustrations/NothingMatches.astro +66 -0
  41. package/src/components/empty-state/illustrations/index.ts +27 -0
  42. package/src/components/empty-state/index.ts +7 -0
  43. package/src/components/favorite-toggle/CHANGELOG.md +13 -0
  44. package/src/components/favorite-toggle/FavoriteToggle.astro +94 -0
  45. package/src/components/favorite-toggle/index.ts +1 -0
  46. package/src/components/field/CHANGELOG.md +18 -0
  47. package/src/components/field/Field.astro +18 -0
  48. package/src/components/field/FieldError.astro +13 -1
  49. package/src/components/field/FieldGroup.astro +12 -3
  50. package/src/components/hover-card/CHANGELOG.md +9 -0
  51. package/src/components/hover-card/HoverCardTrigger.astro +5 -1
  52. package/src/components/input-group/CHANGELOG.md +7 -0
  53. package/src/components/input-group/InputGroup.astro +2 -0
  54. package/src/components/loading-overlay/CHANGELOG.md +25 -0
  55. package/src/components/loading-overlay/LoadingOverlay.astro +179 -0
  56. package/src/components/loading-overlay/index.ts +8 -0
  57. package/src/components/notifications-menu/CHANGELOG.md +29 -0
  58. package/src/components/notifications-menu/NotificationsMenu.astro +375 -0
  59. package/src/components/notifications-menu/NotificationsMenuItem.astro +130 -0
  60. package/src/components/notifications-menu/all-caught-up.svg +28 -0
  61. package/src/components/notifications-menu/index.ts +3 -0
  62. package/src/components/notifications-menu/types.ts +32 -0
  63. package/src/components/option-card/CHANGELOG.md +26 -0
  64. package/src/components/option-card/OptionCard.astro +143 -0
  65. package/src/components/option-card/index.ts +1 -0
  66. package/src/components/organisms/top-bar/CHANGELOG.md +27 -0
  67. package/src/components/organisms/top-bar/TopBar.astro +257 -0
  68. package/src/components/organisms/top-bar/index.ts +2 -0
  69. package/src/components/organisms/top-bar/types.ts +30 -0
  70. package/src/components/organisms/workspace/CHANGELOG.md +80 -0
  71. package/src/components/organisms/workspace/OrganisationOverview.astro +62 -0
  72. package/src/components/organisms/workspace/RecentWorkspaceCard.astro +235 -0
  73. package/src/components/organisms/workspace/RecentWorkspaces.astro +93 -0
  74. package/src/components/organisms/workspace/WorkspaceAvatar.astro +144 -0
  75. package/src/components/organisms/workspace/WorkspaceCard.astro +172 -0
  76. package/src/components/organisms/workspace/WorkspaceGlyph.astro +62 -0
  77. package/src/components/organisms/workspace/WorkspaceIcon.astro +42 -0
  78. package/src/components/organisms/workspace/WorkspaceIconPicker.astro +288 -0
  79. package/src/components/organisms/workspace/WorkspaceIconPickerGlyph.astro +44 -0
  80. package/src/components/organisms/workspace/icons.ts +98 -0
  81. package/src/components/organisms/workspace/index.ts +35 -0
  82. package/src/components/organisms/workspace/labels.ts +54 -0
  83. package/src/components/organisms/workspace/recent-card-rings-strong.png +0 -0
  84. package/src/components/organisms/workspace/types.ts +30 -0
  85. package/src/components/popover/CHANGELOG.md +14 -0
  86. package/src/components/popover/PopoverClose.astro +2 -1
  87. package/src/components/popover/PopoverTrigger.astro +7 -1
  88. package/src/components/profile-menu/CHANGELOG.md +22 -0
  89. package/src/components/profile-menu/ProfileMenu.astro +236 -0
  90. package/src/components/profile-menu/index.ts +2 -0
  91. package/src/components/profile-menu/types.ts +27 -0
  92. package/src/components/search-field/CHANGELOG.md +14 -0
  93. package/src/components/search-field/SearchField.astro +163 -0
  94. package/src/components/search-field/index.ts +1 -0
  95. package/src/components/select/CHANGELOG.md +8 -0
  96. package/src/components/select/SelectTrigger.astro +1 -1
  97. package/src/components/table/CHANGELOG.md +15 -0
  98. package/src/components/table/TableCell.astro +6 -0
  99. package/src/components/table/TableEditableCell.astro +2 -2
  100. package/src/components/table/TableHead.astro +5 -1
  101. package/src/components/table/TablePagination.astro +4 -4
  102. package/src/components/table/TableRow.astro +3 -1
  103. package/src/components/table/table-cell-variants.ts +2 -0
  104. package/src/components/tabs/CHANGELOG.md +16 -0
  105. package/src/components/tabs/Tabs.astro +26 -0
  106. package/src/components/tabs/TabsList.astro +30 -3
  107. package/src/components/toast/CHANGELOG.md +32 -0
  108. package/src/components/toast/Toast.astro +145 -0
  109. package/src/components/toast/Toaster.astro +322 -0
  110. package/src/components/toast/index.ts +9 -0
  111. package/src/components/toggle/CHANGELOG.md +7 -0
  112. package/src/components/toggle-group/CHANGELOG.md +11 -0
  113. package/src/components/toggle-group/ToggleGroup.astro +19 -2
  114. package/src/components/toggle-group/ToggleGroupItem.astro +10 -0
  115. package/src/components/tooltip/CHANGELOG.md +26 -0
  116. package/src/components/tooltip/Tooltip.astro +31 -3
  117. package/src/components/tooltip/TooltipTrigger.astro +5 -1
  118. package/src/lib/command-search.ts +78 -0
  119. package/src/lib/dialog-focus.ts +73 -0
  120. package/src/lib/dropdown-menu-radio-groups.ts +79 -0
  121. package/src/lib/loading-overlay.ts +174 -0
  122. package/src/lib/scroll-lock.ts +97 -0
  123. package/src/lib/toast.ts +127 -0
  124. package/src/lib/toggle-shared.ts +9 -1
  125. package/src/lib/workspace-palette.ts +119 -0
  126. package/src/styles/CHANGELOG.md +35 -0
  127. package/src/styles/globals.css +109 -0
@@ -0,0 +1,93 @@
1
+ ---
2
+ /**
3
+ * @component RecentWorkspaces
4
+ * @title Recent Workspaces
5
+ * @description Organism. The 1–4 most recently opened workspaces: the latest as the large card, the others beside it, sized by how many there are. The large card is always 222px high.
6
+ *
7
+ * @preview
8
+ *
9
+ * <RecentWorkspaces
10
+ * class="w-full"
11
+ * items={[
12
+ * { name: "Acme Logistics", href: "#", color: "blue", icon: "truck", opened: "2 hours ago", lastEditedBy: "Ana Silva", recordings: 128 },
13
+ * { name: "Globex Finance", href: "#", color: "emerald", icon: "wallet", opened: "yesterday" },
14
+ * { name: "Initech Field Service", href: "#", color: "orange", icon: "wrench", opened: "3 days ago" },
15
+ * ]}
16
+ * />
17
+ *
18
+ * @usage
19
+ *
20
+ * ```astro nocollapse
21
+ * ---
22
+ * import { RecentWorkspaces } from '@cl-light-ds/ui/components/organisms/workspace';
23
+ * ---
24
+ *
25
+ * <RecentWorkspaces items={recents} />
26
+ * ```
27
+ *
28
+ * `items` is most recent first; only the first four are shown, and nothing is
29
+ * rendered without any. On wide screens (`lg`) the large card takes two of
30
+ * three columns and the others share the third:
31
+ *
32
+ * - 1 recent: the large card takes the row
33
+ * - 2: the large card + a medium card (`md`, as high as the large one)
34
+ * - 3: the large card + two small cards (`sm`) stacked, sharing its height
35
+ * - 4: the large card + three extra small cards (`xs`) stacked
36
+ *
37
+ * Below `lg` the cards stack. A `section` named by `label`, with no visible
38
+ * heading (add your own beside it if the page needs one).
39
+ */
40
+
41
+ import type { HTMLAttributes } from "astro/types";
42
+ import { cn } from "@cl-light-ds/ui/lib/utils";
43
+ import type { WorkspaceCardLabels } from "./labels";
44
+ import type { RecentWorkspace, RecentWorkspaceCardSize, RecentWorkspaceTone } from "./types";
45
+ import RecentWorkspaceCard from "./RecentWorkspaceCard.astro";
46
+
47
+ interface Props extends Omit<HTMLAttributes<"section">, "class"> {
48
+ /** The recents, most recent first. Up to four are shown. */
49
+ items: RecentWorkspace[];
50
+ /** The large card: the workspace's own gradient (default) or the brand gradient. */
51
+ tone?: RecentWorkspaceTone;
52
+ /** Accessible name of the section. */
53
+ label?: string;
54
+ /** Copy overrides for the cards (see `WorkspaceCardLabels`). */
55
+ labels?: Partial<WorkspaceCardLabels>;
56
+ class?: string;
57
+ }
58
+
59
+ const { items, tone = "workspace", label = "Recent workspaces", labels, class: className = "", ...rest } =
60
+ Astro.props as Props;
61
+
62
+ const shown = items.slice(0, 4);
63
+ const [latest, ...others] = shown;
64
+ const count = shown.length;
65
+ const otherSize: Record<number, RecentWorkspaceCardSize> = { 2: "md", 3: "sm", 4: "xs" };
66
+ ---
67
+
68
+ {
69
+ latest && (
70
+ <section
71
+ {...rest}
72
+ aria-label={label}
73
+ data-slot="recent-workspaces"
74
+ data-count={count}
75
+ class={cn("grid gap-4 lg:grid-cols-3", className)}
76
+ >
77
+ <RecentWorkspaceCard
78
+ {...latest}
79
+ size="lg"
80
+ tone={tone}
81
+ labels={labels}
82
+ class={count === 1 ? "lg:col-span-3" : "lg:col-span-2"}
83
+ />
84
+ {count > 1 && (
85
+ <div data-slot="recent-workspaces-others" class="flex min-w-0 flex-col gap-3">
86
+ {others.map((item) => (
87
+ <RecentWorkspaceCard {...item} size={otherSize[count]} labels={labels} class="min-w-0 flex-1" />
88
+ ))}
89
+ </div>
90
+ )}
91
+ </section>
92
+ )
93
+ }
@@ -0,0 +1,144 @@
1
+ ---
2
+ /**
3
+ * @component WorkspaceAvatar
4
+ * @title Workspace Avatar
5
+ * @description Organism. A workspace's placeholder icon, or the first letter of its name, on the workspace's colour from the workspace palette. Decorative by default, since the workspace name sits beside it.
6
+ *
7
+ * @preview
8
+ *
9
+ * <div class="flex items-center gap-3">
10
+ * <WorkspaceAvatar color="blue" icon="truck" />
11
+ * <WorkspaceAvatar color="emerald" icon="leaf" tone="solid" shape="square" />
12
+ * <WorkspaceAvatar color="purple" icon="initial" name="Northwind" size="lg" shape="square" />
13
+ * </div>
14
+ *
15
+ * @usage
16
+ *
17
+ * ```astro nocollapse
18
+ * ---
19
+ * import { WorkspaceAvatar } from '@cl-light-ds/ui/components/organisms/workspace';
20
+ * ---
21
+ *
22
+ * <WorkspaceAvatar color="teal" icon="rocket" shape="square" />
23
+ * ```
24
+ *
25
+ * - `tone="soft"` (default): the colour, strong, on a light tint of it
26
+ * - `tone="solid"`: a white glyph on the colour
27
+ * - `tone="inverse"`: the colour on white, e.g. on a coloured card
28
+ * - `tone="brand"`: the primary blue on white, on brand surfaces
29
+ * - `shape="square"`: a rounded square (8px; 14px at `lg` and `xl`)
30
+ * - sizes: `sm` 32px (16px glyph), `default` 40px (20px), `lg` 56px (28px),
31
+ * `xl` 64px (28px, the icon picker's trigger)
32
+ *
33
+ * Pass `label` to make it a named image (`role="img"`) when no name is
34
+ * beside it. A default slot replaces the glyph, e.g. with a logo `<img>`.
35
+ */
36
+
37
+ import type { HTMLAttributes } from "astro/types";
38
+ import { cva, type VariantProps } from "class-variance-authority";
39
+ import { cn } from "@cl-light-ds/ui/lib/utils";
40
+ import {
41
+ DEFAULT_WORKSPACE_COLOR,
42
+ workspacePalette,
43
+ type WorkspaceColor,
44
+ } from "@cl-light-ds/ui/lib/workspace-palette";
45
+ import type { WorkspaceGlyphName } from "./icons";
46
+ import type { WorkspaceAvatarSize, WorkspaceAvatarTone } from "./types";
47
+ import WorkspaceGlyph from "./WorkspaceGlyph.astro";
48
+
49
+ export const workspaceAvatarVariants = cva(
50
+ "relative inline-flex shrink-0 items-center justify-center overflow-hidden select-none",
51
+ {
52
+ variants: {
53
+ size: {
54
+ sm: "size-8",
55
+ default: "size-10",
56
+ lg: "size-14",
57
+ xl: "size-16",
58
+ },
59
+ shape: {
60
+ circle: "rounded-full",
61
+ square: "",
62
+ },
63
+ },
64
+ compoundVariants: [
65
+ { shape: "square", size: ["sm", "default"], class: "rounded-lg" },
66
+ { shape: "square", size: ["lg", "xl"], class: "rounded-xl" },
67
+ ],
68
+ defaultVariants: {
69
+ size: "default",
70
+ shape: "circle",
71
+ },
72
+ },
73
+ );
74
+
75
+ /** Glyph size per avatar size: the icon, or the letter's box and font size. */
76
+ const glyphSizes: Record<string, string> = {
77
+ sm: "size-4 text-sm",
78
+ default: "size-5 text-base",
79
+ lg: "size-7 text-2xl",
80
+ xl: "size-7 text-2xl",
81
+ };
82
+
83
+ interface Props
84
+ extends VariantProps<typeof workspaceAvatarVariants>,
85
+ Omit<HTMLAttributes<"span">, "class"> {
86
+ /** Palette colour. Defaults to `DEFAULT_WORKSPACE_COLOR` (blue). */
87
+ color?: WorkspaceColor;
88
+ /** An icon name from `WORKSPACE_ICONS`, or `"initial"`. */
89
+ icon?: WorkspaceGlyphName | (string & {});
90
+ /** The workspace name, for the initial. */
91
+ name?: string;
92
+ tone?: WorkspaceAvatarTone;
93
+ /** An accessible name. Without it the avatar is decorative (`aria-hidden`). */
94
+ label?: string;
95
+ class?: string;
96
+ /** Extra classes for the glyph (icon or letter). */
97
+ glyphClass?: string;
98
+ }
99
+
100
+ const {
101
+ color = DEFAULT_WORKSPACE_COLOR,
102
+ icon = "initial",
103
+ name = "",
104
+ tone = "soft",
105
+ size = "default",
106
+ shape = "circle",
107
+ label,
108
+ class: className = "",
109
+ glyphClass = "",
110
+ ...rest
111
+ } = Astro.props as Props;
112
+
113
+ const swatch = workspacePalette[color] ?? workspacePalette[DEFAULT_WORKSPACE_COLOR];
114
+ const toneClasses: Record<WorkspaceAvatarTone, string[]> = {
115
+ soft: [swatch.tint, swatch.text],
116
+ solid: [swatch.solid, "text-primary-foreground"],
117
+ inverse: ["bg-background", swatch.text],
118
+ brand: ["bg-background", "text-primary"],
119
+ };
120
+ const toneClass = toneClasses[tone as WorkspaceAvatarTone] ?? toneClasses.soft;
121
+ const glyphSize = glyphSizes[(size ?? "default") as WorkspaceAvatarSize];
122
+ const hasSlot = Astro.slots.has("default");
123
+ ---
124
+
125
+ <span
126
+ {...rest}
127
+ data-slot="workspace-avatar"
128
+ data-size={size}
129
+ data-shape={shape}
130
+ data-tone={tone}
131
+ data-color={color}
132
+ role={label ? "img" : undefined}
133
+ aria-label={label}
134
+ aria-hidden={label ? undefined : "true"}
135
+ class={cn(workspaceAvatarVariants({ size, shape }), toneClass, className)}
136
+ >
137
+ {
138
+ hasSlot ? (
139
+ <slot />
140
+ ) : (
141
+ <WorkspaceGlyph icon={icon} name={name} class={cn(glyphSize, glyphClass)} />
142
+ )
143
+ }
144
+ </span>
@@ -0,0 +1,172 @@
1
+ ---
2
+ /**
3
+ * @component WorkspaceCard
4
+ * @title Workspace Card
5
+ * @description Organism. A workspace in a grid: its soft square avatar with a favourite star and an actions menu top right, the name and when you last opened it, and under a divider who last edited it and how many recordings it has. The whole card opens the workspace and lifts on hover and keyboard focus.
6
+ *
7
+ * @preview
8
+ *
9
+ * <WorkspaceCard
10
+ * class="w-72"
11
+ * name="Acme Logistics"
12
+ * href="#"
13
+ * color="blue"
14
+ * icon="truck"
15
+ * opened="2 hours ago"
16
+ * lastEditedBy="Ana Silva"
17
+ * recordings={128}
18
+ * favorite
19
+ * />
20
+ *
21
+ * @usage
22
+ *
23
+ * ```astro nocollapse
24
+ * ---
25
+ * import { WorkspaceCard } from '@cl-light-ds/ui/components/organisms/workspace';
26
+ * import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@cl-light-ds/ui/components/dropdown-menu';
27
+ * import { Ellipsis } from '@lucide/astro';
28
+ * ---
29
+ *
30
+ * <WorkspaceCard
31
+ * name={ws.name}
32
+ * href={`/w/${ws.id}`}
33
+ * color={ws.color}
34
+ * icon={ws.icon}
35
+ * opened="2 hours ago"
36
+ * lastEditedBy="Ana Silva"
37
+ * recordings={128}
38
+ * favorite={ws.starred}
39
+ * data-workspace-id={ws.id}
40
+ * >
41
+ * <DropdownMenu slot="actions">
42
+ * <DropdownMenuTrigger variant="ghost" size="icon-sm" tailIcon={false} aria-label={`Actions for ${ws.name}`}>
43
+ * <Ellipsis />
44
+ * </DropdownMenuTrigger>
45
+ * <DropdownMenuContent align="end">
46
+ * <DropdownMenuItem>Duplicate</DropdownMenuItem>
47
+ * </DropdownMenuContent>
48
+ * </DropdownMenu>
49
+ * </WorkspaceCard>
50
+ * ```
51
+ *
52
+ * The name is the card's one link, stretched over it (`after:absolute
53
+ * after:inset-0`), so the card opens from anywhere; the star and the
54
+ * `actions` slot sit above it (`relative z-10`) and stay separate controls,
55
+ * with no interactive element nested in the link. The focus ring goes round
56
+ * the whole card while the link has keyboard focus.
57
+ *
58
+ * The star shows only on a favourite, or while the card is hovered or has
59
+ * keyboard focus; always on touch screens. It is a `FavoriteToggle`: listen
60
+ * for its `toggle:change` (`{ pressed }`, bubbling) to save the change.
61
+ *
62
+ * Long names and editors truncate, with the full text in `title`.
63
+ */
64
+
65
+ import type { HTMLAttributes } from "astro/types";
66
+ import { cn } from "@cl-light-ds/ui/lib/utils";
67
+ import { DEFAULT_WORKSPACE_COLOR, type WorkspaceColor } from "@cl-light-ds/ui/lib/workspace-palette";
68
+ import { Card, CardTitle } from "../../card";
69
+ import { FavoriteToggle } from "../../favorite-toggle";
70
+ import type { WorkspaceGlyphName } from "./icons";
71
+ import { workspaceCardTexts, type WorkspaceCardLabels } from "./labels";
72
+ import WorkspaceAvatar from "./WorkspaceAvatar.astro";
73
+
74
+ interface Props extends Omit<HTMLAttributes<"div">, "class"> {
75
+ /** Workspace name (required). */
76
+ name: string;
77
+ /** Where the card goes: the workspace. */
78
+ href: string;
79
+ /** Palette colour. Defaults to blue. */
80
+ color?: WorkspaceColor;
81
+ /** Placeholder icon, or `"initial"` (default). */
82
+ icon?: WorkspaceGlyphName | (string & {});
83
+ /** When it was last opened, e.g. "2 hours ago" ("Opened {time}"). */
84
+ opened?: string;
85
+ /** Who last edited it ("Last edited by {name}"). */
86
+ lastEditedBy?: string;
87
+ /** Number of recordings ("{count} recordings"), or a ready-made string. */
88
+ recordings?: number | string;
89
+ /** A favourite: the star is filled and always shown. */
90
+ favorite?: boolean;
91
+ /** Show the star at all. */
92
+ showFavorite?: boolean;
93
+ /** Copy overrides (see `WorkspaceCardLabels`). */
94
+ labels?: Partial<WorkspaceCardLabels>;
95
+ class?: string;
96
+ }
97
+
98
+ const {
99
+ name,
100
+ href,
101
+ color = DEFAULT_WORKSPACE_COLOR,
102
+ icon = "initial",
103
+ opened,
104
+ lastEditedBy,
105
+ recordings,
106
+ favorite = false,
107
+ showFavorite = true,
108
+ labels,
109
+ class: className = "",
110
+ ...rest
111
+ } = Astro.props as Props;
112
+
113
+ const text = workspaceCardTexts({ name, opened, lastEditedBy, recordings }, labels);
114
+ const hasActions = Astro.slots.has("actions");
115
+ const hasFooter = Boolean(text.lastEditedBy || text.recordings);
116
+ ---
117
+
118
+ <Card
119
+ {...rest}
120
+ interactive
121
+ data-workspace-card
122
+ data-color={color}
123
+ class={cn(
124
+ "gap-0 p-0",
125
+ // The stretched link's focus ring, round the whole card.
126
+ "has-[[data-slot=workspace-card-link]:focus-visible]:border-ring has-[[data-slot=workspace-card-link]:focus-visible]:ring-2 has-[[data-slot=workspace-card-link]:focus-visible]:ring-chart-1",
127
+ className,
128
+ )}
129
+ >
130
+ <div class="flex items-start justify-between gap-3 px-4 pt-4">
131
+ <WorkspaceAvatar color={color} icon={icon} name={name} shape="square" />
132
+ {
133
+ (showFavorite || hasActions) && (
134
+ <div data-slot="workspace-card-actions" class="relative z-10 flex items-center gap-1">
135
+ {showFavorite && (
136
+ <FavoriteToggle
137
+ label={text.favorite}
138
+ defaultPressed={favorite}
139
+ class="not-aria-pressed:opacity-0 group-hover/card:opacity-100 group-has-focus-visible/card:opacity-100 pointer-coarse:opacity-100"
140
+ />
141
+ )}
142
+ <slot name="actions" />
143
+ </div>
144
+ )
145
+ }
146
+ </div>
147
+
148
+ <div class="flex min-w-0 flex-col gap-1 px-4 pt-3 pb-4">
149
+ <CardTitle class="min-w-0 text-base leading-6">
150
+ <a
151
+ href={href}
152
+ title={name}
153
+ data-slot="workspace-card-link"
154
+ class="block truncate rounded-sm outline-none after:absolute after:inset-0 after:rounded-lg"
155
+ >
156
+ {name}
157
+ </a>
158
+ </CardTitle>
159
+ {text.opened && <p class="text-muted-foreground text-sm">{text.opened}</p>}
160
+ </div>
161
+
162
+ {
163
+ hasFooter && (
164
+ <div class="mx-4 flex items-center justify-between gap-2 border-t py-3 text-xs text-muted-foreground">
165
+ <p class="min-w-0 truncate" title={text.lastEditedBy || undefined}>
166
+ {text.lastEditedBy}
167
+ </p>
168
+ {text.recordings && <p class="shrink-0" data-slot="workspace-card-recordings">{text.recordings}</p>}
169
+ </div>
170
+ )
171
+ }
172
+ </Card>
@@ -0,0 +1,62 @@
1
+ ---
2
+ /**
3
+ * @component WorkspaceGlyph
4
+ * @title Workspace Glyph
5
+ * @description Organism part. What a workspace avatar shows: one of its placeholder icons, or the first letter of its name when the glyph is `initial`. The letter is centred in a box the size of the icon, so it sits in the middle of the avatar at every size.
6
+ *
7
+ * @usage
8
+ *
9
+ * ```astro nocollapse
10
+ * ---
11
+ * import { WorkspaceGlyph } from '@cl-light-ds/ui/components/organisms/workspace';
12
+ * ---
13
+ *
14
+ * <WorkspaceGlyph icon="initial" name="Acme Logistics" class="size-5 text-base" />
15
+ * <WorkspaceGlyph icon="truck" class="size-5" />
16
+ * ```
17
+ *
18
+ * `class` sizes the icon, or the letter's box and its font size (give both a
19
+ * `size-*` and a `text-*`). Colour comes from `currentColor`.
20
+ *
21
+ * Hooks for scripts (e.g. an icon picker that swaps the glyph as you pick):
22
+ * `data-glyph` is the current glyph name, `data-slot="workspace-glyph-initial"`
23
+ * holds the letter.
24
+ */
25
+
26
+ import type { HTMLAttributes } from "astro/types";
27
+ import { cn } from "@cl-light-ds/ui/lib/utils";
28
+ import { DEFAULT_WORKSPACE_ICON, workspaceInitial, type WorkspaceGlyphName } from "./icons";
29
+ import WorkspaceIcon from "./WorkspaceIcon.astro";
30
+
31
+ interface Props extends Omit<HTMLAttributes<"span">, "class"> {
32
+ /** An icon name, or `"initial"` for the first letter of `name`. */
33
+ icon: WorkspaceGlyphName | (string & {});
34
+ /** The workspace name, for the initial. With no name the initial falls back to the default icon. */
35
+ name?: string;
36
+ /** Size of the icon, or of the letter's box (add a `text-*` for the letter). */
37
+ class?: string;
38
+ /** Lucide stroke width for icons. */
39
+ strokeWidth?: number;
40
+ }
41
+
42
+ const { icon, name = "", class: className = "", strokeWidth, ...rest } = Astro.props as Props;
43
+ const initial = workspaceInitial(name);
44
+ const showInitial = icon === "initial" && initial !== "";
45
+ const iconName = icon === "initial" ? DEFAULT_WORKSPACE_ICON : icon;
46
+ ---
47
+
48
+ <span {...rest} data-slot="workspace-glyph" data-glyph={icon} class="flex items-center justify-center">
49
+ {
50
+ showInitial ? (
51
+ <span
52
+ data-slot="workspace-glyph-initial"
53
+ class={cn("flex size-4 items-center justify-center text-sm leading-none font-semibold", className)}
54
+ aria-hidden="true"
55
+ >
56
+ {initial}
57
+ </span>
58
+ ) : (
59
+ <WorkspaceIcon name={iconName} class={className} strokeWidth={strokeWidth} />
60
+ )
61
+ }
62
+ </span>
@@ -0,0 +1,42 @@
1
+ ---
2
+ /**
3
+ * @component WorkspaceIcon
4
+ * @title Workspace Icon
5
+ * @description Organism part. One of the 27 workspace placeholder Lucide icons, by name. Unknown names fall back to a building. Always decorative.
6
+ *
7
+ * @usage
8
+ *
9
+ * ```astro nocollapse
10
+ * ---
11
+ * import { WorkspaceIcon } from '@cl-light-ds/ui/components/organisms/workspace';
12
+ * ---
13
+ *
14
+ * <WorkspaceIcon name="rocket" class="size-5" />
15
+ * ```
16
+ */
17
+
18
+ import { cn } from "@cl-light-ds/ui/lib/utils";
19
+ import { resolveWorkspaceIcon, type WorkspaceIconName } from "./icons";
20
+
21
+ interface Props {
22
+ /** Icon name from `WORKSPACE_ICONS`. Unknown names show the building. */
23
+ name: WorkspaceIconName | (string & {});
24
+ /** Lucide stroke width; thinner reads better at large sizes. */
25
+ strokeWidth?: number;
26
+ class?: string;
27
+ /** Other SVG attributes pass through to the icon. */
28
+ [attribute: string]: unknown;
29
+ }
30
+
31
+ const { name, strokeWidth = 2, class: className = "", ...rest } = Astro.props as Props;
32
+ const Icon = resolveWorkspaceIcon(name);
33
+ ---
34
+
35
+ <Icon
36
+ {...rest}
37
+ data-slot="workspace-icon"
38
+ data-icon={name}
39
+ class={cn("size-4 shrink-0", className)}
40
+ stroke-width={strokeWidth}
41
+ aria-hidden="true"
42
+ />