File size: 3,421 Bytes
50eef7d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import type { DomainPreferenceForm, PreferenceField } from '../utils/api'

type Props = {
  form: DomainPreferenceForm
  values: Record<string, any>
  onChange: (values: Record<string, any>) => void
}

function asArray(value: unknown): string[] {
  if (Array.isArray(value)) return value.map(String)
  if (typeof value === 'string' && value.trim()) {
    return value.split(',').map(part => part.trim()).filter(Boolean)
  }
  return []
}

/**
 * Generic renderer for a server-generated preference form. Adding a domain's
 * form is a backend data change; this component renders whatever fields arrive.
 */
export default function PreferenceForm({ form, values, onChange }: Props) {
  const setValue = (key: string, value: unknown) => onChange({ ...values, [key]: value })

  return (
    <div style={{ display: 'grid', gap: 10 }}>
      {form.fields.map(field => (
        <div key={field.key} style={{ display: 'grid', gap: 4 }}>
          <label style={{ fontSize: 13, color: 'var(--muted, #666)' }}>
            {field.label}
            {field.required && <span style={{ color: 'var(--danger, #c0392b)' }}> *</span>}
          </label>
          {renderField(field, values[field.key], setValue)}
        </div>
      ))}
    </div>
  )
}

function renderField(
  field: PreferenceField,
  value: unknown,
  setValue: (key: string, value: unknown) => void,
) {
  if (field.type === 'multiselect') {
    const selected = asArray(value)
    return (
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
        {field.options.map(option => {
          const active = selected.includes(option)
          return (
            <button
              type="button"
              key={option}
              aria-pressed={active}
              onClick={() =>
                setValue(
                  field.key,
                  active ? selected.filter(item => item !== option) : [...selected, option],
                )
              }
              style={chipStyle(active)}
            >
              {option}
            </button>
          )
        })}
      </div>
    )
  }

  if (field.type === 'select') {
    return (
      <select
        aria-label={field.label}
        value={typeof value === 'string' ? value : ''}
        onChange={e => setValue(field.key, e.target.value)}
        style={inputStyle}
      >
        <option value=""></option>
        {field.options.map(option => (
          <option key={option} value={option}>
            {option}
          </option>
        ))}
      </select>
    )
  }

  // text (default)
  return (
    <input
      aria-label={field.label}
      value={typeof value === 'string' ? value : Array.isArray(value) ? value.join(', ') : ''}
      placeholder={field.placeholder}
      onChange={e => setValue(field.key, e.target.value)}
      style={inputStyle}
    />
  )
}

function chipStyle(active: boolean): React.CSSProperties {
  return {
    padding: '4px 10px',
    borderRadius: 999,
    border: `1px solid ${active ? 'var(--primary, #e8742c)' : 'var(--border, #ddd)'}`,
    background: active ? 'var(--primary, #e8742c)' : 'transparent',
    color: active ? '#fff' : 'inherit',
    cursor: 'pointer',
    fontSize: 13,
  }
}

const inputStyle: React.CSSProperties = {
  width: '100%',
  padding: '8px 10px',
  borderRadius: 8,
  border: '1px solid var(--border, #ddd)',
  background: 'var(--bg, #fff)',
  color: 'inherit',
  fontSize: 14,
}