@shadow-js/ui 0.0.0-stage → 1.0.1
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/CHANGELOG.md +30 -0
- package/LICENSE +21 -0
- package/README.md +598 -2
- package/dist/cli.mjs +2281 -0
- package/dist/dev/cli/schema.d.ts +71 -0
- package/dist/dev/index.d.ts +6 -0
- package/dist/dev/index.js +5 -0
- package/dist/dev/locales/ar.d.ts +139 -0
- package/dist/dev/locales/ar.js +127 -0
- package/dist/dev/locales/ar.js.map +1 -0
- package/dist/dev/locales/en.d.ts +139 -0
- package/dist/dev/locales/en.js +127 -0
- package/dist/dev/locales/en.js.map +1 -0
- package/dist/dev/registry/utils/utils.d.ts +27 -0
- package/dist/dev/registry/utils/utils.js +26 -0
- package/dist/dev/registry/utils/utils.js.map +1 -0
- package/dist/dev/theme/theme-logic.d.ts +185 -0
- package/dist/dev/theme/theme-logic.js +183 -0
- package/dist/dev/theme/theme-logic.js.map +1 -0
- package/dist/dev/theme/theme-provider.d.ts +88 -0
- package/dist/dev/theme/theme-provider.js +149 -0
- package/dist/dev/theme/theme-provider.js.map +1 -0
- package/dist/prod/index.js +1 -0
- package/dist/prod/locales/ar.js +2 -0
- package/dist/prod/locales/ar.js.map +1 -0
- package/dist/prod/locales/en.js +2 -0
- package/dist/prod/locales/en.js.map +1 -0
- package/dist/prod/registry/utils/utils.js +2 -0
- package/dist/prod/registry/utils/utils.js.map +1 -0
- package/dist/prod/theme/theme-logic.js +2 -0
- package/dist/prod/theme/theme-logic.js.map +1 -0
- package/dist/prod/theme/theme-provider.js +2 -0
- package/dist/prod/theme/theme-provider.js.map +1 -0
- package/package.json +102 -4
- package/registry/accordion/accordion.tsx +210 -0
- package/registry/accordion/item.json +4 -0
- package/registry/alert/alert.tsx +200 -0
- package/registry/alert/item.json +4 -0
- package/registry/alert-dialog/alert-dialog.tsx +530 -0
- package/registry/alert-dialog/item.json +4 -0
- package/registry/aspect-ratio/aspect-ratio.tsx +71 -0
- package/registry/aspect-ratio/item.json +4 -0
- package/registry/attachment/attachment.tsx +459 -0
- package/registry/attachment/item.json +4 -0
- package/registry/avatar/avatar.tsx +385 -0
- package/registry/avatar/item.json +4 -0
- package/registry/badge/badge.tsx +108 -0
- package/registry/badge/item.json +4 -0
- package/registry/breadcrumb/breadcrumb.tsx +354 -0
- package/registry/breadcrumb/item.json +4 -0
- package/registry/bubble/bubble.tsx +260 -0
- package/registry/bubble/item.json +4 -0
- package/registry/button/button.tsx +125 -0
- package/registry/button/item.json +4 -0
- package/registry/button-group/button-group.tsx +189 -0
- package/registry/button-group/item.json +4 -0
- package/registry/calendar/calendar.tsx +348 -0
- package/registry/calendar/item.json +4 -0
- package/registry/card/card.tsx +311 -0
- package/registry/card/item.json +4 -0
- package/registry/carousel/carousel.tsx +851 -0
- package/registry/carousel/item.json +4 -0
- package/registry/chart/chart.tsx +1956 -0
- package/registry/chart/item.json +4 -0
- package/registry/chart-scale/chart-scale.ts +670 -0
- package/registry/chart-scale/item.json +5 -0
- package/registry/checkbox/checkbox.tsx +79 -0
- package/registry/checkbox/item.json +4 -0
- package/registry/code-block/code-block.tsx +470 -0
- package/registry/code-block/item.json +4 -0
- package/registry/collapsible/collapsible.tsx +117 -0
- package/registry/collapsible/item.json +4 -0
- package/registry/combobox/combobox.tsx +977 -0
- package/registry/combobox/item.json +4 -0
- package/registry/command/command.tsx +757 -0
- package/registry/command/item.json +4 -0
- package/registry/context-menu/context-menu.tsx +697 -0
- package/registry/context-menu/item.json +4 -0
- package/registry/data-table/data-table.tsx +437 -0
- package/registry/data-table/item.json +4 -0
- package/registry/date-picker/date-picker.tsx +349 -0
- package/registry/date-picker/item.json +4 -0
- package/registry/dialog/dialog.tsx +412 -0
- package/registry/dialog/item.json +4 -0
- package/registry/direction/direction.tsx +88 -0
- package/registry/direction/item.json +4 -0
- package/registry/drawer/drawer.tsx +787 -0
- package/registry/drawer/item.json +4 -0
- package/registry/dropdown-menu/dropdown-menu.tsx +692 -0
- package/registry/dropdown-menu/item.json +4 -0
- package/registry/empty/empty.tsx +240 -0
- package/registry/empty/item.json +4 -0
- package/registry/field/field.tsx +624 -0
- package/registry/field/item.json +4 -0
- package/registry/form/form.tsx +1112 -0
- package/registry/form/item.json +4 -0
- package/registry/hover-card/hover-card.tsx +144 -0
- package/registry/hover-card/item.json +4 -0
- package/registry/icons/icons.tsx +1691 -0
- package/registry/icons/item.json +4 -0
- package/registry/input/input.tsx +43 -0
- package/registry/input/item.json +4 -0
- package/registry/input-group/input-group.tsx +315 -0
- package/registry/input-group/item.json +4 -0
- package/registry/input-otp/input-otp.tsx +329 -0
- package/registry/input-otp/item.json +4 -0
- package/registry/item/item.json +4 -0
- package/registry/item/item.tsx +439 -0
- package/registry/kbd/item.json +4 -0
- package/registry/kbd/kbd.tsx +65 -0
- package/registry/label/item.json +4 -0
- package/registry/label/label.tsx +60 -0
- package/registry/marker/item.json +4 -0
- package/registry/marker/marker.tsx +175 -0
- package/registry/menubar/item.json +4 -0
- package/registry/menubar/menubar.tsx +694 -0
- package/registry/message/item.json +4 -0
- package/registry/message/message.tsx +280 -0
- package/registry/message-scroller/item.json +4 -0
- package/registry/message-scroller/message-scroller.tsx +262 -0
- package/registry/native-select/item.json +4 -0
- package/registry/native-select/native-select.tsx +113 -0
- package/registry/navigation-menu/item.json +4 -0
- package/registry/navigation-menu/navigation-menu.tsx +362 -0
- package/registry/pagination/item.json +4 -0
- package/registry/pagination/pagination.tsx +460 -0
- package/registry/password-input/item.json +5 -0
- package/registry/password-input/password-input.tsx +494 -0
- package/registry/popover/item.json +4 -0
- package/registry/popover/popover.tsx +284 -0
- package/registry/progress/item.json +4 -0
- package/registry/progress/progress.tsx +190 -0
- package/registry/questionnaire/item.json +4 -0
- package/registry/questionnaire/questionnaire.tsx +569 -0
- package/registry/radio-group/item.json +4 -0
- package/registry/radio-group/radio-group.tsx +101 -0
- package/registry/resizable/item.json +4 -0
- package/registry/resizable/resizable.tsx +455 -0
- package/registry/scroll-area/item.json +4 -0
- package/registry/scroll-area/scroll-area.tsx +149 -0
- package/registry/select/item.json +4 -0
- package/registry/select/select.tsx +571 -0
- package/registry/separator/item.json +4 -0
- package/registry/separator/separator.tsx +52 -0
- package/registry/sheet/item.json +4 -0
- package/registry/sheet/sheet.tsx +476 -0
- package/registry/sidebar/item.json +4 -0
- package/registry/sidebar/sidebar.tsx +1870 -0
- package/registry/skeleton/item.json +4 -0
- package/registry/skeleton/skeleton.tsx +47 -0
- package/registry/slider/item.json +4 -0
- package/registry/slider/slider.tsx +149 -0
- package/registry/spinner/item.json +4 -0
- package/registry/spinner/spinner.tsx +51 -0
- package/registry/switch/item.json +4 -0
- package/registry/switch/switch.tsx +93 -0
- package/registry/table/item.json +4 -0
- package/registry/table/table.tsx +373 -0
- package/registry/tabs/item.json +4 -0
- package/registry/tabs/tabs.tsx +209 -0
- package/registry/textarea/item.json +4 -0
- package/registry/textarea/textarea.tsx +51 -0
- package/registry/toast/item.json +4 -0
- package/registry/toast/toast.tsx +949 -0
- package/registry/toggle/item.json +4 -0
- package/registry/toggle/toggle.tsx +110 -0
- package/registry/toggle-group/item.json +4 -0
- package/registry/toggle-group/toggle-group.tsx +189 -0
- package/registry/tooltip/item.json +4 -0
- package/registry/tooltip/tooltip.tsx +169 -0
- package/registry/trace/item.json +4 -0
- package/registry/trace/trace.tsx +356 -0
- package/registry/ui-strings/item.json +5 -0
- package/registry/ui-strings/ui-strings.tsx +454 -0
- package/registry/use-highlighted-code/item.json +5 -0
- package/registry/use-highlighted-code/use-highlighted-code.tsx +145 -0
- package/registry/use-mobile/item.json +5 -0
- package/registry/use-mobile/use-mobile.ts +46 -0
- package/registry/utils/item.json +5 -0
- package/registry/utils/utils.ts +38 -0
- package/registry.json +3274 -0
- package/styles/animations.css +321 -0
- package/styles/theme-core.css +55 -0
- package/styles/theme.css +12 -0
- package/styles/themes/gray.css +69 -0
- package/styles/themes/neutral.css +69 -0
- package/styles/themes/slate.css +69 -0
- package/styles/themes/stone.css +69 -0
- package/styles/themes/zinc.css +69 -0
- package/styles/typeset.css +251 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { splitProps, useMemo } from "@shadow-js/core";
|
|
2
|
+
import {
|
|
3
|
+
createOneTimePasswordPattern,
|
|
4
|
+
createPartContext,
|
|
5
|
+
type Direction,
|
|
6
|
+
OneTimePasswordField,
|
|
7
|
+
type OneTimePasswordType,
|
|
8
|
+
} from "@shadow-js/primitives";
|
|
9
|
+
import type { ComponentProps, JSX } from "@shadow-js/web";
|
|
10
|
+
import { useUiStrings } from "@/lib/ui-strings";
|
|
11
|
+
import { cn } from "@/lib/utils";
|
|
12
|
+
|
|
13
|
+
/** What a code field shares with its slots, to name them. @internal */
|
|
14
|
+
interface InputOtpContextValue {
|
|
15
|
+
readonly length: () => number;
|
|
16
|
+
/** Whether the slots take digits (numeric, no pattern) rather than characters. */
|
|
17
|
+
readonly digits: () => boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const inputOtpContext = createPartContext<InputOtpContextValue>("InputOTP");
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A per-character digit pattern; Arabic-Indic and Persian digits normalize before matching.
|
|
24
|
+
* @example
|
|
25
|
+
* import { REGEXP_ONLY_DIGITS } from "@/components/ui/input-otp";
|
|
26
|
+
* export const acceptsDigit = new RegExp(REGEXP_ONLY_DIGITS).test("7");
|
|
27
|
+
* @since 1.0.0
|
|
28
|
+
*/
|
|
29
|
+
const REGEXP_ONLY_DIGITS: string = "^[0-9]+$";
|
|
30
|
+
/**
|
|
31
|
+
* A per-character ASCII letter pattern, accepting uppercase and lowercase letters.
|
|
32
|
+
* @example
|
|
33
|
+
* import { REGEXP_ONLY_CHARS } from "@/components/ui/input-otp";
|
|
34
|
+
* export const acceptsLetter = new RegExp(REGEXP_ONLY_CHARS).test("A");
|
|
35
|
+
* @since 1.0.0
|
|
36
|
+
*/
|
|
37
|
+
const REGEXP_ONLY_CHARS: string = "^[a-zA-Z]+$";
|
|
38
|
+
/**
|
|
39
|
+
* A per-character ASCII letter and digit pattern.
|
|
40
|
+
* @example
|
|
41
|
+
* import { REGEXP_ONLY_DIGITS_AND_CHARS } from "@/components/ui/input-otp";
|
|
42
|
+
* export const acceptsCode = new RegExp(REGEXP_ONLY_DIGITS_AND_CHARS).test("A7");
|
|
43
|
+
* @since 1.0.0
|
|
44
|
+
*/
|
|
45
|
+
const REGEXP_ONLY_DIGITS_AND_CHARS: string = "^[a-zA-Z0-9]+$";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Props of {@link InputOTP}. The slots and separators are its children; the code is `value`
|
|
49
|
+
* (controlled) or `defaultValue` (uncontrolled).
|
|
50
|
+
* @since 1.0.0
|
|
51
|
+
* @see {@link InputOTP}
|
|
52
|
+
*/
|
|
53
|
+
export interface InputOTPProps {
|
|
54
|
+
/** Explicit reading direction for layout and arrow keys. @default undefined (inherited direction) */
|
|
55
|
+
readonly dir?: Direction | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* The number of slots, an integer from 1 to 64, fixed when the component mounts.
|
|
58
|
+
* @default 6
|
|
59
|
+
*/
|
|
60
|
+
readonly maxLength?: number | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Which characters a slot accepts: `"numeric"` for digits, `"alphanumeric"` for letters and digits.
|
|
63
|
+
* @default "numeric"
|
|
64
|
+
*/
|
|
65
|
+
readonly type?: OneTimePasswordType | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* A regular expression source of at most 256 characters, matched against each normalized
|
|
68
|
+
* character. Overrides `type` filtering; use the exported REGEXP constants for common codes.
|
|
69
|
+
* This filters characters rather than validating the whole code. Custom patterns use text input mode.
|
|
70
|
+
* @default undefined (use `type`)
|
|
71
|
+
*/
|
|
72
|
+
readonly pattern?: string | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* The code when controlled. Pair it with `onChange` to keep it up to date.
|
|
75
|
+
* @default none (uncontrolled)
|
|
76
|
+
*/
|
|
77
|
+
readonly value?: string | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* The starting code when uncontrolled.
|
|
80
|
+
* @default "" (empty)
|
|
81
|
+
*/
|
|
82
|
+
readonly defaultValue?: string | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Called with the whole code after every change: typing, deleting or pasting.
|
|
85
|
+
* @default none
|
|
86
|
+
*/
|
|
87
|
+
readonly onChange?: ((value: string) => void) | undefined;
|
|
88
|
+
/**
|
|
89
|
+
* Called once when every slot is filled, with the full code.
|
|
90
|
+
* @default none
|
|
91
|
+
*/
|
|
92
|
+
readonly onComplete?: ((value: string) => void) | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* Disables every slot.
|
|
95
|
+
* @default false
|
|
96
|
+
*/
|
|
97
|
+
readonly disabled?: boolean | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Submits the code with a surrounding form under this name.
|
|
100
|
+
* @default none
|
|
101
|
+
*/
|
|
102
|
+
readonly name?: string | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* The slots ({@link InputOTPSlot}) and separators ({@link InputOTPSeparator}), in order.
|
|
105
|
+
* @default none
|
|
106
|
+
*/
|
|
107
|
+
readonly children?: unknown;
|
|
108
|
+
/**
|
|
109
|
+
* Classes of the wrapper that holds the slots. It is the same element as `class`.
|
|
110
|
+
* @default none
|
|
111
|
+
*/
|
|
112
|
+
readonly containerClassName?: string | undefined;
|
|
113
|
+
/**
|
|
114
|
+
* Classes of the wrapper that holds the slots. It is the same element as `containerClassName`.
|
|
115
|
+
* @default none
|
|
116
|
+
*/
|
|
117
|
+
readonly class?: string | undefined;
|
|
118
|
+
/**
|
|
119
|
+
* The name of the code field, for example "Verification code". The wrapper is named with it.
|
|
120
|
+
* @default none
|
|
121
|
+
*/
|
|
122
|
+
readonly "aria-label"?: string | undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* A one-time code, one character per slot. Typing fills the slot and moves on; Backspace steps
|
|
127
|
+
* back; the arrow keys, Home and End move between slots; a paste or an OS autofill fills them all.
|
|
128
|
+
* Only the characters `type` or `pattern` accepts are kept. Arabic-Indic and Persian digits
|
|
129
|
+
* normalize before matching. An edit scans at most its first 4,096 UTF-16 units; surrogate pairs
|
|
130
|
+
* are refused instead of being split between slots.
|
|
131
|
+
*
|
|
132
|
+
* Keyboard and ARIA: the wrapper is a `role="group"`. Name it with `aria-label`, for example
|
|
133
|
+
* "Verification code", because the slots on their own are only named "Digit 1 of 6". Each slot is
|
|
134
|
+
* an `input` named "Digit n of N" (or "Character n of N" for alphanumeric) unless it has its own
|
|
135
|
+
* `aria-label`. The first slot has `autocomplete="one-time-code"`, so the operating system can
|
|
136
|
+
* offer to fill in a code it has received. Backspace clears the slot and steps back from an empty one; Delete clears
|
|
137
|
+
* the slot; ArrowLeft and ArrowRight (swapped in right-to-left text) and Home and End move between
|
|
138
|
+
* slots. `aria-invalid="true"` on a slot, which you pass, turns its border destructive.
|
|
139
|
+
*
|
|
140
|
+
* shadcn/ui differences: slots compose through JSX children and are native inputs. `type` is a
|
|
141
|
+
* convenience when no `pattern` is supplied. `class` and `containerClassName` style the same root.
|
|
142
|
+
*
|
|
143
|
+
* @param props See {@link InputOTPProps}.
|
|
144
|
+
* @returns The wrapper `div` with the slots and separators inside.
|
|
145
|
+
* @example
|
|
146
|
+
* import {
|
|
147
|
+
* InputOTP,
|
|
148
|
+
* InputOTPGroup,
|
|
149
|
+
* InputOTPSeparator,
|
|
150
|
+
* InputOTPSlot,
|
|
151
|
+
* } from "@/components/ui/input-otp";
|
|
152
|
+
*
|
|
153
|
+
* const submit = (code: string): void => console.info("submit", code);
|
|
154
|
+
*
|
|
155
|
+
* <InputOTP maxLength={6} onComplete={submit} aria-label="Verification code">
|
|
156
|
+
* <InputOTPGroup>
|
|
157
|
+
* <InputOTPSlot index={0} />
|
|
158
|
+
* <InputOTPSlot index={1} />
|
|
159
|
+
* <InputOTPSlot index={2} />
|
|
160
|
+
* </InputOTPGroup>
|
|
161
|
+
* <InputOTPSeparator />
|
|
162
|
+
* <InputOTPGroup>
|
|
163
|
+
* <InputOTPSlot index={3} />
|
|
164
|
+
* <InputOTPSlot index={4} />
|
|
165
|
+
* <InputOTPSlot index={5} />
|
|
166
|
+
* </InputOTPGroup>
|
|
167
|
+
* </InputOTP>
|
|
168
|
+
* @since 1.0.0
|
|
169
|
+
* @see {@link InputOTPSlot}
|
|
170
|
+
*/
|
|
171
|
+
function InputOTP(props: InputOTPProps): JSX.Element {
|
|
172
|
+
const [local, rest] = splitProps(props as InputOTPProps & Record<string, unknown>, [
|
|
173
|
+
"maxLength",
|
|
174
|
+
"type",
|
|
175
|
+
"pattern",
|
|
176
|
+
"value",
|
|
177
|
+
"defaultValue",
|
|
178
|
+
"onChange",
|
|
179
|
+
"onComplete",
|
|
180
|
+
"disabled",
|
|
181
|
+
"name",
|
|
182
|
+
"children",
|
|
183
|
+
"containerClassName",
|
|
184
|
+
"class",
|
|
185
|
+
]);
|
|
186
|
+
const filter = useMemo(() =>
|
|
187
|
+
local.pattern === undefined ? undefined : createOneTimePasswordPattern(local.pattern as string),
|
|
188
|
+
);
|
|
189
|
+
const length = (): number => (local.maxLength as number | undefined) ?? 6;
|
|
190
|
+
const digits = (): boolean =>
|
|
191
|
+
((local.type as OneTimePasswordType | undefined) ?? "numeric") === "numeric" &&
|
|
192
|
+
local.pattern === undefined;
|
|
193
|
+
return (
|
|
194
|
+
<inputOtpContext.Provider value={{ length, digits }}>
|
|
195
|
+
<OneTimePasswordField
|
|
196
|
+
data-slot="input-otp"
|
|
197
|
+
length={(local.maxLength as number | undefined) ?? 6}
|
|
198
|
+
type={(local.type as OneTimePasswordType | undefined) ?? "numeric"}
|
|
199
|
+
filter={filter()}
|
|
200
|
+
value={local.value as string | undefined}
|
|
201
|
+
defaultValue={local.defaultValue as string | undefined}
|
|
202
|
+
onValueChange={local.onChange as ((value: string) => void) | undefined}
|
|
203
|
+
onComplete={local.onComplete as ((value: string) => void) | undefined}
|
|
204
|
+
disabled={local.disabled as boolean | undefined}
|
|
205
|
+
name={local.name as string | undefined}
|
|
206
|
+
class={cn(
|
|
207
|
+
"flex items-center gap-2 has-disabled:opacity-50",
|
|
208
|
+
local.containerClassName as string | undefined,
|
|
209
|
+
local.class as string | undefined,
|
|
210
|
+
)}
|
|
211
|
+
{...rest}
|
|
212
|
+
>
|
|
213
|
+
{local.children as JSX.Element}
|
|
214
|
+
</OneTimePasswordField>
|
|
215
|
+
</inputOtpContext.Provider>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* A row of slots, drawn with the kit's border, focus ring and radius on each one.
|
|
221
|
+
*
|
|
222
|
+
* Keyboard and ARIA: no keys and no role of its own; the slots inside keep theirs.
|
|
223
|
+
*
|
|
224
|
+
* @param props The `div` attributes; `class` adds to the classes.
|
|
225
|
+
* @returns The group `div`.
|
|
226
|
+
* @example
|
|
227
|
+
* import { InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp";
|
|
228
|
+
*
|
|
229
|
+
* <InputOTPGroup>
|
|
230
|
+
* <InputOTPSlot index={0} />
|
|
231
|
+
* </InputOTPGroup>
|
|
232
|
+
* @since 1.0.0
|
|
233
|
+
* @see {@link InputOTPSlot}
|
|
234
|
+
*/
|
|
235
|
+
function InputOTPGroup(props: ComponentProps<"div">): JSX.Element {
|
|
236
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
237
|
+
return <div data-slot="input-otp-group" class={cn("flex items-center", local.class)} {...rest} />;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* One character of an {@link InputOTP}, at `index` (counted from 0). It is a text `input` of its
|
|
242
|
+
* own: its border and ring turn destructive while you pass `aria-invalid="true"`.
|
|
243
|
+
*
|
|
244
|
+
* Keyboard and ARIA: the keys are those of {@link InputOTP}. The slot is named "Digit n of N" (or
|
|
245
|
+
* "Character n of N") unless you pass an `aria-label`.
|
|
246
|
+
*
|
|
247
|
+
* @param props The `input` attributes plus `index`, the position of the slot from 0. `class` adds to
|
|
248
|
+
* the classes.
|
|
249
|
+
* @returns The slot `input`.
|
|
250
|
+
* @throws Error when rendered outside an {@link InputOTP}.
|
|
251
|
+
* @example
|
|
252
|
+
* import { InputOTP, InputOTPSlot } from "@/components/ui/input-otp";
|
|
253
|
+
*
|
|
254
|
+
* <InputOTP maxLength={1} aria-label="PIN">
|
|
255
|
+
* <InputOTPSlot index={0} aria-invalid="true" />
|
|
256
|
+
* </InputOTP>
|
|
257
|
+
* @since 1.0.0
|
|
258
|
+
* @see {@link InputOTP}
|
|
259
|
+
*/
|
|
260
|
+
function InputOTPSlot(props: ComponentProps<"input"> & { readonly index: number }): JSX.Element {
|
|
261
|
+
const [local, rest] = splitProps(
|
|
262
|
+
props as ComponentProps<"input"> & { readonly index: number; readonly class?: unknown },
|
|
263
|
+
["class", "index"],
|
|
264
|
+
);
|
|
265
|
+
const field = inputOtpContext.useOptional();
|
|
266
|
+
const t = useUiStrings();
|
|
267
|
+
// The name is "Digit n of N" or "Character n of N", in the language of the UI strings.
|
|
268
|
+
const name = (): string | undefined => {
|
|
269
|
+
if (field === undefined) return undefined;
|
|
270
|
+
const values = { position: local.index + 1, total: field.length() };
|
|
271
|
+
return field.digits()
|
|
272
|
+
? t("inputOtp.digit", "Digit {position} of {total}", values)
|
|
273
|
+
: t("inputOtp.character", "Character {position} of {total}", values);
|
|
274
|
+
};
|
|
275
|
+
return (
|
|
276
|
+
<OneTimePasswordField.Input
|
|
277
|
+
data-slot="input-otp-slot"
|
|
278
|
+
aria-label={name()}
|
|
279
|
+
index={local.index}
|
|
280
|
+
class={cn(
|
|
281
|
+
"relative flex size-9 items-center justify-center border-y border-e border-input bg-transparent text-center text-sm shadow-xs transition-all outline-none first:rounded-s-md first:border-s last:rounded-e-md dark:bg-input/30",
|
|
282
|
+
"focus:z-10 focus:border-ring focus:ring-[3px] focus:ring-ring/50 focus:outline-solid focus:outline-2 focus:outline-offset-2 focus:outline-transparent aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
283
|
+
"disabled:pointer-events-none disabled:opacity-50 motion-reduce:transition-none forced-colors:focus:outline-[Highlight]",
|
|
284
|
+
local.class as string | undefined,
|
|
285
|
+
)}
|
|
286
|
+
{...rest}
|
|
287
|
+
/>
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* A dot between two groups of slots.
|
|
293
|
+
*
|
|
294
|
+
* Keyboard and ARIA: a horizontal `role="separator"` with `aria-orientation="horizontal"`. It is not
|
|
295
|
+
* focusable. The dot inside it is decorative and hidden from assistive technology.
|
|
296
|
+
*
|
|
297
|
+
* @param props The `div` attributes; `class` adds to the classes.
|
|
298
|
+
* @returns The separator `div`.
|
|
299
|
+
* @example
|
|
300
|
+
* import { InputOTPSeparator } from "@/components/ui/input-otp";
|
|
301
|
+
*
|
|
302
|
+
* <InputOTPSeparator />
|
|
303
|
+
* @since 1.0.0
|
|
304
|
+
* @see {@link InputOTP}
|
|
305
|
+
*/
|
|
306
|
+
function InputOTPSeparator(props: ComponentProps<"div">): JSX.Element {
|
|
307
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
308
|
+
return (
|
|
309
|
+
<div
|
|
310
|
+
role="separator"
|
|
311
|
+
aria-orientation="horizontal"
|
|
312
|
+
data-slot="input-otp-separator"
|
|
313
|
+
class={cn("mx-1 flex w-4 items-center justify-center", local.class)}
|
|
314
|
+
{...rest}
|
|
315
|
+
>
|
|
316
|
+
<span aria-hidden="true" class="size-1.5 rounded-full bg-muted-foreground" />
|
|
317
|
+
</div>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export {
|
|
322
|
+
InputOTP,
|
|
323
|
+
InputOTPGroup,
|
|
324
|
+
InputOTPSeparator,
|
|
325
|
+
InputOTPSlot,
|
|
326
|
+
REGEXP_ONLY_CHARS,
|
|
327
|
+
REGEXP_ONLY_DIGITS,
|
|
328
|
+
REGEXP_ONLY_DIGITS_AND_CHARS,
|
|
329
|
+
};
|