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.
- package/dist/components/aspect-ratio/AspectRatio.stories.tsx +290 -1199
- package/dist/components/avatar/Avatar.stories.tsx +235 -237
- 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/button/index.tsx +7 -7
- 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 -620
- 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 +533 -856
- package/dist/components/dialog/Dialog.stories.tsx +505 -949
- package/dist/components/divider/Divider.stories.tsx +265 -502
- package/dist/components/dot-loader/DotLoader.stories.tsx +256 -257
- package/dist/components/drawer/Drawer.stories.tsx +659 -993
- package/dist/components/drawer/index.tsx +3 -3
- package/dist/components/dropdown/Dropdown.stories.tsx +643 -1018
- 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 -1221
- 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 +485 -822
- package/dist/components/marquee/Marquee.stories.tsx +356 -694
- package/dist/components/otp-inputs/OtpInputs.stories.tsx +352 -410
- package/dist/components/overlay/Overlay.stories.tsx +452 -818
- package/dist/components/overlay/index.tsx +4 -4
- package/dist/components/pagination/Pagination.stories.tsx +721 -210
- package/dist/components/popover/Popover.stories.tsx +484 -873
- package/dist/components/radio/Radio.stories.tsx +432 -124
- package/dist/components/resizable/Resizable.stories.tsx +496 -752
- package/dist/components/scroll-area/ScrollArea.stories.tsx +384 -1006
- package/dist/components/search/Search.stories.tsx +314 -575
- package/dist/components/select/Select.stories.tsx +684 -787
- package/dist/components/sheet/Sheet.stories.tsx +671 -936
- package/dist/components/skelton/Skelton.stories.tsx +230 -764
- package/dist/components/slider/Slider.stories.tsx +384 -737
- 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 -914
- package/dist/components/tabs/Tabs.stories.tsx +459 -1400
- 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 -148
- package/dist/components/toast/Toast.stories.tsx +452 -1333
- package/dist/components/toggle/Toggle.stories.tsx +488 -909
- package/dist/components/tooltip/Tooltip.stories.tsx +344 -1372
- 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 +226 -1013
- package/dist/icons/alert-icon/AlertIcon.stories.tsx +109 -929
- package/dist/icons/all-icons.tsx +124 -87
- package/dist/icons/angle-down-icon/AngleDownIcon.stories.tsx +140 -971
- package/dist/icons/apple-logo-icon/AppleLogoIcon.stories.tsx +148 -888
- package/dist/icons/arrow-box-left-icon/ArrowBoxLeftIcon.stories.tsx +135 -1019
- package/dist/icons/arrow-corner-up-left-icon/ArrowCornerUpLeftIcon.stories.tsx +137 -953
- package/dist/icons/arrow-corner-up-right-icon/ArrowCornerUpRightIcon.stories.tsx +138 -997
- package/dist/icons/arrow-left-icon/ArrowLeftIcon.stories.tsx +136 -942
- package/dist/icons/arrow-right-icon/ArrowRightIcon.stories.tsx +148 -1092
- package/dist/icons/arrow-right-up-icon/ArrowRightUpIcon.stories.tsx +146 -1211
- package/dist/icons/art-board-icon/ArtBoardIcon.stories.tsx +126 -615
- package/dist/icons/audio-bar-icon/AudioBarIcon.stories.tsx +144 -1164
- package/dist/icons/backward-ten-seconds-icon/BackwardTenSecondsIcon.stories.tsx +167 -985
- package/dist/icons/bubble-check-icon/BubbleCheckIcon.stories.tsx +122 -1179
- package/dist/icons/bubble-crossed-icon/BubbleCrossedIcon.stories.tsx +124 -1168
- package/dist/icons/bubble-sparkle-icon/BubbleSparkleIcon.stories.tsx +119 -850
- package/dist/icons/camera-icon/CameraIcon.stories.tsx +112 -1213
- package/dist/icons/capital-a-letter-icon/CapitalALetterIcon.stories.tsx +117 -934
- package/dist/icons/chevron-double-left-icon/ChevronDoubleLeftIcon.stories.tsx +160 -961
- package/dist/icons/chevron-double-right-icon/ChevronDoubleRightIcon.stories.tsx +163 -961
- package/dist/icons/chevron-down-icon/ChevronDownIcon.stories.tsx +144 -942
- package/dist/icons/chevron-left-icon/ChevronLeftIcon.stories.tsx +129 -966
- package/dist/icons/chevron-right-icon/ChevronRightIcon.stories.tsx +147 -964
- package/dist/icons/chevron-up-icon/ChevronUpIcon.stories.tsx +145 -975
- package/dist/icons/circle-tick-icon/CircleTickIcon.stories.tsx +150 -1142
- package/dist/icons/circular-play-icon/CircularPlayIcon.stories.tsx +114 -461
- package/dist/icons/coin-icon/CoinIcon.stories.tsx +124 -1322
- package/dist/icons/coin-toons-icon/CoinToonsIcon.stories.tsx +117 -1318
- package/dist/icons/column-wide-add-icon/ColumnWideAddIcon.stories.tsx +114 -903
- package/dist/icons/command-icon/CommandIcon.stories.tsx +127 -1042
- package/dist/icons/copy-icon/CopyIcon.stories.tsx +123 -962
- package/dist/icons/cross-circle-icon/CrossCircleIcon.stories.tsx +147 -999
- package/dist/icons/cross-icon/CrossIcon.stories.tsx +139 -960
- package/dist/icons/download-icon/DownloadIcon.stories.tsx +126 -820
- package/dist/icons/edit-big-icon/EditBigIcon.stories.tsx +124 -1031
- package/dist/icons/email-icon/EmailIcon.stories.tsx +115 -936
- package/dist/icons/expand-icon/ExpandIcon.stories.tsx +112 -1111
- package/dist/icons/eye-close-icon/EyeCloseIcon.stories.tsx +144 -1025
- package/dist/icons/eye-open-icon/EyeOpenIcon.stories.tsx +143 -1036
- package/dist/icons/feature-shine-icon/FeatureShineIcon.stories.tsx +127 -1011
- package/dist/icons/file-chart-icon/FileChartIcon.stories.tsx +126 -1056
- package/dist/icons/file-text-icon/FileTextIcon.stories.tsx +125 -614
- package/dist/icons/filter-bar-row-icon/FilterBarRowIcon.stories.tsx +119 -1050
- package/dist/icons/forward-ten-seconds-icon/ForwardTenSecondsIcon.stories.tsx +169 -989
- package/dist/icons/git-branch-icon/GitBranchIcon.stories.tsx +115 -1145
- package/dist/icons/git-fork-icon/GitForkIcon.stories.tsx +115 -1122
- package/dist/icons/globe-icon/GlobeIcon.stories.tsx +130 -313
- package/dist/icons/google-logo-icon/GoogleLogoIcon.stories.tsx +145 -940
- package/dist/icons/grip-vertical-icon/GripVerticalIcon.stories.tsx +119 -1174
- package/dist/icons/head-icon/HeadIcon.stories.tsx +111 -916
- package/dist/icons/heart-icon/HeartIcon.stories.tsx +120 -1019
- package/dist/icons/image-avatar-sparkle-icon/ImageAvatarSparkleIcon.stories.tsx +119 -683
- package/dist/icons/image-icon/ImageIcon.stories.tsx +105 -1121
- package/dist/icons/import-folder-icon/ImportFolderIcon.stories.tsx +111 -1192
- package/dist/icons/import-left-arrow-folder-icon/ImportLeftArrowFolderIcon.stories.tsx +136 -1256
- package/dist/icons/indian-flag-icon/IndianFlagIcon.stories.tsx +159 -962
- package/dist/icons/instagram-icon/InstagramIcon.stories.tsx +161 -1385
- package/dist/icons/layout-column-icon/LayoutColumnIcon.stories.tsx +124 -972
- package/dist/icons/layout-left-icon/LayoutLeftIcon.stories.tsx +119 -948
- package/dist/icons/layout-right-icon/LayoutRightIcon.stories.tsx +119 -942
- package/dist/icons/light-bulb-simple-icon/LightBulbSimpleIcon.stories.tsx +108 -1215
- package/dist/icons/linked-in-icon/LinkedInIcon.stories.tsx +154 -1517
- package/dist/icons/magic-book-icon/MagicBookIcon.stories.tsx +110 -1188
- package/dist/icons/magic-edit-icon/MagicEditIcon.stories.tsx +119 -678
- package/dist/icons/maintenance-icon/MaintenanceIcon.stories.tsx +123 -1184
- package/dist/icons/message-icon/MessageIcon.stories.tsx +114 -538
- package/dist/icons/minimize-icon/MinimizeIcon.stories.tsx +116 -1158
- package/dist/icons/moon-icon/MoonIcon.stories.tsx +120 -536
- package/dist/icons/move-horizontal-icon/MoveHorizontalIcon.stories.tsx +109 -1184
- package/dist/icons/move-vertical-icon/MoveVerticalIcon.stories.tsx +115 -1134
- package/dist/icons/musical-note-icon/MusicalNoteIcon.stories.tsx +119 -971
- package/dist/icons/notepad-icon/NotepadIcon.stories.tsx +111 -1100
- package/dist/icons/notes-icon/NotesIcon.stories.tsx +119 -1101
- package/dist/icons/page-search-icon/PageSearchIcon.stories.tsx +109 -1111
- package/dist/icons/page-text-icon/PageTextIcon.stories.tsx +122 -684
- package/dist/icons/paint-roll-icon/PaintRollIcon.stories.tsx +113 -954
- package/dist/icons/paper-plane-icon/PaperPlaneIcon.stories.tsx +112 -877
- package/dist/icons/pause-icon/PauseIcon.stories.tsx +113 -1000
- package/dist/icons/pencil-icon/PencilIcon.stories.tsx +115 -1070
- package/dist/icons/phone-icon/PhoneIcon.stories.tsx +115 -978
- package/dist/icons/plus-icon/PlusIcon.stories.tsx +106 -1093
- package/dist/icons/pocket-studio-icon/PocketStudioIcon.stories.tsx +107 -829
- package/dist/icons/scroll-down-icon/ScrollDownIcon.stories.tsx +102 -469
- package/dist/icons/search-icon/SearchIcon.stories.tsx +111 -1124
- package/dist/icons/setting-icon/SettingIcon.stories.tsx +107 -970
- package/dist/icons/share-icon/ShareIcon.stories.tsx +120 -1025
- package/dist/icons/shield-icon/ShieldIcon.stories.tsx +117 -931
- package/dist/icons/site-logo-icon/SiteLogoIcon.stories.tsx +137 -1104
- package/dist/icons/skip-backward-icon/SkipBackwardIcon.stories.tsx +172 -982
- package/dist/icons/skip-forward-icon/SkipForwardIcon.stories.tsx +164 -983
- package/dist/icons/sparkles-soft-icon/SparklesSoftIcon.stories.tsx +105 -958
- package/dist/icons/spinner-gradient-icon/SpinnerGradientIcon.stories.tsx +158 -580
- package/dist/icons/spinner-gradient-icon/index.tsx +6 -1
- package/dist/icons/spinner-solid-icon/SpinnerSolidIcon.stories.tsx +158 -587
- package/dist/icons/spinner-solid-icon/index.tsx +6 -1
- package/dist/icons/spinner-solid-neutral-icon/SpinnerSolidINeutralcon.stories.tsx +146 -682
- package/dist/icons/spinner-solid-neutral-icon/index.tsx +1 -1
- package/dist/icons/star-icon/StarIcon.stories.tsx +124 -904
- package/dist/icons/store-coin-icon/StoreCoinIcon.stories.tsx +112 -964
- package/dist/icons/suggestion-icon/SuggestionIcon.stories.tsx +116 -852
- package/dist/icons/sun-icon/SunIcon.stories.tsx +120 -831
- package/dist/icons/text-color-icon/TextColorIcon.stories.tsx +116 -950
- package/dist/icons/text-indicator-icon/TextIndicatorIcon.stories.tsx +123 -980
- package/dist/icons/threads-icon/ThreadsIcon.stories.tsx +156 -1427
- package/dist/icons/tick-circle-icon/TickCircleIcon.stories.tsx +146 -1142
- package/dist/icons/tick-icon/TickIcon.stories.tsx +145 -1276
- package/dist/icons/trash-icon/TrashIcon.stories.tsx +108 -933
- package/dist/icons/twitter-x-icon/TwitterXIcon.stories.tsx +157 -1402
- package/dist/icons/upload-icon/UploadIcon.stories.tsx +115 -889
- package/dist/icons/vertical-menu-icon/VerticalMenuIcon.stories.tsx +118 -984
- package/dist/icons/video-play-list-icon/VideoPlaylistIcon.stories.tsx +125 -1049
- package/dist/icons/voice-playing-icon/VoicePlayingIcon.stories.tsx +123 -1356
- package/dist/icons/volume-full-icon/VolumeFullIcon.stories.tsx +110 -1171
- package/dist/icons/volume-half-icon/VolumeHalfIcon.stories.tsx +112 -1093
- package/dist/icons/volume-off-icon/VolumeOffIcon.stories.tsx +115 -1087
- package/dist/icons/warning-icon/WarningIcon.stories.tsx +122 -1046
- package/dist/icons/youtube-icon/YoutubeIcon.stories.tsx +161 -936
- package/dist/index.cjs +84 -84
- package/dist/index.js +84 -84
- package/dist/styles/aural-all-theme.css +1222 -0
- package/dist/styles/{aural-theme.css → aural-dark-theme.css} +15 -3
- package/dist/styles/aural-light-theme.css +1047 -0
- 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
|
-
|
|
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,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
|
-
|
|
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
|
-
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
|
-
|
|
252
|
-
<
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
290
|
-
<
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
279
|
+
const handleSend = () => {
|
|
280
|
+
setStep("sent")
|
|
281
|
+
setIsValid(null)
|
|
282
|
+
}
|
|
425
283
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
-
|
|
481
|
-
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return <InteractiveDemo />
|
|
482
423
|
},
|
|
483
424
|
parameters: {
|
|
425
|
+
layout: "fullscreen",
|
|
484
426
|
docs: {
|
|
485
427
|
description: {
|
|
486
|
-
story:
|
|
487
|
-
|
|
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
|
},
|