aural-ui 3.0.7 → 4.1.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.
Files changed (183) hide show
  1. package/dist/components/aspect-ratio/AspectRatio.stories.tsx +290 -1199
  2. package/dist/components/avatar/Avatar.stories.tsx +235 -237
  3. package/dist/components/badge/Badge.stories.tsx +379 -116
  4. package/dist/components/banner/Banner.stories.tsx +445 -391
  5. package/dist/components/breadcrumb/Breadcrumb.stories.tsx +453 -199
  6. package/dist/components/button/Button.stories.tsx +585 -230
  7. package/dist/components/button/index.tsx +7 -7
  8. package/dist/components/card/Card.stories.tsx +619 -301
  9. package/dist/components/char-count/CharCount.stories.tsx +350 -248
  10. package/dist/components/checkbox/Checkbox.stories.tsx +309 -167
  11. package/dist/components/chip/Chip.stories.tsx +362 -168
  12. package/dist/components/circular-loader/CircularLoader.stories.tsx +221 -620
  13. package/dist/components/clamp-lines/ClampLines.stories.tsx +246 -117
  14. package/dist/components/collapsible/Collapsible.stories.tsx +391 -252
  15. package/dist/components/command/Command.stories.tsx +533 -856
  16. package/dist/components/dialog/Dialog.stories.tsx +505 -949
  17. package/dist/components/divider/Divider.stories.tsx +265 -502
  18. package/dist/components/dot-loader/DotLoader.stories.tsx +256 -257
  19. package/dist/components/drawer/Drawer.stories.tsx +659 -993
  20. package/dist/components/drawer/index.tsx +3 -3
  21. package/dist/components/dropdown/Dropdown.stories.tsx +643 -1018
  22. package/dist/components/form/Form.stories.tsx +560 -274
  23. package/dist/components/helper-text/HelperText.stories.tsx +199 -200
  24. package/dist/components/hover-card/HoverCard.stories.tsx +318 -1221
  25. package/dist/components/icon-button/IconButton.stories.tsx +837 -194
  26. package/dist/components/if-else/if-else.stories.tsx +370 -83
  27. package/dist/components/input/Input.stories.tsx +436 -368
  28. package/dist/components/label/Label.stories.tsx +156 -154
  29. package/dist/components/list/List.stories.tsx +485 -822
  30. package/dist/components/marquee/Marquee.stories.tsx +356 -694
  31. package/dist/components/otp-inputs/OtpInputs.stories.tsx +352 -410
  32. package/dist/components/overlay/Overlay.stories.tsx +452 -818
  33. package/dist/components/overlay/index.tsx +4 -4
  34. package/dist/components/pagination/Pagination.stories.tsx +721 -210
  35. package/dist/components/popover/Popover.stories.tsx +484 -873
  36. package/dist/components/radio/Radio.stories.tsx +432 -124
  37. package/dist/components/resizable/Resizable.stories.tsx +496 -752
  38. package/dist/components/scroll-area/ScrollArea.stories.tsx +384 -1006
  39. package/dist/components/search/Search.stories.tsx +314 -575
  40. package/dist/components/select/Select.stories.tsx +684 -787
  41. package/dist/components/sheet/Sheet.stories.tsx +671 -936
  42. package/dist/components/skelton/Skelton.stories.tsx +230 -764
  43. package/dist/components/slider/Slider.stories.tsx +384 -737
  44. package/dist/components/stepper/Stepper.stories.tsx +371 -514
  45. package/dist/components/switch/Switch.stories.tsx +461 -208
  46. package/dist/components/switch-case/SwitchCase.stories.tsx +367 -188
  47. package/dist/components/table/Table.stories.tsx +770 -914
  48. package/dist/components/tabs/Tabs.stories.tsx +459 -1400
  49. package/dist/components/tag/Tag.stories.tsx +714 -542
  50. package/dist/components/textarea/TextArea.stories.tsx +621 -562
  51. package/dist/components/thumbnail-tags/ThumbnailTags.stories.tsx +228 -148
  52. package/dist/components/toast/Toast.stories.tsx +452 -1333
  53. package/dist/components/toggle/Toggle.stories.tsx +488 -909
  54. package/dist/components/tooltip/Tooltip.stories.tsx +344 -1372
  55. package/dist/components/typography/Typography.stories.tsx +406 -89
  56. package/dist/hooks/use-change-state/UseChangeState.stories.tsx +309 -606
  57. package/dist/hooks/use-previous/UsePrevious.stories.tsx +367 -917
  58. package/dist/hooks/use-standalone-pagination/UseStandalonePagination.stories.tsx +639 -867
  59. package/dist/icons/Icons.stories.tsx +0 -12
  60. package/dist/icons/ai-avatar-icon/AiAvatarIcon.stories.tsx +226 -1013
  61. package/dist/icons/alert-icon/AlertIcon.stories.tsx +109 -929
  62. package/dist/icons/all-icons.tsx +124 -87
  63. package/dist/icons/angle-down-icon/AngleDownIcon.stories.tsx +140 -971
  64. package/dist/icons/apple-logo-icon/AppleLogoIcon.stories.tsx +148 -888
  65. package/dist/icons/arrow-box-left-icon/ArrowBoxLeftIcon.stories.tsx +135 -1019
  66. package/dist/icons/arrow-corner-up-left-icon/ArrowCornerUpLeftIcon.stories.tsx +137 -953
  67. package/dist/icons/arrow-corner-up-right-icon/ArrowCornerUpRightIcon.stories.tsx +138 -997
  68. package/dist/icons/arrow-left-icon/ArrowLeftIcon.stories.tsx +136 -942
  69. package/dist/icons/arrow-right-icon/ArrowRightIcon.stories.tsx +148 -1092
  70. package/dist/icons/arrow-right-up-icon/ArrowRightUpIcon.stories.tsx +146 -1211
  71. package/dist/icons/art-board-icon/ArtBoardIcon.stories.tsx +126 -615
  72. package/dist/icons/audio-bar-icon/AudioBarIcon.stories.tsx +144 -1164
  73. package/dist/icons/backward-ten-seconds-icon/BackwardTenSecondsIcon.stories.tsx +167 -985
  74. package/dist/icons/bubble-check-icon/BubbleCheckIcon.stories.tsx +122 -1179
  75. package/dist/icons/bubble-crossed-icon/BubbleCrossedIcon.stories.tsx +124 -1168
  76. package/dist/icons/bubble-sparkle-icon/BubbleSparkleIcon.stories.tsx +119 -850
  77. package/dist/icons/camera-icon/CameraIcon.stories.tsx +112 -1213
  78. package/dist/icons/capital-a-letter-icon/CapitalALetterIcon.stories.tsx +117 -934
  79. package/dist/icons/chevron-double-left-icon/ChevronDoubleLeftIcon.stories.tsx +160 -961
  80. package/dist/icons/chevron-double-right-icon/ChevronDoubleRightIcon.stories.tsx +163 -961
  81. package/dist/icons/chevron-down-icon/ChevronDownIcon.stories.tsx +144 -942
  82. package/dist/icons/chevron-left-icon/ChevronLeftIcon.stories.tsx +129 -966
  83. package/dist/icons/chevron-right-icon/ChevronRightIcon.stories.tsx +147 -964
  84. package/dist/icons/chevron-up-icon/ChevronUpIcon.stories.tsx +145 -975
  85. package/dist/icons/circle-tick-icon/CircleTickIcon.stories.tsx +150 -1142
  86. package/dist/icons/circular-play-icon/CircularPlayIcon.stories.tsx +114 -461
  87. package/dist/icons/coin-icon/CoinIcon.stories.tsx +124 -1322
  88. package/dist/icons/coin-toons-icon/CoinToonsIcon.stories.tsx +117 -1318
  89. package/dist/icons/column-wide-add-icon/ColumnWideAddIcon.stories.tsx +114 -903
  90. package/dist/icons/command-icon/CommandIcon.stories.tsx +127 -1042
  91. package/dist/icons/copy-icon/CopyIcon.stories.tsx +123 -962
  92. package/dist/icons/cross-circle-icon/CrossCircleIcon.stories.tsx +147 -999
  93. package/dist/icons/cross-icon/CrossIcon.stories.tsx +139 -960
  94. package/dist/icons/download-icon/DownloadIcon.stories.tsx +126 -820
  95. package/dist/icons/edit-big-icon/EditBigIcon.stories.tsx +124 -1031
  96. package/dist/icons/email-icon/EmailIcon.stories.tsx +115 -936
  97. package/dist/icons/expand-icon/ExpandIcon.stories.tsx +112 -1111
  98. package/dist/icons/eye-close-icon/EyeCloseIcon.stories.tsx +144 -1025
  99. package/dist/icons/eye-open-icon/EyeOpenIcon.stories.tsx +143 -1036
  100. package/dist/icons/feature-shine-icon/FeatureShineIcon.stories.tsx +127 -1011
  101. package/dist/icons/file-chart-icon/FileChartIcon.stories.tsx +126 -1056
  102. package/dist/icons/file-text-icon/FileTextIcon.stories.tsx +125 -614
  103. package/dist/icons/filter-bar-row-icon/FilterBarRowIcon.stories.tsx +119 -1050
  104. package/dist/icons/forward-ten-seconds-icon/ForwardTenSecondsIcon.stories.tsx +169 -989
  105. package/dist/icons/git-branch-icon/GitBranchIcon.stories.tsx +115 -1145
  106. package/dist/icons/git-fork-icon/GitForkIcon.stories.tsx +115 -1122
  107. package/dist/icons/globe-icon/GlobeIcon.stories.tsx +130 -313
  108. package/dist/icons/google-logo-icon/GoogleLogoIcon.stories.tsx +145 -940
  109. package/dist/icons/grip-vertical-icon/GripVerticalIcon.stories.tsx +119 -1174
  110. package/dist/icons/head-icon/HeadIcon.stories.tsx +111 -916
  111. package/dist/icons/heart-icon/HeartIcon.stories.tsx +120 -1019
  112. package/dist/icons/image-avatar-sparkle-icon/ImageAvatarSparkleIcon.stories.tsx +119 -683
  113. package/dist/icons/image-icon/ImageIcon.stories.tsx +105 -1121
  114. package/dist/icons/import-folder-icon/ImportFolderIcon.stories.tsx +111 -1192
  115. package/dist/icons/import-left-arrow-folder-icon/ImportLeftArrowFolderIcon.stories.tsx +136 -1256
  116. package/dist/icons/indian-flag-icon/IndianFlagIcon.stories.tsx +159 -962
  117. package/dist/icons/instagram-icon/InstagramIcon.stories.tsx +161 -1385
  118. package/dist/icons/layout-column-icon/LayoutColumnIcon.stories.tsx +124 -972
  119. package/dist/icons/layout-left-icon/LayoutLeftIcon.stories.tsx +119 -948
  120. package/dist/icons/layout-right-icon/LayoutRightIcon.stories.tsx +119 -942
  121. package/dist/icons/light-bulb-simple-icon/LightBulbSimpleIcon.stories.tsx +108 -1215
  122. package/dist/icons/linked-in-icon/LinkedInIcon.stories.tsx +154 -1517
  123. package/dist/icons/magic-book-icon/MagicBookIcon.stories.tsx +110 -1188
  124. package/dist/icons/magic-edit-icon/MagicEditIcon.stories.tsx +119 -678
  125. package/dist/icons/maintenance-icon/MaintenanceIcon.stories.tsx +123 -1184
  126. package/dist/icons/message-icon/MessageIcon.stories.tsx +114 -538
  127. package/dist/icons/minimize-icon/MinimizeIcon.stories.tsx +116 -1158
  128. package/dist/icons/moon-icon/MoonIcon.stories.tsx +120 -536
  129. package/dist/icons/move-horizontal-icon/MoveHorizontalIcon.stories.tsx +109 -1184
  130. package/dist/icons/move-vertical-icon/MoveVerticalIcon.stories.tsx +115 -1134
  131. package/dist/icons/musical-note-icon/MusicalNoteIcon.stories.tsx +119 -971
  132. package/dist/icons/notepad-icon/NotepadIcon.stories.tsx +111 -1100
  133. package/dist/icons/notes-icon/NotesIcon.stories.tsx +119 -1101
  134. package/dist/icons/page-search-icon/PageSearchIcon.stories.tsx +109 -1111
  135. package/dist/icons/page-text-icon/PageTextIcon.stories.tsx +122 -684
  136. package/dist/icons/paint-roll-icon/PaintRollIcon.stories.tsx +113 -954
  137. package/dist/icons/paper-plane-icon/PaperPlaneIcon.stories.tsx +112 -877
  138. package/dist/icons/pause-icon/PauseIcon.stories.tsx +113 -1000
  139. package/dist/icons/pencil-icon/PencilIcon.stories.tsx +115 -1070
  140. package/dist/icons/phone-icon/PhoneIcon.stories.tsx +115 -978
  141. package/dist/icons/plus-icon/PlusIcon.stories.tsx +106 -1093
  142. package/dist/icons/pocket-studio-icon/PocketStudioIcon.stories.tsx +107 -829
  143. package/dist/icons/scroll-down-icon/ScrollDownIcon.stories.tsx +102 -469
  144. package/dist/icons/search-icon/SearchIcon.stories.tsx +111 -1124
  145. package/dist/icons/setting-icon/SettingIcon.stories.tsx +107 -970
  146. package/dist/icons/share-icon/ShareIcon.stories.tsx +120 -1025
  147. package/dist/icons/shield-icon/ShieldIcon.stories.tsx +117 -931
  148. package/dist/icons/site-logo-icon/SiteLogoIcon.stories.tsx +137 -1104
  149. package/dist/icons/skip-backward-icon/SkipBackwardIcon.stories.tsx +172 -982
  150. package/dist/icons/skip-forward-icon/SkipForwardIcon.stories.tsx +164 -983
  151. package/dist/icons/sparkles-soft-icon/SparklesSoftIcon.stories.tsx +105 -958
  152. package/dist/icons/spinner-gradient-icon/SpinnerGradientIcon.stories.tsx +158 -580
  153. package/dist/icons/spinner-gradient-icon/index.tsx +6 -1
  154. package/dist/icons/spinner-solid-icon/SpinnerSolidIcon.stories.tsx +158 -587
  155. package/dist/icons/spinner-solid-icon/index.tsx +6 -1
  156. package/dist/icons/spinner-solid-neutral-icon/SpinnerSolidINeutralcon.stories.tsx +146 -682
  157. package/dist/icons/spinner-solid-neutral-icon/index.tsx +1 -1
  158. package/dist/icons/star-icon/StarIcon.stories.tsx +124 -904
  159. package/dist/icons/store-coin-icon/StoreCoinIcon.stories.tsx +112 -964
  160. package/dist/icons/suggestion-icon/SuggestionIcon.stories.tsx +116 -852
  161. package/dist/icons/sun-icon/SunIcon.stories.tsx +120 -831
  162. package/dist/icons/text-color-icon/TextColorIcon.stories.tsx +116 -950
  163. package/dist/icons/text-indicator-icon/TextIndicatorIcon.stories.tsx +123 -980
  164. package/dist/icons/threads-icon/ThreadsIcon.stories.tsx +156 -1427
  165. package/dist/icons/tick-circle-icon/TickCircleIcon.stories.tsx +146 -1142
  166. package/dist/icons/tick-icon/TickIcon.stories.tsx +145 -1276
  167. package/dist/icons/trash-icon/TrashIcon.stories.tsx +108 -933
  168. package/dist/icons/twitter-x-icon/TwitterXIcon.stories.tsx +157 -1402
  169. package/dist/icons/upload-icon/UploadIcon.stories.tsx +115 -889
  170. package/dist/icons/vertical-menu-icon/VerticalMenuIcon.stories.tsx +118 -984
  171. package/dist/icons/video-play-list-icon/VideoPlaylistIcon.stories.tsx +125 -1049
  172. package/dist/icons/voice-playing-icon/VoicePlayingIcon.stories.tsx +123 -1356
  173. package/dist/icons/volume-full-icon/VolumeFullIcon.stories.tsx +110 -1171
  174. package/dist/icons/volume-half-icon/VolumeHalfIcon.stories.tsx +112 -1093
  175. package/dist/icons/volume-off-icon/VolumeOffIcon.stories.tsx +115 -1087
  176. package/dist/icons/warning-icon/WarningIcon.stories.tsx +122 -1046
  177. package/dist/icons/youtube-icon/YoutubeIcon.stories.tsx +161 -936
  178. package/dist/index.cjs +84 -84
  179. package/dist/index.js +84 -84
  180. package/dist/styles/aural-all-theme.css +1222 -0
  181. package/dist/styles/{aural-theme.css → aural-dark-theme.css} +15 -3
  182. package/dist/styles/aural-light-theme.css +1047 -0
  183. package/package.json +1 -1
@@ -1,6 +1,8 @@
1
1
  import React, { useState } from "react"
2
2
  import type { Meta, StoryObj } from "@storybook/react-vite"
3
3
 
4
+ import { AuralComponentDocsPage } from "src/ui/story-spec/components/component-story-docs-page"
5
+
4
6
  import OtpInputs from "."
5
7
 
6
8
  const meta: Meta<typeof OtpInputs> = {
@@ -8,58 +10,29 @@ const meta: Meta<typeof OtpInputs> = {
8
10
  component: OtpInputs,
9
11
  parameters: {
10
12
  layout: "centered",
11
- backgrounds: {
12
- default: "dark",
13
- values: [
14
- { name: "dark", value: "#0a0a0a" },
15
- { name: "light", value: "#ffffff" },
16
- ],
17
- },
18
13
  docs: {
19
14
  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
- `,
15
+ component:
16
+ "An accessible OTP (One-Time Password) input that renders individual character boxes for entering verification codes. Supports configurable length, numeric or alphanumeric modes, keyboard navigation with auto-focus progression, paste handling, disabled state, and live validation feedback via helper text.",
62
17
  },
18
+ page: () => (
19
+ <AuralComponentDocsPage
20
+ features={[
21
+ {
22
+ title: "Configurable Length",
23
+ description: "1–10 digit boxes",
24
+ },
25
+ {
26
+ title: "Auto-focus & Paste",
27
+ description: "Keyboard nav built in",
28
+ },
29
+ {
30
+ title: "Numeric or Alpha",
31
+ description: "isNumberInput toggle",
32
+ },
33
+ ]}
34
+ />
35
+ ),
63
36
  },
64
37
  },
65
38
  tags: ["autodocs"],
@@ -70,7 +43,7 @@ A flexible and accessible OTP (One-Time Password) input component that allows us
70
43
  },
71
44
  isNumberInput: {
72
45
  control: { type: "boolean" },
73
- description: "Whether to restrict input to numbers only",
46
+ description: "Restrict input to digits only",
74
47
  },
75
48
  disabled: {
76
49
  control: { type: "boolean" },
@@ -78,7 +51,7 @@ A flexible and accessible OTP (One-Time Password) input component that allows us
78
51
  },
79
52
  onChangeOTP: {
80
53
  action: "otpChanged",
81
- description: "Callback when OTP value changes",
54
+ description: "Callback fired when the composed OTP string changes",
82
55
  },
83
56
  },
84
57
  }
@@ -86,405 +59,374 @@ A flexible and accessible OTP (One-Time Password) input component that allows us
86
59
  export default meta
87
60
  type Story = StoryObj<typeof OtpInputs>
88
61
 
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
- }
62
+ // ─── 1. Playground ────────────────────────────────────────────────────────────
97
63
 
98
- export const FourDigits: Story = {
64
+ export const Playground: Story = {
99
65
  args: {
100
- length: 4,
66
+ length: 6,
101
67
  isNumberInput: true,
102
68
  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),
69
+ onChangeOTP: (otp: string) => console.log("OTP:", otp),
120
70
  },
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),
71
+ render: (args) => (
72
+ <div className="w-full max-w-sm space-y-4">
73
+ <OtpInputs {...args} />
74
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border px-4 py-3">
75
+ <code className="text-fm-secondary text-fm-md leading-fm-md font-(--font-fm-mono)">
76
+ {`<OtpInputs length={${args.length}} isNumberInput={${args.isNumberInput}} disabled={${args.disabled}} />`}
77
+ </code>
78
+ </div>
79
+ </div>
80
+ ),
81
+ parameters: {
82
+ docs: {
83
+ description: {
84
+ story:
85
+ "Use the sidebar controls to adjust length, numeric mode, and disabled state. The code snippet updates to reflect the current props.",
86
+ },
87
+ },
129
88
  },
130
89
  }
131
90
 
132
- export const Interactive: Story = {
133
- render: () => {
134
- const [otpValue, setOtpValue] = useState("")
91
+ // ─── 2. Configurations ────────────────────────────────────────────────────────
135
92
 
136
- return (
93
+ export const Configurations: Story = {
94
+ render: () => (
95
+ <div className="space-y-8">
96
+ {/* Length variants */}
137
97
  <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>
98
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
99
+ Length
100
+ </h4>
101
+ <div className="space-y-6">
102
+ <div className="space-y-2 text-center">
103
+ <OtpInputs
104
+ length={4}
105
+ isNumberInput={true}
106
+ onChangeOTP={(otp) => console.log("4-digit OTP:", otp)}
107
+ messages={{ neutral: "4-digit code" }}
108
+ />
109
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
110
+ 4-digit — common for SMS PIN
111
+ </p>
112
+ </div>
113
+ <div className="space-y-2 text-center">
114
+ <OtpInputs
115
+ length={6}
116
+ isNumberInput={true}
117
+ onChangeOTP={(otp) => console.log("6-digit OTP:", otp)}
118
+ messages={{ neutral: "6-digit code" }}
119
+ />
120
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
121
+ 6-digit — standard authenticator / email
122
+ </p>
123
+ </div>
124
+ <div className="space-y-2 text-center">
125
+ <OtpInputs
126
+ length={8}
127
+ isNumberInput={true}
128
+ onChangeOTP={(otp) => console.log("8-digit OTP:", otp)}
129
+ messages={{ neutral: "8-digit code" }}
130
+ />
131
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
132
+ 8-digit — extended security token
133
+ </p>
134
+ </div>
135
+ </div>
142
136
  </div>
143
- )
144
- },
145
- }
146
-
147
- export const WithValidation: Story = {
148
- render: () => {
149
- const [isValid, setIsValid] = useState<boolean | null>(null)
150
137
 
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
- )
138
+ {/* Input type */}
139
+ <div className="space-y-4">
140
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
141
+ Input Type
142
+ </h4>
143
+ <div className="space-y-6">
144
+ <div className="space-y-2 text-center">
145
+ <OtpInputs
146
+ length={6}
147
+ isNumberInput={true}
148
+ onChangeOTP={(otp) => console.log("Numeric OTP:", otp)}
149
+ messages={{ neutral: "Digits only (0–9)" }}
150
+ />
151
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
152
+ Numeric accepts digits only
153
+ </p>
154
+ </div>
155
+ <div className="space-y-2 text-center">
156
+ <OtpInputs
157
+ length={6}
158
+ isNumberInput={false}
159
+ onChangeOTP={(otp) => console.log("Alphanumeric OTP:", otp)}
160
+ messages={{ neutral: "Letters + numbers" }}
161
+ />
162
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
163
+ Alphanumeric — accepts letters and digits
164
+ </p>
165
+ </div>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ ),
170
+ parameters: {
171
+ docs: {
172
+ description: {
173
+ story:
174
+ "Two configuration axes: code length (4 / 6 / 8 digits) and input type (numeric vs alphanumeric). Combine both axes to cover every common OTP use case.",
175
+ },
176
+ },
169
177
  },
170
178
  }
171
179
 
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
- }
180
+ // ─── 3. States ────────────────────────────────────────────────────────────────
189
181
 
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.
182
+ export const States: Story = {
183
+ render: () => (
184
+ <div className="space-y-8">
185
+ <div className="space-y-4">
186
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
187
+ Default
188
+ </h4>
189
+ <div className="space-y-2 text-center">
190
+ <OtpInputs
191
+ length={6}
192
+ isNumberInput={true}
193
+ onChangeOTP={(otp) => console.log("Default OTP:", otp)}
194
+ messages={{ neutral: "Enter your 6-digit verification code" }}
195
+ />
196
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
197
+ Ready to receive input
199
198
  </p>
200
199
  </div>
200
+ </div>
201
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>
202
+ <div className="space-y-4">
203
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
204
+ Disabled
205
+ </h4>
206
+ <div className="space-y-2 text-center">
207
+ <OtpInputs
208
+ length={6}
209
+ isNumberInput={true}
210
+ disabled={true}
211
+ onChangeOTP={() => {}}
212
+ messages={{ neutral: "Input is currently disabled" }}
213
+ />
214
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
215
+ All fields locked — no interaction possible
216
+ </p>
249
217
  </div>
218
+ </div>
250
219
 
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>
220
+ <div className="space-y-4">
221
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
222
+ Validation Success
223
+ </h4>
224
+ <div className="space-y-2 text-center">
225
+ <OtpInputs
226
+ length={6}
227
+ isNumberInput={true}
228
+ onChangeOTP={() => {}}
229
+ isValid={true}
230
+ messages={{ success: "Verification successful" }}
231
+ />
232
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
233
+ Code accepted
234
+ </p>
287
235
  </div>
236
+ </div>
288
237
 
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>
238
+ <div className="space-y-4">
239
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
240
+ Validation Error
241
+ </h4>
242
+ <div className="space-y-2 text-center">
243
+ <OtpInputs
244
+ length={6}
245
+ isNumberInput={true}
246
+ onChangeOTP={() => {}}
247
+ isValid={false}
248
+ messages={{ error: "Invalid code. Please try again." }}
249
+ />
250
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
251
+ Code rejected — helper text turns negative
252
+ </p>
317
253
  </div>
254
+ </div>
255
+ </div>
256
+ ),
257
+ parameters: {
258
+ docs: {
259
+ description: {
260
+ story:
261
+ "Four states: default (awaiting input), disabled (locked), validation success, and validation error. Each state drives distinct helper text and visual feedback.",
262
+ },
263
+ },
264
+ },
265
+ }
318
266
 
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>
267
+ // ─── 4. Interactive ───────────────────────────────────────────────────────────
370
268
 
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>
269
+ export const Interactive: Story = {
270
+ render: () => {
271
+ type Step = "idle" | "sent" | "verified" | "failed"
397
272
 
398
- {/* Custom Styling Examples */}
399
- <div className="space-y-4">
400
- <h3 className="text-xl font-semibold text-white">Custom Styling</h3>
273
+ const VALID_CODE = "123456"
401
274
 
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>
275
+ const InteractiveDemo = () => {
276
+ const [step, setStep] = useState<Step>("idle")
277
+ const [isValid, setIsValid] = useState<boolean | null>(null)
421
278
 
422
- {/* Usage Examples */}
423
- <div className="space-y-4">
424
- <h3 className="text-xl font-semibold text-white">Common Use Cases</h3>
279
+ const handleSend = () => {
280
+ setStep("sent")
281
+ setIsValid(null)
282
+ }
425
283
 
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>
284
+ const handleOtpChange = (value: string) => {
285
+ if (value.length === 6) {
286
+ const valid = value === VALID_CODE
287
+ setIsValid(valid)
288
+ setStep(valid ? "verified" : "failed")
289
+ } else {
290
+ setIsValid(null)
291
+ if (step === "verified" || step === "failed") {
292
+ setStep("sent")
293
+ }
294
+ }
295
+ }
438
296
 
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>
297
+ const handleReset = () => {
298
+ setStep("idle")
299
+ setIsValid(null)
300
+ }
452
301
 
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>
302
+ const stepMessages = {
303
+ neutral: "Enter the 6-digit code (hint: 123456)",
304
+ success: "Identity verified — welcome back!",
305
+ error: "Incorrect code. Try again or resend.",
306
+ }
466
307
 
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
- />
308
+ return (
309
+ <div className="w-full p-8">
310
+ <div className="mx-auto max-w-3xl space-y-6">
311
+ <div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
312
+ {/* Controls panel */}
313
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary space-y-5 rounded-xl border p-5">
314
+ <p className="text-fm-primary font-fm-brand text-fm-sm leading-fm-sm font-semibold tracking-widest uppercase">
315
+ Flow
316
+ </p>
317
+
318
+ <div className="space-y-2">
319
+ <button
320
+ onClick={handleSend}
321
+ disabled={step === "sent" || step === "verified"}
322
+ className="border-fm-divider-secondary bg-fm-surface-secondary text-fm-primary font-fm-text text-fm-sm leading-fm-sm w-full rounded-lg border px-4 py-2.5 font-medium transition-opacity disabled:opacity-40"
323
+ >
324
+ {step === "idle" ? "Send Code" : "Code Sent"}
325
+ </button>
326
+ <button
327
+ onClick={handleReset}
328
+ className="border-fm-divider-secondary text-fm-secondary font-fm-text text-fm-sm leading-fm-sm w-full rounded-lg border px-4 py-2.5 font-medium"
329
+ >
330
+ Reset
331
+ </button>
332
+ </div>
333
+
334
+ <div className="border-fm-divider-secondary border-t pt-4" />
335
+
336
+ <div className="space-y-1">
337
+ <p className="text-fm-primary font-fm-brand text-fm-sm leading-fm-sm font-semibold tracking-widest uppercase">
338
+ Status
339
+ </p>
340
+ <div className="space-y-1">
341
+ {(
342
+ [
343
+ { key: "idle", label: "Waiting for send" },
344
+ { key: "sent", label: "Code dispatched" },
345
+ { key: "verified", label: "Verified" },
346
+ { key: "failed", label: "Failed attempt" },
347
+ ] as const
348
+ ).map(({ key, label }) => (
349
+ <div key={key} className="flex items-center gap-2">
350
+ <span
351
+ className={
352
+ step === key
353
+ ? key === "verified"
354
+ ? "text-fm-positive"
355
+ : key === "failed"
356
+ ? "text-fm-negative"
357
+ : "text-fm-primary"
358
+ : "text-fm-tertiary"
359
+ }
360
+ >
361
+ {step === key ? "●" : "○"}
362
+ </span>
363
+ <p
364
+ className={`font-fm-text text-fm-sm leading-fm-sm ${step === key ? "text-fm-primary font-medium" : "text-fm-tertiary"}`}
365
+ >
366
+ {label}
367
+ </p>
368
+ </div>
369
+ ))}
370
+ </div>
371
+ </div>
372
+ </div>
373
+
374
+ {/* Preview stage */}
375
+ <div className="flex flex-col gap-3 lg:col-span-2">
376
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-xl border p-6">
377
+ <div className="space-y-4">
378
+ <div className="space-y-1">
379
+ <p className="text-fm-primary font-fm-text text-fm-md leading-fm-md font-semibold">
380
+ Verify your identity
381
+ </p>
382
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
383
+ {step === "idle"
384
+ ? 'Click "Send Code" to begin.'
385
+ : "A 6-digit code has been sent. Enter it below."}
386
+ </p>
387
+ </div>
388
+
389
+ {step !== "idle" && (
390
+ <OtpInputs
391
+ length={6}
392
+ isNumberInput={true}
393
+ disabled={step === "verified"}
394
+ onChangeOTP={handleOtpChange}
395
+ isValid={isValid}
396
+ messages={stepMessages}
397
+ />
398
+ )}
399
+
400
+ {step === "idle" && (
401
+ <div className="border-fm-divider-secondary rounded-lg border px-4 py-3">
402
+ <p className="text-fm-tertiary font-fm-text text-fm-sm leading-fm-sm">
403
+ OTP input will appear after sending the code.
404
+ </p>
405
+ </div>
406
+ )}
407
+ </div>
408
+ </div>
409
+
410
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border px-4 py-3">
411
+ <code className="text-fm-secondary text-fm-md leading-fm-md font-(--font-fm-mono)">
412
+ {`<OtpInputs length={6} isNumberInput isValid={${isValid === null ? "null" : isValid}} />`}
413
+ </code>
414
+ </div>
415
+ </div>
477
416
  </div>
478
417
  </div>
479
418
  </div>
480
- </div>
481
- )
419
+ )
420
+ }
421
+
422
+ return <InteractiveDemo />
482
423
  },
483
424
  parameters: {
425
+ layout: "fullscreen",
484
426
  docs: {
485
427
  description: {
486
- story: `
487
- This comprehensive story demonstrates all available variants, states, and configurations of the OTP Input component:`,
428
+ story:
429
+ 'A three-step verification flow: click "Send Code" to reveal the OTP input, type the 6-digit code (hint: 123456) to trigger live validation, and watch the helper text transition between neutral, success, and error states. Reset returns to step one.',
488
430
  },
489
431
  },
490
432
  },