@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,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
|
+
/>
|