@godxjp/ui 18.10.0 → 18.11.1
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/dist/components/data-display/card.d.ts +27 -3
- package/dist/components/data-display/card.js +4 -1
- package/dist/components/data-display/data-table.d.ts +4 -1
- package/dist/components/data-display/data-table.js +33 -5
- package/dist/components/data-entry/command-palette.d.ts +42 -1
- package/dist/components/data-entry/command-palette.js +31 -2
- package/dist/components/data-entry/input-otp.d.ts +1 -1
- package/dist/components/data-entry/select.d.ts +7 -1
- package/dist/components/data-entry/select.js +46 -26
- package/dist/components/feedback/banner.js +5 -1
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +61 -41
- package/dist/components/layout/resizable.d.ts +1 -1
- package/dist/components/layout/resizable.js +26 -8
- package/dist/components/navigation/pagination.js +20 -1
- package/dist/components/navigation/steps.d.ts +2 -2
- package/dist/components/navigation/steps.js +4 -2
- package/dist/components/navigation/tabs.js +6 -1
- package/dist/components/ui/avatar.d.ts +1 -1
- package/dist/components/ui/avatar.js +2 -1
- package/dist/components/ui/input-otp.d.ts +15 -3
- package/dist/components/ui/input-otp.js +2 -1
- package/dist/email/tokens.generated.d.ts +2 -2
- package/dist/email/tokens.generated.js +2 -2
- package/dist/i18n/messages/en.json +3 -0
- package/dist/i18n/messages/ja.json +3 -0
- package/dist/i18n/messages/vi.json +3 -0
- package/dist/lib/field-a11y.d.ts +7 -0
- package/dist/lib/field-a11y.js +6 -0
- package/dist/lib/hooks.d.ts +15 -0
- package/dist/lib/hooks.js +37 -0
- package/dist/props/components/data-display.prop.d.ts +16 -0
- package/dist/props/components/data-entry.prop.d.ts +7 -0
- package/dist/props/components/layout.prop.d.ts +8 -0
- package/dist/props/components/navigation.prop.d.ts +9 -0
- package/dist/props/registry.d.ts +20 -1
- package/dist/props/registry.js +21 -1
- package/dist/styles/alert-layout.css +1 -1
- package/dist/styles/base.css +28 -0
- package/dist/styles/card-layout.css +49 -13
- package/dist/styles/control.css +17 -2
- package/dist/styles/data-display-layout.css +16 -0
- package/dist/styles/layout.css +16 -0
- package/dist/styles/navigation-layout.css +8 -3
- package/dist/styles/shell-layout.css +21 -1
- package/dist/styles/table-layout.css +53 -0
- package/dist/tokens/components/card.css +12 -0
- package/dist/tokens/components/control.css +16 -0
- package/dist/tokens/components/data-display.css +12 -0
- package/dist/tokens/components/email.css +8 -2
- package/dist/tokens/components/feedback.css +24 -0
- package/dist/tokens/components/navigation.css +9 -0
- package/dist/tokens/components/shell.css +27 -4
- package/dist/tokens/components/table.css +21 -0
- package/dist/tokens/semantic/layout.css +8 -0
- package/package.json +2 -2
|
@@ -3,9 +3,21 @@ import { type VariantProps } from "class-variance-authority";
|
|
|
3
3
|
import type { LucideIcon } from "lucide-react";
|
|
4
4
|
import type { HeadingLevelProp } from "../../props/vocabulary/index.js";
|
|
5
5
|
type CardSize = "md" | "compact";
|
|
6
|
-
/** Semantic
|
|
7
|
-
*
|
|
6
|
+
/** Semantic accent tone. `accentPlacement` decides WHERE it is drawn — a leading-edge
|
|
7
|
+
* stripe (default) or the full perimeter. */
|
|
8
8
|
type CardAccent = "primary" | "success" | "warning" | "info" | "attention" | "destructive";
|
|
9
|
+
/**
|
|
10
|
+
* Where the semantic `accent` tone is drawn.
|
|
11
|
+
*
|
|
12
|
+
* - `"edge"` (default) — the leading-edge stripe on `border-inline-start` only, at the
|
|
13
|
+
* `--card-accent-rail-width` measure (6px). The classic "this row needs a look" rail.
|
|
14
|
+
* - `"perimeter"` — a full attention border around the WHOLE card in the same semantic
|
|
15
|
+
* tone, measured by `--card-accent-perimeter-width` + `--card-accent-perimeter-ring-width`.
|
|
16
|
+
* This is what `variant="featured"` does, except the tone is yours instead of `--primary`,
|
|
17
|
+
* so a card can shout "action required" (`accent="attention"`) or "this failed"
|
|
18
|
+
* (`accent="destructive"`) without borrowing the brand colour.
|
|
19
|
+
*/
|
|
20
|
+
type CardAccentPlacement = "edge" | "perimeter";
|
|
9
21
|
/** Surface fill — plain card, muted band, borderless outline, or emphasized featured ring. */
|
|
10
22
|
type CardVariant = "default" | "muted" | "outline" | "featured";
|
|
11
23
|
/** Padding density — base 16px · tight 12px · cozy 20px. */
|
|
@@ -16,6 +28,11 @@ declare const cardVariants: (props?: ({
|
|
|
16
28
|
export type CardProps = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof cardVariants> & {
|
|
17
29
|
size?: CardSize;
|
|
18
30
|
accent?: CardAccent;
|
|
31
|
+
/**
|
|
32
|
+
* Where `accent` is drawn — `"edge"` (default, the leading-edge stripe) or `"perimeter"`
|
|
33
|
+
* (a full attention border in the accent tone). Inert without `accent`.
|
|
34
|
+
*/
|
|
35
|
+
accentPlacement?: CardAccentPlacement;
|
|
19
36
|
variant?: CardVariant;
|
|
20
37
|
density?: CardDensity;
|
|
21
38
|
};
|
|
@@ -24,6 +41,11 @@ export declare const Card: React.ForwardRefExoticComponent<React.HTMLAttributes<
|
|
|
24
41
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string> & {
|
|
25
42
|
size?: CardSize;
|
|
26
43
|
accent?: CardAccent;
|
|
44
|
+
/**
|
|
45
|
+
* Where `accent` is drawn — `"edge"` (default, the leading-edge stripe) or `"perimeter"`
|
|
46
|
+
* (a full attention border in the accent tone). Inert without `accent`.
|
|
47
|
+
*/
|
|
48
|
+
accentPlacement?: CardAccentPlacement;
|
|
27
49
|
variant?: CardVariant;
|
|
28
50
|
density?: CardDensity;
|
|
29
51
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -136,9 +158,11 @@ export type StatCardProps = React.HTMLAttributes<HTMLDivElement> & VariantProps<
|
|
|
136
158
|
inverse?: boolean;
|
|
137
159
|
/** Semantic leading-edge rail (Card accent) — flags a KPI needing attention. */
|
|
138
160
|
accent?: CardAccent;
|
|
161
|
+
/** Where `accent` is drawn — `"edge"` (default rail) or `"perimeter"` (full attention border). */
|
|
162
|
+
accentPlacement?: CardAccentPlacement;
|
|
139
163
|
};
|
|
140
164
|
/** KPI / stat tile — token-driven layout aligned to dashboard KPI cards. */
|
|
141
|
-
export declare function StatCard({ label, value, hint, icon: Icon, delta, layout, align, inverse, accent, className, size, ...props }: StatCardProps): React.JSX.Element;
|
|
165
|
+
export declare function StatCard({ label, value, hint, icon: Icon, delta, layout, align, inverse, accent, accentPlacement, className, size, ...props }: StatCardProps): React.JSX.Element;
|
|
142
166
|
/** Header actions slot — pair with `CardHeader className="flex flex-row …"`. */
|
|
143
167
|
export declare const CardAction: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
144
168
|
export {};
|
|
@@ -12,7 +12,7 @@ const cardVariants = cva("group/card", {
|
|
|
12
12
|
defaultVariants: { size: "md" }
|
|
13
13
|
});
|
|
14
14
|
const Card = React.forwardRef(
|
|
15
|
-
({ className, size = "md", accent, variant, density, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
15
|
+
({ className, size = "md", accent, accentPlacement, variant, density, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
16
16
|
"div",
|
|
17
17
|
{
|
|
18
18
|
ref,
|
|
@@ -20,6 +20,7 @@ const Card = React.forwardRef(
|
|
|
20
20
|
"data-slot": "card",
|
|
21
21
|
"data-size": size === "compact" ? "compact" : void 0,
|
|
22
22
|
"data-accent": accent,
|
|
23
|
+
"data-accent-placement": accentPlacement === "perimeter" ? "perimeter" : void 0,
|
|
23
24
|
"data-variant": variant && variant !== "default" ? variant : void 0,
|
|
24
25
|
"data-density": density,
|
|
25
26
|
...props
|
|
@@ -108,6 +109,7 @@ function StatCard({
|
|
|
108
109
|
align = "start",
|
|
109
110
|
inverse = false,
|
|
110
111
|
accent,
|
|
112
|
+
accentPlacement,
|
|
111
113
|
className,
|
|
112
114
|
size = "compact",
|
|
113
115
|
...props
|
|
@@ -118,6 +120,7 @@ function StatCard({
|
|
|
118
120
|
{
|
|
119
121
|
size: size ?? "compact",
|
|
120
122
|
accent,
|
|
123
|
+
accentPlacement,
|
|
121
124
|
className: cn("ui-stat-card", className),
|
|
122
125
|
"data-stat-card": "",
|
|
123
126
|
"data-stat-layout": layout,
|
|
@@ -41,7 +41,10 @@ interface DataTableProps<T> {
|
|
|
41
41
|
onColumnVisibilityChange?: OnChangeFn<VisibilityState>;
|
|
42
42
|
/**
|
|
43
43
|
* Manual (server) flags. Default `false` so the simple `data`+`columns` case
|
|
44
|
-
* sorts / filters
|
|
44
|
+
* sorts / filters in-browser with no extra wiring. Client pagination slices
|
|
45
|
+
* rows ONLY when engaged — a numbered `<DataTable.Pagination>` child is
|
|
46
|
+
* composed, or `pagination`/`onPaginationChange` is supplied; a plain table
|
|
47
|
+
* with neither renders every row (no silent 10-row cap, gh#270). Set the
|
|
45
48
|
* relevant flag `true` and drive the matching state from your query for
|
|
46
49
|
* server-side sort / filter / pagination.
|
|
47
50
|
*/
|
|
@@ -178,6 +178,10 @@ function DataTable({
|
|
|
178
178
|
}
|
|
179
179
|
};
|
|
180
180
|
const tanstackColumns = React.useMemo(() => toTanstackColumns(columns), [columns]);
|
|
181
|
+
const hasNumberedPager = React.Children.toArray(children).some(
|
|
182
|
+
(c) => React.isValidElement(c) && c.type.displayName === "DataTable.Pagination" && typeof c.props.onChange !== "function"
|
|
183
|
+
);
|
|
184
|
+
const paginationEngaged = manualPagination || controlledPagination !== void 0 || onPaginationChange !== void 0 || hasNumberedPager;
|
|
181
185
|
const table = useReactTable({
|
|
182
186
|
data,
|
|
183
187
|
columns: tanstackColumns,
|
|
@@ -185,7 +189,7 @@ function DataTable({
|
|
|
185
189
|
getCoreRowModel: getCoreRowModel(),
|
|
186
190
|
...manualSorting ? {} : { getSortedRowModel: getSortedRowModel() },
|
|
187
191
|
...manualFiltering ? {} : { getFilteredRowModel: getFilteredRowModel() },
|
|
188
|
-
...manualPagination ? {} : { getPaginationRowModel: getPaginationRowModel() },
|
|
192
|
+
...manualPagination || !paginationEngaged ? {} : { getPaginationRowModel: getPaginationRowModel() },
|
|
189
193
|
manualSorting,
|
|
190
194
|
manualFiltering,
|
|
191
195
|
manualPagination,
|
|
@@ -419,6 +423,26 @@ DataTable.Content = function DataTableContent() {
|
|
|
419
423
|
);
|
|
420
424
|
}
|
|
421
425
|
}, [missingHeaderNames]);
|
|
426
|
+
const scrollRef = React.useRef(null);
|
|
427
|
+
const [hasOverflowEnd, setHasOverflowEnd] = React.useState(false);
|
|
428
|
+
React.useEffect(() => {
|
|
429
|
+
const el = scrollRef.current;
|
|
430
|
+
if (!el) return;
|
|
431
|
+
const update = () => {
|
|
432
|
+
const overflowing = el.scrollWidth - el.clientWidth > 1;
|
|
433
|
+
const atEnd = Math.abs(el.scrollLeft) + el.clientWidth >= el.scrollWidth - 1;
|
|
434
|
+
setHasOverflowEnd(overflowing && !atEnd);
|
|
435
|
+
};
|
|
436
|
+
update();
|
|
437
|
+
el.addEventListener("scroll", update, { passive: true });
|
|
438
|
+
const observer = new ResizeObserver(update);
|
|
439
|
+
observer.observe(el);
|
|
440
|
+
if (el.firstElementChild) observer.observe(el.firstElementChild);
|
|
441
|
+
return () => {
|
|
442
|
+
el.removeEventListener("scroll", update);
|
|
443
|
+
observer.disconnect();
|
|
444
|
+
};
|
|
445
|
+
}, []);
|
|
422
446
|
const activeSort = sort ?? sortingStateToSort(table.getState().sorting);
|
|
423
447
|
const isControlledSort = sort !== void 0 || !!onSortChange;
|
|
424
448
|
const onHeaderClick = (col) => {
|
|
@@ -440,7 +464,12 @@ DataTable.Content = function DataTableContent() {
|
|
|
440
464
|
return /* @__PURE__ */ jsx(
|
|
441
465
|
"div",
|
|
442
466
|
{
|
|
443
|
-
|
|
467
|
+
ref: scrollRef,
|
|
468
|
+
className: cn(
|
|
469
|
+
"ui-data-table-scroll",
|
|
470
|
+
hasPinEnd && "ui-data-table-has-pin-end",
|
|
471
|
+
hasOverflowEnd && "ui-data-table-has-overflow-end"
|
|
472
|
+
),
|
|
444
473
|
"aria-busy": loading,
|
|
445
474
|
tabIndex: presetAttr ? void 0 : 0,
|
|
446
475
|
children: /* @__PURE__ */ jsx(
|
|
@@ -636,9 +665,8 @@ DataTable.Content = function DataTableContent() {
|
|
|
636
665
|
),
|
|
637
666
|
children: col.render ? col.render(original) : (() => {
|
|
638
667
|
const v = original[col.key];
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
return "\u2014";
|
|
668
|
+
const text = v == null || !(typeof v === "string" || typeof v === "number") ? "\u2014" : String(v);
|
|
669
|
+
return /* @__PURE__ */ jsx("span", { "data-slot": "table-cell-text", children: text });
|
|
642
670
|
})()
|
|
643
671
|
},
|
|
644
672
|
col.key
|
|
@@ -29,9 +29,50 @@ export type CommandPaletteProps = {
|
|
|
29
29
|
open?: boolean;
|
|
30
30
|
defaultOpen?: boolean;
|
|
31
31
|
onOpenChange?: (open: boolean) => void;
|
|
32
|
+
/**
|
|
33
|
+
* Controlled search-box query. Pairs with `onSearchChange`; leave it out for the uncontrolled
|
|
34
|
+
* palette (seeded by `defaultSearch`). Same idiom as `SearchSelect`'s `search`.
|
|
35
|
+
*/
|
|
36
|
+
search?: string;
|
|
37
|
+
/** Initial uncontrolled query, and the value the palette resets to when it closes. Default `""`. */
|
|
38
|
+
defaultSearch?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Fires on every keystroke with the current query — the seam a server-backed group needs to run
|
|
41
|
+
* search-as-you-type. Also fires with `defaultSearch` when the palette closes.
|
|
42
|
+
*/
|
|
43
|
+
onSearchChange?: (query: string) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the palette filters `groups` itself (cmdk's client-side fuzzy match). Set `false` when
|
|
46
|
+
* the QUERY is answered by a server and `groups` already holds the matches — otherwise every row
|
|
47
|
+
* is scored a second time against the same string and late-arriving matches are dropped.
|
|
48
|
+
*
|
|
49
|
+
* It also decides who owns the empty node; see the component doc block. Default `true`.
|
|
50
|
+
*/
|
|
51
|
+
shouldFilter?: boolean;
|
|
32
52
|
loading?: boolean;
|
|
33
53
|
error?: React.ReactNode;
|
|
34
54
|
trigger?: React.ReactNode;
|
|
35
55
|
shortcut?: boolean;
|
|
36
56
|
};
|
|
37
|
-
|
|
57
|
+
/**
|
|
58
|
+
* CommandPalette — the ⌘K search dialog.
|
|
59
|
+
*
|
|
60
|
+
* ## The empty-state contract (gh#412)
|
|
61
|
+
*
|
|
62
|
+
* `labels.empty` renders under exactly one rule, and which mechanism decides follows `shouldFilter`:
|
|
63
|
+
*
|
|
64
|
+
* - **`shouldFilter` (default, client-side)** — cmdk owns it: the palette holds items, the query
|
|
65
|
+
* matches none of them. Unchanged.
|
|
66
|
+
* - **`shouldFilter={false}` (server-side)** — the PALETTE owns it, derived synchronously from
|
|
67
|
+
* props: the empty node renders when `groups` carries no items, and never while `loading` or
|
|
68
|
+
* `error` is set. cmdk's own empty node cannot be trusted here, because it is driven by a
|
|
69
|
+
* scheduled count of the items that have REGISTERED in the DOM, not by what the consumer knows —
|
|
70
|
+
* so an async group that populates a frame late flips it on and off and any assertion over it
|
|
71
|
+
* races. Reading `groups` instead makes "did we render the empty state?" a pure function of the
|
|
72
|
+
* props at that render, which is what a contract test can assert.
|
|
73
|
+
*
|
|
74
|
+
* The practical consequence for search-as-you-type: hold `loading` true for the whole in-flight
|
|
75
|
+
* window. While it is true the palette shows `labels.loading` and never claims "no results" —
|
|
76
|
+
* a request that has not answered yet is not an empty result.
|
|
77
|
+
*/
|
|
78
|
+
export declare function CommandPalette({ groups, labels, onSelect, open: controlledOpen, defaultOpen, onOpenChange, search: controlledSearch, defaultSearch, onSearchChange, shouldFilter, loading, error, trigger, shortcut, }: CommandPaletteProps): React.JSX.Element;
|
|
@@ -19,14 +19,20 @@ function CommandPalette({
|
|
|
19
19
|
open: controlledOpen,
|
|
20
20
|
defaultOpen = false,
|
|
21
21
|
onOpenChange,
|
|
22
|
+
search: controlledSearch,
|
|
23
|
+
defaultSearch = "",
|
|
24
|
+
onSearchChange,
|
|
25
|
+
shouldFilter = true,
|
|
22
26
|
loading = false,
|
|
23
27
|
error,
|
|
24
28
|
trigger,
|
|
25
29
|
shortcut = true
|
|
26
30
|
}) {
|
|
27
31
|
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
|
|
32
|
+
const [uncontrolledSearch, setUncontrolledSearch] = React.useState(defaultSearch);
|
|
28
33
|
const shortcutRestoreFocusRef = React.useRef(null);
|
|
29
34
|
const open = controlledOpen ?? uncontrolledOpen;
|
|
35
|
+
const search = controlledSearch ?? uncontrolledSearch;
|
|
30
36
|
const setOpen = React.useCallback(
|
|
31
37
|
(next) => {
|
|
32
38
|
if (controlledOpen === void 0) {
|
|
@@ -36,6 +42,22 @@ function CommandPalette({
|
|
|
36
42
|
},
|
|
37
43
|
[controlledOpen, onOpenChange]
|
|
38
44
|
);
|
|
45
|
+
const setSearch = React.useCallback(
|
|
46
|
+
(next) => {
|
|
47
|
+
if (controlledSearch === void 0) {
|
|
48
|
+
setUncontrolledSearch(next);
|
|
49
|
+
}
|
|
50
|
+
onSearchChange?.(next);
|
|
51
|
+
},
|
|
52
|
+
[controlledSearch, onSearchChange]
|
|
53
|
+
);
|
|
54
|
+
const previousOpenRef = React.useRef(open);
|
|
55
|
+
React.useEffect(() => {
|
|
56
|
+
const wasOpen = previousOpenRef.current;
|
|
57
|
+
previousOpenRef.current = open;
|
|
58
|
+
if (!wasOpen || open || search === defaultSearch) return;
|
|
59
|
+
setSearch(defaultSearch);
|
|
60
|
+
}, [open, search, defaultSearch, setSearch]);
|
|
39
61
|
React.useEffect(() => {
|
|
40
62
|
if (!shortcut) return;
|
|
41
63
|
const handleKeyDown = (event) => {
|
|
@@ -50,6 +72,7 @@ function CommandPalette({
|
|
|
50
72
|
document.addEventListener("keydown", handleKeyDown);
|
|
51
73
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
52
74
|
}, [open, setOpen, shortcut]);
|
|
75
|
+
const hasItems = React.useMemo(() => groups.some((group) => group.items.length > 0), [groups]);
|
|
53
76
|
const triggerNode = trigger ?? /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "ui-command-palette-trigger", children: [
|
|
54
77
|
/* @__PURE__ */ jsx(Search, { "aria-hidden": "true" }),
|
|
55
78
|
/* @__PURE__ */ jsx("span", { children: labels.open }),
|
|
@@ -79,17 +102,23 @@ function CommandPalette({
|
|
|
79
102
|
children: [
|
|
80
103
|
/* @__PURE__ */ jsx(Dialog.Title, { className: "sr-only", children: labels.title }),
|
|
81
104
|
/* @__PURE__ */ jsx(Dialog.Description, { id: "ui-command-palette-description", className: "sr-only", children: labels.description }),
|
|
82
|
-
/* @__PURE__ */ jsxs(Command, { label: labels.title, children: [
|
|
105
|
+
/* @__PURE__ */ jsxs(Command, { label: labels.title, shouldFilter, children: [
|
|
83
106
|
/* @__PURE__ */ jsx(
|
|
84
107
|
CommandInput,
|
|
85
108
|
{
|
|
86
109
|
autoFocus: true,
|
|
110
|
+
value: search,
|
|
111
|
+
onValueChange: setSearch,
|
|
87
112
|
placeholder: labels.placeholder,
|
|
88
113
|
"aria-label": labels.placeholder
|
|
89
114
|
}
|
|
90
115
|
),
|
|
91
116
|
/* @__PURE__ */ jsx(CommandList, { "aria-busy": loading, children: loading ? /* @__PURE__ */ jsx("div", { className: "ui-command-palette-state", role: "status", children: labels.loading }) : error ? /* @__PURE__ */ jsx("div", { className: "ui-command-palette-state", role: "alert", children: error }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
92
|
-
/* @__PURE__ */ jsx(CommandEmpty, { children: labels.empty })
|
|
117
|
+
shouldFilter ? /* @__PURE__ */ jsx(CommandEmpty, { children: labels.empty }) : hasItems ? null : (
|
|
118
|
+
// Same node cmdk would have rendered (class + role), decided from `groups`
|
|
119
|
+
// instead of from its scheduled DOM-registration count — see the doc block.
|
|
120
|
+
/* @__PURE__ */ jsx("div", { className: "ui-command-empty", role: "presentation", children: labels.empty })
|
|
121
|
+
),
|
|
93
122
|
groups.map((group) => /* @__PURE__ */ jsx(CommandGroup, { heading: group.label, children: group.items.map((item) => /* @__PURE__ */ jsxs(
|
|
94
123
|
CommandItem,
|
|
95
124
|
{
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator } from "../ui/input-otp.js";
|
|
2
|
-
export type { InputOTPGroupAppearanceProp, InputOTPGroupProp, InputOTPGroupProps, } from "../ui/input-otp.js";
|
|
2
|
+
export type { InputOTPAlignProp, InputOTPGroupAppearanceProp, InputOTPGroupProp, InputOTPGroupProps, } from "../ui/input-otp.js";
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
3
|
+
import type { FieldA11yProps } from "../../lib/field-a11y.js";
|
|
3
4
|
import type { SelectDataProp } from "../../props/components/data-entry.prop.js";
|
|
4
|
-
|
|
5
|
+
/** Compound-API props: the Radix root's own props PLUS the FormField field-a11y contract, which
|
|
6
|
+
* this component re-routes to the trigger (see {@link SelectFieldA11yContext}). */
|
|
7
|
+
export type SelectCompoundProp = React.ComponentProps<typeof SelectPrimitive.Root> & FieldA11yProps & {
|
|
8
|
+
id?: string;
|
|
9
|
+
};
|
|
10
|
+
export type SelectProp = SelectDataProp | SelectCompoundProp;
|
|
5
11
|
/**
|
|
6
12
|
* Select — one component for every single-select. Use the compound API for full control
|
|
7
13
|
* (`<Select><SelectTrigger/><SelectContent><SelectItem/></SelectContent></Select>`), OR the
|
|
@@ -5,15 +5,22 @@ import * as SelectPrimitive from "@radix-ui/react-select";
|
|
|
5
5
|
import { ChevronDown, ChevronUp } from "lucide-react";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
7
|
import { controlTriggerClass } from "../../lib/control-styles.js";
|
|
8
|
+
import { mergeAriaIds, omitFieldA11y, pickFieldA11y } from "../../lib/field-a11y.js";
|
|
8
9
|
import { SearchSelect } from "./search-select.js";
|
|
9
10
|
function isDataSelect(props) {
|
|
10
11
|
return "options" in props || "loadOptions" in props;
|
|
11
12
|
}
|
|
13
|
+
const SelectFieldA11yContext = React.createContext(null);
|
|
12
14
|
function Select(props) {
|
|
13
15
|
if (isDataSelect(props)) {
|
|
14
16
|
return /* @__PURE__ */ jsx(DataSelect, { ...props });
|
|
15
17
|
}
|
|
16
|
-
return /* @__PURE__ */ jsx(
|
|
18
|
+
return /* @__PURE__ */ jsx(CompoundSelect, { ...props });
|
|
19
|
+
}
|
|
20
|
+
function CompoundSelect({ id, ...props }) {
|
|
21
|
+
const fieldA11y = pickFieldA11y(props);
|
|
22
|
+
const rootProps = omitFieldA11y(props);
|
|
23
|
+
return /* @__PURE__ */ jsx(SelectFieldA11yContext.Provider, { value: { ...fieldA11y, id }, children: /* @__PURE__ */ jsx(SelectPrimitive.Root, { "data-slot": "select", ...rootProps }) });
|
|
17
24
|
}
|
|
18
25
|
function SelectGroup(props) {
|
|
19
26
|
return /* @__PURE__ */ jsx(SelectPrimitive.Group, { "data-slot": "select-group", ...props });
|
|
@@ -21,31 +28,44 @@ function SelectGroup(props) {
|
|
|
21
28
|
function SelectValue(props) {
|
|
22
29
|
return /* @__PURE__ */ jsx(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
|
|
23
30
|
}
|
|
24
|
-
const SelectTrigger = React.forwardRef(({ className, children, size = "md", showIndicator = true, ...props }, ref) =>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
31
|
+
const SelectTrigger = React.forwardRef(({ className, children, size = "md", showIndicator = true, ...props }, ref) => {
|
|
32
|
+
const field = React.useContext(SelectFieldA11yContext);
|
|
33
|
+
const ownsName = props["aria-label"] !== void 0 || props["aria-labelledby"] !== void 0;
|
|
34
|
+
const fieldA11y = field ? {
|
|
35
|
+
id: props.id ?? field.id,
|
|
36
|
+
...ownsName ? {} : { "aria-label": field["aria-label"], "aria-labelledby": field["aria-labelledby"] },
|
|
37
|
+
"aria-describedby": mergeAriaIds(props["aria-describedby"], field["aria-describedby"]),
|
|
38
|
+
"aria-errormessage": mergeAriaIds(props["aria-errormessage"], field["aria-errormessage"]),
|
|
39
|
+
"aria-required": props["aria-required"] ?? field["aria-required"],
|
|
40
|
+
"aria-invalid": props["aria-invalid"] ?? field["aria-invalid"]
|
|
41
|
+
} : void 0;
|
|
42
|
+
return /* @__PURE__ */ jsxs(
|
|
43
|
+
SelectPrimitive.Trigger,
|
|
44
|
+
{
|
|
45
|
+
ref,
|
|
46
|
+
"data-slot": "select-trigger",
|
|
47
|
+
"data-size": size,
|
|
48
|
+
className: cn(
|
|
49
|
+
controlTriggerClass,
|
|
50
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground w-full gap-2 whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2",
|
|
51
|
+
className
|
|
52
|
+
),
|
|
53
|
+
...props,
|
|
54
|
+
...fieldA11y,
|
|
55
|
+
children: [
|
|
56
|
+
children,
|
|
57
|
+
showIndicator ? /* @__PURE__ */ jsx(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
58
|
+
ChevronDown,
|
|
59
|
+
{
|
|
60
|
+
"data-slot": "select-chevron",
|
|
61
|
+
className: "size-4 shrink-0 opacity-50",
|
|
62
|
+
"aria-hidden": "true"
|
|
63
|
+
}
|
|
64
|
+
) }) : null
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
});
|
|
49
69
|
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
|
50
70
|
const SelectScrollUpButton = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
51
71
|
SelectPrimitive.ScrollUpButton,
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { AlertActions, AlertBase, AlertContent, AlertDescription, AlertTitle } from "./alert.js";
|
|
5
|
-
const BannerBase = React.forwardRef((props, ref) =>
|
|
5
|
+
const BannerBase = React.forwardRef((props, ref) => (
|
|
6
|
+
// `variant` sits AFTER the spread: the type already excludes it, and the runtime half of the
|
|
7
|
+
// same guarantee means a spread of leftover Alert props can never silently downgrade the strip.
|
|
8
|
+
/* @__PURE__ */ jsx(AlertBase, { ref, ...props, variant: "banner" })
|
|
9
|
+
));
|
|
6
10
|
BannerBase.displayName = "Banner";
|
|
7
11
|
const Banner = Object.assign(BannerBase, {
|
|
8
12
|
Title: AlertTitle,
|
|
@@ -2,7 +2,7 @@ import type { PageContainerProp, PageInsetProp } from "../../props/components/la
|
|
|
2
2
|
export type { PageContainerProp, PageContainerProp as PageContainerProps, } from "../../props/components/layout.prop.js";
|
|
3
3
|
export type { BreadcrumbItemProp, BreadcrumbItemProp as BreadcrumbItem, } from "../../props/vocabulary/navigation.prop.js";
|
|
4
4
|
export declare function PageContainerInset({ className, children, ...props }: PageInsetProp): import("react").JSX.Element;
|
|
5
|
-
declare function PageContainerRoot({ title, subtitle, status, extra, footer, breadcrumb, breadcrumbLabel, breadcrumbAriaLabel, linkComponent: LinkComponent, density, variant, preset, headerLayout, measure, stickyFooter, footerReveal, fill, children, className, }: PageContainerProp): import("react").JSX.Element;
|
|
5
|
+
declare function PageContainerRoot({ title, subtitle, status, extra, footer, breadcrumb, breadcrumbLabel, breadcrumbAriaLabel, linkComponent: LinkComponent, headerLoading, density, variant, preset, headerLayout, measure, stickyFooter, footerReveal, fill, children, className, }: PageContainerProp): import("react").JSX.Element;
|
|
6
6
|
export declare const PageContainer: typeof PageContainerRoot & {
|
|
7
7
|
Inset: typeof PageContainerInset;
|
|
8
8
|
};
|
|
@@ -43,6 +43,7 @@ function PageContainerRoot({
|
|
|
43
43
|
breadcrumbLabel,
|
|
44
44
|
breadcrumbAriaLabel,
|
|
45
45
|
linkComponent: LinkComponent = "a",
|
|
46
|
+
headerLoading = false,
|
|
46
47
|
density,
|
|
47
48
|
variant = "default",
|
|
48
49
|
preset = "default",
|
|
@@ -75,47 +76,66 @@ function PageContainerRoot({
|
|
|
75
76
|
className
|
|
76
77
|
),
|
|
77
78
|
children: [
|
|
78
|
-
/* @__PURE__ */ jsxs(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
79
|
+
/* @__PURE__ */ jsxs(
|
|
80
|
+
"header",
|
|
81
|
+
{
|
|
82
|
+
ref: headerRef,
|
|
83
|
+
className: "ui-page-header",
|
|
84
|
+
"data-layout": headerLayout,
|
|
85
|
+
"aria-busy": headerLoading ? "true" : void 0,
|
|
86
|
+
children: [
|
|
87
|
+
breadcrumb && breadcrumb.length > 0 && /* @__PURE__ */ jsx(
|
|
88
|
+
"nav",
|
|
89
|
+
{
|
|
90
|
+
"aria-label": breadcrumbLabel ?? breadcrumbAriaLabel ?? t("navigation.breadcrumb.ariaLabel"),
|
|
91
|
+
className: "ui-breadcrumb",
|
|
92
|
+
children: /* @__PURE__ */ jsx("ol", { className: "ui-breadcrumb-list", children: breadcrumb.map((item, i) => {
|
|
93
|
+
const isLast = i === breadcrumb.length - 1;
|
|
94
|
+
return /* @__PURE__ */ jsxs("li", { className: "ui-inline-xs", children: [
|
|
95
|
+
item.to && !isLast ? /* @__PURE__ */ jsx(
|
|
96
|
+
LinkComponent,
|
|
97
|
+
{
|
|
98
|
+
href: item.to,
|
|
99
|
+
to: item.to,
|
|
100
|
+
className: "hover:text-foreground hover:underline",
|
|
101
|
+
children: item.label
|
|
102
|
+
}
|
|
103
|
+
) : /* @__PURE__ */ jsx(
|
|
104
|
+
"span",
|
|
105
|
+
{
|
|
106
|
+
className: isLast ? "text-foreground" : "",
|
|
107
|
+
"aria-current": isLast ? "page" : void 0,
|
|
108
|
+
children: item.label
|
|
109
|
+
}
|
|
110
|
+
),
|
|
111
|
+
!isLast && /* @__PURE__ */ jsx(ChevronRight, { className: "size-3", "aria-hidden": "true" })
|
|
112
|
+
] }, i);
|
|
113
|
+
}) })
|
|
114
|
+
}
|
|
115
|
+
),
|
|
116
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-page-header-row", children: [
|
|
117
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-page-header-heading min-w-0", children: [
|
|
118
|
+
headerLoading ? /* @__PURE__ */ jsx("h1", { className: "ui-page-title ui-skeleton-block ui-page-title-placeholder", children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("layout.pageHeader.loading") }) }) : status != null ? /* @__PURE__ */ jsxs("div", { className: "ui-page-header-title-row", children: [
|
|
119
|
+
/* @__PURE__ */ jsx("h1", { className: "ui-page-title", children: title }),
|
|
120
|
+
/* @__PURE__ */ jsx("div", { className: "ui-page-header-status", children: status })
|
|
121
|
+
] }) : /* @__PURE__ */ jsx("h1", { className: "ui-page-title", children: title }),
|
|
122
|
+
headerLoading ? (
|
|
123
|
+
// Decorative only — the pending state is already announced once by the heading above,
|
|
124
|
+
// so a second live placeholder here would double-announce it.
|
|
125
|
+
/* @__PURE__ */ jsx(
|
|
126
|
+
"p",
|
|
127
|
+
{
|
|
128
|
+
className: "ui-page-subtitle ui-skeleton-block ui-page-subtitle-placeholder",
|
|
129
|
+
"aria-hidden": "true"
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
) : subtitle && /* @__PURE__ */ jsx("p", { className: "ui-page-subtitle", children: subtitle })
|
|
133
|
+
] }),
|
|
134
|
+
extra && /* @__PURE__ */ jsx("div", { className: "ui-page-header-extra", children: extra })
|
|
135
|
+
] })
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
),
|
|
119
139
|
children != null && /* @__PURE__ */ jsx("div", { className: "ui-page-body", children }),
|
|
120
140
|
footer && /* @__PURE__ */ jsx("footer", { className: "ui-page-footer", children: footer })
|
|
121
141
|
]
|
|
@@ -5,7 +5,7 @@ export declare const ResizablePanelGroup: {
|
|
|
5
5
|
displayName: string;
|
|
6
6
|
};
|
|
7
7
|
export declare const ResizablePanel: {
|
|
8
|
-
({ className, ...props }: React.ComponentPropsWithoutRef<typeof ResizablePrimitive.Panel>): React.JSX.Element;
|
|
8
|
+
({ className, elementRef, ...props }: React.ComponentPropsWithoutRef<typeof ResizablePrimitive.Panel>): React.JSX.Element;
|
|
9
9
|
displayName: string;
|
|
10
10
|
};
|
|
11
11
|
export declare const ResizableHandle: {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
3
4
|
import * as ResizablePrimitive from "react-resizable-panels";
|
|
4
5
|
import { cn } from "../../lib/utils.js";
|
|
6
|
+
import { useScrollableRegionTabIndex } from "../../lib/hooks.js";
|
|
5
7
|
const ResizablePanelGroup = ({
|
|
6
8
|
className,
|
|
7
9
|
...props
|
|
@@ -16,15 +18,31 @@ const ResizablePanelGroup = ({
|
|
|
16
18
|
ResizablePanelGroup.displayName = "ResizablePanelGroup";
|
|
17
19
|
const ResizablePanel = ({
|
|
18
20
|
className,
|
|
21
|
+
elementRef,
|
|
19
22
|
...props
|
|
20
|
-
}) =>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
);
|
|
23
|
+
}) => {
|
|
24
|
+
const [scrollRegion, setScrollRegion] = React.useState(null);
|
|
25
|
+
const setRootElement = React.useCallback(
|
|
26
|
+
(node) => {
|
|
27
|
+
setScrollRegion(
|
|
28
|
+
node?.firstElementChild instanceof HTMLElement ? node.firstElementChild : null
|
|
29
|
+
);
|
|
30
|
+
if (typeof elementRef === "function") elementRef(node);
|
|
31
|
+
else if (elementRef) elementRef.current = node;
|
|
32
|
+
},
|
|
33
|
+
[elementRef]
|
|
34
|
+
);
|
|
35
|
+
useScrollableRegionTabIndex(scrollRegion);
|
|
36
|
+
return /* @__PURE__ */ jsx(
|
|
37
|
+
ResizablePrimitive.Panel,
|
|
38
|
+
{
|
|
39
|
+
"data-slot": "resizable-panel",
|
|
40
|
+
className: cn("ui-resizable-panel", className),
|
|
41
|
+
elementRef: setRootElement,
|
|
42
|
+
...props
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
};
|
|
28
46
|
ResizablePanel.displayName = "ResizablePanel";
|
|
29
47
|
const ResizableHandle = ({
|
|
30
48
|
className,
|