@scm-manager/ui-forms 2.42.3 → 2.42.4-20230213-150253

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@scm-manager/ui-forms",
3
3
  "private": false,
4
- "version": "2.42.3",
4
+ "version": "2.42.4-20230213-150253",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
7
7
  "module": "build/index.mjs",
@@ -16,7 +16,7 @@
16
16
  "@scm-manager/eslint-config": "^2.16.0",
17
17
  "@scm-manager/prettier-config": "^2.10.1",
18
18
  "@scm-manager/tsconfig": "^2.13.0",
19
- "@scm-manager/ui-styles": "2.42.3",
19
+ "@scm-manager/ui-styles": "2.42.4-20230213-150253",
20
20
  "@storybook/addon-actions": "^6.5.10",
21
21
  "@storybook/addon-essentials": "^6.5.10",
22
22
  "@storybook/addon-interactions": "^6.5.10",
@@ -32,16 +32,18 @@
32
32
  "tsup": "^6.2.3"
33
33
  },
34
34
  "peerDependencies": {
35
- "@scm-manager/ui-components": "2.42.3",
35
+ "@scm-manager/ui-components": "2.42.4-20230213-150253",
36
36
  "classnames": "^2.3.1",
37
37
  "react": "17",
38
38
  "react-hook-form": "7",
39
39
  "react-i18next": "11",
40
- "react-query": "3"
40
+ "react-query": "3",
41
+ "styled-components": "5"
41
42
  },
42
43
  "dependencies": {
43
- "@scm-manager/ui-buttons": "2.42.3",
44
- "@scm-manager/ui-api": "2.42.3"
44
+ "@scm-manager/ui-buttons": "2.42.4-20230213-150253",
45
+ "@scm-manager/ui-overlays": "2.42.4-20230213-150253",
46
+ "@scm-manager/ui-api": "2.42.4-20230213-150253"
45
47
  },
46
48
  "prettier": "@scm-manager/prettier-config",
47
49
  "eslintConfig": {
@@ -0,0 +1,123 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2020-present Cloudogu GmbH and Contributors
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ import React, { useCallback, useEffect } from "react";
26
+ import { ScmFormPathContextProvider, useScmFormPathContext } from "./FormPathContext";
27
+ import { ScmFormContextProvider, useScmFormContext } from "./ScmFormContext";
28
+ import { DeepPartial, UseFieldArrayReturn, useForm, UseFormReturn } from "react-hook-form";
29
+ import { Button } from "@scm-manager/ui-buttons";
30
+ import { prefixWithoutIndices } from "./helpers";
31
+ import { useScmFormListContext } from "./ScmFormListContext";
32
+ import { useTranslation } from "react-i18next";
33
+ import styled from "styled-components";
34
+
35
+ const StyledHr = styled.hr`
36
+ &:last-child {
37
+ display: none;
38
+ }
39
+ `;
40
+
41
+ type RenderProps<T extends Record<string, unknown>> = Omit<
42
+ UseFormReturn<T>,
43
+ "register" | "unregister" | "handleSubmit" | "control"
44
+ >;
45
+
46
+ type Props<FormType extends Record<string, unknown>, DefaultValues extends FormType> = {
47
+ children: ((renderProps: RenderProps<FormType>) => React.ReactNode | React.ReactNode[]) | React.ReactNode;
48
+ defaultValues: DefaultValues;
49
+ submit?: (data: FormType, append: UseFieldArrayReturn["append"]) => unknown;
50
+ /**
51
+ * @default true
52
+ */
53
+ disableSubmitWhenDirty?: boolean;
54
+ };
55
+
56
+ /**
57
+ * @beta
58
+ * @since 2.43.0
59
+ */
60
+ function AddListEntryForm<FormType extends Record<string, unknown>, DefaultValues extends FormType>({
61
+ children,
62
+ defaultValues,
63
+ submit,
64
+ disableSubmitWhenDirty = true,
65
+ }: Props<FormType, DefaultValues>) {
66
+ const [defaultTranslate] = useTranslation("commons", { keyPrefix: "form" });
67
+ const { readOnly, t } = useScmFormContext();
68
+ const nameWithPrefix = useScmFormPathContext();
69
+ const prefixedNameWithoutIndices = prefixWithoutIndices(nameWithPrefix);
70
+ const { append, isNested } = useScmFormListContext();
71
+ const form = useForm<FormType, DefaultValues>({
72
+ mode: "onChange",
73
+ defaultValues: defaultValues as DeepPartial<FormType>,
74
+ });
75
+
76
+ const {
77
+ reset,
78
+ formState: { isSubmitSuccessful, isDirty, isValid },
79
+ } = form;
80
+
81
+ const translateWithExtraPrefix = useCallback<typeof t>(
82
+ (key, ...args) => t(`${prefixedNameWithoutIndices}.${key}`, ...(args as any)),
83
+ [prefixedNameWithoutIndices, t]
84
+ );
85
+
86
+ const onSubmit = useCallback((data) => (submit ? submit(data, append) : append(data)), [append, submit]);
87
+
88
+ const submitButtonLabel = translateWithExtraPrefix("add", {
89
+ defaultValue: defaultTranslate("list.add.label", { entity: translateWithExtraPrefix("entity") }),
90
+ });
91
+
92
+ useEffect(() => {
93
+ if (isSubmitSuccessful) {
94
+ reset(defaultValues);
95
+ }
96
+ }, [defaultValues, isSubmitSuccessful, reset]);
97
+
98
+ if (readOnly) {
99
+ return null;
100
+ }
101
+
102
+ return (
103
+ <ScmFormContextProvider {...form} t={translateWithExtraPrefix} formId={nameWithPrefix}>
104
+ <ScmFormPathContextProvider path="">
105
+ <form id={nameWithPrefix} onSubmit={form.handleSubmit(onSubmit)}></form>
106
+ {typeof children === "function" ? children(form) : children}
107
+ <div className="level-right">
108
+ <Button
109
+ form={nameWithPrefix}
110
+ type="submit"
111
+ variant={isNested ? undefined : "secondary"}
112
+ disabled={(disableSubmitWhenDirty && !isDirty) || !isValid}
113
+ >
114
+ {submitButtonLabel}
115
+ </Button>
116
+ </div>
117
+ <StyledHr />
118
+ </ScmFormPathContextProvider>
119
+ </ScmFormContextProvider>
120
+ );
121
+ }
122
+
123
+ export default AddListEntryForm;
@@ -0,0 +1,295 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2020-present Cloudogu GmbH and Contributors
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ /* eslint-disable no-console */
25
+ import React from "react";
26
+ import { storiesOf } from "@storybook/react";
27
+ import Form from "./Form";
28
+ import FormRow from "./FormRow";
29
+ import ControlledInputField from "./input/ControlledInputField";
30
+ import ControlledSecretConfirmationField from "./input/ControlledSecretConfirmationField";
31
+ import ControlledCheckboxField from "./checkbox/ControlledCheckboxField";
32
+ import ControlledList from "./list/ControlledList";
33
+ import ControlledSelectField from "./select/ControlledSelectField";
34
+ import ControlledColumn from "./table/ControlledColumn";
35
+ import ControlledTable from "./table/ControlledTable";
36
+ import AddListEntryForm from "./AddListEntryForm";
37
+ import { ScmFormListContextProvider } from "./ScmFormListContext";
38
+
39
+ export type SimpleWebHookConfiguration = {
40
+ urlPattern: string;
41
+ executeOnEveryCommit: boolean;
42
+ sendCommitData: boolean;
43
+ method: string;
44
+ headers: WebhookHeader[];
45
+ };
46
+
47
+ export type WebhookHeader = {
48
+ key: string;
49
+ value: string;
50
+ concealed: boolean;
51
+ };
52
+
53
+ storiesOf("Forms", module)
54
+ .add("Creation", () => (
55
+ <Form
56
+ onSubmit={console.log}
57
+ translationPath={["sample", "form"]}
58
+ defaultValues={{
59
+ name: "",
60
+ password: "",
61
+ active: true,
62
+ }}
63
+ >
64
+ <FormRow>
65
+ <ControlledInputField name="name" />
66
+ </FormRow>
67
+ <FormRow>
68
+ <ControlledSecretConfirmationField name="password" />
69
+ </FormRow>
70
+ <FormRow>
71
+ <ControlledCheckboxField name="active" />
72
+ </FormRow>
73
+ </Form>
74
+ ))
75
+ .add("Editing", () => (
76
+ <Form
77
+ onSubmit={console.log}
78
+ translationPath={["sample", "form"]}
79
+ defaultValues={{
80
+ name: "trillian",
81
+ password: "secret",
82
+ active: true,
83
+ }}
84
+ >
85
+ <FormRow>
86
+ <ControlledInputField name="name" />
87
+ </FormRow>
88
+ <FormRow>
89
+ <ControlledSecretConfirmationField name="password" />
90
+ </FormRow>
91
+ <FormRow>
92
+ <ControlledCheckboxField name="active" />
93
+ </FormRow>
94
+ </Form>
95
+ ))
96
+ .add("GlobalConfiguration", () => (
97
+ <Form
98
+ onSubmit={console.log}
99
+ translationPath={["sample", "form"]}
100
+ defaultValues={{
101
+ url: "",
102
+ filter: "",
103
+ username: "",
104
+ password: "",
105
+ roleLevel: "",
106
+ updateIssues: false,
107
+ disableRepoConfig: false,
108
+ }}
109
+ >
110
+ {({ watch }) => (
111
+ <>
112
+ <FormRow>
113
+ <ControlledInputField name="url" label="URL" helpText="URL of Jira installation (with context path)." />
114
+ </FormRow>
115
+ <FormRow>
116
+ <ControlledInputField name="filter" label="Project Filter" helpText="Filters for jira project key." />
117
+ </FormRow>
118
+ <FormRow>
119
+ <ControlledCheckboxField
120
+ name="updateIssues"
121
+ label="Update Jira Issues"
122
+ helpText="Enable the automatic update function."
123
+ />
124
+ </FormRow>
125
+ <FormRow hidden={watch("filter")}>
126
+ <ControlledInputField
127
+ name="username"
128
+ label="Username"
129
+ helpText="Jira username for connection."
130
+ className="is-half"
131
+ />
132
+ <ControlledInputField
133
+ name="password"
134
+ label="Password"
135
+ helpText="Jira password for connection."
136
+ type="password"
137
+ className="is-half"
138
+ />
139
+ </FormRow>
140
+ <FormRow hidden={watch("filter")}>
141
+ <ControlledInputField
142
+ name="roleLevel"
143
+ label="Role Visibility"
144
+ helpText="Defines for which Project Role the comments are visible."
145
+ />
146
+ </FormRow>
147
+ <FormRow>
148
+ <ControlledCheckboxField
149
+ name="disableRepoConfig"
150
+ label="Do not allow repository configuration"
151
+ helpText="Do not allow repository owners to configure jira instances."
152
+ />
153
+ </FormRow>
154
+ </>
155
+ )}
156
+ </Form>
157
+ ))
158
+ .add("RepoConfiguration", () => (
159
+ <Form
160
+ onSubmit={console.log}
161
+ translationPath={["sample", "form"]}
162
+ defaultValues={{
163
+ url: "",
164
+ option: "",
165
+ anotherOption: "",
166
+ disableA: false,
167
+ disableB: false,
168
+ disableC: true,
169
+ }}
170
+ >
171
+ <ControlledInputField name="url" />
172
+ <ControlledInputField name="option" />
173
+ <ControlledInputField name="anotherOption" />
174
+ <ControlledCheckboxField name="disableA" />
175
+ <ControlledCheckboxField name="disableB" />
176
+ <ControlledCheckboxField name="disableC" />
177
+ </Form>
178
+ ))
179
+ .add("ReadOnly", () => (
180
+ <Form
181
+ onSubmit={console.log}
182
+ translationPath={["sample", "form"]}
183
+ defaultValues={{
184
+ name: "trillian",
185
+ password: "secret",
186
+ active: true,
187
+ }}
188
+ readOnly
189
+ >
190
+ <FormRow>
191
+ <ControlledInputField name="name" />
192
+ </FormRow>
193
+ <FormRow>
194
+ <ControlledSecretConfirmationField name="password" />
195
+ </FormRow>
196
+ <FormRow>
197
+ <ControlledCheckboxField name="active" />
198
+ </FormRow>
199
+ </Form>
200
+ ))
201
+ .add("Nested", () => (
202
+ <Form
203
+ onSubmit={console.log}
204
+ translationPath={["sample", "form"]}
205
+ defaultValues={{
206
+ webhooks: [
207
+ {
208
+ urlPattern: "https://hitchhiker.com",
209
+ executeOnEveryCommit: false,
210
+ sendCommitData: false,
211
+ method: "post",
212
+ headers: [
213
+ {
214
+ key: "test",
215
+ value: "val",
216
+ concealed: false,
217
+ },
218
+ {
219
+ key: "secret",
220
+ value: "password",
221
+ concealed: true,
222
+ },
223
+ ],
224
+ },
225
+ {
226
+ urlPattern: "http://test.com",
227
+ executeOnEveryCommit: false,
228
+ sendCommitData: false,
229
+ method: "get",
230
+ headers: [
231
+ {
232
+ key: "other",
233
+ value: "thing",
234
+ concealed: true,
235
+ },
236
+ {
237
+ key: "stuff",
238
+ value: "haha",
239
+ concealed: false,
240
+ },
241
+ ],
242
+ },
243
+ ],
244
+ }}
245
+ >
246
+ <ScmFormListContextProvider name="webhooks">
247
+ <ControlledList withDelete>
248
+ {({ value: webhook }) => (
249
+ <>
250
+ <FormRow>
251
+ <ControlledSelectField name="method">
252
+ {["post", "get", "put"].map((value) => (
253
+ <option value={value} key={value}>
254
+ {value}
255
+ </option>
256
+ ))}
257
+ </ControlledSelectField>
258
+ <ControlledInputField name="urlPattern" />
259
+ </FormRow>
260
+ <FormRow>
261
+ <ControlledCheckboxField name="executeOnEveryCommit" />
262
+ </FormRow>
263
+ <FormRow>
264
+ <ControlledCheckboxField name="sendCommitData" />
265
+ </FormRow>
266
+ <details className="has-background-dark-25 mb-2 p-2">
267
+ <summary className="is-clickable">Headers</summary>
268
+ <div>
269
+ <ScmFormListContextProvider name="headers">
270
+ <ControlledTable withDelete>
271
+ <ControlledColumn name="key" />
272
+ <ControlledColumn name="value" />
273
+ <ControlledColumn name="concealed">{(value) => (value ? <b>Hallo</b> : null)}</ControlledColumn>
274
+ </ControlledTable>
275
+ <AddListEntryForm defaultValues={{ key: "", value: "", concealed: false }}>
276
+ <ControlledInputField
277
+ name="key"
278
+ rules={{
279
+ validate: (newKey) =>
280
+ !(webhook as SimpleWebHookConfiguration).headers.some(({ key }) => newKey === key),
281
+ required: true,
282
+ }}
283
+ />
284
+ <ControlledInputField name="value" />
285
+ <ControlledCheckboxField name="concealed" />
286
+ </AddListEntryForm>
287
+ </ScmFormListContextProvider>
288
+ </div>
289
+ </details>
290
+ </>
291
+ )}
292
+ </ControlledList>
293
+ </ScmFormListContextProvider>
294
+ </Form>
295
+ ));
package/src/Form.tsx CHANGED
@@ -28,12 +28,7 @@ import { ErrorNotification, Level } from "@scm-manager/ui-components";
28
28
  import { ScmFormContextProvider } from "./ScmFormContext";
29
29
  import { useTranslation } from "react-i18next";
30
30
  import { Button } from "@scm-manager/ui-buttons";
31
- import FormRow from "./FormRow";
32
- import ControlledInputField from "./input/ControlledInputField";
33
- import ControlledCheckboxField from "./checkbox/ControlledCheckboxField";
34
- import ControlledSecretConfirmationField from "./input/ControlledSecretConfirmationField";
35
31
  import { HalRepresentation } from "@scm-manager/ui-types";
36
- import ControlledSelectField from "./select/ControlledSelectField";
37
32
 
38
33
  type RenderProps<T extends Record<string, unknown>> = Omit<
39
34
  UseFormReturn<T>,
@@ -81,9 +76,24 @@ function Form<FormType extends Record<string, unknown>, DefaultValues extends Fo
81
76
  const { formState, handleSubmit, reset } = form;
82
77
  const [ns, prefix] = translationPath;
83
78
  const { t } = useTranslation(ns, { keyPrefix: prefix });
79
+ const [defaultTranslate] = useTranslation("commons", { keyPrefix: "form" });
80
+ const translateWithFallback = useCallback<typeof t>(
81
+ (key, ...args) => {
82
+ const translation = t(key, ...(args as any));
83
+ if (translation === `${prefix}.${key}`) {
84
+ return "";
85
+ }
86
+ return translation;
87
+ },
88
+ [prefix, t]
89
+ );
84
90
  const { isDirty, isValid, isSubmitting, isSubmitSuccessful } = formState;
85
91
  const [error, setError] = useState<Error | null | undefined>();
86
92
  const [showSuccessNotification, setShowSuccessNotification] = useState(false);
93
+ const submitButtonLabel = t("submit", { defaultValue: defaultTranslate("submit") });
94
+ const successNotification = translateWithFallback("submit-success-notification", {
95
+ defaultValue: defaultTranslate("submit-success-notification"),
96
+ });
87
97
 
88
98
  // See https://react-hook-form.com/api/useform/reset/
89
99
  useEffect(() => {
@@ -100,17 +110,6 @@ function Form<FormType extends Record<string, unknown>, DefaultValues extends Fo
100
110
  }
101
111
  }, [isDirty]);
102
112
 
103
- const translateWithFallback = useCallback<typeof t>(
104
- (key, ...args) => {
105
- const translation = t(key, ...(args as any));
106
- if (translation === `${prefix}.${key}`) {
107
- return "";
108
- }
109
- return translation;
110
- },
111
- [prefix, t]
112
- );
113
-
114
113
  const submit = useCallback(
115
114
  async (data) => {
116
115
  setError(null);
@@ -128,40 +127,31 @@ function Form<FormType extends Record<string, unknown>, DefaultValues extends Fo
128
127
  );
129
128
 
130
129
  return (
131
- <ScmFormContextProvider {...form} readOnly={isSubmitting || readOnly} t={translateWithFallback}>
132
- <form onSubmit={handleSubmit(submit)}>
133
- {showSuccessNotification ? (
134
- <SuccessNotification
135
- label={translateWithFallback("submit-success-notification")}
136
- hide={() => setShowSuccessNotification(false)}
137
- />
138
- ) : null}
139
- {typeof children === "function" ? children(form) : children}
140
- {error ? <ErrorNotification error={error} /> : null}
141
- {!readOnly ? (
142
- <Level
143
- right={
144
- <Button
145
- type="submit"
146
- variant="primary"
147
- testId={submitButtonTestId ?? "submit-button"}
148
- disabled={!isDirty || !isValid}
149
- isLoading={isSubmitting}
150
- >
151
- {t("submit")}
152
- </Button>
153
- }
154
- />
155
- ) : null}
156
- </form>
130
+ <ScmFormContextProvider {...form} readOnly={isSubmitting || readOnly} t={translateWithFallback} formId={prefix}>
131
+ <form onSubmit={handleSubmit(submit)} id={prefix}></form>
132
+ {showSuccessNotification ? (
133
+ <SuccessNotification label={successNotification} hide={() => setShowSuccessNotification(false)} />
134
+ ) : null}
135
+ {typeof children === "function" ? children(form) : children}
136
+ {error ? <ErrorNotification error={error} /> : null}
137
+ {!readOnly ? (
138
+ <Level
139
+ right={
140
+ <Button
141
+ type="submit"
142
+ variant="primary"
143
+ testId={submitButtonTestId ?? "submit-button"}
144
+ disabled={!isDirty || !isValid}
145
+ isLoading={isSubmitting}
146
+ form={prefix}
147
+ >
148
+ {submitButtonLabel}
149
+ </Button>
150
+ }
151
+ />
152
+ ) : null}
157
153
  </ScmFormContextProvider>
158
154
  );
159
155
  }
160
156
 
161
- export default Object.assign(Form, {
162
- Row: FormRow,
163
- Input: ControlledInputField,
164
- Checkbox: ControlledCheckboxField,
165
- SecretConfirmation: ControlledSecretConfirmationField,
166
- Select: ControlledSelectField,
167
- });
157
+ export default Form;
@@ -0,0 +1,73 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2020-present Cloudogu GmbH and Contributors
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ import React, { FC, useContext, useMemo } from "react";
26
+
27
+ const ScmFormPathContext = React.createContext<string>("");
28
+
29
+ export function useScmFormPathContext() {
30
+ return useContext(ScmFormPathContext);
31
+ }
32
+
33
+ export const ScmFormPathContextProvider: FC<{ path: string }> = ({ children, path }) => (
34
+ <ScmFormPathContext.Provider value={path}>{children}</ScmFormPathContext.Provider>
35
+ );
36
+
37
+ /**
38
+ * This component removes redundancy by declaring a prefix that is shared by all enclosed form-related components.
39
+ * It might be helpful when the data structure of a list's items does not correspond with the individual list item's
40
+ * form structure.
41
+ *
42
+ * @beta
43
+ * @since 2.43.0
44
+ * @example ```
45
+ * // For data of structure
46
+ * {
47
+ * subForm: { foo: boolean, bar: string };
48
+ * flag: boolean;
49
+ * tag: string;
50
+ * }
51
+ * // Because we use ConfigurationForm we get and update the whole object.
52
+ * // We only want to create a form for 'subForm' without having to repeat it for every subField
53
+ * // while keeping the original data structure for the whole form. *
54
+ *
55
+ * // Using this component we can write:
56
+ * <Form.PathContext path="subForm">
57
+ * <Form.Input name="foo" />
58
+ * <Form.Checkbox name="bar" />
59
+ * </Form.PathContext>
60
+ *
61
+ * // Instead of
62
+ *
63
+ * <Form.Input name="subForm.foo" />
64
+ * <Form.Checkbox name="subForm.bar" />
65
+ *
66
+ * // This pattern becomes useful in complex or large forms.
67
+ * ```
68
+ */
69
+ export const ScmNestedFormPathContextProvider: FC<{ path: string }> = ({ children, path }) => {
70
+ const prefix = useScmFormPathContext();
71
+ const pathWithPrefix = useMemo(() => (prefix ? `${prefix}.${path}` : path), [path, prefix]);
72
+ return <ScmFormPathContext.Provider value={pathWithPrefix}>{children}</ScmFormPathContext.Provider>;
73
+ };
package/src/FormRow.tsx CHANGED
@@ -24,13 +24,26 @@
24
24
 
25
25
  import React, { HTMLProps } from "react";
26
26
  import classNames from "classnames";
27
+ import styled from "styled-components";
28
+
29
+ const FormRowDiv = styled.div`
30
+ .field {
31
+ margin-left: 0;
32
+ }
33
+
34
+ gap: 1rem;
35
+
36
+ &:not(:last-child) {
37
+ margin-bottom: 1rem;
38
+ }
39
+ `;
27
40
 
28
41
  const FormRow = React.forwardRef<HTMLDivElement, HTMLProps<HTMLDivElement>>(
29
42
  ({ className, children, hidden, ...rest }, ref) =>
30
43
  hidden ? null : (
31
- <div ref={ref} className={classNames("columns", className)} {...rest}>
44
+ <FormRowDiv ref={ref} className={classNames("is-flex is-flex-wrap-wrap", className)} {...rest}>
32
45
  {children}
33
- </div>
46
+ </FormRowDiv>
34
47
  )
35
48
  );
36
49