@umbra.ui/core 0.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/controls/Dropdown/types.d.ts +5 -0
- package/dist/components/controls/Dropdown/types.d.ts.map +1 -0
- package/dist/components/controls/Dropdown/types.js +1 -0
- package/dist/components/controls/SegmentedControl/types.d.ts +6 -0
- package/dist/components/controls/SegmentedControl/types.d.ts.map +1 -0
- package/dist/components/controls/SegmentedControl/types.js +1 -0
- package/dist/components/dialogs/Alert/types.d.ts +7 -0
- package/dist/components/dialogs/Alert/types.d.ts.map +1 -0
- package/dist/components/dialogs/Alert/types.js +1 -0
- package/dist/components/dialogs/Toast/types.d.ts +34 -0
- package/dist/components/dialogs/Toast/types.d.ts.map +1 -0
- package/dist/components/dialogs/Toast/types.js +10 -0
- package/dist/components/dialogs/Toast/useToast.d.ts +36 -0
- package/dist/components/dialogs/Toast/useToast.d.ts.map +1 -0
- package/dist/components/dialogs/Toast/useToast.js +90 -0
- package/dist/components/indicators/Tooltip/tooltip.d.ts +3 -0
- package/dist/components/indicators/Tooltip/tooltip.d.ts.map +1 -0
- package/dist/components/indicators/Tooltip/tooltip.js +33 -0
- package/dist/components/indicators/Tooltip/types.d.ts +14 -0
- package/dist/components/indicators/Tooltip/types.d.ts.map +1 -0
- package/dist/components/indicators/Tooltip/types.js +1 -0
- package/dist/components/indicators/Tooltip/useTooltip.d.ts +18 -0
- package/dist/components/indicators/Tooltip/useTooltip.d.ts.map +1 -0
- package/dist/components/indicators/Tooltip/useTooltip.js +57 -0
- package/dist/components/inputs/Tags/tag-bar-styles.d.ts +14 -0
- package/dist/components/inputs/Tags/tag-bar-styles.d.ts.map +1 -0
- package/dist/components/inputs/Tags/tag-bar-styles.js +313 -0
- package/dist/components/inputs/Tags/types.d.ts +93 -0
- package/dist/components/inputs/Tags/types.d.ts.map +1 -0
- package/dist/components/inputs/Tags/types.js +216 -0
- package/dist/components/inputs/search/types.d.ts +9 -0
- package/dist/components/inputs/search/types.d.ts.map +1 -0
- package/dist/components/inputs/search/types.js +1 -0
- package/dist/components/navigation/adaptive/types.d.ts +16 -0
- package/dist/components/navigation/adaptive/types.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/types.js +1 -0
- package/dist/components/navigation/adaptive/useAdaptiveLayout.d.ts +27 -0
- package/dist/components/navigation/adaptive/useAdaptiveLayout.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useAdaptiveLayout.js +40 -0
- package/dist/components/navigation/adaptive/useBreakpoints.d.ts +6 -0
- package/dist/components/navigation/adaptive/useBreakpoints.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useBreakpoints.js +37 -0
- package/dist/components/navigation/adaptive/useContainerMonitor.d.ts +93 -0
- package/dist/components/navigation/adaptive/useContainerMonitor.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useContainerMonitor.js +145 -0
- package/dist/components/navigation/adaptive/useViewAnimation.d.ts +31 -0
- package/dist/components/navigation/adaptive/useViewAnimation.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useViewAnimation.js +591 -0
- package/dist/components/navigation/adaptive/useViewResize.d.ts +52 -0
- package/dist/components/navigation/adaptive/useViewResize.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useViewResize.js +146 -0
- package/dist/components/navigation/navstack/useNavigationStack.d.ts +25 -0
- package/dist/components/navigation/navstack/useNavigationStack.d.ts.map +1 -0
- package/dist/components/navigation/navstack/useNavigationStack.js +133 -0
- package/dist/components/navigation/slideover/useSlideoverController.d.ts +20 -0
- package/dist/components/navigation/slideover/useSlideoverController.d.ts.map +1 -0
- package/dist/components/navigation/slideover/useSlideoverController.js +267 -0
- package/dist/components/navigation/splitview/useSplitViewController.d.ts +20 -0
- package/dist/components/navigation/splitview/useSplitViewController.d.ts.map +1 -0
- package/dist/components/navigation/splitview/useSplitViewController.js +325 -0
- package/dist/components/navigation/tabcontroller/types.d.ts +21 -0
- package/dist/components/navigation/tabcontroller/types.d.ts.map +1 -0
- package/dist/components/navigation/tabcontroller/types.js +1 -0
- package/dist/components/navigation/tabcontroller/useTabController.d.ts +5 -0
- package/dist/components/navigation/tabcontroller/useTabController.d.ts.map +1 -0
- package/dist/components/navigation/tabcontroller/useTabController.js +10 -0
- package/dist/components/navigation/types.d.ts +8 -0
- package/dist/components/navigation/types.d.ts.map +1 -0
- package/dist/components/navigation/types.js +1 -0
- package/dist/components/pickers/CollectionPicker/types.d.ts +11 -0
- package/dist/components/pickers/CollectionPicker/types.d.ts.map +1 -0
- package/dist/components/pickers/CollectionPicker/types.js +1 -0
- package/dist/components/pickers/ColorPicker/colors.d.ts +13 -0
- package/dist/components/pickers/ColorPicker/colors.d.ts.map +1 -0
- package/dist/components/pickers/ColorPicker/colors.js +266 -0
- package/dist/components/pickers/FilePicker/types.d.ts +10 -0
- package/dist/components/pickers/FilePicker/types.d.ts.map +1 -0
- package/dist/components/pickers/FilePicker/types.js +1 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +196 -0
- package/dist/theme.d.ts +73 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +279 -0
- package/dist/themes/blank.d.ts +7 -0
- package/dist/themes/blank.d.ts.map +1 -0
- package/dist/themes/blank.js +543 -0
- package/dist/themes/crimson-dark.d.ts +4 -0
- package/dist/themes/crimson-dark.d.ts.map +1 -0
- package/dist/themes/crimson-dark.js +552 -0
- package/dist/themes/cyan-light.d.ts +4 -0
- package/dist/themes/cyan-light.d.ts.map +1 -0
- package/dist/themes/cyan-light.js +552 -0
- package/dist/themes/dark.d.ts +4 -0
- package/dist/themes/dark.d.ts.map +1 -0
- package/dist/themes/dark.js +551 -0
- package/dist/themes/gold-dark.d.ts +4 -0
- package/dist/themes/gold-dark.d.ts.map +1 -0
- package/dist/themes/gold-dark.js +552 -0
- package/dist/themes/grass-dark.d.ts +4 -0
- package/dist/themes/grass-dark.d.ts.map +1 -0
- package/dist/themes/grass-dark.js +552 -0
- package/dist/themes/indigo.d.ts +4 -0
- package/dist/themes/indigo.d.ts.map +1 -0
- package/dist/themes/indigo.js +552 -0
- package/dist/themes/light.d.ts +4 -0
- package/dist/themes/light.d.ts.map +1 -0
- package/dist/themes/light.js +551 -0
- package/dist/themes/orange-dark.d.ts +4 -0
- package/dist/themes/orange-dark.d.ts.map +1 -0
- package/dist/themes/orange-dark.js +551 -0
- package/dist/themes/orange-light.d.ts +4 -0
- package/dist/themes/orange-light.d.ts.map +1 -0
- package/dist/themes/orange-light.js +551 -0
- package/package.json +62 -0
- package/src/components/controls/Button/Button.vue +417 -0
- package/src/components/controls/Button/README.md +348 -0
- package/src/components/controls/Button/theme.css +200 -0
- package/src/components/controls/Checkbox/Checkbox.vue +164 -0
- package/src/components/controls/Checkbox/README.md +441 -0
- package/src/components/controls/Checkbox/theme.css +36 -0
- package/src/components/controls/Dropdown/Dropdown.vue +476 -0
- package/src/components/controls/Dropdown/README.md +370 -0
- package/src/components/controls/Dropdown/theme.css +50 -0
- package/src/components/controls/Dropdown/types.ts +6 -0
- package/src/components/controls/IconButton/IconButton.vue +267 -0
- package/src/components/controls/IconButton/README.md +502 -0
- package/src/components/controls/IconButton/theme.css +89 -0
- package/src/components/controls/Radio/README.md +591 -0
- package/src/components/controls/Radio/Radio.vue +89 -0
- package/src/components/controls/Radio/theme.css +14 -0
- package/src/components/controls/RangeSlider/README.md +608 -0
- package/src/components/controls/RangeSlider/RangeSlider.vue +535 -0
- package/src/components/controls/RangeSlider/theme.css +80 -0
- package/src/components/controls/SegmentedControl/README.md +587 -0
- package/src/components/controls/SegmentedControl/SegmentedControl.vue +284 -0
- package/src/components/controls/SegmentedControl/theme.css +60 -0
- package/src/components/controls/SegmentedControl/types.ts +5 -0
- package/src/components/controls/Slider/README.md +627 -0
- package/src/components/controls/Slider/Slider.vue +260 -0
- package/src/components/controls/Slider/theme.css +74 -0
- package/src/components/controls/Stepper/README.md +601 -0
- package/src/components/controls/Stepper/Stepper.vue +103 -0
- package/src/components/controls/Stepper/theme.css +53 -0
- package/src/components/controls/Switch/README.md +667 -0
- package/src/components/controls/Switch/Switch.vue +127 -0
- package/src/components/controls/Switch/theme.css +42 -0
- package/src/components/dialogs/Alert/Alert.vue +218 -0
- package/src/components/dialogs/Alert/README.md +450 -0
- package/src/components/dialogs/Alert/theme.css +44 -0
- package/src/components/dialogs/Alert/types.ts +11 -0
- package/src/components/dialogs/Toast/README.md +522 -0
- package/src/components/dialogs/Toast/Toast.vue +296 -0
- package/src/components/dialogs/Toast/ToastContainer.vue +330 -0
- package/src/components/dialogs/Toast/theme.css +44 -0
- package/src/components/dialogs/Toast/types.ts +46 -0
- package/src/components/dialogs/Toast/useToast.ts +127 -0
- package/src/components/indicators/ProgressBar/ProgressBar.vue +98 -0
- package/src/components/indicators/ProgressBar/README.md +744 -0
- package/src/components/indicators/ProgressBar/theme.css +36 -0
- package/src/components/indicators/Tooltip/README.md +723 -0
- package/src/components/indicators/Tooltip/TooltipProvider.vue +142 -0
- package/src/components/indicators/Tooltip/theme.css +18 -0
- package/src/components/indicators/Tooltip/tooltip.ts +48 -0
- package/src/components/indicators/Tooltip/types.ts +15 -0
- package/src/components/indicators/Tooltip/useTooltip.ts +71 -0
- package/src/components/inputs/AutogrowTextView/AutogrowTextView.vue +110 -0
- package/src/components/inputs/AutogrowTextView/README.md +643 -0
- package/src/components/inputs/AutogrowTextView/theme.css +28 -0
- package/src/components/inputs/InputCard/InputCard.vue +600 -0
- package/src/components/inputs/InputCard/README.md +636 -0
- package/src/components/inputs/InputEmail/InputEmail.vue +698 -0
- package/src/components/inputs/InputEmail/README.md +764 -0
- package/src/components/inputs/InputNumber/InputNumber.vue +300 -0
- package/src/components/inputs/InputNumber/README.md +749 -0
- package/src/components/inputs/InputPhone/InputPhone.vue +645 -0
- package/src/components/inputs/InputPhone/README.md +636 -0
- package/src/components/inputs/InputSecure/InputSecure.vue +646 -0
- package/src/components/inputs/InputSecure/README.md +771 -0
- package/src/components/inputs/InputText/InputText.vue +225 -0
- package/src/components/inputs/InputText/README.md +844 -0
- package/src/components/inputs/OTP/OTP.vue +349 -0
- package/src/components/inputs/OTP/README.md +736 -0
- package/src/components/inputs/OTP/theme.css +50 -0
- package/src/components/inputs/StringCapture/README.md +718 -0
- package/src/components/inputs/StringCapture/StringCapture.vue +315 -0
- package/src/components/inputs/StringCapture/theme.css +86 -0
- package/src/components/inputs/Tags/README.md +897 -0
- package/src/components/inputs/Tags/TagBar.vue +793 -0
- package/src/components/inputs/Tags/TagCreation.vue +219 -0
- package/src/components/inputs/Tags/TagPicker.vue +380 -0
- package/src/components/inputs/Tags/tag-bar-styles.ts +354 -0
- package/src/components/inputs/Tags/theme.css +121 -0
- package/src/components/inputs/Tags/types.ts +346 -0
- package/src/components/inputs/search/README.md +759 -0
- package/src/components/inputs/search/SearchBar.vue +394 -0
- package/src/components/inputs/search/SearchResults.vue +310 -0
- package/src/components/inputs/search/theme.css +187 -0
- package/src/components/inputs/search/types.ts +8 -0
- package/src/components/inputs/theme.css +102 -0
- package/src/components/menus/ActionMenu/ActionMenu.vue +383 -0
- package/src/components/menus/ActionMenu/README.md +825 -0
- package/src/components/menus/ActionMenu/theme.css +93 -0
- package/src/components/models/Popover/Popover.vue +551 -0
- package/src/components/models/Popover/README.md +885 -0
- package/src/components/models/Popover/theme.css +52 -0
- package/src/components/models/Sheet/README.md +1159 -0
- package/src/components/models/Sheet/Sheet.vue +465 -0
- package/src/components/models/Sheet/theme.css +72 -0
- package/src/components/models/Sidebar/README.md +1228 -0
- package/src/components/models/Sidebar/Sidebar.vue +480 -0
- package/src/components/models/Sidebar/theme.css +90 -0
- package/src/components/navigation/adaptive/AdaptiveLayout.vue +779 -0
- package/src/components/navigation/adaptive/AdaptiveLayoutBreadcrumbs.vue +192 -0
- package/src/components/navigation/adaptive/AdaptiveLayoutMenuButton.vue +149 -0
- package/src/components/navigation/adaptive/README.md +768 -0
- package/src/components/navigation/adaptive/types.ts +19 -0
- package/src/components/navigation/adaptive/useAdaptiveLayout.ts +89 -0
- package/src/components/navigation/adaptive/useBreakpoints.ts +41 -0
- package/src/components/navigation/adaptive/useContainerMonitor.ts +214 -0
- package/src/components/navigation/adaptive/useViewAnimation.ts +721 -0
- package/src/components/navigation/adaptive/useViewResize.ts +211 -0
- package/src/components/navigation/navstack/NavigationStack.vue +180 -0
- package/src/components/navigation/navstack/README.md +994 -0
- package/src/components/navigation/navstack/useNavigationStack.ts +164 -0
- package/src/components/navigation/slideover/README.md +1275 -0
- package/src/components/navigation/slideover/SlideoverController.vue +287 -0
- package/src/components/navigation/slideover/useSlideoverController.ts +320 -0
- package/src/components/navigation/splitview/README.md +1115 -0
- package/src/components/navigation/splitview/SplitViewController.vue +176 -0
- package/src/components/navigation/splitview/useSplitViewController.ts +388 -0
- package/src/components/navigation/tabcontroller/README.md +919 -0
- package/src/components/navigation/tabcontroller/TabController.vue +307 -0
- package/src/components/navigation/tabcontroller/TabItem.vue +57 -0
- package/src/components/navigation/tabcontroller/types.ts +24 -0
- package/src/components/navigation/tabcontroller/useTabController.ts +18 -0
- package/src/components/navigation/theme.css +91 -0
- package/src/components/navigation/types.ts +7 -0
- package/src/components/pickers/CollectionPicker/CollectionPicker.vue +398 -0
- package/src/components/pickers/CollectionPicker/README.md +1115 -0
- package/src/components/pickers/CollectionPicker/theme.css +14 -0
- package/src/components/pickers/CollectionPicker/types.ts +11 -0
- package/src/components/pickers/ColorPicker/ColorPicker.vue +376 -0
- package/src/components/pickers/ColorPicker/README.md +1439 -0
- package/src/components/pickers/ColorPicker/colors.ts +299 -0
- package/src/components/pickers/ColorPicker/theme.css +32 -0
- package/src/components/pickers/DatePicker/DatePicker.vue +660 -0
- package/src/components/pickers/DatePicker/README.md +1195 -0
- package/src/components/pickers/DatePicker/theme.css +22 -0
- package/src/components/pickers/FilePicker/FilePicker.vue +534 -0
- package/src/components/pickers/FilePicker/README.md +1542 -0
- package/src/components/pickers/FilePicker/theme.css +48 -0
- package/src/components/pickers/FilePicker/types.ts +10 -0
- package/src/components/pickers/IconPicker/IconPicker.vue +327 -0
- package/src/components/pickers/IconPicker/README.md +1161 -0
- package/src/components/pickers/IconPicker/theme.css +28 -0
- package/src/components/pickers/theme.css +82 -0
- package/src/components/views/MarkdownViewer/MarkdownViewer.vue +442 -0
- package/src/components/views/MarkdownViewer/README.md +833 -0
- package/src/components/views/MarkdownViewer/theme.css +130 -0
- package/src/index.ts +263 -0
- package/src/theme.ts +378 -0
- package/src/themes/crimson-dark.ts +556 -0
- package/src/themes/cyan-light.ts +556 -0
- package/src/themes/dark.ts +557 -0
- package/src/themes/gold-dark.ts +556 -0
- package/src/themes/grass-dark.ts +556 -0
- package/src/themes/indigo.ts +556 -0
- package/src/themes/light.ts +557 -0
- package/src/themes/orange-dark.ts +557 -0
- package/src/themes/orange-light.ts +557 -0
- package/src/vue.d.ts +45 -0
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
# OTP
|
|
2
|
+
|
|
3
|
+
A One-Time Password (OTP) input component built with Vue 3 Composition API and TypeScript. The OTP component provides a secure, user-friendly interface for entering verification codes with automatic focus management, paste support, keyboard navigation, and accessibility features.
|
|
4
|
+
|
|
5
|
+
## Installation/Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { OTP } from "@umbra-ui/core";
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Dependencies:**
|
|
12
|
+
|
|
13
|
+
- Vue 3.x
|
|
14
|
+
|
|
15
|
+
## Basic Usage
|
|
16
|
+
|
|
17
|
+
```vue
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
import { ref } from "vue";
|
|
20
|
+
import { OTP } from "@umbra-ui/core";
|
|
21
|
+
|
|
22
|
+
const otpCode = ref("");
|
|
23
|
+
|
|
24
|
+
const handleOTPUpdate = (value: string) => {
|
|
25
|
+
console.log("OTP Code:", value);
|
|
26
|
+
otpCode.value = value;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const handleOTPComplete = (value: string) => {
|
|
30
|
+
console.log("OTP Complete:", value);
|
|
31
|
+
// Handle OTP verification
|
|
32
|
+
};
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<template>
|
|
36
|
+
<div>
|
|
37
|
+
<OTP
|
|
38
|
+
v-model="otpCode"
|
|
39
|
+
:length="6"
|
|
40
|
+
:auto-focus="true"
|
|
41
|
+
:auto-submit="true"
|
|
42
|
+
@complete="handleOTPComplete"
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
</template>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Props
|
|
49
|
+
|
|
50
|
+
| Prop Name | Type | Required | Default | Description |
|
|
51
|
+
| ------------ | --------- | -------- | ------- | ---------------------------------------------------------- |
|
|
52
|
+
| `modelValue` | `string` | No | `""` | The OTP code value |
|
|
53
|
+
| `length` | `number` | No | `6` | Number of OTP digits |
|
|
54
|
+
| `disabled` | `boolean` | No | `false` | Whether the OTP input is disabled |
|
|
55
|
+
| `error` | `string` | No | `""` | Error message to display |
|
|
56
|
+
| `loading` | `boolean` | No | `false` | Whether the OTP is in loading state |
|
|
57
|
+
| `autoFocus` | `boolean` | No | `true` | Whether to auto-focus the first input on mount |
|
|
58
|
+
| `autoSubmit` | `boolean` | No | `true` | Whether to emit complete event when all digits are entered |
|
|
59
|
+
|
|
60
|
+
## Events
|
|
61
|
+
|
|
62
|
+
| Event Name | Payload Type | Description |
|
|
63
|
+
| ------------------- | ------------ | ----------------------------------------------------------- |
|
|
64
|
+
| `update:modelValue` | `string` | Emitted when the OTP value changes |
|
|
65
|
+
| `complete` | `string` | Emitted when all digits are entered (if autoSubmit is true) |
|
|
66
|
+
| `change` | `string` | Emitted when the OTP value changes |
|
|
67
|
+
| `focus` | `void` | Emitted when any input receives focus |
|
|
68
|
+
| `blur` | `void` | Emitted when any input loses focus |
|
|
69
|
+
|
|
70
|
+
## Slots
|
|
71
|
+
|
|
72
|
+
This component does not provide any slots.
|
|
73
|
+
|
|
74
|
+
## Exposed Methods/Refs
|
|
75
|
+
|
|
76
|
+
The OTP component exposes the following methods via template refs:
|
|
77
|
+
|
|
78
|
+
| Method Name | Parameters | Description |
|
|
79
|
+
| ----------- | ---------- | ------------------------------------------------- |
|
|
80
|
+
| `focus` | `()` | Focuses the first input field |
|
|
81
|
+
| `clear` | `()` | Clears all input fields and focuses the first one |
|
|
82
|
+
|
|
83
|
+
### Example Usage
|
|
84
|
+
|
|
85
|
+
```vue
|
|
86
|
+
<script setup lang="ts">
|
|
87
|
+
import { ref } from "vue";
|
|
88
|
+
import { OTP } from "@umbra-ui/core";
|
|
89
|
+
|
|
90
|
+
const otpRef = ref();
|
|
91
|
+
|
|
92
|
+
const clearOTP = () => {
|
|
93
|
+
otpRef.value?.clear();
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const focusOTP = () => {
|
|
97
|
+
otpRef.value?.focus();
|
|
98
|
+
};
|
|
99
|
+
</script>
|
|
100
|
+
|
|
101
|
+
<template>
|
|
102
|
+
<div>
|
|
103
|
+
<OTP ref="otpRef" v-model="otpCode" />
|
|
104
|
+
<button @click="clearOTP">Clear</button>
|
|
105
|
+
<button @click="focusOTP">Focus</button>
|
|
106
|
+
</div>
|
|
107
|
+
</template>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## CSS Customization
|
|
111
|
+
|
|
112
|
+
The OTP component uses CSS custom properties for theming and customization:
|
|
113
|
+
|
|
114
|
+
### OTP Pin Variables
|
|
115
|
+
|
|
116
|
+
```css
|
|
117
|
+
/* Pin Background Colors */
|
|
118
|
+
--otp-pin-empty-bg: var(--otp-pin-empty-background);
|
|
119
|
+
--otp-pin-filled-bg: var(--otp-pin-filled-background);
|
|
120
|
+
|
|
121
|
+
/* Pin Text Colors */
|
|
122
|
+
--otp-pin-empty-text: var(--otp-pin-empty-text-color);
|
|
123
|
+
--otp-pin-filled-text: var(--otp-pin-filled-text-color);
|
|
124
|
+
|
|
125
|
+
/* Pin Border Colors */
|
|
126
|
+
--otp-pin-border: var(--otp-pin-border-color);
|
|
127
|
+
--otp-filled-border: var(--otp-filled-border-color);
|
|
128
|
+
|
|
129
|
+
/* Focus Colors */
|
|
130
|
+
--otp-focus-border: var(--otp-focus-border-color);
|
|
131
|
+
|
|
132
|
+
/* Error Colors */
|
|
133
|
+
--otp-error-border: var(--otp-error-border-color);
|
|
134
|
+
--otp-error-text: var(--otp-error-text-color);
|
|
135
|
+
|
|
136
|
+
/* Loading Colors */
|
|
137
|
+
--otp-loading-text: var(--otp-loading-text-color);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Key Features
|
|
141
|
+
|
|
142
|
+
### Input Management
|
|
143
|
+
|
|
144
|
+
- **Automatic Focus**: Auto-focuses the first input on mount
|
|
145
|
+
- **Smart Navigation**: Automatically moves to the next field when a digit is entered
|
|
146
|
+
- **Backspace Handling**: Moves to the previous field when backspace is pressed on an empty field
|
|
147
|
+
- **Arrow Key Navigation**: Left/right arrow keys move between fields
|
|
148
|
+
- **Delete Key**: Clears all fields from the current position
|
|
149
|
+
|
|
150
|
+
### Paste Support
|
|
151
|
+
|
|
152
|
+
- **Paste Detection**: Automatically detects and handles pasted OTP codes
|
|
153
|
+
- **Smart Distribution**: Distributes pasted digits across available fields
|
|
154
|
+
- **Focus Management**: Focuses the next empty field after pasting
|
|
155
|
+
|
|
156
|
+
### Validation & States
|
|
157
|
+
|
|
158
|
+
- **Digit-Only Input**: Only accepts numeric digits (0-9)
|
|
159
|
+
- **Error States**: Visual error indication with custom error messages
|
|
160
|
+
- **Loading States**: Loading indicator with disabled interaction
|
|
161
|
+
- **Disabled States**: Non-interactive state with visual feedback
|
|
162
|
+
|
|
163
|
+
### Accessibility
|
|
164
|
+
|
|
165
|
+
- **ARIA Labels**: Proper labeling for screen readers
|
|
166
|
+
- **Keyboard Navigation**: Full keyboard support
|
|
167
|
+
- **Focus Management**: Clear focus indicators
|
|
168
|
+
- **Error Announcements**: Error messages are announced to screen readers
|
|
169
|
+
|
|
170
|
+
## Examples
|
|
171
|
+
|
|
172
|
+
### Basic OTP Input
|
|
173
|
+
|
|
174
|
+
```vue
|
|
175
|
+
<script setup lang="ts">
|
|
176
|
+
import { ref } from "vue";
|
|
177
|
+
import { OTP } from "@umbra-ui/core";
|
|
178
|
+
|
|
179
|
+
const otpCode = ref("");
|
|
180
|
+
const isComplete = ref(false);
|
|
181
|
+
|
|
182
|
+
const handleOTPComplete = (value: string) => {
|
|
183
|
+
isComplete.value = true;
|
|
184
|
+
console.log("OTP Complete:", value);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const handleOTPChange = (value: string) => {
|
|
188
|
+
isComplete.value = false;
|
|
189
|
+
console.log("OTP Changed:", value);
|
|
190
|
+
};
|
|
191
|
+
</script>
|
|
192
|
+
|
|
193
|
+
<template>
|
|
194
|
+
<div class="otp-form">
|
|
195
|
+
<h3>Enter Verification Code</h3>
|
|
196
|
+
|
|
197
|
+
<div class="otp-container">
|
|
198
|
+
<OTP
|
|
199
|
+
v-model="otpCode"
|
|
200
|
+
:length="6"
|
|
201
|
+
:auto-focus="true"
|
|
202
|
+
:auto-submit="true"
|
|
203
|
+
@complete="handleOTPComplete"
|
|
204
|
+
@change="handleOTPChange"
|
|
205
|
+
/>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<div class="otp-status">
|
|
209
|
+
<p v-if="isComplete" class="success">Code entered successfully!</p>
|
|
210
|
+
<p v-else class="instruction">
|
|
211
|
+
Enter the 6-digit code sent to your phone
|
|
212
|
+
</p>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
</template>
|
|
216
|
+
|
|
217
|
+
<style module>
|
|
218
|
+
.otp-form {
|
|
219
|
+
max-width: 400px;
|
|
220
|
+
padding: 2rem;
|
|
221
|
+
border: 1px solid #e0e0e0;
|
|
222
|
+
border-radius: 0.5rem;
|
|
223
|
+
text-align: center;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.otp-container {
|
|
227
|
+
margin: 2rem 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.otp-status {
|
|
231
|
+
margin-top: 1rem;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.success {
|
|
235
|
+
color: #28a745;
|
|
236
|
+
font-weight: 500;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.instruction {
|
|
240
|
+
color: #666;
|
|
241
|
+
font-size: 0.875rem;
|
|
242
|
+
}
|
|
243
|
+
</style>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### OTP with Error Handling
|
|
247
|
+
|
|
248
|
+
```vue
|
|
249
|
+
<script setup lang="ts">
|
|
250
|
+
import { ref, computed } from "vue";
|
|
251
|
+
import { OTP } from "@umbra-ui/core";
|
|
252
|
+
|
|
253
|
+
const otpCode = ref("");
|
|
254
|
+
const error = ref("");
|
|
255
|
+
const isLoading = ref(false);
|
|
256
|
+
const attempts = ref(0);
|
|
257
|
+
|
|
258
|
+
const isComplete = computed(() => {
|
|
259
|
+
return otpCode.value.length === 6;
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const handleOTPComplete = async (value: string) => {
|
|
263
|
+
isLoading.value = true;
|
|
264
|
+
error.value = "";
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
// Simulate API call
|
|
268
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
269
|
+
|
|
270
|
+
// Simulate verification failure
|
|
271
|
+
if (value !== "123456") {
|
|
272
|
+
attempts.value++;
|
|
273
|
+
error.value = `Invalid code. ${3 - attempts.value} attempts remaining.`;
|
|
274
|
+
otpCode.value = "";
|
|
275
|
+
|
|
276
|
+
if (attempts.value >= 3) {
|
|
277
|
+
error.value = "Too many attempts. Please request a new code.";
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
error.value = "";
|
|
281
|
+
console.log("Verification successful!");
|
|
282
|
+
}
|
|
283
|
+
} catch (err) {
|
|
284
|
+
error.value = "Verification failed. Please try again.";
|
|
285
|
+
} finally {
|
|
286
|
+
isLoading.value = false;
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const handleOTPChange = () => {
|
|
291
|
+
error.value = "";
|
|
292
|
+
};
|
|
293
|
+
</script>
|
|
294
|
+
|
|
295
|
+
<template>
|
|
296
|
+
<div class="otp-verification">
|
|
297
|
+
<h3>Verify Your Account</h3>
|
|
298
|
+
|
|
299
|
+
<div class="otp-container">
|
|
300
|
+
<OTP
|
|
301
|
+
v-model="otpCode"
|
|
302
|
+
:length="6"
|
|
303
|
+
:error="error"
|
|
304
|
+
:loading="isLoading"
|
|
305
|
+
:auto-focus="true"
|
|
306
|
+
:auto-submit="true"
|
|
307
|
+
@complete="handleOTPComplete"
|
|
308
|
+
@change="handleOTPChange"
|
|
309
|
+
/>
|
|
310
|
+
</div>
|
|
311
|
+
|
|
312
|
+
<div class="verification-status">
|
|
313
|
+
<p v-if="isLoading" class="loading">Verifying code...</p>
|
|
314
|
+
<p v-else-if="error" class="error">{{ error }}</p>
|
|
315
|
+
<p v-else class="instruction">Enter the 6-digit verification code</p>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
</template>
|
|
319
|
+
|
|
320
|
+
<style module>
|
|
321
|
+
.otp-verification {
|
|
322
|
+
max-width: 400px;
|
|
323
|
+
padding: 2rem;
|
|
324
|
+
border: 1px solid #e0e0e0;
|
|
325
|
+
border-radius: 0.5rem;
|
|
326
|
+
text-align: center;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.otp-container {
|
|
330
|
+
margin: 2rem 0;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.verification-status {
|
|
334
|
+
margin-top: 1rem;
|
|
335
|
+
min-height: 1.5rem;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.loading {
|
|
339
|
+
color: #007bff;
|
|
340
|
+
font-weight: 500;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.error {
|
|
344
|
+
color: #dc3545;
|
|
345
|
+
font-weight: 500;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.instruction {
|
|
349
|
+
color: #666;
|
|
350
|
+
font-size: 0.875rem;
|
|
351
|
+
}
|
|
352
|
+
</style>
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Custom Length OTP
|
|
356
|
+
|
|
357
|
+
```vue
|
|
358
|
+
<script setup lang="ts">
|
|
359
|
+
import { ref } from "vue";
|
|
360
|
+
import { OTP } from "@umbra-ui/core";
|
|
361
|
+
|
|
362
|
+
const otpCode = ref("");
|
|
363
|
+
const otpLength = ref(4);
|
|
364
|
+
|
|
365
|
+
const handleOTPComplete = (value: string) => {
|
|
366
|
+
console.log(`${otpLength.value}-digit OTP Complete:`, value);
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const changeLength = (length: number) => {
|
|
370
|
+
otpLength.value = length;
|
|
371
|
+
otpCode.value = "";
|
|
372
|
+
};
|
|
373
|
+
</script>
|
|
374
|
+
|
|
375
|
+
<template>
|
|
376
|
+
<div class="custom-otp">
|
|
377
|
+
<h3>Custom Length OTP</h3>
|
|
378
|
+
|
|
379
|
+
<div class="length-selector">
|
|
380
|
+
<label>OTP Length:</label>
|
|
381
|
+
<div class="length-buttons">
|
|
382
|
+
<button
|
|
383
|
+
v-for="length in [4, 6, 8]"
|
|
384
|
+
:key="length"
|
|
385
|
+
@click="changeLength(length)"
|
|
386
|
+
:class="{ active: otpLength === length }"
|
|
387
|
+
class="length-btn"
|
|
388
|
+
>
|
|
389
|
+
{{ length }} digits
|
|
390
|
+
</button>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
|
|
394
|
+
<div class="otp-container">
|
|
395
|
+
<OTP
|
|
396
|
+
v-model="otpCode"
|
|
397
|
+
:length="otpLength"
|
|
398
|
+
:auto-focus="true"
|
|
399
|
+
:auto-submit="true"
|
|
400
|
+
@complete="handleOTPComplete"
|
|
401
|
+
/>
|
|
402
|
+
</div>
|
|
403
|
+
|
|
404
|
+
<div class="otp-info">
|
|
405
|
+
<p>Enter the {{ otpLength }}-digit code</p>
|
|
406
|
+
</div>
|
|
407
|
+
</div>
|
|
408
|
+
</template>
|
|
409
|
+
|
|
410
|
+
<style module>
|
|
411
|
+
.custom-otp {
|
|
412
|
+
max-width: 500px;
|
|
413
|
+
padding: 2rem;
|
|
414
|
+
border: 1px solid #e0e0e0;
|
|
415
|
+
border-radius: 0.5rem;
|
|
416
|
+
text-align: center;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.length-selector {
|
|
420
|
+
margin-bottom: 2rem;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.length-selector label {
|
|
424
|
+
display: block;
|
|
425
|
+
margin-bottom: 1rem;
|
|
426
|
+
font-weight: 500;
|
|
427
|
+
color: #333;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.length-buttons {
|
|
431
|
+
display: flex;
|
|
432
|
+
gap: 0.5rem;
|
|
433
|
+
justify-content: center;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.length-btn {
|
|
437
|
+
padding: 0.5rem 1rem;
|
|
438
|
+
border: 1px solid #ddd;
|
|
439
|
+
background-color: #fff;
|
|
440
|
+
border-radius: 0.25rem;
|
|
441
|
+
cursor: pointer;
|
|
442
|
+
transition: all 0.2s;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.length-btn:hover {
|
|
446
|
+
background-color: #f8f9fa;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.length-btn.active {
|
|
450
|
+
background-color: #007bff;
|
|
451
|
+
color: white;
|
|
452
|
+
border-color: #007bff;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.otp-container {
|
|
456
|
+
margin: 2rem 0;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.otp-info {
|
|
460
|
+
margin-top: 1rem;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.otp-info p {
|
|
464
|
+
color: #666;
|
|
465
|
+
font-size: 0.875rem;
|
|
466
|
+
}
|
|
467
|
+
</style>
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### OTP with Resend Functionality
|
|
471
|
+
|
|
472
|
+
```vue
|
|
473
|
+
<script setup lang="ts">
|
|
474
|
+
import { ref, computed } from "vue";
|
|
475
|
+
import { OTP } from "@umbra-ui/core";
|
|
476
|
+
|
|
477
|
+
const otpCode = ref("");
|
|
478
|
+
const isResending = ref(false);
|
|
479
|
+
const resendCooldown = ref(0);
|
|
480
|
+
const resendCount = ref(0);
|
|
481
|
+
|
|
482
|
+
const canResend = computed(() => {
|
|
483
|
+
return resendCooldown.value === 0 && !isResending.value;
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
const handleOTPComplete = (value: string) => {
|
|
487
|
+
console.log("OTP Complete:", value);
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const resendOTP = async () => {
|
|
491
|
+
if (!canResend.value) return;
|
|
492
|
+
|
|
493
|
+
isResending.value = true;
|
|
494
|
+
resendCount.value++;
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
// Simulate API call
|
|
498
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
499
|
+
console.log("OTP resent successfully");
|
|
500
|
+
|
|
501
|
+
// Start cooldown
|
|
502
|
+
resendCooldown.value = 30;
|
|
503
|
+
const interval = setInterval(() => {
|
|
504
|
+
resendCooldown.value--;
|
|
505
|
+
if (resendCooldown.value === 0) {
|
|
506
|
+
clearInterval(interval);
|
|
507
|
+
}
|
|
508
|
+
}, 1000);
|
|
509
|
+
} catch (err) {
|
|
510
|
+
console.error("Failed to resend OTP");
|
|
511
|
+
} finally {
|
|
512
|
+
isResending.value = false;
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
</script>
|
|
516
|
+
|
|
517
|
+
<template>
|
|
518
|
+
<div class="otp-with-resend">
|
|
519
|
+
<h3>Two-Factor Authentication</h3>
|
|
520
|
+
|
|
521
|
+
<div class="otp-container">
|
|
522
|
+
<OTP
|
|
523
|
+
v-model="otpCode"
|
|
524
|
+
:length="6"
|
|
525
|
+
:auto-focus="true"
|
|
526
|
+
:auto-submit="true"
|
|
527
|
+
@complete="handleOTPComplete"
|
|
528
|
+
/>
|
|
529
|
+
</div>
|
|
530
|
+
|
|
531
|
+
<div class="resend-section">
|
|
532
|
+
<p class="instruction">Enter the 6-digit code sent to your phone</p>
|
|
533
|
+
|
|
534
|
+
<div class="resend-actions">
|
|
535
|
+
<button @click="resendOTP" :disabled="!canResend" class="resend-btn">
|
|
536
|
+
<span v-if="isResending">Sending...</span>
|
|
537
|
+
<span v-else-if="resendCooldown > 0"
|
|
538
|
+
>Resend in {{ resendCooldown }}s</span
|
|
539
|
+
>
|
|
540
|
+
<span v-else>Resend Code</span>
|
|
541
|
+
</button>
|
|
542
|
+
|
|
543
|
+
<p v-if="resendCount > 0" class="resend-count">
|
|
544
|
+
Resent {{ resendCount }} time{{ resendCount > 1 ? "s" : "" }}
|
|
545
|
+
</p>
|
|
546
|
+
</div>
|
|
547
|
+
</div>
|
|
548
|
+
</div>
|
|
549
|
+
</template>
|
|
550
|
+
|
|
551
|
+
<style module>
|
|
552
|
+
.otp-with-resend {
|
|
553
|
+
max-width: 400px;
|
|
554
|
+
padding: 2rem;
|
|
555
|
+
border: 1px solid #e0e0e0;
|
|
556
|
+
border-radius: 0.5rem;
|
|
557
|
+
text-align: center;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.otp-container {
|
|
561
|
+
margin: 2rem 0;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.resend-section {
|
|
565
|
+
margin-top: 1rem;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.instruction {
|
|
569
|
+
color: #666;
|
|
570
|
+
font-size: 0.875rem;
|
|
571
|
+
margin-bottom: 1rem;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.resend-actions {
|
|
575
|
+
display: flex;
|
|
576
|
+
flex-direction: column;
|
|
577
|
+
gap: 0.5rem;
|
|
578
|
+
align-items: center;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.resend-btn {
|
|
582
|
+
padding: 0.5rem 1rem;
|
|
583
|
+
background-color: #007bff;
|
|
584
|
+
color: white;
|
|
585
|
+
border: none;
|
|
586
|
+
border-radius: 0.25rem;
|
|
587
|
+
cursor: pointer;
|
|
588
|
+
font-size: 0.875rem;
|
|
589
|
+
transition: background-color 0.2s;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.resend-btn:hover:not(:disabled) {
|
|
593
|
+
background-color: #0056b3;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.resend-btn:disabled {
|
|
597
|
+
background-color: #6c757d;
|
|
598
|
+
cursor: not-allowed;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.resend-count {
|
|
602
|
+
color: #666;
|
|
603
|
+
font-size: 0.75rem;
|
|
604
|
+
margin: 0;
|
|
605
|
+
}
|
|
606
|
+
</style>
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### OTP with Manual Submit
|
|
610
|
+
|
|
611
|
+
```vue
|
|
612
|
+
<script setup lang="ts">
|
|
613
|
+
import { ref, computed } from "vue";
|
|
614
|
+
import { OTP } from "@umbra-ui/core";
|
|
615
|
+
|
|
616
|
+
const otpCode = ref("");
|
|
617
|
+
const isVerifying = ref(false);
|
|
618
|
+
const error = ref("");
|
|
619
|
+
|
|
620
|
+
const isComplete = computed(() => {
|
|
621
|
+
return otpCode.value.length === 6;
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
const handleOTPChange = () => {
|
|
625
|
+
error.value = "";
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
const verifyOTP = async () => {
|
|
629
|
+
if (!isComplete.value) return;
|
|
630
|
+
|
|
631
|
+
isVerifying.value = true;
|
|
632
|
+
error.value = "";
|
|
633
|
+
|
|
634
|
+
try {
|
|
635
|
+
// Simulate API call
|
|
636
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
637
|
+
|
|
638
|
+
if (otpCode.value === "123456") {
|
|
639
|
+
console.log("Verification successful!");
|
|
640
|
+
} else {
|
|
641
|
+
error.value = "Invalid verification code";
|
|
642
|
+
}
|
|
643
|
+
} catch (err) {
|
|
644
|
+
error.value = "Verification failed. Please try again.";
|
|
645
|
+
} finally {
|
|
646
|
+
isVerifying.value = false;
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
</script>
|
|
650
|
+
|
|
651
|
+
<template>
|
|
652
|
+
<div class="manual-submit-otp">
|
|
653
|
+
<h3>Manual Submit OTP</h3>
|
|
654
|
+
|
|
655
|
+
<div class="otp-container">
|
|
656
|
+
<OTP
|
|
657
|
+
v-model="otpCode"
|
|
658
|
+
:length="6"
|
|
659
|
+
:error="error"
|
|
660
|
+
:auto-submit="false"
|
|
661
|
+
@change="handleOTPChange"
|
|
662
|
+
/>
|
|
663
|
+
</div>
|
|
664
|
+
|
|
665
|
+
<div class="submit-section">
|
|
666
|
+
<button
|
|
667
|
+
@click="verifyOTP"
|
|
668
|
+
:disabled="!isComplete || isVerifying"
|
|
669
|
+
class="verify-btn"
|
|
670
|
+
>
|
|
671
|
+
<span v-if="isVerifying">Verifying...</span>
|
|
672
|
+
<span v-else>Verify Code</span>
|
|
673
|
+
</button>
|
|
674
|
+
|
|
675
|
+
<p v-if="error" class="error-message">{{ error }}</p>
|
|
676
|
+
</div>
|
|
677
|
+
</div>
|
|
678
|
+
</template>
|
|
679
|
+
|
|
680
|
+
<style module>
|
|
681
|
+
.manual-submit-otp {
|
|
682
|
+
max-width: 400px;
|
|
683
|
+
padding: 2rem;
|
|
684
|
+
border: 1px solid #e0e0e0;
|
|
685
|
+
border-radius: 0.5rem;
|
|
686
|
+
text-align: center;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.otp-container {
|
|
690
|
+
margin: 2rem 0;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.submit-section {
|
|
694
|
+
margin-top: 1rem;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.verify-btn {
|
|
698
|
+
padding: 0.75rem 2rem;
|
|
699
|
+
background-color: #28a745;
|
|
700
|
+
color: white;
|
|
701
|
+
border: none;
|
|
702
|
+
border-radius: 0.25rem;
|
|
703
|
+
cursor: pointer;
|
|
704
|
+
font-size: 1rem;
|
|
705
|
+
transition: background-color 0.2s;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
.verify-btn:hover:not(:disabled) {
|
|
709
|
+
background-color: #218838;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
.verify-btn:disabled {
|
|
713
|
+
background-color: #6c757d;
|
|
714
|
+
cursor: not-allowed;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
.error-message {
|
|
718
|
+
color: #dc3545;
|
|
719
|
+
font-size: 0.875rem;
|
|
720
|
+
margin-top: 0.5rem;
|
|
721
|
+
}
|
|
722
|
+
</style>
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
## Technical Notes
|
|
726
|
+
|
|
727
|
+
- OTP component provides secure, accessible input for verification codes
|
|
728
|
+
- Automatic focus management moves between fields as digits are entered
|
|
729
|
+
- Paste support automatically distributes pasted digits across fields
|
|
730
|
+
- Keyboard navigation includes arrow keys, backspace, and delete functionality
|
|
731
|
+
- Error states provide visual feedback with custom error messages
|
|
732
|
+
- Loading states disable interaction during verification
|
|
733
|
+
- Accessibility features include ARIA labels and screen reader support
|
|
734
|
+
- Responsive design adapts to mobile devices with appropriate sizing
|
|
735
|
+
- CSS custom properties enable easy theming for light and dark modes
|
|
736
|
+
- Exposed methods allow programmatic control of focus and clearing
|