Skip to content

Commit

Permalink
fix: Add support for custom styles in a manner similar to classNames (#…
Browse files Browse the repository at this point in the history
…3378)

* fix: Add support for custom styles in a manner similar to classNames
Fixes #1200 by reimplementing #1256
- In `@rjsf/utils`, added the new `style` prop onto `FieldTemplateProps`, `WrapIfAdditionalTemplateProps` and `UIOptionsBaseType`
- In `@rjsf/core`, added support for the new `style` prop in `uiSchema` as follows:
  - Updated `SchemaField` to handle the new `style` prop in the `uiSchema` similarly to `classNames`, passing it to the `FieldTemplate` and removing it from being passed down to children.
  - Also, added support for new `style` prop on `FieldTemplate` and `WrapIfAdditionalTemplate` rendering them on the outermost wrapper
  - Added or updated tests to verify the `style` prop functionality
- In all the themes, added support for new `style` prop on `FieldTemplate` and `WrapIfAdditionalTemplate` rendering them on the outermost wrapper
  - Fluent-ui is a special case since it doesn't currently implement `WrapIfAdditionalTemplate` fully
- Updated the documentation to describe this new `style` prop
  - Also updated the `validation` documentation to describe the `uiSchema` prop that can be passed to the `customValidate()` and `transformError()` functions
- Updated the `CHANGELOG.md` accordingly

* - Renamed `styles` to `style`

* Apply suggestions from code review

Co-authored-by: Nick Grosenbacher <nickgrosenbacher@gmail.com>

Co-authored-by: Nick Grosenbacher <nickgrosenbacher@gmail.com>
  • Loading branch information
heath-freenome and nickgros authored Jan 15, 2023
1 parent 76cd718 commit 5db71e3
Show file tree
Hide file tree
Showing 21 changed files with 189 additions and 26 deletions.
29 changes: 27 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,45 @@ should change the heading of the (upcoming) version to include a major version b

## @rjsf/antd
- Enable searching in the `SelectWidget` by the label that the user sees rather than by the value
- Added support for new `style` prop on `FieldTemplate` and `WrapIfAdditionalTemplate` rendering them on the outermost wrapper, partially fixing [#1200](https://github.com/rjsf-team/react-jsonschema-form/issues/1200)

## @rjsf/bootstrap-4
- Added support for new `style` prop on `FieldTemplate` and `WrapIfAdditionalTemplate` rendering them on the outermost wrapper, partially fixing [#1200](https://github.com/rjsf-team/react-jsonschema-form/issues/1200)

## @rjsf/chakra-ui
- Added support for new `style` prop on `FieldTemplate` and `WrapIfAdditionalTemplate` rendering them on the outermost wrapper, partially fixing [#1200](https://github.com/rjsf-team/react-jsonschema-form/issues/1200)

## @rjsf/core
- Updated `SchemaField` to handle the new `style` prop in the `uiSchema` similarly to `classNames`, passing it to the `FieldTemplate` and removing it from being passed down to children.
- Also, added support for new `style` prop on `FieldTemplate` and `WrapIfAdditionalTemplate` rendering them on the outermost wrapper
- This partially fixes [#1200](https://github.com/rjsf-team/react-jsonschema-form/issues/1200)

## @rjsf/fluent-ui
- Added support for new `style` prop on `FieldTemplate` rendering them on the outermost wrapper, partially fixing [#1200](https://github.com/rjsf-team/react-jsonschema-form/issues/1200)

## @rjsf/material-ui
- Updated `SelectWidget` to support additional `TextFieldProps` in a manner similar to how `BaseInputTemplate` does
- Added support for new `style` prop on `FieldTemplate` and `WrapIfAdditionalTemplate` rendering them on the outermost wrapper, partially fixing [#1200](https://github.com/rjsf-team/react-jsonschema-form/issues/1200)

## @rjsf/mui
- Updated `SelectWidget` to support additional `TextFieldProps` in a manner similar to how `BaseInputTemplate` does
- Added support for new `style` prop on `FieldTemplate` and `WrapIfAdditionalTemplate` rendering them on the outermost wrapper, partially fixing [#1200](https://github.com/rjsf-team/react-jsonschema-form/issues/1200)

## @rjsf/playground
- Change Vite `preserveSymlinks` to `true`, which provides an alternative fix for [#3228](https://github.com/rjsf-team/react-jsonschema-form/issues/3228) since the prior fix caused [#3215](https://github.com/rjsf-team/react-jsonschema-form/issues/3215).
## @rjsf/semantic-ui
- Added support for new `style` prop on `FieldTemplate` and `WrapIfAdditionalTemplate` rendering them on the outermost wrapper, partially fixing [#1200](https://github.com/rjsf-team/react-jsonschema-form/issues/1200)

## @rjsf/utils
- Updated the `FieldTemplateProps`, `WrapIfAdditionalTemplateProps` and `UIOptionsBaseType` types to add `style?: StyleHTMLAttributes<any>`, partially fixing [#1200](https://github.com/rjsf-team/react-jsonschema-form/issues/1200)

## @rjsf/validator-ajv8
- Remove alias for ajv -> ajv8 in package.json. This fixes [#3215](https://github.com/rjsf-team/react-jsonschema-form/issues/3215).
- Updated `AJV8Validator#transformRJSFValidationErrors` to return more human readable error messages. The ajv8 `ErrorObject` message is enhanced by replacing the error message field with either the `uiSchema`'s `ui:title` field if one exists or the `parentSchema` title if one exists. Fixes [#3246](https://github.com/rjsf-team/react-jsonschema-form/issues/3246)

## Dev / docs / playground
- In the playground, change Vite `preserveSymlinks` to `true`, which provides an alternative fix for [#3228](https://github.com/rjsf-team/react-jsonschema-form/issues/3228) since the prior fix caused [#3215](https://github.com/rjsf-team/react-jsonschema-form/issues/3215).
- Updated the `custom-templates.md` and `uiSchema.md` to document the new `style` prop
- Updated the `validation.md` documentation to describe the new `uiSchema` parameter passed to the `customValidate()` and `transformError()` functions

# 5.0.0-beta-16

## @rjsf/antd
Expand Down
10 changes: 7 additions & 3 deletions docs/advanced-customization/custom-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -561,9 +561,9 @@ const schema: RJSFSchema = {
};

function CustomFieldTemplate(props: FieldTemplateProps) {
const {id, classNames, label, help, required, description, errors, children} = props;
const {id, classNames, style, label, help, required, description, errors, children} = props;
return (
<div className={classNames}>
<div className={classNames} style={style}>
<label htmlFor={id}>{label}{required ? "*" : null}</label>
{description}
{children}
Expand Down Expand Up @@ -594,6 +594,7 @@ The following props are passed to a custom field template component:

- `id`: The id of the field in the hierarchy. You can use it to render a label targeting the wrapped widget.
- `classNames`: A string containing the base Bootstrap CSS classes, merged with any [custom ones](#custom-css-class-names) defined in your uiSchema.
- `style`: An object containing the `StyleHTMLAttributes` defined in the `uiSchema`.
- `label`: The computed label for this field, as a string.
- `description`: A component instance rendering the field description, if one is defined (this will use any [custom `DescriptionField`](#custom-descriptions) defined).
- `rawDescription`: A string containing any `ui:description` uiSchema directive defined.
Expand Down Expand Up @@ -793,6 +794,8 @@ function WrapIfAdditionalTemplate(
children,
uiSchema,
registry,
classNames,
style,
} = props;
const { RemoveButton } = registry.templates.ButtonTemplates;
const additional = ADDITIONAL_PROPERTY_FLAG in schema;
Expand All @@ -802,7 +805,7 @@ function WrapIfAdditionalTemplate(
}

return (
<div>
<div className={classNames} style={style}>
<label label={keyLabel} id={`${id}-key`}>Custom Field Key</label>
<input
className="form-control"
Expand Down Expand Up @@ -830,6 +833,7 @@ The following props are passed to the `WrapIfAdditionalTemplate`:

- `id`: The id of the field in the hierarchy. You can use it to render a label targeting the wrapped widget.
- `classNames`: A string containing the base Bootstrap CSS classes, merged with any [custom ones](#custom-css-class-names) defined in your uiSchema.
- `style`: An object containing the `StyleHTMLAttributes` defined in the `uiSchema`.
- `label`: The computed label for this field, as a string.
- `required`: A boolean value stating if the field is required.
- `readonly`: A boolean value stating if the field is read-only.
Expand Down
24 changes: 24 additions & 0 deletions docs/api-reference/uiSchema.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,30 @@ Will result in:
</label>
</div>
```
### style

The uiSchema object accepts a `ui:style` property for each field of the schema:

```tsx
import { UiSchema } from "@rjsf/utils";

const uiSchema = {
title: {
"ui:style": { color: "red" }
}
};
```

Will result in:

```html
<div class="field field-string task-title" style={{ color: "red" }}>
<label>
<span>Title*</span>
<input value="My task" required="" type="text">
</label>
</div>
```

### autocomplete

Expand Down
6 changes: 4 additions & 2 deletions docs/usage/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ This is especially useful when the validation depends on several interdependent
import { RJSFSchema } from "@rjsf/utils";
import validator from "@rjsf/validator-ajv8";

function customValidate(formData, errors) {
function customValidate(formData, errors, uiSchema) {
if (formData.pass1 !== formData.pass2) {
errors.pass2.addError("Passwords don't match");
}
Expand All @@ -123,6 +123,7 @@ render((
> - The `customValidate()` function must implement the `CustomValidator` interface found in `@rjsf/utils`.
> - The `customValidate()` function must **always** return the `errors` object received as second argument.
> - The `customValidate()` function is called **after** the JSON schema validation.
> - The `customValidate()` function is passed the `uiSchema` as the third argument. This allows the `customValidate()` function to be able to derive additional information from it for generating errors.
## Custom error messages

Expand All @@ -133,7 +134,7 @@ If you need to change these messages or make any other modifications to the erro
import { RJSFSchema } from "@rjsf/utils";
import validator from "@rjsf/validator-ajv8";

function transformErrors(errors) {
function transformErrors(errors, uiSchema) {
return errors.map(error => {
if (error.name === "pattern") {
error.message = "Only digits are allowed"
Expand All @@ -157,6 +158,7 @@ render((
> Notes:
> - The `transformErrors()` function must implement the `ErrorTransformer` interface found in `@rjsf/utils`.
> - The `transformErrors()` function must return the list of errors. Modifying the list in place without returning it will result in an error.
> - The `transformErrors()` function is passed the `uiSchema` as the second argument. This allows the `transformErrors()` function to be able to derive additional information from it for transforming errors.
Each element in the `errors` list passed to `transformErrors` is a `RJSFValidationError` interface (in `@rjsf/utils`) and has the following properties:

Expand Down
2 changes: 2 additions & 0 deletions packages/antd/src/templates/FieldTemplate/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default function FieldTemplate<
const {
children,
classNames,
style,
description,
disabled,
displayLabel,
Expand Down Expand Up @@ -68,6 +69,7 @@ export default function FieldTemplate<
return (
<WrapIfAdditionalTemplate
classNames={classNames}
style={style}
disabled={disabled}
id={id}
label={label}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default function WrapIfAdditionalTemplate<
const {
children,
classNames,
style,
disabled,
id,
label,
Expand Down Expand Up @@ -59,7 +60,11 @@ export default function WrapIfAdditionalTemplate<
const additional = ADDITIONAL_PROPERTY_FLAG in schema;

if (!additional) {
return <div className={classNames}>{children}</div>;
return (
<div className={classNames} style={style}>
{children}
</div>
);
}

const handleBlur = ({ target }: React.FocusEvent<HTMLInputElement>) =>
Expand All @@ -73,7 +78,7 @@ export default function WrapIfAdditionalTemplate<
};

return (
<div className={classNames}>
<div className={classNames} style={style}>
<Row align={toolbarAlign} gutter={rowGutter}>
<Col className="form-additional" flex="1">
<div className="form-group">
Expand Down
2 changes: 2 additions & 0 deletions packages/bootstrap-4/src/FieldTemplate/FieldTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default function FieldTemplate<
help,
rawDescription,
classNames,
style,
disabled,
label,
hidden,
Expand All @@ -46,6 +47,7 @@ export default function FieldTemplate<
return (
<WrapIfAdditionalTemplate
classNames={classNames}
style={style}
disabled={disabled}
id={id}
label={label}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default function WrapIfAdditionalTemplate<
F extends FormContextType = any
>({
classNames,
style,
children,
disabled,
id,
Expand All @@ -36,15 +37,19 @@ export default function WrapIfAdditionalTemplate<
const additional = ADDITIONAL_PROPERTY_FLAG in schema;

if (!additional) {
return <div className={classNames}>{children}</div>;
return (
<div className={classNames} style={style}>
{children}
</div>
);
}

const handleBlur = ({ target }: React.FocusEvent<HTMLInputElement>) =>
onKeyChange(target.value);
const keyId = `${id}-key`;

return (
<Row className={classNames} key={keyId}>
<Row className={classNames} style={style} key={keyId}>
<Col xs={5}>
<Form.Group>
<Form.Label htmlFor={keyId}>{keyLabel}</Form.Label>
Expand Down
2 changes: 2 additions & 0 deletions packages/chakra-ui/src/FieldTemplate/FieldTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default function FieldTemplate<
id,
children,
classNames,
style,
disabled,
displayLabel,
hidden,
Expand Down Expand Up @@ -49,6 +50,7 @@ export default function FieldTemplate<
return (
<WrapIfAdditionalTemplate
classNames={classNames}
style={style}
disabled={disabled}
id={id}
label={label}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default function WrapIfAdditionalTemplate<
const {
children,
classNames,
style,
disabled,
id,
label,
Expand All @@ -37,15 +38,25 @@ export default function WrapIfAdditionalTemplate<
const { RemoveButton } = registry.templates.ButtonTemplates;
const additional = ADDITIONAL_PROPERTY_FLAG in schema;
if (!additional) {
return <div className={classNames}>{children}</div>;
return (
<div className={classNames} style={style}>
{children}
</div>
);
}
const keyLabel = `${label} Key`;

const handleBlur = ({ target }: React.FocusEvent<HTMLInputElement>) =>
onKeyChange(target.value);

return (
<Grid key={`${id}-key`} className={classNames} alignItems="center" gap={2}>
<Grid
key={`${id}-key`}
className={classNames}
style={style}
alignItems="center"
gap={2}
>
<GridItem>
<FormControl isRequired={required}>
<FormLabel htmlFor={`${id}-key`} id={`${id}-key-label`}>
Expand Down
15 changes: 11 additions & 4 deletions packages/core/src/components/fields/SchemaField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
UIOptionsType,
ID_KEY,
ADDITIONAL_PROPERTY_FLAG,
UI_OPTIONS_KEY,
} from "@rjsf/utils";
import isObject from "lodash/isObject";
import omit from "lodash/omit";
Expand Down Expand Up @@ -188,11 +189,16 @@ function SchemaFieldRender<
const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema);

const { __errors, ...fieldErrorSchema } = errorSchema || {};
// See #439: uiSchema: Don't pass consumed class names to child components
const fieldUiSchema = omit(uiSchema, ["ui:classNames", "classNames"]);
if ("ui:options" in fieldUiSchema) {
fieldUiSchema["ui:options"] = omit(fieldUiSchema["ui:options"], [
// See #439: uiSchema: Don't pass consumed class names or style to child components
const fieldUiSchema = omit(uiSchema, [
"ui:classNames",
"classNames",
"ui:style",
]);
if (UI_OPTIONS_KEY in fieldUiSchema) {
fieldUiSchema[UI_OPTIONS_KEY] = omit(fieldUiSchema[UI_OPTIONS_KEY], [
"classNames",
"style",
]);
}

Expand Down Expand Up @@ -297,6 +303,7 @@ function SchemaFieldRender<
hideError,
displayLabel,
classNames: classNames.join(" ").trim(),
style: uiOptions.style,
formContext,
formData,
schema,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default function WrapIfAdditionalTemplate<
const {
id,
classNames,
style,
disabled,
label,
onKeyChange,
Expand All @@ -39,11 +40,15 @@ export default function WrapIfAdditionalTemplate<
const additional = ADDITIONAL_PROPERTY_FLAG in schema;

if (!additional) {
return <div className={classNames}>{children}</div>;
return (
<div className={classNames} style={style}>
{children}
</div>
);
}

return (
<div className={classNames}>
<div className={classNames} style={style}>
<div className="row">
<div className="col-xs-5 form-additional">
<div className="form-group">
Expand Down
Loading

0 comments on commit 5db71e3

Please sign in to comment.