@scm-manager/ui-core 4.0.0-REACT18-20250701-125025 → 4.0.0-REACT18-20250824-143504

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 (56) hide show
  1. package/{src/base/buttons/a11y.test.ts → .storybook/i18n.ts} +27 -9
  2. package/.storybook/main.ts +54 -0
  3. package/.storybook/preview-head.html +6 -1
  4. package/.storybook/preview.tsx +125 -0
  5. package/.turbo/turbo-test.log +164 -0
  6. package/.turbo/turbo-typecheck.log +3 -2
  7. package/package.json +36 -41
  8. package/src/base/buttons/Button.stories.tsx +179 -70
  9. package/src/base/buttons/Button.tsx +9 -9
  10. package/src/base/forms/AddListEntryForm.tsx +8 -8
  11. package/src/base/forms/ConfigurationForm.tsx +14 -5
  12. package/src/base/forms/Form.stories.tsx +599 -289
  13. package/src/base/forms/Form.tsx +8 -8
  14. package/src/base/forms/FormPathContext.tsx +3 -3
  15. package/src/base/forms/ScmFormContext.tsx +7 -4
  16. package/src/base/forms/ScmFormListContext.tsx +2 -1
  17. package/src/base/forms/base/Field.tsx +1 -1
  18. package/src/base/forms/base/label/Label.tsx +1 -1
  19. package/src/base/forms/chip-input/ChipInputField.stories.tsx +109 -28
  20. package/src/base/forms/chip-input/ChipInputField.tsx +20 -8
  21. package/src/base/forms/chip-input/ControlledChipInputField.tsx +3 -1
  22. package/src/base/forms/combobox/Combobox.stories.tsx +216 -89
  23. package/src/base/forms/combobox/Combobox.tsx +4 -2
  24. package/src/base/forms/combobox/ComboboxField.tsx +2 -1
  25. package/src/base/forms/headless-chip-input/ChipInput.tsx +9 -9
  26. package/src/base/forms/helpers.ts +12 -9
  27. package/src/base/forms/input/ControlledSecretConfirmationField.tsx +4 -2
  28. package/src/base/forms/radio-button/RadioButton.stories.tsx +317 -124
  29. package/src/base/forms/radio-button/RadioButton.tsx +8 -4
  30. package/src/base/forms/radio-button/RadioButtonContext.tsx +2 -1
  31. package/src/base/forms/table/ControlledColumn.tsx +1 -1
  32. package/src/base/forms/table/ControlledTable.tsx +12 -4
  33. package/src/base/helpers/useDocumentTitle.test.ts +15 -7
  34. package/src/base/layout/card/Card.stories.tsx +171 -72
  35. package/src/base/layout/card/Card.tsx +4 -4
  36. package/src/base/layout/card/CardDetail.tsx +2 -3
  37. package/src/base/layout/card-list/CardList.stories.tsx +283 -169
  38. package/src/base/layout/collapsible/Collapsible.stories.tsx +54 -16
  39. package/src/base/layout/index.ts +2 -5
  40. package/src/base/layout/tabs/Tabs.stories.tsx +58 -16
  41. package/src/base/layout/templates/data-page/DataPage.stories.tsx +289 -156
  42. package/src/base/layout/templates/data-page/DataPageHeader.tsx +1 -1
  43. package/src/base/overlays/dialog/Dialog.stories.tsx +94 -34
  44. package/src/base/overlays/menu/Menu.stories.tsx +116 -48
  45. package/src/base/overlays/menu/Menu.tsx +1 -0
  46. package/src/base/overlays/popover/Popover.stories.tsx +50 -37
  47. package/src/base/shortcuts/iterator/keyboardIterator.test.tsx +16 -7
  48. package/src/base/shortcuts/iterator/keyboardIterator.tsx +13 -5
  49. package/src/base/status/StatusIcon.stories.tsx +76 -27
  50. package/src/base/status/index.ts +1 -1
  51. package/src/base/text/SplitAndReplace.stories.tsx +128 -50
  52. package/src/base/text/index.ts +1 -1
  53. package/.storybook/RemoveThemesPlugin.js +0 -49
  54. package/.storybook/main.js +0 -86
  55. package/.storybook/preview.js +0 -87
  56. package/src/base/buttons/image-snapshot.test.ts +0 -26
@@ -14,24 +14,132 @@
14
14
  * along with this program. If not, see https://www.gnu.org/licenses/.
15
15
  */
16
16
 
17
- import React, { ComponentProps } from "react";
17
+ // import React, { ComponentProps } from "react";
18
+ //
19
+ // import {
20
+ // Button as ButtonComponent,
21
+ // ButtonVariantList,
22
+ // ButtonVariants,
23
+ // ExternalLinkButton as ExternalLinkButtonComponent,
24
+ // LinkButton as LinkButtonComponent,
25
+ // IconButton as IconButtonComponent,
26
+ // } from "./Button";
27
+ // import Icon from "./Icon";
28
+ // import StoryRouter from "storybook-react-router";
29
+ // import { StoryFn } from "@storybook/react";
30
+ //
31
+ // // More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
32
+ // export default {
33
+ // title: "Components",
34
+ // component: null,
35
+ // subcomponents: {
36
+ // Button: ButtonComponent,
37
+ // LinkButton: LinkButtonComponent,
38
+ // ExternalLinkButton: ExternalLinkButtonComponent,
39
+ // IconButton: IconButtonComponent,
40
+ // },
41
+ // argTypes: {
42
+ // variant: {
43
+ // options: ButtonVariantList,
44
+ // control: { type: "select" },
45
+ // },
46
+ // },
47
+ // decorators: [StoryRouter()],
48
+ // parameters: {
49
+ // storyshots: { disable: true },
50
+ // },
51
+ // };
52
+ //
53
+ // // More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
54
+ // const ButtonTemplate: StoryFn<ComponentProps<typeof ButtonComponent>> = (args) => <ButtonComponent {...args} />;
55
+ // const LinkButtonTemplate: StoryFn<ComponentProps<typeof LinkButtonComponent>> = (args) => (
56
+ // <LinkButtonComponent {...args} />
57
+ // );
58
+ // const ExternalLinkButtonTemplate: StoryFn<ComponentProps<typeof ExternalLinkButtonComponent>> = (args) => (
59
+ // <ExternalLinkButtonComponent {...args} />
60
+ // );
61
+ //
62
+ // const IconButtonTemplate: StoryFn<ComponentProps<typeof IconButtonComponent>> = (args) => (
63
+ // <IconButtonComponent {...args} />
64
+ // );
65
+ //
66
+ // export const Button = ButtonTemplate.bind({});
67
+ // // More on args: https://storybook.js.org/docs/react/writing-stories/args
68
+ // Button.args = {
69
+ // children: "Button",
70
+ // variant: ButtonVariants.PRIMARY,
71
+ // disabled: false,
72
+ // };
73
+ //
74
+ // export const LinkButton = LinkButtonTemplate.bind({});
75
+ // // More on args: https://storybook.js.org/docs/react/writing-stories/args
76
+ // LinkButton.args = {
77
+ // children: "Link Button",
78
+ // to: "/repos",
79
+ // variant: ButtonVariants.PRIMARY,
80
+ // };
81
+ //
82
+ // export const ExternalLinkButton = ExternalLinkButtonTemplate.bind({});
83
+ // // More on args: https://storybook.js.org/docs/react/writing-stories/args
84
+ // ExternalLinkButton.args = {
85
+ // children: "External Link Button",
86
+ // href: "https://scm-manager.org",
87
+ // variant: ButtonVariants.PRIMARY,
88
+ // };
89
+ //
90
+ // const smallIcon = <Icon className="is-small">trash</Icon>;
91
+ // const mediumIcon = <Icon className="is-medium">trash</Icon>;
92
+ //
93
+ // /*
94
+ //
95
+ // Variant and size are defaulted to medium and colored and do not have to be explicitly added as parameters.
96
+ // However for the sake of documentation here they are still passed in
97
+ //
98
+ // */
99
+ // export const IconButtonBorder = IconButtonTemplate.bind({});
100
+ // IconButtonBorder.args = {
101
+ // children: mediumIcon,
102
+ // variant: "colored",
103
+ // size: "medium",
104
+ // };
105
+ //
106
+ //
107
+ // export const IconButtonBorderDefault = IconButtonTemplate.bind({});
108
+ // IconButtonBorderDefault.args = {
109
+ // children: mediumIcon,
110
+ // variant: "default",
111
+ // size: "medium",
112
+ // };
113
+ //
114
+ // export const IconButtonBorderlessSmall = IconButtonTemplate.bind({});
115
+ // IconButtonBorderlessSmall.args = {
116
+ // children: smallIcon,
117
+ // variant: "colored",
118
+ // size: "small",
119
+ // };
120
+ //
121
+ // export const IconButtonBorderlessSmallDefault = IconButtonTemplate.bind({});
122
+ // IconButtonBorderlessSmallDefault.args = {
123
+ // children: smallIcon,
124
+ // variant: "default",
125
+ // size: "small",
126
+ // };
127
+
128
+ import React from "react";
129
+ import type { Meta, StoryObj } from "@storybook/react";
18
130
 
19
131
  import {
20
132
  Button as ButtonComponent,
21
133
  ButtonVariantList,
22
134
  ButtonVariants,
23
135
  ExternalLinkButton as ExternalLinkButtonComponent,
24
- LinkButton as LinkButtonComponent,
25
136
  IconButton as IconButtonComponent,
137
+ LinkButton as LinkButtonComponent,
26
138
  } from "./Button";
27
139
  import Icon from "./Icon";
28
- import StoryRouter from "storybook-react-router";
29
- import { StoryFn } from "@storybook/react";
30
140
 
31
- // More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
32
- export default {
33
- title: "Components",
34
- component: null,
141
+ const meta: Meta = {
142
+ title: "Buttons/Button",
35
143
  subcomponents: {
36
144
  Button: ButtonComponent,
37
145
  LinkButton: LinkButtonComponent,
@@ -44,83 +152,84 @@ export default {
44
152
  control: { type: "select" },
45
153
  },
46
154
  },
47
- decorators: [StoryRouter()],
48
- parameters: {
49
- storyshots: { disable: true },
50
- },
155
+ decorators: [
156
+ (Story) => (
157
+ <div style={{ margin: "2rem", display: "flex", gap: "1rem", alignItems: "center" }}>
158
+ <Story />
159
+ </div>
160
+ ),
161
+ ],
162
+ tags: ["autodocs"],
51
163
  };
52
164
 
53
- // More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
54
- const ButtonTemplate: StoryFn<ComponentProps<typeof ButtonComponent>> = (args) => <ButtonComponent {...args} />;
55
- const LinkButtonTemplate: StoryFn<ComponentProps<typeof LinkButtonComponent>> = (args) => (
56
- <LinkButtonComponent {...args} />
57
- );
58
- const ExternalLinkButtonTemplate: StoryFn<ComponentProps<typeof ExternalLinkButtonComponent>> = (args) => (
59
- <ExternalLinkButtonComponent {...args} />
60
- );
61
-
62
- const IconButtonTemplate: StoryFn<ComponentProps<typeof IconButtonComponent>> = (args) => (
63
- <IconButtonComponent {...args} />
64
- );
165
+ export default meta;
65
166
 
66
- export const Button = ButtonTemplate.bind({});
67
- // More on args: https://storybook.js.org/docs/react/writing-stories/args
68
- Button.args = {
69
- children: "Button",
70
- variant: ButtonVariants.PRIMARY,
71
- disabled: false,
167
+ export const Button: StoryObj<typeof ButtonComponent> = {
168
+ render: (args) => <ButtonComponent {...args} />,
169
+ args: {
170
+ children: "Button",
171
+ variant: "primary",
172
+ disabled: false,
173
+ },
72
174
  };
73
175
 
74
- export const LinkButton = LinkButtonTemplate.bind({});
75
- // More on args: https://storybook.js.org/docs/react/writing-stories/args
76
- LinkButton.args = {
77
- children: "Link Button",
78
- to: "/repos",
79
- variant: ButtonVariants.PRIMARY,
176
+ export const LinkButton: StoryObj<typeof LinkButtonComponent> = {
177
+ render: (args) => <LinkButtonComponent {...args} />,
178
+ args: {
179
+ children: "Link Button",
180
+ to: "/repos",
181
+ variant: "primary",
182
+ },
80
183
  };
81
184
 
82
- export const ExternalLinkButton = ExternalLinkButtonTemplate.bind({});
83
- // More on args: https://storybook.js.org/docs/react/writing-stories/args
84
- ExternalLinkButton.args = {
85
- children: "External Link Button",
86
- href: "https://scm-manager.org",
87
- variant: ButtonVariants.PRIMARY,
185
+ export const ExternalLinkButton: StoryObj<typeof ExternalLinkButtonComponent> = {
186
+ render: (args) => <ExternalLinkButtonComponent {...args} />,
187
+ args: {
188
+ children: "External Link Button",
189
+ href: "https://scm-manager.org",
190
+ variant: "primary",
191
+ },
88
192
  };
89
193
 
90
- const smallIcon = <Icon className="is-small">trash</Icon>;
91
194
  const mediumIcon = <Icon className="is-medium">trash</Icon>;
195
+ const smallIcon = <Icon className="is-small">trash</Icon>;
92
196
 
93
- /*
94
-
95
- Variant and size are defaulted to medium and colored and do not have to be explicitly added as parameters.
96
- However for the sake of documentation here they are still passed in
97
-
98
- */
99
- export const IconButtonBorder = IconButtonTemplate.bind({});
100
- IconButtonBorder.args = {
101
- children: mediumIcon,
102
- variant: "colored",
103
- size: "medium",
197
+ export const BorderedIcon: StoryObj<typeof IconButtonComponent> = {
198
+ name: "Icon Button (Bordered)",
199
+ render: (args) => <IconButtonComponent {...args} />,
200
+ args: {
201
+ children: mediumIcon,
202
+ variant: "colored",
203
+ size: "medium",
204
+ },
104
205
  };
105
206
 
106
-
107
- export const IconButtonBorderDefault = IconButtonTemplate.bind({});
108
- IconButtonBorderDefault.args = {
109
- children: mediumIcon,
110
- variant: "default",
111
- size: "medium",
207
+ export const BorderedIconDefault: StoryObj<typeof IconButtonComponent> = {
208
+ name: "Icon Button (Bordered, Default)",
209
+ render: (args) => <IconButtonComponent {...args} />,
210
+ args: {
211
+ children: mediumIcon,
212
+ variant: "default",
213
+ size: "medium",
214
+ },
112
215
  };
113
216
 
114
- export const IconButtonBorderlessSmall = IconButtonTemplate.bind({});
115
- IconButtonBorderlessSmall.args = {
116
- children: smallIcon,
117
- variant: "colored",
118
- size: "small",
217
+ export const BorderlessIcon: StoryObj<typeof IconButtonComponent> = {
218
+ name: "Icon Button (Borderless, Small)",
219
+ render: (args) => <IconButtonComponent {...args} />,
220
+ args: {
221
+ children: smallIcon,
222
+ variant: "colored",
223
+ size: "small",
224
+ },
119
225
  };
120
226
 
121
- export const IconButtonBorderlessSmallDefault = IconButtonTemplate.bind({});
122
- IconButtonBorderlessSmallDefault.args = {
123
- children: smallIcon,
124
- variant: "default",
125
- size: "small",
227
+ export const BorderlessIconDefault: StoryObj<typeof IconButtonComponent> = {
228
+ name: "Icon Button (Borderless, Small, Default)",
229
+ render: (args) => <IconButtonComponent {...args} />,
230
+ args: {
231
+ children: smallIcon,
232
+ variant: "default",
233
+ size: "small",
234
+ },
126
235
  };
@@ -15,7 +15,7 @@
15
15
  */
16
16
 
17
17
  import React, { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from "react";
18
- import { Link as ReactRouterLink, LinkProps as ReactRouterLinkProps } from "react-router-dom";
18
+ import { Link as ReactRouterLink, LinkProps as ReactRouterLinkProps } from "react-router";
19
19
  import classNames from "classnames";
20
20
  import { createAttributesForTesting } from "../helpers";
21
21
  import styled from "styled-components";
@@ -35,7 +35,7 @@ export const ButtonVariants = {
35
35
 
36
36
  export const ButtonVariantList = Object.values(ButtonVariants);
37
37
 
38
- type ButtonVariant = typeof ButtonVariants[keyof typeof ButtonVariants];
38
+ type ButtonVariant = (typeof ButtonVariants)[keyof typeof ButtonVariants];
39
39
 
40
40
  const createButtonClasses = (variant?: ButtonVariant, isLoading?: boolean) =>
41
41
  classNames("button", {
@@ -75,7 +75,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
75
75
  >
76
76
  {children}
77
77
  </button>
78
- )
78
+ ),
79
79
  );
80
80
 
81
81
  type LinkButtonProps = BaseButtonProps & ReactRouterLinkProps;
@@ -98,7 +98,7 @@ export const LinkButton = React.forwardRef<HTMLAnchorElement, LinkButtonProps>(
98
98
  >
99
99
  {children}
100
100
  </ReactRouterLink>
101
- )
101
+ ),
102
102
  );
103
103
 
104
104
  /**
@@ -112,7 +112,7 @@ export const ExternalLink = React.forwardRef<HTMLAnchorElement, AnchorHTMLAttrib
112
112
  <a target="_blank" rel="noreferrer noopener" {...props} ref={ref}>
113
113
  {children}
114
114
  </a>
115
- )
115
+ ),
116
116
  );
117
117
 
118
118
  type ExternalLinkButtonProps = BaseButtonProps & AnchorHTMLAttributes<HTMLAnchorElement>;
@@ -136,7 +136,7 @@ export const ExternalLinkButton = React.forwardRef<HTMLAnchorElement, ExternalLi
136
136
  >
137
137
  {children}
138
138
  </ExternalLink>
139
- )
139
+ ),
140
140
  );
141
141
 
142
142
  const StyledIconButton = styled.button`
@@ -180,9 +180,9 @@ export const IconSizes = {
180
180
  MEDIUM: "medium",
181
181
  } as const;
182
182
 
183
- type IconButtonSize = typeof IconSizes[keyof typeof IconSizes];
183
+ type IconButtonSize = (typeof IconSizes)[keyof typeof IconSizes];
184
184
 
185
- type IconButtonVariant = typeof IconButtonVariants[keyof typeof IconButtonVariants];
185
+ type IconButtonVariant = (typeof IconButtonVariants)[keyof typeof IconButtonVariants];
186
186
 
187
187
  const createIconButtonClasses = (variant?: IconButtonVariant) =>
188
188
  classNames("button", {
@@ -218,5 +218,5 @@ export const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(
218
218
  ) : (
219
219
  <StyledIconButton {...elementAttributes}>{children}</StyledIconButton>
220
220
  );
221
- }
221
+ },
222
222
  );
@@ -17,7 +17,7 @@
17
17
  import React, { useCallback, useEffect } from "react";
18
18
  import { ScmFormPathContextProvider, useScmFormPathContext } from "./FormPathContext";
19
19
  import { ScmFormContextProvider, useScmFormContext } from "./ScmFormContext";
20
- import { DeepPartial, UseFieldArrayReturn, useForm, UseFormReturn } from "react-hook-form";
20
+ import { UseFieldArrayReturn, useForm, DefaultValues, UseFormReturn } from "react-hook-form";
21
21
  import { Button } from "../buttons";
22
22
  import { prefixWithoutIndices } from "./helpers";
23
23
  import { useScmFormListContext } from "./ScmFormListContext";
@@ -35,9 +35,9 @@ type RenderProps<T extends Record<string, unknown>> = Omit<
35
35
  "register" | "unregister" | "handleSubmit" | "control"
36
36
  >;
37
37
 
38
- type Props<FormType extends Record<string, unknown>, DefaultValues extends FormType> = {
38
+ type Props<FormType extends Record<string, unknown>> = {
39
39
  children: ((renderProps: RenderProps<FormType>) => React.ReactNode | React.ReactNode[]) | React.ReactNode;
40
- defaultValues: DefaultValues;
40
+ defaultValues: DefaultValues<FormType>;
41
41
  submit?: (data: FormType, append: UseFieldArrayReturn["append"]) => unknown;
42
42
  /**
43
43
  * @default true
@@ -49,20 +49,20 @@ type Props<FormType extends Record<string, unknown>, DefaultValues extends FormT
49
49
  * @beta
50
50
  * @since 2.43.0
51
51
  */
52
- function AddListEntryForm<FormType extends Record<string, unknown>, DefaultValues extends FormType>({
52
+ function AddListEntryForm<FormType extends Record<string, unknown>>({
53
53
  children,
54
54
  defaultValues,
55
55
  submit,
56
56
  disableSubmitWhenDirty = true,
57
- }: Props<FormType, DefaultValues>) {
57
+ }: Props<FormType>) {
58
58
  const [defaultTranslate] = useTranslation("commons", { keyPrefix: "form" });
59
59
  const { readOnly, t } = useScmFormContext();
60
60
  const nameWithPrefix = useScmFormPathContext();
61
61
  const prefixedNameWithoutIndices = prefixWithoutIndices(nameWithPrefix);
62
62
  const { append, isNested } = useScmFormListContext();
63
- const form = useForm<FormType, DefaultValues>({
63
+ const form = useForm<FormType>({
64
64
  mode: "onChange",
65
- defaultValues: defaultValues as DeepPartial<FormType>,
65
+ defaultValues: defaultValues,
66
66
  });
67
67
 
68
68
  const {
@@ -75,7 +75,7 @@ function AddListEntryForm<FormType extends Record<string, unknown>, DefaultValue
75
75
  [prefixedNameWithoutIndices, t]
76
76
  );
77
77
 
78
- const onSubmit = useCallback((data) => (submit ? submit(data, append) : append(data)), [append, submit]);
78
+ const onSubmit = useCallback((data: any) => (submit ? submit(data, append) : append(data)), [append, submit]);
79
79
 
80
80
  const submitButtonLabel = translateWithExtraPrefix("add", {
81
81
  defaultValue: defaultTranslate("list.add.label", { entity: translateWithExtraPrefix("entity") }),
@@ -20,11 +20,11 @@ import React, { ComponentProps } from "react";
20
20
  import { HalRepresentation } from "@scm-manager/ui-types";
21
21
  import Form from "./Form";
22
22
  import { useTranslation } from "react-i18next";
23
+ import { DefaultValues, FieldValues } from "react-hook-form";
23
24
 
24
25
  type Props<T extends HalRepresentation> =
25
26
  // eslint-disable-next-line prettier/prettier
26
- Omit<ComponentProps<typeof Form<T>>, "onSubmit" | "defaultValues" | "readOnly" | "successMessageFallback">
27
- & {
27
+ Omit<ComponentProps<typeof Form<T>>, "onSubmit" | "defaultValues" | "readOnly" | "successMessageFallback"> & {
28
28
  link: string;
29
29
  };
30
30
 
@@ -32,7 +32,11 @@ type Props<T extends HalRepresentation> =
32
32
  * @beta
33
33
  * @since 2.41.0
34
34
  */
35
- export function ConfigurationForm<T extends HalRepresentation>({ link, children, ...formProps }: Props<T>) {
35
+ export function ConfigurationForm<T extends HalRepresentation & FieldValues>({
36
+ link,
37
+ children,
38
+ ...formProps
39
+ }: Props<T>) {
36
40
  const { initialConfiguration, isReadOnly, update, isLoading } = useConfigLink<T>(link);
37
41
  const [t] = useTranslation("commons", { keyPrefix: "form" });
38
42
 
@@ -41,8 +45,13 @@ export function ConfigurationForm<T extends HalRepresentation>({ link, children,
41
45
  }
42
46
 
43
47
  return (
44
- <Form onSubmit={update} defaultValues={initialConfiguration} readOnly={isReadOnly}
45
- successMessageFallback={t("submit-success-notification")} {...formProps}>
48
+ <Form
49
+ onSubmit={update}
50
+ defaultValues={initialConfiguration as DefaultValues<T>}
51
+ readOnly={isReadOnly}
52
+ successMessageFallback={t("submit-success-notification")}
53
+ {...formProps}
54
+ >
46
55
  {children}
47
56
  </Form>
48
57
  );