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.
- package/README.md +8 -1
- package/dist/components/aspect-ratio/AspectRatio.stories.tsx +290 -1228
- package/dist/components/avatar/Avatar.stories.tsx +219 -235
- package/dist/components/badge/Badge.stories.tsx +379 -116
- package/dist/components/banner/Banner.stories.tsx +445 -391
- package/dist/components/breadcrumb/Breadcrumb.stories.tsx +453 -199
- package/dist/components/button/Button.stories.tsx +585 -230
- package/dist/components/card/Card.stories.tsx +619 -301
- package/dist/components/char-count/CharCount.stories.tsx +350 -248
- package/dist/components/checkbox/Checkbox.stories.tsx +309 -167
- package/dist/components/chip/Chip.stories.tsx +362 -168
- package/dist/components/circular-loader/CircularLoader.stories.tsx +221 -636
- package/dist/components/clamp-lines/ClampLines.stories.tsx +246 -117
- package/dist/components/collapsible/Collapsible.stories.tsx +391 -252
- package/dist/components/command/Command.stories.tsx +530 -867
- package/dist/components/dialog/Dialog.stories.tsx +501 -950
- package/dist/components/divider/Divider.stories.tsx +264 -527
- package/dist/components/dot-loader/DotLoader.stories.tsx +256 -257
- package/dist/components/drawer/Drawer.stories.tsx +659 -1023
- package/dist/components/dropdown/Dropdown.stories.tsx +643 -1028
- package/dist/components/form/Form.stories.tsx +560 -274
- package/dist/components/helper-text/HelperText.stories.tsx +199 -200
- package/dist/components/hover-card/HoverCard.stories.tsx +318 -1254
- package/dist/components/icon-button/IconButton.stories.tsx +837 -194
- package/dist/components/if-else/if-else.stories.tsx +370 -83
- package/dist/components/input/Input.stories.tsx +436 -368
- package/dist/components/label/Label.stories.tsx +156 -154
- package/dist/components/list/List.stories.tsx +484 -835
- package/dist/components/marquee/Marquee.stories.tsx +356 -712
- package/dist/components/otp-inputs/OtpInputs.stories.tsx +352 -422
- package/dist/components/overlay/Overlay.stories.tsx +452 -824
- package/dist/components/pagination/Pagination.stories.tsx +721 -210
- package/dist/components/popover/Popover.stories.tsx +481 -896
- package/dist/components/radio/Radio.stories.tsx +432 -124
- package/dist/components/resizable/Resizable.stories.tsx +495 -799
- package/dist/components/scroll-area/ScrollArea.stories.tsx +383 -1059
- package/dist/components/search/Search.stories.tsx +312 -595
- package/dist/components/select/Select.stories.tsx +684 -789
- package/dist/components/sheet/Sheet.stories.tsx +671 -950
- package/dist/components/skelton/Skelton.stories.tsx +230 -764
- package/dist/components/slider/Slider.stories.tsx +383 -760
- package/dist/components/stepper/Stepper.stories.tsx +371 -514
- package/dist/components/switch/Switch.stories.tsx +461 -208
- package/dist/components/switch-case/SwitchCase.stories.tsx +367 -188
- package/dist/components/table/Table.stories.tsx +770 -916
- package/dist/components/tabs/Tabs.stories.tsx +458 -1455
- package/dist/components/tag/Tag.stories.tsx +714 -542
- package/dist/components/textarea/TextArea.stories.tsx +621 -562
- package/dist/components/thumbnail-tags/ThumbnailTags.stories.tsx +228 -154
- package/dist/components/toast/Toast.stories.tsx +452 -1339
- package/dist/components/toggle/Toggle.stories.tsx +488 -931
- package/dist/components/tooltip/Tooltip.stories.tsx +344 -1388
- package/dist/components/typography/Typography.stories.tsx +406 -89
- package/dist/hooks/use-change-state/UseChangeState.stories.tsx +309 -606
- package/dist/hooks/use-previous/UsePrevious.stories.tsx +367 -917
- package/dist/hooks/use-standalone-pagination/UseStandalonePagination.stories.tsx +639 -867
- package/dist/icons/Icons.stories.tsx +0 -12
- package/dist/icons/ai-avatar-icon/AiAvatarIcon.stories.tsx +223 -1060
- package/dist/icons/alert-icon/AlertIcon.stories.tsx +106 -968
- package/dist/icons/all-icons.tsx +37 -16
- package/dist/icons/angle-down-icon/AngleDownIcon.stories.tsx +137 -1010
- package/dist/icons/apple-logo-icon/AppleLogoIcon.stories.tsx +145 -935
- package/dist/icons/arrow-box-left-icon/ArrowBoxLeftIcon.stories.tsx +132 -1046
- package/dist/icons/arrow-corner-up-left-icon/ArrowCornerUpLeftIcon.stories.tsx +134 -986
- package/dist/icons/arrow-corner-up-right-icon/ArrowCornerUpRightIcon.stories.tsx +135 -1028
- package/dist/icons/arrow-left-icon/ArrowLeftIcon.stories.tsx +133 -971
- package/dist/icons/arrow-right-icon/ArrowRightIcon.stories.tsx +145 -1123
- package/dist/icons/arrow-right-up-icon/ArrowRightUpIcon.stories.tsx +143 -1252
- package/dist/icons/art-board-icon/ArtBoardIcon.stories.tsx +123 -632
- package/dist/icons/audio-bar-icon/AudioBarIcon.stories.tsx +141 -1223
- package/dist/icons/backward-ten-seconds-icon/BackwardTenSecondsIcon.stories.tsx +164 -1018
- package/dist/icons/bubble-check-icon/BubbleCheckIcon.stories.tsx +121 -1236
- package/dist/icons/bubble-crossed-icon/BubbleCrossedIcon.stories.tsx +121 -1213
- package/dist/icons/bubble-sparkle-icon/BubbleSparkleIcon.stories.tsx +116 -893
- package/dist/icons/camera-icon/CameraIcon.stories.tsx +109 -1254
- package/dist/icons/capital-a-letter-icon/CapitalALetterIcon.stories.tsx +114 -975
- package/dist/icons/chevron-double-left-icon/ChevronDoubleLeftIcon.stories.tsx +157 -994
- package/dist/icons/chevron-double-right-icon/ChevronDoubleRightIcon.stories.tsx +160 -992
- package/dist/icons/chevron-down-icon/ChevronDownIcon.stories.tsx +140 -970
- package/dist/icons/chevron-left-icon/ChevronLeftIcon.stories.tsx +126 -993
- package/dist/icons/chevron-right-icon/ChevronRightIcon.stories.tsx +144 -987
- package/dist/icons/chevron-up-icon/ChevronUpIcon.stories.tsx +141 -1007
- package/dist/icons/circle-tick-icon/CircleTickIcon.stories.tsx +147 -1187
- package/dist/icons/circular-play-icon/CircularPlayIcon.stories.tsx +110 -476
- package/dist/icons/coin-icon/CoinIcon.stories.tsx +120 -1364
- package/dist/icons/coin-toons-icon/CoinToonsIcon.stories.tsx +113 -1360
- package/dist/icons/column-wide-add-icon/ColumnWideAddIcon.stories.tsx +111 -942
- package/dist/icons/command-icon/CommandIcon.stories.tsx +124 -1087
- package/dist/icons/copy-icon/CopyIcon.stories.tsx +119 -996
- package/dist/icons/cross-circle-icon/CrossCircleIcon.stories.tsx +144 -1046
- package/dist/icons/cross-icon/CrossIcon.stories.tsx +136 -999
- package/dist/icons/download-icon/DownloadIcon.stories.tsx +123 -857
- package/dist/icons/edit-big-icon/EditBigIcon.stories.tsx +121 -1080
- package/dist/icons/email-icon/EmailIcon.stories.tsx +112 -979
- package/dist/icons/expand-icon/ExpandIcon.stories.tsx +109 -1146
- package/dist/icons/eye-close-icon/EyeCloseIcon.stories.tsx +141 -1068
- package/dist/icons/eye-open-icon/EyeOpenIcon.stories.tsx +140 -1081
- package/dist/icons/feature-shine-icon/FeatureShineIcon.stories.tsx +124 -1050
- package/dist/icons/file-chart-icon/FileChartIcon.stories.tsx +123 -1091
- package/dist/icons/file-text-icon/FileTextIcon.stories.tsx +122 -633
- package/dist/icons/filter-bar-row-icon/FilterBarRowIcon.stories.tsx +116 -1087
- package/dist/icons/forward-ten-seconds-icon/ForwardTenSecondsIcon.stories.tsx +166 -1020
- package/dist/icons/git-branch-icon/GitBranchIcon.stories.tsx +112 -1182
- package/dist/icons/git-fork-icon/GitForkIcon.stories.tsx +112 -1155
- package/dist/icons/globe-icon/GlobeIcon.stories.tsx +127 -325
- package/dist/icons/google-logo-icon/GoogleLogoIcon.stories.tsx +142 -985
- package/dist/icons/grip-vertical-icon/GripVerticalIcon.stories.tsx +116 -1217
- package/dist/icons/head-icon/HeadIcon.stories.tsx +108 -953
- package/dist/icons/heart-icon/HeartIcon.stories.tsx +117 -1060
- package/dist/icons/image-avatar-sparkle-icon/ImageAvatarSparkleIcon.stories.tsx +116 -716
- package/dist/icons/image-icon/ImageIcon.stories.tsx +102 -1164
- package/dist/icons/import-folder-icon/ImportFolderIcon.stories.tsx +108 -1233
- package/dist/icons/import-left-arrow-folder-icon/ImportLeftArrowFolderIcon.stories.tsx +133 -1289
- package/dist/icons/indian-flag-icon/IndianFlagIcon.stories.tsx +155 -1012
- package/dist/icons/instagram-icon/InstagramIcon.stories.tsx +158 -1438
- package/dist/icons/layout-column-icon/LayoutColumnIcon.stories.tsx +121 -1011
- package/dist/icons/layout-left-icon/LayoutLeftIcon.stories.tsx +116 -981
- package/dist/icons/layout-right-icon/LayoutRightIcon.stories.tsx +116 -979
- package/dist/icons/light-bulb-simple-icon/LightBulbSimpleIcon.stories.tsx +105 -1252
- package/dist/icons/linked-in-icon/LinkedInIcon.stories.tsx +151 -1554
- package/dist/icons/magic-book-icon/MagicBookIcon.stories.tsx +107 -1227
- package/dist/icons/magic-edit-icon/MagicEditIcon.stories.tsx +116 -707
- package/dist/icons/maintenance-icon/MaintenanceIcon.stories.tsx +119 -1226
- package/dist/icons/message-icon/MessageIcon.stories.tsx +111 -557
- package/dist/icons/minimize-icon/MinimizeIcon.stories.tsx +112 -1198
- package/dist/icons/moon-icon/MoonIcon.stories.tsx +117 -557
- package/dist/icons/move-horizontal-icon/MoveHorizontalIcon.stories.tsx +106 -1235
- package/dist/icons/move-vertical-icon/MoveVerticalIcon.stories.tsx +112 -1185
- package/dist/icons/musical-note-icon/MusicalNoteIcon.stories.tsx +116 -1012
- package/dist/icons/notepad-icon/NotepadIcon.stories.tsx +108 -1137
- package/dist/icons/notes-icon/NotesIcon.stories.tsx +116 -1138
- package/dist/icons/page-search-icon/PageSearchIcon.stories.tsx +106 -1146
- package/dist/icons/page-text-icon/PageTextIcon.stories.tsx +119 -719
- package/dist/icons/paint-roll-icon/PaintRollIcon.stories.tsx +110 -999
- package/dist/icons/paper-plane-icon/PaperPlaneIcon.stories.tsx +109 -912
- package/dist/icons/pause-icon/PauseIcon.stories.tsx +110 -1041
- package/dist/icons/pencil-icon/PencilIcon.stories.tsx +112 -1109
- package/dist/icons/phone-icon/PhoneIcon.stories.tsx +112 -1023
- package/dist/icons/plus-icon/PlusIcon.stories.tsx +103 -1132
- package/dist/icons/pocket-studio-icon/PocketStudioIcon.stories.tsx +104 -870
- package/dist/icons/scroll-down-icon/ScrollDownIcon.stories.tsx +99 -476
- package/dist/icons/search-icon/SearchIcon.stories.tsx +108 -1161
- package/dist/icons/setting-icon/SettingIcon.stories.tsx +104 -1009
- package/dist/icons/share-icon/ShareIcon.stories.tsx +117 -1064
- package/dist/icons/shield-icon/ShieldIcon.stories.tsx +114 -974
- package/dist/icons/site-logo-icon/SiteLogoIcon.stories.tsx +134 -1160
- package/dist/icons/skip-backward-icon/SkipBackwardIcon.stories.tsx +169 -1017
- package/dist/icons/skip-forward-icon/SkipForwardIcon.stories.tsx +161 -1016
- package/dist/icons/sparkles-soft-icon/SparklesSoftIcon.stories.tsx +102 -1001
- package/dist/icons/spinner-gradient-icon/SpinnerGradientIcon.stories.tsx +155 -593
- package/dist/icons/spinner-solid-icon/SpinnerSolidIcon.stories.tsx +155 -608
- package/dist/icons/spinner-solid-neutral-icon/SpinnerSolidINeutralcon.stories.tsx +142 -712
- package/dist/icons/star-icon/StarIcon.stories.tsx +120 -946
- package/dist/icons/store-coin-icon/StoreCoinIcon.stories.tsx +109 -1013
- package/dist/icons/suggestion-icon/SuggestionIcon.stories.tsx +113 -891
- package/dist/icons/sun-icon/SunIcon.stories.tsx +117 -864
- package/dist/icons/text-color-icon/TextColorIcon.stories.tsx +113 -989
- package/dist/icons/text-indicator-icon/TextIndicatorIcon.stories.tsx +120 -1027
- package/dist/icons/threads-icon/ThreadsIcon.stories.tsx +153 -1476
- package/dist/icons/tick-circle-icon/TickCircleIcon.stories.tsx +143 -1187
- package/dist/icons/tick-icon/TickIcon.stories.tsx +142 -1322
- package/dist/icons/trash-icon/TrashIcon.stories.tsx +105 -970
- package/dist/icons/twitter-x-icon/TwitterXIcon.stories.tsx +154 -1457
- package/dist/icons/upload-icon/UploadIcon.stories.tsx +112 -930
- package/dist/icons/vertical-menu-icon/VerticalMenuIcon.stories.tsx +115 -1019
- package/dist/icons/video-play-list-icon/VideoPlaylistIcon.stories.tsx +122 -1092
- package/dist/icons/voice-playing-icon/VoicePlayingIcon.stories.tsx +120 -1401
- package/dist/icons/volume-full-icon/VolumeFullIcon.stories.tsx +107 -1212
- package/dist/icons/volume-half-icon/VolumeHalfIcon.stories.tsx +109 -1122
- package/dist/icons/volume-off-icon/VolumeOffIcon.stories.tsx +112 -1124
- package/dist/icons/warning-icon/WarningIcon.stories.tsx +119 -1083
- package/dist/icons/youtube-icon/YoutubeIcon.stories.tsx +158 -983
- package/dist/index.cjs +90 -90
- package/dist/index.js +90 -90
- 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
|
-
|
|
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: "
|
|
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
|
|
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
|
-
|
|
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
|
|
64
|
+
export const Playground: Story = {
|
|
99
65
|
args: {
|
|
100
|
-
length:
|
|
66
|
+
length: 6,
|
|
101
67
|
isNumberInput: true,
|
|
102
68
|
disabled: false,
|
|
103
|
-
onChangeOTP: (otp: string) => console.log("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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
133
|
-
render: () => {
|
|
134
|
-
const [otpValue, setOtpValue] = useState("")
|
|
91
|
+
// ─── 2. Configurations ────────────────────────────────────────────────────────
|
|
135
92
|
|
|
136
|
-
|
|
93
|
+
export const Configurations: Story = {
|
|
94
|
+
render: () => (
|
|
95
|
+
<div className="space-y-8">
|
|
96
|
+
{/* Length variants */}
|
|
137
97
|
<div className="space-y-4">
|
|
138
|
-
<
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
203
|
-
<
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
254
|
-
<
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
292
|
-
<
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
</h3>
|
|
279
|
+
const handleSend = () => {
|
|
280
|
+
setStep("sent")
|
|
281
|
+
setIsValid(null)
|
|
282
|
+
}
|
|
433
283
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
|
|
493
|
-
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return <InteractiveDemo />
|
|
494
423
|
},
|
|
495
424
|
parameters: {
|
|
425
|
+
layout: "fullscreen",
|
|
496
426
|
docs: {
|
|
497
427
|
description: {
|
|
498
|
-
story:
|
|
499
|
-
|
|
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
|
},
|