@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.
- package/accessibility.json +14 -1
- package/dist/components/AlertDialog/AlertDialog.d.ts +17 -0
- package/dist/components/AlertDialog/AlertDialog.js +40 -5
- package/dist/components/AlertDialog/index.d.ts +1 -0
- package/dist/components/EmptyState/EmptyStatePreset.d.ts +14 -0
- package/dist/components/EmptyState/EmptyStatePreset.js +23 -0
- package/dist/components/EmptyState/index.d.ts +2 -0
- package/dist/components/EmptyState/index.js +3 -1
- package/dist/components/FileUpload/FileUpload.d.ts +10 -3
- package/dist/components/FileUpload/FileUpload.js +13 -4
- package/dist/components/FileUpload/index.d.ts +1 -1
- package/dist/components/OTPInput/OTPInput.d.ts +28 -0
- package/dist/components/OTPInput/OTPInput.js +107 -0
- package/dist/components/OTPInput/index.d.ts +2 -0
- package/dist/components/OTPInput/index.js +4 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +4 -1
- package/package.json +6 -6
package/accessibility.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "5.
|
|
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
|
-
|
|
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
|
|
@@ -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
|
+
};
|
|
@@ -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.
|
|
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__ */
|
|
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
|
-
|
|
168
|
-
|
|
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
|
+
};
|
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.
|
|
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.
|
|
85
|
-
"@nexub/icons": "5.
|
|
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.
|
|
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.
|
|
124
|
-
"@nexub/tokens": "5.
|
|
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",
|