1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
import { useLayoutEffect, useRef, useState } from 'react'
import { Form } from '@react-spectrum/s2/Form'
import { Button, ButtonGroup } from '@react-spectrum/s2/ButtonGroup'
import { Picker, PickerItem } from '@react-spectrum/s2/Picker'
import { Switch } from '@react-spectrum/s2/Switch'
import { Checkbox } from '@react-spectrum/s2/Checkbox'
import TextFields from './text-fields'
import SelectionFields from './selection-fields'
import DateNumberFields from './date-number-fields'
import ColorFileFields from './color-file-fields'
import styles from './form-examples.module.css'
function DemoForm({ onReset }: { onReset: () => void }) {
const containerRef = useRef<HTMLDivElement>(null)
const [isCompact, setIsCompact] = useState(false)
const [size, setSize] = useState<'S' | 'M' | 'L' | 'XL'>('M')
const [sideLabels, setSideLabels] = useState(false)
const [disabled, setDisabled] = useState(false)
const [errors, setErrors] = useState<Record<string, string>>({})
const [result, setResult] = useState<Record<string, string | string[]> | null>(null)
useLayoutEffect(() => {
const container = containerRef.current
if (!container) return
const update = () => setIsCompact(container.clientWidth < 560)
update()
const observer = new ResizeObserver(update)
observer.observe(container)
return () => observer.disconnect()
}, [])
const useSideLabels = sideLabels && !isCompact
return (
<div ref={containerRef}>
<div className={styles.controls}>
<Picker label="控件尺寸" selectedKey={size} onSelectionChange={(key) => { if (key === 'S' || key === 'M' || key === 'L' || key === 'XL') setSize(key) }}>
<PickerItem id="S">S</PickerItem><PickerItem id="M">M</PickerItem><PickerItem id="L">L</PickerItem><PickerItem id="XL">XL</PickerItem>
</Picker>
<Switch isSelected={sideLabels} onChange={setSideLabels}>标签放在侧边</Switch>
<Switch isSelected={disabled} onChange={setDisabled}>禁用表单</Switch>
</div>
{sideLabels && isCompact && <p className={styles.layoutHint}>当前宽度较窄,已自动使用顶部标签。</p>}
<Form UNSAFE_className={`${styles.form}${useSideLabels ? ` ${styles.sideLabels}` : ''}`} size={size} isDisabled={disabled} labelPosition={useSideLabels ? 'side' : 'top'} necessityIndicator="label"
validationBehavior="native" validationErrors={errors}
onReset={(event) => { event.preventDefault(); onReset() }}
onSubmit={(event) => {
event.preventDefault()
const data = new FormData(event.currentTarget)
if (data.get('username') === 'admin') {
setErrors({ username: '该用户名已被占用,请换一个。(本地模拟业务校验)' })
setResult(null)
return
}
setErrors({})
const entries = [...new Set(data.keys())].map((key) => {
const values = data.getAll(key).map((value) => typeof value === 'string' ? value : value.name)
return [key, key === 'password' && values[0] ? '••••••••' : values.length === 1 ? values[0] : values]
})
setResult(Object.fromEntries(entries))
}}>
<fieldset className={styles.fields} disabled={disabled} inert={disabled}>
<legend className={styles.srOnly}>Spectrum 表单示例</legend>
<TextFields />
<SelectionFields disabled={disabled} />
<DateNumberFields disabled={disabled} />
<ColorFileFields disabled={disabled} />
<section className={styles.section} aria-labelledby="submit-title">
<h2 id="submit-title">校验与提交</h2>
<p>用户名和邮箱为必填项。可以直接提交,体验错误提示和焦点定位。</p>
<Checkbox name="agreement" value="accepted" isRequired>我已了解这是本地表单示例</Checkbox>
<ButtonGroup>
<Button type="submit" variant="primary">提交并预览</Button>
<Button type="reset" variant="secondary">重置全部</Button>
</ButtonGroup>
</section>
</fieldset>
</Form>
<section className={styles.result} aria-labelledby="result-title">
<h2 id="result-title">提交结果</h2>
<p role="status">{result ? '校验通过,以下为本次提交的数据。' : '填写表单并提交后,在这里查看结果。'}</p>
{result && <pre tabIndex={0}>{JSON.stringify(result, null, 2)}</pre>}
</section>
</div>
)
}
export default function FormExamples() {
const [version, setVersion] = useState(0)
return <DemoForm key={version} onReset={() => setVersion((value) => value + 1)} />
}
|