Skip to content

Commit bbdfe30

Browse files
committed
Update docs + make checkbox and radio group re-usable
1 parent b655eda commit bbdfe30

File tree

8 files changed

+181
-76
lines changed

8 files changed

+181
-76
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ The class model can be run from the command line, see [packages/class/README.md]
2323

2424
The class model can be used a package or library, see [packages/class/README.md](packages/class/README.md#package-usage) for more information.
2525

26-
The app use a form component has been generalized into a package, see [packages/form/README.md](packages/form/README.md) for more information.
26+
The app uses a form component that has been generalized into a package, see [packages/form/README.md](packages/form/README.md) for more information.
2727

2828
## Developers
2929

packages/class/scripts/json2ts.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,5 +131,5 @@ function main() {
131131
}
132132
}
133133

134-
// TODO temporary disabled as json-schema-to-typescript can not handle if/then/else
134+
// TODO disabled as json-schema-to-typescript can not handle if/then/else
135135
// main();

packages/form/README.md

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -63,40 +63,47 @@ export default {
6363

6464
The form expects a JSON schema of 2020-12 version. The schema is used to generate and validate the form.
6565

66-
Some additional keywords are allowed that are used specialize the form generation.
66+
Some additional keywords can be used to specialize the form generation.
6767

6868
### symbol keyword
6969

7070
The form label uses the value of the `title` key or property key.
71-
If you want an even shorter label you can add the `symbol` key with a string value.
71+
If you want an shorter label you can add the `symbol` key with a string value.
7272
An example value could be `β` for beta.
7373
When symbol is set the title will be displayed as a tooltip.
7474

7575
### unit keyword
7676

77-
A property can have a `unit` key with a string value. The value can for example
78-
`kg kg-1`. The unit will be displayed in the form.
77+
A property can have a `unit` key with a string value.
78+
An example value could be `kg kg-1`.
79+
The unit will be displayed in the form.
7980

8081
### ui:group keyword
8182

8283
The JSON schema must be flat, due to its use for form generation. There can not be a nested object in the schema.
83-
In the form generation use the `ui:group` key and any string value to group properties together.
84-
The group name will be displayed as a header in the form. The `ui:group` value should not be used across different `then` blocks.
84+
During the form generation, the `ui:group` key and its string value are used to group properties together.
85+
The group name will be displayed as a header in the form.
86+
The `ui:group` value should be the same for the properties in a `if` and `then` block.
8587

8688
### ui:widget keyword
8789

88-
Some property you would like to have a different input widget for. This widget can be chosen by setting the `ui:widget` key to a string value. Valid values are
90+
Some property you would like to have a different input widget for.
91+
This widget can be chosen by setting the `ui:widget` key to a string value.
92+
Valid values are
8993

90-
- `textarea` for a text area input
94+
- `textarea` for a text area input. Given property type is `string`.
9195

9296
### Supported types
9397

94-
The form generation can handle the following types:
98+
The form generation can handle the following property types:
9599

96100
- string
101+
- rendered as text input or
102+
- if has enum values then rendered as radio group
97103
- number
98-
- boolean
99-
- array of numbers: The form will display a text input that can be filled with a comma separated list of numbers.
104+
- integer
105+
- boolean: rendered as checkbox
106+
- array of numbers: rendered as text input that can be filled with a comma separated list of numbers
100107

101108
## API
102109

@@ -107,20 +114,20 @@ Properties of Form component:
107114
- `schema` - The JSON schema for the form. Must be version 2020-12.
108115
- `values` - Initial values for the form fields.
109116
- `onSubmit` - Callback function to handle form submission. Called with the form values.
110-
- `defaults` - Default values to overwrite in the schema.
111-
- `id` - The id of the form element. Can be used to submit form from a non-child element.
112-
- `children` - Child elements to render inside the form. Mostly used for submit button.
113-
- `uiComponents` - Custom UI components to use in the form.
117+
- `defaults` - Default values to overwrite in the schema. Optional.
118+
- `id` - The id of the form element. Can be used to submit form from a non-child element. Optional.
119+
- `children` - Child elements to render inside the form. Mostly used for submit button. Optional.
120+
- `uiComponents` - Custom UI components to use in the form. Optional.
114121

115122
The values, defaults and onSubmit argument are the same type.
116123

117124
## Development
118125

119-
To use in a monorepo as a dependency this package needs to be built first with
126+
To use this package in this monorepo, the package needs to be built first with
120127

121128
```bash
122129
pnpm build
123-
# or during development
130+
# or during development of the form package
124131
pnpm dev
125132
```
126133

packages/form/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="utf-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1" />
66
<meta name="theme-color" content="#000000" />
7-
<title>Solid App</title>
7+
<title>@classmodel/form package examples</title>
88
</head>
99
<body>
1010
<noscript>You need to enable JavaScript to run this app.</noscript>

packages/form/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@classmodel/form",
33
"version": "0.0.1",
4-
"description": "JSON schema to form with Solid UI components",
4+
"description": "Generate form from JSON schema with Solid UI components",
55
"type": "module",
66
"repository": {
77
"type": "git",

packages/form/src/App.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,28 @@ function GroupEnumToggle() {
312312
);
313313
}
314314

315+
function StringEnumExample() {
316+
const values = {
317+
s1: "a",
318+
};
319+
const schema: JSONSchemaType<typeof values> = {
320+
type: "object",
321+
properties: {
322+
s1: { type: "string", enum: ["a", "b", "c"] },
323+
},
324+
required: ["s1"],
325+
};
326+
return (
327+
<Form
328+
schema={schema}
329+
onSubmit={(data) => console.log(data)}
330+
values={values}
331+
>
332+
<Button type="submit">Submit</Button>
333+
</Form>
334+
);
335+
}
336+
315337
function NumberExample() {
316338
const values = {
317339
n1: 3.14,
@@ -358,6 +380,29 @@ function IntegerExample() {
358380
);
359381
}
360382

383+
function BooleanExample() {
384+
const values = {
385+
b1: true,
386+
};
387+
const schema: JSONSchemaType<typeof values> = {
388+
type: "object",
389+
properties: {
390+
b1: { type: "boolean" },
391+
},
392+
required: ["b1"],
393+
};
394+
return (
395+
<Form
396+
schema={schema}
397+
onSubmit={(data) => console.log(data)}
398+
values={values}
399+
>
400+
<Button type="submit">Submit</Button>
401+
<p>on submit returns boolean `true` if unchanged.</p>
402+
</Form>
403+
);
404+
}
405+
361406
function ArrayOfNumberExample() {
362407
const values = {
363408
nn1: [1, 2, 3],
@@ -459,12 +504,18 @@ const App: Component = () => {
459504
<ExampleWrapper legend="Group enum toggle">
460505
<GroupEnumToggle />
461506
</ExampleWrapper>
507+
<ExampleWrapper legend="String as radio group">
508+
<StringEnumExample />
509+
</ExampleWrapper>
462510
<ExampleWrapper legend="Number">
463511
<NumberExample />
464512
</ExampleWrapper>
465513
<ExampleWrapper legend="Integer">
466514
<IntegerExample />
467515
</ExampleWrapper>
516+
<ExampleWrapper legend="Boolean as checkbox">
517+
<BooleanExample />
518+
</ExampleWrapper>
468519
<ExampleWrapper legend="Array of number">
469520
<ArrayOfNumberExample />
470521
</ExampleWrapper>

packages/form/src/Form.tsx

Lines changed: 87 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const MyCheckbox: Component<{
6363
id?: string;
6464
checked: boolean;
6565
onChange: (checked: boolean) => void;
66+
disabled?: boolean;
6667
}> = Checkbox;
6768

6869
const DEFAULT_UI_COMPONENTS = {
@@ -293,12 +294,22 @@ const PropField: Component<PropFieldProps> = (props) => {
293294
<InputInteger item={props.item} disabled={props.disabled} />
294295
</Match>
295296
<Match when={props.item.schema.type === "string"}>
296-
<Show
297-
when={props.item.schema["ui:widget"] === "textarea"}
298-
fallback={<InputText item={props.item} disabled={props.disabled} />}
299-
>
300-
<TextAreaWidget item={props.item} disabled={props.disabled} />
301-
</Show>
297+
<Switch>
298+
{/* TODO when enum.length > 10 then use select/combobox */}
299+
<Match when={props.item.schema.enum}>
300+
{/* TODO allow user to overwrite used input with ui:widget */}
301+
<InputTextEnum item={props.item} disabled={props.disabled} />
302+
</Match>
303+
<Match when={props.item.schema["ui:widget"] === "textarea"}>
304+
<TextAreaWidget item={props.item} disabled={props.disabled} />
305+
</Match>
306+
<Match when={props.item.schema.type === "string"}>
307+
<InputText item={props.item} disabled={props.disabled} />
308+
</Match>
309+
</Switch>
310+
</Match>
311+
<Match when={props.item.schema.type === "boolean"}>
312+
<InputBoolean item={props.item} disabled={props.disabled} />
302313
</Match>
303314
<Match
304315
when={
@@ -513,6 +524,74 @@ const InputNumber: Component<PropFieldProps> = (props) => {
513524
);
514525
};
515526

527+
const InputBoolean: Component<PropFieldProps> = (props) => {
528+
const UiComponents = useFormContext().uiComponents;
529+
const id = createUniqueId();
530+
const checked = createMemo(
531+
() => useFormContext().values[props.item.key] as boolean,
532+
);
533+
const label = createLabel(props.item);
534+
const setProperty = useFormContext().setProperty;
535+
function onChange(checked: boolean) {
536+
setProperty(props.item.key, checked);
537+
// TODO when unchecked the checkedMembers should be removed/reset
538+
}
539+
return (
540+
<div class="flex items-center space-x-2">
541+
<div class="basis-1/2">
542+
<UiComponents.Label for={id}>{label()}</UiComponents.Label>
543+
<DescriptionTooltip schema={props.item.schema} />
544+
</div>
545+
<UiComponents.Checkbox
546+
id={id}
547+
checked={checked()}
548+
onChange={onChange}
549+
disabled={props.disabled}
550+
/>
551+
</div>
552+
);
553+
};
554+
555+
const InputTextEnum: Component<PropFieldProps> = (props) => {
556+
const UiComponents = useFormContext().uiComponents;
557+
const label = createLabel(props.item);
558+
const id = createUniqueId();
559+
const value = createMemo(
560+
() => useFormContext().values[props.item.key] as string,
561+
);
562+
const setProperty = useFormContext().setProperty;
563+
function onChange(value: string) {
564+
setProperty(props.item.key, value);
565+
// TODO choice members that are not in the new choice should be removed/reset
566+
}
567+
568+
return (
569+
<div class="flex items-center gap-2">
570+
<div class="basis-1/2">
571+
<UiComponents.Label for={id}>{label()}</UiComponents.Label>
572+
<DescriptionTooltip schema={props.item.schema} />
573+
</div>
574+
<UiComponents.RadioGroup
575+
id={id}
576+
value={value()}
577+
onChange={onChange}
578+
class="basis-1/2"
579+
disabled={props.disabled}
580+
>
581+
<For each={props.item.schema.enum}>
582+
{(choice) => (
583+
<UiComponents.RadioGroupItem value={choice}>
584+
<UiComponents.RadioGroupItemLabel>
585+
{choice}
586+
</UiComponents.RadioGroupItemLabel>
587+
</UiComponents.RadioGroupItem>
588+
)}
589+
</For>
590+
</UiComponents.RadioGroup>
591+
</div>
592+
);
593+
};
594+
516595
function string2numbers(value: string): number[] {
517596
return value
518597
.split(",")
@@ -570,31 +649,13 @@ const InputNumbers: Component<PropFieldProps> = (props) => {
570649
};
571650

572651
function BooleanChoicesField(props: { item: Choices }) {
573-
const UiComponents = useFormContext().uiComponents;
574-
const id = createUniqueId();
575652
const checked = createMemo(
576653
() => useFormContext().values[props.item.key] as boolean,
577654
);
578-
const label = createLabel(props.item);
579655
const checkedMembers = createMemo(() => props.item.choices[0].members);
580-
const setProperty = useFormContext().setProperty;
581-
function onChange(checked: boolean) {
582-
setProperty(props.item.key, checked);
583-
// TODO when unchecked the checkedMembers should be removed/reset
584-
}
585656
return (
586657
<>
587-
<div class="flex items-center space-x-2">
588-
<div class="basis-1/2">
589-
<UiComponents.Label for={id}>{label()}</UiComponents.Label>
590-
<DescriptionTooltip schema={props.item.schema} />
591-
</div>
592-
<UiComponents.Checkbox
593-
id={id}
594-
checked={checked()}
595-
onChange={onChange}
596-
/>
597-
</div>
658+
<InputBoolean item={props.item} />
598659
<For each={checkedMembers()}>
599660
{(item) => <ItemField item={item} disabled={!checked()} />}
600661
</For>
@@ -603,42 +664,13 @@ function BooleanChoicesField(props: { item: Choices }) {
603664
}
604665

605666
function StringChoicesField(props: { item: Choices }) {
606-
const UiComponents = useFormContext().uiComponents;
607-
const id = createUniqueId();
608667
const value = createMemo(
609668
() => useFormContext().values[props.item.key] as string,
610669
);
611-
const setProperty = useFormContext().setProperty;
612-
function onChange(value: string) {
613-
setProperty(props.item.key, value);
614-
// TODO choice members that are not in the new choice should be removed/reset
615-
}
616670

617-
const label = createLabel(props.item);
618671
return (
619672
<>
620-
<div class="flex items-center gap-2">
621-
<div class="basis-1/2">
622-
<UiComponents.Label for={id}>{label()}</UiComponents.Label>
623-
<DescriptionTooltip schema={props.item.schema} />
624-
</div>
625-
<UiComponents.RadioGroup
626-
id={id}
627-
value={value()}
628-
onChange={onChange}
629-
class="basis-1/2"
630-
>
631-
<For each={props.item.schema.enum}>
632-
{(choice) => (
633-
<UiComponents.RadioGroupItem value={choice}>
634-
<UiComponents.RadioGroupItemLabel>
635-
{choice}
636-
</UiComponents.RadioGroupItemLabel>
637-
</UiComponents.RadioGroupItem>
638-
)}
639-
</For>
640-
</UiComponents.RadioGroup>
641-
</div>
673+
<InputTextEnum item={props.item} />
642674
<div>
643675
<For each={props.item.choices}>
644676
{(choice) => (

0 commit comments

Comments
 (0)