@scm-manager/ui-core 3.0.0-20240024-101702

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 (128) hide show
  1. package/.storybook/.babelrc +3 -0
  2. package/.storybook/RemoveThemesPlugin.js +57 -0
  3. package/.storybook/main.js +92 -0
  4. package/.storybook/preview-head.html +25 -0
  5. package/.storybook/preview.js +95 -0
  6. package/.storybook/withApiProvider.js +46 -0
  7. package/docs/introduction.stories.mdx +64 -0
  8. package/docs/usage.stories.mdx +22 -0
  9. package/package.json +81 -0
  10. package/src/base/buttons/Button.stories.tsx +89 -0
  11. package/src/base/buttons/Button.test.stories.mdx +74 -0
  12. package/src/base/buttons/Button.tsx +143 -0
  13. package/src/base/buttons/Icon.tsx +58 -0
  14. package/src/base/buttons/a11y.test.ts +34 -0
  15. package/src/base/buttons/docs/introduction.stories.mdx +64 -0
  16. package/src/base/buttons/docs/usage.stories.mdx +22 -0
  17. package/src/base/buttons/image-snapshot.test.ts +33 -0
  18. package/src/base/buttons/index.ts +26 -0
  19. package/src/base/forms/AddListEntryForm.tsx +127 -0
  20. package/src/base/forms/ConfigurationForm.tsx +59 -0
  21. package/src/base/forms/Form.stories.tsx +453 -0
  22. package/src/base/forms/Form.tsx +215 -0
  23. package/src/base/forms/FormPathContext.tsx +73 -0
  24. package/src/base/forms/FormRow.tsx +37 -0
  25. package/src/base/forms/ScmFormContext.tsx +43 -0
  26. package/src/base/forms/ScmFormListContext.tsx +65 -0
  27. package/src/base/forms/base/Control.tsx +34 -0
  28. package/src/base/forms/base/Field.tsx +35 -0
  29. package/src/base/forms/base/field-message/FieldMessage.tsx +34 -0
  30. package/src/base/forms/base/help/Help.tsx +34 -0
  31. package/src/base/forms/base/label/Label.tsx +35 -0
  32. package/src/base/forms/checkbox/Checkbox.stories.mdx +26 -0
  33. package/src/base/forms/checkbox/Checkbox.tsx +118 -0
  34. package/src/base/forms/checkbox/CheckboxField.tsx +39 -0
  35. package/src/base/forms/checkbox/ControlledCheckboxField.stories.mdx +36 -0
  36. package/src/base/forms/checkbox/ControlledCheckboxField.tsx +82 -0
  37. package/src/base/forms/chip-input/ChipInputField.stories.tsx +75 -0
  38. package/src/base/forms/chip-input/ChipInputField.tsx +169 -0
  39. package/src/base/forms/chip-input/ControlledChipInputField.tsx +111 -0
  40. package/src/base/forms/combobox/Combobox.stories.tsx +125 -0
  41. package/src/base/forms/combobox/Combobox.tsx +223 -0
  42. package/src/base/forms/combobox/ComboboxField.tsx +62 -0
  43. package/src/base/forms/combobox/ControlledComboboxField.tsx +96 -0
  44. package/src/base/forms/headless-chip-input/ChipInput.tsx +237 -0
  45. package/src/base/forms/helpers.ts +74 -0
  46. package/src/base/forms/index.ts +85 -0
  47. package/src/base/forms/input/ControlledInputField.stories.mdx +36 -0
  48. package/src/base/forms/input/ControlledInputField.tsx +87 -0
  49. package/src/base/forms/input/ControlledSecretConfirmationField.stories.mdx +39 -0
  50. package/src/base/forms/input/ControlledSecretConfirmationField.tsx +138 -0
  51. package/src/base/forms/input/Input.stories.mdx +22 -0
  52. package/src/base/forms/input/Input.tsx +46 -0
  53. package/src/base/forms/input/InputField.stories.mdx +22 -0
  54. package/src/base/forms/input/InputField.tsx +61 -0
  55. package/src/base/forms/input/Textarea.stories.mdx +28 -0
  56. package/src/base/forms/input/Textarea.tsx +46 -0
  57. package/src/base/forms/list/ControlledList.tsx +88 -0
  58. package/src/base/forms/radio-button/ControlledRadioGroupField.tsx +94 -0
  59. package/src/base/forms/radio-button/RadioButton.stories.tsx +226 -0
  60. package/src/base/forms/radio-button/RadioButton.tsx +116 -0
  61. package/src/base/forms/radio-button/RadioButtonContext.tsx +42 -0
  62. package/src/base/forms/radio-button/RadioGroup.tsx +49 -0
  63. package/src/base/forms/radio-button/RadioGroupField.tsx +58 -0
  64. package/src/base/forms/resourceHooks.ts +164 -0
  65. package/src/base/forms/select/ControlledSelectField.tsx +87 -0
  66. package/src/base/forms/select/Select.tsx +57 -0
  67. package/src/base/forms/select/SelectField.tsx +63 -0
  68. package/src/base/forms/table/ControlledColumn.tsx +49 -0
  69. package/src/base/forms/table/ControlledTable.tsx +99 -0
  70. package/src/base/forms/variants.ts +27 -0
  71. package/src/base/helpers/devbuild.ts +44 -0
  72. package/src/base/helpers/index.ts +26 -0
  73. package/src/base/helpers/useAriaId.tsx +31 -0
  74. package/src/base/index.ts +34 -0
  75. package/src/base/layout/_helpers/with-classes.tsx +52 -0
  76. package/src/base/layout/card/Card.stories.tsx +113 -0
  77. package/src/base/layout/card/Card.tsx +76 -0
  78. package/src/base/layout/card/CardDetail.tsx +196 -0
  79. package/src/base/layout/card/CardRow.tsx +46 -0
  80. package/src/base/layout/card/CardTitle.tsx +59 -0
  81. package/src/base/layout/card-list/CardList.stories.tsx +201 -0
  82. package/src/base/layout/card-list/CardList.tsx +76 -0
  83. package/src/base/layout/collapsible/Collapsible.stories.tsx +45 -0
  84. package/src/base/layout/collapsible/Collapsible.tsx +87 -0
  85. package/src/base/layout/index.ts +93 -0
  86. package/src/base/layout/tabs/TabTrigger.tsx +46 -0
  87. package/src/base/layout/tabs/Tabs.stories.tsx +48 -0
  88. package/src/base/layout/tabs/Tabs.tsx +52 -0
  89. package/src/base/layout/tabs/TabsContent.tsx +33 -0
  90. package/src/base/layout/tabs/TabsList.tsx +41 -0
  91. package/src/base/layout/templates/data-page/DataPage.stories.tsx +201 -0
  92. package/src/base/layout/templates/data-page/DataPageHeader.tsx +100 -0
  93. package/src/base/misc/Image.tsx +32 -0
  94. package/src/base/misc/Level.tsx +40 -0
  95. package/src/base/misc/Loading.tsx +64 -0
  96. package/src/base/misc/SubSubtitle.tsx +36 -0
  97. package/src/base/misc/Subtitle.tsx +37 -0
  98. package/src/base/misc/Title.tsx +56 -0
  99. package/src/base/misc/index.ts +30 -0
  100. package/src/base/notifications/BackendErrorNotification.tsx +160 -0
  101. package/src/base/notifications/ErrorNotification.tsx +73 -0
  102. package/src/base/notifications/Notification.tsx +48 -0
  103. package/src/base/notifications/index.tsx +27 -0
  104. package/src/base/overlays/dialog/Dialog.stories.tsx +64 -0
  105. package/src/base/overlays/dialog/Dialog.tsx +85 -0
  106. package/src/base/overlays/index.ts +44 -0
  107. package/src/base/overlays/menu/Menu.stories.tsx +78 -0
  108. package/src/base/overlays/menu/Menu.tsx +213 -0
  109. package/src/base/overlays/menu/MenuTrigger.tsx +63 -0
  110. package/src/base/overlays/popover/Popover.stories.tsx +69 -0
  111. package/src/base/overlays/popover/Popover.tsx +95 -0
  112. package/src/base/overlays/tooltip/Tooltip.examples.js +41 -0
  113. package/src/base/overlays/tooltip/Tooltip.stories.mdx +52 -0
  114. package/src/base/overlays/tooltip/Tooltip.tsx +96 -0
  115. package/src/base/shortcuts/index.ts +28 -0
  116. package/src/base/shortcuts/iterator/callbackIterator.ts +220 -0
  117. package/src/base/shortcuts/iterator/keyboardIterator.test.tsx +431 -0
  118. package/src/base/shortcuts/iterator/keyboardIterator.tsx +141 -0
  119. package/src/base/shortcuts/usePauseShortcuts.ts +44 -0
  120. package/src/base/shortcuts/useShortcut.ts +110 -0
  121. package/src/base/shortcuts/useShortcutDocs.tsx +54 -0
  122. package/src/base/text/SplitAndReplace.stories.tsx +83 -0
  123. package/src/base/text/SplitAndReplace.tsx +65 -0
  124. package/src/base/text/index.ts +25 -0
  125. package/src/base/text/textSplitAndReplace.test.ts +134 -0
  126. package/src/base/text/textSplitAndReplace.ts +86 -0
  127. package/src/index.ts +25 -0
  128. package/tsconfig.json +6 -0
@@ -0,0 +1,88 @@
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 from "react";
26
+ import { Path, PathValue } from "react-hook-form";
27
+ import { useScmFormContext } from "../ScmFormContext";
28
+ import { ScmFormPathContextProvider, useScmFormPathContext } from "../FormPathContext";
29
+ import { prefixWithoutIndices } from "../helpers";
30
+ import { useScmFormListContext } from "../ScmFormListContext";
31
+ import { useTranslation } from "react-i18next";
32
+ import { Button } from "../../buttons";
33
+
34
+ type ArrayItemType<T> = T extends Array<infer U> ? U : unknown;
35
+
36
+ type RenderProps<T extends Record<string, unknown>, PATH extends Path<T>> = {
37
+ value: ArrayItemType<PathValue<T, PATH>>;
38
+ index: number;
39
+ remove: () => void;
40
+ };
41
+
42
+ type Props<T extends Record<string, unknown>, PATH extends Path<T>> = {
43
+ withDelete?: boolean;
44
+ children: ((renderProps: RenderProps<T, PATH>) => React.ReactNode | React.ReactNode[]) | React.ReactNode;
45
+ };
46
+
47
+ /**
48
+ * @beta
49
+ * @since 2.43.0
50
+ */
51
+ function ControlledList<T extends Record<string, unknown>, PATH extends Path<T>>({
52
+ withDelete,
53
+ children,
54
+ }: Props<T, PATH>) {
55
+ const [defaultTranslate] = useTranslation("commons", { keyPrefix: "form" });
56
+ const { readOnly, t } = useScmFormContext();
57
+ const nameWithPrefix = useScmFormPathContext();
58
+ const prefixedNameWithoutIndices = prefixWithoutIndices(nameWithPrefix);
59
+ const { fields, remove } = useScmFormListContext();
60
+
61
+ const deleteButtonTranslation = t(`${prefixedNameWithoutIndices}.delete`, {
62
+ defaultValue: defaultTranslate("list.delete.label", {
63
+ entity: t(`${prefixedNameWithoutIndices}.entity`),
64
+ }),
65
+ });
66
+
67
+ return (
68
+ <>
69
+ {fields.map((value, index) => (
70
+ <ScmFormPathContextProvider key={value.id} path={`${nameWithPrefix}.${index}`}>
71
+ {typeof children === "function"
72
+ ? children({ value: value as never, index, remove: () => remove(index) })
73
+ : children}
74
+ {withDelete && !readOnly ? (
75
+ <div className="level-right">
76
+ <Button variant="signal" onClick={() => remove(index)}>
77
+ {deleteButtonTranslation}
78
+ </Button>
79
+ </div>
80
+ ) : null}
81
+ <hr />
82
+ </ScmFormPathContextProvider>
83
+ ))}
84
+ </>
85
+ );
86
+ }
87
+
88
+ export default ControlledList;
@@ -0,0 +1,94 @@
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, { ComponentProps } from "react";
26
+ import RadioGroupField from "./RadioGroupField";
27
+ import { Controller, ControllerRenderProps, Path } from "react-hook-form";
28
+ import { useScmFormContext } from "../ScmFormContext";
29
+ import { useScmFormPathContext } from "../FormPathContext";
30
+ import { prefixWithoutIndices } from "../helpers";
31
+ import classNames from "classnames";
32
+ import { RadioButtonContextProvider } from "./RadioButtonContext";
33
+
34
+ type Props<T extends Record<string, unknown>> = Omit<
35
+ ComponentProps<typeof RadioGroupField>,
36
+ "label" | keyof ControllerRenderProps
37
+ > & {
38
+ name: Path<T>;
39
+ rules?: ComponentProps<typeof Controller>["rules"];
40
+ label?: string;
41
+ readOnly?: boolean;
42
+ };
43
+
44
+ /**
45
+ * @beta
46
+ * @since 2.48.0
47
+ */
48
+ function ControlledRadioGroupField<T extends Record<string, unknown>>({
49
+ name,
50
+ label,
51
+ helpText,
52
+ rules,
53
+ defaultValue,
54
+ children,
55
+ fieldClassName,
56
+ readOnly,
57
+ ...props
58
+ }: Props<T>) {
59
+ const { control, t, readOnly: formReadOnly, formId } = useScmFormContext();
60
+ const formPathPrefix = useScmFormPathContext();
61
+ const nameWithPrefix = formPathPrefix ? `${formPathPrefix}.${name}` : name;
62
+ const prefixedNameWithoutIndices = prefixWithoutIndices(nameWithPrefix);
63
+ const labelTranslation = label ?? t(`${prefixedNameWithoutIndices}.label`) ?? "";
64
+ const helpTextTranslation = helpText ?? t(`${prefixedNameWithoutIndices}.helpText`);
65
+
66
+ return (
67
+ <Controller
68
+ control={control}
69
+ name={nameWithPrefix}
70
+ rules={rules}
71
+ defaultValue={defaultValue}
72
+ render={({ field }) => (
73
+ <RadioButtonContextProvider t={t} prefix={prefixedNameWithoutIndices} formId={formId}>
74
+ <RadioGroupField
75
+ defaultValue={defaultValue}
76
+ disabled={readOnly ?? formReadOnly}
77
+ required={rules?.required as boolean}
78
+ label={labelTranslation}
79
+ helpText={helpTextTranslation}
80
+ fieldClassName={classNames("column", fieldClassName)}
81
+ onValueChange={field.onChange}
82
+ {...props}
83
+ {...field}
84
+ name={nameWithPrefix}
85
+ >
86
+ {children}
87
+ </RadioGroupField>
88
+ </RadioButtonContextProvider>
89
+ )}
90
+ />
91
+ );
92
+ }
93
+
94
+ export default ControlledRadioGroupField;
@@ -0,0 +1,226 @@
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, { useState } from "react";
26
+ import { storiesOf } from "@storybook/react";
27
+ import RadioButton from "./RadioButton";
28
+ import RadioGroup from "./RadioGroup";
29
+ import RadioGroupField from "./RadioGroupField";
30
+ import Form from "../Form";
31
+ import ControlledRadioGroupField from "./ControlledRadioGroupField";
32
+ import ControlledInputField from "../input/ControlledInputField";
33
+ import FormRow from "../FormRow";
34
+ import { ScmFormListContextProvider } from "../ScmFormListContext";
35
+ import ControlledList from "../list/ControlledList";
36
+ import ControlledTable from "../table/ControlledTable";
37
+ import ControlledColumn from "../table/ControlledColumn";
38
+ import AddListEntryForm from "../AddListEntryForm";
39
+
40
+ storiesOf("Radio Group", module)
41
+ .add("Uncontrolled State", () => {
42
+ return (
43
+ <RadioGroup name="starter_pokemon">
44
+ <RadioButton value="CHARMANDER" label="Charmander" />
45
+ <RadioButton value="SQUIRTLE" label="Squirtle" />
46
+ <RadioButton value="BULBASAUR" label="Bulbasaur" helpText="Dont pick this one" />
47
+ </RadioGroup>
48
+ );
49
+ })
50
+ .add("Controlled State", () => {
51
+ const [value, setValue] = useState<string | undefined>(undefined);
52
+ return (
53
+ <RadioGroup name="starter_pokemon" value={value} onValueChange={setValue}>
54
+ <RadioButton value="CHARMANDER" label="Charmander" />
55
+ <RadioButton value="SQUIRTLE" label="Squirtle" />
56
+ <RadioButton value="BULBASAUR" label="Bulbasaur" helpText="Dont pick this one" />
57
+ </RadioGroup>
58
+ );
59
+ })
60
+ .add("Radio Group Field", () => {
61
+ const [value, setValue] = useState<string | undefined>(undefined);
62
+ return (
63
+ <RadioGroupField
64
+ label="Choose your starter"
65
+ helpText="Choose wisely"
66
+ name="starter_pokemon"
67
+ value={value}
68
+ onValueChange={setValue}
69
+ >
70
+ <RadioButton value="CHARMANDER" label="Charmander" />
71
+ <RadioButton value="SQUIRTLE" label="Squirtle" />
72
+ <RadioButton value="BULBASAUR" label="Bulbasaur" helpText="Dont pick this one" />
73
+ </RadioGroupField>
74
+ );
75
+ })
76
+ .add("Controlled Radio Group Field", () => (
77
+ <Form
78
+ // eslint-disable-next-line no-console
79
+ onSubmit={console.log}
80
+ translationPath={["sample", "form"]}
81
+ defaultValues={{ name: "", starter_pokemon: null }}
82
+ >
83
+ <FormRow>
84
+ <ControlledInputField name="name" />
85
+ </FormRow>
86
+ <FormRow>
87
+ <ControlledRadioGroupField
88
+ name="starter_pokemon"
89
+ label="Starter Pokemon"
90
+ helpText="Choose wisely"
91
+ rules={{ required: true }}
92
+ >
93
+ <RadioButton value="CHARMANDER" label="Charmander" labelClassName="is-block" />
94
+ <RadioButton value="SQUIRTLE" label="Squirtle" />
95
+ <RadioButton value="BULBASAUR" label="Bulbasaur" helpText="Dont pick this one" />
96
+ </ControlledRadioGroupField>
97
+ </FormRow>
98
+ </Form>
99
+ ))
100
+ .add("Readonly Controlled Radio Group Field", () => (
101
+ <Form
102
+ // eslint-disable-next-line no-console
103
+ onSubmit={console.log}
104
+ translationPath={["sample", "form"]}
105
+ defaultValues={{ name: "Red", starter_pokemon: "SQUIRTLE" }}
106
+ >
107
+ <FormRow>
108
+ <ControlledInputField name="name" />
109
+ </FormRow>
110
+ <FormRow>
111
+ <ControlledRadioGroupField
112
+ name="starter_pokemon"
113
+ label="Starter Pokemon"
114
+ helpText="Choose wisely"
115
+ readOnly={true}
116
+ >
117
+ <RadioButton value="CHARMANDER" label="Charmander" labelClassName="is-block" />
118
+ <RadioButton value="SQUIRTLE" label="Squirtle" />
119
+ <RadioButton value="BULBASAUR" label="Bulbasaur" helpText="Dont pick this one" />
120
+ </ControlledRadioGroupField>
121
+ </FormRow>
122
+ </Form>
123
+ ))
124
+ .add("With options", () => (
125
+ <Form
126
+ // eslint-disable-next-line no-console
127
+ onSubmit={console.log}
128
+ translationPath={["sample", "form"]}
129
+ defaultValues={{ starter_pokemon: "CHARMANDER" }}
130
+ >
131
+ <FormRow>
132
+ <ControlledRadioGroupField
133
+ name="starter_pokemon"
134
+ label="Starter Pokemon"
135
+ helpText="Choose wisely"
136
+ rules={{ required: true }}
137
+ options={[
138
+ { value: "CHARMANDER", label: "Charmander" },
139
+ { value: "SQUIRTLE", label: "Squirtle" },
140
+ { value: "BULBASAUR", label: "Bulbasaur", helpText: "Dont pick this one" },
141
+ ]}
142
+ />
143
+ </FormRow>
144
+ </Form>
145
+ ))
146
+ .add("Without label prop", () => (
147
+ <Form
148
+ // eslint-disable-next-line no-console
149
+ onSubmit={console.log}
150
+ translationPath={["sample", "form"]}
151
+ defaultValues={{ starter_pokemon: "CHARMANDER" }}
152
+ >
153
+ <FormRow>
154
+ <ControlledRadioGroupField
155
+ name="starter_pokemon"
156
+ label="Starter Pokemon"
157
+ helpText="Choose wisely"
158
+ rules={{ required: true }}
159
+ options={[
160
+ { value: "CHARMANDER" },
161
+ { value: "SQUIRTLE" },
162
+ { value: "BULBASAUR", helpText: "Dont pick this one" },
163
+ ]}
164
+ />
165
+ </FormRow>
166
+ </Form>
167
+ ))
168
+ .add("Nested", () => (
169
+ <Form
170
+ translationPath={["sample", "form"]}
171
+ // eslint-disable-next-line no-console
172
+ onSubmit={console.log}
173
+ defaultValues={{
174
+ trainers: [
175
+ {
176
+ name: "Red",
177
+ team: [{ nickname: "Charlie", pokemon: "CHARMANDER" }],
178
+ },
179
+ {
180
+ name: "Blue",
181
+ team: [{ nickname: "Squirtle", pokemon: "SQUIRTLE" }],
182
+ },
183
+ {
184
+ name: "Green",
185
+ team: [{ nickname: "Plant", pokemon: "SQUIRTLE" }],
186
+ },
187
+ ],
188
+ }}
189
+ >
190
+ <ScmFormListContextProvider name={"trainers"}>
191
+ <ControlledList withDelete>
192
+ {({ value: trainers }) => (
193
+ <>
194
+ <FormRow>
195
+ <ControlledInputField name={"name"} />
196
+ </FormRow>
197
+ <details className="has-background-dark-25 mb-2 p-2">
198
+ <summary className="is-clickable">Team</summary>
199
+ <div>
200
+ <ScmFormListContextProvider name={"team"}>
201
+ <ControlledTable withDelete>
202
+ <ControlledColumn name="nickname" />
203
+ <ControlledColumn name="pokemon" />
204
+ </ControlledTable>
205
+ <AddListEntryForm defaultValues={{ nickname: "", pokemon: null }}>
206
+ <FormRow>
207
+ <ControlledInputField name="nickname" />
208
+ </FormRow>
209
+ <FormRow>
210
+ <ControlledRadioGroupField
211
+ name="pokemon"
212
+ label="Starter Pokemon"
213
+ rules={{ required: true }}
214
+ options={[{ value: "CHARMANDER" }, { value: "SQUIRTLE" }, { value: "BULBASAUR" }]}
215
+ />
216
+ </FormRow>
217
+ </AddListEntryForm>
218
+ </ScmFormListContextProvider>
219
+ </div>
220
+ </details>
221
+ </>
222
+ )}
223
+ </ControlledList>
224
+ </ScmFormListContextProvider>
225
+ </Form>
226
+ ));
@@ -0,0 +1,116 @@
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, { ComponentProps } from "react";
26
+ import classNames from "classnames";
27
+ import Help from "../base/help/Help";
28
+ import * as RadioGroup from "@radix-ui/react-radio-group";
29
+ import { createAttributesForTesting, useAriaId } from "../../helpers";
30
+ import styled from "styled-components";
31
+ import { useRadioButtonContext } from "./RadioButtonContext";
32
+
33
+ const StyledRadioButton = styled(RadioGroup.Item)`
34
+ all: unset;
35
+ width: 1rem;
36
+ height: 1rem;
37
+ border: var(--scm-border);
38
+ border-radius: 100%;
39
+
40
+ :hover {
41
+ border-color: var(--scm-hover-color);
42
+ }
43
+
44
+ :hover *::after {
45
+ background-color: var(--scm-info-hover-color);
46
+ }
47
+
48
+ :disabled {
49
+ background-color: var(--scm-dark-color-25);
50
+ border-color: var(--scm-hover-color);
51
+ }
52
+
53
+ :disabled *::after {
54
+ background-color: var(--scm-info-color);
55
+ }
56
+ `;
57
+
58
+ const StyledIndicator = styled(RadioGroup.Indicator)`
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ width: 100%;
63
+ height: 100%;
64
+ position: relative;
65
+
66
+ ::after {
67
+ content: "";
68
+ display: block;
69
+ width: 0.5rem;
70
+ height: 0.5rem;
71
+ border-radius: 50%;
72
+ background-color: var(--scm-info-color);
73
+ }
74
+ `;
75
+
76
+ type Props = {
77
+ value: string;
78
+ id?: string;
79
+ testId?: string;
80
+ indicatorClassName?: string;
81
+ label?: string;
82
+ labelClassName?: string;
83
+ helpText?: string;
84
+ } & ComponentProps<typeof RadioGroup.Item>;
85
+
86
+ /**
87
+ * @beta
88
+ * @since 2.48.0
89
+ */
90
+ const RadioButton = React.forwardRef<HTMLButtonElement, Props>(
91
+ ({ id, testId, indicatorClassName, label, labelClassName, className, helpText, value, ...props }, ref) => {
92
+ const context = useRadioButtonContext();
93
+ const inputId = useAriaId(id);
94
+ const labelKey = `${context?.prefix}.radio.${value}`;
95
+
96
+ return (
97
+ <label className={classNames("radio is-flex is-align-items-center", labelClassName)} htmlFor={inputId}>
98
+ <StyledRadioButton
99
+ form={context?.formId}
100
+ id={inputId}
101
+ value={value}
102
+ ref={ref}
103
+ className={classNames("mr-3 mt-3 mb-3", className)}
104
+ {...props}
105
+ {...createAttributesForTesting(testId)}
106
+ >
107
+ <StyledIndicator className={indicatorClassName} />
108
+ </StyledRadioButton>
109
+ {label ?? context?.t(labelKey) ?? value}
110
+ {helpText ? <Help className="ml-3" text={helpText} /> : null}
111
+ </label>
112
+ );
113
+ }
114
+ );
115
+
116
+ export default RadioButton;
@@ -0,0 +1,42 @@
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, { createContext, FC, useContext } from "react";
26
+ import { TFunction } from "i18next";
27
+
28
+ type ContextType = {
29
+ t: TFunction;
30
+ prefix: string;
31
+ formId?: string;
32
+ };
33
+
34
+ const RadioButtonContext = createContext<ContextType | null>(null);
35
+
36
+ export function useRadioButtonContext() {
37
+ return useContext(RadioButtonContext);
38
+ }
39
+
40
+ export const RadioButtonContextProvider: FC<ContextType> = ({ children, ...props }) => (
41
+ <RadioButtonContext.Provider value={props}>{children}</RadioButtonContext.Provider>
42
+ );
@@ -0,0 +1,49 @@
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, { ComponentProps } from "react";
26
+ import Control from "../base/Control";
27
+ import * as RadixRadio from "@radix-ui/react-radio-group";
28
+ import RadioButton from "./RadioButton";
29
+
30
+ type Props = {
31
+ options?: { value: string; label?: string; helpText?: string }[];
32
+ } & ComponentProps<typeof RadixRadio.Root>;
33
+
34
+ /**
35
+ * @beta
36
+ * @since 2.48.0
37
+ */
38
+ const RadioGroup = React.forwardRef<HTMLDivElement, Props>(({ options, children, className, ...props }, ref) => (
39
+ <RadixRadio.Root {...props} asChild>
40
+ <Control ref={ref} className={className}>
41
+ {children ??
42
+ options?.map((option) => (
43
+ <RadioButton key={option.value} value={option.value} label={option.label} helpText={option.helpText} />
44
+ ))}
45
+ </Control>
46
+ </RadixRadio.Root>
47
+ ));
48
+
49
+ export default RadioGroup;
@@ -0,0 +1,58 @@
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, { ComponentProps } from "react";
26
+ import Field from "../base/Field";
27
+ import Label from "../base/label/Label";
28
+ import Help from "../base/help/Help";
29
+ import RadioGroup from "./RadioGroup";
30
+
31
+ type Props = {
32
+ fieldClassName?: string;
33
+ labelClassName?: string;
34
+ label: string;
35
+ helpText?: string;
36
+ } & ComponentProps<typeof RadioGroup>;
37
+
38
+ /**
39
+ * @beta
40
+ * @since 2.48.0
41
+ */
42
+ const RadioGroupField = React.forwardRef<HTMLDivElement, Props>(
43
+ ({ fieldClassName, labelClassName, label, helpText, children, ...props }, ref) => {
44
+ return (
45
+ <Field className={fieldClassName} as="fieldset">
46
+ <Label className={labelClassName} as="legend">
47
+ {label}
48
+ {helpText ? <Help className="ml-1" text={helpText} /> : null}
49
+ </Label>
50
+ <RadioGroup ref={ref} {...props}>
51
+ {children}
52
+ </RadioGroup>
53
+ </Field>
54
+ );
55
+ }
56
+ );
57
+
58
+ export default RadioGroupField;