|
1 | | -import React, { FieldsetHTMLAttributes } from 'react'; |
| 1 | +import React, { |
| 2 | + createContext, |
| 3 | + FC, |
| 4 | + FieldsetHTMLAttributes, |
| 5 | + useCallback, |
| 6 | + useContext, |
| 7 | + useMemo, |
| 8 | + useRef, |
| 9 | +} from 'react'; |
2 | 10 | import classnames from 'classnames'; |
3 | 11 | import { FormElementProps } from './FormElement'; |
| 12 | +import { FieldSetColumnContext } from './FieldSet'; |
4 | 13 |
|
5 | | -export type CheckboxGroupProps<ValueType extends string | number> = { |
| 14 | +/** |
| 15 | + * |
| 16 | + */ |
| 17 | +export type CheckboxValueType = string | number; |
| 18 | + |
| 19 | +/** |
| 20 | + * |
| 21 | + */ |
| 22 | +export const CheckboxGroupContext = createContext<{ grouped?: boolean }>({}); |
| 23 | + |
| 24 | +/** |
| 25 | + * |
| 26 | + */ |
| 27 | +export type CheckboxGroupProps = { |
6 | 28 | label?: string; |
7 | 29 | required?: boolean; |
8 | 30 | error?: FormElementProps['error']; |
9 | 31 | name?: string; |
10 | | - totalCols?: number; |
11 | 32 | cols?: number; |
12 | | - onValueChange?: (values: ValueType[]) => void; |
| 33 | + onValueChange?: (values: CheckboxValueType[]) => void; |
13 | 34 | } & FieldsetHTMLAttributes<HTMLFieldSetElement>; |
14 | 35 |
|
15 | | -export class CheckboxGroup< |
16 | | - ValueType extends string | number |
17 | | -> extends React.Component<CheckboxGroupProps<ValueType>> { |
18 | | - static isFormElement = true; |
19 | | - |
20 | | - private nodes: { [key: string]: any } = {}; |
21 | | - |
22 | | - constructor(props: Readonly<CheckboxGroupProps<ValueType>>) { |
23 | | - super(props); |
24 | | - |
25 | | - this.onChange = this.onChange.bind(this); |
26 | | - this.renderControl = this.renderControl.bind(this); |
27 | | - } |
| 36 | +/** |
| 37 | + * |
| 38 | + */ |
| 39 | +export const CheckboxGroup: FC<CheckboxGroupProps> = (props) => { |
| 40 | + const { |
| 41 | + className, |
| 42 | + label, |
| 43 | + cols, |
| 44 | + style, |
| 45 | + required, |
| 46 | + error, |
| 47 | + onValueChange, |
| 48 | + onChange: onChange_, |
| 49 | + children, |
| 50 | + ...rprops |
| 51 | + } = props; |
| 52 | + const { totalCols } = useContext(FieldSetColumnContext); |
| 53 | + const controlElRef = useRef<HTMLDivElement | null>(null); |
28 | 54 |
|
29 | | - onChange(e: React.FormEvent<HTMLFieldSetElement>) { |
30 | | - if (this.props.onValueChange) { |
31 | | - const values: ValueType[] = []; |
32 | | - React.Children.forEach(this.props.children, (check: any, i) => { |
33 | | - const el = check.props.ref || this.nodes[`check${i + 1}`]; |
34 | | - const checkEl = el && el.querySelector('input[type=checkbox]'); |
35 | | - if (checkEl && checkEl.checked) { |
36 | | - values.push(check.props.value); |
| 55 | + const onChange = useCallback( |
| 56 | + (e: React.FormEvent<HTMLFieldSetElement>) => { |
| 57 | + if (onValueChange) { |
| 58 | + const checkboxes = |
| 59 | + controlElRef.current?.querySelectorAll<HTMLInputElement>( |
| 60 | + 'input[type=checkbox]' |
| 61 | + ); |
| 62 | + if (!checkboxes) { |
| 63 | + return; |
37 | 64 | } |
38 | | - }); |
39 | | - this.props.onValueChange(values); |
40 | | - } |
41 | | - if (this.props.onChange) { |
42 | | - this.props.onChange(e); |
43 | | - } |
44 | | - } |
| 65 | + const values = [...checkboxes] |
| 66 | + .filter((checkbox) => checkbox.checked) |
| 67 | + .map((checkbox) => checkbox.value); |
| 68 | + onValueChange?.(values); |
| 69 | + } |
| 70 | + onChange_?.(e); |
| 71 | + }, |
| 72 | + [onChange_, onValueChange] |
| 73 | + ); |
45 | 74 |
|
46 | | - renderControl(checkbox: any, i: number) { |
47 | | - const props: any = { grouped: true }; |
48 | | - if (checkbox.props.ref) { |
49 | | - props.ref = checkbox.props.ref; |
50 | | - } else { |
51 | | - props.checkboxRef = (node: any) => (this.nodes[`check${i + 1}`] = node); |
52 | | - } |
53 | | - if (this.props.name) { |
54 | | - props.name = this.props.name; |
55 | | - } |
56 | | - return React.cloneElement(checkbox, props); |
57 | | - } |
| 75 | + const grpClassNames = classnames( |
| 76 | + className, |
| 77 | + 'slds-form-element', |
| 78 | + { |
| 79 | + 'slds-has-error': error, |
| 80 | + 'slds-is-required': required, |
| 81 | + }, |
| 82 | + typeof totalCols === 'number' |
| 83 | + ? `slds-size_${cols || 1}-of-${totalCols}` |
| 84 | + : null |
| 85 | + ); |
| 86 | + const grpStyles = |
| 87 | + typeof totalCols === 'number' |
| 88 | + ? { display: 'inline-block', ...style } |
| 89 | + : style; |
| 90 | + const errorMessage = error |
| 91 | + ? typeof error === 'string' |
| 92 | + ? error |
| 93 | + : typeof error === 'object' |
| 94 | + ? error.message |
| 95 | + : undefined |
| 96 | + : undefined; |
| 97 | + const grpCtx = useMemo(() => ({ grouped: true }), []); |
58 | 98 |
|
59 | | - render() { |
60 | | - const { |
61 | | - className, |
62 | | - label, |
63 | | - totalCols, |
64 | | - cols, |
65 | | - style, |
66 | | - required, |
67 | | - error, |
68 | | - children, |
69 | | - ...props |
70 | | - } = this.props; |
71 | | - const grpClassNames = classnames( |
72 | | - className, |
73 | | - 'slds-form-element', |
74 | | - { |
75 | | - 'slds-has-error': error, |
76 | | - 'slds-is-required': required, |
77 | | - }, |
78 | | - typeof totalCols === 'number' |
79 | | - ? `slds-size_${cols || 1}-of-${totalCols}` |
80 | | - : null |
81 | | - ); |
82 | | - const grpStyles = |
83 | | - typeof totalCols === 'number' |
84 | | - ? { display: 'inline-block', ...style } |
85 | | - : style; |
86 | | - const errorMessage = error |
87 | | - ? typeof error === 'string' |
88 | | - ? error |
89 | | - : typeof error === 'object' |
90 | | - ? error.message |
91 | | - : undefined |
92 | | - : undefined; |
| 99 | + return ( |
| 100 | + <fieldset |
| 101 | + className={grpClassNames} |
| 102 | + style={grpStyles} |
| 103 | + {...rprops} |
| 104 | + onChange={onChange} |
| 105 | + > |
| 106 | + <legend className='slds-form-element__label'> |
| 107 | + {label} |
| 108 | + {required ? <abbr className='slds-required'>*</abbr> : undefined} |
| 109 | + </legend> |
| 110 | + <div className='slds-form-element__control' ref={controlElRef}> |
| 111 | + <CheckboxGroupContext.Provider value={grpCtx}> |
| 112 | + {children} |
| 113 | + </CheckboxGroupContext.Provider> |
| 114 | + {errorMessage ? ( |
| 115 | + <div className='slds-form-element__help'>{errorMessage}</div> |
| 116 | + ) : undefined} |
| 117 | + </div> |
| 118 | + </fieldset> |
| 119 | + ); |
| 120 | +}; |
93 | 121 |
|
94 | | - delete props.onChange; |
95 | | - return ( |
96 | | - <fieldset |
97 | | - className={grpClassNames} |
98 | | - style={grpStyles} |
99 | | - onChange={this.onChange} |
100 | | - {...props} |
101 | | - > |
102 | | - <legend className='slds-form-element__label'> |
103 | | - {label} |
104 | | - {required ? <abbr className='slds-required'>*</abbr> : undefined} |
105 | | - </legend> |
106 | | - <div className='slds-form-element__control'> |
107 | | - {React.Children.map(children, this.renderControl)} |
108 | | - {errorMessage ? ( |
109 | | - <div className='slds-form-element__help'>{errorMessage}</div> |
110 | | - ) : undefined} |
111 | | - </div> |
112 | | - </fieldset> |
113 | | - ); |
114 | | - } |
115 | | -} |
| 122 | +(CheckboxGroup as unknown as { isFormElement?: boolean }).isFormElement = true; |
0 commit comments