blob: e9d0e749d7693963bf6de68e75a9a5860bf4457e (
plain)
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
|
import FieldGrid from './field-grid'
import { useState } from 'react'
import { ColorArea, parseColor } from '@react-spectrum/s2/ColorArea'
import { ColorField } from '@react-spectrum/s2/ColorField'
import { ColorSlider } from '@react-spectrum/s2/ColorSlider'
import { ColorWheel } from '@react-spectrum/s2/ColorWheel'
import { ColorSwatch, ColorSwatchPicker } from '@react-spectrum/s2/ColorSwatchPicker'
import { DropZone } from '@react-spectrum/s2/DropZone'
import { FileTrigger } from '@react-spectrum/s2/FileTrigger'
import { Button } from '@react-spectrum/s2/Button'
import styles from './form-examples.module.css'
export default function ColorFileFields({ disabled }: { disabled: boolean }) {
const [color, setColor] = useState(() => parseColor('hsl(265, 75%, 60%)'))
const [files, setFiles] = useState<File[]>([])
const [fileError, setFileError] = useState('')
return (
<>
<section className={styles.section} aria-labelledby="color-fields-title">
<h2 id="color-fields-title">颜色选择</h2>
<p>输入框、色板、色轮和滑块共享同一个颜色值。</p>
<FieldGrid>
<ColorField label="颜色值" value={color} onChange={(next) => { if (next) setColor(next) }} />
<ColorSlider isDisabled={disabled} label="色相" channel="hue" colorSpace="hsl" value={color} onChange={setColor} />
<ColorSlider isDisabled={disabled} label="透明度" channel="alpha" value={color} onChange={setColor} />
<div className={styles.stack}>
<span>预设色板</span>
<ColorSwatchPicker value={color} onChange={setColor}>
<ColorSwatch color="#8b5cf6" /><ColorSwatch color="#3b82f6" /><ColorSwatch color="#22c55e" /><ColorSwatch color="#f97316" />
</ColorSwatchPicker>
</div>
</FieldGrid>
<div className={styles.calendars}>
<div className={styles.stack}><span id="color-area-label">二维颜色面板</span><ColorArea isDisabled={disabled} aria-labelledby="color-area-label" colorSpace="hsl" xChannel="saturation" yChannel="lightness" value={color} onChange={setColor} /></div>
<div className={styles.stack}><span id="color-wheel-label">色轮</span><ColorWheel isDisabled={disabled} aria-labelledby="color-wheel-label" value={color} onChange={setColor} /></div>
</div>
<input type="hidden" name="color" value={color.toString('hexa')} />
<output className={styles.output}>{color.toString('hexa')}</output>
</section>
<section className={styles.section} aria-labelledby="file-fields-title">
<h2 id="file-fields-title">文件选择与拖放</h2>
<DropZone aria-label="附件拖放区域" getDropOperation={(types) => !disabled && types.has('Files') ? 'copy' : 'cancel'}
onDrop={async (event) => {
try {
const next = await Promise.all(event.items.filter((item) => item.kind === 'file').map((item) => item.getFile()))
setFiles(next)
setFileError('')
} catch {
setFileError('无法读取文件,请重新选择。')
}
}}>
<div className={styles.dropContent}>
<p>将文件拖放到这里,或从设备中选择。</p>
<FileTrigger allowsMultiple onSelect={(selected) => { if (selected) { setFiles(Array.from(selected)); setFileError('') } }}>
<Button type="button" variant="secondary">选择文件</Button>
</FileTrigger>
<p>仅在本页预览文件信息,不上传。</p>
</div>
</DropZone>
{fileError && <p role="alert">{fileError}</p>}
<div aria-live="polite">
{files.length ? <ul>{files.map((file, index) => <li key={`${file.name}-${index}`}>{file.name} · {Math.ceil(file.size / 1024)} KB</li>)}</ul> : <p>尚未选择文件。</p>}
</div>
{files.length > 0 && <Button type="button" variant="secondary" onPress={() => setFiles([])}>清空文件</Button>}
<input type="hidden" name="files" value={JSON.stringify(files.map(({ name, size, type }) => ({ name, size, type })))} />
</section>
</>
)
}
|