atlas-react-kit 1.2.0 → 1.2.2
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/COMPONENTS.md +40 -40
- package/DESIGN_RULES.md +14 -40
- package/README.md +7 -6
- package/dist/alert.js +7 -6
- package/dist/calendar.js +5 -4
- package/dist/card.js +2 -1
- package/dist/combobox.d.ts +4 -3
- package/dist/combobox.js +8 -17
- package/dist/date-time-picker.js +2 -1
- package/dist/dialog.js +2 -1
- package/dist/dropdown-menu.js +7 -5
- package/dist/field.d.ts +6 -3
- package/dist/field.js +3 -2
- package/dist/form/checkbox-field.d.ts +3 -5
- package/dist/form/checkbox-field.js +13 -19
- package/dist/form/combobox-field.d.ts +2 -2
- package/dist/form/combobox-field.js +5 -6
- package/dist/form/control-field.d.ts +5 -5
- package/dist/form/control-field.js +6 -9
- package/dist/form/date-picker-field.d.ts +1 -1
- package/dist/form/date-picker-field.js +3 -7
- package/dist/form/date-time-picker-field.d.ts +1 -1
- package/dist/form/date-time-picker-field.js +3 -7
- package/dist/form/field-error.d.ts +13 -4
- package/dist/form/field-shell.d.ts +63 -47
- package/dist/form/field-shell.js +67 -35
- package/dist/form/focus-first-invalid.d.ts +7 -4
- package/dist/form/focus-first-invalid.js +10 -6
- package/dist/form/markdown-field.d.ts +1 -1
- package/dist/form/markdown-field.js +8 -4
- package/dist/form/select-field.d.ts +2 -8
- package/dist/form/select-field.js +5 -7
- package/dist/form/text-field.d.ts +2 -8
- package/dist/form/text-field.js +9 -6
- package/dist/form/toggle-group-field.d.ts +2 -2
- package/dist/form/toggle-group-field.js +5 -6
- package/dist/info-tip.d.ts +7 -4
- package/dist/info-tip.js +12 -4
- package/dist/internal/active-link.d.ts +7 -0
- package/dist/internal/active-link.js +9 -0
- package/dist/internal/calendar-size.d.ts +5 -0
- package/dist/internal/calendar-size.js +5 -0
- package/dist/internal/form-control.d.ts +7 -1
- package/dist/internal/form-control.js +8 -1
- package/dist/internal/kanban-layout.js +2 -1
- package/dist/internal/listbox-navigation.d.ts +8 -8
- package/dist/internal/listbox-navigation.js +23 -23
- package/dist/internal/listbox-option.d.ts +16 -3
- package/dist/internal/listbox-option.js +22 -5
- package/dist/internal/overlay-styles.d.ts +15 -5
- package/dist/internal/overlay-styles.js +15 -5
- package/dist/internal/search.d.ts +4 -0
- package/dist/internal/search.js +6 -0
- package/dist/internal/styles.d.ts +21 -0
- package/dist/internal/styles.js +21 -0
- package/dist/kanban.js +3 -2
- package/dist/label.js +2 -1
- package/dist/lazy-calendar.js +3 -2
- package/dist/nav-link.js +2 -1
- package/dist/picker-shell.d.ts +3 -1
- package/dist/picker-shell.js +3 -3
- package/dist/popover.js +6 -5
- package/dist/search-picker.js +8 -16
- package/dist/select.js +5 -5
- package/dist/skip-link.js +2 -1
- package/dist/tabs.js +3 -2
- package/dist/toast.js +11 -10
- package/dist/toggle-group.d.ts +1 -1
- package/dist/toggle-group.js +36 -2
- package/dist/tooltip.js +2 -2
- package/package.json +1 -1
- package/skills/atlas-react-kit/SKILL.md +46 -42
|
@@ -1,23 +1,21 @@
|
|
|
1
|
+
import { clamp } from "./math.js";
|
|
1
2
|
/** Page keys move ten rows, a little more than one max-h-60 list viewport. */
|
|
2
3
|
const PAGE_SIZE = 10;
|
|
4
|
+
/** Rows each key moves the highlight; Home and End jump to the ends. */
|
|
5
|
+
const keySteps = new Map([
|
|
6
|
+
["ArrowDown", 1],
|
|
7
|
+
["ArrowUp", -1],
|
|
8
|
+
["PageDown", PAGE_SIZE],
|
|
9
|
+
["PageUp", -PAGE_SIZE],
|
|
10
|
+
["Home", -Infinity],
|
|
11
|
+
["End", Infinity],
|
|
12
|
+
]);
|
|
3
13
|
/** Return the next highlighted index for a listbox navigation key. */
|
|
4
14
|
function moveListboxHighlight(key, index, length) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return Math.max(index - 1, 0);
|
|
10
|
-
case "Home":
|
|
11
|
-
return 0;
|
|
12
|
-
case "End":
|
|
13
|
-
return length === 0 ? 0 : length - 1;
|
|
14
|
-
case "PageDown":
|
|
15
|
-
return length === 0 ? 0 : Math.min(index + PAGE_SIZE, length - 1);
|
|
16
|
-
case "PageUp":
|
|
17
|
-
return Math.max(index - PAGE_SIZE, 0);
|
|
18
|
-
default:
|
|
19
|
-
return undefined;
|
|
20
|
-
}
|
|
15
|
+
const step = keySteps.get(key);
|
|
16
|
+
if (step === undefined)
|
|
17
|
+
return undefined;
|
|
18
|
+
return clamp(index + step, 0, Math.max(length - 1, 0));
|
|
21
19
|
}
|
|
22
20
|
/**
|
|
23
21
|
* Option id for `aria-activedescendant`. Index-based because option values
|
|
@@ -45,15 +43,15 @@ function isComposingKey(event) {
|
|
|
45
43
|
}
|
|
46
44
|
/**
|
|
47
45
|
* Shared keydown for a search input that drives a listbox: navigation keys
|
|
48
|
-
* move the highlight
|
|
49
|
-
* the key is handled (or an IME is composing, when every key
|
|
50
|
-
* IME), so the caller can stop; `false` leaves the key to the
|
|
51
|
-
* example SearchPicker's Escape).
|
|
46
|
+
* move the highlight over `items`, Enter picks the highlighted item. Returns
|
|
47
|
+
* `true` when the key is handled (or an IME is composing, when every key
|
|
48
|
+
* belongs to the IME), so the caller can stop; `false` leaves the key to the
|
|
49
|
+
* caller (for example SearchPicker's Escape).
|
|
52
50
|
*/
|
|
53
|
-
export function handleListboxKey(event, {
|
|
51
|
+
export function handleListboxKey(event, { items, highlight, setHighlight, onPick, }) {
|
|
54
52
|
if (isComposingKey(event))
|
|
55
53
|
return true;
|
|
56
|
-
const next = moveListboxHighlight(event.key, highlight, length);
|
|
54
|
+
const next = moveListboxHighlight(event.key, highlight, items.length);
|
|
57
55
|
if (next !== undefined) {
|
|
58
56
|
event.preventDefault();
|
|
59
57
|
setHighlight(next);
|
|
@@ -61,7 +59,9 @@ export function handleListboxKey(event, { highlight, length, setHighlight, onEnt
|
|
|
61
59
|
}
|
|
62
60
|
if (event.key === "Enter") {
|
|
63
61
|
event.preventDefault();
|
|
64
|
-
|
|
62
|
+
const item = items[highlight];
|
|
63
|
+
if (item !== undefined)
|
|
64
|
+
onPick(item);
|
|
65
65
|
return true;
|
|
66
66
|
}
|
|
67
67
|
return false;
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* ARIA for the search input of a listbox; the input sets `aria-controls`
|
|
4
|
+
* itself, where the linter can see it.
|
|
5
|
+
*/
|
|
6
|
+
export declare function listboxInputAria(listId: string, activeIndex: number | undefined): {
|
|
7
|
+
"aria-autocomplete": "list";
|
|
8
|
+
"aria-activedescendant": string | undefined;
|
|
9
|
+
};
|
|
10
|
+
/** The scrolling listbox; sr-only while `ListboxEmptyStatus` shows. */
|
|
11
|
+
export declare function Listbox({ empty, className, ...props }: Omit<React.ComponentProps<"ul">, "role"> & {
|
|
12
|
+
empty: boolean;
|
|
13
|
+
}): React.JSX.Element;
|
|
2
14
|
type PickerOptionProps<T> = {
|
|
3
15
|
item: T;
|
|
4
16
|
getLabel: (item: T) => string;
|
|
@@ -16,9 +28,10 @@ type PickerOptionProps<T> = {
|
|
|
16
28
|
declare function PickerOptionInner<T>({ item, getLabel, onSelect, onHighlight, showCheck, ...props }: PickerOptionProps<T>): React.JSX.Element;
|
|
17
29
|
export declare const PickerOption: typeof PickerOptionInner;
|
|
18
30
|
/**
|
|
19
|
-
* The "No results" status
|
|
20
|
-
*
|
|
21
|
-
* announce the empty state as
|
|
31
|
+
* The "No results" status (`children`, default "No results"). It sits
|
|
32
|
+
* outside the listbox, which may own only options, and stays mounted
|
|
33
|
+
* (sr-only when not empty) so screen readers announce the empty state as
|
|
34
|
+
* the query changes.
|
|
22
35
|
*/
|
|
23
36
|
export declare function ListboxEmptyStatus({ empty, className, children, ...props }: Omit<React.ComponentProps<"div">, "role"> & {
|
|
24
37
|
empty: boolean;
|
|
@@ -4,7 +4,23 @@ import { CheckIcon } from "lucide-react";
|
|
|
4
4
|
import { cn } from "../utils.js";
|
|
5
5
|
import { listboxOptionId, scrollOptionIntoView } from "./listbox-navigation.js";
|
|
6
6
|
import { optionRow } from "./overlay-styles.js";
|
|
7
|
-
import { listboxEmpty, optionState } from "./styles.js";
|
|
7
|
+
import { listboxEmpty, listboxScroll, optionState } from "./styles.js";
|
|
8
|
+
/**
|
|
9
|
+
* ARIA for the search input of a listbox; the input sets `aria-controls`
|
|
10
|
+
* itself, where the linter can see it.
|
|
11
|
+
*/
|
|
12
|
+
export function listboxInputAria(listId, activeIndex) {
|
|
13
|
+
return {
|
|
14
|
+
"aria-autocomplete": "list",
|
|
15
|
+
"aria-activedescendant": activeIndex === undefined
|
|
16
|
+
? undefined
|
|
17
|
+
: listboxOptionId(listId, activeIndex),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** The scrolling listbox; sr-only while `ListboxEmptyStatus` shows. */
|
|
21
|
+
export function Listbox({ empty, className, ...props }) {
|
|
22
|
+
return (_jsx("ul", { role: "listbox", className: empty ? "sr-only" : cn(listboxScroll, className), ...props }));
|
|
23
|
+
}
|
|
8
24
|
/**
|
|
9
25
|
* One option of a search-driven listbox (Combobox, SearchPicker): a
|
|
10
26
|
* non-focusable button in a presentational `li`, highlighted by state and
|
|
@@ -20,10 +36,11 @@ function PickerOptionInner({ item, getLabel, onSelect, onHighlight, showCheck, .
|
|
|
20
36
|
}
|
|
21
37
|
export const PickerOption = React.memo(PickerOptionInner);
|
|
22
38
|
/**
|
|
23
|
-
* The "No results" status
|
|
24
|
-
*
|
|
25
|
-
* announce the empty state as
|
|
39
|
+
* The "No results" status (`children`, default "No results"). It sits
|
|
40
|
+
* outside the listbox, which may own only options, and stays mounted
|
|
41
|
+
* (sr-only when not empty) so screen readers announce the empty state as
|
|
42
|
+
* the query changes.
|
|
26
43
|
*/
|
|
27
|
-
export function ListboxEmptyStatus({ empty, className, children, ...props }) {
|
|
44
|
+
export function ListboxEmptyStatus({ empty, className, children = "No results", ...props }) {
|
|
28
45
|
return (_jsx("div", { role: "status", className: empty ? cn(listboxEmpty, className) : "sr-only", ...props, children: empty ? children : null }));
|
|
29
46
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Classes shared by the kit's floating surfaces
|
|
3
|
-
* Select, Dialog, Tooltip) and the option rows
|
|
4
|
-
*
|
|
5
|
-
* and move the same everywhere. Tailwind only
|
|
6
|
-
* finds as literals, so every class appears here
|
|
2
|
+
* Classes and placement defaults shared by the kit's floating surfaces
|
|
3
|
+
* (Popover, DropdownMenu, Select, Dialog, Tooltip) and the option rows
|
|
4
|
+
* inside them. Components use these instead of retyping them, so the
|
|
5
|
+
* panels and rows look, sit, and move the same everywhere. Tailwind only
|
|
6
|
+
* generates class names it finds as literals, so every class appears here
|
|
7
|
+
* in full.
|
|
7
8
|
*/
|
|
8
9
|
/** The panel: square corners, popover colors, shadow, and a hairline ring. */
|
|
9
10
|
export declare const overlaySurface = "rounded-none bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10";
|
|
@@ -16,6 +17,11 @@ export declare const overlaySideSlide = "data-[side=bottom]:slide-in-from-top-2
|
|
|
16
17
|
export declare const overlayFade = "duration-100 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0";
|
|
17
18
|
/** Fade plus a slight zoom, for panels (the backdrop only fades). */
|
|
18
19
|
export declare const overlayFadeZoom = "duration-100 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 data-open:zoom-in-95 data-closed:zoom-out-95";
|
|
20
|
+
/**
|
|
21
|
+
* A panel opened from a trigger (Popover, Select, DropdownMenu): the
|
|
22
|
+
* surface, sliding in from the trigger's side with a fade and zoom.
|
|
23
|
+
*/
|
|
24
|
+
export declare const anchoredPanel = "rounded-none bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 duration-100 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 data-open:zoom-in-95 data-closed:zoom-out-95";
|
|
19
25
|
/**
|
|
20
26
|
* Base of a menu or listbox row (SelectItem, DropdownMenuItem, Combobox and
|
|
21
27
|
* SearchPicker options). Each component adds its own right padding and
|
|
@@ -24,3 +30,7 @@ export declare const overlayFadeZoom = "duration-100 data-open:animate-in data-o
|
|
|
24
30
|
export declare const optionRow = "relative flex w-full cursor-default items-center gap-2.5 rounded-none py-2 pl-3 text-left text-sm outline-hidden select-none";
|
|
25
31
|
/** Group label in a menu or listbox (DropdownMenuLabel, SelectLabel). */
|
|
26
32
|
export declare const menuLabel = "px-3 py-2 text-xs font-semibold tracking-wider text-muted-foreground uppercase";
|
|
33
|
+
/** Default gap in px between a trigger and its panel (Radix `sideOffset`). */
|
|
34
|
+
export declare const OVERLAY_SIDE_OFFSET = 4;
|
|
35
|
+
/** Default px a panel keeps from the viewport edge (Radix `collisionPadding`). */
|
|
36
|
+
export declare const OVERLAY_COLLISION_PADDING = 8;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Classes shared by the kit's floating surfaces
|
|
3
|
-
* Select, Dialog, Tooltip) and the option rows
|
|
4
|
-
*
|
|
5
|
-
* and move the same everywhere. Tailwind only
|
|
6
|
-
* finds as literals, so every class appears here
|
|
2
|
+
* Classes and placement defaults shared by the kit's floating surfaces
|
|
3
|
+
* (Popover, DropdownMenu, Select, Dialog, Tooltip) and the option rows
|
|
4
|
+
* inside them. Components use these instead of retyping them, so the
|
|
5
|
+
* panels and rows look, sit, and move the same everywhere. Tailwind only
|
|
6
|
+
* generates class names it finds as literals, so every class appears here
|
|
7
|
+
* in full.
|
|
7
8
|
*/
|
|
8
9
|
/** The panel: square corners, popover colors, shadow, and a hairline ring. */
|
|
9
10
|
export const overlaySurface = "rounded-none bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10";
|
|
@@ -16,6 +17,11 @@ export const overlaySideSlide = "data-[side=bottom]:slide-in-from-top-2 data-[si
|
|
|
16
17
|
export const overlayFade = "duration-100 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0";
|
|
17
18
|
/** Fade plus a slight zoom, for panels (the backdrop only fades). */
|
|
18
19
|
export const overlayFadeZoom = `${overlayFade} data-open:zoom-in-95 data-closed:zoom-out-95`;
|
|
20
|
+
/**
|
|
21
|
+
* A panel opened from a trigger (Popover, Select, DropdownMenu): the
|
|
22
|
+
* surface, sliding in from the trigger's side with a fade and zoom.
|
|
23
|
+
*/
|
|
24
|
+
export const anchoredPanel = `${overlaySurface} ${overlaySideSlide} ${overlayFadeZoom}`;
|
|
19
25
|
/**
|
|
20
26
|
* Base of a menu or listbox row (SelectItem, DropdownMenuItem, Combobox and
|
|
21
27
|
* SearchPicker options). Each component adds its own right padding and
|
|
@@ -24,3 +30,7 @@ export const overlayFadeZoom = `${overlayFade} data-open:zoom-in-95 data-closed:
|
|
|
24
30
|
export const optionRow = "relative flex w-full cursor-default items-center gap-2.5 rounded-none py-2 pl-3 text-left text-sm outline-hidden select-none";
|
|
25
31
|
/** Group label in a menu or listbox (DropdownMenuLabel, SelectLabel). */
|
|
26
32
|
export const menuLabel = "px-3 py-2 text-xs font-semibold tracking-wider text-muted-foreground uppercase";
|
|
33
|
+
/** Default gap in px between a trigger and its panel (Radix `sideOffset`). */
|
|
34
|
+
export const OVERLAY_SIDE_OFFSET = 4;
|
|
35
|
+
/** Default px a panel keeps from the viewport edge (Radix `collisionPadding`). */
|
|
36
|
+
export const OVERLAY_COLLISION_PADDING = 8;
|
|
@@ -9,4 +9,8 @@ export declare function createSearchIndex<T>(items: T[], getLabel: (item: T) =>
|
|
|
9
9
|
item: T;
|
|
10
10
|
label: string;
|
|
11
11
|
}[];
|
|
12
|
+
/**
|
|
13
|
+
* The items whose folded label includes `needle` (a `normalizeSearchQuery`
|
|
14
|
+
* result), in order; every item when `needle` is empty.
|
|
15
|
+
*/
|
|
12
16
|
export declare function filterSearchIndex<T>(index: ReturnType<typeof createSearchIndex<T>>, needle: string): T[];
|
package/dist/internal/search.js
CHANGED
|
@@ -12,7 +12,13 @@ export function normalizeSearchQuery(query) {
|
|
|
12
12
|
export function createSearchIndex(items, getLabel) {
|
|
13
13
|
return items.map((item) => ({ item, label: foldSearchText(getLabel(item)) }));
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* The items whose folded label includes `needle` (a `normalizeSearchQuery`
|
|
17
|
+
* result), in order; every item when `needle` is empty.
|
|
18
|
+
*/
|
|
15
19
|
export function filterSearchIndex(index, needle) {
|
|
20
|
+
if (!needle)
|
|
21
|
+
return index.map(({ item }) => item);
|
|
16
22
|
return index
|
|
17
23
|
.filter(({ label }) => label.includes(needle))
|
|
18
24
|
.map(({ item }) => item);
|
|
@@ -8,8 +8,12 @@
|
|
|
8
8
|
*/
|
|
9
9
|
/** Keyboard focus: a solid 2px ring separated from filled controls (WCAG 1.4.11). */
|
|
10
10
|
export declare const focusRing = "outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background";
|
|
11
|
+
/** The same ring on a wrapper with keyboard focus inside it. */
|
|
12
|
+
export declare const focusRingWithin = "has-focus-visible:ring-2 has-focus-visible:ring-ring has-focus-visible:ring-offset-2 has-focus-visible:ring-offset-background";
|
|
11
13
|
/** `aria-invalid`: destructive border and a soft glow; the error text carries the message. */
|
|
12
14
|
export declare const invalidState = "aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40";
|
|
15
|
+
/** Muted trigger text while empty (`data-placeholder` or `data-empty`). */
|
|
16
|
+
export declare const emptyValueText = "data-placeholder:text-muted-foreground data-[empty=true]:text-muted-foreground";
|
|
13
17
|
/** Native `disabled`: dimmed, with no hover or click. */
|
|
14
18
|
export declare const disabledState = "disabled:pointer-events-none disabled:opacity-50";
|
|
15
19
|
/** Radix `data-disabled` (menu and list items, slider). */
|
|
@@ -37,6 +41,16 @@ export declare const selectedState: {
|
|
|
37
41
|
/** Quiet buttons and toggles on hover. */
|
|
38
42
|
hover: string;
|
|
39
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* Alert and Toast tones: a border, and `--status-tone` for the title and
|
|
46
|
+
* icon (`text-(--status-tone)`). `neutral` unsets it, so they inherit.
|
|
47
|
+
*/
|
|
48
|
+
export declare const statusTone: {
|
|
49
|
+
neutral: string;
|
|
50
|
+
success: string;
|
|
51
|
+
warning: string;
|
|
52
|
+
destructive: string;
|
|
53
|
+
};
|
|
40
54
|
/** The "No results" row of a listbox. */
|
|
41
55
|
export declare const listboxEmpty = "px-3 py-4 text-center text-sm text-muted-foreground";
|
|
42
56
|
/**
|
|
@@ -83,6 +97,13 @@ export declare const controlSize: {
|
|
|
83
97
|
* tailwind-merge then drops the `text-xs`.
|
|
84
98
|
*/
|
|
85
99
|
export declare const controlText = "text-xs font-semibold tracking-widest whitespace-nowrap uppercase";
|
|
100
|
+
/**
|
|
101
|
+
* Secondary text under a title (CardDescription, PopoverDescription,
|
|
102
|
+
* DialogDescription, AlertDescription).
|
|
103
|
+
*/
|
|
104
|
+
export declare const descriptionText = "text-sm leading-relaxed text-muted-foreground";
|
|
105
|
+
/** Field-label text (Label, FieldLegend, FieldTitle, PopoverTitle). */
|
|
106
|
+
export declare const labelText = "text-xs font-semibold tracking-wide uppercase";
|
|
86
107
|
/**
|
|
87
108
|
* Icons inside a control or row: no pointer events, no shrinking, 14px
|
|
88
109
|
* unless the icon sets its own `size-*`. `sm` lowers that default to 12px
|
package/dist/internal/styles.js
CHANGED
|
@@ -8,8 +8,12 @@
|
|
|
8
8
|
*/
|
|
9
9
|
/** Keyboard focus: a solid 2px ring separated from filled controls (WCAG 1.4.11). */
|
|
10
10
|
export const focusRing = "outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background";
|
|
11
|
+
/** The same ring on a wrapper with keyboard focus inside it. */
|
|
12
|
+
export const focusRingWithin = "has-focus-visible:ring-2 has-focus-visible:ring-ring has-focus-visible:ring-offset-2 has-focus-visible:ring-offset-background";
|
|
11
13
|
/** `aria-invalid`: destructive border and a soft glow; the error text carries the message. */
|
|
12
14
|
export const invalidState = "aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40";
|
|
15
|
+
/** Muted trigger text while empty (`data-placeholder` or `data-empty`). */
|
|
16
|
+
export const emptyValueText = "data-placeholder:text-muted-foreground data-[empty=true]:text-muted-foreground";
|
|
13
17
|
/** Native `disabled`: dimmed, with no hover or click. */
|
|
14
18
|
export const disabledState = "disabled:pointer-events-none disabled:opacity-50";
|
|
15
19
|
/** Radix `data-disabled` (menu and list items, slider). */
|
|
@@ -37,6 +41,16 @@ export const selectedState = {
|
|
|
37
41
|
/** Quiet buttons and toggles on hover. */
|
|
38
42
|
hover: "hover:border-selected-foreground hover:bg-selected hover:text-selected-foreground",
|
|
39
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* Alert and Toast tones: a border, and `--status-tone` for the title and
|
|
46
|
+
* icon (`text-(--status-tone)`). `neutral` unsets it, so they inherit.
|
|
47
|
+
*/
|
|
48
|
+
export const statusTone = {
|
|
49
|
+
neutral: "[--status-tone:initial]",
|
|
50
|
+
success: "border-success/40 [--status-tone:var(--color-success)]",
|
|
51
|
+
warning: "border-warning/40 [--status-tone:var(--color-warning)]",
|
|
52
|
+
destructive: "border-destructive/40 [--status-tone:var(--color-destructive)]",
|
|
53
|
+
};
|
|
40
54
|
/** The "No results" row of a listbox. */
|
|
41
55
|
export const listboxEmpty = "px-3 py-4 text-center text-sm text-muted-foreground";
|
|
42
56
|
/**
|
|
@@ -83,6 +97,13 @@ export const controlSize = {
|
|
|
83
97
|
* tailwind-merge then drops the `text-xs`.
|
|
84
98
|
*/
|
|
85
99
|
export const controlText = "text-xs font-semibold tracking-widest whitespace-nowrap uppercase";
|
|
100
|
+
/**
|
|
101
|
+
* Secondary text under a title (CardDescription, PopoverDescription,
|
|
102
|
+
* DialogDescription, AlertDescription).
|
|
103
|
+
*/
|
|
104
|
+
export const descriptionText = "text-sm leading-relaxed text-muted-foreground";
|
|
105
|
+
/** Field-label text (Label, FieldLegend, FieldTitle, PopoverTitle). */
|
|
106
|
+
export const labelText = "text-xs font-semibold tracking-wide uppercase";
|
|
86
107
|
/**
|
|
87
108
|
* Icons inside a control or row: no pointer events, no shrinking, 14px
|
|
88
109
|
* unless the icon sets its own `size-*`. `sm` lowers that default to 12px
|
package/dist/kanban.js
CHANGED
|
@@ -7,6 +7,7 @@ import { CheckCircle2, ChevronLeft, ChevronRight, GripVertical, Trash2, } from "
|
|
|
7
7
|
import { Button } from "./button.js";
|
|
8
8
|
import { KANBAN_COMPLETE_ID, KANBAN_DELETE_ID, buildKanbanGroups, kanbanOverflowEdges, kanbanScrollBy, resolveKanbanDrop, } from "./internal/kanban-layout.js";
|
|
9
9
|
import { configureCardSortablePlugins, configureKanbanPlugins, } from "./internal/kanban-plugins.js";
|
|
10
|
+
import { focusRingWithin } from "./internal/styles.js";
|
|
10
11
|
import { Skeleton } from "./skeleton.js";
|
|
11
12
|
import { cn } from "./utils.js";
|
|
12
13
|
/** Column droppables lose to cards, so a drop lands between cards. */
|
|
@@ -124,7 +125,7 @@ function KanbanColumnView({ column, count, emptyLabel, loading, className, child
|
|
|
124
125
|
collisionPriority: COLUMN_DROP_PRIORITY,
|
|
125
126
|
});
|
|
126
127
|
const empty = count === 0;
|
|
127
|
-
return (_jsxs("div", { ref: ref, "data-slot": "kanban-column", "data-drop-target": isDropTarget || undefined, className: cn("flex snap-start flex-col gap-3 p-3", "bg-muted/40 ring-1 ring-foreground/5", "data-drop-target:bg-muted/60 data-drop-target:ring-2 data-drop-target:ring-ring/60", columnSize, className), children: [_jsxs("header", { "data-slot": "kanban-column-header", className: "flex items-start justify-between gap-2", children: [_jsxs("span", { className: "flex items-center gap-2 text-xs font-semibold tracking-widest uppercase", children: [column.color ? (_jsx("span", { "aria-hidden": true, className: "size-2 shrink-0 rounded-full", style: { backgroundColor: column.color } })) : null, _jsx("span", { className: "leading-tight", children: column.label })] }), _jsxs("span", { className: "flex
|
|
128
|
+
return (_jsxs("div", { ref: ref, "data-slot": "kanban-column", "data-drop-target": isDropTarget || undefined, className: cn("flex snap-start flex-col gap-3 p-3", "bg-muted/40 ring-1 ring-foreground/5", "data-drop-target:bg-muted/60 data-drop-target:ring-2 data-drop-target:ring-ring/60", columnSize, className), children: [_jsxs("header", { "data-slot": "kanban-column-header", className: "flex flex-wrap items-start justify-between gap-x-2 gap-y-0.5", children: [_jsxs("span", { className: "flex max-w-full min-w-0 items-center gap-2 text-xs font-semibold tracking-widest uppercase", children: [column.color ? (_jsx("span", { "aria-hidden": true, className: "size-2 shrink-0 rounded-full", style: { backgroundColor: column.color } })) : null, _jsx("span", { className: "truncate leading-tight", title: column.label, children: column.label })] }), _jsxs("span", { className: "ml-auto flex max-w-full min-w-0 flex-col items-end gap-0.5 text-muted-foreground tabular-nums", children: [_jsx("span", { className: "text-xs", children: count }), column.badge ? (_jsx("span", { className: "max-w-full truncate text-[10px] font-medium", children: column.badge })) : null] })] }), _jsx("div", { className: "-m-1 mr-0 flex min-h-32 flex-1 flex-col gap-2 overflow-y-auto p-1", children: loading ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "h-20" }), _jsx(Skeleton, { className: "h-16" })] })) : (_jsxs(_Fragment, { children: [children, _jsx("p", { "data-slot": "kanban-column-empty", "aria-hidden": !empty, className: cn("border border-dashed px-3 py-6 text-center text-xs text-muted-foreground", !empty && "hidden"), children: emptyLabel })] })) })] }));
|
|
128
129
|
}
|
|
129
130
|
const cardChrome = cn("flex border border-border bg-card text-card-foreground");
|
|
130
131
|
const handleChrome = cn("flex shrink-0 items-center self-stretch border-r border-border", "bg-muted/30 px-1.5 text-muted-foreground");
|
|
@@ -146,7 +147,7 @@ function KanbanCardViewImpl({ item, index, column, onCardClick, className, rende
|
|
|
146
147
|
// The overlay moves; the source keeps its slot.
|
|
147
148
|
"data-dragging:pointer-events-none data-dragging:invisible",
|
|
148
149
|
// One ring on the card for the grip and the body alike.
|
|
149
|
-
|
|
150
|
+
focusRingWithin, onCardClick && "hover:border-foreground/15", className), children: [_jsx("button", { type: "button", ref: handleRef, "data-slot": "kanban-card-handle", "aria-label": "Drag to move", onClick: stopClick, className: cn(handleChrome, "cursor-grab outline-none hover:bg-muted/50 active:cursor-grabbing"), children: _jsx(GripVertical, { className: "size-4", "aria-hidden": true }) }), onCardClick ? (_jsx("button", { type: "button", "data-slot": "kanban-card-body", onClick: () => onCardClick(item), className: "min-w-0 flex-1 cursor-pointer p-3 text-left outline-none hover:bg-muted/20", children: children })) : (_jsx("div", { "data-slot": "kanban-card-body", className: "min-w-0 flex-1 p-3", children: children }))] }));
|
|
150
151
|
}
|
|
151
152
|
/**
|
|
152
153
|
* Memoized so a board update re-renders only the cards whose item, index or
|
package/dist/label.js
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Label as LabelPrimitive } from "radix-ui";
|
|
5
|
+
import { labelText } from "./internal/styles.js";
|
|
5
6
|
import { cn } from "./utils.js";
|
|
6
7
|
/**
|
|
7
8
|
* Form label. `required` adds a visible marker; style it with
|
|
8
9
|
* `requiredClassName` or the `label-required` slot.
|
|
9
10
|
*/
|
|
10
11
|
function Label({ className, required, requiredClassName, children, ...props }) {
|
|
11
|
-
return (_jsxs(LabelPrimitive.Root, { "data-slot": "label", className: cn("flex items-center gap-2
|
|
12
|
+
return (_jsxs(LabelPrimitive.Root, { "data-slot": "label", className: cn(labelText, "flex items-center gap-2 select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50 peer-data-[slot=checkbox]:text-sm peer-data-[slot=checkbox]:font-normal peer-data-[slot=checkbox]:tracking-normal peer-data-[slot=checkbox]:normal-case peer-data-[slot=radio-group-item]:text-sm peer-data-[slot=radio-group-item]:font-normal peer-data-[slot=radio-group-item]:tracking-normal peer-data-[slot=radio-group-item]:normal-case peer-data-[slot=switch]:text-sm peer-data-[slot=switch]:font-normal peer-data-[slot=switch]:tracking-normal peer-data-[slot=switch]:normal-case", className), ...props, children: [children, required ? (_jsx("span", { "data-slot": "label-required", "aria-hidden": "true", className: cn("-ml-1.5 text-destructive", requiredClassName), children: "*" })) : null] }));
|
|
12
13
|
}
|
|
13
14
|
export { Label };
|
package/dist/lazy-calendar.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import { calendarSize } from "./internal/calendar-size.js";
|
|
4
5
|
import { Spinner } from "./spinner.js";
|
|
5
6
|
import { cn } from "./utils.js";
|
|
6
7
|
/**
|
|
@@ -16,11 +17,11 @@ export function preloadCalendar() {
|
|
|
16
17
|
const LazyCalendarInner = React.lazy(() => loadCalendar().then((module) => ({ default: module.Calendar })));
|
|
17
18
|
/**
|
|
18
19
|
* While loading, a placeholder the size of a one-month calendar, measured in
|
|
19
|
-
* the calendar's own
|
|
20
|
+
* the calendar's own `calendarSize` (7 columns plus padding; about 9 cells
|
|
20
21
|
* tall), so the popover does not jump. It takes the calendar's `className`,
|
|
21
22
|
* so a `--cell-size` override sizes both.
|
|
22
23
|
*/
|
|
23
|
-
const fallbackClassName = "flex h-[calc(var(--cell-size)*9)] w-[calc(var(--cell-size)*7
|
|
24
|
+
const fallbackClassName = cn("flex h-[calc(var(--cell-size)*9)] w-[calc(var(--cell-size)*7+var(--calendar-padding)*2)] items-center justify-center", calendarSize);
|
|
24
25
|
export function LazyCalendar(props) {
|
|
25
26
|
return (_jsx(React.Suspense, { fallback: _jsx("div", { className: cn(fallbackClassName, props.className), children: _jsx(Spinner, { "aria-label": "Loading calendar" }) }), children: _jsx(LazyCalendarInner, { ...props }) }));
|
|
26
27
|
}
|
package/dist/nav-link.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
4
|
import { Slot } from "radix-ui";
|
|
5
|
+
import { activeLinkAttributes } from "./internal/active-link.js";
|
|
5
6
|
import { focusRing, selectedState } from "./internal/styles.js";
|
|
6
7
|
import { cn } from "./utils.js";
|
|
7
8
|
/**
|
|
@@ -36,6 +37,6 @@ function isNavActive(pathname, href) {
|
|
|
36
37
|
*/
|
|
37
38
|
function NavLink({ asChild, active, className, ...props }) {
|
|
38
39
|
const Comp = asChild ? Slot.Root : "a";
|
|
39
|
-
return (_jsx(Comp, { "data-slot": "nav-link",
|
|
40
|
+
return (_jsx(Comp, { "data-slot": "nav-link", ...activeLinkAttributes(active), className: cn(navLinkVariants(), className), ...props }));
|
|
40
41
|
}
|
|
41
42
|
export { NavLink, isNavActive, navLinkVariants };
|
package/dist/picker-shell.d.ts
CHANGED
|
@@ -64,6 +64,8 @@ export type PickerTriggerProps = {
|
|
|
64
64
|
"aria-labelledby"?: string;
|
|
65
65
|
/** Forwarded to the trigger, e.g. the id of a hint or error text. */
|
|
66
66
|
"aria-describedby"?: string;
|
|
67
|
+
/** Forwarded to the trigger; a form field sets it for `focusFirstInvalid`. */
|
|
68
|
+
"data-field-name"?: string;
|
|
67
69
|
/** Popover side. Defaults to `bottom`; Radix still flips on collision. */
|
|
68
70
|
side?: React.ComponentProps<typeof PopoverContent>["side"];
|
|
69
71
|
/** Month shown when the popover opens; falls back to the selected date. */
|
|
@@ -112,5 +114,5 @@ type PickerShellProps = Omit<PickerTriggerProps, "placeholder"> & {
|
|
|
112
114
|
* Form-control trigger plus the viewport-clamped popover holding the
|
|
113
115
|
* calendar, any extra rows, and an optional Clear row.
|
|
114
116
|
*/
|
|
115
|
-
export declare function PickerShell({ id, disabled, className, contentClassName, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, side, defaultMonth, minDate, maxDate, calendarProps, empty, label, dialogLabel, open, onOpenChange, calendar, onClear, clearDisabled, children, }: PickerShellProps): React.JSX.Element;
|
|
117
|
+
export declare function PickerShell({ id, disabled, className, contentClassName, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "data-field-name": fieldName, side, defaultMonth, minDate, maxDate, calendarProps, empty, label, dialogLabel, open, onOpenChange, calendar, onClear, clearDisabled, children, }: PickerShellProps): React.JSX.Element;
|
|
116
118
|
export {};
|
package/dist/picker-shell.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { CalendarIcon } from "lucide-react";
|
|
5
5
|
import { Button } from "./button.js";
|
|
6
6
|
import { toDate, } from "./internal/date-like.js";
|
|
7
|
-
import { PICKER_COLLISION_PADDING, pickerContentClassName, pickerTriggerClassName, pickerValueId, } from "./internal/form-control.js";
|
|
7
|
+
import { fieldNameProps, PICKER_COLLISION_PADDING, pickerContentClassName, pickerTriggerClassName, pickerValueId, } from "./internal/form-control.js";
|
|
8
8
|
import { LazyCalendar, preloadCalendar } from "./lazy-calendar.js";
|
|
9
9
|
import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
|
|
10
10
|
import { cn } from "./utils.js";
|
|
@@ -39,8 +39,8 @@ function limitCalendarProps(calendarProps, minDate, maxDate) {
|
|
|
39
39
|
* Form-control trigger plus the viewport-clamped popover holding the
|
|
40
40
|
* calendar, any extra rows, and an optional Clear row.
|
|
41
41
|
*/
|
|
42
|
-
export function PickerShell({ id, disabled, className, contentClassName, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, side, defaultMonth, minDate, maxDate, calendarProps, empty, label, dialogLabel, open, onOpenChange, calendar, onClear, clearDisabled = false, children, }) {
|
|
43
|
-
return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { id: id, type: "button", variant: "outline", disabled: disabled, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "data-empty": empty, className: cn(pickerTriggerClassName, className), onPointerEnter: preloadCalendar, onFocus: preloadCalendar, children: [_jsx(CalendarIcon, { "data-icon": "inline-start" }), _jsx("span", { "data-slot": "date-picker-value", className: "min-w-0 text-left whitespace-normal", id: id ? pickerValueId(id) : undefined, children: label })] }) }), _jsxs(PopoverContent, { align: "start", side: side, collisionPadding: PICKER_COLLISION_PADDING, "aria-label": dialogLabel,
|
|
42
|
+
export function PickerShell({ id, disabled, className, contentClassName, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "data-field-name": fieldName, side, defaultMonth, minDate, maxDate, calendarProps, empty, label, dialogLabel, open, onOpenChange, calendar, onClear, clearDisabled = false, children, }) {
|
|
43
|
+
return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { id: id, type: "button", variant: "outline", disabled: disabled, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, ...fieldNameProps(fieldName), "data-empty": empty, className: cn(pickerTriggerClassName, className), onPointerEnter: preloadCalendar, onFocus: preloadCalendar, children: [_jsx(CalendarIcon, { "data-icon": "inline-start" }), _jsx("span", { "data-slot": "date-picker-value", className: "min-w-0 text-left whitespace-normal", id: id ? pickerValueId(id) : undefined, children: label })] }) }), _jsxs(PopoverContent, { align: "start", side: side, collisionPadding: PICKER_COLLISION_PADDING, "aria-label": dialogLabel,
|
|
44
44
|
// The calendar's `autoFocus` puts focus on the selected day (or
|
|
45
45
|
// today) once it loads, as the APG date picker dialog does; Radix
|
|
46
46
|
// focusing the popover first would leave it on the container.
|
package/dist/popover.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Popover as PopoverPrimitive } from "radix-ui";
|
|
5
|
-
import {
|
|
5
|
+
import { anchoredPanel, OVERLAY_COLLISION_PADDING, OVERLAY_SIDE_OFFSET, } from "./internal/overlay-styles.js";
|
|
6
|
+
import { descriptionText, labelText } from "./internal/styles.js";
|
|
6
7
|
import { cn } from "./utils.js";
|
|
7
8
|
function Popover({ ...props }) {
|
|
8
9
|
return _jsx(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
|
|
@@ -10,8 +11,8 @@ function Popover({ ...props }) {
|
|
|
10
11
|
function PopoverTrigger({ ...props }) {
|
|
11
12
|
return _jsx(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
|
|
12
13
|
}
|
|
13
|
-
function PopoverContent({ className, align = "center", sideOffset =
|
|
14
|
-
return (_jsx(PopoverPrimitive.Portal, { children: _jsx(PopoverPrimitive.Content, { "data-slot": "popover-content", align: align, sideOffset: sideOffset, collisionPadding: collisionPadding, className: cn(
|
|
14
|
+
function PopoverContent({ className, align = "center", sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, ...props }) {
|
|
15
|
+
return (_jsx(PopoverPrimitive.Portal, { children: _jsx(PopoverPrimitive.Content, { "data-slot": "popover-content", align: align, sideOffset: sideOffset, collisionPadding: collisionPadding, className: cn(anchoredPanel, "z-50 flex max-w-(--radix-popover-content-available-width) origin-(--radix-popover-content-transform-origin) flex-col gap-4 p-4 text-sm outline-hidden", className), ...props }) }));
|
|
15
16
|
}
|
|
16
17
|
function PopoverAnchor({ ...props }) {
|
|
17
18
|
return _jsx(PopoverPrimitive.Anchor, { "data-slot": "popover-anchor", ...props });
|
|
@@ -20,9 +21,9 @@ function PopoverHeader({ className, ...props }) {
|
|
|
20
21
|
return (_jsx("div", { "data-slot": "popover-header", className: cn("flex flex-col gap-1 text-sm", className), ...props }));
|
|
21
22
|
}
|
|
22
23
|
function PopoverTitle({ className, ...props }) {
|
|
23
|
-
return (_jsx("h2", { "data-slot": "popover-title", className: cn(
|
|
24
|
+
return (_jsx("h2", { "data-slot": "popover-title", className: cn(labelText, className), ...props }));
|
|
24
25
|
}
|
|
25
26
|
function PopoverDescription({ className, ...props }) {
|
|
26
|
-
return (_jsx("p", { "data-slot": "popover-description", className: cn("mt-0.5
|
|
27
|
+
return (_jsx("p", { "data-slot": "popover-description", className: cn("mt-0.5", descriptionText, className), ...props }));
|
|
27
28
|
}
|
|
28
29
|
export { Popover, PopoverAnchor, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, };
|
package/dist/search-picker.js
CHANGED
|
@@ -3,11 +3,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Input } from "./input.js";
|
|
5
5
|
import { Label } from "./label.js";
|
|
6
|
-
import { handleListboxKey
|
|
7
|
-
import { ListboxEmptyStatus, PickerOption } from "./internal/listbox-option.js";
|
|
6
|
+
import { handleListboxKey } from "./internal/listbox-navigation.js";
|
|
7
|
+
import { Listbox, ListboxEmptyStatus, listboxInputAria, PickerOption, } from "./internal/listbox-option.js";
|
|
8
8
|
import { createSearchIndex, filterSearchIndex, foldSearchText, normalizeSearchQuery, } from "./internal/search.js";
|
|
9
|
-
import {
|
|
10
|
-
import { cn } from "./utils.js";
|
|
9
|
+
import { cn, onInputChange } from "./utils.js";
|
|
11
10
|
export { foldSearchText };
|
|
12
11
|
/**
|
|
13
12
|
* Default SearchPicker filter: the folded label includes the folded query.
|
|
@@ -25,7 +24,7 @@ const listFrame = "border border-input bg-background";
|
|
|
25
24
|
* the list is not in a popover. Keyboard: arrows, Home/End, PageUp/PageDown,
|
|
26
25
|
* Enter, Escape.
|
|
27
26
|
*/
|
|
28
|
-
function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter, value, onSelect, label, placeholder = "Search", emptyText
|
|
27
|
+
function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter, value, onSelect, label, placeholder = "Search", emptyText, listLabel, disabled, className, listClassName, ...props }) {
|
|
29
28
|
const [query, setQuery] = React.useState("");
|
|
30
29
|
const listId = React.useId();
|
|
31
30
|
const inputId = React.useId();
|
|
@@ -34,11 +33,8 @@ function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter,
|
|
|
34
33
|
: null, [filter, getLabel, items]);
|
|
35
34
|
const foldedQuery = filter === defaultSearchFilter ? normalizeSearchQuery(query) : "";
|
|
36
35
|
const filtered = React.useMemo(() => {
|
|
37
|
-
if (normalizedItems)
|
|
38
|
-
if (!foldedQuery)
|
|
39
|
-
return items;
|
|
36
|
+
if (normalizedItems)
|
|
40
37
|
return filterSearchIndex(normalizedItems, foldedQuery);
|
|
41
|
-
}
|
|
42
38
|
return items.filter((item) => filter(item, query, getLabel));
|
|
43
39
|
}, [items, filter, foldedQuery, getLabel, normalizedItems, query]);
|
|
44
40
|
const selectedIndex = React.useMemo(() => {
|
|
@@ -67,14 +63,10 @@ function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter,
|
|
|
67
63
|
}, [onSelect]);
|
|
68
64
|
const onKeyDown = (event) => {
|
|
69
65
|
const handled = handleListboxKey(event, {
|
|
66
|
+
items: filtered,
|
|
70
67
|
highlight,
|
|
71
|
-
length: filtered.length,
|
|
72
68
|
setHighlight,
|
|
73
|
-
|
|
74
|
-
const item = filtered[index];
|
|
75
|
-
if (item)
|
|
76
|
-
selectItem(item);
|
|
77
|
-
},
|
|
69
|
+
onPick: selectItem,
|
|
78
70
|
});
|
|
79
71
|
if (handled || event.key !== "Escape")
|
|
80
72
|
return;
|
|
@@ -85,6 +77,6 @@ function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter,
|
|
|
85
77
|
}
|
|
86
78
|
event.currentTarget.blur();
|
|
87
79
|
};
|
|
88
|
-
return (_jsxs("div", { "data-slot": "search-picker", className: cn("flex flex-col gap-1.5", className), ...props, children: [_jsx(Label, { htmlFor: inputId, children: label }), _jsx(Input, { id: inputId, role: "combobox", "aria-expanded": true, "aria-controls": listId,
|
|
80
|
+
return (_jsxs("div", { "data-slot": "search-picker", className: cn("flex flex-col gap-1.5", className), ...props, children: [_jsx(Label, { htmlFor: inputId, children: label }), _jsx(Input, { id: inputId, role: "combobox", "aria-expanded": true, "aria-controls": listId, ...listboxInputAria(listId, active ? highlight : undefined), value: query, disabled: disabled, placeholder: placeholder, onChange: onInputChange(setQuery), onKeyDown: onKeyDown }), _jsx(Listbox, { id: listId, empty: empty, "aria-label": listLabel ?? label, className: cn(listFrame, listClassName), children: rows.map(({ item, value: itemValue }, index) => (_jsx(PickerOption, { "data-slot": "search-picker-option", listId: listId, index: index, active: index === highlight, selected: itemValue === value, disabled: disabled, item: item, getLabel: getLabel, onHighlight: setHighlight, onSelect: selectItem }, itemValue))) }), _jsx(ListboxEmptyStatus, { "data-slot": "search-picker-empty", empty: empty, className: listFrame, children: emptyText })] }));
|
|
89
81
|
}
|
|
90
82
|
export { SearchPicker };
|
package/dist/select.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Select as SelectPrimitive } from "radix-ui";
|
|
5
|
-
import { dataDisabledState, disabledState, fieldSurface, focusRing, inlineIcon, invalidState, optionState, } from "./internal/styles.js";
|
|
6
|
-
import { menuLabel, optionRow,
|
|
5
|
+
import { dataDisabledState, disabledState, emptyValueText, fieldSurface, focusRing, inlineIcon, invalidState, optionState, } from "./internal/styles.js";
|
|
6
|
+
import { anchoredPanel, menuLabel, optionRow, OVERLAY_COLLISION_PADDING, OVERLAY_SIDE_OFFSET, } from "./internal/overlay-styles.js";
|
|
7
7
|
import { cn } from "./utils.js";
|
|
8
8
|
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
|
|
9
9
|
/**
|
|
@@ -22,10 +22,10 @@ function SelectValue({ ...props }) {
|
|
|
22
22
|
return _jsx(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
|
|
23
23
|
}
|
|
24
24
|
function SelectTrigger({ className, size = "default", children, ...props }) {
|
|
25
|
-
return (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, className: cn(fieldSurface, focusRing, invalidState, disabledState, inlineIcon.default, triggerHeight[size], "flex items-center justify-between gap-1.5 whitespace-nowrap
|
|
25
|
+
return (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, className: cn(fieldSurface, focusRing, invalidState, disabledState, emptyValueText, inlineIcon.default, triggerHeight[size], "flex items-center justify-between gap-1.5 whitespace-nowrap *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5", className), ...props, children: [children, _jsx(SelectPrimitive.Icon, { "data-slot": "select-icon", asChild: true, children: _jsx(ChevronDownIcon, { className: "pointer-events-none size-3.5 text-muted-foreground" }) })] }));
|
|
26
26
|
}
|
|
27
|
-
function SelectContent({ className, children, position = "popper", align = "center", sideOffset =
|
|
28
|
-
return (_jsx(SelectPrimitive.Portal, { children: _jsxs(SelectPrimitive.Content, { "data-slot": "select-content", "data-align-trigger": position === "item-aligned", className: cn(
|
|
27
|
+
function SelectContent({ className, children, position = "popper", align = "center", sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, ...props }) {
|
|
28
|
+
return (_jsx(SelectPrimitive.Portal, { children: _jsxs(SelectPrimitive.Content, { "data-slot": "select-content", "data-align-trigger": position === "item-aligned", className: cn(anchoredPanel, "relative z-50 max-h-(--radix-select-content-available-height) min-w-(--radix-select-trigger-width) origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto data-[align-trigger=true]:animate-none", position === "popper" &&
|
|
29
29
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className), position: position, align: align, sideOffset: sideOffset, collisionPadding: collisionPadding, ...props, children: [_jsx(SelectScrollUpButton, {}), _jsx(SelectPrimitive.Viewport, { "data-slot": "select-viewport", "data-position": position, className: "data-[position=popper]:h-(--radix-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--radix-select-trigger-width)", children: children }), _jsx(SelectScrollDownButton, {})] }) }));
|
|
30
30
|
}
|
|
31
31
|
function SelectLabel({ className, ...props }) {
|
package/dist/skip-link.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { focusRing } from "./internal/styles.js";
|
|
2
3
|
import { cn } from "./utils.js";
|
|
3
4
|
/**
|
|
4
5
|
* First focusable link on the page: hidden until a keyboard user tabs to
|
|
5
6
|
* it, then it jumps past the navigation to `href` (the main landmark's id).
|
|
6
7
|
*/
|
|
7
8
|
function SkipLink({ href = "#main-content", className, children = "Skip to content", ...props }) {
|
|
8
|
-
return (_jsx("a", { "data-slot": "skip-link", href: href, className: cn("sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:bg-background focus:px-3 focus:py-2 focus:text-sm
|
|
9
|
+
return (_jsx("a", { "data-slot": "skip-link", href: href, className: cn("sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:bg-background focus:px-3 focus:py-2 focus:text-sm", focusRing, className), ...props, children: children }));
|
|
9
10
|
}
|
|
10
11
|
export { SkipLink };
|