aural-ui 4.0.1 → 4.2.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 (175) hide show
  1. package/README.md +8 -1
  2. package/dist/components/aspect-ratio/AspectRatio.stories.tsx +290 -1228
  3. package/dist/components/avatar/Avatar.stories.tsx +219 -235
  4. package/dist/components/badge/Badge.stories.tsx +379 -116
  5. package/dist/components/banner/Banner.stories.tsx +445 -391
  6. package/dist/components/breadcrumb/Breadcrumb.stories.tsx +453 -199
  7. package/dist/components/button/Button.stories.tsx +585 -230
  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 -636
  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 +530 -867
  16. package/dist/components/dialog/Dialog.stories.tsx +501 -950
  17. package/dist/components/divider/Divider.stories.tsx +264 -527
  18. package/dist/components/dot-loader/DotLoader.stories.tsx +256 -257
  19. package/dist/components/drawer/Drawer.stories.tsx +659 -1023
  20. package/dist/components/dropdown/Dropdown.stories.tsx +643 -1028
  21. package/dist/components/form/Form.stories.tsx +560 -274
  22. package/dist/components/helper-text/HelperText.stories.tsx +199 -200
  23. package/dist/components/hover-card/HoverCard.stories.tsx +318 -1254
  24. package/dist/components/icon-button/IconButton.stories.tsx +837 -194
  25. package/dist/components/if-else/if-else.stories.tsx +370 -83
  26. package/dist/components/input/Input.stories.tsx +436 -368
  27. package/dist/components/label/Label.stories.tsx +156 -154
  28. package/dist/components/list/List.stories.tsx +484 -835
  29. package/dist/components/marquee/Marquee.stories.tsx +356 -712
  30. package/dist/components/otp-inputs/OtpInputs.stories.tsx +352 -422
  31. package/dist/components/overlay/Overlay.stories.tsx +452 -824
  32. package/dist/components/pagination/Pagination.stories.tsx +721 -210
  33. package/dist/components/popover/Popover.stories.tsx +481 -896
  34. package/dist/components/radio/Radio.stories.tsx +432 -124
  35. package/dist/components/resizable/Resizable.stories.tsx +495 -799
  36. package/dist/components/scroll-area/ScrollArea.stories.tsx +383 -1059
  37. package/dist/components/search/Search.stories.tsx +312 -595
  38. package/dist/components/select/Select.stories.tsx +684 -789
  39. package/dist/components/sheet/Sheet.stories.tsx +671 -950
  40. package/dist/components/skelton/Skelton.stories.tsx +230 -764
  41. package/dist/components/slider/Slider.stories.tsx +383 -760
  42. package/dist/components/stepper/Stepper.stories.tsx +371 -514
  43. package/dist/components/switch/Switch.stories.tsx +461 -208
  44. package/dist/components/switch-case/SwitchCase.stories.tsx +367 -188
  45. package/dist/components/table/Table.stories.tsx +770 -916
  46. package/dist/components/tabs/Tabs.stories.tsx +458 -1455
  47. package/dist/components/tag/Tag.stories.tsx +714 -542
  48. package/dist/components/textarea/TextArea.stories.tsx +621 -562
  49. package/dist/components/thumbnail-tags/ThumbnailTags.stories.tsx +228 -154
  50. package/dist/components/toast/Toast.stories.tsx +452 -1339
  51. package/dist/components/toggle/Toggle.stories.tsx +488 -931
  52. package/dist/components/tooltip/Tooltip.stories.tsx +344 -1388
  53. package/dist/components/typography/Typography.stories.tsx +406 -89
  54. package/dist/hooks/use-change-state/UseChangeState.stories.tsx +309 -606
  55. package/dist/hooks/use-previous/UsePrevious.stories.tsx +367 -917
  56. package/dist/hooks/use-standalone-pagination/UseStandalonePagination.stories.tsx +639 -867
  57. package/dist/icons/Icons.stories.tsx +0 -12
  58. package/dist/icons/ai-avatar-icon/AiAvatarIcon.stories.tsx +223 -1060
  59. package/dist/icons/alert-icon/AlertIcon.stories.tsx +106 -968
  60. package/dist/icons/all-icons.tsx +37 -16
  61. package/dist/icons/angle-down-icon/AngleDownIcon.stories.tsx +137 -1010
  62. package/dist/icons/apple-logo-icon/AppleLogoIcon.stories.tsx +145 -935
  63. package/dist/icons/arrow-box-left-icon/ArrowBoxLeftIcon.stories.tsx +132 -1046
  64. package/dist/icons/arrow-corner-up-left-icon/ArrowCornerUpLeftIcon.stories.tsx +134 -986
  65. package/dist/icons/arrow-corner-up-right-icon/ArrowCornerUpRightIcon.stories.tsx +135 -1028
  66. package/dist/icons/arrow-left-icon/ArrowLeftIcon.stories.tsx +133 -971
  67. package/dist/icons/arrow-right-icon/ArrowRightIcon.stories.tsx +145 -1123
  68. package/dist/icons/arrow-right-up-icon/ArrowRightUpIcon.stories.tsx +143 -1252
  69. package/dist/icons/art-board-icon/ArtBoardIcon.stories.tsx +123 -632
  70. package/dist/icons/audio-bar-icon/AudioBarIcon.stories.tsx +141 -1223
  71. package/dist/icons/backward-ten-seconds-icon/BackwardTenSecondsIcon.stories.tsx +164 -1018
  72. package/dist/icons/bubble-check-icon/BubbleCheckIcon.stories.tsx +121 -1236
  73. package/dist/icons/bubble-crossed-icon/BubbleCrossedIcon.stories.tsx +121 -1213
  74. package/dist/icons/bubble-sparkle-icon/BubbleSparkleIcon.stories.tsx +116 -893
  75. package/dist/icons/camera-icon/CameraIcon.stories.tsx +109 -1254
  76. package/dist/icons/capital-a-letter-icon/CapitalALetterIcon.stories.tsx +114 -975
  77. package/dist/icons/chevron-double-left-icon/ChevronDoubleLeftIcon.stories.tsx +157 -994
  78. package/dist/icons/chevron-double-right-icon/ChevronDoubleRightIcon.stories.tsx +160 -992
  79. package/dist/icons/chevron-down-icon/ChevronDownIcon.stories.tsx +140 -970
  80. package/dist/icons/chevron-left-icon/ChevronLeftIcon.stories.tsx +126 -993
  81. package/dist/icons/chevron-right-icon/ChevronRightIcon.stories.tsx +144 -987
  82. package/dist/icons/chevron-up-icon/ChevronUpIcon.stories.tsx +141 -1007
  83. package/dist/icons/circle-tick-icon/CircleTickIcon.stories.tsx +147 -1187
  84. package/dist/icons/circular-play-icon/CircularPlayIcon.stories.tsx +110 -476
  85. package/dist/icons/coin-icon/CoinIcon.stories.tsx +120 -1364
  86. package/dist/icons/coin-toons-icon/CoinToonsIcon.stories.tsx +113 -1360
  87. package/dist/icons/column-wide-add-icon/ColumnWideAddIcon.stories.tsx +111 -942
  88. package/dist/icons/command-icon/CommandIcon.stories.tsx +124 -1087
  89. package/dist/icons/copy-icon/CopyIcon.stories.tsx +119 -996
  90. package/dist/icons/cross-circle-icon/CrossCircleIcon.stories.tsx +144 -1046
  91. package/dist/icons/cross-icon/CrossIcon.stories.tsx +136 -999
  92. package/dist/icons/download-icon/DownloadIcon.stories.tsx +123 -857
  93. package/dist/icons/edit-big-icon/EditBigIcon.stories.tsx +121 -1080
  94. package/dist/icons/email-icon/EmailIcon.stories.tsx +112 -979
  95. package/dist/icons/expand-icon/ExpandIcon.stories.tsx +109 -1146
  96. package/dist/icons/eye-close-icon/EyeCloseIcon.stories.tsx +141 -1068
  97. package/dist/icons/eye-open-icon/EyeOpenIcon.stories.tsx +140 -1081
  98. package/dist/icons/feature-shine-icon/FeatureShineIcon.stories.tsx +124 -1050
  99. package/dist/icons/file-chart-icon/FileChartIcon.stories.tsx +123 -1091
  100. package/dist/icons/file-text-icon/FileTextIcon.stories.tsx +122 -633
  101. package/dist/icons/filter-bar-row-icon/FilterBarRowIcon.stories.tsx +116 -1087
  102. package/dist/icons/forward-ten-seconds-icon/ForwardTenSecondsIcon.stories.tsx +166 -1020
  103. package/dist/icons/git-branch-icon/GitBranchIcon.stories.tsx +112 -1182
  104. package/dist/icons/git-fork-icon/GitForkIcon.stories.tsx +112 -1155
  105. package/dist/icons/globe-icon/GlobeIcon.stories.tsx +127 -325
  106. package/dist/icons/google-logo-icon/GoogleLogoIcon.stories.tsx +142 -985
  107. package/dist/icons/grip-vertical-icon/GripVerticalIcon.stories.tsx +116 -1217
  108. package/dist/icons/head-icon/HeadIcon.stories.tsx +108 -953
  109. package/dist/icons/heart-icon/HeartIcon.stories.tsx +117 -1060
  110. package/dist/icons/image-avatar-sparkle-icon/ImageAvatarSparkleIcon.stories.tsx +116 -716
  111. package/dist/icons/image-icon/ImageIcon.stories.tsx +102 -1164
  112. package/dist/icons/import-folder-icon/ImportFolderIcon.stories.tsx +108 -1233
  113. package/dist/icons/import-left-arrow-folder-icon/ImportLeftArrowFolderIcon.stories.tsx +133 -1289
  114. package/dist/icons/indian-flag-icon/IndianFlagIcon.stories.tsx +155 -1012
  115. package/dist/icons/instagram-icon/InstagramIcon.stories.tsx +158 -1438
  116. package/dist/icons/layout-column-icon/LayoutColumnIcon.stories.tsx +121 -1011
  117. package/dist/icons/layout-left-icon/LayoutLeftIcon.stories.tsx +116 -981
  118. package/dist/icons/layout-right-icon/LayoutRightIcon.stories.tsx +116 -979
  119. package/dist/icons/light-bulb-simple-icon/LightBulbSimpleIcon.stories.tsx +105 -1252
  120. package/dist/icons/linked-in-icon/LinkedInIcon.stories.tsx +151 -1554
  121. package/dist/icons/magic-book-icon/MagicBookIcon.stories.tsx +107 -1227
  122. package/dist/icons/magic-edit-icon/MagicEditIcon.stories.tsx +116 -707
  123. package/dist/icons/maintenance-icon/MaintenanceIcon.stories.tsx +119 -1226
  124. package/dist/icons/message-icon/MessageIcon.stories.tsx +111 -557
  125. package/dist/icons/minimize-icon/MinimizeIcon.stories.tsx +112 -1198
  126. package/dist/icons/moon-icon/MoonIcon.stories.tsx +117 -557
  127. package/dist/icons/move-horizontal-icon/MoveHorizontalIcon.stories.tsx +106 -1235
  128. package/dist/icons/move-vertical-icon/MoveVerticalIcon.stories.tsx +112 -1185
  129. package/dist/icons/musical-note-icon/MusicalNoteIcon.stories.tsx +116 -1012
  130. package/dist/icons/notepad-icon/NotepadIcon.stories.tsx +108 -1137
  131. package/dist/icons/notes-icon/NotesIcon.stories.tsx +116 -1138
  132. package/dist/icons/page-search-icon/PageSearchIcon.stories.tsx +106 -1146
  133. package/dist/icons/page-text-icon/PageTextIcon.stories.tsx +119 -719
  134. package/dist/icons/paint-roll-icon/PaintRollIcon.stories.tsx +110 -999
  135. package/dist/icons/paper-plane-icon/PaperPlaneIcon.stories.tsx +109 -912
  136. package/dist/icons/pause-icon/PauseIcon.stories.tsx +110 -1041
  137. package/dist/icons/pencil-icon/PencilIcon.stories.tsx +112 -1109
  138. package/dist/icons/phone-icon/PhoneIcon.stories.tsx +112 -1023
  139. package/dist/icons/plus-icon/PlusIcon.stories.tsx +103 -1132
  140. package/dist/icons/pocket-studio-icon/PocketStudioIcon.stories.tsx +104 -870
  141. package/dist/icons/scroll-down-icon/ScrollDownIcon.stories.tsx +99 -476
  142. package/dist/icons/search-icon/SearchIcon.stories.tsx +108 -1161
  143. package/dist/icons/setting-icon/SettingIcon.stories.tsx +104 -1009
  144. package/dist/icons/share-icon/ShareIcon.stories.tsx +117 -1064
  145. package/dist/icons/shield-icon/ShieldIcon.stories.tsx +114 -974
  146. package/dist/icons/site-logo-icon/SiteLogoIcon.stories.tsx +134 -1160
  147. package/dist/icons/skip-backward-icon/SkipBackwardIcon.stories.tsx +169 -1017
  148. package/dist/icons/skip-forward-icon/SkipForwardIcon.stories.tsx +161 -1016
  149. package/dist/icons/sparkles-soft-icon/SparklesSoftIcon.stories.tsx +102 -1001
  150. package/dist/icons/spinner-gradient-icon/SpinnerGradientIcon.stories.tsx +155 -593
  151. package/dist/icons/spinner-solid-icon/SpinnerSolidIcon.stories.tsx +155 -608
  152. package/dist/icons/spinner-solid-neutral-icon/SpinnerSolidINeutralcon.stories.tsx +142 -712
  153. package/dist/icons/star-icon/StarIcon.stories.tsx +120 -946
  154. package/dist/icons/store-coin-icon/StoreCoinIcon.stories.tsx +109 -1013
  155. package/dist/icons/suggestion-icon/SuggestionIcon.stories.tsx +113 -891
  156. package/dist/icons/sun-icon/SunIcon.stories.tsx +117 -864
  157. package/dist/icons/text-color-icon/TextColorIcon.stories.tsx +113 -989
  158. package/dist/icons/text-indicator-icon/TextIndicatorIcon.stories.tsx +120 -1027
  159. package/dist/icons/threads-icon/ThreadsIcon.stories.tsx +153 -1476
  160. package/dist/icons/tick-circle-icon/TickCircleIcon.stories.tsx +143 -1187
  161. package/dist/icons/tick-icon/TickIcon.stories.tsx +142 -1322
  162. package/dist/icons/trash-icon/TrashIcon.stories.tsx +105 -970
  163. package/dist/icons/twitter-x-icon/TwitterXIcon.stories.tsx +154 -1457
  164. package/dist/icons/upload-icon/UploadIcon.stories.tsx +112 -930
  165. package/dist/icons/vertical-menu-icon/VerticalMenuIcon.stories.tsx +115 -1019
  166. package/dist/icons/video-play-list-icon/VideoPlaylistIcon.stories.tsx +122 -1092
  167. package/dist/icons/voice-playing-icon/VoicePlayingIcon.stories.tsx +120 -1401
  168. package/dist/icons/volume-full-icon/VolumeFullIcon.stories.tsx +107 -1212
  169. package/dist/icons/volume-half-icon/VolumeHalfIcon.stories.tsx +109 -1122
  170. package/dist/icons/volume-off-icon/VolumeOffIcon.stories.tsx +112 -1124
  171. package/dist/icons/warning-icon/WarningIcon.stories.tsx +119 -1083
  172. package/dist/icons/youtube-icon/YoutubeIcon.stories.tsx +158 -983
  173. package/dist/index.cjs +90 -90
  174. package/dist/index.js +90 -90
  175. package/package.json +8 -3
@@ -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,417 +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-fm-secondary text-sm">
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-fm-primary text-2xl font-bold">
194
- OTP Input - All Variants
195
- </h2>
196
- <p className="text-fm-secondary">
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-fm-primary text-xl font-semibold">
205
- Length Variants
206
- </h3>
207
-
208
- <div className="space-y-3">
209
- <div>
210
- <label className="text-fm-secondary mb-2 block text-sm font-medium">
211
- 4-Digit OTP (Common for SMS)
212
- </label>
213
- <OtpInputs
214
- length={4}
215
- isNumberInput={true}
216
- onChangeOTP={setOtp4}
217
- />
218
- <p className="text-fm-tertiary mt-1 text-xs">
219
- Current: {otp4 || "Empty"}
220
- </p>
221
- </div>
222
-
223
- <div>
224
- <label className="text-fm-secondary mb-2 block text-sm font-medium">
225
- 6-Digit OTP (Standard)
226
- </label>
227
- <OtpInputs
228
- length={6}
229
- isNumberInput={true}
230
- onChangeOTP={setOtp6}
231
- />
232
- <p className="text-fm-tertiary mt-1 text-xs">
233
- Current: {otp6 || "Empty"}
234
- </p>
235
- </div>
236
-
237
- <div>
238
- <label className="text-fm-secondary mb-2 block text-sm font-medium">
239
- 8-Digit OTP (Extended Security)
240
- </label>
241
- <OtpInputs
242
- length={8}
243
- isNumberInput={true}
244
- onChangeOTP={setOtp8}
245
- />
246
- <p className="text-fm-tertiary mt-1 text-xs">
247
- Current: {otp8 || "Empty"}
248
- </p>
249
- </div>
250
- </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>
251
217
  </div>
218
+ </div>
252
219
 
253
- {/* Input Type Variants */}
254
- <div className="space-y-4">
255
- <h3 className="text-fm-primary text-xl font-semibold">
256
- Input Type Variants
257
- </h3>
258
-
259
- <div className="space-y-3">
260
- <div>
261
- <label className="text-fm-secondary mb-2 block text-sm font-medium">
262
- Numeric Only (Default)
263
- </label>
264
- <OtpInputs
265
- length={6}
266
- isNumberInput={true}
267
- onChangeOTP={(otp) => console.log("Numeric OTP:", otp)}
268
- />
269
- <p className="text-fm-tertiary mt-1 text-xs">
270
- Accepts only numbers (0-9)
271
- </p>
272
- </div>
273
-
274
- <div>
275
- <label className="text-fm-secondary mb-2 block text-sm font-medium">
276
- Alphanumeric (Letters + Numbers)
277
- </label>
278
- <OtpInputs
279
- length={6}
280
- isNumberInput={false}
281
- onChangeOTP={setAlphanumericOtp}
282
- />
283
- <p className="text-fm-tertiary mt-1 text-xs">
284
- Current: {alphanumericOtp || "Empty"} | Accepts letters and
285
- numbers
286
- </p>
287
- </div>
288
- </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>
289
235
  </div>
236
+ </div>
290
237
 
291
- {/* State Variants */}
292
- <div className="space-y-4">
293
- <h3 className="text-fm-primary text-xl font-semibold">
294
- State Variants
295
- </h3>
296
-
297
- <div className="space-y-3">
298
- <div>
299
- <label className="text-fm-secondary mb-2 block text-sm font-medium">
300
- Enabled State (Default)
301
- </label>
302
- <OtpInputs
303
- length={6}
304
- isNumberInput={true}
305
- onChangeOTP={(otp) => console.log("Enabled OTP:", otp)}
306
- />
307
- </div>
308
-
309
- <div>
310
- <label className="text-fm-secondary mb-2 block text-sm font-medium">
311
- Disabled State
312
- </label>
313
- <OtpInputs
314
- length={6}
315
- isNumberInput={true}
316
- disabled={true}
317
- onChangeOTP={(otp) => console.log("Disabled OTP:", otp)}
318
- />
319
- </div>
320
- </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>
321
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
+ }
322
266
 
323
- {/* Validation States */}
324
- <div className="space-y-4">
325
- <h3 className="text-fm-primary text-xl font-semibold">
326
- Validation States
327
- </h3>
328
-
329
- <div className="space-y-3">
330
- <div>
331
- <label className="text-fm-secondary mb-2 block text-sm font-medium">
332
- Neutral State (No validation)
333
- </label>
334
- <OtpInputs
335
- length={6}
336
- isNumberInput={true}
337
- onChangeOTP={(otp) => console.log("Neutral OTP:", otp)}
338
- isValid={null}
339
- messages={{
340
- neutral: "Enter your 6-digit verification code",
341
- }}
342
- />
343
- </div>
344
-
345
- <div>
346
- <label className="text-fm-secondary mb-2 block text-sm font-medium">
347
- Success State (Valid OTP)
348
- </label>
349
- <OtpInputs
350
- length={6}
351
- isNumberInput={true}
352
- onChangeOTP={(otp) => console.log("Success OTP:", otp)}
353
- isValid={true}
354
- messages={{
355
- success: "✓ Verification successful!",
356
- }}
357
- />
358
- </div>
359
-
360
- <div>
361
- <label className="text-fm-secondary mb-2 block text-sm font-medium">
362
- Error State (Invalid OTP)
363
- </label>
364
- <OtpInputs
365
- length={6}
366
- isNumberInput={true}
367
- onChangeOTP={(otp) => console.log("Error OTP:", otp)}
368
- isValid={false}
369
- messages={{
370
- error: "✗ Invalid code. Please try again.",
371
- }}
372
- />
373
- </div>
267
+ // ─── 4. Interactive ───────────────────────────────────────────────────────────
374
268
 
375
- <div>
376
- <label className="text-fm-secondary mb-2 block text-sm font-medium">
377
- Interactive Validation (Try: 123456)
378
- </label>
379
- <OtpInputs
380
- length={6}
381
- isNumberInput={true}
382
- onChangeOTP={handleValidation}
383
- isValid={isValid}
384
- messages={{
385
- neutral: "Enter 6-digit code",
386
- success: "✓ Code verified successfully!",
387
- error: "✗ Invalid code. Try: 123456",
388
- }}
389
- />
390
- <p className="text-fm-tertiary mt-1 text-xs">
391
- Current: {validatedOtp || "Empty"} | Status:{" "}
392
- {isValid === null
393
- ? "Neutral"
394
- : isValid === true
395
- ? "Valid"
396
- : "Invalid"}
397
- </p>
398
- </div>
399
- </div>
400
- </div>
269
+ export const Interactive: Story = {
270
+ render: () => {
271
+ type Step = "idle" | "sent" | "verified" | "failed"
401
272
 
402
- {/* Custom Styling Examples */}
403
- <div className="space-y-4">
404
- <h3 className="text-fm-primary text-xl font-semibold">
405
- Custom Styling
406
- </h3>
273
+ const VALID_CODE = "123456"
407
274
 
408
- <div className="space-y-3">
409
- <div>
410
- <label className="text-fm-secondary mb-2 block text-sm font-medium">
411
- Custom Input Styling
412
- </label>
413
- <OtpInputs
414
- length={6}
415
- isNumberInput={true}
416
- onChangeOTP={(otp) => console.log("Custom styled OTP:", otp)}
417
- inputStyle={{
418
- backgroundColor: "#1f2937",
419
- borderColor: "#374151",
420
- color: "#f9fafb",
421
- }}
422
- inputClassName="border-2 border-blue-500 focus:border-blue-400"
423
- />
424
- </div>
425
- </div>
426
- </div>
275
+ const InteractiveDemo = () => {
276
+ const [step, setStep] = useState<Step>("idle")
277
+ const [isValid, setIsValid] = useState<boolean | null>(null)
427
278
 
428
- {/* Usage Examples */}
429
- <div className="space-y-4">
430
- <h3 className="text-fm-primary text-xl font-semibold">
431
- Common Use Cases
432
- </h3>
279
+ const handleSend = () => {
280
+ setStep("sent")
281
+ setIsValid(null)
282
+ }
433
283
 
434
- <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
435
- <div className="bg-fm-surface-secondary rounded-lg p-4">
436
- <h4 className="text-fm-primary mb-2 font-medium">
437
- SMS Verification
438
- </h4>
439
- <OtpInputs
440
- length={4}
441
- isNumberInput={true}
442
- onChangeOTP={(otp) => console.log("SMS OTP:", otp)}
443
- messages={{
444
- neutral: "Enter SMS code",
445
- }}
446
- />
447
- </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
+ }
448
296
 
449
- <div className="bg-fm-surface-secondary rounded-lg p-4">
450
- <h4 className="text-fm-primary mb-2 font-medium">
451
- Email Verification
452
- </h4>
453
- <OtpInputs
454
- length={6}
455
- isNumberInput={true}
456
- onChangeOTP={(otp) => console.log("Email OTP:", otp)}
457
- messages={{
458
- neutral: "Enter email code",
459
- }}
460
- />
461
- </div>
297
+ const handleReset = () => {
298
+ setStep("idle")
299
+ setIsValid(null)
300
+ }
462
301
 
463
- <div className="bg-fm-surface-secondary rounded-lg p-4">
464
- <h4 className="text-fm-primary mb-2 font-medium">
465
- 2FA Authentication
466
- </h4>
467
- <OtpInputs
468
- length={6}
469
- isNumberInput={true}
470
- onChangeOTP={(otp) => console.log("2FA OTP:", otp)}
471
- messages={{
472
- neutral: "Enter authenticator code",
473
- }}
474
- />
475
- </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
+ }
476
307
 
477
- <div className="bg-fm-surface-secondary rounded-lg p-4">
478
- <h4 className="text-fm-primary mb-2 font-medium">
479
- Alphanumeric Code
480
- </h4>
481
- <OtpInputs
482
- length={6}
483
- isNumberInput={false}
484
- onChangeOTP={(otp) => console.log("Alphanumeric OTP:", otp)}
485
- messages={{
486
- neutral: "Enter alphanumeric code",
487
- }}
488
- />
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>
489
416
  </div>
490
417
  </div>
491
418
  </div>
492
- </div>
493
- )
419
+ )
420
+ }
421
+
422
+ return <InteractiveDemo />
494
423
  },
495
424
  parameters: {
425
+ layout: "fullscreen",
496
426
  docs: {
497
427
  description: {
498
- story: `
499
- 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.',
500
430
  },
501
431
  },
502
432
  },