@nexub/react 7.5.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/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/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/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/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 +2 -0
- 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,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';
|
|
@@ -15,6 +15,12 @@ declare function Root({ scrollLock, modal, ...props }: DialogRootProps): React.J
|
|
|
15
15
|
interface ContentProps extends RadixDialog.DialogContentProps {
|
|
16
16
|
size?: Size | 'xl' | 'full';
|
|
17
17
|
surface?: SurfaceValue;
|
|
18
|
+
/** Adds a close button in the top-end corner. @default false */
|
|
19
|
+
showClose?: boolean;
|
|
20
|
+
/** Accessible name of the close button. @default messages.close */
|
|
21
|
+
closeLabel?: string;
|
|
22
|
+
/** `true`: bottom sheet. `'auto'`: bottom sheet below the `md` breakpoint, centred above. @default false */
|
|
23
|
+
sheet?: boolean | 'auto';
|
|
18
24
|
}
|
|
19
25
|
export declare const Dialog: {
|
|
20
26
|
Root: typeof Root;
|
|
@@ -26,6 +32,7 @@ export declare const Dialog: {
|
|
|
26
32
|
Title: React.ForwardRefExoticComponent<RadixDialog.DialogTitleProps & React.RefAttributes<HTMLHeadingElement>>;
|
|
27
33
|
Description: React.ForwardRefExoticComponent<RadixDialog.DialogDescriptionProps & React.RefAttributes<HTMLParagraphElement>>;
|
|
28
34
|
Header: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
35
|
+
Body: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
29
36
|
Footer: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
30
37
|
};
|
|
31
38
|
export {};
|
|
@@ -1,11 +1,15 @@
|
|
|
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 * as RadixDialog from "@radix-ui/react-dialog";
|
|
5
5
|
import { cn } from "../../utils/cn.js";
|
|
6
6
|
import { overlaySurface, scrim } from "../../utils/surface.js";
|
|
7
7
|
import { glassSurfaceClass } from "../../utils/glass.js";
|
|
8
8
|
import { ClassLockOverlay, ScrollLockContext } from "../../hooks/useClassScrollLock.js";
|
|
9
|
+
import { useMediaQuery } from "../../hooks/useMediaQuery.js";
|
|
10
|
+
import { X } from "@nexub/icons";
|
|
11
|
+
import { IconButton } from "../IconButton/IconButton.js";
|
|
12
|
+
import { useNexubMessages } from "../NexubProvider/NexubProvider.js";
|
|
9
13
|
function Root({ scrollLock = "style", modal = true, ...props }) {
|
|
10
14
|
const lock = React.useMemo(() => ({ scrollLock, modal }), [scrollLock, modal]);
|
|
11
15
|
return /* @__PURE__ */ jsx(ScrollLockContext.Provider, { value: lock, children: /* @__PURE__ */ jsx(RadixDialog.Root, { modal, ...props }) });
|
|
@@ -33,8 +37,19 @@ const Overlay = React.forwardRef(
|
|
|
33
37
|
}
|
|
34
38
|
);
|
|
35
39
|
Overlay.displayName = "Dialog.Overlay";
|
|
40
|
+
const StackContext = React.createContext(null);
|
|
36
41
|
const Content = React.forwardRef(
|
|
37
|
-
function DialogContent({ className, size = "md", surface = "solid", children, ...props }, ref) {
|
|
42
|
+
function DialogContent({ className, size = "md", surface = "solid", showClose = false, closeLabel, sheet = false, children, ...props }, ref) {
|
|
43
|
+
const messages = useNexubMessages();
|
|
44
|
+
const narrow = useMediaQuery("(max-width: 47.99rem)");
|
|
45
|
+
const asSheet = sheet === true || sheet === "auto" && narrow;
|
|
46
|
+
const parent = React.useContext(StackContext);
|
|
47
|
+
const [openChildren, setOpenChildren] = React.useState(0);
|
|
48
|
+
React.useEffect(() => {
|
|
49
|
+
if (!parent) return;
|
|
50
|
+
parent((n) => n + 1);
|
|
51
|
+
return () => parent((n) => n - 1);
|
|
52
|
+
}, [parent]);
|
|
38
53
|
const sizeMap = {
|
|
39
54
|
sm: "max-w-sm",
|
|
40
55
|
md: "max-w-lg",
|
|
@@ -42,23 +57,28 @@ const Content = React.forwardRef(
|
|
|
42
57
|
xl: "max-w-4xl",
|
|
43
58
|
full: "max-w-[calc(100vw-2rem)]"
|
|
44
59
|
};
|
|
45
|
-
return /* @__PURE__ */
|
|
60
|
+
return /* @__PURE__ */ jsxs(
|
|
46
61
|
RadixDialog.Content,
|
|
47
62
|
{
|
|
48
63
|
ref,
|
|
49
64
|
className: cn(
|
|
50
|
-
"fixed left-1/2 top-1/2 z-[var(--z-modal)] w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2",
|
|
65
|
+
asSheet ? "fixed inset-x-0 bottom-0 z-[var(--z-modal)] w-full" : "fixed left-1/2 top-1/2 z-[var(--z-modal)] w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2",
|
|
51
66
|
"nx-motion nx-dialog",
|
|
52
67
|
overlaySurface,
|
|
53
68
|
"max-h-[90vh] overflow-y-auto p-6 focus:outline-none",
|
|
54
|
-
sizeMap[size],
|
|
69
|
+
asSheet ? "rounded-b-none" : sizeMap[size],
|
|
55
70
|
surface === "glass" && glassSurfaceClass,
|
|
56
71
|
className
|
|
57
72
|
),
|
|
58
73
|
"data-material": surface === "glass" ? "glass" : void 0,
|
|
59
74
|
"data-glass-thickness": surface === "glass" ? "thick" : void 0,
|
|
75
|
+
"data-sheet": asSheet ? "" : void 0,
|
|
76
|
+
"data-nested": openChildren > 0 ? "" : void 0,
|
|
60
77
|
...props,
|
|
61
|
-
children
|
|
78
|
+
children: [
|
|
79
|
+
/* @__PURE__ */ jsx(StackContext.Provider, { value: setOpenChildren, children }),
|
|
80
|
+
showClose && /* @__PURE__ */ jsx(RadixDialog.Close, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { label: closeLabel ?? messages.close, variant: "ghost", size: "sm", className: "absolute end-3 top-3", children: /* @__PURE__ */ jsx(X, { size: "sm", "aria-hidden": "true" }) }) })
|
|
81
|
+
]
|
|
62
82
|
}
|
|
63
83
|
);
|
|
64
84
|
}
|
|
@@ -92,16 +112,22 @@ const Description = React.forwardRef(
|
|
|
92
112
|
Description.displayName = "Dialog.Description";
|
|
93
113
|
const Header = React.forwardRef(
|
|
94
114
|
function DialogHeader({ className, ...props }, ref) {
|
|
95
|
-
return /* @__PURE__ */ jsx("div", { ref, className: cn("mb-4 flex flex-col gap-1", className), ...props });
|
|
115
|
+
return /* @__PURE__ */ jsx("div", { ref, className: cn("nx-dialog-header mb-4 flex flex-col gap-1", className), ...props });
|
|
96
116
|
}
|
|
97
117
|
);
|
|
98
118
|
Header.displayName = "Dialog.Header";
|
|
99
119
|
const Footer = React.forwardRef(
|
|
100
120
|
function DialogFooter({ className, ...props }, ref) {
|
|
101
|
-
return /* @__PURE__ */ jsx("div", { ref, className: cn("mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className), ...props });
|
|
121
|
+
return /* @__PURE__ */ jsx("div", { ref, className: cn("nx-dialog-footer mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className), ...props });
|
|
102
122
|
}
|
|
103
123
|
);
|
|
104
124
|
Footer.displayName = "Dialog.Footer";
|
|
125
|
+
const Body = React.forwardRef(
|
|
126
|
+
function DialogBody({ className, ...props }, ref) {
|
|
127
|
+
return /* @__PURE__ */ jsx("div", { ref, className: cn("nx-dialog-body", className), ...props });
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
Body.displayName = "Dialog.Body";
|
|
105
131
|
const Dialog = {
|
|
106
132
|
Root,
|
|
107
133
|
Trigger,
|
|
@@ -112,6 +138,7 @@ const Dialog = {
|
|
|
112
138
|
Title,
|
|
113
139
|
Description,
|
|
114
140
|
Header,
|
|
141
|
+
Body,
|
|
115
142
|
Footer
|
|
116
143
|
};
|
|
117
144
|
export {
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
export interface EmptyStateRootProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
/** Scales spacing, icon and text. @default 'md' */
|
|
4
|
+
size?: 'sm' | 'md' | 'lg';
|
|
5
|
+
/** Fades in on mount (nx-reveal; reduced motion keeps the crossfade). */
|
|
6
|
+
animate?: boolean;
|
|
7
|
+
}
|
|
2
8
|
export declare const EmptyState: {
|
|
3
|
-
Root: React.ForwardRefExoticComponent<
|
|
9
|
+
Root: React.ForwardRefExoticComponent<EmptyStateRootProps & React.RefAttributes<HTMLDivElement>>;
|
|
4
10
|
Illustration: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
5
11
|
Title: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLHeadingElement> & React.RefAttributes<HTMLHeadingElement>>;
|
|
6
12
|
Description: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLParagraphElement> & React.RefAttributes<HTMLParagraphElement>>;
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cn } from "../../utils/cn.js";
|
|
4
|
+
const rootSizes = { sm: "gap-3 py-8", md: "gap-4 py-16", lg: "gap-6 py-24" };
|
|
4
5
|
const Root = React.forwardRef(
|
|
5
|
-
({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
6
|
+
({ className, size = "md", animate, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
6
7
|
"div",
|
|
7
8
|
{
|
|
8
9
|
ref,
|
|
10
|
+
"data-size": size,
|
|
9
11
|
className: cn(
|
|
10
|
-
"flex flex-col items-center justify-center
|
|
12
|
+
"group/empty-state flex flex-col items-center justify-center text-center",
|
|
13
|
+
rootSizes[size],
|
|
14
|
+
animate && "nx-reveal",
|
|
11
15
|
className
|
|
12
16
|
),
|
|
13
17
|
...props
|
|
@@ -20,7 +24,11 @@ const Illustration = React.forwardRef(
|
|
|
20
24
|
"div",
|
|
21
25
|
{
|
|
22
26
|
ref,
|
|
23
|
-
className: cn(
|
|
27
|
+
className: cn(
|
|
28
|
+
"text-[var(--color-on-surface-muted)]",
|
|
29
|
+
"group-data-[size=sm]/empty-state:[&_svg]:size-8 group-data-[size=lg]/empty-state:[&_svg]:size-16",
|
|
30
|
+
className
|
|
31
|
+
),
|
|
24
32
|
...props
|
|
25
33
|
}
|
|
26
34
|
)
|
|
@@ -31,7 +39,11 @@ const Title = React.forwardRef(
|
|
|
31
39
|
"h3",
|
|
32
40
|
{
|
|
33
41
|
ref,
|
|
34
|
-
className: cn(
|
|
42
|
+
className: cn(
|
|
43
|
+
"text-lg font-semibold text-[var(--color-on-surface)]",
|
|
44
|
+
"group-data-[size=sm]/empty-state:text-base group-data-[size=lg]/empty-state:text-xl",
|
|
45
|
+
className
|
|
46
|
+
),
|
|
35
47
|
...props
|
|
36
48
|
}
|
|
37
49
|
)
|
|
@@ -41,7 +53,11 @@ const Description = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
|
|
|
41
53
|
"p",
|
|
42
54
|
{
|
|
43
55
|
ref,
|
|
44
|
-
className: cn(
|
|
56
|
+
className: cn(
|
|
57
|
+
"max-w-sm text-sm text-[var(--color-on-surface-muted)]",
|
|
58
|
+
"group-data-[size=sm]/empty-state:text-xs group-data-[size=lg]/empty-state:text-base",
|
|
59
|
+
className
|
|
60
|
+
),
|
|
45
61
|
...props
|
|
46
62
|
}
|
|
47
63
|
));
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import type { NexubMessages } from '@nexub/core';
|
|
3
|
+
import { type EmptyStateRootProps } from './EmptyState.js';
|
|
3
4
|
export type EmptyStateKind = keyof NexubMessages['emptyStates'];
|
|
4
|
-
export interface EmptyStatePresetProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
|
|
5
|
+
export interface EmptyStatePresetProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'>, Pick<EmptyStateRootProps, 'size' | 'animate'> {
|
|
5
6
|
/** noData, noResults, noAccess (403), serverError (500) or offline. */
|
|
6
7
|
kind: EmptyStateKind;
|
|
7
8
|
/** Overrides the preset's title; the description too. Both default to localised text. */
|