aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/components/form-examples/color-file-fields.tsx
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>
    </>
  )
}