From 50dd49823e19e267823b020e1b22e14fbf440d50 Mon Sep 17 00:00:00 2001 From: Julien Bouquillon Date: Sun, 14 Jul 2024 22:44:18 +0200 Subject: [PATCH 1/7] fix(TemplateArrayField): use uncontrolled tabs --- src/components/TemplateArrayField.tsx | 80 +++++++++++++++------------ 1 file changed, 45 insertions(+), 35 deletions(-) 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} +
From 655825128c078a1092f6cb052904c823519c41c0 Mon Sep 17 00:00:00 2001 From: Julien Bouquillon Date: Sun, 14 Jul 2024 22:45:27 +0200 Subject: [PATCH 2/7] feat: add WidgetCheckBoxes --- src/components/WidgetCheckBoxes.tsx | 109 ++++++++++++++++++++++++++++ src/index.tsx | 4 + 2 files changed, 113 insertions(+) create mode 100644 src/components/WidgetCheckBoxes.tsx diff --git a/src/components/WidgetCheckBoxes.tsx b/src/components/WidgetCheckBoxes.tsx new file mode 100644 index 0000000..fe2ef52 --- /dev/null +++ b/src/components/WidgetCheckBoxes.tsx @@ -0,0 +1,109 @@ +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 (props: WidgetProps) { + +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/index.tsx b/src/index.tsx index 6636011..5c2a4c9 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -11,6 +11,7 @@ import defaultValidator from '@rjsf/validator-ajv8' import { Button } from '@codegouvfr/react-dsfr/Button' import WidgetCheckBox from './components/WidgetCheckBox' +import WidgetCheckBoxes from './components/WidgetCheckBoxes' import WidgetRadio from './components/WidgetRadio' import WidgetSelect from './components/WidgetSelect' import WidgetTextarea from './components/WidgetTextarea' @@ -89,13 +90,16 @@ export default function FormDSFR< ) } +// https://rjsf-team.github.io/react-jsonschema-form/docs/advanced-customization/custom-widgets-fields export const widgetsDSFR = { CheckboxWidget: WidgetCheckBox, + CheckboxesWidget: WidgetCheckBoxes, RadioWidget: WidgetRadio, SelectWidget: WidgetSelect, TextareaWidget: WidgetTextarea, } +// https://rjsf-team.github.io/react-jsonschema-form/docs/advanced-customization/custom-templates export const templatesDSFR = { ArrayFieldTemplate: TemplateArrayField, BaseInputTemplate: TemplateBaseInput, From 68f2748ca2a60948c3c92458decc9db8c4626f66 Mon Sep 17 00:00:00 2001 From: Julien Bouquillon Date: Sun, 14 Jul 2024 22:45:42 +0200 Subject: [PATCH 3/7] fix: small fixes --- src/components/WidgetRadio.tsx | 46 +++++++++++++++++++++------------ src/components/WidgetSelect.tsx | 11 +++++--- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/components/WidgetRadio.tsx b/src/components/WidgetRadio.tsx index 0e4bc4d..b907890 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) - + console.log(value, options) 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..f6637b9 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 @@ -74,13 +75,17 @@ export default function < label={undefined} {...props} > - {!props.multiple && + {(!props.multiple && props.schema.default === undefined && props.options.placeholder && ( - - )} + )) || ( + + )} {props.options.enumOptions?.map((item, index) => (