aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/components/form-examples/form-examples.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/form-examples/form-examples.tsx')
-rw-r--r--src/components/form-examples/form-examples.tsx91
1 files changed, 91 insertions, 0 deletions
diff --git a/src/components/form-examples/form-examples.tsx b/src/components/form-examples/form-examples.tsx
new file mode 100644
index 0000000..e1dd6c8
--- /dev/null
+++ b/src/components/form-examples/form-examples.tsx
@@ -0,0 +1,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)} />
+}