@tempoxyz/ds 0.3.9 → 0.3.11
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/docs/AGENTS.md +9 -0
- package/dist/docs/components/accordion.md +489 -0
- package/dist/docs/components/address-card.md +147 -0
- package/dist/docs/components/agent-logo.md +194 -0
- package/dist/docs/components/alert-dialog.md +631 -0
- package/dist/docs/components/alert.md +448 -0
- package/dist/docs/components/amount-input.md +305 -0
- package/dist/docs/components/amount.md +794 -0
- package/dist/docs/components/app-shell.md +559 -0
- package/dist/docs/components/asset-chip.md +294 -0
- package/dist/docs/components/autocomplete.md +619 -0
- package/dist/docs/components/avatar.md +321 -0
- package/dist/docs/components/badge.md +339 -0
- package/dist/docs/components/badged-logo.md +116 -0
- package/dist/docs/components/banner.md +379 -0
- package/dist/docs/components/bar-chart.md +208 -0
- package/dist/docs/components/breadcrumbs.md +380 -0
- package/dist/docs/components/button-group.md +286 -0
- package/dist/docs/components/button.md +650 -0
- package/dist/docs/components/calendar.md +375 -0
- package/dist/docs/components/callout.md +96 -0
- package/dist/docs/components/checkbox-group.md +521 -0
- package/dist/docs/components/checkbox.md +467 -0
- package/dist/docs/components/choice-group.md +516 -0
- package/dist/docs/components/choicebox.md +641 -0
- package/dist/docs/components/code.md +100 -0
- package/dist/docs/components/combobox.md +939 -0
- package/dist/docs/components/container.md +257 -0
- package/dist/docs/components/copy-button.md +642 -0
- package/dist/docs/components/date-picker.md +514 -0
- package/dist/docs/components/dialog.md +1332 -0
- package/dist/docs/components/drawer.md +966 -0
- package/dist/docs/components/empty.md +340 -0
- package/dist/docs/components/field.md +570 -0
- package/dist/docs/components/fieldset.md +398 -0
- package/dist/docs/components/frame.md +434 -0
- package/dist/docs/components/gauge.md +299 -0
- package/dist/docs/components/get-started-visual.md +117 -0
- package/dist/docs/components/grid.md +288 -0
- package/dist/docs/components/hero-visual.md +155 -0
- package/dist/docs/components/hex.md +487 -0
- package/dist/docs/components/icon-tile.md +292 -0
- package/dist/docs/components/identicon.md +371 -0
- package/dist/docs/components/inline.md +233 -0
- package/dist/docs/components/inset.md +225 -0
- package/dist/docs/components/kbd.md +423 -0
- package/dist/docs/components/list-group.md +981 -0
- package/dist/docs/components/loading-dots.md +237 -0
- package/dist/docs/components/menu.md +934 -0
- package/dist/docs/components/native-select.md +204 -0
- package/dist/docs/components/network-logo.md +248 -0
- package/dist/docs/components/number-field.md +543 -0
- package/dist/docs/components/otp-field.md +616 -0
- package/dist/docs/components/pagination.md +516 -0
- package/dist/docs/components/popover.md +544 -0
- package/dist/docs/components/progress.md +388 -0
- package/dist/docs/components/qr.md +314 -0
- package/dist/docs/components/radio-group.md +528 -0
- package/dist/docs/components/secret-value.md +406 -0
- package/dist/docs/components/select.md +751 -0
- package/dist/docs/components/separator.md +379 -0
- package/dist/docs/components/sidebar-menu.md +922 -0
- package/dist/docs/components/skeleton.md +517 -0
- package/dist/docs/components/slider.md +539 -0
- package/dist/docs/components/snippet.md +424 -0
- package/dist/docs/components/spinner.md +216 -0
- package/dist/docs/components/split-bar.md +139 -0
- package/dist/docs/components/split-button.md +464 -0
- package/dist/docs/components/stack.md +299 -0
- package/dist/docs/components/status-chip.md +226 -0
- package/dist/docs/components/status-indicator.md +119 -0
- package/dist/docs/components/status-row.md +174 -0
- package/dist/docs/components/stepper.md +451 -0
- package/dist/docs/components/success-hero.md +157 -0
- package/dist/docs/components/surface.md +639 -0
- package/dist/docs/components/switch.md +472 -0
- package/dist/docs/components/table.md +967 -0
- package/dist/docs/components/tabs.md +627 -0
- package/dist/docs/components/tempo-logo.md +185 -0
- package/dist/docs/components/text-input.md +624 -0
- package/dist/docs/components/textarea.md +352 -0
- package/dist/docs/components/toast.md +535 -0
- package/dist/docs/components/token-logo.md +371 -0
- package/dist/docs/components/token.md +247 -0
- package/dist/docs/components/tooltip.md +490 -0
- package/dist/docs/components/verification-code.md +116 -0
- package/dist/docs/components/visual.md +249 -0
- package/dist/docs/components.md +203 -0
- package/dist/docs/foundations/accessibility.md +377 -0
- package/dist/docs/foundations/agents.md +150 -0
- package/dist/docs/foundations/color.md +522 -0
- package/dist/docs/foundations/content/tone.md +194 -0
- package/dist/docs/foundations/content/voice.md +194 -0
- package/dist/docs/foundations/content/writing-style.md +406 -0
- package/dist/docs/foundations/iconography.md +282 -0
- package/dist/docs/foundations/intent.md +360 -0
- package/dist/docs/foundations/layout.md +508 -0
- package/dist/docs/foundations/linter.md +296 -0
- package/dist/docs/foundations/motion.md +151 -0
- package/dist/docs/foundations/radius-and-padding.md +86 -0
- package/dist/docs/foundations/typography.md +266 -0
- package/dist/docs/index.md +137 -0
- package/dist/docs/introduction/faq.md +132 -0
- package/dist/docs/introduction/getting-started.md +377 -0
- package/dist/docs/patterns/blockchain-values.md +709 -0
- package/dist/docs/patterns/dashboards.md +638 -0
- package/dist/docs/patterns/destructive-actions.md +626 -0
- package/dist/docs/patterns/empty-states.md +612 -0
- package/dist/docs/patterns/errors-and-validation.md +808 -0
- package/dist/docs/patterns/feedback-and-status.md +518 -0
- package/dist/docs/patterns/forms.md +679 -0
- package/dist/docs/patterns/loading.md +630 -0
- package/dist/docs/patterns/navigation.md +397 -0
- package/dist/docs/patterns/payments.md +765 -0
- package/dist/docs/patterns/responsive-layout.md +997 -0
- package/dist/docs/patterns/tables-and-lists.md +964 -0
- package/dist/docs/products/tempo-console.md +671 -0
- package/dist/docs/products/tempo-performance.md +437 -0
- package/dist/docs/products/tempo-wallet.md +438 -0
- package/dist/web/components/OtpField.css +1 -1
- package/dist/web/components/OtpField.d.ts +1 -1
- package/dist/web/components/OtpField.d.ts.map +1 -1
- package/dist/web/components/OtpField.js +3 -4
- package/dist/web/components/OtpField.js.map +1 -1
- package/dist/web/components/OtpField.js.zyzz.json +1 -1
- package/dist/web/index.js.zyzz.json +1 -1
- package/package.json +1 -1
- package/src/web/components/OtpField.tsx +2 -24
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
# OtpField
|
|
2
|
+
|
|
3
|
+
Accepts a one-time passcode, one character per slot.
|
|
4
|
+
|
|
5
|
+
[View source and API](https://github.com/tempoxyz/ds/blob/main/src/web/components/OtpField.tsx)
|
|
6
|
+
|
|
7
|
+
Figma: [Inputs/Code Input](https://www.figma.com/design/yE96kcymnNR88PJp6G1sPs?node-id=5227-3204) (`OtpField.Root`)
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
OtpField renders one slot per character of a one-time passcode. Label it with `Field.Label` or a `<label>`, which also names the first slot.
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
### Basic
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { OtpField, style } from '@tempoxyz/ds'
|
|
19
|
+
import { useState } from 'react'
|
|
20
|
+
|
|
21
|
+
const code = '123456'
|
|
22
|
+
|
|
23
|
+
const messages = {
|
|
24
|
+
idle: `Enter ${code} to verify`,
|
|
25
|
+
invalid: 'Wrong code. Delete a digit and try again.',
|
|
26
|
+
success: 'Code accepted',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function Demo() {
|
|
30
|
+
const [value, setValue] = useState('')
|
|
31
|
+
const status = (() => {
|
|
32
|
+
if (value.length < code.length) return 'idle'
|
|
33
|
+
if (value === code) return 'success'
|
|
34
|
+
return 'invalid'
|
|
35
|
+
})()
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div {...styles.column()}>
|
|
39
|
+
<OtpField.Root
|
|
40
|
+
aria-label="Verification code"
|
|
41
|
+
invalid={status === 'invalid'}
|
|
42
|
+
length={6}
|
|
43
|
+
onValueChange={setValue}
|
|
44
|
+
success={status === 'success'}
|
|
45
|
+
value={value}
|
|
46
|
+
/>
|
|
47
|
+
<output>{messages[status]}</output>
|
|
48
|
+
</div>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
namespace styles {
|
|
53
|
+
export const column = style({
|
|
54
|
+
alignItems: 'flex-start',
|
|
55
|
+
display: 'flex',
|
|
56
|
+
flexDirection: 'column',
|
|
57
|
+
gap: '24',
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
[View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/OtpField/Basic.tsx)
|
|
63
|
+
|
|
64
|
+
### Invalid
|
|
65
|
+
|
|
66
|
+
Set `invalid` to fill the slots red and nudge them sideways.
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { OtpField, style } from '@tempoxyz/ds'
|
|
70
|
+
|
|
71
|
+
export function Demo() {
|
|
72
|
+
return (
|
|
73
|
+
<div {...styles.column()}>
|
|
74
|
+
<OtpField.Root aria-label="Invalid code" defaultValue="000000" invalid length={6} />
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
namespace styles {
|
|
80
|
+
export const column = style({
|
|
81
|
+
alignItems: 'flex-start',
|
|
82
|
+
display: 'flex',
|
|
83
|
+
flexDirection: 'column',
|
|
84
|
+
gap: '24',
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
[View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/OtpField/Invalid.tsx)
|
|
90
|
+
|
|
91
|
+
### Success
|
|
92
|
+
|
|
93
|
+
Set `success` to fill the slots green.
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { OtpField, style } from '@tempoxyz/ds'
|
|
97
|
+
|
|
98
|
+
export function Demo() {
|
|
99
|
+
return (
|
|
100
|
+
<div {...styles.column()}>
|
|
101
|
+
<OtpField.Root aria-label="Verified code" defaultValue="123456" length={6} success />
|
|
102
|
+
</div>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
namespace styles {
|
|
107
|
+
export const column = style({
|
|
108
|
+
alignItems: 'flex-start',
|
|
109
|
+
display: 'flex',
|
|
110
|
+
flexDirection: 'column',
|
|
111
|
+
gap: '24',
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
[View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/OtpField/Success.tsx)
|
|
117
|
+
|
|
118
|
+
### Disabled
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import { OtpField, style } from '@tempoxyz/ds'
|
|
122
|
+
|
|
123
|
+
export function Demo() {
|
|
124
|
+
return (
|
|
125
|
+
<div {...styles.column()}>
|
|
126
|
+
<OtpField.Root aria-label="Disabled code" defaultValue="123" disabled length={6} />
|
|
127
|
+
</div>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
namespace styles {
|
|
132
|
+
export const column = style({
|
|
133
|
+
alignItems: 'flex-start',
|
|
134
|
+
display: 'flex',
|
|
135
|
+
flexDirection: 'column',
|
|
136
|
+
gap: '24',
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
[View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/OtpField/Disabled.tsx)
|
|
142
|
+
|
|
143
|
+
## Anatomy
|
|
144
|
+
|
|
145
|
+
Import the component and assemble its parts.
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
import { OtpField } from '@tempoxyz/ds'
|
|
149
|
+
|
|
150
|
+
<OtpField.Root>
|
|
151
|
+
<OtpField.Input />
|
|
152
|
+
</OtpField.Root>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Guidelines
|
|
156
|
+
|
|
157
|
+
### When to Use
|
|
158
|
+
|
|
159
|
+
* Verify an email address or phone number with a code sent to it.
|
|
160
|
+
* Confirm a sensitive action with an authenticator code, such as a new API key or withdrawal address.
|
|
161
|
+
* Enter a fixed-length backup code. If it mixes letters and digits, set `validationType="alphanumeric"`.
|
|
162
|
+
|
|
163
|
+
### When Not to Use
|
|
164
|
+
|
|
165
|
+
* Use [TextInput](/docs/components/text-input) for codes without a fixed length, such as invite or promo codes.
|
|
166
|
+
* Use [TextInput](/docs/components/text-input) with `type="password"` for passwords.
|
|
167
|
+
* Use [SecretValue](/docs/components/secret-value) to show a key or recovery phrase.
|
|
168
|
+
|
|
169
|
+
### Support Autofill and Paste
|
|
170
|
+
|
|
171
|
+
A paste drops spaces and rejected characters, so 482 913 and 482-913 both work. Some people can't type the code at all.
|
|
172
|
+
|
|
173
|
+
* Set `length` to the exact length of the code.
|
|
174
|
+
* End SMS messages with an origin-bound line, such as `@wallet.tempo.xyz #482913`. Supporting browsers then offer the code only on your domain.
|
|
175
|
+
* Use `onValueInvalid` to report characters dropped from pasted text.
|
|
176
|
+
|
|
177
|
+
**Do:** Use one OtpField, so autofill and paste fill every slot.
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
/**
|
|
181
|
+
* Do example for "Support Autofill and Paste" on the OtpField page.
|
|
182
|
+
* Use one OtpField, so autofill and paste fill every slot.
|
|
183
|
+
* @module
|
|
184
|
+
*/
|
|
185
|
+
import { Badge, Field, OtpField, style } from '@tempoxyz/ds'
|
|
186
|
+
|
|
187
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
188
|
+
export function Demo() {
|
|
189
|
+
return (
|
|
190
|
+
<Field.Root {...styles.field()}>
|
|
191
|
+
<Field.Label>Verification code</Field.Label>
|
|
192
|
+
<OtpField.Root defaultValue="482913" length={6} />
|
|
193
|
+
<span {...styles.suggestion()}>
|
|
194
|
+
<Badge size="small">From Messages: 482913</Badge>
|
|
195
|
+
</span>
|
|
196
|
+
</Field.Root>
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
namespace styles {
|
|
201
|
+
export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
|
|
202
|
+
|
|
203
|
+
export const suggestion = style({ display: 'flex' })
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Don't:** Split the code across separate inputs or intercept paste.
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
/**
|
|
211
|
+
* Don't example for "Support Autofill and Paste" on the OtpField page.
|
|
212
|
+
* Don't split the code across separate inputs or intercept paste.
|
|
213
|
+
* @module
|
|
214
|
+
*/
|
|
215
|
+
import { Field, style, TextInput } from '@tempoxyz/ds'
|
|
216
|
+
|
|
217
|
+
const digits = ['4', '8', '2', '9', '1', '3']
|
|
218
|
+
|
|
219
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
220
|
+
export function Demo() {
|
|
221
|
+
return (
|
|
222
|
+
<Field.Root {...styles.field()}>
|
|
223
|
+
<Field.Label>Verification code</Field.Label>
|
|
224
|
+
<div {...styles.row()}>
|
|
225
|
+
{digits.map((digit, index) => (
|
|
226
|
+
<TextInput.Group key={index} {...styles.slot()}>
|
|
227
|
+
<TextInput.Control
|
|
228
|
+
aria-label={`Digit ${index + 1}`}
|
|
229
|
+
defaultValue={digit}
|
|
230
|
+
maxLength={1}
|
|
231
|
+
/>
|
|
232
|
+
</TextInput.Group>
|
|
233
|
+
))}
|
|
234
|
+
</div>
|
|
235
|
+
</Field.Root>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
namespace styles {
|
|
240
|
+
export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
|
|
241
|
+
|
|
242
|
+
export const row = style({ display: 'flex', gap: '8' })
|
|
243
|
+
|
|
244
|
+
export const slot = style({ minWidth: '0 !custom', width: '2.5rem !custom' })
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Say Where the Code Went
|
|
249
|
+
|
|
250
|
+
Name the destination and the expiry in `Field.Description`.
|
|
251
|
+
|
|
252
|
+
**Do:** Say where you sent the code and when it expires.
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
/**
|
|
256
|
+
* Do example for "Say Where the Code Went" on the OtpField page.
|
|
257
|
+
* Say where you sent the code and when it expires.
|
|
258
|
+
* @module
|
|
259
|
+
*/
|
|
260
|
+
import { Field, OtpField, style } from '@tempoxyz/ds'
|
|
261
|
+
|
|
262
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
263
|
+
export function Demo() {
|
|
264
|
+
return (
|
|
265
|
+
<Field.Root {...styles.field()}>
|
|
266
|
+
<Field.Label>Verification code</Field.Label>
|
|
267
|
+
<OtpField.Root length={6} />
|
|
268
|
+
<Field.Description>Sent to alice@example.com. It expires in 10 minutes.</Field.Description>
|
|
269
|
+
</Field.Root>
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
namespace styles {
|
|
274
|
+
export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Don't:** Ask for a code without saying where to find it.
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
/**
|
|
282
|
+
* Don't example for "Say Where the Code Went" on the OtpField page.
|
|
283
|
+
* Don't ask for a code without saying where to find it.
|
|
284
|
+
* @module
|
|
285
|
+
*/
|
|
286
|
+
import { Field, OtpField, style } from '@tempoxyz/ds'
|
|
287
|
+
|
|
288
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
289
|
+
export function Demo() {
|
|
290
|
+
return (
|
|
291
|
+
<Field.Root {...styles.field()}>
|
|
292
|
+
<Field.Label>Verification code</Field.Label>
|
|
293
|
+
<OtpField.Root length={6} />
|
|
294
|
+
<Field.Description>Enter the code.</Field.Description>
|
|
295
|
+
</Field.Root>
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
namespace styles {
|
|
300
|
+
export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Verify When the Code Is Complete
|
|
305
|
+
|
|
306
|
+
Start verification in `onValueComplete` when the last slot fills, or set `autoSubmit` to submit the owning form. A separate confirm step adds a click.
|
|
307
|
+
|
|
308
|
+
**Do:** Verify the code as soon as the last slot fills, and show progress below it.
|
|
309
|
+
|
|
310
|
+
```tsx
|
|
311
|
+
/**
|
|
312
|
+
* Do example for "Verify When the Code Is Complete" on the OtpField page.
|
|
313
|
+
* Verify the code as soon as the last slot fills, and show progress below it.
|
|
314
|
+
* @module
|
|
315
|
+
*/
|
|
316
|
+
import { Field, LoadingDots, OtpField, style } from '@tempoxyz/ds'
|
|
317
|
+
|
|
318
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
319
|
+
export function Demo() {
|
|
320
|
+
return (
|
|
321
|
+
<Field.Root {...styles.field()}>
|
|
322
|
+
<Field.Label>Verification code</Field.Label>
|
|
323
|
+
<OtpField.Root defaultValue="482913" length={6} readOnly />
|
|
324
|
+
<Field.Description>
|
|
325
|
+
Verifying <LoadingDots aria-label="Verifying code" />
|
|
326
|
+
</Field.Description>
|
|
327
|
+
</Field.Root>
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
namespace styles {
|
|
332
|
+
export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**Don't:** Add a Verify button after a complete code.
|
|
337
|
+
|
|
338
|
+
```tsx
|
|
339
|
+
/**
|
|
340
|
+
* Don't example for "Verify When the Code Is Complete" on the OtpField page.
|
|
341
|
+
* Don't add a Verify button after a complete code.
|
|
342
|
+
* @module
|
|
343
|
+
*/
|
|
344
|
+
import { Button, Field, OtpField, style } from '@tempoxyz/ds'
|
|
345
|
+
|
|
346
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
347
|
+
export function Demo() {
|
|
348
|
+
return (
|
|
349
|
+
<div {...styles.column()}>
|
|
350
|
+
<Field.Root>
|
|
351
|
+
<Field.Label>Verification code</Field.Label>
|
|
352
|
+
<OtpField.Root defaultValue="482913" length={6} />
|
|
353
|
+
</Field.Root>
|
|
354
|
+
<Button>Verify code</Button>
|
|
355
|
+
</div>
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
namespace styles {
|
|
360
|
+
export const column = style({
|
|
361
|
+
alignItems: 'flex-start',
|
|
362
|
+
display: 'flex',
|
|
363
|
+
flexDirection: 'column',
|
|
364
|
+
gap: '16',
|
|
365
|
+
})
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Set `readOnly` while the request runs, with a [Spinner](/docs/components/spinner) or [LoadingDots](/docs/components/loading-dots) nearby. Then set `success` or `invalid`.
|
|
370
|
+
|
|
371
|
+
### Explain a Rejected Code in Text
|
|
372
|
+
|
|
373
|
+
`invalid` only colors and nudges the slots. Add a `Field.Error` with the next step, and keep the entered code so people can fix a typo.
|
|
374
|
+
|
|
375
|
+
**Do:** Pair `invalid` with a message that says what to do.
|
|
376
|
+
|
|
377
|
+
```tsx
|
|
378
|
+
/**
|
|
379
|
+
* Do example for "Explain a Rejected Code in Text" on the OtpField page.
|
|
380
|
+
* Pair `invalid` with a message that says what to do.
|
|
381
|
+
* @module
|
|
382
|
+
*/
|
|
383
|
+
import { Field, OtpField, style } from '@tempoxyz/ds'
|
|
384
|
+
|
|
385
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
386
|
+
export function Demo() {
|
|
387
|
+
return (
|
|
388
|
+
<Field.Root invalid {...styles.field()}>
|
|
389
|
+
<Field.Label>Verification code</Field.Label>
|
|
390
|
+
<OtpField.Root defaultValue="482913" invalid length={6} />
|
|
391
|
+
<Field.Error match>That code didn't match. Check your latest email.</Field.Error>
|
|
392
|
+
</Field.Root>
|
|
393
|
+
)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
namespace styles {
|
|
397
|
+
export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
**Don't:** Rely on the red slots alone.
|
|
402
|
+
|
|
403
|
+
```tsx
|
|
404
|
+
/**
|
|
405
|
+
* Don't example for "Explain a Rejected Code in Text" on the OtpField page.
|
|
406
|
+
* Don't rely on the red slots alone.
|
|
407
|
+
* @module
|
|
408
|
+
*/
|
|
409
|
+
import { Field, OtpField, style } from '@tempoxyz/ds'
|
|
410
|
+
|
|
411
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
412
|
+
export function Demo() {
|
|
413
|
+
return (
|
|
414
|
+
<Field.Root invalid {...styles.field()}>
|
|
415
|
+
<Field.Label>Verification code</Field.Label>
|
|
416
|
+
<OtpField.Root defaultValue="482913" invalid length={6} />
|
|
417
|
+
</Field.Root>
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
namespace styles {
|
|
422
|
+
export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Offer a New Code
|
|
427
|
+
|
|
428
|
+
Place a `tertiary` [Button](/docs/components/button) such as Send a new code below the field. After a resend, disable it for a short cooldown and state when it returns.
|
|
429
|
+
|
|
430
|
+
**Do:** State when a disabled resend returns.
|
|
431
|
+
|
|
432
|
+
```tsx
|
|
433
|
+
/** Do example for "Offer a New Code" on the OtpField page: state when a disabled resend returns. @module */
|
|
434
|
+
import { Button, Field, OtpField, style } from '@tempoxyz/ds'
|
|
435
|
+
|
|
436
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
437
|
+
export function Demo() {
|
|
438
|
+
return (
|
|
439
|
+
<div {...styles.stack()}>
|
|
440
|
+
<Field.Root>
|
|
441
|
+
<Field.Label>Verification code</Field.Label>
|
|
442
|
+
<OtpField.Root length={6} />
|
|
443
|
+
<Field.Description>Sent to alice@example.com.</Field.Description>
|
|
444
|
+
</Field.Root>
|
|
445
|
+
<span {...styles.action()}>
|
|
446
|
+
<Button disabled size="small" variant="tertiary">
|
|
447
|
+
Send a new code in 30s
|
|
448
|
+
</Button>
|
|
449
|
+
</span>
|
|
450
|
+
</div>
|
|
451
|
+
)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
namespace styles {
|
|
455
|
+
export const action = style({ display: 'flex' })
|
|
456
|
+
|
|
457
|
+
export const stack = style({
|
|
458
|
+
display: 'grid',
|
|
459
|
+
gap: '8',
|
|
460
|
+
maxWidth: '18rem !custom',
|
|
461
|
+
width: '100% !custom',
|
|
462
|
+
})
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**Don't:** Disable the resend button without saying why.
|
|
467
|
+
|
|
468
|
+
```tsx
|
|
469
|
+
/**
|
|
470
|
+
* Don't example for "Offer a New Code" on the OtpField page.
|
|
471
|
+
* Don't disable the resend button without saying why.
|
|
472
|
+
* @module
|
|
473
|
+
*/
|
|
474
|
+
import { Button, Field, OtpField, style } from '@tempoxyz/ds'
|
|
475
|
+
|
|
476
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
477
|
+
export function Demo() {
|
|
478
|
+
return (
|
|
479
|
+
<div {...styles.stack()}>
|
|
480
|
+
<Field.Root>
|
|
481
|
+
<Field.Label>Verification code</Field.Label>
|
|
482
|
+
<OtpField.Root length={6} />
|
|
483
|
+
<Field.Description>Sent to alice@example.com.</Field.Description>
|
|
484
|
+
</Field.Root>
|
|
485
|
+
<span {...styles.action()}>
|
|
486
|
+
<Button disabled size="small" variant="tertiary">
|
|
487
|
+
Send a new code
|
|
488
|
+
</Button>
|
|
489
|
+
</span>
|
|
490
|
+
</div>
|
|
491
|
+
)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
namespace styles {
|
|
495
|
+
export const action = style({ display: 'flex' })
|
|
496
|
+
|
|
497
|
+
export const stack = style({
|
|
498
|
+
display: 'grid',
|
|
499
|
+
gap: '8',
|
|
500
|
+
maxWidth: '18rem !custom',
|
|
501
|
+
width: '100% !custom',
|
|
502
|
+
})
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Leave Codes Visible
|
|
507
|
+
|
|
508
|
+
Short-lived codes are safe to show. Set `mask` only for codes that stay valid, such as a PIN, or on shared screens.
|
|
509
|
+
|
|
510
|
+
**Do:** Show a short-lived code, so people can check it against the message.
|
|
511
|
+
|
|
512
|
+
```tsx
|
|
513
|
+
/**
|
|
514
|
+
* Do example for "Leave Codes Visible" on the OtpField page.
|
|
515
|
+
* Show a short-lived code, so people can check it against the message.
|
|
516
|
+
* @module
|
|
517
|
+
*/
|
|
518
|
+
import { Field, OtpField, style } from '@tempoxyz/ds'
|
|
519
|
+
|
|
520
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
521
|
+
export function Demo() {
|
|
522
|
+
return (
|
|
523
|
+
<Field.Root {...styles.field()}>
|
|
524
|
+
<Field.Label>Verification code</Field.Label>
|
|
525
|
+
<OtpField.Root defaultValue="482913" length={6} />
|
|
526
|
+
<Field.Description>It expires in 10 minutes.</Field.Description>
|
|
527
|
+
</Field.Root>
|
|
528
|
+
)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
namespace styles {
|
|
532
|
+
export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
**Don't:** Mask a code that expires in minutes.
|
|
537
|
+
|
|
538
|
+
```tsx
|
|
539
|
+
/**
|
|
540
|
+
* Don't example for "Leave Codes Visible" on the OtpField page.
|
|
541
|
+
* Don't mask a code that expires in minutes.
|
|
542
|
+
* @module
|
|
543
|
+
*/
|
|
544
|
+
import { Field, OtpField, style } from '@tempoxyz/ds'
|
|
545
|
+
|
|
546
|
+
/** Renders the static preview, which the guideline card shows inert. */
|
|
547
|
+
export function Demo() {
|
|
548
|
+
return (
|
|
549
|
+
<Field.Root {...styles.field()}>
|
|
550
|
+
<Field.Label>Verification code</Field.Label>
|
|
551
|
+
<OtpField.Root defaultValue="482913" length={6} mask />
|
|
552
|
+
<Field.Description>It expires in 10 minutes.</Field.Description>
|
|
553
|
+
</Field.Root>
|
|
554
|
+
)
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
namespace styles {
|
|
558
|
+
export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
## Content
|
|
563
|
+
|
|
564
|
+
* **Label:** Verification code, or the source, such as Authenticator code.
|
|
565
|
+
* **Term:** Write code, not OTP or one-time password.
|
|
566
|
+
|
|
567
|
+
## Accessibility
|
|
568
|
+
|
|
569
|
+
OtpField renders a group of single-character text inputs.
|
|
570
|
+
|
|
571
|
+
### Semantics
|
|
572
|
+
|
|
573
|
+
* **OtpField.Root (group):** Groups the slots into one control. Its `aria-label` names the group.
|
|
574
|
+
* **Slots (inputmode=numeric):** Each slot is a native text input with a numeric keyboard by default.
|
|
575
|
+
* **Autofill (one-time-code):** Set on the first slot, so browsers can offer a code from SMS or email.
|
|
576
|
+
* **Invalid and Success (aria-invalid):** `invalid` marks every slot invalid. `success` sets no ARIA state, so announce the result in text.
|
|
577
|
+
* **Disabled (disabled):** Disables every slot, so none of the slots takes focus or accepts input.
|
|
578
|
+
|
|
579
|
+
### Keyboard
|
|
580
|
+
|
|
581
|
+
* **Enter the Field (Tab):** Moves focus to the active slot. Only one slot is in the tab order.
|
|
582
|
+
* **Type a Character:** Fills the slot and moves to the next one. Ignores characters that `validationType` rejects.
|
|
583
|
+
* **Delete Backward (Backspace):** Removes the previous character and moves back one slot.
|
|
584
|
+
* **Delete Forward (Delete):** Removes the character in the focused slot and shifts the rest left.
|
|
585
|
+
* **Move Between Slots (Left or Right Arrow):** Moves focus one slot at a time between filled slots and the first empty one.
|
|
586
|
+
* **Go to the First Slot (Home):** Moves focus to the first slot, whether or not the slots are filled.
|
|
587
|
+
* **Go to the End (End):** Moves to the first empty slot, or to the last slot when every slot is filled.
|
|
588
|
+
* **Paste a Code (Paste):** Fills the slots from the focused one and drops spaces and rejected characters.
|
|
589
|
+
|
|
590
|
+
### Size and Motion
|
|
591
|
+
|
|
592
|
+
* **Slot Size (56px):** Slots are 56px tall and fill the field. The focused slot is 3px wider than the others, and a message in `Field.Root` sits 8px below them. Slots shrink to fit narrow screens instead of wrapping.
|
|
593
|
+
* **Reduced Motion (reduce):** If the reader prefers reduced motion, the nudge stops.
|
|
594
|
+
|
|
595
|
+
### Your Responsibilities
|
|
596
|
+
|
|
597
|
+
* **Label the Slots (Field.Label):** Use a field label, which names the first slot. A root `aria-label` names only the group.
|
|
598
|
+
* **Announce the Result (output):** State the result in text beside the field, such as "Code accepted" or "Wrong code".
|
|
599
|
+
|
|
600
|
+
## Related
|
|
601
|
+
|
|
602
|
+
[TextInput](/docs/components/text-input)
|
|
603
|
+
|
|
604
|
+
Use for variable-length codes.
|
|
605
|
+
|
|
606
|
+
[Field](/docs/components/field)
|
|
607
|
+
|
|
608
|
+
Use for labels, descriptions, and errors.
|
|
609
|
+
|
|
610
|
+
[SecretValue](/docs/components/secret-value)
|
|
611
|
+
|
|
612
|
+
Use for showing keys and secrets.
|
|
613
|
+
|
|
614
|
+
[Errors and Validation](/docs/patterns/errors-and-validation)
|
|
615
|
+
|
|
616
|
+
Covers explaining rejected values.
|