aural-ui 2.1.7 → 2.1.9

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.
@@ -9,7 +9,7 @@ import { cn } from "@lib/utils"
9
9
  type HelperTextVariant = "default" | "error" | "warning" | "success"
10
10
 
11
11
  interface HelperTextProps {
12
- variant: HelperTextVariant
12
+ variant?: HelperTextVariant
13
13
  className?: string
14
14
  children: ReactNode
15
15
  disabled?: boolean
@@ -17,7 +17,7 @@ interface HelperTextProps {
17
17
  }
18
18
 
19
19
  const HelperText = forwardRef<HTMLSpanElement, HelperTextProps>(
20
- ({ variant, className = "", children, disabled, id }, ref) => {
20
+ ({ variant = "default", className = "", children, disabled, id }, ref) => {
21
21
  return (
22
22
  <SwitchCase value={variant}>
23
23
  <Case value="error">
@@ -37,6 +37,9 @@ const meta: Meta<typeof Input> = {
37
37
  fullWidth: {
38
38
  control: { type: "boolean" },
39
39
  },
40
+ transparentOnAutofill: {
41
+ control: { type: "boolean" },
42
+ },
40
43
  },
41
44
  }
42
45
 
@@ -95,6 +95,7 @@ type InputProps = {
95
95
  min?: number
96
96
  max?: number
97
97
  unstyled?: boolean
98
+ transparentOnAutofill?: boolean
98
99
  classes?: {
99
100
  root?: string
100
101
  label?: string
@@ -237,6 +238,7 @@ const InputBase = forwardRef<
237
238
  maxLength?: number
238
239
  startIcon?: boolean // Indicates if start icon spacing should be applied
239
240
  endIcon?: boolean // Indicates if end icon spacing should be applied
241
+ transparentOnAutofill?: boolean
240
242
  } & Omit<
241
243
  React.InputHTMLAttributes<HTMLInputElement>,
242
244
  "onChange" | "onBlur" | "onFocus" | "defaultValue"
@@ -263,6 +265,7 @@ const InputBase = forwardRef<
263
265
  startIcon = false,
264
266
  endIcon = false,
265
267
  ariaLabel,
268
+ transparentOnAutofill = false,
266
269
  ...props
267
270
  },
268
271
  ref
@@ -289,6 +292,16 @@ const InputBase = forwardRef<
289
292
  // Determine focus state
290
293
  const state = disabled ? "disabled" : isFocused ? "focused" : "default"
291
294
 
295
+ const autoFillStyle = transparentOnAutofill
296
+ ? {
297
+ WebkitTextFillColor: "#ffffff",
298
+ color: "#ffffff",
299
+ caretColor: "#ffffff",
300
+ transition: "background-color 999999s ease-in-out 0s",
301
+ backgroundColor: "transparent",
302
+ }
303
+ : {}
304
+
292
305
  // Apply styles only if not unstyled
293
306
  const inputClassName = unstyled
294
307
  ? className
@@ -322,6 +335,7 @@ const InputBase = forwardRef<
322
335
  onBlur={handleBlur}
323
336
  required={required}
324
337
  maxLength={maxLength}
338
+ style={autoFillStyle}
325
339
  {...props}
326
340
  />
327
341
  )
@@ -356,6 +370,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
356
370
  decoration,
357
371
  unstyled = false,
358
372
  classes = {},
373
+ transparentOnAutofill = false,
359
374
  ...props
360
375
  },
361
376
  ref
@@ -436,6 +451,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
436
451
  endIcon={!!(endIcon || type === "password")}
437
452
  unstyled={unstyled}
438
453
  className={unstyled ? className : classes.input}
454
+ transparentOnAutofill={transparentOnAutofill}
439
455
  {...props}
440
456
  />
441
457
 
@@ -0,0 +1,491 @@
1
+ import React, { useState } from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+
4
+ import OtpInputs from "."
5
+
6
+ const meta: Meta<typeof OtpInputs> = {
7
+ title: "Components/UI/OtpInputs",
8
+ component: OtpInputs,
9
+ parameters: {
10
+ layout: "centered",
11
+ backgrounds: {
12
+ default: "dark",
13
+ values: [
14
+ { name: "dark", value: "#0a0a0a" },
15
+ { name: "light", value: "#ffffff" },
16
+ ],
17
+ },
18
+ docs: {
19
+ description: {
20
+ component: `
21
+ # OTP Input Component
22
+
23
+ A flexible and accessible OTP (One-Time Password) input component that allows users to enter verification codes with individual input fields.
24
+
25
+ ## Features
26
+
27
+ - **Customizable Length**: Configure the number of input fields (default: 6)
28
+ - **Number/Text Input**: Support for both numeric and alphanumeric OTP codes
29
+ - **Keyboard Navigation**: Full keyboard support with arrow keys, backspace, and delete
30
+ - **Auto-focus Management**: Automatic focus progression when typing
31
+ - **Accessibility**: Proper ARIA attributes and keyboard navigation
32
+ - **Custom Styling**: Flexible styling options for inputs and container
33
+ - **Disabled State**: Support for disabled state
34
+
35
+ ## Usage Examples
36
+
37
+ ### Basic OTP Input
38
+ \`\`\`tsx
39
+ <OtpInputs
40
+ length={6}
41
+ onChangeOTP={(otp) => console.log(otp)}
42
+ />
43
+ \`\`\`
44
+
45
+ ### Alphanumeric OTP
46
+ \`\`\`tsx
47
+ <OtpInputs
48
+ length={6}
49
+ isNumberInput={false}
50
+ onChangeOTP={(otp) => console.log(otp)}
51
+ />
52
+ \`\`\`
53
+
54
+ ## Props Overview
55
+
56
+ - **length**: Number of input fields (required)
57
+ - **isNumberInput**: Whether to restrict input to numbers only (default: true)
58
+ - **disabled**: Disable all inputs (default: false)
59
+ - **onChangeOTP**: Callback when OTP value changes (required)
60
+ - **inputStyle**: Inline styles for individual inputs
61
+ `,
62
+ },
63
+ },
64
+ },
65
+ tags: ["autodocs"],
66
+ argTypes: {
67
+ length: {
68
+ control: { type: "number", min: 1, max: 10 },
69
+ description: "Number of input fields",
70
+ },
71
+ isNumberInput: {
72
+ control: { type: "boolean" },
73
+ description: "Whether to restrict input to numbers only",
74
+ },
75
+ disabled: {
76
+ control: { type: "boolean" },
77
+ description: "Disable all inputs",
78
+ },
79
+ onChangeOTP: {
80
+ action: "otpChanged",
81
+ description: "Callback when OTP value changes",
82
+ },
83
+ },
84
+ }
85
+
86
+ export default meta
87
+ type Story = StoryObj<typeof OtpInputs>
88
+
89
+ export const Default: Story = {
90
+ args: {
91
+ length: 6,
92
+ isNumberInput: true,
93
+ disabled: false,
94
+ onChangeOTP: (otp: string) => console.log("OTP changed:", otp),
95
+ },
96
+ }
97
+
98
+ export const FourDigits: Story = {
99
+ args: {
100
+ length: 4,
101
+ isNumberInput: true,
102
+ disabled: false,
103
+ onChangeOTP: (otp: string) => console.log("OTP changed:", otp),
104
+ },
105
+ }
106
+
107
+ export const EightDigits: Story = {
108
+ args: {
109
+ length: 8,
110
+ isNumberInput: true,
111
+ onChangeOTP: (otp: string) => console.log("OTP changed:", otp),
112
+ },
113
+ }
114
+
115
+ export const Alphanumeric: Story = {
116
+ args: {
117
+ length: 6,
118
+ isNumberInput: false,
119
+ onChangeOTP: (otp: string) => console.log("OTP changed:", otp),
120
+ },
121
+ }
122
+
123
+ export const Disabled: Story = {
124
+ args: {
125
+ length: 6,
126
+ isNumberInput: true,
127
+ disabled: true,
128
+ onChangeOTP: (otp: string) => console.log("OTP changed:", otp),
129
+ },
130
+ }
131
+
132
+ export const Interactive: Story = {
133
+ render: () => {
134
+ const [otpValue, setOtpValue] = useState("")
135
+
136
+ return (
137
+ <div className="space-y-4">
138
+ <OtpInputs length={6} onChangeOTP={setOtpValue} />
139
+ <p className="text-sm text-gray-600">
140
+ Current OTP: {otpValue || "Empty"}
141
+ </p>
142
+ </div>
143
+ )
144
+ },
145
+ }
146
+
147
+ export const WithValidation: Story = {
148
+ render: () => {
149
+ const [isValid, setIsValid] = useState<boolean | null>(null)
150
+
151
+ const handleOtpChange = (otp: string) => {
152
+ if (otp.length === 6) {
153
+ setIsValid(otp === "123456")
154
+ } else {
155
+ setIsValid(null)
156
+ }
157
+ }
158
+
159
+ return (
160
+ <OtpInputs
161
+ length={6}
162
+ onChangeOTP={handleOtpChange}
163
+ isValid={isValid}
164
+ messages={{
165
+ error: "Invalid OTP, try: 123456",
166
+ }}
167
+ />
168
+ )
169
+ },
170
+ }
171
+
172
+ export const AllVariants: Story = {
173
+ render: () => {
174
+ const [otp4, setOtp4] = useState("")
175
+ const [otp6, setOtp6] = useState("")
176
+ const [otp8, setOtp8] = useState("")
177
+ const [alphanumericOtp, setAlphanumericOtp] = useState("")
178
+ const [validatedOtp, setValidatedOtp] = useState("")
179
+ const [isValid, setIsValid] = useState<boolean | null>(null)
180
+
181
+ const handleValidation = (otp: string) => {
182
+ setValidatedOtp(otp)
183
+ if (otp.length === 6) {
184
+ setIsValid(otp === "123456")
185
+ } else {
186
+ setIsValid(null)
187
+ }
188
+ }
189
+
190
+ return (
191
+ <div className="max-w-4xl space-y-8 p-6">
192
+ <div className="space-y-2">
193
+ <h2 className="text-2xl font-bold text-white">
194
+ OTP Input - All Variants
195
+ </h2>
196
+ <p className="text-gray-400">
197
+ Comprehensive showcase of all OTP input configurations, states, and
198
+ use cases.
199
+ </p>
200
+ </div>
201
+
202
+ {/* Basic Length Variants */}
203
+ <div className="space-y-4">
204
+ <h3 className="text-xl font-semibold text-white">Length Variants</h3>
205
+
206
+ <div className="space-y-3">
207
+ <div>
208
+ <label className="mb-2 block text-sm font-medium text-gray-300">
209
+ 4-Digit OTP (Common for SMS)
210
+ </label>
211
+ <OtpInputs
212
+ length={4}
213
+ isNumberInput={true}
214
+ onChangeOTP={setOtp4}
215
+ />
216
+ <p className="mt-1 text-xs text-gray-500">
217
+ Current: {otp4 || "Empty"}
218
+ </p>
219
+ </div>
220
+
221
+ <div>
222
+ <label className="mb-2 block text-sm font-medium text-gray-300">
223
+ 6-Digit OTP (Standard)
224
+ </label>
225
+ <OtpInputs
226
+ length={6}
227
+ isNumberInput={true}
228
+ onChangeOTP={setOtp6}
229
+ />
230
+ <p className="mt-1 text-xs text-gray-500">
231
+ Current: {otp6 || "Empty"}
232
+ </p>
233
+ </div>
234
+
235
+ <div>
236
+ <label className="mb-2 block text-sm font-medium text-gray-300">
237
+ 8-Digit OTP (Extended Security)
238
+ </label>
239
+ <OtpInputs
240
+ length={8}
241
+ isNumberInput={true}
242
+ onChangeOTP={setOtp8}
243
+ />
244
+ <p className="mt-1 text-xs text-gray-500">
245
+ Current: {otp8 || "Empty"}
246
+ </p>
247
+ </div>
248
+ </div>
249
+ </div>
250
+
251
+ {/* Input Type Variants */}
252
+ <div className="space-y-4">
253
+ <h3 className="text-xl font-semibold text-white">
254
+ Input Type Variants
255
+ </h3>
256
+
257
+ <div className="space-y-3">
258
+ <div>
259
+ <label className="mb-2 block text-sm font-medium text-gray-300">
260
+ Numeric Only (Default)
261
+ </label>
262
+ <OtpInputs
263
+ length={6}
264
+ isNumberInput={true}
265
+ onChangeOTP={(otp) => console.log("Numeric OTP:", otp)}
266
+ />
267
+ <p className="mt-1 text-xs text-gray-500">
268
+ Accepts only numbers (0-9)
269
+ </p>
270
+ </div>
271
+
272
+ <div>
273
+ <label className="mb-2 block text-sm font-medium text-gray-300">
274
+ Alphanumeric (Letters + Numbers)
275
+ </label>
276
+ <OtpInputs
277
+ length={6}
278
+ isNumberInput={false}
279
+ onChangeOTP={setAlphanumericOtp}
280
+ />
281
+ <p className="mt-1 text-xs text-gray-500">
282
+ Current: {alphanumericOtp || "Empty"} | Accepts letters and
283
+ numbers
284
+ </p>
285
+ </div>
286
+ </div>
287
+ </div>
288
+
289
+ {/* State Variants */}
290
+ <div className="space-y-4">
291
+ <h3 className="text-xl font-semibold text-white">State Variants</h3>
292
+
293
+ <div className="space-y-3">
294
+ <div>
295
+ <label className="mb-2 block text-sm font-medium text-gray-300">
296
+ Enabled State (Default)
297
+ </label>
298
+ <OtpInputs
299
+ length={6}
300
+ isNumberInput={true}
301
+ onChangeOTP={(otp) => console.log("Enabled OTP:", otp)}
302
+ />
303
+ </div>
304
+
305
+ <div>
306
+ <label className="mb-2 block text-sm font-medium text-gray-300">
307
+ Disabled State
308
+ </label>
309
+ <OtpInputs
310
+ length={6}
311
+ isNumberInput={true}
312
+ disabled={true}
313
+ onChangeOTP={(otp) => console.log("Disabled OTP:", otp)}
314
+ />
315
+ </div>
316
+ </div>
317
+ </div>
318
+
319
+ {/* Validation States */}
320
+ <div className="space-y-4">
321
+ <h3 className="text-xl font-semibold text-white">
322
+ Validation States
323
+ </h3>
324
+
325
+ <div className="space-y-3">
326
+ <div>
327
+ <label className="mb-2 block text-sm font-medium text-gray-300">
328
+ Neutral State (No validation)
329
+ </label>
330
+ <OtpInputs
331
+ length={6}
332
+ isNumberInput={true}
333
+ onChangeOTP={(otp) => console.log("Neutral OTP:", otp)}
334
+ isValid={null}
335
+ messages={{
336
+ neutral: "Enter your 6-digit verification code",
337
+ }}
338
+ />
339
+ </div>
340
+
341
+ <div>
342
+ <label className="mb-2 block text-sm font-medium text-gray-300">
343
+ Success State (Valid OTP)
344
+ </label>
345
+ <OtpInputs
346
+ length={6}
347
+ isNumberInput={true}
348
+ onChangeOTP={(otp) => console.log("Success OTP:", otp)}
349
+ isValid={true}
350
+ messages={{
351
+ success: "✓ Verification successful!",
352
+ }}
353
+ />
354
+ </div>
355
+
356
+ <div>
357
+ <label className="mb-2 block text-sm font-medium text-gray-300">
358
+ Error State (Invalid OTP)
359
+ </label>
360
+ <OtpInputs
361
+ length={6}
362
+ isNumberInput={true}
363
+ onChangeOTP={(otp) => console.log("Error OTP:", otp)}
364
+ isValid={false}
365
+ messages={{
366
+ error: "✗ Invalid code. Please try again.",
367
+ }}
368
+ />
369
+ </div>
370
+
371
+ <div>
372
+ <label className="mb-2 block text-sm font-medium text-gray-300">
373
+ Interactive Validation (Try: 123456)
374
+ </label>
375
+ <OtpInputs
376
+ length={6}
377
+ isNumberInput={true}
378
+ onChangeOTP={handleValidation}
379
+ isValid={isValid}
380
+ messages={{
381
+ neutral: "Enter 6-digit code",
382
+ success: "✓ Code verified successfully!",
383
+ error: "✗ Invalid code. Try: 123456",
384
+ }}
385
+ />
386
+ <p className="mt-1 text-xs text-gray-500">
387
+ Current: {validatedOtp || "Empty"} | Status:{" "}
388
+ {isValid === null
389
+ ? "Neutral"
390
+ : isValid === true
391
+ ? "Valid"
392
+ : "Invalid"}
393
+ </p>
394
+ </div>
395
+ </div>
396
+ </div>
397
+
398
+ {/* Custom Styling Examples */}
399
+ <div className="space-y-4">
400
+ <h3 className="text-xl font-semibold text-white">Custom Styling</h3>
401
+
402
+ <div className="space-y-3">
403
+ <div>
404
+ <label className="mb-2 block text-sm font-medium text-gray-300">
405
+ Custom Input Styling
406
+ </label>
407
+ <OtpInputs
408
+ length={6}
409
+ isNumberInput={true}
410
+ onChangeOTP={(otp) => console.log("Custom styled OTP:", otp)}
411
+ inputStyle={{
412
+ backgroundColor: "#1f2937",
413
+ borderColor: "#374151",
414
+ color: "#f9fafb",
415
+ }}
416
+ inputClassName="border-2 border-blue-500 focus:border-blue-400"
417
+ />
418
+ </div>
419
+ </div>
420
+ </div>
421
+
422
+ {/* Usage Examples */}
423
+ <div className="space-y-4">
424
+ <h3 className="text-xl font-semibold text-white">Common Use Cases</h3>
425
+
426
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
427
+ <div className="rounded-lg bg-gray-800 p-4">
428
+ <h4 className="mb-2 font-medium text-white">SMS Verification</h4>
429
+ <OtpInputs
430
+ length={4}
431
+ isNumberInput={true}
432
+ onChangeOTP={(otp) => console.log("SMS OTP:", otp)}
433
+ messages={{
434
+ neutral: "Enter SMS code",
435
+ }}
436
+ />
437
+ </div>
438
+
439
+ <div className="rounded-lg bg-gray-800 p-4">
440
+ <h4 className="mb-2 font-medium text-white">
441
+ Email Verification
442
+ </h4>
443
+ <OtpInputs
444
+ length={6}
445
+ isNumberInput={true}
446
+ onChangeOTP={(otp) => console.log("Email OTP:", otp)}
447
+ messages={{
448
+ neutral: "Enter email code",
449
+ }}
450
+ />
451
+ </div>
452
+
453
+ <div className="rounded-lg bg-gray-800 p-4">
454
+ <h4 className="mb-2 font-medium text-white">
455
+ 2FA Authentication
456
+ </h4>
457
+ <OtpInputs
458
+ length={6}
459
+ isNumberInput={true}
460
+ onChangeOTP={(otp) => console.log("2FA OTP:", otp)}
461
+ messages={{
462
+ neutral: "Enter authenticator code",
463
+ }}
464
+ />
465
+ </div>
466
+
467
+ <div className="rounded-lg bg-gray-800 p-4">
468
+ <h4 className="mb-2 font-medium text-white">Alphanumeric Code</h4>
469
+ <OtpInputs
470
+ length={6}
471
+ isNumberInput={false}
472
+ onChangeOTP={(otp) => console.log("Alphanumeric OTP:", otp)}
473
+ messages={{
474
+ neutral: "Enter alphanumeric code",
475
+ }}
476
+ />
477
+ </div>
478
+ </div>
479
+ </div>
480
+ </div>
481
+ )
482
+ },
483
+ parameters: {
484
+ docs: {
485
+ description: {
486
+ story: `
487
+ This comprehensive story demonstrates all available variants, states, and configurations of the OTP Input component:`,
488
+ },
489
+ },
490
+ },
491
+ }