@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.
Files changed (37) hide show
  1. package/accessibility.json +1 -1
  2. package/dist/components/Badge/Badge.d.ts +9 -0
  3. package/dist/components/Badge/Badge.js +40 -2
  4. package/dist/components/Button/Button.d.ts +1 -1
  5. package/dist/components/Combobox/Combobox.d.ts +6 -9
  6. package/dist/components/Combobox/Combobox.js +30 -9
  7. package/dist/components/CommandPalette/CommandPalette.d.ts +34 -13
  8. package/dist/components/CommandPalette/CommandPalette.js +174 -40
  9. package/dist/components/CommandPalette/index.d.ts +1 -1
  10. package/dist/components/DatePicker/DatePicker.d.ts +16 -1
  11. package/dist/components/DatePicker/DatePicker.js +100 -26
  12. package/dist/components/Dialog/Dialog.d.ts +7 -0
  13. package/dist/components/Dialog/Dialog.js +35 -8
  14. package/dist/components/EmptyState/EmptyState.d.ts +7 -1
  15. package/dist/components/EmptyState/EmptyState.js +21 -5
  16. package/dist/components/EmptyState/EmptyStatePreset.d.ts +2 -1
  17. package/dist/components/FileUpload/FileUpload.js +86 -32
  18. package/dist/components/Heading/Heading.d.ts +1 -1
  19. package/dist/components/IconButton/IconButton.d.ts +5 -1
  20. package/dist/components/IconButton/IconButton.js +14 -3
  21. package/dist/components/Input/Input.d.ts +6 -0
  22. package/dist/components/Input/Input.js +47 -5
  23. package/dist/components/Select/Select.d.ts +18 -3
  24. package/dist/components/Select/Select.js +170 -71
  25. package/dist/components/Slider/Slider.d.ts +10 -0
  26. package/dist/components/Slider/Slider.js +48 -3
  27. package/dist/components/Slider/index.d.ts +1 -1
  28. package/dist/components/Switch/Switch.d.ts +6 -0
  29. package/dist/components/Switch/Switch.js +29 -2
  30. package/dist/components/Tabs/Tabs.d.ts +5 -1
  31. package/dist/components/Tabs/Tabs.js +98 -35
  32. package/dist/components/Text/Text.d.ts +1 -1
  33. package/dist/components/Tooltip/Tooltip.d.ts +10 -3
  34. package/dist/components/Tooltip/Tooltip.js +16 -4
  35. package/dist/index.d.ts +3 -1
  36. package/dist/styles.css +1 -1
  37. package/package.json +6 -6
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "7.4.0",
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" | "ghost" | "subtle" | "filled" | "tonal" | "outlined" | "hero" | "secondary" | "destructive" | null | undefined;
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) => /* @__PURE__ */ jsx(
51
- Command.List,
52
- {
53
- ref,
54
- className: cn("max-h-64 overflow-y-auto overflow-x-hidden p-1", className),
55
- ...props
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
- ({ children, label: labelProp, scrollLock = "style", shouldFilter, shortcut, open: openProp, defaultOpen, onOpenChange, ...props }, ref) => {
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 [open = false, setOpen] = useControllableState({ value: openProp, defaultValue: defaultOpen, onChange: onOpenChange });
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) => /* @__PURE__ */ jsxs("div", { className: "flex items-center border-b border-[var(--color-outline-variant)] px-3", children: [
74
- /* @__PURE__ */ jsx(Search, { size: "sm", className: "me-2 shrink-0 opacity-50", "aria-hidden": "true" }),
75
- /* @__PURE__ */ jsx(
76
- Command.Input,
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
- "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)]",
81
- className
82
- ),
83
- ...props
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(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
123
- Command.Item,
124
- {
125
- ref,
126
- className: cn(
127
- // Concentric: overlay 24px − 8px padding = 16px.
128
- "relative flex cursor-default select-none items-center gap-2 rounded-shape-container px-3 py-2.5 text-body-sm outline-none",
129
- "aria-selected:bg-[color-mix(in_srgb,var(--color-on-surface)_8%,transparent)]",
130
- "data-[disabled=true]:pointer-events-none data-[disabled=true]:text-[var(--color-text-disabled)]",
131
- className
132
- ),
133
- ...props
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
- const CommandPalette = { Root, Input, List, Empty, Group, Item, Separator };
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 {};