@nexub/react 7.4.0 → 7.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/accessibility.json +1 -1
- package/dist/components/Badge/Badge.d.ts +9 -0
- package/dist/components/Badge/Badge.js +40 -2
- package/dist/components/Button/Button.d.ts +1 -1
- package/dist/components/Combobox/Combobox.d.ts +6 -9
- package/dist/components/Combobox/Combobox.js +30 -9
- package/dist/components/CommandPalette/CommandPalette.d.ts +34 -13
- package/dist/components/CommandPalette/CommandPalette.js +174 -40
- package/dist/components/CommandPalette/index.d.ts +1 -1
- package/dist/components/DatePicker/DatePicker.d.ts +16 -1
- package/dist/components/DatePicker/DatePicker.js +100 -26
- package/dist/components/Dialog/Dialog.d.ts +7 -0
- package/dist/components/Dialog/Dialog.js +35 -8
- package/dist/components/EmptyState/EmptyState.d.ts +7 -1
- package/dist/components/EmptyState/EmptyState.js +21 -5
- package/dist/components/EmptyState/EmptyStatePreset.d.ts +2 -1
- package/dist/components/FileUpload/FileUpload.js +86 -32
- package/dist/components/Heading/Heading.d.ts +1 -1
- package/dist/components/IconButton/IconButton.d.ts +5 -1
- package/dist/components/IconButton/IconButton.js +14 -3
- package/dist/components/Input/Input.d.ts +6 -0
- package/dist/components/Input/Input.js +47 -5
- package/dist/components/Select/Select.d.ts +18 -3
- package/dist/components/Select/Select.js +170 -71
- package/dist/components/Slider/Slider.d.ts +10 -0
- package/dist/components/Slider/Slider.js +48 -3
- package/dist/components/Slider/index.d.ts +1 -1
- package/dist/components/Switch/Switch.d.ts +6 -0
- package/dist/components/Switch/Switch.js +29 -2
- package/dist/components/Tabs/Tabs.d.ts +5 -1
- package/dist/components/Tabs/Tabs.js +98 -35
- package/dist/components/Text/Text.d.ts +1 -1
- package/dist/components/Tooltip/Tooltip.d.ts +10 -3
- package/dist/components/Tooltip/Tooltip.js +16 -4
- package/dist/index.d.ts +3 -1
- package/dist/styles.css +1 -1
- package/package.json +6 -6
package/accessibility.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "7.
|
|
2
|
+
"version": "7.6.0",
|
|
3
3
|
"standard": "WCAG 2.2 Level AA",
|
|
4
4
|
"method": {
|
|
5
5
|
"automated": "axe-core rules tagged wcag2a, wcag2aa, wcag21a, wcag21aa and wcag22aa, run on every Storybook story in light and dark, and on open popups (menus, listboxes, dialogs), on every pull request; a new violation fails the build.",
|
|
@@ -8,5 +8,14 @@ export declare const badgeVariants: (props?: ({
|
|
|
8
8
|
export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement>, VariantProps<typeof badgeVariants> {
|
|
9
9
|
tone?: ToneValue;
|
|
10
10
|
leadingIcon?: React.ReactNode;
|
|
11
|
+
/**
|
|
12
|
+
* A status dot. Before the label when there is one; on its own (no children
|
|
13
|
+
* or count) it is just the dot: give it an `aria-label`, or it's decorative.
|
|
14
|
+
*/
|
|
15
|
+
dot?: boolean;
|
|
16
|
+
/** A number to show. Above `max` it reads "99+"; screen readers get the full number. */
|
|
17
|
+
count?: number;
|
|
18
|
+
/** @default 99 */
|
|
19
|
+
max?: number;
|
|
11
20
|
}
|
|
12
21
|
export declare const Badge: React.ForwardRefExoticComponent<BadgeProps & React.RefAttributes<HTMLSpanElement>>;
|
|
@@ -72,10 +72,37 @@ const toneClasses = {
|
|
|
72
72
|
outline: "border-[color:light-dark(var(--color-palette-founderub-80),var(--color-palette-founderub-40))] text-[var(--color-founderub)]"
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
|
+
const dotClasses = {
|
|
76
|
+
neutral: "bg-[var(--color-outline)]",
|
|
77
|
+
info: "bg-[var(--color-info)]",
|
|
78
|
+
success: "bg-[var(--color-success)]",
|
|
79
|
+
warning: "bg-[var(--color-warning)]",
|
|
80
|
+
danger: "bg-[var(--color-error)]",
|
|
81
|
+
leap: "bg-[var(--color-leap)]",
|
|
82
|
+
bookub: "bg-[var(--color-bookub)]",
|
|
83
|
+
payub: "bg-[var(--color-payub)]",
|
|
84
|
+
founderub: "bg-[var(--color-founderub)]"
|
|
85
|
+
};
|
|
86
|
+
const dotSizes = { sm: "size-1.5", md: "size-2", lg: "size-2.5" };
|
|
75
87
|
const Badge = React.forwardRef(
|
|
76
|
-
({ className, size, variant = "solid", tone = "neutral", leadingIcon, children, ...props }, ref) => {
|
|
88
|
+
({ className, size, variant = "solid", tone = "neutral", leadingIcon, dot, count, max = 99, children, ...props }, ref) => {
|
|
77
89
|
const resolvedVariant = variant ?? "solid";
|
|
78
90
|
const toneStyle = toneClasses[tone][resolvedVariant];
|
|
91
|
+
const dotSize = dotSizes[size ?? "md"];
|
|
92
|
+
if (dot && children == null && count == null) {
|
|
93
|
+
const labelled = props["aria-label"] != null || props["aria-labelledby"] != null;
|
|
94
|
+
return /* @__PURE__ */ jsx(
|
|
95
|
+
"span",
|
|
96
|
+
{
|
|
97
|
+
ref,
|
|
98
|
+
role: labelled ? "img" : void 0,
|
|
99
|
+
"aria-hidden": labelled ? void 0 : true,
|
|
100
|
+
className: cn("inline-block shrink-0 rounded-full", dotSize, dotClasses[tone], className),
|
|
101
|
+
...props
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const capped = count != null && count > max;
|
|
79
106
|
return /* @__PURE__ */ jsxs(
|
|
80
107
|
"span",
|
|
81
108
|
{
|
|
@@ -83,8 +110,19 @@ const Badge = React.forwardRef(
|
|
|
83
110
|
className: cn(badgeVariants({ size, variant: resolvedVariant }), toneStyle, className),
|
|
84
111
|
...props,
|
|
85
112
|
children: [
|
|
113
|
+
dot && /* @__PURE__ */ jsx(
|
|
114
|
+
"span",
|
|
115
|
+
{
|
|
116
|
+
"aria-hidden": "true",
|
|
117
|
+
className: cn("shrink-0 rounded-full", dotSize, resolvedVariant === "solid" ? "bg-current" : dotClasses[tone])
|
|
118
|
+
}
|
|
119
|
+
),
|
|
86
120
|
leadingIcon && /* @__PURE__ */ jsx("span", { className: "shrink-0 inline-flex items-center", children: leadingIcon }),
|
|
87
|
-
children
|
|
121
|
+
children,
|
|
122
|
+
count != null && /* @__PURE__ */ jsxs("span", { className: "tabular-nums", children: [
|
|
123
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": capped || void 0, className: "nx-tick", children: capped ? `${max}+` : count }, count),
|
|
124
|
+
capped && /* @__PURE__ */ jsx("span", { className: "sr-only", children: count })
|
|
125
|
+
] })
|
|
88
126
|
]
|
|
89
127
|
}
|
|
90
128
|
);
|
|
@@ -4,7 +4,7 @@ import type { ToneValue } from '@nexub/core';
|
|
|
4
4
|
export type { ToneValue };
|
|
5
5
|
export { toneTextClasses } from '../../utils/control.js';
|
|
6
6
|
export declare const buttonVariants: (props?: ({
|
|
7
|
-
variant?: "accent" | "text" | "link" | "outline" | "primary" | "glass" | "
|
|
7
|
+
variant?: "accent" | "text" | "link" | "outline" | "primary" | "glass" | "filled" | "tonal" | "outlined" | "hero" | "secondary" | "subtle" | "ghost" | "destructive" | null | undefined;
|
|
8
8
|
size?: "xs" | "sm" | "md" | "lg" | "xl" | null | undefined;
|
|
9
9
|
fullWidth?: boolean | null | undefined;
|
|
10
10
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import { Command } from 'cmdk';
|
|
3
|
+
export interface ComboboxListProps extends React.ComponentProps<typeof Command.List> {
|
|
4
|
+
/** Options are loading: shows a loading row, sets aria-busy and hides Empty. */
|
|
5
|
+
loading?: boolean;
|
|
6
|
+
}
|
|
2
7
|
export declare const Combobox: {
|
|
3
8
|
Root: React.ForwardRefExoticComponent<Omit<{
|
|
4
9
|
children?: React.ReactNode;
|
|
@@ -25,15 +30,7 @@ export declare const Combobox: {
|
|
|
25
30
|
value?: string;
|
|
26
31
|
onValueChange?: (search: string) => void;
|
|
27
32
|
} & React.RefAttributes<HTMLInputElement>, "ref"> & React.RefAttributes<HTMLInputElement>>;
|
|
28
|
-
List: React.ForwardRefExoticComponent<Omit<
|
|
29
|
-
children?: React.ReactNode;
|
|
30
|
-
} & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
|
|
31
|
-
ref?: React.Ref<HTMLDivElement>;
|
|
32
|
-
} & {
|
|
33
|
-
asChild?: boolean;
|
|
34
|
-
}, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
|
|
35
|
-
label?: string;
|
|
36
|
-
} & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
33
|
+
List: React.ForwardRefExoticComponent<Omit<ComboboxListProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
37
34
|
Empty: React.ForwardRefExoticComponent<Omit<{
|
|
38
35
|
children?: React.ReactNode;
|
|
39
36
|
} & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Command } from "cmdk";
|
|
5
5
|
import { cn } from "../../utils/cn.js";
|
|
6
|
+
import { useNexubMessages } from "../NexubProvider/NexubProvider.js";
|
|
7
|
+
import { Spinner } from "../Spinner/Spinner.js";
|
|
6
8
|
function scrollWithinList(el) {
|
|
7
9
|
if (!el) return;
|
|
8
10
|
el.scrollIntoView = () => {
|
|
@@ -47,14 +49,33 @@ const Input = React.forwardRef(
|
|
|
47
49
|
);
|
|
48
50
|
Input.displayName = "Combobox.Input";
|
|
49
51
|
const List = React.forwardRef(
|
|
50
|
-
({ className, ...props }, ref) =>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
({ className, loading, children, ...props }, ref) => {
|
|
53
|
+
const messages = useNexubMessages();
|
|
54
|
+
return /* @__PURE__ */ jsxs(
|
|
55
|
+
Command.List,
|
|
56
|
+
{
|
|
57
|
+
ref,
|
|
58
|
+
"aria-busy": loading || void 0,
|
|
59
|
+
className: cn(
|
|
60
|
+
// cmdk measures its content into --cmdk-list-height; box-content keeps the padding outside it.
|
|
61
|
+
"box-content h-[var(--cmdk-list-height)] max-h-64 overflow-y-auto overflow-x-hidden p-1",
|
|
62
|
+
"transition-[height] duration-spatial-default ease-spatial-default motion-reduce:transition-none",
|
|
63
|
+
loading && "[&_[cmdk-empty]]:hidden",
|
|
64
|
+
className
|
|
65
|
+
),
|
|
66
|
+
...props,
|
|
67
|
+
children: [
|
|
68
|
+
loading && // Not Command.Loading: its role="progressbar" isn't allowed inside a listbox
|
|
69
|
+
// (axe aria-required-children). The listbox's aria-busy carries the state.
|
|
70
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-2.5 py-2 text-body-sm text-[var(--color-on-surface-muted)]", children: [
|
|
71
|
+
/* @__PURE__ */ jsx(Spinner, { size: "sm", "aria-hidden": "true" }),
|
|
72
|
+
messages.loading
|
|
73
|
+
] }),
|
|
74
|
+
children
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
}
|
|
58
79
|
);
|
|
59
80
|
List.displayName = "Combobox.List";
|
|
60
81
|
const Empty = React.forwardRef(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import * as RadixDialog from '@radix-ui/react-dialog';
|
|
3
|
+
import { Command } from 'cmdk';
|
|
3
4
|
export interface CommandPaletteRootProps extends RadixDialog.DialogProps {
|
|
4
5
|
className?: string;
|
|
5
6
|
/** Accessible name of the dialog. @default 'Command palette' */
|
|
@@ -17,6 +18,36 @@ export interface CommandPaletteRootProps extends RadixDialog.DialogProps {
|
|
|
17
18
|
* the page, e.g. `'k'` for Cmd+K / Ctrl+K. Off by default.
|
|
18
19
|
*/
|
|
19
20
|
shortcut?: string;
|
|
21
|
+
/** The open page stack (ids of `CommandPalette.Page`s), deepest last. Empty = the root page. */
|
|
22
|
+
pages?: string[];
|
|
23
|
+
defaultPages?: string[];
|
|
24
|
+
onPagesChange?: (pages: string[]) => void;
|
|
25
|
+
/** Shows a loading row (e.g. while async results arrive) and hides `Empty`. */
|
|
26
|
+
loading?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Opt-in recents: the last `recentLimit` items picked on the root page are
|
|
29
|
+
* kept in localStorage under this key and shown as a "Recent" group while
|
|
30
|
+
* the query is empty.
|
|
31
|
+
*/
|
|
32
|
+
recentKey?: string;
|
|
33
|
+
/** @default 5 */
|
|
34
|
+
recentLimit?: number;
|
|
35
|
+
}
|
|
36
|
+
export interface CommandPaletteItemProps extends React.ComponentProps<typeof Command.Item> {
|
|
37
|
+
/** Opens this `CommandPalette.Page` (by id) when selected. */
|
|
38
|
+
page?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface CommandPalettePageProps {
|
|
41
|
+
/** Pushed by `<CommandPalette.Item page="id">`. Leave out for the root page. */
|
|
42
|
+
id?: string;
|
|
43
|
+
/** Shown in the input's breadcrumb. Defaults to the id. */
|
|
44
|
+
title?: string;
|
|
45
|
+
children?: React.ReactNode;
|
|
46
|
+
}
|
|
47
|
+
/** Content shown only while this page is on top of the stack (no id: the root page). */
|
|
48
|
+
declare function Page({ id, title, children }: CommandPalettePageProps): React.JSX.Element | null;
|
|
49
|
+
declare namespace Page {
|
|
50
|
+
var displayName: string;
|
|
20
51
|
}
|
|
21
52
|
export declare const CommandPalette: {
|
|
22
53
|
Root: React.ForwardRefExoticComponent<CommandPaletteRootProps & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -55,19 +86,7 @@ export declare const CommandPalette: {
|
|
|
55
86
|
value?: string;
|
|
56
87
|
forceMount?: boolean;
|
|
57
88
|
} & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
58
|
-
Item: React.ForwardRefExoticComponent<Omit<
|
|
59
|
-
children?: React.ReactNode;
|
|
60
|
-
} & Omit<Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
|
|
61
|
-
ref?: React.Ref<HTMLDivElement>;
|
|
62
|
-
} & {
|
|
63
|
-
asChild?: boolean;
|
|
64
|
-
}, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>>, "value" | "disabled" | "onSelect"> & {
|
|
65
|
-
disabled?: boolean;
|
|
66
|
-
onSelect?: (value: string) => void;
|
|
67
|
-
value?: string;
|
|
68
|
-
keywords?: string[];
|
|
69
|
-
forceMount?: boolean;
|
|
70
|
-
} & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
89
|
+
Item: React.ForwardRefExoticComponent<Omit<CommandPaletteItemProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
71
90
|
Separator: React.ForwardRefExoticComponent<Omit<Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
|
|
72
91
|
ref?: React.Ref<HTMLDivElement>;
|
|
73
92
|
} & {
|
|
@@ -75,4 +94,6 @@ export declare const CommandPalette: {
|
|
|
75
94
|
}, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
|
|
76
95
|
alwaysRender?: boolean;
|
|
77
96
|
} & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
97
|
+
Page: typeof Page;
|
|
78
98
|
};
|
|
99
|
+
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import * as RadixDialog from "@radix-ui/react-dialog";
|
|
5
|
-
import { Command } from "cmdk";
|
|
5
|
+
import { Command, useCommandState } from "cmdk";
|
|
6
6
|
import { Search } from "@nexub/icons";
|
|
7
7
|
import { cn } from "../../utils/cn.js";
|
|
8
8
|
import { useNexubMessages } from "../NexubProvider/NexubProvider.js";
|
|
@@ -10,15 +10,72 @@ import { overlaySurface, scrim } from "../../utils/surface.js";
|
|
|
10
10
|
import { useClassScrollLock } from "../../hooks/useClassScrollLock.js";
|
|
11
11
|
import { useControllableState } from "../../hooks/useControllableState.js";
|
|
12
12
|
import { useMergedRefs } from "../../hooks/useMergedRefs.js";
|
|
13
|
+
import { useIsomorphicLayoutEffect } from "../../hooks/useIsomorphicLayoutEffect.js";
|
|
14
|
+
import { Badge } from "../Badge/Badge.js";
|
|
15
|
+
import { Spinner } from "../Spinner/Spinner.js";
|
|
16
|
+
const PaletteContext = React.createContext(null);
|
|
17
|
+
const usePalette = () => React.useContext(PaletteContext);
|
|
18
|
+
function readRecents(key) {
|
|
19
|
+
try {
|
|
20
|
+
const parsed = JSON.parse(localStorage.getItem(key) ?? "[]");
|
|
21
|
+
return Array.isArray(parsed) ? parsed.filter((r) => typeof r?.value === "string" && typeof r?.label === "string") : [];
|
|
22
|
+
} catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
13
26
|
const overlayClasses = cn("fixed inset-0 z-[var(--z-modal)] nx-motion", scrim);
|
|
14
27
|
function ClassLockOverlay() {
|
|
15
28
|
useClassScrollLock();
|
|
16
29
|
return /* @__PURE__ */ jsx("div", { "data-state": "open", className: overlayClasses });
|
|
17
30
|
}
|
|
18
31
|
const Root = React.forwardRef(
|
|
19
|
-
({
|
|
32
|
+
({
|
|
33
|
+
children,
|
|
34
|
+
label: labelProp,
|
|
35
|
+
scrollLock = "style",
|
|
36
|
+
shouldFilter,
|
|
37
|
+
shortcut,
|
|
38
|
+
open: openProp,
|
|
39
|
+
defaultOpen,
|
|
40
|
+
onOpenChange,
|
|
41
|
+
pages: pagesProp,
|
|
42
|
+
defaultPages,
|
|
43
|
+
onPagesChange,
|
|
44
|
+
loading,
|
|
45
|
+
recentKey,
|
|
46
|
+
recentLimit = 5,
|
|
47
|
+
...props
|
|
48
|
+
}, ref) => {
|
|
20
49
|
const messages = useNexubMessages();
|
|
21
|
-
const [
|
|
50
|
+
const [pages = [], setPages] = useControllableState({ value: pagesProp, defaultValue: defaultPages, onChange: onPagesChange });
|
|
51
|
+
const [open = false, setOpenState] = useControllableState({ value: openProp, defaultValue: defaultOpen, onChange: onOpenChange });
|
|
52
|
+
const setOpen = (next) => {
|
|
53
|
+
setOpenState(next);
|
|
54
|
+
if (!next && pages.length) setPages([]);
|
|
55
|
+
};
|
|
56
|
+
const [titles, setTitles] = React.useState({});
|
|
57
|
+
const [recents, setRecents] = React.useState([]);
|
|
58
|
+
const handlers = React.useRef(/* @__PURE__ */ new Map()).current;
|
|
59
|
+
React.useEffect(() => setRecents(recentKey ? readRecents(recentKey) : []), [recentKey]);
|
|
60
|
+
const ctx = {
|
|
61
|
+
pages,
|
|
62
|
+
push: (page) => setPages([...pages, page]),
|
|
63
|
+
pop: () => setPages(pages.slice(0, -1)),
|
|
64
|
+
titles,
|
|
65
|
+
setTitle: (page, title) => setTitles((t) => t[page] === title ? t : { ...t, [page]: title }),
|
|
66
|
+
loading,
|
|
67
|
+
recents,
|
|
68
|
+
record: (recent) => {
|
|
69
|
+
if (!recentKey || pages.length) return;
|
|
70
|
+
const next = [recent, ...recents.filter((r) => r.value !== recent.value)].slice(0, recentLimit);
|
|
71
|
+
setRecents(next);
|
|
72
|
+
try {
|
|
73
|
+
localStorage.setItem(recentKey, JSON.stringify(next));
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
handlers
|
|
78
|
+
};
|
|
22
79
|
React.useEffect(() => {
|
|
23
80
|
if (!shortcut) return;
|
|
24
81
|
const onKeyDown = (e) => {
|
|
@@ -60,7 +117,7 @@ const Root = React.forwardRef(
|
|
|
60
117
|
shouldFilter,
|
|
61
118
|
loop: true,
|
|
62
119
|
className: "flex h-full w-full flex-col overflow-hidden text-[var(--color-on-surface)]",
|
|
63
|
-
children
|
|
120
|
+
children: /* @__PURE__ */ jsx(PaletteContext.Provider, { value: ctx, children })
|
|
64
121
|
}
|
|
65
122
|
)
|
|
66
123
|
]
|
|
@@ -70,31 +127,75 @@ const Root = React.forwardRef(
|
|
|
70
127
|
}
|
|
71
128
|
);
|
|
72
129
|
Root.displayName = "CommandPalette.Root";
|
|
73
|
-
const Input = React.forwardRef(({ className, ...props }, ref) =>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
130
|
+
const Input = React.forwardRef(({ className, value: valueProp, onValueChange, onKeyDown, ...props }, ref) => {
|
|
131
|
+
const palette = usePalette();
|
|
132
|
+
const pages = palette?.pages ?? [];
|
|
133
|
+
const [value = "", setValue] = useControllableState({ value: valueProp, defaultValue: "", onChange: onValueChange });
|
|
134
|
+
const depth = pages.length;
|
|
135
|
+
const first = React.useRef(true);
|
|
136
|
+
React.useEffect(() => {
|
|
137
|
+
if (first.current) first.current = false;
|
|
138
|
+
else setValue("");
|
|
139
|
+
}, [depth]);
|
|
140
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 border-b border-[var(--color-outline-variant)] px-3", children: [
|
|
141
|
+
/* @__PURE__ */ jsx(Search, { size: "sm", className: "me-1 shrink-0 opacity-50", "aria-hidden": "true" }),
|
|
142
|
+
pages.map((page) => /* @__PURE__ */ jsx(Badge, { variant: "soft", size: "sm", className: "shrink-0", children: palette?.titles[page] ?? page }, page)),
|
|
143
|
+
/* @__PURE__ */ jsx(
|
|
144
|
+
Command.Input,
|
|
145
|
+
{
|
|
146
|
+
ref,
|
|
147
|
+
value,
|
|
148
|
+
onValueChange: setValue,
|
|
149
|
+
onKeyDown: (e) => {
|
|
150
|
+
onKeyDown?.(e);
|
|
151
|
+
if (!e.defaultPrevented && e.key === "Backspace" && !e.currentTarget.value && depth) {
|
|
152
|
+
e.preventDefault();
|
|
153
|
+
palette?.pop();
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
className: cn(
|
|
157
|
+
"ms-1 flex h-control-xl w-full bg-transparent text-body-lg outline-none placeholder:text-[var(--color-on-surface-muted)] disabled:cursor-not-allowed disabled:text-[var(--color-text-disabled)]",
|
|
158
|
+
className
|
|
159
|
+
),
|
|
160
|
+
...props
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
] });
|
|
164
|
+
});
|
|
165
|
+
Input.displayName = "CommandPalette.Input";
|
|
166
|
+
const List = React.forwardRef(({ className, children, ...props }, ref) => {
|
|
167
|
+
const palette = usePalette();
|
|
168
|
+
const messages = useNexubMessages();
|
|
169
|
+
const search = useCommandState((state) => state.search);
|
|
170
|
+
const showRecents = palette && !palette.loading && !palette.pages.length && !search && palette.recents.length > 0;
|
|
171
|
+
return /* @__PURE__ */ jsxs(
|
|
172
|
+
Command.List,
|
|
77
173
|
{
|
|
78
174
|
ref,
|
|
79
|
-
className: cn(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
175
|
+
className: cn("max-h-[400px] overflow-y-auto overflow-x-hidden p-2", className),
|
|
176
|
+
...props,
|
|
177
|
+
children: [
|
|
178
|
+
palette?.loading && /* @__PURE__ */ jsxs(Command.Loading, { label: messages.loading, className: cn(itemClasses, "text-[var(--color-on-surface-muted)]"), children: [
|
|
179
|
+
/* @__PURE__ */ jsx(Spinner, { size: "sm", "aria-hidden": "true" }),
|
|
180
|
+
messages.loading
|
|
181
|
+
] }),
|
|
182
|
+
showRecents && /* @__PURE__ */ jsx(Group, { heading: messages.recent, children: palette.recents.map((r) => /* @__PURE__ */ jsx(
|
|
183
|
+
Command.Item,
|
|
184
|
+
{
|
|
185
|
+
value: `recent:${r.value}`,
|
|
186
|
+
onSelect: () => palette.handlers.get(r.value)?.(r.value),
|
|
187
|
+
className: itemClasses,
|
|
188
|
+
children: r.label
|
|
189
|
+
},
|
|
190
|
+
r.value
|
|
191
|
+
)) }),
|
|
192
|
+
children
|
|
193
|
+
]
|
|
84
194
|
}
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
Input.displayName = "CommandPalette.Input";
|
|
88
|
-
const List = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
89
|
-
Command.List,
|
|
90
|
-
{
|
|
91
|
-
ref,
|
|
92
|
-
className: cn("max-h-[400px] overflow-y-auto overflow-x-hidden p-2", className),
|
|
93
|
-
...props
|
|
94
|
-
}
|
|
95
|
-
));
|
|
195
|
+
);
|
|
196
|
+
});
|
|
96
197
|
List.displayName = "CommandPalette.List";
|
|
97
|
-
const Empty = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
198
|
+
const Empty = React.forwardRef(({ className, ...props }, ref) => usePalette()?.loading ? null : /* @__PURE__ */ jsx(
|
|
98
199
|
Command.Empty,
|
|
99
200
|
{
|
|
100
201
|
ref,
|
|
@@ -106,6 +207,11 @@ const Empty = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */
|
|
|
106
207
|
}
|
|
107
208
|
));
|
|
108
209
|
Empty.displayName = "CommandPalette.Empty";
|
|
210
|
+
const itemClasses = cn(
|
|
211
|
+
"relative flex cursor-default select-none items-center gap-2 rounded-shape-container px-3 py-2.5 text-body-sm outline-none",
|
|
212
|
+
"aria-selected:bg-[color-mix(in_srgb,var(--color-on-surface)_8%,transparent)]",
|
|
213
|
+
"data-[disabled=true]:pointer-events-none data-[disabled=true]:text-[var(--color-text-disabled)]"
|
|
214
|
+
);
|
|
109
215
|
const Group = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
110
216
|
Command.Group,
|
|
111
217
|
{
|
|
@@ -119,20 +225,37 @@ const Group = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */
|
|
|
119
225
|
}
|
|
120
226
|
));
|
|
121
227
|
Group.displayName = "CommandPalette.Group";
|
|
122
|
-
const Item = React.forwardRef(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
228
|
+
const Item = React.forwardRef(
|
|
229
|
+
({ className, page, onSelect, ...props }, ref) => {
|
|
230
|
+
const palette = usePalette();
|
|
231
|
+
const local = React.useRef(null);
|
|
232
|
+
const select = (value) => {
|
|
233
|
+
onSelect?.(value);
|
|
234
|
+
if (page) palette?.push(page);
|
|
235
|
+
palette?.record({ value, label: local.current?.textContent?.trim() ?? value });
|
|
236
|
+
};
|
|
237
|
+
const selectRef = React.useRef(select);
|
|
238
|
+
selectRef.current = select;
|
|
239
|
+
React.useEffect(() => {
|
|
240
|
+
const value = local.current?.getAttribute("data-value");
|
|
241
|
+
if (!palette || !value) return;
|
|
242
|
+
const run = (v) => selectRef.current(v);
|
|
243
|
+
palette.handlers.set(value, run);
|
|
244
|
+
return () => {
|
|
245
|
+
if (palette.handlers.get(value) === run) palette.handlers.delete(value);
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
return /* @__PURE__ */ jsx(
|
|
249
|
+
Command.Item,
|
|
250
|
+
{
|
|
251
|
+
ref: useMergedRefs(ref, local),
|
|
252
|
+
onSelect: select,
|
|
253
|
+
className: cn(itemClasses, className),
|
|
254
|
+
...props
|
|
255
|
+
}
|
|
256
|
+
);
|
|
134
257
|
}
|
|
135
|
-
)
|
|
258
|
+
);
|
|
136
259
|
Item.displayName = "CommandPalette.Item";
|
|
137
260
|
const Separator = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
138
261
|
Command.Separator,
|
|
@@ -143,7 +266,18 @@ const Separator = React.forwardRef(({ className, ...props }, ref) => /* @__PURE_
|
|
|
143
266
|
}
|
|
144
267
|
));
|
|
145
268
|
Separator.displayName = "CommandPalette.Separator";
|
|
146
|
-
|
|
269
|
+
function Page({ id, title, children }) {
|
|
270
|
+
const palette = usePalette();
|
|
271
|
+
const setTitle = palette?.setTitle;
|
|
272
|
+
useIsomorphicLayoutEffect(() => {
|
|
273
|
+
if (id && title) setTitle?.(id, title);
|
|
274
|
+
}, [id, title]);
|
|
275
|
+
const pages = palette?.pages ?? [];
|
|
276
|
+
const active = id ? pages[pages.length - 1] === id : pages.length === 0;
|
|
277
|
+
return active ? /* @__PURE__ */ jsx(Fragment, { children }) : null;
|
|
278
|
+
}
|
|
279
|
+
Page.displayName = "CommandPalette.Page";
|
|
280
|
+
const CommandPalette = { Root, Input, List, Empty, Group, Item, Separator, Page };
|
|
147
281
|
export {
|
|
148
282
|
CommandPalette
|
|
149
283
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { CommandPalette } from './CommandPalette.js';
|
|
2
|
-
export type { CommandPaletteRootProps } from './CommandPalette.js';
|
|
2
|
+
export type { CommandPaletteRootProps, CommandPaletteItemProps, CommandPalettePageProps } from './CommandPalette.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { type ChevronProps, type Labels } from 'react-day-picker';
|
|
2
|
+
import { type ChevronProps, type Labels, type Matcher } from 'react-day-picker';
|
|
3
3
|
import { type Locale } from 'date-fns';
|
|
4
4
|
/**
|
|
5
5
|
* A date-fns Locale wins (date-fns 'PP'); else with a NexubProvider locale,
|
|
@@ -34,6 +34,21 @@ export interface DatePickerProps {
|
|
|
34
34
|
'aria-label'?: string;
|
|
35
35
|
/** date-fns Locale for the calendar and trigger text; wins over NexubProvider's `dateLocale`. */
|
|
36
36
|
locale?: Locale;
|
|
37
|
+
/** Days that can't be picked (react-day-picker Matcher), e.g. `{ dayOfWeek: [0, 6] }`. */
|
|
38
|
+
disabledDates?: Matcher | Matcher[];
|
|
39
|
+
/** Quick picks under the calendar (Today, In a week…); choosing one sets the value and closes. */
|
|
40
|
+
presets?: {
|
|
41
|
+
label: string;
|
|
42
|
+
date: Date;
|
|
43
|
+
}[];
|
|
44
|
+
/**
|
|
45
|
+
* The trigger is a text input that also accepts a typed date in the locale's
|
|
46
|
+
* short format (date-fns `P`, e.g. 01/15/2024). Enter or blur commits; an
|
|
47
|
+
* invalid or unavailable date stays as typed, marked aria-invalid, and the
|
|
48
|
+
* value is unchanged. `id` and `aria-label` go to the input, `ref` to the
|
|
49
|
+
* calendar button.
|
|
50
|
+
*/
|
|
51
|
+
typeable?: boolean;
|
|
37
52
|
}
|
|
38
53
|
export declare const DatePicker: React.ForwardRefExoticComponent<DatePickerProps & React.RefAttributes<HTMLButtonElement>>;
|
|
39
54
|
export {};
|