@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/.turbo/turbo-build.log +7 -7
- package/build/index.d.ts +105 -38
- package/build/index.js +447 -207
- package/build/index.mjs +435 -195
- package/package.json +8 -6
- package/src/AddListEntryForm.tsx +123 -0
- package/src/Form.stories.tsx +295 -0
- package/src/Form.tsx +39 -49
- package/src/FormPathContext.tsx +73 -0
- package/src/FormRow.tsx +15 -2
- package/src/ScmFormContext.tsx +1 -0
- package/src/ScmFormListContext.tsx +65 -0
- package/src/base/help/Help.tsx +4 -6
- package/src/checkbox/ControlledCheckboxField.tsx +11 -8
- package/src/helpers.ts +27 -0
- package/src/index.ts +28 -1
- package/src/input/ControlledInputField.tsx +16 -9
- package/src/input/ControlledSecretConfirmationField.tsx +28 -17
- package/src/list/ControlledList.tsx +88 -0
- package/src/select/ControlledSelectField.tsx +16 -9
- package/src/select/Select.tsx +18 -8
- package/src/table/ControlledColumn.tsx +49 -0
- package/src/table/ControlledTable.tsx +100 -0
- package/src/Form.stories.mdx +0 -160
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.
|
|
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.
|
|
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.
|
|
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.
|
|
44
|
-
"@scm-manager/ui-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
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
|
-
<
|
|
44
|
+
<FormRowDiv ref={ref} className={classNames("is-flex is-flex-wrap-wrap", className)} {...rest}>
|
|
32
45
|
{children}
|
|
33
|
-
</
|
|
46
|
+
</FormRowDiv>
|
|
34
47
|
)
|
|
35
48
|
);
|
|
36
49
|
|