@cl-light-ds/ui 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +19 -17
- package/src/components/alert/Alert.astro +8 -2
- package/src/components/alert/CHANGELOG.md +9 -0
- package/src/components/button/Button.astro +78 -19
- package/src/components/button/CHANGELOG.md +22 -0
- package/src/components/button-group/ButtonGroup.astro +14 -3
- package/src/components/button-group/CHANGELOG.md +17 -0
- package/src/components/card/CHANGELOG.md +9 -0
- package/src/components/card/Card.astro +22 -0
- package/src/components/combobox/CHANGELOG.md +14 -0
- package/src/components/combobox/ComboboxTrigger.astro +31 -1
- package/src/components/command-search/CHANGELOG.md +27 -0
- package/src/components/command-search/CommandSearch.astro +515 -0
- package/src/components/command-search/CommandSearchGroup.astro +32 -0
- package/src/components/command-search/CommandSearchItem.astro +64 -0
- package/src/components/command-search/classes.ts +18 -0
- package/src/components/command-search/index.ts +12 -0
- package/src/components/command-search/nothing-matches.svg +42 -0
- package/src/components/dialog/CHANGELOG.md +13 -0
- package/src/components/dialog/DialogClose.astro +26 -12
- package/src/components/dropdown-menu/CHANGELOG.md +7 -0
- package/src/components/dropdown-menu/dropdown-menu-item-variants.ts +1 -1
- package/src/components/empty-state/CHANGELOG.md +18 -0
- package/src/components/empty-state/EmptyState.astro +157 -0
- package/src/components/empty-state/illustrations/AllCaughtUp.astro +52 -0
- package/src/components/empty-state/illustrations/EmptyWorkspaces.astro +56 -0
- package/src/components/empty-state/illustrations/LoadError.astro +61 -0
- package/src/components/empty-state/illustrations/NoResults.astro +60 -0
- package/src/components/empty-state/illustrations/NothingMatches.astro +66 -0
- package/src/components/empty-state/illustrations/index.ts +27 -0
- package/src/components/empty-state/index.ts +7 -0
- package/src/components/favorite-toggle/CHANGELOG.md +13 -0
- package/src/components/favorite-toggle/FavoriteToggle.astro +94 -0
- package/src/components/favorite-toggle/index.ts +1 -0
- package/src/components/input-group/CHANGELOG.md +7 -0
- package/src/components/input-group/InputGroup.astro +2 -0
- package/src/components/loading-overlay/CHANGELOG.md +25 -0
- package/src/components/loading-overlay/LoadingOverlay.astro +179 -0
- package/src/components/loading-overlay/index.ts +8 -0
- package/src/components/notifications-menu/CHANGELOG.md +22 -0
- package/src/components/notifications-menu/NotificationsMenu.astro +374 -0
- package/src/components/notifications-menu/NotificationsMenuItem.astro +129 -0
- package/src/components/notifications-menu/all-caught-up.svg +28 -0
- package/src/components/notifications-menu/index.ts +3 -0
- package/src/components/notifications-menu/types.ts +32 -0
- package/src/components/option-card/CHANGELOG.md +16 -0
- package/src/components/option-card/OptionCard.astro +135 -0
- package/src/components/option-card/index.ts +1 -0
- package/src/components/organisms/top-bar/CHANGELOG.md +21 -0
- package/src/components/organisms/top-bar/TopBar.astro +264 -0
- package/src/components/organisms/top-bar/index.ts +2 -0
- package/src/components/organisms/top-bar/types.ts +30 -0
- package/src/components/organisms/workspace/CHANGELOG.md +73 -0
- package/src/components/organisms/workspace/OrganisationOverview.astro +62 -0
- package/src/components/organisms/workspace/RecentWorkspaceCard.astro +235 -0
- package/src/components/organisms/workspace/RecentWorkspaces.astro +93 -0
- package/src/components/organisms/workspace/WorkspaceAvatar.astro +144 -0
- package/src/components/organisms/workspace/WorkspaceCard.astro +172 -0
- package/src/components/organisms/workspace/WorkspaceGlyph.astro +62 -0
- package/src/components/organisms/workspace/WorkspaceIcon.astro +42 -0
- package/src/components/organisms/workspace/WorkspaceIconPicker.astro +288 -0
- package/src/components/organisms/workspace/WorkspaceIconPickerGlyph.astro +44 -0
- package/src/components/organisms/workspace/icons.ts +98 -0
- package/src/components/organisms/workspace/index.ts +35 -0
- package/src/components/organisms/workspace/labels.ts +54 -0
- package/src/components/organisms/workspace/recent-card-rings-strong.png +0 -0
- package/src/components/organisms/workspace/types.ts +30 -0
- package/src/components/profile-menu/CHANGELOG.md +22 -0
- package/src/components/profile-menu/ProfileMenu.astro +236 -0
- package/src/components/profile-menu/index.ts +2 -0
- package/src/components/profile-menu/types.ts +27 -0
- package/src/components/search-field/CHANGELOG.md +14 -0
- package/src/components/search-field/SearchField.astro +163 -0
- package/src/components/search-field/index.ts +1 -0
- package/src/components/table/CHANGELOG.md +9 -0
- package/src/components/table/TableCell.astro +6 -0
- package/src/components/table/TableHead.astro +5 -1
- package/src/components/table/TableRow.astro +3 -1
- package/src/components/table/table-cell-variants.ts +2 -0
- package/src/components/toast/CHANGELOG.md +32 -0
- package/src/components/toast/Toast.astro +145 -0
- package/src/components/toast/Toaster.astro +322 -0
- package/src/components/toast/index.ts +9 -0
- package/src/components/toggle/CHANGELOG.md +7 -0
- package/src/lib/command-search.ts +73 -0
- package/src/lib/loading-overlay.ts +174 -0
- package/src/lib/toast.ts +127 -0
- package/src/lib/toggle-shared.ts +4 -1
- package/src/lib/workspace-palette.ts +119 -0
- package/src/styles/CHANGELOG.md +22 -0
- package/src/styles/globals.css +87 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* @component RecentWorkspaceCard
|
|
4
|
+
* @title Recent Workspace Card
|
|
5
|
+
* @description Organism. A recently opened workspace as a shortcut card, in four sizes: `lg` (the large card on the workspace's gradient, "Jump back in"), `md` (tall, beside the large card when there are two recents), `sm` (two stacked) and `xs` (three stacked, one row). The whole card is the link.
|
|
6
|
+
*
|
|
7
|
+
* @preview
|
|
8
|
+
*
|
|
9
|
+
* <div class="grid w-full max-w-3xl gap-4 sm:grid-cols-3">
|
|
10
|
+
* <RecentWorkspaceCard
|
|
11
|
+
* class="sm:col-span-2"
|
|
12
|
+
* name="Acme Logistics"
|
|
13
|
+
* href="#"
|
|
14
|
+
* color="blue"
|
|
15
|
+
* icon="truck"
|
|
16
|
+
* opened="2 hours ago"
|
|
17
|
+
* lastEditedBy="Ana Silva"
|
|
18
|
+
* recordings={128}
|
|
19
|
+
* />
|
|
20
|
+
* <RecentWorkspaceCard size="md" name="Globex Finance" href="#" color="emerald" icon="wallet" opened="yesterday" lastEditedBy="You" recordings={42} />
|
|
21
|
+
* </div>
|
|
22
|
+
*
|
|
23
|
+
* @usage
|
|
24
|
+
*
|
|
25
|
+
* ```astro nocollapse
|
|
26
|
+
* ---
|
|
27
|
+
* import { RecentWorkspaceCard } from '@cl-light-ds/ui/components/organisms/workspace';
|
|
28
|
+
* ---
|
|
29
|
+
*
|
|
30
|
+
* <RecentWorkspaceCard size="lg" name={ws.name} href={ws.url} color={ws.color} icon={ws.icon} opened="2 hours ago" lastEditedBy="Ana Silva" recordings={128} />
|
|
31
|
+
* <RecentWorkspaceCard size="xs" name={ws.name} href={ws.url} color={ws.color} icon={ws.icon} opened="3 days ago" />
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* Usually laid out by `RecentWorkspaces`, which picks the sizes from the
|
|
35
|
+
* number of recents. On their own:
|
|
36
|
+
*
|
|
37
|
+
* - `lg`: gradient in the workspace colour (or the brand gradient with
|
|
38
|
+
* `tone="brand"`), 16px corners, the ring artwork bottom right; 56px inverse
|
|
39
|
+
* avatar top left, "Jump back in" (12px caps) top right; the name (30px
|
|
40
|
+
* semibold) on a divider; "Opened … · Last edited by …" and the recordings
|
|
41
|
+
* (14px) under it; a white arrow centred on the right. At least 222px high.
|
|
42
|
+
* - `md`: white; 56px soft avatar; the name (18px) and "Opened …" (16px) on
|
|
43
|
+
* the divider; "Last edited by …" and recordings (14px); 20px arrow centred
|
|
44
|
+
* on the right. At least 222px high.
|
|
45
|
+
* - `sm`: 40px avatar, then the name (14px semibold), then "Opened …" (12px);
|
|
46
|
+
* 20px arrow centred on the right.
|
|
47
|
+
* - `xs`: one row: 40px avatar, name and "Opened …", 16px arrow.
|
|
48
|
+
*
|
|
49
|
+
* Every size is `Card as="a" interactive`: one link (no controls inside),
|
|
50
|
+
* with the card's focus ring and its hover / focus lift. The full name is in
|
|
51
|
+
* `title`, since long names truncate.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
import type { HTMLAttributes } from "astro/types";
|
|
55
|
+
import { ArrowRight, RotateCcwClock } from "@lucide/astro";
|
|
56
|
+
import { cn } from "@cl-light-ds/ui/lib/utils";
|
|
57
|
+
import {
|
|
58
|
+
DEFAULT_WORKSPACE_COLOR,
|
|
59
|
+
brandGradient,
|
|
60
|
+
workspacePalette,
|
|
61
|
+
type WorkspaceColor,
|
|
62
|
+
} from "@cl-light-ds/ui/lib/workspace-palette";
|
|
63
|
+
import { Card } from "../../card";
|
|
64
|
+
import type { WorkspaceGlyphName } from "./icons";
|
|
65
|
+
import { workspaceCardTexts, type WorkspaceCardLabels } from "./labels";
|
|
66
|
+
import type { RecentWorkspaceCardSize, RecentWorkspaceTone } from "./types";
|
|
67
|
+
import WorkspaceAvatar from "./WorkspaceAvatar.astro";
|
|
68
|
+
import rings from "./recent-card-rings-strong.png?url";
|
|
69
|
+
|
|
70
|
+
interface Props extends Omit<HTMLAttributes<"a">, "class"> {
|
|
71
|
+
/** Workspace name (required). */
|
|
72
|
+
name: string;
|
|
73
|
+
/** The workspace's address (required): the whole card is this link. */
|
|
74
|
+
href: string;
|
|
75
|
+
/** Palette colour. Defaults to blue. */
|
|
76
|
+
color?: WorkspaceColor;
|
|
77
|
+
/** Placeholder icon, or `"initial"` (default). */
|
|
78
|
+
icon?: WorkspaceGlyphName | (string & {});
|
|
79
|
+
/** `lg` (default), `md`, `sm` or `xs`. */
|
|
80
|
+
size?: RecentWorkspaceCardSize;
|
|
81
|
+
/** `lg` only: the workspace's own gradient (default) or the brand gradient. */
|
|
82
|
+
tone?: RecentWorkspaceTone;
|
|
83
|
+
/** When it was last opened, e.g. "2 hours ago". */
|
|
84
|
+
opened?: string;
|
|
85
|
+
/** Who last edited it (`lg` and `md`). */
|
|
86
|
+
lastEditedBy?: string;
|
|
87
|
+
/** Number of recordings, or a ready-made string (`lg` and `md`). */
|
|
88
|
+
recordings?: number | string;
|
|
89
|
+
/** Copy overrides (see `WorkspaceCardLabels`). */
|
|
90
|
+
labels?: Partial<WorkspaceCardLabels>;
|
|
91
|
+
class?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const {
|
|
95
|
+
name,
|
|
96
|
+
href,
|
|
97
|
+
color = DEFAULT_WORKSPACE_COLOR,
|
|
98
|
+
icon = "initial",
|
|
99
|
+
size = "lg",
|
|
100
|
+
tone = "workspace",
|
|
101
|
+
opened,
|
|
102
|
+
lastEditedBy,
|
|
103
|
+
recordings,
|
|
104
|
+
labels,
|
|
105
|
+
class: className = "",
|
|
106
|
+
...rest
|
|
107
|
+
} = Astro.props as Props;
|
|
108
|
+
|
|
109
|
+
const text = workspaceCardTexts({ name, opened, lastEditedBy, recordings }, labels);
|
|
110
|
+
const swatch = workspacePalette[color] ?? workspacePalette[DEFAULT_WORKSPACE_COLOR];
|
|
111
|
+
const largeMeta = [text.opened, text.lastEditedBy].filter(Boolean).join(" · ");
|
|
112
|
+
|
|
113
|
+
const sizeClasses: Record<RecentWorkspaceCardSize, string> = {
|
|
114
|
+
lg: cn(
|
|
115
|
+
"isolate min-h-55.5 gap-0 rounded-2xl border-transparent bg-linear-to-br p-0 text-primary-foreground focus-visible:ring-3 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
116
|
+
tone === "brand" ? brandGradient : swatch.gradient,
|
|
117
|
+
),
|
|
118
|
+
md: "min-h-55.5 gap-0 p-0",
|
|
119
|
+
sm: "justify-center gap-1.5 px-3 py-2.5 pe-11",
|
|
120
|
+
xs: "flex-row items-center gap-3 p-3",
|
|
121
|
+
};
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
<Card
|
|
125
|
+
{...rest}
|
|
126
|
+
as="a"
|
|
127
|
+
href={href}
|
|
128
|
+
title={name}
|
|
129
|
+
interactive
|
|
130
|
+
data-recent-workspace-card
|
|
131
|
+
data-recent-size={size}
|
|
132
|
+
data-color={color}
|
|
133
|
+
class={cn(sizeClasses[size], className)}
|
|
134
|
+
>
|
|
135
|
+
{
|
|
136
|
+
size === "lg" && (
|
|
137
|
+
<>
|
|
138
|
+
<div class="flex items-start justify-between gap-4 px-7 pt-6">
|
|
139
|
+
<WorkspaceAvatar
|
|
140
|
+
color={color}
|
|
141
|
+
icon={icon}
|
|
142
|
+
name={name}
|
|
143
|
+
size="lg"
|
|
144
|
+
shape="square"
|
|
145
|
+
tone={tone === "brand" ? "brand" : "inverse"}
|
|
146
|
+
/>
|
|
147
|
+
<span class="flex items-center gap-2 text-xs font-semibold tracking-wide text-primary-foreground/90 uppercase">
|
|
148
|
+
<RotateCcwClock class="size-4" aria-hidden="true" />
|
|
149
|
+
{text.jumpBackIn}
|
|
150
|
+
</span>
|
|
151
|
+
</div>
|
|
152
|
+
<ArrowRight
|
|
153
|
+
class="absolute top-1/2 right-7 size-5 -translate-y-1/2 text-primary-foreground/90"
|
|
154
|
+
aria-hidden="true"
|
|
155
|
+
/>
|
|
156
|
+
<div class="mt-auto flex min-w-0 flex-col px-7 pt-3 pb-3 pe-14">
|
|
157
|
+
<span data-slot="recent-workspace-card-name" class="block truncate font-heading text-3xl font-semibold">
|
|
158
|
+
{name}
|
|
159
|
+
</span>
|
|
160
|
+
</div>
|
|
161
|
+
{(largeMeta || text.recordings) && (
|
|
162
|
+
<div class="mx-7 flex items-center justify-between gap-4 border-t border-primary-foreground/25 py-3 text-sm text-primary-foreground/90">
|
|
163
|
+
<span class="min-w-0 truncate">{largeMeta}</span>
|
|
164
|
+
{text.recordings && <span class="shrink-0">{text.recordings}</span>}
|
|
165
|
+
</div>
|
|
166
|
+
)}
|
|
167
|
+
<img
|
|
168
|
+
src={rings}
|
|
169
|
+
alt=""
|
|
170
|
+
aria-hidden="true"
|
|
171
|
+
class="pointer-events-none absolute right-0 bottom-0 -z-10 h-7/5 w-auto max-w-none"
|
|
172
|
+
/>
|
|
173
|
+
</>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
{
|
|
178
|
+
size === "md" && (
|
|
179
|
+
<>
|
|
180
|
+
<div class="px-4 pt-4">
|
|
181
|
+
<WorkspaceAvatar color={color} icon={icon} name={name} size="lg" shape="square" />
|
|
182
|
+
</div>
|
|
183
|
+
<ArrowRight
|
|
184
|
+
class="absolute top-1/2 right-4 size-5 -translate-y-1/2 text-muted-foreground"
|
|
185
|
+
aria-hidden="true"
|
|
186
|
+
/>
|
|
187
|
+
<div class="mt-auto flex min-w-0 flex-col gap-1 px-4 pt-3 pb-4 pe-10">
|
|
188
|
+
<span data-slot="recent-workspace-card-name" class="block truncate text-lg font-semibold text-foreground">
|
|
189
|
+
{name}
|
|
190
|
+
</span>
|
|
191
|
+
{text.opened && <span class="text-base text-muted-foreground">{text.opened}</span>}
|
|
192
|
+
</div>
|
|
193
|
+
{(text.lastEditedBy || text.recordings) && (
|
|
194
|
+
<div class="mx-4 flex items-center justify-between gap-2 border-t py-3 text-sm text-muted-foreground">
|
|
195
|
+
<span class="min-w-0 truncate">{text.lastEditedBy}</span>
|
|
196
|
+
{text.recordings && <span class="shrink-0">{text.recordings}</span>}
|
|
197
|
+
</div>
|
|
198
|
+
)}
|
|
199
|
+
</>
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
{
|
|
204
|
+
size === "sm" && (
|
|
205
|
+
<>
|
|
206
|
+
<WorkspaceAvatar color={color} icon={icon} name={name} shape="square" />
|
|
207
|
+
<span class="flex min-w-0 flex-col">
|
|
208
|
+
<span data-slot="recent-workspace-card-name" class="block truncate text-sm font-semibold text-foreground">
|
|
209
|
+
{name}
|
|
210
|
+
</span>
|
|
211
|
+
{text.opened && <span class="block truncate text-xs text-muted-foreground">{text.opened}</span>}
|
|
212
|
+
</span>
|
|
213
|
+
<ArrowRight
|
|
214
|
+
class="absolute top-1/2 right-3 size-5 -translate-y-1/2 text-muted-foreground"
|
|
215
|
+
aria-hidden="true"
|
|
216
|
+
/>
|
|
217
|
+
</>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
{
|
|
222
|
+
size === "xs" && (
|
|
223
|
+
<>
|
|
224
|
+
<WorkspaceAvatar color={color} icon={icon} name={name} shape="square" />
|
|
225
|
+
<span class="flex min-w-0 flex-1 flex-col">
|
|
226
|
+
<span data-slot="recent-workspace-card-name" class="block truncate text-sm font-semibold text-foreground">
|
|
227
|
+
{name}
|
|
228
|
+
</span>
|
|
229
|
+
{text.opened && <span class="block truncate text-xs text-muted-foreground">{text.opened}</span>}
|
|
230
|
+
</span>
|
|
231
|
+
<ArrowRight class="size-4 shrink-0 text-muted-foreground" aria-hidden="true" />
|
|
232
|
+
</>
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
</Card>
|
|
@@ -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>
|