@calliopelabs/ui 0.3.1 → 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/README.md +5 -3
- package/dist/appearance/AppearancePicker.d.ts +19 -0
- package/dist/appearance/AppearancePicker.js +9 -2
- package/dist/appearance/brands.d.ts +10 -9
- package/dist/appearance/brands.js +10 -9
- package/dist/appearance/product.d.ts +1 -1
- package/dist/data/DataTable.js +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/nav/model.d.ts +6 -1
- package/dist/nav/model.js +10 -2
- package/dist/overlays/ConfirmDialog.d.ts +9 -2
- package/dist/overlays/ConfirmDialog.js +9 -5
- package/dist/overlays/Dialog.d.ts +1 -1
- package/dist/overlays/Dialog.js +4 -4
- package/dist/overlays/Drawer.d.ts +3 -3
- package/dist/overlays/Drawer.js +6 -5
- package/dist/page/PageHeader.d.ts +7 -1
- package/dist/page/PageHeader.js +2 -2
- package/dist/primitives/Button.js +4 -4
- package/dist/primitives/Checkbox.js +1 -1
- package/dist/primitives/DropdownMenu.d.ts +3 -0
- package/dist/primitives/DropdownMenu.js +12 -1
- package/dist/primitives/Input.js +1 -1
- package/dist/primitives/Popover.d.ts +2 -0
- package/dist/primitives/Popover.js +6 -0
- package/dist/provider/messages.d.ts +6 -0
- package/dist/provider/messages.js +3 -0
- package/dist/shell/RailTree.d.ts +2 -0
- package/dist/shell/RailTree.js +1 -1
- package/dist/states/EmptyState.js +1 -1
- package/dist/styles/preset.css +38 -1
- package/dist/styles/theme.css +16 -0
- package/dist/theme/tokens.d.ts +4 -0
- package/dist/theme/tokens.js +22 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,9 +75,9 @@ const NAV: NavModel = [
|
|
|
75
75
|
export const astrolift = defineProduct({ ...astroliftBrand, nav: NAV });
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
Astrolift AI: leaf green on the deep planet green sidebar (`#04231a`),
|
|
78
|
+
Astrolift AI: leaf green on the deep planet green sidebar (`#04231a`), six grounds (every one but navy), graphite by default (mist when the system runs light), and its eight named themes.
|
|
79
79
|
|
|
80
|
-
Zentinelle AI: teal (`#37efed`, `#0d7470` on light grounds) on the navy sidebar (`#0b0b19`), a true black ground by default, the family accents beside teal, and no emerald ground. Its seven areas gate rows by role:
|
|
80
|
+
Zentinelle AI: teal (`#37efed`, `#0d7470` on light grounds) on the navy sidebar (`#0b0b19`), a true black ground by default, the family accents beside teal, and no emerald or navy ground. Its seven areas gate rows by role:
|
|
81
81
|
|
|
82
82
|
```ts
|
|
83
83
|
import { defineProduct, zentinelleBrand, type NavModel } from "@calliopelabs/ui";
|
|
@@ -116,6 +116,8 @@ const NAV: NavModel = [
|
|
|
116
116
|
export const zentinelle = defineProduct({ ...zentinelleBrand, nav: NAV });
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
+
Calliope AI Workbench (`workbenchBrand`): Workbench blue (`#8fb8e8`, `#2f5d94` on light grounds) on a navy sidebar (`#0e1730`), its own navy ground by default (mist when the system runs light), the family accents beside its blue, and no emerald ground.
|
|
120
|
+
|
|
119
121
|
`defineProduct` validates the config and throws on anything that would paint wrong: a colour that is not lowercase `#rrggbb`, a default ground the product does not offer, a brand accent key the family already uses.
|
|
120
122
|
|
|
121
123
|
## Wire the shell
|
|
@@ -265,7 +267,7 @@ The package fetches nothing. `DataTable` reads a `TableController`: rows, state,
|
|
|
265
267
|
|
|
266
268
|
## Appearance
|
|
267
269
|
|
|
268
|
-
The person sets four axes, per product: the **ground** (black, charcoal, graphite, emerald, paper, mist; it decides light or dark), the **accent** (the product's own, a family accent, or any colour that stands 3:1 off the ground), the **density** (`compact` hairline rows or boxed `cards`) and the **corners** (0, 2, 4 or 10 px). `AppearancePicker` is the settings UI for all four.
|
|
270
|
+
The person sets four axes, per product: the **ground** (black, charcoal, graphite, emerald, navy, paper, mist; it decides light or dark), the **accent** (the product's own, a family accent, or any colour that stands 3:1 off the ground), the **density** (`compact` hairline rows or boxed `cards`) and the **corners** (0, 2, 4 or 10 px). `AppearancePicker` is the settings UI for all four.
|
|
269
271
|
|
|
270
272
|
- Precedence: a locked org theme, then the person's choice, then the org default, then the product default. Until someone picks a ground, light or dark follows the system.
|
|
271
273
|
- An org policy that arrives after sign-in goes in through `useAppearance().setPolicy`. An app that keeps the preference on its server passes `onChange` and hands the server's copy back through `applyServerAppearance`.
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { type Ground } from "../theme/tokens.js";
|
|
3
|
+
import { type CustomAccent } from "./appearance.js";
|
|
2
4
|
/**
|
|
3
5
|
* The person's appearance for the product in context: theme presets, accent
|
|
4
6
|
* (including any colour that stands 3:1 off the ground), ground, density and
|
|
@@ -8,3 +10,20 @@ import * as React from "react";
|
|
|
8
10
|
export declare function AppearancePicker({ className }: {
|
|
9
11
|
className?: string;
|
|
10
12
|
}): React.JSX.Element;
|
|
13
|
+
export interface CustomAccentFieldProps {
|
|
14
|
+
/** The text field's id; the help text takes `${id}-help`. */
|
|
15
|
+
id: string;
|
|
16
|
+
/** The custom accent in use, or null while a named accent is. */
|
|
17
|
+
value: CustomAccent | null;
|
|
18
|
+
/** The ground the colour must stand 3:1 off. */
|
|
19
|
+
ground: Ground;
|
|
20
|
+
disabled: boolean;
|
|
21
|
+
/** Called only with a colour that passes the contrast check. */
|
|
22
|
+
onChange: (accent: CustomAccent) => void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Any colour as the accent (Astrolift palette option A). It applies only once
|
|
26
|
+
* it stands 3:1 off the ground (WCAG 2.2 §1.4.11); until then the draft stays
|
|
27
|
+
* in the field with the reason.
|
|
28
|
+
*/
|
|
29
|
+
export declare function CustomAccentField({ id, value, ground, disabled, onChange, }: CustomAccentFieldProps): React.JSX.Element;
|
|
@@ -44,17 +44,24 @@ export function AppearancePicker({ className }) {
|
|
|
44
44
|
const id = React.useId();
|
|
45
45
|
const accents = product.accents;
|
|
46
46
|
const activeTheme = product.themes.find((th) => th.ground === appearance.ground && th.accent === appearance.accent);
|
|
47
|
-
return (_jsxs("div", { "data-slot": "appearance-picker", className: cn("flex min-w-0 flex-col gap-6", className), children: [locked && (_jsxs("p", { className: "flex items-center gap-2 rounded-lg border border-info-border bg-info-bg px-3 py-2 text-sm text-info-fg", children: [_jsx(LockIcon, { className: "size-4 shrink-0", "aria-hidden": true }), t.locked] })), product.themes.length > 0 && (_jsx(Group, { legend: t.themes, children: _jsx("div", { className: "grid grid-cols-[repeat(auto-fill,minmax(9rem,1fr))] gap-2", children: product.themes.map((theme) => (_jsxs(Choice, { name: `${id}-theme`, checked: activeTheme?.id === theme.id, disabled: locked, onSelect: () => setAppearance({ ground: theme.ground, accent: theme.accent }), className: "flex-col items-stretch gap-2 p-2", children: [_jsx(ThemePreview, { product: product, ground: theme.ground, accent: accents.find((a) => a.key === theme.accent) }), _jsxs("span", { children: [_jsx("span", { className: "block text-sm font-semibold", children: theme
|
|
47
|
+
return (_jsxs("div", { "data-slot": "appearance-picker", className: cn("flex min-w-0 flex-col gap-6", className), children: [locked && (_jsxs("p", { className: "flex items-center gap-2 rounded-lg border border-info-border bg-info-bg px-3 py-2 text-sm text-info-fg", children: [_jsx(LockIcon, { className: "size-4 shrink-0", "aria-hidden": true }), t.locked] })), product.themes.length > 0 && (_jsx(Group, { legend: t.themes, children: _jsx("div", { className: "grid grid-cols-[repeat(auto-fill,minmax(9rem,1fr))] gap-2", children: product.themes.map((theme) => (_jsxs(Choice, { name: `${id}-theme`, checked: activeTheme?.id === theme.id, disabled: locked, onSelect: () => setAppearance({ ground: theme.ground, accent: theme.accent }), className: "flex-col items-stretch gap-2 p-2", children: [_jsx(ThemePreview, { product: product, ground: theme.ground, accent: accents.find((a) => a.key === theme.accent) }), _jsxs("span", { children: [_jsx("span", { className: "block text-sm font-semibold", children: t.themeName(theme) }), _jsx("span", { className: "block text-xs text-muted-foreground", children: t.themeHint(theme) })] })] }, theme.id))) }) })), _jsxs(Group, { legend: t.accent, children: [_jsx("div", { className: "flex flex-wrap gap-2", children: accents.map((accent) => (_jsxs(Choice, { name: `${id}-accent`, checked: appearance.accent === accent.key, disabled: locked, onSelect: () => setAppearance({ accent: accent.key }), children: [_jsx("span", { "aria-hidden": true, className: "size-3.5 rounded-full", style: { background: accentOn(accent, appearance.ground) } }), t.accentLabel(accent.key, accent.label)] }, accent.key))) }), _jsx(CustomAccentField, { id: `${id}-custom`, value: isCustomAccent(appearance.accent) ? appearance.accent : null, ground: appearance.ground, disabled: locked, onChange: (accent) => setAppearance({ accent }) })] }), _jsx(Group, { legend: t.ground, children: _jsx("div", { className: "flex flex-wrap gap-2", children: product.grounds.map((ground) => (_jsxs(Choice, { name: `${id}-ground`, checked: appearance.ground === ground, disabled: locked, onSelect: () => setAppearance({ ground }), children: [_jsx("span", { "aria-hidden": true, className: "size-3.5 rounded-full border", style: { background: SURFACES[ground].background } }), t.groundLabel(ground, GROUNDS[ground].label), _jsx("span", { className: "text-xs text-muted-foreground", children: t.mode(GROUNDS[ground].mode) })] }, ground))) }) }), _jsx(Group, { legend: t.density, children: _jsx("div", { className: "grid gap-2 sm:grid-cols-2", children: DENSITY_KEYS.map((density) => (_jsxs(Choice, { name: `${id}-density`, checked: appearance.density === density, disabled: locked, onSelect: () => setAppearance({ density }), className: "flex-col items-start gap-0.5 px-3 py-2", children: [_jsx("span", { className: "text-sm font-semibold", children: t.densityLabel(density) }), _jsx("span", { className: "text-xs text-muted-foreground", children: t.densityHint(density) })] }, density))) }) }), _jsx(Group, { legend: t.corners, children: _jsx("div", { className: "flex flex-wrap gap-2", children: CORNERS.map((corners) => (_jsxs(Choice, { name: `${id}-corners`, checked: appearance.corners === corners, disabled: locked, onSelect: () => setAppearance({ corners }), children: [_jsx("span", { "aria-hidden": true, className: "size-3.5 border-t-2 border-l-2 border-current", style: { borderTopLeftRadius: corners } }), t.cornersLabel(corners)] }, corners))) }) }), !locked && (_jsx("div", { children: _jsx(Button, { variant: "outline", size: "sm", onClick: reset, children: t.reset }) }))] }));
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
50
|
* Any colour as the accent (Astrolift palette option A). It applies only once
|
|
51
51
|
* it stands 3:1 off the ground (WCAG 2.2 §1.4.11); until then the draft stays
|
|
52
52
|
* in the field with the reason.
|
|
53
53
|
*/
|
|
54
|
-
function
|
|
54
|
+
export function CustomAccentField({ id, value, ground, disabled, onChange, }) {
|
|
55
55
|
const { messages } = useUI();
|
|
56
56
|
const t = messages.appearance;
|
|
57
57
|
const [draft, setDraft] = React.useState(value ?? "");
|
|
58
|
+
// Controlled by `value`: a new value from outside (a server load, a reset)
|
|
59
|
+
// replaces the draft. Adjusted during render, not in an effect.
|
|
60
|
+
const [shownValue, setShownValue] = React.useState(value);
|
|
61
|
+
if (value !== shownValue) {
|
|
62
|
+
setShownValue(value);
|
|
63
|
+
setDraft(value ?? "");
|
|
64
|
+
}
|
|
58
65
|
const parsed = parseCustomAccent(draft);
|
|
59
66
|
const check = parsed ? accentContrast(parsed, ground) : null;
|
|
60
67
|
const helpId = `${id}-help`;
|
|
@@ -94,24 +94,25 @@ export declare const zentinelleBrand: {
|
|
|
94
94
|
};
|
|
95
95
|
/**
|
|
96
96
|
* Calliope AI Workbench (JunoHub, the installer's default theme, the desktop
|
|
97
|
-
* splash):
|
|
98
|
-
* --calliope-dark)
|
|
97
|
+
* splash): a sober light blue on navy. The navy comes from junohub `custom.css`
|
|
98
|
+
* (--calliope-dark); the hub's teal and violet are retired. Dark only today, so
|
|
99
|
+
* it defaults to its own navy ground, which no other product offers.
|
|
99
100
|
*/
|
|
100
101
|
export declare const workbenchBrand: {
|
|
101
102
|
readonly id: "workbench";
|
|
102
103
|
readonly name: "Calliope AI Workbench";
|
|
103
104
|
readonly accent: {
|
|
104
|
-
readonly key: "workbench-
|
|
105
|
-
readonly label: "Workbench
|
|
106
|
-
readonly dark: "#
|
|
105
|
+
readonly key: "workbench-blue";
|
|
106
|
+
readonly label: "Workbench blue";
|
|
107
|
+
readonly dark: "#8fb8e8";
|
|
107
108
|
readonly onDark: "#0b1120";
|
|
108
|
-
readonly light: "#
|
|
109
|
+
readonly light: "#2f5d94";
|
|
109
110
|
readonly onLight: "#f3fbf7";
|
|
110
111
|
};
|
|
111
|
-
readonly sidebar: "#
|
|
112
|
-
readonly grounds: readonly ["black", "charcoal", "graphite", "paper", "mist"];
|
|
112
|
+
readonly sidebar: "#0e1730";
|
|
113
|
+
readonly grounds: readonly ["navy", "black", "charcoal", "graphite", "paper", "mist"];
|
|
113
114
|
readonly defaultGround: {
|
|
114
|
-
readonly dark: "
|
|
115
|
+
readonly dark: "navy";
|
|
115
116
|
readonly light: "mist";
|
|
116
117
|
};
|
|
117
118
|
};
|
|
@@ -67,24 +67,25 @@ export const zentinelleBrand = {
|
|
|
67
67
|
*/
|
|
68
68
|
/**
|
|
69
69
|
* Calliope AI Workbench (JunoHub, the installer's default theme, the desktop
|
|
70
|
-
* splash):
|
|
71
|
-
* --calliope-dark)
|
|
70
|
+
* splash): a sober light blue on navy. The navy comes from junohub `custom.css`
|
|
71
|
+
* (--calliope-dark); the hub's teal and violet are retired. Dark only today, so
|
|
72
|
+
* it defaults to its own navy ground, which no other product offers.
|
|
72
73
|
*/
|
|
73
74
|
export const workbenchBrand = {
|
|
74
75
|
id: "workbench",
|
|
75
76
|
name: "Calliope AI Workbench",
|
|
76
77
|
accent: {
|
|
77
|
-
key: "workbench-
|
|
78
|
-
label: "Workbench
|
|
79
|
-
dark: "#
|
|
78
|
+
key: "workbench-blue",
|
|
79
|
+
label: "Workbench blue",
|
|
80
|
+
dark: "#8fb8e8",
|
|
80
81
|
onDark: "#0b1120",
|
|
81
82
|
// derived: the hub has no light ground
|
|
82
|
-
light: "#
|
|
83
|
+
light: "#2f5d94",
|
|
83
84
|
onLight: "#f3fbf7",
|
|
84
85
|
},
|
|
85
|
-
sidebar: "#
|
|
86
|
-
grounds: ["black", "charcoal", "graphite", "paper", "mist"],
|
|
87
|
-
defaultGround: { dark: "
|
|
86
|
+
sidebar: "#0e1730",
|
|
87
|
+
grounds: ["navy", "black", "charcoal", "graphite", "paper", "mist"],
|
|
88
|
+
defaultGround: { dark: "navy", light: "mist" },
|
|
88
89
|
};
|
|
89
90
|
/**
|
|
90
91
|
* Calliope AI Chat Studio: blue, from `sql-agent-ui/src/index.css` (--brand
|
|
@@ -36,7 +36,7 @@ export interface ProductConfig {
|
|
|
36
36
|
accent: FamilyAccent | BrandAccent;
|
|
37
37
|
/** The brand surface: the sidebar on dark grounds (graphite and light grounds stay neutral). */
|
|
38
38
|
sidebar: string;
|
|
39
|
-
/** The grounds offered, in picker order. All
|
|
39
|
+
/** The grounds offered, in picker order. All seven when absent. */
|
|
40
40
|
grounds?: readonly Ground[];
|
|
41
41
|
/** The ground until someone chooses: one per mode, following the operating system. */
|
|
42
42
|
defaultGround?: {
|
package/dist/data/DataTable.js
CHANGED
|
@@ -31,7 +31,7 @@ export function DataTable({ label, controller, columns, getRowId, empty, emptyFi
|
|
|
31
31
|
const t = messages.table;
|
|
32
32
|
const contextDensity = useDensity();
|
|
33
33
|
const density = densityProp ?? contextDensity;
|
|
34
|
-
const { rows, state, error, retry, pageSize, clearFilters } = controller;
|
|
34
|
+
const { rows, state, error, retry, pageSize, isFiltered, clearFilters } = controller;
|
|
35
35
|
const cards = density === "cards" && Boolean(renderCard);
|
|
36
36
|
const pageIds = React.useMemo(() => rows.map(getRowId), [rows, getRowId]);
|
|
37
37
|
const visibleColumns = rowActions
|
|
@@ -98,7 +98,7 @@ export function DataTable({ label, controller, columns, getRowId, empty, emptyFi
|
|
|
98
98
|
return (_jsxs("div", { className: "relative", "aria-busy": controller.isStale || undefined, children: [controller.isStale && _jsx(StaleBar, { className: "-top-1.5" }), _jsx("ul", { "aria-label": label, className: grid, children: rows.map((row) => (_jsx("li", { className: cn(box, rowClassName?.(row)), children: rowHref ? (_jsx(Link, { href: rowHref(row), className: "block h-full rounded-lg outline-none after:absolute after:inset-0 focus-visible:after:ring-2 focus-visible:after:ring-ring", children: renderCard?.(row) })) : onRowActivate ? (_jsx("button", { type: "button", "aria-label": rowLabel?.(row), onClick: () => onRowActivate(row), className: "block h-full w-full text-left outline-none after:absolute after:inset-0 focus-visible:after:ring-2 focus-visible:after:ring-ring", children: renderCard?.(row) })) : (renderCard?.(row)) }, getRowId(row)))) })] }));
|
|
99
99
|
}
|
|
100
100
|
})();
|
|
101
|
-
return (_jsxs("div", { "data-slot": "data-table", "data-density": densityProp, className: cn("flex min-w-0 flex-col gap-3", className), children: [chrome === "full" && (_jsx(Toolbar, { controller: controller, placeholder: searchPlaceholder, children: toolbar })), selection && selection.selectedCount > 0 && bulkActions && !cards && (_jsxs("div", { className: "flex flex-wrap items-center gap-3 rounded-lg border bg-muted/50 px-3 py-2", children: [_jsx("span", { className: "text-sm font-medium tabular-nums", children: t.selected(selection.selectedCount) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: selection.clear, children: t.clearSelection }), _jsx("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: bulkActions(selection) })] })), cards ? cardGrid : table, chrome === "full" && _jsx(Pagination, { controller: controller }), state === "loading" && (_jsx("p", { role: "status", className: "sr-only", children: t.loading }))] }));
|
|
101
|
+
return (_jsxs("div", { "data-slot": "data-table", "data-density": densityProp, className: cn("flex min-w-0 flex-col gap-3", className), children: [chrome === "full" && (_jsx(Toolbar, { controller: controller, placeholder: searchPlaceholder, children: toolbar })), selection && selection.selectedCount > 0 && bulkActions && !cards && (_jsxs("div", { className: "flex flex-wrap items-center gap-3 rounded-lg border bg-muted/50 px-3 py-2", children: [_jsx("span", { className: "text-sm font-medium tabular-nums", children: t.selected(selection.selectedCount) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: selection.clear, children: t.clearSelection }), _jsx("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: bulkActions(selection) })] })), cards ? cardGrid : table, chrome === "full" && _jsx(Pagination, { controller: controller }), state === "loading" && (_jsx("p", { role: "status", className: "sr-only", children: t.loading })), isFiltered && state === "ready" && (_jsx("p", { "aria-live": "polite", className: "sr-only", children: t.filteredResults }))] }));
|
|
102
102
|
}
|
|
103
103
|
/**
|
|
104
104
|
* The rows on screen answer the previous question while the next answer is
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { accentContrast, appearanceFromServer, applyAppearance, clearAppearance,
|
|
|
13
13
|
export { appearanceScript, productStyles } from "./appearance/boot.js";
|
|
14
14
|
export { AppearanceScript, type AppearanceScriptProps } from "./appearance/AppearanceScript.js";
|
|
15
15
|
export { AppearanceProvider, useAppearance, useDensity, useProduct, type AppearanceContextValue, type AppearanceProviderProps, } from "./appearance/AppearanceProvider.js";
|
|
16
|
-
export { AppearancePicker } from "./appearance/AppearancePicker.js";
|
|
16
|
+
export { AppearancePicker, CustomAccentField, type CustomAccentFieldProps, } from "./appearance/AppearancePicker.js";
|
|
17
17
|
export { activeFor, areaSwitcher, localizeNav, visibleNav, type CrumbSwitchOption, type NavActive, type NavArea, type NavFunction, type NavGroup, type NavIcon, type NavModel, } from "./nav/model.js";
|
|
18
18
|
export { Nav, type NavProps } from "./nav/Nav.js";
|
|
19
19
|
export { AppShell, type AppShellProps } from "./shell/AppShell.js";
|
|
@@ -44,7 +44,7 @@ export { ConfirmDialog, type ConfirmDialogProps, type ConfirmReason, } from "./o
|
|
|
44
44
|
export { Button, buttonVariants, type ButtonProps } from "./primitives/Button.js";
|
|
45
45
|
export { Input } from "./primitives/Input.js";
|
|
46
46
|
export { Checkbox } from "./primitives/Checkbox.js";
|
|
47
|
-
export { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "./primitives/DropdownMenu.js";
|
|
48
|
-
export { Popover, PopoverContent, PopoverTrigger } from "./primitives/Popover.js";
|
|
47
|
+
export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "./primitives/DropdownMenu.js";
|
|
48
|
+
export { Popover, PopoverAnchor, PopoverContent, PopoverHeader, PopoverTrigger, } from "./primitives/Popover.js";
|
|
49
49
|
export { Tabs, TabsContent, TabsList, TabsTrigger } from "./primitives/Tabs.js";
|
|
50
50
|
export { Skeleton } from "./primitives/Skeleton.js";
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ export { accentContrast, appearanceFromServer, applyAppearance, clearAppearance,
|
|
|
16
16
|
export { appearanceScript, productStyles } from "./appearance/boot.js";
|
|
17
17
|
export { AppearanceScript } from "./appearance/AppearanceScript.js";
|
|
18
18
|
export { AppearanceProvider, useAppearance, useDensity, useProduct, } from "./appearance/AppearanceProvider.js";
|
|
19
|
-
export { AppearancePicker } from "./appearance/AppearancePicker.js";
|
|
19
|
+
export { AppearancePicker, CustomAccentField, } from "./appearance/AppearancePicker.js";
|
|
20
20
|
// Navigation
|
|
21
21
|
export { activeFor, areaSwitcher, localizeNav, visibleNav, } from "./nav/model.js";
|
|
22
22
|
export { Nav } from "./nav/Nav.js";
|
|
@@ -55,7 +55,7 @@ export { ConfirmDialog, } from "./overlays/ConfirmDialog.js";
|
|
|
55
55
|
export { Button, buttonVariants } from "./primitives/Button.js";
|
|
56
56
|
export { Input } from "./primitives/Input.js";
|
|
57
57
|
export { Checkbox } from "./primitives/Checkbox.js";
|
|
58
|
-
export { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "./primitives/DropdownMenu.js";
|
|
59
|
-
export { Popover, PopoverContent, PopoverTrigger } from "./primitives/Popover.js";
|
|
58
|
+
export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "./primitives/DropdownMenu.js";
|
|
59
|
+
export { Popover, PopoverAnchor, PopoverContent, PopoverHeader, PopoverTrigger, } from "./primitives/Popover.js";
|
|
60
60
|
export { Tabs, TabsContent, TabsList, TabsTrigger } from "./primitives/Tabs.js";
|
|
61
61
|
export { Skeleton } from "./primitives/Skeleton.js";
|
package/dist/nav/model.d.ts
CHANGED
|
@@ -59,14 +59,19 @@ export interface CrumbSwitchOption {
|
|
|
59
59
|
label: string;
|
|
60
60
|
href: string;
|
|
61
61
|
active?: boolean;
|
|
62
|
+
/** The function's key, so an app can translate the label from its catalog. */
|
|
63
|
+
navFunction?: string;
|
|
62
64
|
}
|
|
63
65
|
/**
|
|
64
66
|
* The first crumb of a page inside an area: the area's name, switching
|
|
65
67
|
* between its functions with the current one checked. Read from the model,
|
|
66
|
-
* so the sidebar and the breadcrumb always agree.
|
|
68
|
+
* so the sidebar and the breadcrumb always agree. `navArea` and each
|
|
69
|
+
* option's `navFunction` carry the model's keys, so an app can translate the
|
|
70
|
+
* labels from its catalog.
|
|
67
71
|
*/
|
|
68
72
|
export declare function areaSwitcher(nav: NavModel, area: string, activeFn: string): {
|
|
69
73
|
label: string;
|
|
74
|
+
navArea: string;
|
|
70
75
|
switcher: CrumbSwitchOption[];
|
|
71
76
|
};
|
|
72
77
|
/**
|
package/dist/nav/model.js
CHANGED
|
@@ -40,13 +40,21 @@ export function activeFor(nav, pathname) {
|
|
|
40
40
|
/**
|
|
41
41
|
* The first crumb of a page inside an area: the area's name, switching
|
|
42
42
|
* between its functions with the current one checked. Read from the model,
|
|
43
|
-
* so the sidebar and the breadcrumb always agree.
|
|
43
|
+
* so the sidebar and the breadcrumb always agree. `navArea` and each
|
|
44
|
+
* option's `navFunction` carry the model's keys, so an app can translate the
|
|
45
|
+
* labels from its catalog.
|
|
44
46
|
*/
|
|
45
47
|
export function areaSwitcher(nav, area, activeFn) {
|
|
46
48
|
const found = nav.find((a) => a.key === area);
|
|
47
49
|
return {
|
|
48
50
|
label: found?.label ?? area,
|
|
49
|
-
|
|
51
|
+
navArea: area,
|
|
52
|
+
switcher: (found?.groups ?? []).flatMap((group) => group.functions.map((fn) => ({
|
|
53
|
+
label: fn.label,
|
|
54
|
+
href: fn.href,
|
|
55
|
+
active: fn.key === activeFn,
|
|
56
|
+
navFunction: fn.key,
|
|
57
|
+
}))),
|
|
50
58
|
};
|
|
51
59
|
}
|
|
52
60
|
/**
|
|
@@ -23,9 +23,16 @@ export interface ConfirmDialogProps {
|
|
|
23
23
|
* Awaited on confirm, with the trimmed reason ("" without one). While it is
|
|
24
24
|
* pending both buttons are disabled. Resolving `false` keeps the dialog
|
|
25
25
|
* open (the caller reported the failure); any other result closes it. A
|
|
26
|
-
* rejection keeps it open and shows the error's message in the dialog
|
|
26
|
+
* rejection keeps it open and shows the error's message in the dialog,
|
|
27
|
+
* or hands it to `onError` when one is given.
|
|
27
28
|
*/
|
|
28
29
|
onConfirm: (reason: string) => Promise<unknown> | unknown;
|
|
30
|
+
/**
|
|
31
|
+
* Report a rejected `onConfirm` yourself (a toast, say) instead of the
|
|
32
|
+
* inline error. `message` is the line the dialog would have shown. The
|
|
33
|
+
* dialog still stays open. A missing reason is always shown inline.
|
|
34
|
+
*/
|
|
35
|
+
onError?: (error: unknown, message: string) => void;
|
|
29
36
|
reason?: ConfirmReason;
|
|
30
37
|
}
|
|
31
38
|
/**
|
|
@@ -34,4 +41,4 @@ export interface ConfirmDialogProps {
|
|
|
34
41
|
* place of `window.confirm`, which blocks the event loop and can say nothing
|
|
35
42
|
* when the action fails.
|
|
36
43
|
*/
|
|
37
|
-
export declare function ConfirmDialog({ open, onOpenChange, title, description, confirmLabel, cancelLabel, destructive, confirmDisabled, onConfirm, reason, }: ConfirmDialogProps): React.JSX.Element;
|
|
44
|
+
export declare function ConfirmDialog({ open, onOpenChange, title, description, confirmLabel, cancelLabel, destructive, confirmDisabled, onConfirm, onError, reason, }: ConfirmDialogProps): React.JSX.Element;
|
|
@@ -12,7 +12,7 @@ import { overlayClassName } from "./Dialog.js";
|
|
|
12
12
|
* place of `window.confirm`, which blocks the event loop and can say nothing
|
|
13
13
|
* when the action fails.
|
|
14
14
|
*/
|
|
15
|
-
export function ConfirmDialog({ open, onOpenChange, title, description, confirmLabel, cancelLabel, destructive = false, confirmDisabled = false, onConfirm, reason, }) {
|
|
15
|
+
export function ConfirmDialog({ open, onOpenChange, title, description, confirmLabel, cancelLabel, destructive = false, confirmDisabled = false, onConfirm, onError, reason, }) {
|
|
16
16
|
const { messages } = useUI();
|
|
17
17
|
const t = messages.confirm;
|
|
18
18
|
const [pending, setPending] = React.useState(false);
|
|
@@ -44,11 +44,15 @@ export function ConfirmDialog({ open, onOpenChange, title, description, confirmL
|
|
|
44
44
|
close();
|
|
45
45
|
}
|
|
46
46
|
catch (err) {
|
|
47
|
-
|
|
47
|
+
const message = err instanceof Error && err.message
|
|
48
48
|
? err.message
|
|
49
49
|
: typeof err === "string" && err
|
|
50
50
|
? err
|
|
51
|
-
: t.failed
|
|
51
|
+
: t.failed;
|
|
52
|
+
if (onError)
|
|
53
|
+
onError(err, message);
|
|
54
|
+
else
|
|
55
|
+
setError(message);
|
|
52
56
|
}
|
|
53
57
|
finally {
|
|
54
58
|
setPending(false);
|
|
@@ -62,9 +66,9 @@ export function ConfirmDialog({ open, onOpenChange, title, description, confirmL
|
|
|
62
66
|
onOpenChange(true);
|
|
63
67
|
else
|
|
64
68
|
close();
|
|
65
|
-
}, children: _jsxs(AlertDialogPrimitive.Portal, { children: [_jsx(AlertDialogPrimitive.Overlay, { "data-slot": "confirm-dialog-overlay", className: overlayClassName() }), _jsxs(AlertDialogPrimitive.Content, { "data-slot": "confirm-dialog", "aria-busy": pending || undefined, ...(description == null ? { "aria-describedby": undefined } : {}), className: "fixed top-1/2 left-1/2 z-50 grid w-[calc(100%-2rem)] max-w-
|
|
69
|
+
}, children: _jsxs(AlertDialogPrimitive.Portal, { children: [_jsx(AlertDialogPrimitive.Overlay, { "data-slot": "confirm-dialog-overlay", className: overlayClassName("bg-black/10") }), _jsxs(AlertDialogPrimitive.Content, { "data-slot": "confirm-dialog", "aria-busy": pending || undefined, ...(description == null ? { "aria-describedby": undefined } : {}), className: "fixed top-1/2 left-1/2 z-50 grid w-[calc(100%-2rem)] max-w-xs -translate-x-1/2 -translate-y-1/2 grid-cols-[minmax(0,1fr)] gap-4 rounded-xl bg-background p-4 text-foreground shadow-lg ring-1 ring-foreground/10 outline-none data-[state=closed]:animate-cui-pop-out data-[state=open]:animate-cui-pop-in sm:max-w-sm", children: [_jsxs("div", { "data-slot": "confirm-dialog-header", className: "grid place-items-center gap-1.5 text-center sm:place-items-start sm:text-left", children: [_jsx(AlertDialogPrimitive.Title, { className: "font-head text-base font-semibold [overflow-wrap:anywhere]", children: title }), description != null && (_jsx(AlertDialogPrimitive.Description, { className: "text-sm [overflow-wrap:anywhere] text-muted-foreground", children: description }))] }), reason && (_jsxs("div", { className: "grid min-w-0 gap-1.5", children: [_jsx("label", { htmlFor: reasonId, className: "text-sm font-medium", children: reason.label }), _jsx("textarea", { id: reasonId, value: draft, rows: 4, disabled: pending, placeholder: reason.placeholder, "aria-invalid": error != null || undefined, "aria-describedby": error ? errorId : undefined, onChange: (event) => {
|
|
66
70
|
setDraft(event.target.value);
|
|
67
71
|
if (error)
|
|
68
72
|
setError(null);
|
|
69
|
-
}, className: "min-h-20 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-sm outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:opacity-50 aria-invalid:border-destructive" })] })), error && (_jsx("p", { id: errorId, role: "alert", className: "rounded-lg border border-danger-border bg-danger-bg px-3 py-2 text-sm [overflow-wrap:anywhere] text-danger-fg", children: error })), _jsxs("div", { className: "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", children: [_jsx(AlertDialogPrimitive.Cancel, { asChild: true, children: _jsx(Button, { variant: "outline", disabled: pending, children: cancelLabel ?? t.cancel }) }), _jsx(AlertDialogPrimitive.Action, { asChild: true, children: _jsx(Button, { variant: destructive ? "destructive" : "default", disabled: pending || confirmDisabled, onClick: confirm, className: cn(pending && "cursor-progress"), children: pending ? t.working : (confirmLabel ?? t.confirm) }) })] })] })] }) }));
|
|
73
|
+
}, className: "min-h-20 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-sm outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:opacity-50 aria-invalid:border-destructive" })] })), error && (_jsx("p", { id: errorId, role: "alert", className: "rounded-lg border border-danger-border bg-danger-bg px-3 py-2 text-sm [overflow-wrap:anywhere] text-danger-fg", children: error })), _jsxs("div", { "data-slot": "confirm-dialog-footer", className: "-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end", children: [_jsx(AlertDialogPrimitive.Cancel, { asChild: true, children: _jsx(Button, { variant: "outline", disabled: pending, children: cancelLabel ?? t.cancel }) }), _jsx(AlertDialogPrimitive.Action, { asChild: true, children: _jsx(Button, { variant: destructive ? "destructive" : "default", disabled: pending || confirmDisabled, onClick: confirm, className: cn(pending && "cursor-progress"), children: pending ? t.working : (confirmLabel ?? t.confirm) }) })] })] })] }) }));
|
|
70
74
|
}
|
|
@@ -3,7 +3,7 @@ import type * as React from "react";
|
|
|
3
3
|
export declare function Dialog(props: React.ComponentProps<typeof DialogPrimitive.Root>): React.JSX.Element;
|
|
4
4
|
export declare function DialogTrigger(props: React.ComponentProps<typeof DialogPrimitive.Trigger>): React.JSX.Element;
|
|
5
5
|
export declare function DialogClose(props: React.ComponentProps<typeof DialogPrimitive.Close>): React.JSX.Element;
|
|
6
|
-
/** The scrim behind a modal; shared with Drawer. */
|
|
6
|
+
/** The scrim behind a modal; shared with Drawer and ConfirmDialog, which lighten it. */
|
|
7
7
|
export declare function overlayClassName(className?: string): string;
|
|
8
8
|
/**
|
|
9
9
|
* A centred modal on the raised surface. Focus moves in on open and back to
|
package/dist/overlays/Dialog.js
CHANGED
|
@@ -13,9 +13,9 @@ export function DialogTrigger(props) {
|
|
|
13
13
|
export function DialogClose(props) {
|
|
14
14
|
return _jsx(DialogPrimitive.Close, { "data-slot": "dialog-close", ...props });
|
|
15
15
|
}
|
|
16
|
-
/** The scrim behind a modal; shared with Drawer. */
|
|
16
|
+
/** The scrim behind a modal; shared with Drawer and ConfirmDialog, which lighten it. */
|
|
17
17
|
export function overlayClassName(className) {
|
|
18
|
-
return cn("fixed inset-0 z-50 bg-black/
|
|
18
|
+
return cn("fixed inset-0 z-50 bg-black/30 data-[state=closed]:animate-cui-fade-out data-[state=open]:animate-cui-fade-in supports-backdrop-filter:backdrop-blur-xs", className);
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* A centred modal on the raised surface. Focus moves in on open and back to
|
|
@@ -23,10 +23,10 @@ export function overlayClassName(className) {
|
|
|
23
23
|
*/
|
|
24
24
|
export function DialogContent({ className, children, hideCloseButton = false, ...props }) {
|
|
25
25
|
const { messages } = useUI();
|
|
26
|
-
return (_jsxs(DialogPrimitive.Portal, { children: [_jsx(DialogPrimitive.Overlay, { "data-slot": "dialog-overlay", className: overlayClassName() }), _jsxs(DialogPrimitive.Content, { "data-slot": "dialog-content", className: cn("fixed top-1/2 left-1/2 z-50 grid max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-lg -translate-x-1/2 -translate-y-1/2 grid-cols-[minmax(0,1fr)] gap-4 overflow-y-auto rounded-
|
|
26
|
+
return (_jsxs(DialogPrimitive.Portal, { children: [_jsx(DialogPrimitive.Overlay, { "data-slot": "dialog-overlay", className: overlayClassName() }), _jsxs(DialogPrimitive.Content, { "data-slot": "dialog-content", className: cn("fixed top-1/2 left-1/2 z-50 grid max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-lg -translate-x-1/2 -translate-y-1/2 grid-cols-[minmax(0,1fr)] gap-4 overflow-y-auto rounded-xl bg-background p-6 text-foreground shadow-lg ring-1 ring-foreground/10 outline-none data-[state=closed]:animate-cui-pop-out data-[state=open]:animate-cui-pop-in sm:max-w-xl", className), ...props, children: [children, !hideCloseButton && (_jsx(DialogPrimitive.Close, { "data-slot": "dialog-close-button", "aria-label": messages.dialog.close, className: "absolute top-3 right-3 rounded-lg p-1 text-muted-foreground transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", children: _jsx(XIcon, { className: "size-4", "aria-hidden": true }) }))] })] }));
|
|
27
27
|
}
|
|
28
28
|
export function DialogHeader({ className, ...props }) {
|
|
29
|
-
return (_jsx("div", { "data-slot": "dialog-header", className: cn("flex flex-col gap-1.5 pr-6", className), ...props }));
|
|
29
|
+
return (_jsx("div", { "data-slot": "dialog-header", className: cn("flex flex-col gap-1.5 pr-6 text-left", className), ...props }));
|
|
30
30
|
}
|
|
31
31
|
export function DialogFooter({ className, ...props }) {
|
|
32
32
|
return (_jsx("div", { "data-slot": "dialog-footer", className: cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className), ...props }));
|
|
@@ -4,9 +4,9 @@ export declare function Drawer(props: React.ComponentProps<typeof DrawerPrimitiv
|
|
|
4
4
|
export declare function DrawerTrigger(props: React.ComponentProps<typeof DrawerPrimitive.Trigger>): React.JSX.Element;
|
|
5
5
|
export declare function DrawerClose(props: React.ComponentProps<typeof DrawerPrimitive.Close>): React.JSX.Element;
|
|
6
6
|
declare const SIDE: {
|
|
7
|
-
readonly right: "inset-y-0 right-0 h-full w-3/4 border-l data-[state=open]:animate-cui-slide-in-right sm:max-w-sm";
|
|
8
|
-
readonly left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=open]:animate-cui-slide-in-left sm:max-w-sm";
|
|
9
|
-
readonly bottom: "inset-x-0 bottom-0 max-h-[85dvh] border-t data-[state=open]:animate-cui-slide-in-bottom";
|
|
7
|
+
readonly right: "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:animate-cui-slide-out-right data-[state=open]:animate-cui-slide-in-right sm:max-w-sm";
|
|
8
|
+
readonly left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:animate-cui-slide-out-left data-[state=open]:animate-cui-slide-in-left sm:max-w-sm";
|
|
9
|
+
readonly bottom: "inset-x-0 bottom-0 max-h-[85dvh] border-t data-[state=closed]:animate-cui-slide-out-bottom data-[state=open]:animate-cui-slide-in-bottom";
|
|
10
10
|
};
|
|
11
11
|
/**
|
|
12
12
|
* A modal panel from an edge: secondary content that should not cost a page
|
package/dist/overlays/Drawer.js
CHANGED
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { XIcon } from "lucide-react";
|
|
4
4
|
import { Dialog as DrawerPrimitive } from "radix-ui";
|
|
5
5
|
import { cn } from "../lib/cn.js";
|
|
6
|
+
import { Button } from "../primitives/Button.js";
|
|
6
7
|
import { useUI } from "../provider/UIProvider.js";
|
|
7
8
|
import { overlayClassName } from "./Dialog.js";
|
|
8
9
|
export function Drawer(props) {
|
|
@@ -15,9 +16,9 @@ export function DrawerClose(props) {
|
|
|
15
16
|
return _jsx(DrawerPrimitive.Close, { "data-slot": "drawer-close", ...props });
|
|
16
17
|
}
|
|
17
18
|
const SIDE = {
|
|
18
|
-
right: "inset-y-0 right-0 h-full w-3/4 border-l data-[state=open]:animate-cui-slide-in-right sm:max-w-sm",
|
|
19
|
-
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=open]:animate-cui-slide-in-left sm:max-w-sm",
|
|
20
|
-
bottom: "inset-x-0 bottom-0 max-h-[85dvh] border-t data-[state=open]:animate-cui-slide-in-bottom",
|
|
19
|
+
right: "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:animate-cui-slide-out-right data-[state=open]:animate-cui-slide-in-right sm:max-w-sm",
|
|
20
|
+
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:animate-cui-slide-out-left data-[state=open]:animate-cui-slide-in-left sm:max-w-sm",
|
|
21
|
+
bottom: "inset-x-0 bottom-0 max-h-[85dvh] border-t data-[state=closed]:animate-cui-slide-out-bottom data-[state=open]:animate-cui-slide-in-bottom",
|
|
21
22
|
};
|
|
22
23
|
/**
|
|
23
24
|
* A modal panel from an edge: secondary content that should not cost a page
|
|
@@ -26,7 +27,7 @@ const SIDE = {
|
|
|
26
27
|
*/
|
|
27
28
|
export function DrawerContent({ className, children, side = "right", hideCloseButton = false, ...props }) {
|
|
28
29
|
const { messages } = useUI();
|
|
29
|
-
return (_jsxs(DrawerPrimitive.Portal, { children: [_jsx(DrawerPrimitive.Overlay, { "data-slot": "drawer-overlay", className: overlayClassName() }), _jsxs(DrawerPrimitive.Content, { "data-slot": "drawer-content", "data-side": side, className: cn("fixed z-50 flex flex-col gap-4 overflow-y-auto border-border bg-background text-sm text-foreground shadow-lg outline-none", SIDE[side], className), ...props, children: [children, !hideCloseButton && (_jsx(DrawerPrimitive.Close, { "data-slot": "drawer-close-button", "aria-label": messages.dialog.close, className: "absolute top-3 right-3
|
|
30
|
+
return (_jsxs(DrawerPrimitive.Portal, { children: [_jsx(DrawerPrimitive.Overlay, { "data-slot": "drawer-overlay", className: overlayClassName("bg-black/10") }), _jsxs(DrawerPrimitive.Content, { "data-slot": "drawer-content", "data-side": side, className: cn("fixed z-50 flex flex-col gap-4 overflow-y-auto border-border bg-background text-sm text-foreground shadow-lg outline-none", SIDE[side], className), ...props, children: [children, !hideCloseButton && (_jsx(DrawerPrimitive.Close, { asChild: true, children: _jsx(Button, { "data-slot": "drawer-close-button", variant: "ghost", size: "icon-sm", "aria-label": messages.dialog.close, className: "absolute top-3 right-3", children: _jsx(XIcon, { "aria-hidden": true }) }) }))] })] }));
|
|
30
31
|
}
|
|
31
32
|
export function DrawerHeader({ className, ...props }) {
|
|
32
33
|
return (_jsx("div", { "data-slot": "drawer-header", className: cn("flex flex-col gap-1 p-4 pr-10", className), ...props }));
|
|
@@ -35,7 +36,7 @@ export function DrawerFooter({ className, ...props }) {
|
|
|
35
36
|
return (_jsx("div", { "data-slot": "drawer-footer", className: cn("mt-auto flex flex-col gap-2 p-4", className), ...props }));
|
|
36
37
|
}
|
|
37
38
|
export function DrawerTitle({ className, ...props }) {
|
|
38
|
-
return (_jsx(DrawerPrimitive.Title, { "data-slot": "drawer-title", className: cn("font-head text-base font-
|
|
39
|
+
return (_jsx(DrawerPrimitive.Title, { "data-slot": "drawer-title", className: cn("font-head text-base font-medium text-foreground", className), ...props }));
|
|
39
40
|
}
|
|
40
41
|
export function DrawerDescription({ className, ...props }) {
|
|
41
42
|
return (_jsx(DrawerPrimitive.Description, { "data-slot": "drawer-description", className: cn("text-sm text-muted-foreground", className), ...props }));
|
|
@@ -18,6 +18,12 @@ export interface PageHeaderProps {
|
|
|
18
18
|
tabs?: readonly TabNavItem[];
|
|
19
19
|
/** Names the tab row. Defaults to "Page". */
|
|
20
20
|
tabsLabel?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Classes for the tab row, merged over its default gutter
|
|
23
|
+
* (`-mx-3 md:-mx-6`). Match the page's padding: a page padded `p-6` at
|
|
24
|
+
* every width passes `-mx-6 px-6`.
|
|
25
|
+
*/
|
|
26
|
+
tabsClassName?: string;
|
|
21
27
|
className?: string;
|
|
22
28
|
}
|
|
23
29
|
/**
|
|
@@ -25,4 +31,4 @@ export interface PageHeaderProps {
|
|
|
25
31
|
* breadcrumb, the title row with its status, context and actions, and at
|
|
26
32
|
* most one row of tabs. Pure.
|
|
27
33
|
*/
|
|
28
|
-
export declare function PageHeader({ crumbs, title, status, context, description, primaryAction, menu, tabs, tabsLabel, className, }: PageHeaderProps): React.JSX.Element;
|
|
34
|
+
export declare function PageHeader({ crumbs, title, status, context, description, primaryAction, menu, tabs, tabsLabel, tabsClassName, className, }: PageHeaderProps): React.JSX.Element;
|
package/dist/page/PageHeader.js
CHANGED
|
@@ -8,6 +8,6 @@ import { TabNav } from "./TabNav.js";
|
|
|
8
8
|
* breadcrumb, the title row with its status, context and actions, and at
|
|
9
9
|
* most one row of tabs. Pure.
|
|
10
10
|
*/
|
|
11
|
-
export function PageHeader({ crumbs, title, status, context, description, primaryAction, menu, tabs, tabsLabel = "Page", className, }) {
|
|
12
|
-
return (_jsxs("header", { "data-slot": "page-header", className: cn("flex min-w-0 flex-col gap-3", className), children: [crumbs && crumbs.length > 0 && _jsx(Breadcrumbs, { crumbs: crumbs }), _jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2", children: [_jsx("h1", { className: "min-w-0 truncate font-head text-xl font-semibold", children: title }), status, context && (_jsx("span", { className: "min-w-0 truncate text-sm text-muted-foreground", children: context })), (primaryAction || menu) && (_jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: [primaryAction, menu] }))] }), description && (_jsx("div", { className: "max-w-2xl text-sm [overflow-wrap:anywhere] text-muted-foreground", children: description })), tabs && tabs.length > 0 && (_jsx(TabNav, { tabs: tabs, label: tabsLabel, className: "-mx-3 md:-mx-6" }))] }));
|
|
11
|
+
export function PageHeader({ crumbs, title, status, context, description, primaryAction, menu, tabs, tabsLabel = "Page", tabsClassName, className, }) {
|
|
12
|
+
return (_jsxs("header", { "data-slot": "page-header", className: cn("flex min-w-0 flex-col gap-3", className), children: [crumbs && crumbs.length > 0 && _jsx(Breadcrumbs, { crumbs: crumbs }), _jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2", children: [_jsx("h1", { className: "min-w-0 truncate font-head text-xl font-semibold", children: title }), status, context && (_jsx("span", { className: "min-w-0 truncate text-sm text-muted-foreground", children: context })), (primaryAction || menu) && (_jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: [primaryAction, menu] }))] }), description && (_jsx("div", { className: "max-w-2xl text-sm [overflow-wrap:anywhere] text-muted-foreground", children: description })), tabs && tabs.length > 0 && (_jsx(TabNav, { tabs: tabs, label: tabsLabel, className: cn("-mx-3 md:-mx-6", tabsClassName) }))] }));
|
|
13
13
|
}
|
|
@@ -7,13 +7,13 @@ import { cn } from "../lib/cn.js";
|
|
|
7
7
|
* corners setting (`rounded-lg` is `--radius`), so a 0px or 10px choice
|
|
8
8
|
* reaches buttons as it reaches panels.
|
|
9
9
|
*/
|
|
10
|
-
export const buttonVariants = cva("group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
|
|
10
|
+
export const buttonVariants = cva("group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
|
|
11
11
|
variants: {
|
|
12
12
|
variant: {
|
|
13
13
|
default: "bg-primary text-primary-foreground hover:bg-primary/85",
|
|
14
|
-
outline: "border-border bg-
|
|
14
|
+
outline: "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
15
15
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary",
|
|
16
|
-
ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground",
|
|
16
|
+
ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
17
17
|
destructive: "bg-danger/10 text-danger-fg hover:bg-danger/15 focus-visible:border-danger-border focus-visible:ring-danger/20",
|
|
18
18
|
link: "text-primary underline-offset-4 hover:underline",
|
|
19
19
|
},
|
|
@@ -32,5 +32,5 @@ export const buttonVariants = cva("group/button inline-flex shrink-0 items-cente
|
|
|
32
32
|
});
|
|
33
33
|
export function Button({ className, variant, size, asChild = false, ...props }) {
|
|
34
34
|
const Component = asChild ? Slot.Root : "button";
|
|
35
|
-
return (_jsx(Component, { "data-slot": "button", "data-variant": variant ?? "default", className: cn(buttonVariants({ variant, size }), className), ...props }));
|
|
35
|
+
return (_jsx(Component, { "data-slot": "button", "data-variant": variant ?? "default", "data-size": size ?? "default", className: cn(buttonVariants({ variant, size }), className), ...props }));
|
|
36
36
|
}
|
|
@@ -9,5 +9,5 @@ import { cn } from "../lib/cn.js";
|
|
|
9
9
|
* state cannot be expressed in JSX on a native box.
|
|
10
10
|
*/
|
|
11
11
|
export function Checkbox({ className, ...props }) {
|
|
12
|
-
return (_jsx(CheckboxPrimitive.Root, { "data-slot": "checkbox", className: cn("peer size-4 shrink-0 rounded-sm border border-input transition-shadow outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground", className), ...props, children: _jsx(CheckboxPrimitive.Indicator, { "data-slot": "checkbox-indicator", className: "flex items-center justify-center text-current", children: props.checked === "indeterminate" ? (_jsx(MinusIcon, { className: "size-3.5", "aria-hidden": true })) : (_jsx(CheckIcon, { className: "size-3.5", "aria-hidden": true })) }) }));
|
|
12
|
+
return (_jsx(CheckboxPrimitive.Root, { "data-slot": "checkbox", className: cn("peer size-4 shrink-0 rounded-sm border border-input shadow-xs transition-shadow outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground dark:bg-input/30", className), ...props, children: _jsx(CheckboxPrimitive.Indicator, { "data-slot": "checkbox-indicator", className: "flex items-center justify-center text-current transition-none", children: props.checked === "indeterminate" ? (_jsx(MinusIcon, { className: "size-3.5", "aria-hidden": true })) : (_jsx(CheckIcon, { className: "size-3.5", "aria-hidden": true })) }) }));
|
|
13
13
|
}
|
|
@@ -7,5 +7,8 @@ export declare function DropdownMenuGroup(props: React.ComponentProps<typeof Dro
|
|
|
7
7
|
export declare function DropdownMenuItem({ className, variant, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
|
8
8
|
variant?: "default" | "destructive";
|
|
9
9
|
}): React.JSX.Element;
|
|
10
|
+
export declare function DropdownMenuCheckboxItem({ className, children, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>): React.JSX.Element;
|
|
11
|
+
export declare function DropdownMenuRadioGroup(props: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>): React.JSX.Element;
|
|
12
|
+
export declare function DropdownMenuRadioItem({ className, children, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>): React.JSX.Element;
|
|
10
13
|
export declare function DropdownMenuLabel({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Label>): React.JSX.Element;
|
|
11
14
|
export declare function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>): React.JSX.Element;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { CheckIcon } from "lucide-react";
|
|
3
4
|
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
|
|
4
5
|
import { cn } from "../lib/cn.js";
|
|
5
6
|
export function DropdownMenu(props) {
|
|
@@ -17,6 +18,16 @@ export function DropdownMenuGroup(props) {
|
|
|
17
18
|
export function DropdownMenuItem({ className, variant = "default", ...props }) {
|
|
18
19
|
return (_jsx(DropdownMenuPrimitive.Item, { "data-slot": "dropdown-menu-item", "data-variant": variant, className: cn("relative flex cursor-default items-center gap-1.5 rounded-lg px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 data-[variant=destructive]:text-danger-fg data-[variant=destructive]:focus:bg-danger/10 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className), ...props }));
|
|
19
20
|
}
|
|
21
|
+
const selectableItemClass = "relative flex cursor-default items-center gap-1.5 rounded-lg py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4";
|
|
22
|
+
export function DropdownMenuCheckboxItem({ className, children, ...props }) {
|
|
23
|
+
return (_jsxs(DropdownMenuPrimitive.CheckboxItem, { "data-slot": "dropdown-menu-checkbox-item", className: cn(selectableItemClass, className), ...props, children: [_jsx("span", { "data-slot": "dropdown-menu-checkbox-item-indicator", className: "pointer-events-none absolute right-2 flex items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(CheckIcon, { "aria-hidden": true }) }) }), children] }));
|
|
24
|
+
}
|
|
25
|
+
export function DropdownMenuRadioGroup(props) {
|
|
26
|
+
return _jsx(DropdownMenuPrimitive.RadioGroup, { "data-slot": "dropdown-menu-radio-group", ...props });
|
|
27
|
+
}
|
|
28
|
+
export function DropdownMenuRadioItem({ className, children, ...props }) {
|
|
29
|
+
return (_jsxs(DropdownMenuPrimitive.RadioItem, { "data-slot": "dropdown-menu-radio-item", className: cn(selectableItemClass, className), ...props, children: [_jsx("span", { "data-slot": "dropdown-menu-radio-item-indicator", className: "pointer-events-none absolute right-2 flex items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(CheckIcon, { "aria-hidden": true }) }) }), children] }));
|
|
30
|
+
}
|
|
20
31
|
export function DropdownMenuLabel({ className, ...props }) {
|
|
21
32
|
return (_jsx(DropdownMenuPrimitive.Label, { "data-slot": "dropdown-menu-label", className: cn("px-1.5 py-1 text-xs font-medium text-muted-foreground", className), ...props }));
|
|
22
33
|
}
|
package/dist/primitives/Input.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
3
|
export function Input({ className, type, ...props }) {
|
|
4
|
-
return (_jsx("input", { type: type, "data-slot": "input", className: cn("h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm", className), ...props }));
|
|
4
|
+
return (_jsx("input", { type: type, "data-slot": "input", className: cn("h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className), ...props }));
|
|
5
5
|
}
|
|
@@ -3,3 +3,5 @@ import type * as React from "react";
|
|
|
3
3
|
export declare function Popover(props: React.ComponentProps<typeof PopoverPrimitive.Root>): React.JSX.Element;
|
|
4
4
|
export declare function PopoverTrigger(props: React.ComponentProps<typeof PopoverPrimitive.Trigger>): React.JSX.Element;
|
|
5
5
|
export declare function PopoverContent({ className, align, sideOffset, ...props }: React.ComponentProps<typeof PopoverPrimitive.Content>): React.JSX.Element;
|
|
6
|
+
export declare function PopoverAnchor(props: React.ComponentProps<typeof PopoverPrimitive.Anchor>): React.JSX.Element;
|
|
7
|
+
export declare function PopoverHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
@@ -11,3 +11,9 @@ export function PopoverTrigger(props) {
|
|
|
11
11
|
export function PopoverContent({ className, align = "center", sideOffset = 4, ...props }) {
|
|
12
12
|
return (_jsx(PopoverPrimitive.Portal, { children: _jsx(PopoverPrimitive.Content, { "data-slot": "popover-content", align: align, sideOffset: sideOffset, className: cn("z-50 flex w-72 origin-(--radix-popover-content-transform-origin) flex-col gap-2 rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden data-[state=open]:animate-cui-fade-in", className), ...props }) }));
|
|
13
13
|
}
|
|
14
|
+
export function PopoverAnchor(props) {
|
|
15
|
+
return _jsx(PopoverPrimitive.Anchor, { "data-slot": "popover-anchor", ...props });
|
|
16
|
+
}
|
|
17
|
+
export function PopoverHeader({ className, ...props }) {
|
|
18
|
+
return (_jsx("div", { "data-slot": "popover-header", className: cn("flex flex-col gap-0.5 text-sm", className), ...props }));
|
|
19
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ProductTheme } from "../appearance/product.js";
|
|
1
2
|
/**
|
|
2
3
|
* Every string the package renders, in English. An app with a message
|
|
3
4
|
* catalog overrides any of them through `UIProvider messages={...}`; a
|
|
@@ -34,6 +35,7 @@ export interface Messages {
|
|
|
34
35
|
search: string;
|
|
35
36
|
clearSearch: string;
|
|
36
37
|
results: (count: number) => string;
|
|
38
|
+
filteredResults: string;
|
|
37
39
|
loading: string;
|
|
38
40
|
loadFailed: (label: string) => string;
|
|
39
41
|
requestFailed: string;
|
|
@@ -83,6 +85,10 @@ export interface Messages {
|
|
|
83
85
|
density: string;
|
|
84
86
|
corners: string;
|
|
85
87
|
reset: string;
|
|
88
|
+
/** A named theme's title; the default is the product's own English name. */
|
|
89
|
+
themeName: (theme: ProductTheme) => string;
|
|
90
|
+
/** A named theme's one-line hint; the default is the product's own English hint. */
|
|
91
|
+
themeHint: (theme: ProductTheme) => string;
|
|
86
92
|
mode: (mode: "dark" | "light") => string;
|
|
87
93
|
groundLabel: (ground: string, label: string) => string;
|
|
88
94
|
accentLabel: (accent: string, label: string) => string;
|
|
@@ -28,6 +28,7 @@ export const defaultMessages = {
|
|
|
28
28
|
search: "Search",
|
|
29
29
|
clearSearch: "Clear search",
|
|
30
30
|
results: (n) => (n === 1 ? "1 result" : `${count(n)} results`),
|
|
31
|
+
filteredResults: "Filtered results shown.",
|
|
31
32
|
loading: "Loading",
|
|
32
33
|
loadFailed: (label) => `Could not load ${lower(label)}`,
|
|
33
34
|
requestFailed: "The request failed.",
|
|
@@ -76,6 +77,8 @@ export const defaultMessages = {
|
|
|
76
77
|
density: "Density",
|
|
77
78
|
corners: "Corners",
|
|
78
79
|
reset: "Use the default",
|
|
80
|
+
themeName: (theme) => theme.name,
|
|
81
|
+
themeHint: (theme) => theme.hint,
|
|
79
82
|
mode: (mode) => mode,
|
|
80
83
|
groundLabel: (_ground, label) => label,
|
|
81
84
|
accentLabel: (_accent, label) => label,
|
package/dist/shell/RailTree.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface RailTreeItem {
|
|
|
9
9
|
status?: StatusTone;
|
|
10
10
|
/** Read with the status dot, which carries no text of its own ("failing"). */
|
|
11
11
|
statusLabel?: string;
|
|
12
|
+
/** In flight (pending, provisioning): the status dot pulses, unless the viewer prefers reduced motion. */
|
|
13
|
+
statusPulse?: boolean;
|
|
12
14
|
}
|
|
13
15
|
export interface RailTreeGroup {
|
|
14
16
|
key: string;
|
package/dist/shell/RailTree.js
CHANGED
|
@@ -27,6 +27,6 @@ function Branch({ group, activeHref }) {
|
|
|
27
27
|
const isActive = item.href === activeHref;
|
|
28
28
|
return (_jsx("li", { children: _jsxs(Link, { href: item.href, "aria-current": isActive ? "page" : undefined, className: cn("flex h-7 min-w-0 items-center gap-2 rounded-lg px-1.5 text-sm focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", isActive
|
|
29
29
|
? "bg-primary/15 font-medium text-sidebar-foreground"
|
|
30
|
-
: "text-sidebar-foreground/80 hover:bg-sidebar-accent/60"), children: [Icon && _jsx(Icon, { className: "size-3.5 shrink-0 opacity-70", "aria-hidden": true }), _jsx("span", { className: "truncate", title: item.label, children: item.label }), " ", item.status && (_jsx(StatusDot, { tone: item.status, label: item.statusLabel, className: "ml-auto" }))] }) }, item.key));
|
|
30
|
+
: "text-sidebar-foreground/80 hover:bg-sidebar-accent/60"), children: [Icon && _jsx(Icon, { className: "size-3.5 shrink-0 opacity-70", "aria-hidden": true }), _jsx("span", { className: "truncate", title: item.label, children: item.label }), " ", item.status && (_jsx(StatusDot, { tone: item.status, pulse: item.statusPulse, label: item.statusLabel, className: "ml-auto" }))] }) }, item.key));
|
|
31
31
|
}) })] }));
|
|
32
32
|
}
|
|
@@ -10,6 +10,6 @@ export function EmptyState({ icon, title, description, actionHref, actionLabel,
|
|
|
10
10
|
const learnMoreText = learnMoreLabel ?? messages.states.learnMore;
|
|
11
11
|
const external = learnMoreHref ? /^https?:\/\//.test(learnMoreHref) : false;
|
|
12
12
|
const learnMoreClass = "mt-2 inline-flex items-center gap-1 text-xs text-muted-foreground underline-offset-2 hover:text-foreground hover:underline";
|
|
13
|
-
return (_jsxs("div", { "data-slot": "empty-state", className: cn("flex flex-col items-center justify-center gap-3 rounded-lg border border-dashed px-6 py-12 text-center whitespace-normal", className), children: [icon && (_jsx("div", { "aria-hidden": true, className: "rounded-lg bg-muted p-2 text-muted-foreground [&_svg]:size-5", children: icon })), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "font-medium [overflow-wrap:anywhere]", children: title }), description && (_jsx("p", { className: "mx-auto mt-1 max-w-sm text-sm leading-relaxed text-muted-foreground", children: description })), learnMoreHref &&
|
|
13
|
+
return (_jsxs("div", { "data-slot": "empty-state", className: cn("flex flex-col items-center justify-center gap-3 rounded-lg border border-dashed px-6 py-12 text-center whitespace-normal", className), children: [icon && (_jsx("div", { "aria-hidden": true, className: "rounded-lg bg-muted p-2 text-muted-foreground [&_svg:not([class*='size-'])]:size-5", children: icon })), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "font-medium [overflow-wrap:anywhere]", children: title }), description && (_jsx("p", { className: "mx-auto mt-1 max-w-sm text-sm leading-relaxed text-muted-foreground", children: description })), learnMoreHref &&
|
|
14
14
|
(external ? (_jsxs("a", { href: learnMoreHref, target: "_blank", rel: "noreferrer", className: learnMoreClass, children: [learnMoreText, _jsx(ExternalLinkIcon, { className: "size-3", "aria-hidden": true })] })) : (_jsx(Link, { href: learnMoreHref, className: learnMoreClass, children: learnMoreText })))] }), actionHref && actionLabel && (_jsx(Button, { asChild: true, size: "sm", children: _jsx(Link, { href: actionHref, children: actionLabel }) })), secondary] }));
|
|
15
15
|
}
|
package/dist/styles/preset.css
CHANGED
|
@@ -94,12 +94,20 @@
|
|
|
94
94
|
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
|
95
95
|
--ease-emphasized: cubic-bezier(0.3, 0, 0, 1);
|
|
96
96
|
|
|
97
|
-
/* Overlays arrive calmly and leave
|
|
97
|
+
/* Overlays arrive calmly and leave quickly. */
|
|
98
98
|
--animate-cui-fade-in: cui-fade-in var(--motion-fast) var(--ease-standard);
|
|
99
99
|
--animate-cui-pop-in: cui-pop-in var(--motion-base) var(--ease-emphasized);
|
|
100
100
|
--animate-cui-slide-in-right: cui-slide-in-right var(--motion-base) var(--ease-emphasized);
|
|
101
101
|
--animate-cui-slide-in-left: cui-slide-in-left var(--motion-base) var(--ease-emphasized);
|
|
102
102
|
--animate-cui-slide-in-bottom: cui-slide-in-bottom var(--motion-base) var(--ease-emphasized);
|
|
103
|
+
/* Exits hold their last frame so nothing flashes back before Radix unmounts. */
|
|
104
|
+
--animate-cui-fade-out: cui-fade-out var(--motion-fast) var(--ease-standard) forwards;
|
|
105
|
+
--animate-cui-pop-out: cui-pop-out var(--motion-fast) var(--ease-standard) forwards;
|
|
106
|
+
--animate-cui-slide-out-right: cui-slide-out-right var(--motion-fast) var(--ease-standard)
|
|
107
|
+
forwards;
|
|
108
|
+
--animate-cui-slide-out-left: cui-slide-out-left var(--motion-fast) var(--ease-standard) forwards;
|
|
109
|
+
--animate-cui-slide-out-bottom: cui-slide-out-bottom var(--motion-fast) var(--ease-standard)
|
|
110
|
+
forwards;
|
|
103
111
|
/* A table's rows being replaced: a short bar travelling along the top edge. */
|
|
104
112
|
--animate-cui-progress: cui-progress 1.4s var(--ease-standard) infinite;
|
|
105
113
|
|
|
@@ -141,6 +149,35 @@
|
|
|
141
149
|
opacity: 0;
|
|
142
150
|
}
|
|
143
151
|
}
|
|
152
|
+
@keyframes cui-fade-out {
|
|
153
|
+
to {
|
|
154
|
+
opacity: 0;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
@keyframes cui-pop-out {
|
|
158
|
+
to {
|
|
159
|
+
opacity: 0;
|
|
160
|
+
transform: scale(0.98);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
@keyframes cui-slide-out-right {
|
|
164
|
+
to {
|
|
165
|
+
transform: translateX(1.5rem);
|
|
166
|
+
opacity: 0;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
@keyframes cui-slide-out-left {
|
|
170
|
+
to {
|
|
171
|
+
transform: translateX(-1.5rem);
|
|
172
|
+
opacity: 0;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
@keyframes cui-slide-out-bottom {
|
|
176
|
+
to {
|
|
177
|
+
transform: translateY(1.5rem);
|
|
178
|
+
opacity: 0;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
144
181
|
}
|
|
145
182
|
|
|
146
183
|
@layer base {
|
package/dist/styles/theme.css
CHANGED
|
@@ -200,6 +200,22 @@
|
|
|
200
200
|
--sidebar-border: rgba(255, 255, 255, 0.08);
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
/* Ground: Navy (dark) */
|
|
204
|
+
[data-mode="dark"][data-ground="navy"] {
|
|
205
|
+
--background: #0b1120;
|
|
206
|
+
--card: #152141;
|
|
207
|
+
--popover: #1a284a;
|
|
208
|
+
--secondary: #1d2b50;
|
|
209
|
+
--muted: #182545;
|
|
210
|
+
--accent: #213157;
|
|
211
|
+
--muted-foreground: #97a6c2;
|
|
212
|
+
--border: rgba(255, 255, 255, 0.1);
|
|
213
|
+
--input: rgba(255, 255, 255, 0.1);
|
|
214
|
+
--sidebar: var(--brand-sidebar, #152141);
|
|
215
|
+
--sidebar-accent: color-mix(in oklab, var(--primary) 16%, transparent);
|
|
216
|
+
--sidebar-border: rgba(255, 255, 255, 0.08);
|
|
217
|
+
}
|
|
218
|
+
|
|
203
219
|
/* Ground: Paper (light) */
|
|
204
220
|
[data-mode="light"][data-ground="paper"] {
|
|
205
221
|
--background: #faf6ee;
|
package/dist/theme/tokens.d.ts
CHANGED
package/dist/theme/tokens.js
CHANGED
|
@@ -19,6 +19,7 @@ export const GROUNDS = {
|
|
|
19
19
|
charcoal: { label: "Charcoal", mode: "dark" },
|
|
20
20
|
graphite: { label: "Graphite", mode: "dark" },
|
|
21
21
|
emerald: { label: "Emerald", mode: "dark" },
|
|
22
|
+
navy: { label: "Navy", mode: "dark" },
|
|
22
23
|
paper: { label: "Paper", mode: "light" },
|
|
23
24
|
mist: { label: "Mist", mode: "light" },
|
|
24
25
|
};
|
|
@@ -82,6 +83,27 @@ export const SURFACES = {
|
|
|
82
83
|
sidebarAccent: PRIMARY_TINT_16,
|
|
83
84
|
sidebarBorder: "rgba(255, 255, 255, 0.08)",
|
|
84
85
|
},
|
|
86
|
+
/*
|
|
87
|
+
* Workbench's navy, from JunoHub's palette: page #0b1120 and card
|
|
88
|
+
* rgba(22, 35, 69, 0.9) composited on the page, with the neutral hairlines
|
|
89
|
+
* every other dark ground uses. Its text colours are not carried over: foreground is per mode,
|
|
90
|
+
* and the hub's muted text (#e6e9ee) is not muted, so muted text is a
|
|
91
|
+
* navy-tinted grey that reads on every panel.
|
|
92
|
+
*/
|
|
93
|
+
navy: {
|
|
94
|
+
background: "#0b1120",
|
|
95
|
+
card: "#152141",
|
|
96
|
+
popover: "#1a284a",
|
|
97
|
+
secondary: "#1d2b50",
|
|
98
|
+
muted: "#182545",
|
|
99
|
+
accent: "#213157",
|
|
100
|
+
mutedForeground: "#97a6c2",
|
|
101
|
+
border: "rgba(255, 255, 255, 0.1)",
|
|
102
|
+
input: "rgba(255, 255, 255, 0.1)",
|
|
103
|
+
sidebar: "brand",
|
|
104
|
+
sidebarAccent: PRIMARY_TINT_16,
|
|
105
|
+
sidebarBorder: "rgba(255, 255, 255, 0.08)",
|
|
106
|
+
},
|
|
85
107
|
paper: {
|
|
86
108
|
background: "#faf6ee",
|
|
87
109
|
card: "#fffdf9",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calliopelabs/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Calliope AI shared operator UI: app shell, navigation, page frame, data table and list patterns, status colours, design tokens and appearance for Astrolift AI and Zentinelle AI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"calliope-ai",
|