Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(templates): Allow customization per-field by using Registry string key #3881

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ it according to semantic versioning. For example, if your PR adds a breaking cha
should change the heading of the (upcoming) version to include a major version bump.

-->
# 5.14.0

## @rjsf/utils
- Updated `getTemplate()` to allow per-field customization using string key from `Registry`.

## Dev / docs / playground
- Updated `advanced-customization/custom-templates` with the new feature.

# 5.13.0

## @rjsf/antd
Expand Down
10 changes: 5 additions & 5 deletions packages/docs/docs/advanced-customization/custom-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ render(
);
```

You also can provide your own field template to a uiSchema by specifying a `ui:ArrayFieldTemplate` property.
You also can provide your own field template to a uiSchema by specifying a `ui:ArrayFieldTemplate` property with your Component or a string value from the `Registry`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding at least one example of how to use an alternate template with a different name in the uiSchema here and possibly all the sections you've updated below


```tsx
import { UiSchema } from '@rjsf/utils';
Expand Down Expand Up @@ -162,7 +162,7 @@ render(
);
```

You also can provide your own template to a uiSchema by specifying a `ui:ArrayFieldDescriptionTemplate` property.
You also can provide your own template to a uiSchema by specifying a `ui:ArrayFieldDescriptionTemplate` property with your Component or a string value from the `Registry`.

```tsx
import { UiSchema } from '@rjsf/utils';
Expand Down Expand Up @@ -260,7 +260,7 @@ render(
);
```

You also can provide your own template to a uiSchema by specifying a `ui:ArrayFieldDescriptionTemplate` property.
You also can provide your own template to a uiSchema by specifying a `ui:ArrayFieldDescriptionTemplate` property with your Component or a string value from the `Registry`.

```tsx
import { UiSchema } from '@rjsf/utils';
Expand Down Expand Up @@ -612,7 +612,7 @@ render(
);
```

You also can provide your own field template to a uiSchema by specifying a `ui:FieldTemplate` property.
You also can provide your own field template to a uiSchema by specifying a `ui:FieldTemplate` property with your Component or a string value from the `Registry`.

```tsx
import { UiSchema } from '@rjsf/utils';
Expand Down Expand Up @@ -691,7 +691,7 @@ render(
);
```

You also can provide your own field template to a uiSchema by specifying a `ui:ObjectFieldTemplate` property.
You also can provide your own field template to a uiSchema by specifying a `ui:ObjectFieldTemplate` property with your Component or a string value from the `Registry`.

```tsx
import { UiSchema } from '@rjsf/utils';
Expand Down
11 changes: 11 additions & 0 deletions packages/utils/src/getTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ export default function getTemplate<
if (name === 'ButtonTemplates') {
return templates[name];
}
// Allow templates to be customized per-field by using string keys from the registry
if (
Object.hasOwn(uiOptions, name) &&
typeof uiOptions[name] === 'string' &&
Object.hasOwn(templates, uiOptions[name] as string)
) {
const key = uiOptions[name];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To truly support things in a way that matches how widgets and fields updating the TemplateTypes type in @rjsf/utils/src/types.ts to add this one additional line is all the extra work you will need to do:

  /** Allow this to support any named `ComponentType` or an object of named `ComponentType`s */
  [name: string]: ComponentType<any> | { [name: string]: ComponentType<any> };

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I was wrong. It will require creating a new interface called RJSFBaseProps with the definition:

export interface RJSFBaseProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any> {
  /** The schema object for the field being described */
  schema: S;
  /** The uiSchema object for this description field */
  uiSchema?: UiSchema<T, S, F>;
  /** The `registry` object */
  registry: Registry<T, S, F>;
}

Then you will want to update all of the interfaces and types that the TemplateTypes inner props that have ComponentType<XXX> to make the XXX interface/type extend RJSFBaseProps

Then add the following to the top of the TemplateTypes interface instead of what I mentioned above:

/** Allow this to support any named `ComponentType` or an object of named `ComponentType`s */
  [name: string]: ComponentType<RJSFBaseProps<T, S, F> | { [name: string]: ComponentType<RJSFBaseProps<T, S, F> };

// Evaluating templates[key] results in TS2590: Expression produces a union type that is too complex to represent
// To avoid that, we cast templates to `any` before accessing the key field
return (templates as any)[key];
}
return (
// Evaluating uiOptions[name] results in TS2590: Expression produces a union type that is too complex to represent
// To avoid that, we cast uiOptions to `any` before accessing the name field
Expand Down
15 changes: 15 additions & 0 deletions packages/utils/test/getTemplate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,19 @@ describe('getTemplate', () => {
expect(getTemplate<typeof name>(name, registry, uiOptions)).toBe(CustomTemplate);
});
});
it('returns the template from registry using uiOptions key when available', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add another set of tests for custom names

KEYS.forEach(key => {
const name = key as keyof TemplatesType;
expect(
getTemplate<typeof name>(
name,
registry,
Object.keys(uiOptions).reduce((uiOptions, key) => {
uiOptions[key] = key;
return uiOptions;
}, {})
)
).toBe(FakeTemplate);
});
});
});