@nolwenture/formik-jsonschema-form 0.17.0 → 0.18.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.
package/README.md CHANGED
@@ -409,6 +409,8 @@ export function MyFormComponent() {
409
409
  Enum fields are fields with multiple options to either choose `oneOf` or `anyOf` from.
410
410
  There is an alternative field implementation for "anyOf" that when given `"ui:field": "SearchMultiTagSelectField"` results in a searchable multi tag select field.
411
411
 
412
+ A searchable single select field is also available for `oneOf` and `enum`. To use it, add the following to the field's uiSchema: `"ui:field": "SearchableSelectField"`
413
+
412
414
  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).
413
415
 
414
416
  `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".
@@ -451,6 +453,16 @@ const schema = {
451
453
  { const: 4, title: "four" },
452
454
  ],
453
455
  },
456
+ enumSearchableOneOf: {
457
+ type: "string",
458
+ title: "Enum searchable select oneOf",
459
+ oneOf: [
460
+ { const: "one", title: "One" },
461
+ { const: "two", title: "Two" },
462
+ { const: "three", title: "Three" },
463
+ { const: "four", title: "Four" },
464
+ ],
465
+ },
454
466
  },
455
467
  };
456
468
 
@@ -462,6 +474,15 @@ const uiSchema = {
462
474
  emptySearchMessage: "...no matching results found",
463
475
  },
464
476
  },
477
+ enumSearchableOneOf: {
478
+ "ui:field": "SearchableSelectField",
479
+ "ui:options": {
480
+ placeholder: "Search...",
481
+ clearButtonText: "Clear",
482
+ expandText: "More results",
483
+ emptySearchMessage: "...no matching results found",
484
+ },
485
+ },
465
486
  };
466
487
  ```
467
488
 
@@ -0,0 +1,3 @@
1
+ import React from "react";
2
+ import { FieldProps } from "../types";
3
+ export declare const SearchableSelectField: React.FC<FieldProps>;
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useRef, useContext, useMemo } from "react";
3
+ import { useExtractUiSchema, useSetDefault } from "../hooks";
4
+ import { FormRegistry } from "../Form";
5
+ import { useField } from "formik";
6
+ import { getEnums } from "../utils";
7
+ export const SearchableSelectField = (props) => {
8
+ const { templates: { FieldTemplate }, components: { SearchableSelectContainer, SearchDropdownContainer, SearchListOption, SearchListExpand, SearchNotFound, SearchInput, SearchClearButton, SearchInputContainer, }, } = useContext(FormRegistry);
9
+ useSetDefault(props);
10
+ const { name, uiSchema } = props;
11
+ const { props: inputProps, placeholder, clearButtonText, expandText, notFoundMessage, } = useExtractUiSchema(uiSchema);
12
+ const [_, __, { setValue }] = useField(name);
13
+ const enums = useMemo(() => getEnums(props), [props]);
14
+ const options = (enums === null || enums === void 0 ? void 0 : enums.values) || [];
15
+ const [showOptions, setShowOptions] = useState(false);
16
+ const [searchTerm, setSearchTerm] = useState("");
17
+ const [displayedItems, setDisplayedItems] = useState([]);
18
+ const [itemsToShow, setItemsToShow] = useState(10);
19
+ const moreRef = useRef(null);
20
+ const containerRef = useRef(null);
21
+ const loadMoreItems = () => {
22
+ setItemsToShow((prev) => prev + 10);
23
+ };
24
+ useEffect(() => {
25
+ setDisplayedItems(options
26
+ .filter((option) => { var _a, _b; return ((_b = (_a = option.title) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : "").includes(searchTerm.toLowerCase()); })
27
+ .slice(0, itemsToShow));
28
+ }, [itemsToShow, options, searchTerm]);
29
+ const handleSearchChange = (event) => {
30
+ setSearchTerm(event.target.value);
31
+ setItemsToShow(10);
32
+ setShowOptions(true);
33
+ };
34
+ const handleSelect = (item) => {
35
+ var _a;
36
+ setSearchTerm((_a = item.title) !== null && _a !== void 0 ? _a : "");
37
+ setValue(item.const);
38
+ setShowOptions(false);
39
+ };
40
+ const handleFocus = () => {
41
+ setShowOptions(true);
42
+ };
43
+ const handleBlur = (event) => {
44
+ if (containerRef.current &&
45
+ !containerRef.current.contains(event.relatedTarget)) {
46
+ setShowOptions(false);
47
+ }
48
+ };
49
+ const handleClear = () => {
50
+ setSearchTerm("");
51
+ setValue(undefined);
52
+ };
53
+ const filteredOptions = options.filter((option) => { var _a, _b; return ((_b = (_a = option.title) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : "").includes(searchTerm.toLowerCase()); });
54
+ const showMoreOption = displayedItems.length < filteredOptions.length &&
55
+ displayedItems.length >= 10;
56
+ return (_jsx(FieldTemplate, Object.assign({}, props, { children: _jsxs(SearchableSelectContainer, { ref: containerRef, onBlur: handleBlur, children: [_jsxs(SearchInputContainer, { children: [_jsx(SearchInput, Object.assign({}, inputProps, { type: "text", placeholder: placeholder, value: searchTerm, onChange: handleSearchChange, onFocus: handleFocus })), searchTerm && (_jsx(SearchClearButton, { onClick: handleClear, children: clearButtonText !== null && clearButtonText !== void 0 ? clearButtonText : "X" }))] }), showOptions && (_jsxs(SearchDropdownContainer, { role: "dropdown-list", ref: moreRef, children: [displayedItems.length > 0 ? (displayedItems.map((item, index) => (_jsx(SearchListOption, { role: "filtered-list", onClick: () => handleSelect(item), onMouseDown: (e) => e.preventDefault(), children: item.title }, index)))) : (_jsx(SearchNotFound, { onMouseDown: (e) => e.preventDefault(), children: notFoundMessage !== null && notFoundMessage !== void 0 ? notFoundMessage : "No match found" })), showMoreOption && (_jsx(SearchListExpand, { ref: moreRef, onMouseDown: (e) => e.preventDefault(), onClick: loadMoreItems, children: expandText !== null && expandText !== void 0 ? expandText : "More..." }))] }))] }) })));
57
+ };
@@ -10,5 +10,6 @@ export * from "./AnyField";
10
10
  export * from "./RadioField";
11
11
  export * from "./ToggleField";
12
12
  export * from "./SearchMultiTagSelectField";
13
+ export * from "./SearchableSelectField";
13
14
  export * from "./MultipleChoiceField";
14
15
  export * from "./TextAreaField";
@@ -10,5 +10,6 @@ export * from "./AnyField";
10
10
  export * from "./RadioField";
11
11
  export * from "./ToggleField";
12
12
  export * from "./SearchMultiTagSelectField";
13
+ export * from "./SearchableSelectField";
13
14
  export * from "./MultipleChoiceField";
14
15
  export * from "./TextAreaField";
@@ -68,6 +68,27 @@ export const completeSchema = {
68
68
  { const: "dragon", title: "dragon" },
69
69
  ],
70
70
  },
71
+ searchableSelect: {
72
+ type: "string",
73
+ title: "Enum searchable select oneOf",
74
+ description: "Enum searchable select oneOf description",
75
+ oneOf: [
76
+ { const: "cat", title: "cat" },
77
+ { const: "dog", title: "dog" },
78
+ { const: "bunny", title: "bunny" },
79
+ { const: "canary", title: "canary" },
80
+ { const: "hamster", title: "hamster" },
81
+ { const: "goldfish", title: "goldfish" },
82
+ { const: "dragon", title: "dragon" },
83
+ { const: "lizard", title: "lizard" },
84
+ { const: "frog", title: "frog" },
85
+ { const: "parrot", title: "parrot" },
86
+ { const: "turtle", title: "turtle" },
87
+ { const: "raccoon", title: "raccoon" },
88
+ { const: "tiger", title: "tiger" },
89
+ { const: "lion", title: "lion" },
90
+ ],
91
+ },
71
92
  null: { type: "null", title: "Null", description: "Null description" },
72
93
  nested: {
73
94
  type: "object",
@@ -293,5 +314,14 @@ export const completeUiSchema = {
293
314
  emptySearchMessage: "...no matching results found",
294
315
  },
295
316
  },
317
+ searchableSelect: {
318
+ "ui:field": "SearchableSelectField",
319
+ "ui:options": {
320
+ placeholder: "Search...",
321
+ clearButtonText: "Clear",
322
+ expandText: "More results",
323
+ emptySearchMessage: "...no matching results found",
324
+ },
325
+ },
296
326
  textArea: { "ui:field": "TextAreaField" },
297
327
  };
@@ -214,4 +214,28 @@ export declare const Icon: import("styled-components").IStyledComponent<"web", i
214
214
  export declare const CollapsibleChildWrapper: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>;
215
215
  export declare const CollapsibleObjectFieldWrapper: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.FieldsetHTMLAttributes<HTMLFieldSetElement>, HTMLFieldSetElement>, never>>;
216
216
  export declare const CollapsibleObjectFieldTitle: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLLegendElement>, HTMLLegendElement>, never>>;
217
+ export declare const SearchableSelectContainer: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<Omit<import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>, "ref"> & {
218
+ ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject<HTMLDivElement> | null | undefined;
219
+ }, never>> & Omit<import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>, keyof React.Component<any, {}, any>>;
220
+ export declare const SearchDropdownContainer: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<Omit<import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>, never>, "ref"> & {
221
+ ref?: ((instance: HTMLUListElement | null) => void) | React.RefObject<HTMLUListElement> | null | undefined;
222
+ }, never>> & Omit<import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>, never>>, keyof React.Component<any, {}, any>>;
223
+ export declare const SearchListOption: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<Omit<import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, never>, "ref"> & {
224
+ ref?: ((instance: HTMLLIElement | null) => void) | React.RefObject<HTMLLIElement> | null | undefined;
225
+ }, never>> & Omit<import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, never>>, keyof React.Component<any, {}, any>>;
226
+ export declare const SearchListExpand: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<Omit<import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, never>, "ref"> & {
227
+ ref?: ((instance: HTMLLIElement | null) => void) | React.RefObject<HTMLLIElement> | null | undefined;
228
+ }, never>> & Omit<import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, never>>, keyof React.Component<any, {}, any>>;
229
+ export declare const SearchNotFound: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<Omit<import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, never>, "ref"> & {
230
+ ref?: ((instance: HTMLLIElement | null) => void) | React.RefObject<HTMLLIElement> | null | undefined;
231
+ }, never>> & Omit<import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, never>>, keyof React.Component<any, {}, any>>;
232
+ export declare const SearchInput: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<Omit<import("styled-components/dist/types").FastOmit<Omit<Omit<any, "as">, "ref"> & {
233
+ ref?: unknown;
234
+ }, never>, "ref"> & {
235
+ ref?: unknown;
236
+ }, never>> & Omit<import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<Omit<Omit<any, "as">, "ref"> & {
237
+ ref?: unknown;
238
+ }, 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>>;
239
+ export declare const SearchClearButton: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, never>>;
240
+ export declare const SearchInputContainer: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>;
217
241
  export {};
@@ -402,3 +402,31 @@ export const Icon = styled.div `
402
402
  export const CollapsibleChildWrapper = styled.div ``;
403
403
  export const CollapsibleObjectFieldWrapper = ObjectFieldWrapper;
404
404
  export const CollapsibleObjectFieldTitle = ObjectFieldTitle;
405
+ export const SearchableSelectContainer = styled(MultiSelectContainer) ``;
406
+ export const SearchDropdownContainer = styled(MultiSelectOptionList) `
407
+ overflow-y: auto;
408
+ overflow-x: auto;
409
+ max-width: 16rem;
410
+ max-height: 6rem;
411
+ white-space: nowrap;
412
+ `;
413
+ export const SearchListOption = styled(MultiSelectOptionListItem) `
414
+ cursor: default;
415
+ `;
416
+ export const SearchListExpand = styled(MultiSelectOptionListItem) `
417
+ text-decoration: underline;
418
+ &:hover {
419
+ background: none;
420
+ }
421
+ `;
422
+ export const SearchNotFound = styled(MultiSelectOptionListItem) `
423
+ cursor: default;
424
+ &:hover {
425
+ background: none;
426
+ }
427
+ `;
428
+ export const SearchInput = styled(FieldInput) `
429
+ text-overflow: ellipsis;
430
+ `;
431
+ export const SearchClearButton = styled.button ``;
432
+ export const SearchInputContainer = styled.div ``;
package/dist/types.d.ts CHANGED
@@ -147,6 +147,14 @@ export type ComponentsType = {
147
147
  BooleanFieldWrapper: React.FunctionComponent<any>;
148
148
  CollapsibleObjectFieldWrapper: React.FunctionComponent<any>;
149
149
  CollapsibleObjectFieldTitle: React.FunctionComponent<any>;
150
+ SearchableSelectContainer: React.FunctionComponent<any>;
151
+ SearchDropdownContainer: React.FunctionComponent<any>;
152
+ SearchListOption: React.FunctionComponent<any>;
153
+ SearchListExpand: React.FunctionComponent<any>;
154
+ SearchNotFound: React.FunctionComponent<any>;
155
+ SearchInput: React.FunctionComponent<any>;
156
+ SearchClearButton: React.FunctionComponent<any>;
157
+ SearchInputContainer: React.FunctionComponent<any>;
150
158
  };
151
159
  export declare enum ObjectFieldButtonPurpose {
152
160
  ADD = 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nolwenture/formik-jsonschema-form",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Formik form generation from json schema",
5
5
  "author": "Antti Vikman <antti.vikman@nolwenture.com>",
6
6
  "contributors": [