@nolwenture/formik-jsonschema-form 0.10.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +133 -30
  2. package/dist/Form.js +3 -0
  3. package/dist/Form.stories.d.ts +1 -0
  4. package/dist/Form.stories.js +7 -3
  5. package/dist/fields/ArrayField.js +7 -3
  6. package/dist/fields/BooleanField.js +3 -3
  7. package/dist/fields/ObjectField.js +6 -3
  8. package/dist/fields/SchemaField.js +4 -4
  9. package/dist/fields/StringField.js +2 -3
  10. package/dist/fields/stories/ArrayField.stories.d.ts +1 -0
  11. package/dist/fields/stories/ArrayField.stories.js +33 -0
  12. package/dist/fields/stories/MultipleChoiceField.stories.d.ts +6 -0
  13. package/dist/fields/stories/MultipleChoiceField.stories.js +86 -0
  14. package/dist/fields/stories/ObjectField.stories.js +20 -2
  15. package/dist/fields/stories/SearchMultiTagSelectField.stories.js +1 -1
  16. package/dist/sampleData/collapsibleSchema.d.ts +3 -0
  17. package/dist/sampleData/collapsibleSchema.js +27 -0
  18. package/dist/sampleData/completeSchema.js +2 -2
  19. package/dist/sampleData/index.d.ts +3 -0
  20. package/dist/sampleData/index.js +3 -0
  21. package/dist/sampleData/multipleChoiceSchema.js +3 -3
  22. package/dist/sampleData/refDefSchema.js +78 -9
  23. package/dist/styledComponents.d.ts +8 -1
  24. package/dist/styledComponents.js +27 -1
  25. package/dist/templates/CollapsibleObjectFieldTemplate.d.ts +3 -0
  26. package/dist/templates/CollapsibleObjectFieldTemplate.js +20 -0
  27. package/dist/templates/index.d.ts +1 -0
  28. package/dist/templates/index.js +1 -0
  29. package/dist/types.d.ts +27 -1
  30. package/dist/utils/__tests/solveReferences.test.js +18 -4
  31. package/dist/utils/__tests/testSchemas.d.ts +2 -1
  32. package/dist/utils/__tests/testSchemas.js +9 -0
  33. package/dist/utils/createInitialValues.js +2 -2
  34. package/dist/utils/solveReferences.d.ts +10 -2
  35. package/dist/utils/solveReferences.js +8 -5
  36. package/package.json +1 -1
package/README.md CHANGED
@@ -365,6 +365,10 @@ export function MyFormComponent() {
365
365
  Enum fields are fields with multiple options to either choose `oneOf` or `anyOf` from.
366
366
  There is an alternative field implementation for "anyOf" that when given `"ui:field": "SearchMultiTagSelectField"` results in a searchable multi tag select field.
367
367
 
368
+ The type of the field holding the enum is defined by its return value. `oneOf` enums will only return a single value and therefore, the type of the returned value is used as the field type (in the following example, "number" is the correct field type).
369
+
370
+ `anyOf`enums will return the value(s) inside an array, even if only one option was selected. Therefore, the correct field type for the field holding the `anyOf`-enum is "array".
371
+
368
372
  ```tsx
369
373
  const schema = {
370
374
  properties: {
@@ -384,7 +388,7 @@ const schema = {
384
388
  ],
385
389
  },
386
390
  enumAnyOf: {
387
- type: "number",
391
+ type: "array",
388
392
  title: "Enum anyOf (non-standard)",
389
393
  anyOf: [
390
394
  { const: 1, title: "one" },
@@ -394,7 +398,7 @@ const schema = {
394
398
  ],
395
399
  },
396
400
  enumTagSelectAnyOf: {
397
- type: "string",
401
+ type: "array",
398
402
  title: "Enum searchable tag select anyOf",
399
403
  anyOf: [
400
404
  { const: 1, title: "one" },
@@ -420,7 +424,7 @@ const uiSchema = {
420
424
  ### Alternative fields
421
425
 
422
426
  There are alternative fields available for certain types.
423
- For `type: "string"` there is `DateField`, `MultipleChoiceField` and `TextAreaField`.
427
+ For `type: "string"` there is `DateField`, `TextAreaField` and partly `MultipleChoiceField`.
424
428
 
425
429
  `DateField` has a "dd.mm.yyyy" date input field and a calendar date picker and will save the date input as "yyyy-mm-dd" string.
426
430
 
@@ -436,7 +440,28 @@ const uiSchema = {
436
440
  };
437
441
  ```
438
442
 
439
- `MultipleChoiceField` takes an array of `oneOf` or `anyOf`. The choices provided inside an `oneOf`array will be presented on the UI as Radio button selectables and only one of the given values can be selected. The choices provided inside an `anyOf` array will be presented on the UI as Checkbox selectables and multiple values can be selected.
443
+ `TextAreaField` gives more space for a longer string input.
444
+
445
+ ```tsx
446
+ const schema = {
447
+ properties: {
448
+ story: { type: "string", title: "A short story" },
449
+ },
450
+ };
451
+
452
+ const uiSchema = {
453
+ story: { "ui:field": "TextAreaField" },
454
+ };
455
+ ```
456
+
457
+ `MultipleChoiceField` takes an array of `oneOf` or `anyOf`.
458
+ If you are creating a schema using `oneOf`, the type of the field is the value returned by the chosen enum option (e.g. "string" or "number").
459
+ If you are creating a schema using `anyOf`, the type of the field is "array", since the returned value will be in an array format, even if only one option is selected.
460
+
461
+ The choices provided inside an `oneOf`array will be presented on the UI as Radio button selectables and only one of the given values can be selected.
462
+
463
+ The choices provided inside an `anyOf` array will be presented on the UI as Checkbox selectables and multiple values can be selected.
464
+
440
465
  `MultipleChoiceField` comes with a small number of predefined styles, which are `inline`, `oneColumn`, `twoColumns` and `threeColumns`. When no style is given, the choice options are listed in a row under the title.
441
466
 
442
467
  ```tsx
@@ -452,6 +477,19 @@ const schema = {
452
477
  { const: "nope", title: "Please don't even ask..." },
453
478
  ],
454
479
  },
480
+ food: {
481
+ type: "array",
482
+ title: "What would you like to eat today? (style: oneColumn)",
483
+ anyOf: [
484
+ { const: "breakfast", title: "Breakfast" },
485
+ { const: "secondBreakfast", title: "What about second breakfast?" },
486
+ { const: "elevenses", title: "Elevenses" },
487
+ { const: "luncheon", title: "Luncheon" },
488
+ { const: "afternoonTea", title: "Afternoon tea" },
489
+ { const: "dinner", title: "Dinner" },
490
+ { const: "supper", title: "Supper" },
491
+ ],
492
+ },
455
493
  },
456
494
  };
457
495
 
@@ -462,21 +500,13 @@ const uiSchema = {
462
500
  style: "twoColumns",
463
501
  },
464
502
  },
465
- };
466
- ```
467
-
468
- `TextAreaField` gives more space for a longer string input.
469
-
470
- ```tsx
471
- const schema = {
472
- properties: {
473
- story: { type: "string", title: "A short story" },
503
+ food: {
504
+ "ui:field": "MultipleChoiceField",
505
+ "ui:options": {
506
+ style: "oneColumn",
507
+ },
474
508
  },
475
509
  };
476
-
477
- const uiSchema = {
478
- story: { "ui:field": "TextAreaField" },
479
- };
480
510
  ```
481
511
 
482
512
  For `type: "boolean"` there is `RadioField` and `ToggleField`.
@@ -518,12 +548,33 @@ RadioButtonField.styles = {
518
548
  };
519
549
  ```
520
550
 
551
+ ### Collapsible object field
552
+
553
+ Fields of `type: "object"` can be made collapsible via the respective uiSchema setting.
554
+
555
+ ```tsx
556
+ const collapsibleSchema: Schema = {
557
+ title: "FORM WITH COLLAPSIBLE OBJECT",
558
+ properties: {
559
+ colObject: {
560
+ type: "object",
561
+ title: "Collapsible Object",
562
+ properties: { summer: { type: "string" }, winter: { type: "string" } },
563
+ },
564
+ },
565
+ };
566
+
567
+ const collapsibleUiSchema: UiSchema = {
568
+ colObject: { props: { isCollapsible: true } },
569
+ };
570
+ ```
571
+
521
572
  ### Conditional fields
522
573
 
523
574
  Conditional fields depending on a parent field input can be given with an `if-then-else` logic inside the schema making use of `allOf`. While `if`and `then` are required to make the condition work, `else` is optional.
524
575
 
525
576
  ```tsx
526
- export const conditionsSchema = {
577
+ const conditionsSchema = {
527
578
  type: "object",
528
579
  properties: {
529
580
  animal: {
@@ -598,7 +649,7 @@ Dependencies can either be triggered by the conditional-giving property being pr
598
649
  To have value-dependent conditionals, the `dependencies`-object needs to have an `oneOf`-array listing the possible conditional-giving values matching the enum-values of the conditional-giving field. Each case in in the `oneOf` array can either have no conditional fields, then the only key-value pair given inside `properties` is the matching conditional (see "no type"), or an unlimited amount of conditional fields listed after the matching conditional.
599
650
 
600
651
  ```tsx
601
- export const refDefSchema: Schema = {
652
+ const dependenciesSchema: Schema = {
602
653
  type: "object",
603
654
  properties: {
604
655
  creditCard: {
@@ -689,29 +740,81 @@ Using `if-else-then` or `dependencies` depends mostly on the use case. For recur
689
740
  Schemas can be referenced. A referenced schema can be given to a property using `$ref` keyword. The definition for the referenced schema needs to be inside `definitions` object inside the main schema. The value-string given to $ref-key must always look like this:
690
741
  `'#/definitions/{addMatchingKeyHere}'`
691
742
 
743
+ In order to give uiSchema to reused or recursive fields defined in `schema.definitons`, the uiSchema needs to be given to the matching prop of `definitions`-object. This way, the given uiProps are added to the referencing field, no matter how deeply nested it is inside the schema.
744
+
692
745
  ```tsx
693
- const ageFieldDefinition: Schema = {
694
- type: "number",
695
- title: "Age",
746
+ const familyObjectField: Schema = {
747
+ type: "object",
748
+ title: "My family member",
749
+ properties: {
750
+ relationship: { type: "string", title: "Relationship status" },
751
+ names: { $ref: "#/definitions/names" },
752
+ },
696
753
  };
697
754
 
698
- export const refDefSchema: Schema = {
755
+ const refDefSchema: Schema = {
699
756
  title: "References and Definitions",
700
757
  type: "object",
701
758
  properties: {
702
- name: {
703
- $ref: "#/definitions/name",
759
+ names: {
760
+ $ref: "#/definitions/names",
761
+ },
762
+ birthDate: {
763
+ $ref: "#/definitions/birthDate",
704
764
  },
705
- age: {
706
- $ref: "#/definitions/age",
765
+ family: {
766
+ type: "object",
767
+ title: "Family",
768
+ properties: {
769
+ mother: {
770
+ type: "array",
771
+ title: "Maternal family",
772
+ additionalItems: familyObjectField,
773
+ },
774
+ father: {
775
+ type: "array",
776
+ title: "Paternal family",
777
+ additionalItems: familyObjectField,
778
+ },
779
+ },
707
780
  },
708
781
  },
782
+
709
783
  definitions: {
710
- name: {
711
- type: "string",
784
+ names: {
785
+ type: "object",
712
786
  title: "Name",
787
+ description:
788
+ "The schema for this object comes from the 'definitions' object inside the main schema",
789
+ properties: {
790
+ firstName: { type: "string", title: "First name" },
791
+ middleNames: {
792
+ type: "array",
793
+ title: "Middle names",
794
+ additionalItems: { $ref: "#/definitions/middleName" },
795
+ },
796
+ lastName: { type: "string", title: "Last name" },
797
+ },
798
+ },
799
+ birthDate: { type: "string", title: "When is your birthday?" },
800
+ middleName: { type: "string", title: " Middle name" },
801
+ },
802
+ };
803
+
804
+ const refDefUiSchema: UiSchema = {
805
+ definitions: {
806
+ birthDate: { "ui:field": "DateField" },
807
+ names: {
808
+ history: { "ui:field": "TextAreaField" },
809
+ middleNames: { "ui:info": { content: "You can leave those blank" } },
810
+ },
811
+ middleName: {
812
+ props: {
813
+ title: {
814
+ hidden: true,
815
+ },
816
+ },
713
817
  },
714
- age: ageFieldDefinition,
715
818
  },
716
819
  };
717
820
  ```
package/dist/Form.js CHANGED
@@ -10,6 +10,7 @@ export const FormRegistry = React.createContext({
10
10
  templates: defaultTemplates,
11
11
  components: defaultComponents,
12
12
  definitions: {},
13
+ uiSchemaDefinitions: {},
13
14
  formContext: {},
14
15
  useItemsAsAdditionalItems: false,
15
16
  hideUnmarkedAdditionalProperties: false,
@@ -31,11 +32,13 @@ export const Form = (props) => {
31
32
  const { isSubmitting } = useFormikContext();
32
33
  const { schema, uiSchema = {}, className, children, fields = {}, templates = {}, components = {}, formContext = {}, useItemsAsAdditionalItems = false, hideUnmarkedAdditionalProperties = false, } = props;
33
34
  const schemaDefinitions = schema.definitions;
35
+ const uiSchemaDefinitions = uiSchema.definitions;
34
36
  const registry = {
35
37
  fields: Object.assign(Object.assign({}, defaultFields), fields),
36
38
  templates: Object.assign(Object.assign({}, defaultTemplates), templates),
37
39
  components: Object.assign(Object.assign({}, defaultComponents), components),
38
40
  definitions: schemaDefinitions,
41
+ uiSchemaDefinitions,
39
42
  formContext,
40
43
  useItemsAsAdditionalItems,
41
44
  hideUnmarkedAdditionalProperties,
@@ -13,3 +13,4 @@ export declare const WithRequiredInput: Story;
13
13
  export declare const WithConditions: Story;
14
14
  export declare const WithRefAndDefinitions: Story;
15
15
  export declare const MultipleChoice: Story;
16
+ export declare const Collapsible: Story;
@@ -23,10 +23,8 @@ import { action } from "@storybook/addon-actions";
23
23
  import styled from "styled-components";
24
24
  import { Formik, useFormikContext } from "formik";
25
25
  import { InfoFieldButton, SearchableMultiTagSelectField, } from "./styledComponents";
26
- import { completeSchema, completeUiSchema, simpleSchema, simpleUiSchema, generatedSchema, requiredInputSchema, requiredInputUiSchema, conditionsSchema, conditionsUiSchema, } from "./sampleData";
26
+ import { completeSchema, completeUiSchema, simpleSchema, simpleUiSchema, generatedSchema, requiredInputSchema, requiredInputUiSchema, conditionsSchema, conditionsUiSchema, refDefSchema, refDefUiSchema, multipleChoiceSchema, multipleChoiceUiSchema, collapsibleSchema, collapsibleUiSchema, } from "./sampleData";
27
27
  import { Form, styledComponents } from "./";
28
- import { refDefSchema, refDefUiSchema } from "./sampleData/refDefSchema";
29
- import { multipleChoiceSchema, multipleChoiceUiSchema, } from "./sampleData/multipleChoiceSchema";
30
28
  const meta = {
31
29
  title: "Form",
32
30
  component: Form,
@@ -198,3 +196,9 @@ export const MultipleChoice = {
198
196
  uiSchema: multipleChoiceUiSchema,
199
197
  },
200
198
  };
199
+ export const Collapsible = {
200
+ args: {
201
+ schema: collapsibleSchema,
202
+ uiSchema: collapsibleUiSchema,
203
+ },
204
+ };
@@ -4,7 +4,9 @@ import { useField } from "formik";
4
4
  import { FormRegistry } from "../Form";
5
5
  import { ArrayFieldButtonPurpose } from "../types";
6
6
  import { useExtractUiSchema, useSetDefault } from "../hooks";
7
- import { GetArrayFieldButton } from "../styledComponents";
7
+ import { FieldInput, GetArrayFieldButton } from "../styledComponents";
8
+ import { FieldTemplate } from "../templates";
9
+ import { getEnums } from "../utils";
8
10
  const appendArray = (values) => {
9
11
  return values ? [...values, ""] : [""];
10
12
  };
@@ -33,6 +35,7 @@ export const ArrayField = (props) => {
33
35
  const { fields: { SchemaField }, templates: { ArrayFieldTemplate }, components: { ArrayFieldItem, ArrayFieldList, ArrayFieldItemButtons }, useItemsAsAdditionalItems, } = useContext(FormRegistry);
34
36
  useSetDefault(props);
35
37
  const { name, schema, uiSchema } = props;
38
+ const { type, props: inputProps } = useExtractUiSchema(uiSchema);
36
39
  const [{ value }, _, { setValue }] = useField(name);
37
40
  const { items, additionalItems, } = useMemo(() => {
38
41
  let items = schema.items && typeof schema.items !== "boolean" ? schema.items : [];
@@ -51,6 +54,7 @@ export const ArrayField = (props) => {
51
54
  : undefined,
52
55
  };
53
56
  }, [schema]);
57
+ const enums = useMemo(() => getEnums(props), [props]);
54
58
  //This whole mess would give backwards compatibility, but maybe we don't do it
55
59
  // const {
56
60
  // items,
@@ -91,10 +95,10 @@ export const ArrayField = (props) => {
91
95
  // },
92
96
  // [schema]
93
97
  // );
94
- // Bacwards compatibility ends
98
+ // Backwards compatibility ends
95
99
  const { disabled } = useExtractUiSchema(uiSchema);
96
100
  const valueArray = Array.isArray(value) ? value : undefined;
97
- return (_jsxs(ArrayFieldTemplate, Object.assign({}, props, { children: [_jsxs(ArrayFieldList, { children: [(items || []).map((itemSchema, index) => (_jsx(ArrayFieldItem, { children: _jsx(SchemaField, { name: `${name}[${index}]`, schema: typeof itemSchema !== "boolean" ? itemSchema : {}, uiSchema: (uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema[index]) || (uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema.items), forceDefaultValue: true }) }, index))), ((valueArray === null || valueArray === void 0 ? void 0 : valueArray.slice(items.length)) || []).map((_, index) => (_jsxs(ArrayFieldItem, { children: [_jsx(SchemaField, { name: `${name}[${index + items.length}]`, schema: additionalItems && typeof additionalItems !== "boolean"
101
+ return schema.anyOf !== undefined ? (_jsx(FieldTemplate, Object.assign({}, props, { children: _jsx(FieldInput, Object.assign({ type }, inputProps, { name: name, formikAs: "select", multiple: (enums === null || enums === void 0 ? void 0 : enums.type) === "multiSelect", children: enums === null || enums === void 0 ? void 0 : enums.values.map((option, index) => (_jsx("option", { value: option.const, children: option.title }, index))) })) }))) : (_jsxs(ArrayFieldTemplate, Object.assign({}, props, { children: [_jsxs(ArrayFieldList, { children: [(items || []).map((itemSchema, index) => (_jsx(ArrayFieldItem, { children: _jsx(SchemaField, { name: `${name}[${index}]`, schema: typeof itemSchema !== "boolean" ? itemSchema : {}, uiSchema: (uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema[index]) || (uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema.items), forceDefaultValue: true }) }, index))), ((valueArray === null || valueArray === void 0 ? void 0 : valueArray.slice(items.length)) || []).map((_, index) => (_jsxs(ArrayFieldItem, { children: [_jsx(SchemaField, { name: `${name}[${index + items.length}]`, schema: additionalItems && typeof additionalItems !== "boolean"
98
102
  ? additionalItems
99
103
  : {}, uiSchema: Object.assign({ disabled }, ((uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema[index + items.length]) || (uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema.items))) }), _jsxs(ArrayFieldItemButtons, { children: [GetArrayFieldButton({
100
104
  purpose: ArrayFieldButtonPurpose.REMOVE,
@@ -1,11 +1,11 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useContext } from "react";
3
3
  import { FormRegistry } from "../Form";
4
4
  import { useExtractUiSchema, useSetDefault } from "../hooks";
5
5
  export const BooleanField = (props) => {
6
- const { templates: { FieldTemplate }, components: { FieldInput }, } = useContext(FormRegistry);
6
+ const { templates: { FieldTemplate }, components: { FieldInput, BooleanFieldWrapper }, } = useContext(FormRegistry);
7
7
  useSetDefault(props);
8
8
  const { name, uiSchema } = props;
9
9
  const { label, props: inputProps } = useExtractUiSchema(uiSchema);
10
- return (_jsx(FieldTemplate, Object.assign({}, props, { children: _jsxs(_Fragment, { children: [_jsx(FieldInput, Object.assign({}, inputProps, { name: name, type: "checkbox" })), label] }) })));
10
+ return (_jsx(FieldTemplate, Object.assign({}, props, { children: _jsxs(BooleanFieldWrapper, { children: [_jsx(FieldInput, Object.assign({}, inputProps, { name: name, type: "checkbox" })), label] }) })));
11
11
  };
@@ -26,8 +26,8 @@ const useObjectField = (name) => {
26
26
  return { value, setValue };
27
27
  };
28
28
  export const ObjectField = (props) => {
29
- var _a;
30
- const { fields: { SchemaField }, templates: { ObjectFieldTemplate }, components: { ObjectFieldItem, ObjectFieldItemButtons }, hideUnmarkedAdditionalProperties, } = useContext(FormRegistry);
29
+ var _a, _b, _c;
30
+ const { fields: { SchemaField }, templates: { ObjectFieldTemplate, CollapsibleObjectFieldTemplate }, components: { ObjectFieldItem, ObjectFieldItemButtons }, hideUnmarkedAdditionalProperties, } = useContext(FormRegistry);
31
31
  useSetDefault(props);
32
32
  const { name, schema, uiSchema } = props;
33
33
  const { value, setValue } = useObjectField(name);
@@ -45,7 +45,10 @@ export const ObjectField = (props) => {
45
45
  }, [valuesToRemove]);
46
46
  const propertyKeys = useMemo(() => Object.keys(properties || {}), [properties]);
47
47
  const additionalValues = useMemo(() => Object.fromEntries(Object.entries(value || {}).filter(([key, value]) => !propertyKeys.includes(key))), [value, propertyKeys]);
48
- return (_jsxs(ObjectFieldTemplate, Object.assign({}, props, { children: [((_a = uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema["ui:order"]) !== null && _a !== void 0 ? _a : Object.keys(properties || {}))
48
+ const FieldTemplateComponent = ((_b = (_a = props.uiSchema) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.isCollapsible)
49
+ ? CollapsibleObjectFieldTemplate
50
+ : ObjectFieldTemplate;
51
+ return (_jsxs(FieldTemplateComponent, Object.assign({}, props, { children: [((_c = uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema["ui:order"]) !== null && _c !== void 0 ? _c : Object.keys(properties || {}))
49
52
  .map((childName) => {
50
53
  const childSchema = properties === null || properties === void 0 ? void 0 : properties[childName];
51
54
  if (!childSchema)
@@ -23,9 +23,9 @@ const useFieldComponent = (schema, uiSchema, fields) => {
23
23
  };
24
24
  export const SchemaField = (props) => {
25
25
  const { schema, uiSchema } = props;
26
- const { fields, definitions } = useContext(FormRegistry);
27
- const schemaSolvedForReferences = useMemo(() => solveReferences(schema, definitions), [schema, definitions]);
28
- const FieldComponent = useFieldComponent(schemaSolvedForReferences, uiSchema, fields);
29
- const newProps = Object.assign(Object.assign({}, props), { schema: schemaSolvedForReferences });
26
+ const { fields, definitions, uiSchemaDefinitions } = useContext(FormRegistry);
27
+ const schemaSolvedForReferences = useMemo(() => solveReferences(schema, definitions, uiSchema, uiSchemaDefinitions), [schema, definitions]);
28
+ const FieldComponent = useFieldComponent(schemaSolvedForReferences.schema, schemaSolvedForReferences.uiSchema, fields);
29
+ const newProps = Object.assign(Object.assign({}, props), { schema: schemaSolvedForReferences.schema, uiSchema: schemaSolvedForReferences.uiSchema });
30
30
  return _jsx(FieldComponent, Object.assign({}, newProps));
31
31
  };
@@ -12,9 +12,8 @@ export const StringField = (props) => {
12
12
  const enums = useMemo(() => getEnums(props), [props]);
13
13
  switch (enums === null || enums === void 0 ? void 0 : enums.type) {
14
14
  case "select":
15
- case "multiSelect":
16
- return (_jsx(FieldTemplate, Object.assign({}, props, { children: _jsxs(FieldInput, Object.assign({ type }, inputProps, { name: name, formikAs: "select", multiple: enums.type === "multiSelect", children: [enums.type !== "multiSelect" && (_jsx("option", Object.assign({ value: "", disabled: true,
17
- hidden: true }, (_a = uiSchema["ui:placeholderOptionOptions"]) === null || _a === void 0 ? void 0 : _a.props, { children: placeholder }))), enums === null || enums === void 0 ? void 0 : enums.values.map((option, index) => (_jsx("option", { value: option.const, children: option.title }, index)))] })) })));
15
+ return (_jsx(FieldTemplate, Object.assign({}, props, { children: _jsxs(FieldInput, Object.assign({ type }, inputProps, { name: name, formikAs: "select", children: [_jsx("option", Object.assign({ value: "", disabled: true,
16
+ hidden: true }, (_a = uiSchema["ui:placeholderOptionOptions"]) === null || _a === void 0 ? void 0 : _a.props, { children: placeholder })), enums === null || enums === void 0 ? void 0 : enums.values.map((option, index) => (_jsx("option", { value: option.const, children: option.title }, index)))] })) })));
18
17
  default:
19
18
  return (_jsx(FieldTemplate, Object.assign({}, props, { children: _jsx(FieldInput, Object.assign({ type,
20
19
  placeholder }, inputProps, { name: name })) })));
@@ -5,3 +5,4 @@ export default _default;
5
5
  export declare const BasicArray: StoryObj<typeof ArrayField>;
6
6
  export declare const EnumArray: StoryObj<typeof ArrayField>;
7
7
  export declare const ObjectArray: StoryObj<typeof ArrayField>;
8
+ export declare const AnyOfArray: StoryObj<typeof ArrayField>;
@@ -189,3 +189,36 @@ export const ObjectArray = {
189
189
  expect(allLabelsAfterRemove.length).toEqual(0);
190
190
  }),
191
191
  };
192
+ export const AnyOfArray = {
193
+ args: {
194
+ name: "anyOf Select",
195
+ schema: {
196
+ type: "array",
197
+ title: "Array with anyOf-enum",
198
+ description: "Array description",
199
+ anyOf: [
200
+ { const: 1, title: "one" },
201
+ { const: 2, title: "two" },
202
+ { const: 3, title: "three" },
203
+ { const: 4, title: "four" },
204
+ ],
205
+ },
206
+ uiSchema: {},
207
+ },
208
+ play: (_d) => __awaiter(void 0, [_d], void 0, function* ({ canvasElement }) {
209
+ const anyOfSelectField = canvasElement.querySelector('select[name="anyOf Select"]');
210
+ const optionOne = canvasElement.querySelector('option[value="1"]');
211
+ const optionTwo = canvasElement.querySelector('option[value="2"]');
212
+ const optionThree = canvasElement.querySelector('option[value="3"]');
213
+ const optionFour = canvasElement.querySelector('option[value="4"]');
214
+ expect(anyOfSelectField).toBeInTheDocument();
215
+ expect(optionOne).toBeInTheDocument();
216
+ expect(optionTwo).toBeInTheDocument();
217
+ expect(optionThree).toBeInTheDocument();
218
+ expect(optionFour).toBeInTheDocument();
219
+ yield userEvent.selectOptions(anyOfSelectField, optionOne);
220
+ yield userEvent.selectOptions(anyOfSelectField, optionThree);
221
+ yield userEvent.click(optionThree);
222
+ yield expect(anyOfSelectField).toHaveValue(["1", "3"]);
223
+ }),
224
+ };
@@ -0,0 +1,6 @@
1
+ import { StoryObj } from "@storybook/react";
2
+ import { MultipleChoiceField } from "../MultipleChoiceField";
3
+ declare const _default: import("@storybook/types").ComponentAnnotations<import("@storybook/react").ReactRenderer, import("../../types").FieldProps>;
4
+ export default _default;
5
+ export declare const OneOf: StoryObj<typeof MultipleChoiceField>;
6
+ export declare const AnyOf: StoryObj<typeof MultipleChoiceField>;
@@ -0,0 +1,86 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { jsx as _jsx } from "react/jsx-runtime";
11
+ import { userEvent, expect } from "@storybook/test";
12
+ import { Formik, Form } from "formik";
13
+ import { MultipleChoiceField } from "../MultipleChoiceField";
14
+ export default {
15
+ title: "Fields/MultipleChoiceField",
16
+ component: MultipleChoiceField,
17
+ decorators: [
18
+ (Story) => (_jsx(Formik, { initialValues: { choicesAnyOf: [], choicesOneOf: "" }, onSubmit: (values) => console.log(values), children: _jsx(Form, { children: Story() }) })),
19
+ ],
20
+ };
21
+ export const OneOf = {
22
+ args: {
23
+ name: "choicesOneOf",
24
+ schema: {
25
+ type: "string",
26
+ oneOf: [
27
+ { const: "sunny", title: "Sunny" },
28
+ { const: "cloudy", title: "Cloudy" },
29
+ { const: "rainy", title: "Rainy" },
30
+ { const: "windy", title: "Windy" },
31
+ ],
32
+ },
33
+ uiSchema: {
34
+ "ui:field": "MultipleChoiceField",
35
+ },
36
+ },
37
+ play: (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
38
+ const optionSunny = canvasElement.querySelector('input[type="radio"][name="choicesOneOf"][value="sunny"]');
39
+ const optionCloudy = canvasElement.querySelector('input[type="radio"][name="choicesOneOf"][value="cloudy"]');
40
+ const optionRainy = canvasElement.querySelector('input[type="radio"][name="choicesOneOf"][value="rainy"]');
41
+ const optionWindy = canvasElement.querySelector('input[type="radio"][name="choicesOneOf"][value="windy"]');
42
+ yield userEvent.click(optionSunny);
43
+ yield expect(optionSunny).toBeChecked();
44
+ yield expect(optionCloudy).not.toBeChecked();
45
+ yield expect(optionRainy).not.toBeChecked();
46
+ yield expect(optionWindy).not.toBeChecked();
47
+ yield userEvent.click(optionCloudy);
48
+ yield expect(optionCloudy).toBeChecked();
49
+ yield expect(optionSunny).not.toBeChecked();
50
+ yield expect(optionRainy).not.toBeChecked();
51
+ yield expect(optionWindy).not.toBeChecked();
52
+ }),
53
+ };
54
+ export const AnyOf = {
55
+ args: {
56
+ name: "choicesAnyOf",
57
+ schema: {
58
+ type: "array",
59
+ anyOf: [
60
+ { const: "lily", title: "Lily" },
61
+ { const: "rose", title: "Rose" },
62
+ { const: "iris", title: "Iris" },
63
+ { const: "daisy", title: "Daisy" },
64
+ ],
65
+ },
66
+ uiSchema: {
67
+ "ui:field": "MultipleChoiceField",
68
+ },
69
+ },
70
+ play: (_b) => __awaiter(void 0, [_b], void 0, function* ({ canvasElement }) {
71
+ const optionLily = canvasElement.querySelector('input[type="checkbox"][name="choicesAnyOf"][value="lily"]');
72
+ const optionRose = canvasElement.querySelector('input[type="checkbox"][name="choicesAnyOf"][value="rose"]');
73
+ const optionIris = canvasElement.querySelector('input[type="checkbox"][name="choicesAnyOf"][value="iris"]');
74
+ const optionDaisy = canvasElement.querySelector('input[type="checkbox"][name="choicesAnyOf"][value="daisy"]');
75
+ yield userEvent.click(optionIris);
76
+ yield expect(optionIris).toBeChecked();
77
+ yield expect(optionRose).not.toBeChecked();
78
+ yield expect(optionLily).not.toBeChecked();
79
+ yield expect(optionDaisy).not.toBeChecked();
80
+ yield userEvent.click(optionRose);
81
+ yield expect(optionIris).toBeChecked();
82
+ yield expect(optionRose).toBeChecked();
83
+ yield expect(optionLily).not.toBeChecked();
84
+ yield expect(optionDaisy).not.toBeChecked();
85
+ }),
86
+ };
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { jsx as _jsx } from "react/jsx-runtime";
11
- import { userEvent, expect } from "@storybook/test";
11
+ import { within, userEvent, expect, waitFor } from "@storybook/test";
12
12
  import { Formik, Form } from "formik";
13
13
  import { ObjectField } from "./../ObjectField";
14
14
  export default {
@@ -55,22 +55,40 @@ export const Default = {
55
55
  title: "nullField",
56
56
  description: "Null description",
57
57
  },
58
+ collapsibleObject: {
59
+ type: "object",
60
+ title: "nestedCollapsibleObject",
61
+ properties: {
62
+ insideCollapsibleObject: { type: "string" },
63
+ alsoInsideCollapsibleObject: { type: "number" },
64
+ },
65
+ },
58
66
  },
59
67
  },
60
- uiSchema: {},
68
+ uiSchema: { collapsibleObject: { props: { isCollapsible: true } } },
61
69
  },
62
70
  play: (_a) => __awaiter(void 0, [_a], void 0, function* ({ canvasElement }) {
71
+ const canvas = within(canvasElement);
63
72
  const stringField = canvasElement.querySelector('input[name="object.string"]');
64
73
  const numberField = canvasElement.querySelector('input[name="object.number"]');
65
74
  const booleanField = canvasElement.querySelector('input[name="object.boolean"]');
66
75
  const enumField = canvasElement.querySelector('select[name="object.enum[0]"]');
76
+ const collapsibleObjectButton = canvas.getByLabelText("object.collapsibleObject");
77
+ expect(collapsibleObjectButton).toBeInTheDocument();
67
78
  yield userEvent.type(stringField, "string");
68
79
  yield userEvent.type(numberField, "123");
69
80
  yield userEvent.click(booleanField);
70
81
  yield userEvent.selectOptions(enumField, "2");
82
+ yield userEvent.click(collapsibleObjectButton);
83
+ const stringFieldInCollapsibleObject = (yield waitFor(() => canvasElement.querySelector('input[name="object.collapsibleObject.insideCollapsibleObject"]')));
84
+ const numberFieldInCollapsibleObject = (yield waitFor(() => canvasElement.querySelector('input[name="object.collapsibleObject.alsoInsideCollapsibleObject"]')));
85
+ yield userEvent.type(stringFieldInCollapsibleObject, "string in collapsible object");
86
+ yield userEvent.type(numberFieldInCollapsibleObject, "313");
71
87
  expect(stringField).toHaveValue("string");
72
88
  expect(numberField).toHaveValue(123);
73
89
  expect(booleanField).toBeChecked();
74
90
  expect(enumField).toHaveValue("2");
91
+ expect(stringFieldInCollapsibleObject).toHaveValue("string in collapsible object");
92
+ expect(numberFieldInCollapsibleObject).toHaveValue(313);
75
93
  }),
76
94
  };
@@ -22,7 +22,7 @@ export const Default = {
22
22
  args: {
23
23
  name: "tags",
24
24
  schema: {
25
- type: "string",
25
+ type: "array",
26
26
  title: "Enum searchable tag select anyOf",
27
27
  anyOf: [
28
28
  { const: "1", title: "First tag" },
@@ -0,0 +1,3 @@
1
+ import type { Schema, UiSchema } from "../types";
2
+ export declare const collapsibleSchema: Schema;
3
+ export declare const collapsibleUiSchema: UiSchema;
@@ -0,0 +1,27 @@
1
+ export const collapsibleSchema = {
2
+ title: "TITLE",
3
+ properties: {
4
+ colObject: {
5
+ type: "object",
6
+ title: "Collapsible Object",
7
+ properties: { summer: { type: "string" }, winter: { type: "string" } },
8
+ },
9
+ secondColObject: {
10
+ type: "object",
11
+ title: "Second collapsible Object",
12
+ properties: { spring: { type: "string" }, fall: { type: "string" } },
13
+ },
14
+ },
15
+ };
16
+ export const collapsibleUiSchema = {
17
+ "ui:description": "Description text",
18
+ colObject: { props: { isCollapsible: true, isCollapsedOnLoad: false } },
19
+ secondColObject: {
20
+ props: {
21
+ isCollapsible: true,
22
+ isCollapsedOnLoad: true,
23
+ openIcon: "-",
24
+ collapsedIcon: "+",
25
+ },
26
+ },
27
+ };
@@ -44,7 +44,7 @@ export const completeSchema = {
44
44
  ],
45
45
  },
46
46
  enumAnyOf: {
47
- type: "number",
47
+ type: "array",
48
48
  title: "Enum anyOf (non-standard)",
49
49
  description: "Enum anyOf description",
50
50
  anyOf: [
@@ -55,7 +55,7 @@ export const completeSchema = {
55
55
  ],
56
56
  },
57
57
  enumTagSelectAnyOf: {
58
- type: "string",
58
+ type: "array",
59
59
  title: "Enum searchable tag select anyOf",
60
60
  description: "Enum searchable tag select anyOf description",
61
61
  anyOf: [
@@ -3,3 +3,6 @@ export * from "./completeSchema";
3
3
  export * from "./generatedSchema";
4
4
  export * from "./requiredInputSchema";
5
5
  export * from "./conditionsSchema";
6
+ export * from "./multipleChoiceSchema";
7
+ export * from "./refDefSchema";
8
+ export * from "./collapsibleSchema";
@@ -3,3 +3,6 @@ export * from "./completeSchema";
3
3
  export * from "./generatedSchema";
4
4
  export * from "./requiredInputSchema";
5
5
  export * from "./conditionsSchema";
6
+ export * from "./multipleChoiceSchema";
7
+ export * from "./refDefSchema";
8
+ export * from "./collapsibleSchema";
@@ -23,7 +23,7 @@ export const multipleChoiceSchema = {
23
23
  ],
24
24
  },
25
25
  food: {
26
- type: "string",
26
+ type: "array",
27
27
  title: "What would you like to eat today? (style: oneColumn)",
28
28
  anyOf: [
29
29
  { const: "breakfast", title: "Breakfast" },
@@ -36,7 +36,7 @@ export const multipleChoiceSchema = {
36
36
  ],
37
37
  },
38
38
  drinks: {
39
- type: "string",
39
+ type: "array",
40
40
  title: "What would you like to drink today? (style: twoColumns)",
41
41
  anyOf: [
42
42
  { const: "water", title: "Water" },
@@ -53,7 +53,7 @@ export const multipleChoiceSchema = {
53
53
  ],
54
54
  },
55
55
  flowers: {
56
- type: "string",
56
+ type: "array",
57
57
  title: "Pick some flowers (style: threeColumns)",
58
58
  anyOf: [
59
59
  { const: "lily", title: "Lily" },
@@ -1,15 +1,52 @@
1
1
  import { ageFieldDefinition } from "./ageFieldDefinition";
2
+ const familyObjectField = {
3
+ type: "object",
4
+ title: "My family member",
5
+ properties: {
6
+ relationship: { type: "string", title: "Relationship status" },
7
+ names: { $ref: "#/definitions/names" },
8
+ },
9
+ };
2
10
  export const refDefSchema = {
3
11
  title: "References and Definitions",
4
12
  type: "object",
5
- required: ["name", "field"],
13
+ required: ["names", "fieldType"],
6
14
  properties: {
7
- name: {
8
- $ref: "#/definitions/name",
15
+ names: {
16
+ $ref: "#/definitions/names",
17
+ },
18
+ family: {
19
+ type: "object",
20
+ title: "Family",
21
+ properties: {
22
+ mother: {
23
+ type: "array",
24
+ title: "Maternal family",
25
+ additionalItems: familyObjectField,
26
+ },
27
+ father: {
28
+ type: "array",
29
+ title: "Paternal family",
30
+ additionalItems: familyObjectField,
31
+ },
32
+ siblings: {
33
+ type: "array",
34
+ title: "Siblings",
35
+ additionalItems: familyObjectField,
36
+ },
37
+ children: {
38
+ type: "array",
39
+ title: "Own children (and grandchildren)",
40
+ additionalItems: familyObjectField,
41
+ },
42
+ },
9
43
  },
10
44
  age: {
11
45
  $ref: "#/definitions/age",
12
46
  },
47
+ birthDate: {
48
+ $ref: "#/definitions/birthDate",
49
+ },
13
50
  creditCard: {
14
51
  type: "number",
15
52
  title: "Credit card number",
@@ -127,7 +164,10 @@ export const refDefSchema = {
127
164
  {
128
165
  properties: {
129
166
  boolean: true,
130
- something: { type: "string" },
167
+ feedback: {
168
+ type: "string",
169
+ title: "Feedback on your coffee experience",
170
+ },
131
171
  },
132
172
  },
133
173
  ],
@@ -187,18 +227,32 @@ export const refDefSchema = {
187
227
  },
188
228
  ],
189
229
  definitions: {
190
- name: {
191
- type: "string",
230
+ names: {
231
+ type: "object",
192
232
  title: "Name",
193
- description: "The schema for this field comes from the 'definitions' object inside the main schema",
233
+ description: "The schema for this object comes from the 'definitions' object inside the main schema",
234
+ properties: {
235
+ firstName: { type: "string", title: "First name" },
236
+ middleNames: {
237
+ type: "array",
238
+ title: "Middle names",
239
+ additionalItems: { $ref: "#/definitions/middleName" },
240
+ },
241
+ lastName: { type: "string", title: "Last name" },
242
+ history: { type: "string", title: "History of these names" },
243
+ },
194
244
  },
195
245
  age: ageFieldDefinition,
246
+ birthDate: { type: "string", title: "When is your birthday?" },
247
+ middleName: { type: "string", title: " Middle name" },
196
248
  },
197
249
  };
198
250
  export const refDefUiSchema = {
199
251
  "ui:order": [
200
- "name",
252
+ "names",
201
253
  "age",
254
+ "birthDate",
255
+ "family",
202
256
  "creditCard",
203
257
  "billing_address",
204
258
  "fieldType",
@@ -206,7 +260,7 @@ export const refDefUiSchema = {
206
260
  "objectField",
207
261
  "arrayField",
208
262
  "boolean",
209
- "something",
263
+ "feedback",
210
264
  "animal",
211
265
  "food",
212
266
  "water",
@@ -216,4 +270,19 @@ export const refDefUiSchema = {
216
270
  props: { disabled: false, hidden: false },
217
271
  },
218
272
  },
273
+ feedback: { "ui:field": "TextAreaField" },
274
+ definitions: {
275
+ birthDate: { "ui:field": "DateField" },
276
+ names: {
277
+ history: { "ui:field": "TextAreaField" },
278
+ middleNames: { "ui:info": { content: "You can leave those blank" } },
279
+ },
280
+ middleName: {
281
+ props: {
282
+ title: {
283
+ hidden: true,
284
+ },
285
+ },
286
+ },
287
+ },
219
288
  };
@@ -1,5 +1,5 @@
1
1
  /// <reference types="hoist-non-react-statics" />
2
- import type { ArrayFieldButtonProps, InfoFieldButtonProps, ObjectFieldButtonProps, RadioButtonFieldProps, ToggleSwitchFieldProps, SelectFieldProps, MultipleChoiceFieldProps, TextAreaInputFieldProps } from "./types";
2
+ import type { ArrayFieldButtonProps, InfoFieldButtonProps, ObjectFieldButtonProps, RadioButtonFieldProps, ToggleSwitchFieldProps, SelectFieldProps, MultipleChoiceFieldProps, TextAreaInputFieldProps, ClickableProps } from "./types";
3
3
  import React from "react";
4
4
  import * as formik from "formik";
5
5
  export declare const Form: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<Omit<Omit<React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>, "ref"> & React.RefAttributes<HTMLFormElement>, "ref"> & {
@@ -208,4 +208,11 @@ export declare const TextAreaFieldInput: import("styled-components").IStyledComp
208
208
  ref?: unknown;
209
209
  }, never>> & Omit<({ formikAs, ...props }: Omit<formik.FieldAttributes<any>, "as">) => import("react/jsx-runtime").JSX.Element, keyof React.Component<any, {}, any>>, keyof React.Component<any, {}, any>>;
210
210
  export declare const TextAreaInputField: ({ inputProps, name, className, }: TextAreaInputFieldProps) => import("react/jsx-runtime").JSX.Element;
211
+ export declare const BooleanFieldWrapper: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>;
212
+ export declare const Clickable: ({ children, onClick, onMouseUp, onMouseDown, innerRef, className, ariaLabel, ariaExpanded, ariaControls, id, }: ClickableProps) => import("react/jsx-runtime").JSX.Element;
213
+ export declare const CollapsibleToggle: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<ClickableProps, never>> & Omit<({ children, onClick, onMouseUp, onMouseDown, innerRef, className, ariaLabel, ariaExpanded, ariaControls, id, }: ClickableProps) => import("react/jsx-runtime").JSX.Element, keyof React.Component<any, {}, any>>;
214
+ export declare const Icon: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>;
215
+ export declare const CollapsibleChildWrapper: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>;
216
+ export declare const CollapsibleObjectFieldWrapper: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.FieldsetHTMLAttributes<HTMLFieldSetElement>, HTMLFieldSetElement>, never>>;
217
+ export declare const CollapsibleObjectFieldTitle: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLLegendElement>, HTMLLegendElement>, never>>;
211
218
  export {};
@@ -362,7 +362,7 @@ export const SearchableMultiTagSelectField = ({ name, placeholder, enums, emptyS
362
362
  };
363
363
  return (_jsx(formik.Field, { className: className, name: name, multiple: true, children: ({ field, form: { setFieldValue }, meta, }) => {
364
364
  var _a;
365
- return (_jsxs(MultiSelectContainer, { className: className, children: [_jsx(FieldInput, Object.assign({ type: "hidden" }, field)), _jsx(MultiSelectInput, { className: className, type: "text", onChange: (e) => {
365
+ return (_jsxs(MultiSelectContainer, { className: className, children: [_jsx(FieldInput, Object.assign({ id: className, type: "hidden" }, field)), _jsx(MultiSelectInput, { id: className + "_id", className: className, type: "text", onChange: (e) => {
366
366
  setSearch(e.currentTarget.value);
367
367
  }, placeholder: placeholder, value: search }), _jsx(MultiSelectBadgeContainer, { children: ((_a = field.value) !== null && _a !== void 0 ? _a : []).map((value) => {
368
368
  var _a;
@@ -393,3 +393,29 @@ export const TextAreaFieldInput = styled(FieldInput) `
393
393
  export const TextAreaInputField = ({ inputProps, name, className, }) => {
394
394
  return _jsx(TextAreaFieldInput, Object.assign({}, inputProps, { name: name, formikAs: "textarea" }));
395
395
  };
396
+ export const BooleanFieldWrapper = styled.div `
397
+ display: flex;
398
+ `;
399
+ const ClickableEl = styled.button `
400
+ background: none;
401
+ color: inherit;
402
+ border: none;
403
+ padding: 0;
404
+ font: inherit;
405
+ outline: inherit;
406
+ cursor: pointer;
407
+ `;
408
+ export const Clickable = ({ children, onClick, onMouseUp = () => { }, onMouseDown = () => { }, innerRef = () => { }, className, ariaLabel, ariaExpanded = false, ariaControls, id, }) => {
409
+ return (_jsx(ClickableEl, { type: "button", onClick: onClick, onMouseUp: onMouseUp, onMouseDown: onMouseDown, ref: (r) => innerRef(r), className: className, "aria-expanded": ariaExpanded, "aria-label": ariaLabel, "aria-controls": ariaControls, id: id, children: children }));
410
+ };
411
+ export const CollapsibleToggle = styled(Clickable) `
412
+ display: flex;
413
+ flex-direction: column;
414
+ width: 100%;
415
+ `;
416
+ export const Icon = styled.div `
417
+ padding: 0 0.5rem;
418
+ `;
419
+ export const CollapsibleChildWrapper = styled.div ``;
420
+ export const CollapsibleObjectFieldWrapper = ObjectFieldWrapper;
421
+ export const CollapsibleObjectFieldTitle = ObjectFieldTitle;
@@ -0,0 +1,3 @@
1
+ import type { CollapsibleObjectFieldTemplateProps } from "../types";
2
+ import React from "react";
3
+ export declare const CollapsibleObjectFieldTemplate: React.FC<CollapsibleObjectFieldTemplateProps>;
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useContext, useState } from "react";
3
+ import { FormRegistry } from "../Form";
4
+ import { getDescription, getTitle } from "../utils";
5
+ import { useExtractUiSchema } from "../hooks";
6
+ import { CollapsibleChildWrapper, CollapsibleToggle, Icon, RequiredIndicator, } from "../styledComponents";
7
+ export const CollapsibleObjectFieldTemplate = (props) => {
8
+ const { components: { FieldDescription, CollapsibleObjectFieldTitle, CollapsibleObjectFieldWrapper, FieldHelp, InfoFieldButton, }, } = useContext(FormRegistry);
9
+ const { children, name, uiSchema } = props;
10
+ const title = getTitle(props);
11
+ const description = getDescription(props);
12
+ const { help, info,
13
+ //TODO: This is not right
14
+ props: { wrapper: wrapperProps, title: titleProps, description: descriptionProps, help: helpProps, isCollapsedOnLoad = true, openIcon, collapsedIcon, ariaLabel,
15
+ // error: errorProps,
16
+ } = {}, } = useExtractUiSchema(uiSchema);
17
+ const [collapsed, setCollapsed] = useState(isCollapsedOnLoad);
18
+ const hide = collapsed;
19
+ return (_jsxs(CollapsibleObjectFieldWrapper, Object.assign({}, wrapperProps, { children: [title && (_jsx(CollapsibleToggle, { onClick: () => setCollapsed(!collapsed), ariaExpanded: !hide, ariaLabel: ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : name, children: _jsxs(CollapsibleObjectFieldTitle, Object.assign({ style: { display: "flex", alignItems: "center" } }, titleProps, { children: [title, (props === null || props === void 0 ? void 0 : props.required) && _jsx(RequiredIndicator, { children: "*" }), _jsx(Icon, { children: hide ? collapsedIcon !== null && collapsedIcon !== void 0 ? collapsedIcon : "▸" : openIcon !== null && openIcon !== void 0 ? openIcon : "▾" })] })) })), info && _jsx(InfoFieldButton, Object.assign({}, info)), description && (_jsx(FieldDescription, Object.assign({}, descriptionProps, { children: description }))), !hide && _jsx(CollapsibleChildWrapper, { children: children }), help && _jsx(FieldHelp, Object.assign({}, helpProps, { children: help }))] })));
20
+ };
@@ -1,3 +1,4 @@
1
1
  export * from "./ArrayFieldTemplate";
2
2
  export * from "./FieldTemplate";
3
3
  export * from "./ObjectFieldTemplate";
4
+ export * from "./CollapsibleObjectFieldTemplate";
@@ -1,3 +1,4 @@
1
1
  export * from "./ArrayFieldTemplate";
2
2
  export * from "./FieldTemplate";
3
3
  export * from "./ObjectFieldTemplate";
4
+ export * from "./CollapsibleObjectFieldTemplate";
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { JSONSchema7, JSONSchema7Definition } from "json-schema";
2
- import React from "react";
2
+ import React, { MouseEventHandler } from "react";
3
3
  export type FormProps<T> = React.PropsWithChildren<{
4
4
  /** If true, disabled prop is passed down to each field on the form */
5
5
  disabled?: boolean | undefined;
@@ -51,6 +51,9 @@ export type FormRegistryContextValue = {
51
51
  definitions: {
52
52
  [key: string]: JSONSchema7Definition;
53
53
  } | undefined;
54
+ uiSchemaDefinitions: {
55
+ [key: string]: JSONSchema7Definition;
56
+ } | undefined;
54
57
  formContext: Record<string, any>;
55
58
  useItemsAsAdditionalItems: boolean;
56
59
  hideUnmarkedAdditionalProperties: boolean;
@@ -90,10 +93,18 @@ export type ObjectFieldTemplateProps = {
90
93
  required?: boolean;
91
94
  children: React.ReactNode;
92
95
  };
96
+ export type CollapsibleObjectFieldTemplateProps = {
97
+ schema: Schema;
98
+ uiSchema?: UiSchema;
99
+ required?: boolean;
100
+ children: React.ReactNode;
101
+ name?: string;
102
+ };
93
103
  export type TemplatesType = {
94
104
  FieldTemplate: React.FunctionComponent<FieldTemplateProps>;
95
105
  ObjectFieldTemplate: React.FunctionComponent<ObjectFieldTemplateProps>;
96
106
  ArrayFieldTemplate: React.FunctionComponent<ArrayFieldTemplateProps>;
107
+ CollapsibleObjectFieldTemplate: React.FunctionComponent<ObjectFieldTemplateProps>;
97
108
  };
98
109
  /*** COMPONENTS ***/
99
110
  export type ComponentsType = {
@@ -133,6 +144,9 @@ export type ComponentsType = {
133
144
  ToggleSwitchField: React.FunctionComponent<any>;
134
145
  SearchableMultiTagSelectField: React.FunctionComponent<any>;
135
146
  TextAreaInputField: React.FunctionComponent<any>;
147
+ BooleanFieldWrapper: React.FunctionComponent<any>;
148
+ CollapsibleObjectFieldWrapper: React.FunctionComponent<any>;
149
+ CollapsibleObjectFieldTitle: React.FunctionComponent<any>;
136
150
  };
137
151
  export declare enum ObjectFieldButtonPurpose {
138
152
  ADD = 0,
@@ -205,6 +219,18 @@ export type SelectFieldProps = {
205
219
  closeElement?: React.ReactNode;
206
220
  className?: string;
207
221
  };
222
+ export type ClickableProps = {
223
+ children: React.ReactNode;
224
+ onClick?: MouseEventHandler<HTMLButtonElement>;
225
+ onMouseDown?: MouseEventHandler<HTMLButtonElement>;
226
+ onMouseUp?: MouseEventHandler<HTMLButtonElement>;
227
+ innerRef?: (r: HTMLButtonElement | null) => void;
228
+ className?: string;
229
+ ariaLabel?: string;
230
+ ariaExpanded?: boolean;
231
+ ariaControls?: string;
232
+ id?: string;
233
+ };
208
234
  /*** ***/
209
235
  export type AjvError = {
210
236
  message: string;
@@ -1,12 +1,26 @@
1
1
  import { solveReferences } from "../solveReferences";
2
- import { refDefSchema } from "./testSchemas";
2
+ import { refDefSchema, refDefUiSchema } from "./testSchemas";
3
3
  describe("solveReferences", () => {
4
4
  it("should resolve simple references correctly", () => {
5
5
  var _a;
6
- const schemaSolvedForReferences = solveReferences((_a = refDefSchema === null || refDefSchema === void 0 ? void 0 : refDefSchema.properties) === null || _a === void 0 ? void 0 : _a.name, refDefSchema.definitions);
6
+ const schemaSolvedForReferences = solveReferences((_a = refDefSchema === null || refDefSchema === void 0 ? void 0 : refDefSchema.properties) === null || _a === void 0 ? void 0 : _a.name, refDefSchema.definitions, {}, {});
7
7
  expect(schemaSolvedForReferences).toEqual({
8
- type: "string",
9
- title: "Name",
8
+ schema: {
9
+ type: "string",
10
+ title: "Name",
11
+ },
12
+ uiSchema: {},
13
+ });
14
+ });
15
+ it("should resolve simple references with uiSchema correctly", () => {
16
+ var _a;
17
+ const schemaSolvedForReferences = solveReferences((_a = refDefSchema === null || refDefSchema === void 0 ? void 0 : refDefSchema.properties) === null || _a === void 0 ? void 0 : _a.birthday, refDefSchema.definitions, refDefUiSchema, refDefUiSchema.definitions);
18
+ expect(schemaSolvedForReferences).toEqual({
19
+ schema: {
20
+ type: "string",
21
+ title: "Birthday",
22
+ },
23
+ uiSchema: { "ui:field": "DateField" },
10
24
  });
11
25
  });
12
26
  });
@@ -1,4 +1,5 @@
1
- import { Schema } from "../../types";
1
+ import { Schema, UiSchema } from "../../types";
2
2
  export declare const refDefSchema: Schema;
3
+ export declare const refDefUiSchema: UiSchema;
3
4
  export declare const simpleConditionalSchema: Schema;
4
5
  export declare const schemaWithDependenciesAndOneOf: Schema;
@@ -6,9 +6,18 @@ export const refDefSchema = {
6
6
  name: {
7
7
  $ref: "#/definitions/name",
8
8
  },
9
+ birthday: {
10
+ $ref: "#/definitions/birthday",
11
+ },
9
12
  },
10
13
  definitions: {
11
14
  name: { type: "string", title: "Name" },
15
+ birthday: { type: "string", title: "Birthday" },
16
+ },
17
+ };
18
+ export const refDefUiSchema = {
19
+ definitions: {
20
+ birthday: { "ui:field": "DateField" },
12
21
  },
13
22
  };
14
23
  export const simpleConditionalSchema = {
@@ -28,7 +28,7 @@ export function createInitialValues(schema, definitions = {}) {
28
28
  return Object.keys(schema === null || schema === void 0 ? void 0 : schema.properties).reduce((acc, key) => {
29
29
  var _a;
30
30
  const value = (_a = schema === null || schema === void 0 ? void 0 : schema.properties) === null || _a === void 0 ? void 0 : _a[key];
31
- const solvedValue = solveReferences(value, schemaDefinitions);
32
- return Object.assign(Object.assign({}, acc), { [key]: getValue(solvedValue) });
31
+ const solvedValue = solveReferences(value, schemaDefinitions, {}, {});
32
+ return Object.assign(Object.assign({}, acc), { [key]: getValue(solvedValue.schema) });
33
33
  }, {});
34
34
  }
@@ -1,2 +1,10 @@
1
- import { Schema } from "../types";
2
- export declare function solveReferences(schema: Schema, definitions: any): any;
1
+ import { JSONSchema7, JSONSchema7Definition } from "json-schema";
2
+ import { Schema, UiSchema } from "../types";
3
+ export declare function solveReferences(schema: Schema, definitions: {
4
+ [key: string]: JSONSchema7Definition;
5
+ } | undefined, uiSchema: UiSchema, uiSchemaDefinitions: {
6
+ [key: string]: JSONSchema7Definition;
7
+ } | undefined): {
8
+ schema: JSONSchema7;
9
+ uiSchema: UiSchema;
10
+ };
@@ -1,8 +1,11 @@
1
- export function solveReferences(schema, definitions) {
2
- var _a, _b;
3
- if (typeof schema === "object" && schema["$ref"]) {
1
+ export function solveReferences(schema, definitions, uiSchema, uiSchemaDefinitions) {
2
+ var _a, _b, _c;
3
+ let solvedSchema = schema;
4
+ let solvedUiSchema = uiSchema;
5
+ if (typeof schema === "object" && schema["$ref"] && definitions) {
4
6
  const schemaKey = (_b = (_a = schema["$ref"]) === null || _a === void 0 ? void 0 : _a.substring(14)) !== null && _b !== void 0 ? _b : "";
5
- return definitions[schemaKey];
7
+ solvedSchema = definitions[schemaKey];
8
+ solvedUiSchema = (_c = uiSchemaDefinitions === null || uiSchemaDefinitions === void 0 ? void 0 : uiSchemaDefinitions[schemaKey]) !== null && _c !== void 0 ? _c : {};
6
9
  }
7
- return schema;
10
+ return { schema: solvedSchema, uiSchema: solvedUiSchema };
8
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nolwenture/formik-jsonschema-form",
3
- "version": "0.10.1",
3
+ "version": "0.12.0",
4
4
  "description": "Formik form generation from json schema",
5
5
  "author": "Antti Vikman <antti.vikman@nolwenture.com>",
6
6
  "contributors": [