Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG_v6.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ should change the heading of the (upcoming) version to include a major version b
- Implemented the `GridTemplate` component, adding it to the `templates` for the theme
- BREAKING CHANGE: Removed support for version 4 of `antd`
- Updated `ArrayFieldItemTemplate` to replace `Button.Group` with `Space.Compact` since `Button.Group` is deprecated in `antd` version 5
- Upgraded to `@ant-design/icon@5`, fixing typing issues in `IconButton`
- Upgraded to `@ant-design/icon@5`

## @rjsf/chakra-ui

Expand All @@ -39,6 +39,7 @@ should change the heading of the (upcoming) version to include a major version b
- Refactored `ArrayFieldItemTemplate` to use the new `ArrayFieldItemButtonsTemplate`
- Updated the `ArrayFieldTemplate`, `ObjectFieldTemplate`, and `WrapIfAdditionalTemplate` to a unique id using the `buttonId()` function and adding consistent marker classes
- Implemented the `GridTemplate` component, adding it to the `templates` for the theme
- Implemented the new `LayoutGridField`, `LayoutMultiSchemaField` and `LayoutHeaderField` fields, adding them to the `fields` list

## @rjsf/fluent-ui

Expand Down
49 changes: 49 additions & 0 deletions packages/core/src/components/fields/LayoutHeaderField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {
getTemplate,
getUiOptions,
titleId,
FieldProps,
FormContextType,
RJSFSchema,
StrictRJSFSchema,
TemplatesType,
} from '@rjsf/utils';

/** The `LayoutHeaderField` component renders a `TitleFieldTemplate` with an `id` derived from the `idSchema`
* and whether it is `required` from the props. The `title` is derived from the props as follows:
* - If there is a title in the `uiSchema`, it is displayed
* - Else, if there is an explicit `title` passed in the props, it is displayed
* - Otherwise, if there is a title in the `schema`, it is displayed
* - Finally, the `name` prop is displayed as the title
*
* @param props - The `LayoutHeaderField` for the component
*/
export default function LayoutHeaderField<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>(props: FieldProps<T, S, F>) {
const { idSchema, title, schema, uiSchema, required, registry, name } = props;
const options = getUiOptions<T, S, F>(uiSchema, registry.globalUiOptions);
const { title: uiTitle } = options;
const { title: schemaTitle } = schema;
const fieldTitle = uiTitle || title || schemaTitle || name;
if (!fieldTitle) {
return null;
}
const TitleFieldTemplate: TemplatesType<T, S, F>['TitleFieldTemplate'] = getTemplate<'TitleFieldTemplate', T, S, F>(
'TitleFieldTemplate',
registry,
options
);
return (
<TitleFieldTemplate
id={titleId<T>(idSchema)}
title={fieldTitle}
required={required}
schema={schema}
uiSchema={uiSchema}
registry={registry}
/>
);
}
2 changes: 2 additions & 0 deletions packages/core/src/components/fields/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Field, FormContextType, RegistryFieldsType, RJSFSchema, StrictRJSFSchem
import ArrayField from './ArrayField';
import BooleanField from './BooleanField';
import LayoutGridField from './LayoutGridField';
import LayoutHeaderField from './LayoutHeaderField';
import LayoutMultiSchemaField from './LayoutMultiSchemaField';
import MultiSchemaField from './MultiSchemaField';
import NumberField from './NumberField';
Expand All @@ -22,6 +23,7 @@ function fields<
// ArrayField falls back to SchemaField if ArraySchemaField is not defined, which it isn't by default
BooleanField,
LayoutGridField,
LayoutHeaderField,
LayoutMultiSchemaField,
NumberField,
ObjectField,
Expand Down
132 changes: 132 additions & 0 deletions packages/core/test/LayoutHeaderField.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { titleId, FieldProps, ID_KEY, IdSchema, Registry, TitleFieldProps } from '@rjsf/utils';
import { render, screen, within } from '@testing-library/react';
import noop from 'lodash/noop';

import templates from '../src/components/templates';
import LayoutHeaderField from '../src/components/fields/LayoutHeaderField';

const TEST_ID = 'test-id';
const REQUIRED_ID = 'required-id';

const TITLE_BOLD = 'test';
const TITLE_BOLD_2 = 'test ui';
const TITLE_NORMAL = 'title';

function TestTitleField(props: TitleFieldProps) {
const { id, title, required } = props;
return (
<div id={id} data-testid={TEST_ID}>
{title}
{required && <span data-testid={REQUIRED_ID} />}
</div>
);
}

describe('LayoutHeaderField', () => {
function getProps(overrideProps: Partial<FieldProps> = {}): FieldProps {
const { idSchema = {} as IdSchema, schema = {}, name = '', uiSchema = {}, required = false, title } = overrideProps;
return {
// required FieldProps stubbed
autofocus: false,
disabled: false,
errorSchema: {},
formContext: undefined,
formData: undefined,
onBlur: noop,
onChange: noop,
onFocus: noop,
readonly: false,
title,
required,
// end required FieldProps
idSchema,
schema,
uiSchema,
name,
registry: {
templates: {
...templates(),
TitleFieldTemplate: TestTitleField,
},
} as Registry,
};
}

test('default render with no title is empty render', () => {
const props = getProps();
const { container } = render(<LayoutHeaderField {...props} />);

expect(container).toBeEmptyDOMElement();
});

test('name is provided, and its required', () => {
const props = getProps({ name: TITLE_BOLD, required: true });
render(<LayoutHeaderField {...props} />);

// renders header field and has expected text and no id
const headerField = screen.getByTestId(TEST_ID);
expect(headerField).toHaveTextContent(TITLE_BOLD);
expect(headerField).toHaveAttribute('id', titleId('undefined'));

// Is required
const requiredSpan = within(headerField).getByTestId(REQUIRED_ID);
expect(requiredSpan).toBeInTheDocument();
});

test('name is provided, schema has title, idSchema has ID_KEY, not required', () => {
const props = getProps({
name: TITLE_BOLD,
schema: { title: TITLE_NORMAL },
idSchema: { [ID_KEY]: 'foo' } as IdSchema,
});
render(<LayoutHeaderField {...props} />);

// renders header field and has expected text and id
const headerField = screen.getByTestId(TEST_ID);
expect(headerField).toHaveTextContent(TITLE_NORMAL);
expect(headerField).toHaveAttribute('id', titleId(props.idSchema[ID_KEY]));

// Is not required
const requiredSpan = within(headerField).queryByTestId(REQUIRED_ID);
expect(requiredSpan).not.toBeInTheDocument();
});

test('title prop is passed, schema has title, idSchema has ID_KEY, required', () => {
const props = getProps({
title: TITLE_BOLD,
schema: { title: TITLE_NORMAL },
idSchema: { [ID_KEY]: 'foo' } as IdSchema,
required: true,
});
render(<LayoutHeaderField {...props} />);

// renders header field and has expected text and id
const headerField = screen.getByTestId(TEST_ID);
expect(headerField).toHaveTextContent(TITLE_BOLD);
expect(headerField).toHaveAttribute('id', titleId(props.idSchema[ID_KEY]));

// Is not required
const requiredSpan = within(headerField).getByTestId(REQUIRED_ID);
expect(requiredSpan).toBeInTheDocument();
});

test('uiSchema has ui:title, title prop is passed, no id, not required', () => {
const props = getProps({
title: TITLE_BOLD,
uiSchema: {
'ui:title': TITLE_BOLD_2,
},
idSchema: { [ID_KEY]: 'foo' } as IdSchema,
});
render(<LayoutHeaderField {...props} />);

// renders header field and has expected text and no id
const headerField = screen.getByTestId(TEST_ID);
expect(headerField).toHaveTextContent(TITLE_BOLD_2);
expect(headerField).toHaveAttribute('id', titleId(props.idSchema[ID_KEY]));

// Is not required
const requiredSpan = within(headerField).queryByTestId(REQUIRED_ID);
expect(requiredSpan).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ The default fields you can override are:
- `DescriptionField`
- `OneOfField`
- `AnyOfField`
- `LayoutGridField`
- `LayoutMultiSchemaField`
- `LayoutHeaderField`
- `NullField`
- `NumberField`
- `ObjectField`
Expand Down
Loading