@nexub/react 5.5.0 → 5.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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "5.5.0",
2
+ "version": "5.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 on every pull request; a new violation fails the build.",
@@ -751,6 +751,19 @@
751
751
  "Under reduced motion digits change instantly."
752
752
  ]
753
753
  },
754
+ {
755
+ "slug": "otp-input",
756
+ "name": "OTP Input",
757
+ "package": "@nexub/react",
758
+ "automated": "pass",
759
+ "exceptions": [],
760
+ "keyboard": [
761
+ "Type: Fills the box and moves to the next.",
762
+ "Backspace: Clears the box, or the previous one when empty.",
763
+ "ArrowLeft / ArrowRight: Moves between boxes."
764
+ ],
765
+ "screenReader": []
766
+ },
754
767
  {
755
768
  "slug": "pagination",
756
769
  "name": "Pagination",
@@ -1,5 +1,20 @@
1
1
  import * as React from 'react';
2
2
  import * as RadixAlertDialog from '@radix-ui/react-alert-dialog';
3
+ export interface AlertDialogConfirmProps {
4
+ /** What the user must type, e.g. the resource name. Matching ignores outer spaces. */
5
+ text: string;
6
+ /** The label; defaults to "Type ‹text› to confirm". */
7
+ label?: React.ReactNode;
8
+ className?: string;
9
+ }
10
+ /**
11
+ * For critical, irreversible actions: the Action button stays disabled until
12
+ * `text` is typed. Place it inside AlertDialog.Content.
13
+ */
14
+ declare function Confirm({ text, label, className }: AlertDialogConfirmProps): React.JSX.Element;
15
+ declare namespace Confirm {
16
+ var displayName: string;
17
+ }
3
18
  export declare const AlertDialog: {
4
19
  Root: React.FC<RadixAlertDialog.AlertDialogProps>;
5
20
  Trigger: React.ForwardRefExoticComponent<RadixAlertDialog.AlertDialogTriggerProps & React.RefAttributes<HTMLButtonElement>>;
@@ -12,4 +27,6 @@ export declare const AlertDialog: {
12
27
  Footer: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
13
28
  Action: React.ForwardRefExoticComponent<RadixAlertDialog.AlertDialogActionProps & React.RefAttributes<HTMLButtonElement>>;
14
29
  Cancel: React.ForwardRefExoticComponent<RadixAlertDialog.AlertDialogCancelProps & React.RefAttributes<HTMLButtonElement>>;
30
+ Confirm: typeof Confirm;
15
31
  };
32
+ export {};
@@ -1,10 +1,13 @@
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 RadixAlertDialog from "@radix-ui/react-alert-dialog";
5
5
  import { cn } from "../../utils/cn.js";
6
6
  import { overlaySurface, scrim } from "../../utils/surface.js";
7
7
  import { buttonVariants } from "../Button/Button.js";
8
+ import { Input } from "../Input/Input.js";
9
+ import { useNexubMessages } from "../NexubProvider/NexubProvider.js";
10
+ const ConfirmContext = React.createContext(null);
8
11
  const Root = RadixAlertDialog.Root;
9
12
  const Trigger = RadixAlertDialog.Trigger;
10
13
  const Portal = RadixAlertDialog.Portal;
@@ -27,7 +30,10 @@ const Overlay = React.forwardRef(
27
30
  Overlay.displayName = "AlertDialog.Overlay";
28
31
  const Content = React.forwardRef(
29
32
  function AlertDialogContent({ className, children, ...props }, ref) {
30
- return /* @__PURE__ */ jsx(
33
+ const [required, setRequired] = React.useState(null);
34
+ const [typed, setTyped] = React.useState("");
35
+ const confirm = React.useMemo(() => ({ required, setRequired, typed, setTyped }), [required, typed]);
36
+ return /* @__PURE__ */ jsx(ConfirmContext.Provider, { value: confirm, children: /* @__PURE__ */ jsx(
31
37
  RadixAlertDialog.Content,
32
38
  {
33
39
  ref,
@@ -41,7 +47,7 @@ const Content = React.forwardRef(
41
47
  ...props,
42
48
  children
43
49
  }
44
- );
50
+ ) });
45
51
  }
46
52
  );
47
53
  Content.displayName = "AlertDialog.Content";
@@ -82,11 +88,14 @@ const Footer = React.forwardRef(
82
88
  );
83
89
  Footer.displayName = "AlertDialog.Footer";
84
90
  const Action = React.forwardRef(
85
- function AlertDialogAction({ className, ...props }, ref) {
91
+ function AlertDialogAction({ className, disabled, ...props }, ref) {
92
+ const confirm = React.useContext(ConfirmContext);
93
+ const blocked = !!confirm?.required && confirm.typed.trim() !== confirm.required;
86
94
  return /* @__PURE__ */ jsx(
87
95
  RadixAlertDialog.Action,
88
96
  {
89
97
  ref,
98
+ disabled: disabled || blocked,
90
99
  className: cn(
91
100
  // The destructive confirm: same as <Button variant="destructive">.
92
101
  buttonVariants({ variant: "destructive" }),
@@ -114,6 +123,31 @@ const Cancel = React.forwardRef(
114
123
  }
115
124
  );
116
125
  Cancel.displayName = "AlertDialog.Cancel";
126
+ function Confirm({ text, label, className }) {
127
+ const confirm = React.useContext(ConfirmContext);
128
+ const t = useNexubMessages();
129
+ const id = React.useId();
130
+ const setRequired = confirm?.setRequired;
131
+ React.useEffect(() => {
132
+ setRequired?.(text);
133
+ return () => setRequired?.(null);
134
+ }, [text, setRequired]);
135
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-2", className), children: [
136
+ /* @__PURE__ */ jsx("label", { htmlFor: id, className: "text-body-sm text-[var(--color-on-surface)]", children: label ?? t.typeToConfirm(text) }),
137
+ /* @__PURE__ */ jsx(
138
+ Input,
139
+ {
140
+ id,
141
+ value: confirm?.typed ?? "",
142
+ onChange: (e) => confirm?.setTyped(e.target.value),
143
+ autoComplete: "off",
144
+ autoCapitalize: "off",
145
+ spellCheck: false
146
+ }
147
+ )
148
+ ] });
149
+ }
150
+ Confirm.displayName = "AlertDialog.Confirm";
117
151
  const AlertDialog = {
118
152
  Root,
119
153
  Trigger,
@@ -125,7 +159,8 @@ const AlertDialog = {
125
159
  Header,
126
160
  Footer,
127
161
  Action,
128
- Cancel
162
+ Cancel,
163
+ Confirm
129
164
  };
130
165
  export {
131
166
  AlertDialog
@@ -1 +1,2 @@
1
1
  export { AlertDialog } from './AlertDialog.js';
2
+ export type { AlertDialogConfirmProps } from './AlertDialog.js';
@@ -0,0 +1,14 @@
1
+ import * as React from 'react';
2
+ import type { NexubMessages } from '@nexub/core';
3
+ export type EmptyStateKind = keyof NexubMessages['emptyStates'];
4
+ export interface EmptyStatePresetProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
5
+ /** noData, noResults, noAccess (403), serverError (500) or offline. */
6
+ kind: EmptyStateKind;
7
+ /** Overrides the preset's title; the description too. Both default to localised text. */
8
+ title?: React.ReactNode;
9
+ description?: React.ReactNode;
10
+ /** Buttons under the text, e.g. Retry or Clear filters. */
11
+ actions?: React.ReactNode;
12
+ }
13
+ /** A ready-made empty state: icon, title and description for common cases. */
14
+ export declare const EmptyStatePreset: React.ForwardRefExoticComponent<EmptyStatePresetProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,23 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Inbox, Lock, Search, ServerCrash, WifiOff } from "@nexub/icons";
5
+ import { useNexubMessages } from "../NexubProvider/NexubProvider.js";
6
+ import { EmptyState } from "./EmptyState.js";
7
+ const presetIcon = { noData: Inbox, noResults: Search, noAccess: Lock, serverError: ServerCrash, offline: WifiOff };
8
+ const EmptyStatePreset = React.forwardRef(
9
+ ({ kind, title, description, actions, ...props }, ref) => {
10
+ const text = useNexubMessages().emptyStates[kind];
11
+ const Icon = presetIcon[kind];
12
+ return /* @__PURE__ */ jsxs(EmptyState.Root, { ref, role: kind === "serverError" || kind === "offline" ? "alert" : void 0, ...props, children: [
13
+ /* @__PURE__ */ jsx(EmptyState.Illustration, { children: /* @__PURE__ */ jsx(Icon, { size: "xl", "aria-hidden": "true" }) }),
14
+ /* @__PURE__ */ jsx(EmptyState.Title, { children: title ?? text.title }),
15
+ /* @__PURE__ */ jsx(EmptyState.Description, { children: description ?? text.description }),
16
+ actions && /* @__PURE__ */ jsx(EmptyState.Actions, { children: actions })
17
+ ] });
18
+ }
19
+ );
20
+ EmptyStatePreset.displayName = "EmptyStatePreset";
21
+ export {
22
+ EmptyStatePreset
23
+ };
@@ -1 +1,3 @@
1
1
  export { EmptyState } from './EmptyState.js';
2
+ export { EmptyStatePreset } from './EmptyStatePreset.js';
3
+ export type { EmptyStatePresetProps, EmptyStateKind } from './EmptyStatePreset.js';
@@ -1,4 +1,6 @@
1
1
  import { EmptyState } from "./EmptyState.js";
2
+ import { EmptyStatePreset } from "./EmptyStatePreset.js";
2
3
  export {
3
- EmptyState
4
+ EmptyState,
5
+ EmptyStatePreset
4
6
  };
@@ -20,11 +20,18 @@ export interface FileUploadRootProps extends React.HTMLAttributes<HTMLDivElement
20
20
  /** Files turned away by `accept`, `maxSize` or `maxFiles`, from the picker or a drop. */
21
21
  onReject?: (rejections: FileRejection[]) => void;
22
22
  }
23
+ export interface FileUploadItemProps extends React.HTMLAttributes<HTMLLIElement> {
24
+ index: number;
25
+ /** Upload progress 0–100. Below 100 shows a bar and, with `onCancel`, a cancel button. Your app does the upload. */
26
+ progress?: number;
27
+ /** Stops this file's upload (e.g. AbortController.abort()); shown while `progress` is below 100. */
28
+ onCancel?: () => void;
29
+ /** Why this file failed; shown under its name. */
30
+ error?: string;
31
+ }
23
32
  export declare const FileUpload: {
24
33
  Root: React.ForwardRefExoticComponent<FileUploadRootProps & React.RefAttributes<HTMLDivElement>>;
25
34
  Dropzone: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
26
35
  List: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLUListElement> & React.RefAttributes<HTMLUListElement>>;
27
- Item: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLLIElement> & {
28
- index: number;
29
- } & React.RefAttributes<HTMLLIElement>>;
36
+ Item: React.ForwardRefExoticComponent<FileUploadItemProps & React.RefAttributes<HTMLLIElement>>;
30
37
  };
@@ -4,6 +4,7 @@ import * as React from "react";
4
4
  import { Upload, X } from "@nexub/icons";
5
5
  import { cn } from "../../utils/cn.js";
6
6
  import { useLocale, useNexubMessages } from "../NexubProvider/NexubProvider.js";
7
+ import { Progress } from "../Progress/Progress.js";
7
8
  function formatFileSize(bytes, locale) {
8
9
  const [value, unit] = bytes < 1024 ? [bytes, "byte"] : bytes < 1024 * 1024 ? [bytes / 1024, "kilobyte"] : [bytes / (1024 * 1024), "megabyte"];
9
10
  return new Intl.NumberFormat(locale, { style: "unit", unit, unitDisplay: "short", maximumFractionDigits: 1 }).format(value);
@@ -141,12 +142,14 @@ const List = React.forwardRef(
141
142
  }
142
143
  );
143
144
  List.displayName = "FileUpload.List";
144
- const Item = React.forwardRef(({ className, index, ...props }, ref) => {
145
+ const Item = React.forwardRef(({ className, index, progress, onCancel, error, ...props }, ref) => {
145
146
  const { files, removeFile } = useFileUpload();
146
147
  const messages = useNexubMessages();
147
148
  const { locale } = useLocale();
148
149
  const file = files[index];
149
150
  if (!file) return null;
151
+ const uploading = progress !== void 0 && progress < 100;
152
+ const percent = Math.round(Math.max(0, Math.min(100, progress ?? 0)));
150
153
  return /* @__PURE__ */ jsxs(
151
154
  "li",
152
155
  {
@@ -159,13 +162,19 @@ const Item = React.forwardRef(({ className, index, ...props }, ref) => {
159
162
  children: [
160
163
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
161
164
  /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-[var(--color-on-surface)]", children: file.name }),
162
- /* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--color-on-surface-muted)]", children: formatFileSize(file.size, locale) })
165
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-[var(--color-on-surface-muted)]", children: [
166
+ formatFileSize(file.size, locale),
167
+ uploading && ` \xB7 ${percent}%`
168
+ ] }),
169
+ uploading && /* @__PURE__ */ jsx(Progress, { value: percent, size: "sm", className: "mt-1.5", "aria-label": messages.uploadProgress(file.name, percent) }),
170
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 text-xs text-[var(--color-error)]", children: error })
163
171
  ] }),
164
172
  /* @__PURE__ */ jsx(
165
173
  "button",
166
174
  {
167
- onClick: () => removeFile(index),
168
- "aria-label": messages.removeFile(file.name),
175
+ type: "button",
176
+ onClick: () => uploading && onCancel ? onCancel() : removeFile(index),
177
+ "aria-label": uploading && onCancel ? messages.cancelUpload(file.name) : messages.removeFile(file.name),
169
178
  className: "nx-state-layer shrink-0 rounded-full p-1 text-[var(--color-on-surface-muted)] hover:text-[var(--color-on-surface)] transition-colors",
170
179
  children: /* @__PURE__ */ jsx(X, { size: "sm", "aria-hidden": "true" })
171
180
  }
@@ -1,2 +1,2 @@
1
1
  export { FileUpload, fileAccepted } from './FileUpload.js';
2
- export type { FileUploadRootProps, FileRejection } from './FileUpload.js';
2
+ export type { FileUploadRootProps, FileUploadItemProps, FileRejection } from './FileUpload.js';
@@ -0,0 +1,28 @@
1
+ import * as React from 'react';
2
+ export interface OTPInputProps {
3
+ /** Number of characters. Default 6. */
4
+ length?: number;
5
+ value?: string;
6
+ defaultValue?: string;
7
+ onValueChange?: (value: string) => void;
8
+ /** Called once every box is filled. */
9
+ onComplete?: (value: string) => void;
10
+ /** `numeric` (default) accepts digits only; `alphanumeric` accepts letters too. */
11
+ mode?: 'numeric' | 'alphanumeric';
12
+ disabled?: boolean;
13
+ invalid?: boolean;
14
+ /** Group name; defaults to "Verification code". */
15
+ 'aria-label'?: string;
16
+ className?: string;
17
+ /** Submits with a form under this name. */
18
+ name?: string;
19
+ }
20
+ /**
21
+ * One box per character. Typing moves on, Backspace moves back, paste and
22
+ * the OS's one-time-code suggestion fill every box. The value has no gaps:
23
+ * focusing a box past the end jumps to the first empty one.
24
+ */
25
+ export declare function OTPInput({ length, value: valueProp, defaultValue, onValueChange, onComplete, mode, disabled, invalid, 'aria-label': ariaLabel, className, name, }: OTPInputProps): React.JSX.Element;
26
+ export declare namespace OTPInput {
27
+ var displayName: string;
28
+ }
@@ -0,0 +1,107 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../utils/cn.js";
5
+ import { field, fieldFocusVisible, fieldInvalid } from "../../utils/control.js";
6
+ import { useControllableState } from "../../hooks/useControllableState.js";
7
+ import { useIsomorphicLayoutEffect } from "../../hooks/useIsomorphicLayoutEffect.js";
8
+ import { useNexubMessages } from "../NexubProvider/NexubProvider.js";
9
+ function OTPInput({
10
+ length = 6,
11
+ value: valueProp,
12
+ defaultValue,
13
+ onValueChange,
14
+ onComplete,
15
+ mode = "numeric",
16
+ disabled,
17
+ invalid,
18
+ "aria-label": ariaLabel,
19
+ className,
20
+ name
21
+ }) {
22
+ const t = useNexubMessages();
23
+ const [value = "", setValue] = useControllableState({
24
+ value: valueProp,
25
+ defaultValue: defaultValue ?? "",
26
+ onChange: onValueChange
27
+ });
28
+ const refs = React.useRef([]);
29
+ const filled = React.useRef(value.length);
30
+ useIsomorphicLayoutEffect(() => {
31
+ filled.current = value.length;
32
+ });
33
+ const clean = (s) => (s.match(mode === "numeric" ? /\d/g : /[a-z0-9]/gi) ?? []).join("");
34
+ const commit = (next) => {
35
+ const v = next.slice(0, length);
36
+ filled.current = v.length;
37
+ setValue(v);
38
+ if (v.length === length) onComplete?.(v);
39
+ };
40
+ const focus = (i) => refs.current[Math.max(0, Math.min(length - 1, i))]?.focus();
41
+ const writeAt = (i, chars) => {
42
+ commit(value.slice(0, i) + chars + value.slice(i + chars.length));
43
+ focus(i + chars.length);
44
+ };
45
+ return /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": ariaLabel ?? t.verificationCode, className: cn("flex gap-2", className), children: [
46
+ Array.from({ length }, (_, i) => /* @__PURE__ */ jsx(
47
+ "input",
48
+ {
49
+ ref: (el) => {
50
+ refs.current[i] = el;
51
+ },
52
+ "aria-label": t.codeDigit(i + 1, length),
53
+ autoComplete: i === 0 ? "one-time-code" : "off",
54
+ inputMode: mode === "numeric" ? "numeric" : "text",
55
+ pattern: mode === "numeric" ? "[0-9]*" : void 0,
56
+ maxLength: i === 0 ? length : 2,
57
+ disabled,
58
+ "aria-invalid": invalid || void 0,
59
+ value: value[i] ?? "",
60
+ onFocus: (e) => {
61
+ if (i > filled.current) focus(filled.current);
62
+ else e.target.select();
63
+ },
64
+ onChange: (e) => {
65
+ const raw = clean(e.target.value);
66
+ const typed = raw.length === 2 && value[i] && raw.startsWith(value[i]) ? raw.slice(1) : raw;
67
+ if (typed) writeAt(Math.min(i, value.length), typed);
68
+ },
69
+ onPaste: (e) => {
70
+ const pasted = clean(e.clipboardData.getData("text"));
71
+ if (!pasted) return;
72
+ e.preventDefault();
73
+ writeAt(Math.min(i, value.length), pasted);
74
+ },
75
+ onKeyDown: (e) => {
76
+ if (e.key === "Backspace") {
77
+ e.preventDefault();
78
+ if (value[i]) commit(value.slice(0, i) + value.slice(i + 1));
79
+ else if (i > 0) {
80
+ commit(value.slice(0, i - 1) + value.slice(i));
81
+ focus(i - 1);
82
+ }
83
+ } else if (e.key === "ArrowLeft") {
84
+ e.preventDefault();
85
+ focus(i - 1);
86
+ } else if (e.key === "ArrowRight") {
87
+ e.preventDefault();
88
+ focus(i + 1);
89
+ }
90
+ },
91
+ className: cn(
92
+ field,
93
+ fieldFocusVisible,
94
+ invalid && fieldInvalid,
95
+ "h-12 w-10 text-center text-title-md tabular-nums",
96
+ "disabled:cursor-not-allowed disabled:border-[var(--color-outline-variant)] disabled:bg-[var(--color-surface-container-low)] disabled:text-[var(--color-text-disabled)]"
97
+ )
98
+ },
99
+ i
100
+ )),
101
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value })
102
+ ] });
103
+ }
104
+ OTPInput.displayName = "OTPInput";
105
+ export {
106
+ OTPInput
107
+ };
@@ -0,0 +1,2 @@
1
+ export { OTPInput } from './OTPInput.js';
2
+ export type { OTPInputProps } from './OTPInput.js';
@@ -0,0 +1,4 @@
1
+ import { OTPInput } from "./OTPInput.js";
2
+ export {
3
+ OTPInput
4
+ };
package/dist/index.d.ts CHANGED
@@ -144,6 +144,7 @@ export { KeyboardShortcut } from './components/KeyboardShortcut/index.js';
144
144
  export type { KeyboardShortcutProps } from './components/KeyboardShortcut/index.js';
145
145
  export { Dialog } from './components/Dialog/index.js';
146
146
  export { AlertDialog } from './components/AlertDialog/index.js';
147
+ export type { AlertDialogConfirmProps } from './components/AlertDialog/index.js';
147
148
  export { Popover } from './components/Popover/index.js';
148
149
  export { Tooltip } from './components/Tooltip/index.js';
149
150
  export { Provider as TooltipProvider } from './components/Tooltip/index.js';
@@ -205,11 +206,14 @@ export type { CommandPaletteRootProps } from './components/CommandPalette/index.
205
206
  export { Table } from './components/Table/index.js';
206
207
  export { DataTable } from './components/DataTable/index.js';
207
208
  export type { DataTableProps, DataTableDensity } from './components/DataTable/index.js';
208
- export { EmptyState } from './components/EmptyState/index.js';
209
+ export { EmptyState, EmptyStatePreset } from './components/EmptyState/index.js';
210
+ export type { EmptyStatePresetProps, EmptyStateKind } from './components/EmptyState/index.js';
209
211
  export { ErrorState } from './components/ErrorState/index.js';
210
212
  export type { ErrorStateProps } from './components/ErrorState/index.js';
211
213
  export { FileUpload, fileAccepted } from './components/FileUpload/index.js';
212
- export type { FileUploadRootProps, FileRejection } from './components/FileUpload/index.js';
214
+ export type { FileUploadRootProps, FileUploadItemProps, FileRejection } from './components/FileUpload/index.js';
215
+ export { OTPInput } from './components/OTPInput/index.js';
216
+ export type { OTPInputProps } from './components/OTPInput/index.js';
213
217
  export { DatePicker } from './components/DatePicker/index.js';
214
218
  export type { DatePickerProps } from './components/DatePicker/index.js';
215
219
  export { DateRangePicker } from './components/DateRangePicker/index.js';
package/dist/index.js CHANGED
@@ -116,9 +116,10 @@ import { Pagination } from "./components/Pagination/index.js";
116
116
  import { CommandPalette } from "./components/CommandPalette/index.js";
117
117
  import { Table } from "./components/Table/index.js";
118
118
  import { DataTable } from "./components/DataTable/index.js";
119
- import { EmptyState } from "./components/EmptyState/index.js";
119
+ import { EmptyState, EmptyStatePreset } from "./components/EmptyState/index.js";
120
120
  import { ErrorState } from "./components/ErrorState/index.js";
121
121
  import { FileUpload, fileAccepted } from "./components/FileUpload/index.js";
122
+ import { OTPInput } from "./components/OTPInput/index.js";
122
123
  import { DatePicker } from "./components/DatePicker/index.js";
123
124
  import { DateRangePicker } from "./components/DateRangePicker/index.js";
124
125
  import { todayIn } from "./components/DateRangePicker/index.js";
@@ -170,6 +171,7 @@ export {
170
171
  Drawer,
171
172
  DropdownMenu,
172
173
  EmptyState,
174
+ EmptyStatePreset,
173
175
  ErrorState,
174
176
  EyebrowChip,
175
177
  Fade,
@@ -212,6 +214,7 @@ export {
212
214
  NexubProvider,
213
215
  NumberInput,
214
216
  NumberTicker,
217
+ OTPInput,
215
218
  PageHeader,
216
219
  Pagination,
217
220
  PasswordInput,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexub/react",
3
- "version": "5.5.0",
3
+ "version": "5.6.0",
4
4
  "description": "React component library for Nexub Design System — 90+ accessible, themeable UI components built on Radix UI primitives.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -81,14 +81,14 @@
81
81
  "date-fns": "^4.4.0",
82
82
  "react-day-picker": "^10.0.2",
83
83
  "tailwind-merge": "^3.7.0",
84
- "@nexub/core": "5.5.0",
85
- "@nexub/icons": "5.5.0"
84
+ "@nexub/core": "5.6.0",
85
+ "@nexub/icons": "5.6.0"
86
86
  },
87
87
  "peerDependencies": {
88
88
  "react": ">=19 <20",
89
89
  "react-dom": ">=19 <20",
90
90
  "@tanstack/react-virtual": "^3.14.22",
91
- "@nexub/tokens": "^5.5.0"
91
+ "@nexub/tokens": "^5.6.0"
92
92
  },
93
93
  "peerDependenciesMeta": {
94
94
  "@tanstack/react-virtual": {
@@ -120,8 +120,8 @@
120
120
  "vaul": "1.1.2",
121
121
  "vite": "^8.3.5",
122
122
  "vitest": "^5.0.5",
123
- "@nexub/themes": "5.5.0",
124
- "@nexub/tokens": "5.5.0"
123
+ "@nexub/themes": "5.6.0",
124
+ "@nexub/tokens": "5.6.0"
125
125
  },
126
126
  "scripts": {
127
127
  "build": "rm -rf dist && tsup && tsc -p tsconfig.build.json && rm -f dist/vendor/*.d.ts && node scripts/check-dts.mjs && tailwindcss -i src/styles.css -o dist/styles.css --minify && cp -R ../themes/src/fonts dist/fonts",