main
ts 104 lines 4.4 KB
Raw
1 // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 import { createElement as h, useEffect, useState } from 'react'
4 import { StringField, StringFieldProps } from './StringField'
5 import { FieldProps } from '.'
6 import {
7 Box, Checkbox, FormControl, FormControlLabel, FormGroup, FormHelperText, FormLabel, IconButton,
8 InputAdornment, Switch
9 } from '@mui/material'
10 import { Cancel } from '@mui/icons-material'
11 import _ from 'lodash'
12 import { useGetSize } from '@hfs/shared'
13
14 export function DisplayField({ value, empty='-', ...props }: Omit<StringFieldProps, 'onChange'> & { value: any }) {
15 if (!props.toField && empty !== undefined && value !== 0 && !value)
16 value = empty
17 return h(StringField, { ...props, value, disabled: true, onChange() {} })
18 }
19
20 export function NumberField({ value, onChange, setApi, required, min=0, max, step, unit, clearable, ...props }: FieldProps<number | null>) {
21 setApi?.({
22 getError() {
23 return value == null ? (required ? "required" : false)
24 : (value < min ? "too low" : value > max ? "too high" : false)
25 }
26 })
27 const size = useGetSize()
28 return h(StringField, {
29 type: 'number',
30 value: value == null ? '' : String(value),
31 onChange(v, { was, ...rest }) {
32 onChange(!v ? null : Number(v), {
33 ...rest,
34 was: was ? Number(was) : null,
35 })
36 },
37 inputProps: { min, max, step, },
38 InputProps: _.merge({
39 sx: { '& input': { appearance: 'textfield' } },
40 startAdornment: (clearable ?? props.placeholder) && size.w! > 100 && (value || value === 0) && h(InputAdornment, {
41 position: 'start',
42 }, h(IconButton, {
43 size: 'small',
44 edge: 'start',
45 sx: { ml: -1, opacity: .5 },
46 'aria-label': "clear",
47 onClick(event){ onChange(null, { was: value, event }) }
48 }, h(Cancel))),
49 }, unit && {
50 sx: { pr: '6px', '& input': { pl: '.2em', textAlign: 'right' } },
51 endAdornment: h(InputAdornment, {
52 position: 'end',
53 sx: { mt: '1.2em', ml: '5px', '& p': { fontSize: '80%', '.Mui-focused &': { color: 'text.primary' } } }
54 }, unit),
55 }),
56 ...props,
57 fieldRef: size.refToPass,
58 })
59 }
60
61 export function BoolField({ label='', value, onChange, setApi, helperText, error, Control=Switch,
62 type, // avoid passing this by accident, as it disrupts the control
63 ...props }: FieldProps<boolean>) {
64 const setter = () => value ?? false
65 const [state, setState] = useState(setter)
66 useEffect(() => setState(setter),
67 [value]) //eslint-disable-line
68 const control = h(Control, {
69 checked: state,
70 ...props,
71 onChange(event) {
72 onChange((event.target as any).checked, { event, was: value })
73 }
74 })
75 return h(Box, { sx: { ml: 1, ...error && { color: 'error.main', outlineOffset: 6, outline: '1px solid' } } },
76 h(FormControlLabel, { label, control, labelPlacement: 'end', sx: { mr: 0, ...props.size==='small' && { '& .MuiFormControlLabel-label': { fontSize: '.9rem' } } } }),
77 helperText && h(FormHelperText, { sx: { mt: 0 }, error }, helperText)
78 )
79 }
80
81 export function CheckboxField(props: FieldProps<boolean>) {
82 return h(BoolField, { Control: Checkbox, ...props })
83 }
84
85 export function CheckboxesField({ label, options, value, onChange, columns, columnWidth }: FieldProps<string[]> & { options: string[] }) {
86 const doCols = columns > 1 || Boolean(columnWidth)
87 return h(FormControl, { fullWidth: doCols },
88 label && h(FormLabel, {}, label),
89 h(FormGroup, { sx: { ...doCols && { columns, columnWidth, '&, & label': { display: 'block' } } } },
90 options.map(o => {
91 const checked = value?.includes(o)
92 return h(FormControlLabel, {
93 key: o,
94 checked,
95 control: h(Checkbox),
96 label: o,
97 onClick(event) {
98 const newValue = checked ? value!.filter(x => x !== o) : [...value||[], o]
99 onChange(newValue, { was: value, event })
100 }
101 })
102 }))
103 )
104 }