Skip to content

Commit

Permalink
Adding ObjectControl (#26)
Browse files Browse the repository at this point in the history
* Adding ObjectControl

* Updating naming

* Update renderers.ts
  • Loading branch information
TrangPham committed Mar 9, 2024
1 parent 5c321c4 commit f7c56a7
Show file tree
Hide file tree
Showing 9 changed files with 322 additions and 13 deletions.
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@
"build-storybook": "storybook build"
},
"peerDependencies": {
"react": "^17 || ^18",
"@ant-design/icons": "^5.3.0",
"@jsonforms/core": "^3.2.1",
"@jsonforms/react": "^3.2.1",
"@ant-design/icons": "^5.3.0",
"antd": "^5.14.0"
"@types/lodash.isempty": "^4.4.9",
"antd": "^5.14.0",
"lodash.isempty": "^4.4.0",
"react": "^17 || ^18"
},
"devDependencies": {
"@ant-design/icons": "^5.3.0",
Expand All @@ -51,6 +53,7 @@
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.1",
"@testing-library/user-event": "^14.5.2",
"@types/lodash.isempty": "^4.4.9",
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"@typescript-eslint/eslint-plugin": "^6.14.0",
Expand All @@ -64,6 +67,7 @@
"eslint-plugin-storybook": "^0.6.15",
"jsdom": "^24.0.0",
"json-schema-to-ts": "^3.0.0",
"lodash.isempty": "^4.4.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"storybook": "^7.6.8",
Expand Down
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions src/controls/ObjectControl.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { test, expect, describe } from "vitest";
import { objectSchema, objectUISchemaWithName, objectUISchemaWithRule } from "../testSchemas/objectSchema"
import { render } from "../common/test-render";
import { screen } from "@testing-library/react"

describe("ObjectControl", () => {
test("renders nested fields", () => {
render({ schema: objectSchema })

expect(screen.getByText("Name")).not.toBeNull()
expect(screen.getByText("Last Name")).not.toBeNull()
})

describe("only renders when visible", () => {
test("property is not visible if not included in UISchema", () => {
render({ schema: objectSchema, uischema: objectUISchemaWithName })

expect(screen.queryByText("Last Name")).toBeNull()
})

describe("manage visibility with condition rules", () => {
test("hide field when condition matches", () => {
render({
data: { name: "John", lastName: "Doe" },
schema: objectSchema,
uischema: objectUISchemaWithRule,
})

expect(screen.queryByText("Last Name")).toBeNull()
})

test("render field when condition doesn't match", () => {
render({
data: { name: "Bob", lastName: "Doe" },
schema: objectSchema,
uischema: objectUISchemaWithRule,
})

expect(screen.getByText("Name")).not.toBeNull()
expect(screen.getByText("Last Name")).not.toBeNull()
})
})
})
})
57 changes: 57 additions & 0 deletions src/controls/ObjectControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useMemo } from "react"
import {
findUISchema,
Generate,
StatePropsOfControlWithDetail
} from "@jsonforms/core"
import { JsonFormsDispatch } from "@jsonforms/react"
import isEmpty from "lodash.isempty"



export function ObjectControl({
renderers,
cells,
uischemas,
schema,
label,
path,
visible,
enabled,
uischema,
rootSchema,
}: StatePropsOfControlWithDetail) {
const detailUiSchema = useMemo(
() =>
findUISchema(
uischemas ?? [],
schema,
uischema.scope,
path,
() =>
isEmpty(path)
? Generate.uiSchema(schema, "VerticalLayout")
: { ...Generate.uiSchema(schema, "Group"), label },
uischema,
rootSchema,
),
[uischemas, schema, path, label, uischema, rootSchema],
)

if (!visible) {
return null
}

return (
<JsonFormsDispatch
visible={visible}
uischemas={uischemas}
enabled={enabled}
schema={schema}
uischema={detailUiSchema}
path={path}
renderers={renderers}
cells={cells}
/>
)
}
30 changes: 30 additions & 0 deletions src/layouts/GroupLayoutRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { GroupLayout, OwnPropsOfRenderer } from "@jsonforms/core";
import { UISchema } from "../ui-schema";
import { Divider } from "antd";
import { AntDLayoutRenderer } from "./LayoutRenderer";

export type LayoutRendererProps = OwnPropsOfRenderer & {
elements: UISchema[];
};

export function GroupLayoutRenderer({
visible,
enabled,
uischema,
...props
}: LayoutRendererProps) {
const groupLayout = uischema as GroupLayout;
return (
<>
<Divider />
{groupLayout?.label && <b>{groupLayout.label}</b>}
<AntDLayoutRenderer
{...props}
visible={visible}
enabled={enabled}
elements={groupLayout.elements}
/>
<Divider />
</>
);
}
File renamed without changes.
65 changes: 55 additions & 10 deletions src/renderers.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,67 @@
import { JsonFormsRendererRegistryEntry,JsonFormsCellRendererRegistryEntry, isBooleanControl, isStringControl, rankWith, uiTypeIs } from "@jsonforms/core";
import { withJsonFormsControlProps, withJsonFormsLabelProps, withJsonFormsCellProps, withJsonFormsLayoutProps } from "@jsonforms/react";
import {
JsonFormsRendererRegistryEntry,
JsonFormsCellRendererRegistryEntry,
isBooleanControl,
isStringControl,
rankWith,
uiTypeIs,
isObjectControl,
isLayout,
not,
and,
} from "@jsonforms/core";
import {
withJsonFormsControlProps,
withJsonFormsLabelProps,
withJsonFormsCellProps,
withJsonFormsLayoutProps,
withJsonFormsDetailProps,
} from "@jsonforms/react";

import { BooleanControl } from "./controls/BooleanControl";
import { AlertControl } from "./controls/AlertControl";
import { TextControl } from "./controls/TextControl";
import { UnknownControl } from "./controls/UnknownControl";
import { VerticalLayoutRenderer } from "./layouts/VerticalLayout";

import { VerticalLayoutRenderer } from "./layouts/VerticalLayoutRenderer";
import { ObjectControl } from "./controls/ObjectControl";
import { GroupLayoutRenderer } from "./layouts/GroupLayoutRenderer";
import React from "react";

// Ordered from lowest rank to highest rank. Higher rank renderers will be preferred over lower rank renderers.
export const rendererRegistryEntries: JsonFormsRendererRegistryEntry[] = [
{ tester: rankWith(1, () => true), renderer: withJsonFormsControlProps(UnknownControl) },
{ tester: rankWith(2, uiTypeIs("VerticalLayout")), renderer: withJsonFormsLayoutProps(VerticalLayoutRenderer) },
{ tester: rankWith(2, isBooleanControl), renderer: withJsonFormsControlProps(BooleanControl) },
{ tester: rankWith(2, isStringControl), renderer: withJsonFormsControlProps(TextControl) },
{ tester: rankWith(2, uiTypeIs("Label")), renderer: withJsonFormsLabelProps(AlertControl) },
{
tester: rankWith(1, () => true),
renderer: withJsonFormsControlProps(UnknownControl),
},
{
tester: rankWith(1, uiTypeIs("Group")),
renderer: React.memo(GroupLayoutRenderer),
},
{
tester: rankWith(2, uiTypeIs("VerticalLayout")),
renderer: withJsonFormsLayoutProps(VerticalLayoutRenderer),
},
{
tester: rankWith(2, isBooleanControl),
renderer: withJsonFormsControlProps(BooleanControl),
},
{
tester: rankWith(2, isStringControl),
renderer: withJsonFormsControlProps(TextControl),
},
{
tester: rankWith(2, uiTypeIs("Label")),
renderer: withJsonFormsLabelProps(AlertControl),
},
{
tester: rankWith(10, and(isObjectControl, not(isLayout))),
renderer: withJsonFormsDetailProps(ObjectControl),
},
];

export const cellRegistryEntries: JsonFormsCellRendererRegistryEntry[] = [
{ tester: rankWith(1, () => true), cell: withJsonFormsCellProps(UnknownControl)}
{
tester: rankWith(1, () => true),
cell: withJsonFormsCellProps(UnknownControl),
},
];
49 changes: 49 additions & 0 deletions src/stories/controls/ObjectControl.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Meta, StoryObj } from "@storybook/react";
import { rendererRegistryEntries } from "../../renderers";
import { StorybookAntDJsonForm } from "../../common/StorybookAntDJsonForm";
import { objectSchema, objectUISchemaWithName, objectUISchemaWithNameAndLastName, objectUISchemaWithRule } from "../../testSchemas/objectSchema";

const meta: Meta<typeof StorybookAntDJsonForm> = {
title: "Control/Object",
component: StorybookAntDJsonForm,
tags: ["autodocs"],
args: {
jsonSchema: objectSchema,
rendererRegistryEntries: [
...rendererRegistryEntries,
]
},
argTypes: {
rendererRegistryEntries: {},
jsonSchema: {
control: "object",
}
}
};

export default meta;
type Story = StoryObj<typeof StorybookAntDJsonForm>;

export const ObjectWithUISchemaContainingOnlyName: Story = {
tags: ["autodocs"],
args: {
jsonSchema: objectSchema,
uiSchema: objectUISchemaWithName,
},
}

export const ObjectWithUISchemaContainingBothNameAndLastName: Story = {
tags: ["autodocs"],
args: {
jsonSchema: objectSchema,
uiSchema: objectUISchemaWithNameAndLastName,
},
}

export const ObjectWithRuleHidingLastNameIfNameIsJohn: Story = {
tags: ["autodocs"],
args: {
jsonSchema: objectSchema,
uiSchema: objectUISchemaWithRule,
},
}
Loading

0 comments on commit f7c56a7

Please sign in to comment.