@wq/form-web 3.0.0-alpha.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 (50) hide show
  1. package/AutoForm.js +24 -0
  2. package/README.md +10 -0
  3. package/components/Checkbox.js +30 -0
  4. package/components/DateTime.js +14 -0
  5. package/components/DeleteForm.js +56 -0
  6. package/components/Draw.js +78 -0
  7. package/components/Fieldset.js +32 -0
  8. package/components/FieldsetArray.js +30 -0
  9. package/components/File.js +77 -0
  10. package/components/FileArray.js +132 -0
  11. package/components/FlatFieldset.js +30 -0
  12. package/components/FormError.js +19 -0
  13. package/components/FormRoot.js +32 -0
  14. package/components/GeoHelpIcon.js +42 -0
  15. package/components/HelperText.js +28 -0
  16. package/components/Hidden.js +24 -0
  17. package/components/IconSubmitButton.js +20 -0
  18. package/components/Image.js +10 -0
  19. package/components/Input.js +28 -0
  20. package/components/Radio.js +53 -0
  21. package/components/Select.js +191 -0
  22. package/components/SubmitButton.js +36 -0
  23. package/components/Toggle.js +52 -0
  24. package/components/index.js +50 -0
  25. package/index.js +3 -0
  26. package/package.json +47 -0
  27. package/src/AutoForm.js +17 -0
  28. package/src/components/Checkbox.js +28 -0
  29. package/src/components/DateTime.js +13 -0
  30. package/src/components/DeleteForm.js +52 -0
  31. package/src/components/Draw.js +82 -0
  32. package/src/components/Fieldset.js +24 -0
  33. package/src/components/FieldsetArray.js +27 -0
  34. package/src/components/File.js +73 -0
  35. package/src/components/FileArray.js +129 -0
  36. package/src/components/FlatFieldset.js +26 -0
  37. package/src/components/FormError.js +18 -0
  38. package/src/components/FormRoot.js +27 -0
  39. package/src/components/GeoHelpIcon.js +52 -0
  40. package/src/components/HelperText.js +28 -0
  41. package/src/components/Hidden.js +21 -0
  42. package/src/components/IconSubmitButton.js +18 -0
  43. package/src/components/Image.js +9 -0
  44. package/src/components/Input.js +30 -0
  45. package/src/components/Radio.js +38 -0
  46. package/src/components/Select.js +158 -0
  47. package/src/components/SubmitButton.js +38 -0
  48. package/src/components/Toggle.js +35 -0
  49. package/src/components/index.js +52 -0
  50. package/src/index.js +3 -0
@@ -0,0 +1,82 @@
1
+ import { useRef, useEffect } from "react";
2
+ import { useComponents, withWQ, createFallbackComponent } from "@wq/react";
3
+ import MapboxDraw from "@mapbox/mapbox-gl-draw";
4
+ import PropTypes from "prop-types";
5
+
6
+ const DrawFallback = {
7
+ components: {
8
+ useControl: createFallbackComponent(
9
+ "useControl",
10
+ "@wq/map-gl",
11
+ "MapProvider",
12
+ ),
13
+ },
14
+ };
15
+
16
+ function Draw({ type, required, data, setData }) {
17
+ const types = type === "all" ? ["point", "line_string", "polygon"] : [type],
18
+ controls = {},
19
+ ref = useRef();
20
+
21
+ types.forEach((type) => (controls[type] = true));
22
+
23
+ if (!required) {
24
+ controls.trash = true;
25
+ }
26
+ const { useControl } = useComponents();
27
+ const draw = useControl(
28
+ () => {
29
+ const { classes } = MapboxDraw.constants;
30
+ for (const [key, value] of Object.entries(classes)) {
31
+ if (value.startsWith("mapboxgl-")) {
32
+ classes[key] = value.replace("mapboxgl-", "maplibregl-");
33
+ }
34
+ }
35
+ return new MapboxDraw({
36
+ displayControlsDefault: false,
37
+ controls,
38
+ });
39
+ },
40
+ ({ map }) => {
41
+ map.on("draw.create", handleChange);
42
+ map.on("draw.delete", handleChange);
43
+ map.on("draw.update", handleChange);
44
+ map.on("draw.combine", handleChange);
45
+ map.on("draw.uncombine", handleChange);
46
+ },
47
+ ({ map }) => {
48
+ map.off("draw.create", handleChange);
49
+ map.off("draw.delete", handleChange);
50
+ map.off("draw.update", handleChange);
51
+ map.off("draw.combine", handleChange);
52
+ map.off("draw.uncombine", handleChange);
53
+ },
54
+ { position: "top-right" },
55
+ );
56
+
57
+ useEffect(() => {
58
+ ref.current = { draw };
59
+ }, [draw]);
60
+
61
+ useEffect(() => {
62
+ if (draw && data) {
63
+ draw.set(data);
64
+ }
65
+ }, [draw, data]);
66
+
67
+ function handleChange() {
68
+ const { draw } = ref.current;
69
+ setData(draw.getAll());
70
+ }
71
+
72
+ return null;
73
+ }
74
+
75
+ Draw.propTypes = {
76
+ type: PropTypes.string,
77
+ required: PropTypes.boolean,
78
+ data: PropTypes.object,
79
+ setData: PropTypes.func,
80
+ };
81
+
82
+ export default withWQ(Draw, { fallback: DrawFallback });
@@ -0,0 +1,24 @@
1
+ import React from "react";
2
+ import { Card, Typography, CardContent } from "@mui/material";
3
+ import { withWQ } from "@wq/react";
4
+ import PropTypes from "prop-types";
5
+
6
+ function Fieldset({ label, children }) {
7
+ return (
8
+ <Card sx={{ mb: 2 }}>
9
+ <CardContent>
10
+ {label && (
11
+ <Typography color="textSecondary">{label}</Typography>
12
+ )}
13
+ {children}
14
+ </CardContent>
15
+ </Card>
16
+ );
17
+ }
18
+
19
+ Fieldset.propTypes = {
20
+ label: PropTypes.string,
21
+ children: PropTypes.node,
22
+ };
23
+
24
+ export default withWQ(Fieldset);
@@ -0,0 +1,27 @@
1
+ import React from "react";
2
+ import { useComponents, withWQ, createFallbackComponents } from "@wq/react";
3
+ import PropTypes from "prop-types";
4
+
5
+ const FieldsetArrayFallback = {
6
+ components: createFallbackComponents(["View", "Button"], "@wq/material"),
7
+ };
8
+
9
+ function FieldsetArray({ label, children, addRow }) {
10
+ const { View, Button } = useComponents();
11
+ return (
12
+ <View>
13
+ {children}
14
+ {addRow && (
15
+ <Button onClick={() => addRow()}>{`Add ${label}`}</Button>
16
+ )}
17
+ </View>
18
+ );
19
+ }
20
+
21
+ FieldsetArray.propTypes = {
22
+ label: PropTypes.string,
23
+ children: PropTypes.node,
24
+ addRow: PropTypes.func,
25
+ };
26
+
27
+ export default withWQ(FieldsetArray, { fallback: FieldsetArrayFallback });
@@ -0,0 +1,73 @@
1
+ import React, { useMemo, useCallback, useRef } from "react";
2
+ import { withWQ } from "@wq/react";
3
+ import { useField } from "formik";
4
+ import { DropzoneArea } from "mui2-file-dropzone";
5
+ import { InputLabel } from "@mui/material";
6
+ import HelperText from "./HelperText.js";
7
+ import PropTypes from "prop-types";
8
+
9
+ function File({ name, accept, hint, label }) {
10
+ const [, { initialValue }, { setValue }] = useField(name),
11
+ loadedRef = useRef(null);
12
+
13
+ const initialFiles = useMemo(() => {
14
+ if (!initialValue || initialValue === "__clear__") {
15
+ return [];
16
+ } else if (initialValue.type && initialValue.body) {
17
+ return [initialValue.body];
18
+ } else if (typeof initialValue === "string") {
19
+ return [initialValue];
20
+ }
21
+ }, [initialValue]),
22
+ acceptedFiles = useMemo(
23
+ () => (accept ? accept.split(",") : null),
24
+ [accept],
25
+ ),
26
+ setFile = useCallback(
27
+ (files) => {
28
+ if (!loadedRef.current) {
29
+ // initialFiles loaded
30
+ loadedRef.current = files && files.length ? files[0] : true;
31
+ return;
32
+ }
33
+ if (files && files.length) {
34
+ if (files[0] !== loadedRef.current) {
35
+ const { name, type } = files[0];
36
+ setValue({
37
+ name,
38
+ type,
39
+ body: files[0],
40
+ });
41
+ }
42
+ } else if (initialValue) {
43
+ setValue("__clear__");
44
+ } else {
45
+ setValue(null);
46
+ }
47
+ },
48
+ [initialValue],
49
+ );
50
+
51
+ return (
52
+ <>
53
+ <InputLabel shrink>{label}</InputLabel>
54
+ <DropzoneArea
55
+ initialFiles={initialFiles}
56
+ acceptedFiles={acceptedFiles}
57
+ onChange={setFile}
58
+ filesLimit={1}
59
+ maxFileSize={100000000}
60
+ />
61
+ <HelperText name={name} hint={hint} />
62
+ </>
63
+ );
64
+ }
65
+
66
+ File.propTypes = {
67
+ name: PropTypes.string,
68
+ accept: PropTypes.string,
69
+ label: PropTypes.string,
70
+ hint: PropTypes.string,
71
+ };
72
+
73
+ export default withWQ(File);
@@ -0,0 +1,129 @@
1
+ import React, { useMemo, useCallback, useRef } from "react";
2
+ import { useComponents, withWQ } from "@wq/react";
3
+ import { useField } from "formik";
4
+ import { DropzoneArea } from "mui2-file-dropzone";
5
+ import Fieldset from "./Fieldset.js";
6
+ import HelperText from "./HelperText.js";
7
+ import PropTypes from "prop-types";
8
+
9
+ const FileArrayFallback = {
10
+ components: {
11
+ Fieldset,
12
+ },
13
+ };
14
+
15
+ function FileArray({ name, label, subform, hint, maxRows }) {
16
+ const [, { initialValue = [] }, { setValue }] = useField(name),
17
+ fileField = subform.find(
18
+ (field) => field.type === "file" || field.type === "image",
19
+ ) || {
20
+ name: "file",
21
+ type: "file",
22
+ },
23
+ accept = fileField.type === "image" ? "image/*" : null,
24
+ loadedRef = useRef(null),
25
+ { Fieldset } = useComponents();
26
+
27
+ const initialFiles = useMemo(() => {
28
+ if (!initialValue || initialValue.length === 0) {
29
+ return [];
30
+ }
31
+ return initialValue
32
+ .filter((row) => {
33
+ if (
34
+ !row[fileField.name] ||
35
+ row[fileField.name] === "__clear__"
36
+ ) {
37
+ return false;
38
+ }
39
+ return true;
40
+ })
41
+ .map((row) => {
42
+ const value = row[fileField.name];
43
+ if (value.type && value.body) {
44
+ return value.body;
45
+ } else if (typeof value === "string") {
46
+ return value;
47
+ }
48
+ });
49
+ }, [initialValue]),
50
+ acceptedFiles = useMemo(
51
+ () => (accept ? accept.split(",") : null),
52
+ [accept],
53
+ ),
54
+ setFiles = useCallback(
55
+ (files) => {
56
+ if (!loadedRef.current) {
57
+ // initialFiles loaded
58
+ let fileIndex = 0;
59
+ loadedRef.current = initialValue.map((row) => {
60
+ let file;
61
+ if (
62
+ row[fileField.name] &&
63
+ row[fileField.name] !== "__clear__"
64
+ ) {
65
+ file = files[fileIndex];
66
+ fileIndex++;
67
+ }
68
+ return { row, file };
69
+ });
70
+ return;
71
+ }
72
+ const nextValue = files.map((file, i) => {
73
+ const { row, file: initialFile } = loadedRef.current[i] || {
74
+ row: {},
75
+ file: null,
76
+ };
77
+ if (initialFile === file) {
78
+ return row;
79
+ } else {
80
+ return {
81
+ ...row,
82
+ [fileField.name]: {
83
+ name: file.name,
84
+ type: file.type,
85
+ body: file,
86
+ },
87
+ };
88
+ }
89
+ });
90
+ if (initialValue && nextValue.length < initialValue.length) {
91
+ initialValue.slice(nextValue.length).forEach((row) => {
92
+ nextValue.push({
93
+ ...row,
94
+ [fileField.name]: "__clear__",
95
+ });
96
+ });
97
+ }
98
+ setValue(nextValue);
99
+ },
100
+ [initialValue],
101
+ );
102
+
103
+ return (
104
+ <Fieldset label={label}>
105
+ <DropzoneArea
106
+ initialFiles={initialFiles}
107
+ acceptedFiles={acceptedFiles}
108
+ onChange={setFiles}
109
+ filesLimit={maxRows}
110
+ />
111
+ <HelperText name={name} hint={hint} />
112
+ </Fieldset>
113
+ );
114
+ }
115
+
116
+ FileArray.Fieldset = function EmptyFieldset() {
117
+ return null;
118
+ };
119
+
120
+ FileArray.propTypes = {
121
+ name: PropTypes.string,
122
+ accept: PropTypes.string,
123
+ label: PropTypes.string,
124
+ subform: PropTypes.arrayOf(PropTypes.object),
125
+ hint: PropTypes.string,
126
+ maxRows: PropTypes.number,
127
+ };
128
+
129
+ export default withWQ(FileArray, { fallback: FileArrayFallback });
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ import { useComponents, withWQ, createFallbackComponent } from "@wq/react";
3
+ import PropTypes from "prop-types";
4
+
5
+ const FlatFieldsetFallback = {
6
+ components: {
7
+ Typography: createFallbackComponent("Typography", "@wq/material"),
8
+ },
9
+ };
10
+
11
+ function FlatFieldset({ label, children }) {
12
+ const { Typography } = useComponents();
13
+ return (
14
+ <>
15
+ <Typography color="textSecondary">{label}</Typography>
16
+ {children}
17
+ </>
18
+ );
19
+ }
20
+
21
+ FlatFieldset.propTypes = {
22
+ label: PropTypes.string,
23
+ children: PropTypes.node,
24
+ };
25
+
26
+ export default withWQ(FlatFieldset, { fallback: FlatFieldsetFallback });
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ import { withWQ } from "@wq/react";
3
+ import { useField } from "formik";
4
+ import { FormHelperText } from "@mui/material";
5
+
6
+ function FormError(props) {
7
+ const [, { error }] = useField("__other__");
8
+ if (!error) {
9
+ return null;
10
+ }
11
+ return (
12
+ <FormHelperText error {...props}>
13
+ {error}
14
+ </FormHelperText>
15
+ );
16
+ }
17
+
18
+ export default withWQ(FormError);
@@ -0,0 +1,27 @@
1
+ import React from "react";
2
+ import { withWQ } from "@wq/react";
3
+ import { Form } from "formik";
4
+ import PropTypes from "prop-types";
5
+
6
+ function FormRoot({ children }) {
7
+ return (
8
+ <div style={{ flex: 1, display: "flex", justifyContent: "center" }}>
9
+ <Form
10
+ style={{
11
+ width: "100%",
12
+ maxWidth: "70em",
13
+ padding: "1em",
14
+ boxSizing: "border-box",
15
+ }}
16
+ >
17
+ {children}
18
+ </Form>
19
+ </div>
20
+ );
21
+ }
22
+
23
+ FormRoot.propTypes = {
24
+ children: PropTypes.node,
25
+ };
26
+
27
+ export default withWQ(FormRoot);
@@ -0,0 +1,52 @@
1
+ import React from "react";
2
+ import { withWQ } from "@wq/react";
3
+ import PropTypes from "prop-types";
4
+
5
+ function GeoHelpIcon({ name, type }) {
6
+ const iconClass = getIconClass(name, type);
7
+ if (!iconClass) {
8
+ return `{${name}}`;
9
+ }
10
+
11
+ return (
12
+ <span
13
+ className={iconClass}
14
+ style={{
15
+ display: "inline-block",
16
+ width: 18,
17
+ height: 18,
18
+ verticalAlign: "middle",
19
+ }}
20
+ />
21
+ );
22
+ }
23
+
24
+ GeoHelpIcon.propTypes = {
25
+ name: PropTypes.string,
26
+ type: PropTypes.string,
27
+ };
28
+
29
+ export default withWQ(GeoHelpIcon);
30
+
31
+ const SHAPES = ["point", "line", "polygon"],
32
+ ICONS = SHAPES.map((shape) => `${shape.toUpperCase()}_ICON`);
33
+
34
+ function getIconClass(iconName, drawType) {
35
+ let shape = null;
36
+
37
+ if (iconName === "TOOL_ICON") {
38
+ if (drawType === "line_string") {
39
+ shape = "line";
40
+ } else if (SHAPES.includes(drawType)) {
41
+ shape = drawType;
42
+ }
43
+ } else if (ICONS.includes(iconName)) {
44
+ shape = SHAPES[ICONS.indexOf(iconName)];
45
+ }
46
+
47
+ if (shape) {
48
+ return `mapbox-gl-draw_${shape}`;
49
+ } else {
50
+ return null;
51
+ }
52
+ }
@@ -0,0 +1,28 @@
1
+ import React from "react";
2
+ import { withWQ } from "@wq/react";
3
+ import { useFormikContext, getIn } from "formik";
4
+ import { FormHelperText } from "@mui/material";
5
+ import PropTypes from "prop-types";
6
+
7
+ function HelperText({ name, hint }) {
8
+ const { errors, touched } = useFormikContext(),
9
+ error = getIn(errors, name),
10
+ showError = !!error && !!getIn(touched, name);
11
+
12
+ if (showError) {
13
+ hint = error;
14
+ }
15
+
16
+ if (!hint) {
17
+ return null;
18
+ }
19
+
20
+ return <FormHelperText error={!!showError}>{hint}</FormHelperText>;
21
+ }
22
+
23
+ HelperText.propTypes = {
24
+ name: PropTypes.string,
25
+ hint: PropTypes.string,
26
+ };
27
+
28
+ export default withWQ(HelperText);
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import { withWQ } from "@wq/react";
3
+ import { Field } from "formik";
4
+ import HelperText from "./HelperText.js";
5
+ import PropTypes from "prop-types";
6
+
7
+ function Hidden(props) {
8
+ return (
9
+ <>
10
+ <Field {...props} type="hidden" />
11
+ <HelperText name={props.name} hint={props.hint} />
12
+ </>
13
+ );
14
+ }
15
+
16
+ Hidden.propTypes = {
17
+ name: PropTypes.string,
18
+ hint: PropTypes.string,
19
+ };
20
+
21
+ export default withWQ(Hidden);
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ import { useComponents, withWQ, createFallbackComponent } from "@wq/react";
3
+ import { useFormikContext } from "formik";
4
+
5
+ const IconSubmitButtonFallback = {
6
+ components: {
7
+ IconButton: createFallbackComponent("IconButton", "@wq/material"),
8
+ },
9
+ };
10
+
11
+ function IconSubmitButton(props) {
12
+ const { IconButton } = useComponents(),
13
+ { isSubmitting } = useFormikContext();
14
+
15
+ return <IconButton type="submit" disabled={isSubmitting} {...props} />;
16
+ }
17
+
18
+ export default withWQ(IconSubmitButton, { fallback: IconSubmitButtonFallback });
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import { withWQ } from "@wq/react";
3
+ import File from "./File.js";
4
+
5
+ function Image(props) {
6
+ return <File accept="image/*" {...props} />;
7
+ }
8
+
9
+ export default withWQ(Image);
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+ import { withWQ } from "@wq/react";
3
+ import { Field } from "formik";
4
+ import { TextField } from "formik-mui";
5
+ import { useHtmlInput } from "@wq/form-common";
6
+ import PropTypes from "prop-types";
7
+
8
+ function Input({ hint, inputProps, ...rest }) {
9
+ const { name, type, maxLength } = useHtmlInput(rest);
10
+ return (
11
+ <Field
12
+ name={name}
13
+ fullWidth
14
+ margin="dense"
15
+ component={TextField}
16
+ helperText={hint}
17
+ inputProps={{ maxLength, ...inputProps }}
18
+ {...rest}
19
+ type={type}
20
+ />
21
+ );
22
+ }
23
+
24
+ Input.propTypes = {
25
+ name: PropTypes.string,
26
+ hint: PropTypes.string,
27
+ inputProps: PropTypes.object,
28
+ };
29
+
30
+ export default withWQ(Input);
@@ -0,0 +1,38 @@
1
+ import React from "react";
2
+ import { withWQ } from "@wq/react";
3
+ import { Field } from "formik";
4
+ import { RadioGroup } from "formik-mui";
5
+ import {
6
+ FormControl,
7
+ FormLabel,
8
+ FormControlLabel,
9
+ Radio as MuiRadio,
10
+ } from "@mui/material";
11
+ import HelperText from "./HelperText.js";
12
+ import PropTypes from "prop-types";
13
+
14
+ function Radio({ choices, label, ...rest }) {
15
+ return (
16
+ <FormControl component="fieldset" fullWidth margin="dense">
17
+ <FormLabel component="legend">{label}</FormLabel>
18
+ <Field component={RadioGroup} {...rest}>
19
+ {choices.map(({ name, label }) => (
20
+ <FormControlLabel
21
+ key={name}
22
+ value={name}
23
+ label={label}
24
+ control={<MuiRadio />}
25
+ />
26
+ ))}
27
+ </Field>
28
+ <HelperText name={rest.name} hint={rest.hint} />
29
+ </FormControl>
30
+ );
31
+ }
32
+
33
+ Radio.propTypes = {
34
+ choices: PropTypes.arrayOf(PropTypes.object),
35
+ label: PropTypes.string,
36
+ };
37
+
38
+ export default withWQ(Radio);