diff --git a/demo/src/samples/widgets.tsx b/demo/src/samples/widgets.tsx index a93b236..2f1475c 100644 --- a/demo/src/samples/widgets.tsx +++ b/demo/src/samples/widgets.tsx @@ -1,4 +1,4 @@ -import { Sample } from './Sample'; +import { Sample } from './Sample' const widgets: Sample = { schema: { @@ -146,19 +146,19 @@ const widgets: Sample = { onChange, options, }: { - value: any; - onChange: (value: any) => void; - options: { backgroundColor: string }; + value: any + onChange: (value: any) => void + options: { backgroundColor: string } }) => { - const { backgroundColor } = options; + const { backgroundColor } = options return ( onChange(event.target.value)} style={{ backgroundColor }} value={value} /> - ); + ) }, 'ui:options': { backgroundColor: 'yellow', @@ -170,14 +170,17 @@ const widgets: Sample = { onChange, options, }: { - value: any; - onChange: (value: any) => void; - options: { enumOptions: { label: string; value: any }[]; backgroundColor: string }; + value: any + onChange: (value: any) => void + options: { + enumOptions: { label: string; value: any }[] + backgroundColor: string + } }) => { - const { enumOptions, backgroundColor } = options; + const { enumOptions, backgroundColor } = options return ( - ); + ) }, 'ui:options': { backgroundColor: 'pink', @@ -213,6 +216,6 @@ const widgets: Sample = { }, secret: "I'm a hidden string.", }, -}; +} -export default widgets; +export default widgets diff --git a/src/components/TemplateArrayField.tsx b/src/components/TemplateArrayField.tsx index 0f87208..98a91b8 100644 --- a/src/components/TemplateArrayField.tsx +++ b/src/components/TemplateArrayField.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useState } from 'react' import { FrIconClassName, RiIconClassName } from '@codegouvfr/react-dsfr' import Button from '@codegouvfr/react-dsfr/Button' import Tabs from '@codegouvfr/react-dsfr/Tabs' @@ -30,59 +30,69 @@ export default function ({ items, canAdd, onAddClick, + registry, }: ArrayFieldTemplateProps & { uiSchema?: UiSchemaDSFR }) { + const [selectedTabId, setSelectedTabId] = useState('tab0') const tabLabel = uiSchema?.['ui:tabLabel'] ?? 'Element' const removeIcon = uiSchema?.['ui:removeIcon'] ?? 'fr-icon-delete-line' const addIcon = uiSchema?.['ui:addIcon'] ?? 'fr-icon-add-circle-line' - console.log('items', items) + + // ensure no exception when last selected tab has been destroyed + const selectedIndex = Math.min( + items.length - 1, + parseInt(selectedTabId.replace(/^tab(\d+)$/, '$1')), + ) + + const tabContent = + (items.length && ( + <> + + {items[selectedIndex].children} + + )) || + null + + const onTabChange = (id: string) => { + if (id === 'add') { + onAddClick() + setSelectedTabId(`tab${items.length}`) + return + } + setSelectedTabId(id) + } + return (
({ label: `${tabLabel} ${element.index + 1}`, - content: ( - <> - - {element.children} - - ), + tabId: `tab${element.index}`, })) .concat([ { label: `Ajouter`, - content: ( - <> - {canAdd && ( - - )} - - ), + tabId: 'add', }, ])} - /> + > + {selectedTabId !== 'add' && tabContent} +
diff --git a/src/components/WidgetCheckBoxes.tsx b/src/components/WidgetCheckBoxes.tsx new file mode 100644 index 0000000..9f9b919 --- /dev/null +++ b/src/components/WidgetCheckBoxes.tsx @@ -0,0 +1,108 @@ +import { + enumOptionsDeselectValue, + enumOptionsIsSelected, + enumOptionsSelectValue, + enumOptionsValueForIndex, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, +} from '@rjsf/utils' +import Checkbox from '@codegouvfr/react-dsfr/Checkbox' +import LabelWithHelp from './LabelWithHelp' +import { ChangeEvent, FocusEvent } from 'react' + + +export default function CheckboxesWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>({ + id, + disabled, + options, + value, + autofocus, + uiSchema, + schema, + readonly, + required, + onChange, + onBlur, + onFocus, +}: WidgetProps) { + const { enumOptions, enumDisabled, inline, emptyValue } = options + const checkboxesValues = Array.isArray(value) ? value : [value] + + const _onChange = + (index: number) => + ({ target: { checked } }: ChangeEvent) => { + if (checked) { + onChange( + enumOptionsSelectValue(index, checkboxesValues, enumOptions), + ) + } else { + onChange( + enumOptionsDeselectValue(index, checkboxesValues, enumOptions), + ) + } + } + + const _onBlur = ({ target }: FocusEvent) => + onBlur( + id, + enumOptionsValueForIndex( + target && target.value, + enumOptions, + emptyValue, + ), + ) + const _onFocus = ({ target }: FocusEvent) => + onFocus( + id, + enumOptionsValueForIndex( + target && target.value, + enumOptions, + emptyValue, + ), + ) + + return ( +
+ { + const checked = enumOptionsIsSelected( + option.value, + checkboxesValues, + ) + const itemDisabled = + Array.isArray(enumDisabled) && + enumDisabled.indexOf(option.value) !== -1 + + return { + label: ( + + {option.label + (required ? '*' : '')} + + ), + nativeInputProps: { + checked, + disabled: itemDisabled, + onChange: _onChange(index), + onBlur: _onBlur, + onFocus: _onFocus, + }, + } + })) || + [] + } + /> +
+ ) +} diff --git a/src/components/WidgetRadio.tsx b/src/components/WidgetRadio.tsx index 0e4bc4d..2c4580f 100644 --- a/src/components/WidgetRadio.tsx +++ b/src/components/WidgetRadio.tsx @@ -1,5 +1,6 @@ import { ChangeEvent, FocusEvent } from 'react' import { + enumOptionsIsSelected, enumOptionsValueForIndex, FormContextType, RJSFSchema, @@ -37,28 +38,41 @@ export default function RadioWidget< const _onFocus = ({ target: { value } }: FocusEvent) => onFocus(id, enumOptionsValueForIndex(value, enumOptions, emptyValue)) + const radioValues = Array.isArray(value) ? value : [value] const inline = Boolean(options && options.inline) - return (
({ - label: ( - - {option.label} - - ), - nativeInputProps: { - checked: value === option.value, - onChange: (e) => onChange(option.value), - }, - }))) || + options.enumOptions?.map((option) => { + const checked = enumOptionsIsSelected( + option.value, + radioValues, + ) + const itemDisabled = + Array.isArray(enumDisabled) && + enumDisabled.indexOf(option.value) !== -1 + + return { + label: ( + + {option.label} + + ), + nativeInputProps: { + checked, + disabled: itemDisabled, + onChange: (e) => onChange(option.value), + value: option.value, + }, + } + })) || [] } /> diff --git a/src/components/WidgetSelect.tsx b/src/components/WidgetSelect.tsx index 8833675..51b67a1 100644 --- a/src/components/WidgetSelect.tsx +++ b/src/components/WidgetSelect.tsx @@ -23,6 +23,7 @@ export default function < hideError, hideLabel, uiSchema, + multiple, ...props }: WidgetProps) { const { enumOptions, emptyValue: optEmptyVal } = props.options @@ -58,7 +59,7 @@ export default function < const uiOptions = getUiOptions(uiSchema) const backgroundColor = (uiOptions.backgroundColor as string) || 'auto' - + const placeholder = uiOptions.placeholder || props.options.placeholder return (