Skip to content

Commit

Permalink
fix: #2007 by reimplementing #2533 (#3186)
Browse files Browse the repository at this point in the history
Fixed #2007

The current code for FieldTemplate and TextWidget in bootstrap 4 is incompatible with the core package. This breaks the
external facing API for customizing field template.
According to the core package implementation and documents, FieldTemplate is responsible for layout of labels, description, errors and help while the input widget along with the state is to be managed by the Field Component themselves (and Widgets)
If a user, uses custom field template with the bootstrap 4 form, the label is displayed multiple times, once by the custom field template (as intended in the API) and second time by the TextWidget (breaks from the core package convention)
This fixes the problem and updates the tests to catch any future regressions.

- Updated `FieldTemplate` to output `Label` generation, adding an additional check to simply render `children` when the component is `hidden`
- Updated `BaseInputTemplate`, `CheckboxesWidget`, `RadioWidget`, `SelectWidget` and `TextareaWidget`, removing the `Label` generation
- Updated the test snapshots
- Updated the `CHANGELOG.md` accordingly
  • Loading branch information
heath-freenome committed Oct 7, 2022
1 parent 7313da1 commit 4542b16
Show file tree
Hide file tree
Showing 13 changed files with 929 additions and 1,087 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ should change the heading of the (upcoming) version to include a major version b
-->
# 5.0.0-beta.11

## @rjsf/bootstrap-4
- Make label generation consistent with other themes by refactoring the code into the `FieldTemplate` instead of having the widgets implementing the label, fixing [#2007](https://github.com/rjsf-team/react-jsonschema-form/issues/2007)

## @rjsf/chakra-ui
- Added support for `chakra-react-select` v4, fixing [#3152](https://github.com/rjsf-team/react-jsonschema-form/issues/3152).

Expand Down
16 changes: 3 additions & 13 deletions packages/bootstrap-4/src/BaseInputTemplate/BaseInputTemplate.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import Form from "react-bootstrap/Form";
import { getInputProps, getUiOptions, WidgetProps } from "@rjsf/utils";
import { getInputProps, WidgetProps } from "@rjsf/utils";

const BaseInputTemplate = ({
id,
Expand All @@ -9,7 +9,6 @@ const BaseInputTemplate = ({
readonly,
disabled,
type,
label,
value,
onChange,
onBlur,
Expand All @@ -18,12 +17,10 @@ const BaseInputTemplate = ({
options,
schema,
rawErrors = [],
uiSchema,
children,
extraProps,
}: WidgetProps) => {
const inputProps = { ...extraProps, ...getInputProps(schema, type, options) };
const uiOptions = getUiOptions(uiSchema);
const _onChange = ({
target: { value },
}: React.ChangeEvent<HTMLInputElement>) =>
Expand All @@ -36,14 +33,7 @@ const BaseInputTemplate = ({

// const classNames = [rawErrors.length > 0 ? "is-invalid" : "", type === 'file' ? 'custom-file-label': ""]
return (
<Form.Group className="mb-0">
<Form.Label
htmlFor={id}
className={rawErrors.length > 0 ? "text-danger" : ""}
>
{uiOptions.title || label || schema.title}
{(label || uiOptions.title) && required ? "*" : null}
</Form.Label>
<>
<Form.Control
id={id}
name={id}
Expand All @@ -70,7 +60,7 @@ const BaseInputTemplate = ({
})}
</datalist>
) : null}
</Form.Group>
</>
);
};

Expand Down
61 changes: 28 additions & 33 deletions packages/bootstrap-4/src/CheckboxesWidget/CheckboxesWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ const deselectValue = (value: any, selected: any) => {
};

const CheckboxesWidget = ({
schema,
label,
id,
disabled,
options,
Expand Down Expand Up @@ -50,38 +48,35 @@ const CheckboxesWidget = ({
}: React.FocusEvent<HTMLInputElement>) => onFocus(id, value);

return (
<>
<Form.Label htmlFor={id}>{label || schema.title}</Form.Label>
<Form.Group>
{Array.isArray(enumOptions) &&
enumOptions.map((option, index: number) => {
const checked = value.indexOf(option.value) !== -1;
const itemDisabled =
Array.isArray(enumDisabled) &&
enumDisabled.indexOf(option.value) !== -1;
<Form.Group>
{Array.isArray(enumOptions) &&
enumOptions.map((option, index: number) => {
const checked = value.indexOf(option.value) !== -1;
const itemDisabled =
Array.isArray(enumDisabled) &&
enumDisabled.indexOf(option.value) !== -1;

return (
<Form.Check
key={option.value}
inline={inline}
custom
required={required}
checked={checked}
className="bg-transparent border-0"
type={"checkbox"}
id={`${id}-${option.value}`}
name={id}
label={option.label}
autoFocus={autofocus && index === 0}
onChange={_onChange(option)}
onBlur={_onBlur}
onFocus={_onFocus}
disabled={disabled || itemDisabled || readonly}
/>
);
})}
</Form.Group>
</>
return (
<Form.Check
key={option.value}
inline={inline}
custom
required={required}
checked={checked}
className="bg-transparent border-0"
type={"checkbox"}
id={`${id}-${option.value}`}
name={id}
label={option.label}
autoFocus={autofocus && index === 0}
onChange={_onChange(option)}
onBlur={_onBlur}
onFocus={_onFocus}
disabled={disabled || itemDisabled || readonly}
/>
);
})}
</Form.Group>
);
};

Expand Down
13 changes: 13 additions & 0 deletions packages/bootstrap-4/src/FieldTemplate/FieldTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const FieldTemplate = ({
classNames,
disabled,
label,
hidden,
onDropPropertyClick,
onKeyChange,
readonly,
Expand All @@ -27,6 +28,9 @@ const FieldTemplate = ({
registry,
uiOptions
);
if (hidden) {
return <div className="hidden">{children}</div>;
}
return (
<WrapIfAdditionalTemplate
classNames={classNames}
Expand All @@ -42,6 +46,15 @@ const FieldTemplate = ({
registry={registry}
>
<Form.Group>
{displayLabel && (
<Form.Label
htmlFor={id}
className={rawErrors.length > 0 ? "text-danger" : ""}
>
{label}
{required ? "*" : null}
</Form.Label>
)}
{children}
{displayLabel && rawDescription && (
<Form.Text
Expand Down
9 changes: 1 addition & 8 deletions packages/bootstrap-4/src/RadioWidget/RadioWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";

import Form from "react-bootstrap/Form";

import { WidgetProps, getUiOptions } from "@rjsf/utils";
import { WidgetProps } from "@rjsf/utils";

const RadioWidget = ({
id,
Expand All @@ -12,14 +12,11 @@ const RadioWidget = ({
required,
disabled,
readonly,
label,
onChange,
onBlur,
onFocus,
uiSchema,
}: WidgetProps) => {
const { enumOptions, enumDisabled } = options;
const uiOptions = getUiOptions(uiSchema);

const _onChange = ({
target: { value },
Expand All @@ -35,10 +32,6 @@ const RadioWidget = ({

return (
<Form.Group className="mb-0">
<Form.Label className="d-block">
{uiOptions.title || schema.title || label}
{(label || uiOptions.title || schema.title) && required ? "*" : null}
</Form.Label>
{Array.isArray(enumOptions) &&
enumOptions.map((option) => {
const itemDisabled =
Expand Down
96 changes: 43 additions & 53 deletions packages/bootstrap-4/src/SelectWidget/SelectWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const SelectWidget = ({
schema,
id,
options,
label,
required,
disabled,
readonly,
Expand Down Expand Up @@ -40,59 +39,50 @@ const SelectWidget = ({
}

return (
<Form.Group>
<Form.Label
className={rawErrors.length > 0 ? "text-danger" : ""}
htmlFor={id}
>
{label || schema.title}
{(label || schema.title) && required ? "*" : null}
</Form.Label>
<Form.Control
as="select"
bsPrefix="custom-select"
id={id}
name={id}
value={typeof value === "undefined" ? emptyValue : value}
required={required}
multiple={multiple}
disabled={disabled || readonly}
autoFocus={autofocus}
className={rawErrors.length > 0 ? "is-invalid" : ""}
onBlur={
onBlur &&
((event: React.FocusEvent) => {
const newValue = getValue(event, multiple);
onBlur(id, processSelectValue(schema, newValue, options));
})
}
onFocus={
onFocus &&
((event: React.FocusEvent) => {
const newValue = getValue(event, multiple);
onFocus(id, processSelectValue(schema, newValue, options));
})
}
onChange={(event: React.ChangeEvent) => {
<Form.Control
as="select"
bsPrefix="custom-select"
id={id}
name={id}
value={typeof value === "undefined" ? emptyValue : value}
required={required}
multiple={multiple}
disabled={disabled || readonly}
autoFocus={autofocus}
className={rawErrors.length > 0 ? "is-invalid" : ""}
onBlur={
onBlur &&
((event: React.FocusEvent) => {
const newValue = getValue(event, multiple);
onChange(processSelectValue(schema, newValue, options));
}}
>
{!multiple && schema.default === undefined && (
<option value="">{placeholder}</option>
)}
{(enumOptions as any).map(({ value, label }: any, i: number) => {
const disabled: any =
Array.isArray(enumDisabled) &&
(enumDisabled as any).indexOf(value) != -1;
return (
<option key={i} id={label} value={value} disabled={disabled}>
{label}
</option>
);
})}
</Form.Control>
</Form.Group>
onBlur(id, processSelectValue(schema, newValue, options));
})
}
onFocus={
onFocus &&
((event: React.FocusEvent) => {
const newValue = getValue(event, multiple);
onFocus(id, processSelectValue(schema, newValue, options));
})
}
onChange={(event: React.ChangeEvent) => {
const newValue = getValue(event, multiple);
onChange(processSelectValue(schema, newValue, options));
}}
>
{!multiple && schema.default === undefined && (
<option value="">{placeholder}</option>
)}
{(enumOptions as any).map(({ value, label }: any, i: number) => {
const disabled: any =
Array.isArray(enumDisabled) &&
(enumDisabled as any).indexOf(value) != -1;
return (
<option key={i} id={label} value={value} disabled={disabled}>
{label}
</option>
);
})}
</Form.Control>
);
};

Expand Down
54 changes: 18 additions & 36 deletions packages/bootstrap-4/src/TextareaWidget/TextareaWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";

import { getUiOptions, WidgetProps } from "@rjsf/utils";
import { WidgetProps } from "@rjsf/utils";
import FormControl from "react-bootstrap/FormControl";
import InputGroup from "react-bootstrap/InputGroup";

Expand All @@ -15,17 +15,12 @@ const TextareaWidget = ({
required,
disabled,
autofocus,
label,
readonly,
onBlur,
onFocus,
onChange,
options,
schema,
rawErrors = [],
uiSchema,
}: CustomWidgetProps) => {
const uiOptions = getUiOptions(uiSchema);
const _onChange = ({
target: { value },
}: React.ChangeEvent<HTMLTextAreaElement>) =>
Expand All @@ -38,36 +33,23 @@ const TextareaWidget = ({
}: React.FocusEvent<HTMLTextAreaElement>) => onFocus(id, value);

return (
<>
<label htmlFor={id}>
{uiOptions.title || schema.title || label}
{required && (
<span
aria-hidden
className={rawErrors.length > 0 ? "text-danger ml-1" : "ml-1"}
>
&thinsp;{"*"}
</span>
)}
</label>
<InputGroup>
<FormControl
id={id}
name={id}
as="textarea"
placeholder={placeholder}
disabled={disabled}
readOnly={readonly}
value={value}
required={required}
autoFocus={autofocus}
rows={options.rows || 5}
onChange={_onChange}
onBlur={_onBlur}
onFocus={_onFocus}
/>
</InputGroup>
</>
<InputGroup>
<FormControl
id={id}
name={id}
as="textarea"
placeholder={placeholder}
disabled={disabled}
readOnly={readonly}
value={value}
required={required}
autoFocus={autofocus}
rows={options.rows || 5}
onChange={_onChange}
onBlur={_onBlur}
onFocus={_onFocus}
/>
</InputGroup>
);
};

Expand Down
Loading

0 comments on commit 4542b16

Please sign in to comment.