@scm-manager/ui-components 3.10.4-20250824-132529 → 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 (130) hide show
  1. package/package.json +45 -50
  2. package/src/BranchSelector.stories.tsx +60 -11
  3. package/src/Breadcrumb.stories.tsx +131 -57
  4. package/src/Breadcrumb.tsx +3 -3
  5. package/src/CardColumn.stories.tsx +94 -27
  6. package/src/CardColumnSmall.stories.tsx +102 -27
  7. package/src/CommaSeparatedList.tsx +2 -2
  8. package/src/Date.stories.tsx +64 -17
  9. package/src/Duration.stories.tsx +92 -45
  10. package/src/ErrorBoundary.tsx +38 -58
  11. package/src/ErrorNotification.tsx +1 -1
  12. package/src/Help.stories.tsx +61 -17
  13. package/src/Help.tsx +5 -11
  14. package/src/Icon.stories.tsx +93 -13
  15. package/src/Image.tsx +2 -3
  16. package/src/LinkPaginator.tsx +9 -6
  17. package/src/Loading.stories.tsx +26 -6
  18. package/src/Logo.stories.tsx +44 -13
  19. package/src/Notification.stories.tsx +111 -23
  20. package/src/OverviewPageActions.tsx +5 -5
  21. package/src/Paginator.test.tsx +115 -94
  22. package/src/PdfViewer.stories.tsx +83 -5
  23. package/src/PreformattedCodeBlock.stories.tsx +97 -26
  24. package/src/ProtectedRoute.tsx +14 -39
  25. package/src/SmallLoadingSpinner.stories.tsx +26 -6
  26. package/src/SyntaxHighlighter.stories.tsx +122 -40
  27. package/src/SyntaxHighlighterRenderer.tsx +7 -7
  28. package/src/Tag.stories.tsx +135 -18
  29. package/src/Tag.tsx +2 -1
  30. package/src/Tooltip.stories.tsx +147 -34
  31. package/src/__snapshots__/storyshots.test.ts.snap +21 -144
  32. package/src/avatar/Avatar.ts +1 -1
  33. package/src/avatar/AvatarImage.tsx +2 -2
  34. package/src/avatar/AvatarWrapper.tsx +2 -2
  35. package/src/buttons/Button.stories.tsx +159 -0
  36. package/src/buttons/Button.tsx +5 -5
  37. package/src/config/ConfigurationBinder.tsx +39 -39
  38. package/src/config/ConfigurationForm.tsx +2 -1
  39. package/src/config/TitledSettings.tsx +4 -1
  40. package/src/forms/AddKeyValueEntryToTableField.stories.tsx +60 -25
  41. package/src/forms/Checkbox.stories.tsx +165 -27
  42. package/src/forms/Checkbox.tsx +1 -0
  43. package/src/forms/DropDown.stories.tsx +81 -35
  44. package/src/forms/FileInput.stories.tsx +85 -10
  45. package/src/forms/InputField.stories.tsx +210 -37
  46. package/src/forms/InputField.tsx +1 -0
  47. package/src/forms/Radio.stories.tsx +181 -34
  48. package/src/forms/Radio.tsx +1 -0
  49. package/src/forms/Select.stories.tsx +266 -46
  50. package/src/forms/Select.tsx +1 -0
  51. package/src/forms/Textarea.stories.tsx +99 -53
  52. package/src/forms/Textarea.tsx +1 -0
  53. package/src/forms/index.ts +3 -1
  54. package/src/index.ts +5 -5
  55. package/src/jest-dom.d.ts +17 -0
  56. package/src/layout/Footer.stories.tsx +114 -22
  57. package/src/layout/Footer.tsx +11 -7
  58. package/src/layout/FooterSection.tsx +1 -0
  59. package/src/layout/Header.tsx +2 -1
  60. package/src/layout/Page.tsx +1 -3
  61. package/src/layout/PrimaryContentColumn.tsx +2 -2
  62. package/src/layout/SecondaryNavigationColumn.tsx +3 -3
  63. package/src/layout/SubSubtitle.tsx +2 -1
  64. package/src/layout/Subtitle.tsx +2 -1
  65. package/src/layout/Title.tsx +2 -5
  66. package/src/markdown/LazyMarkdownView.tsx +16 -5
  67. package/src/markdown/MarkdownHeadingRenderer.test.ts +9 -1
  68. package/src/markdown/MarkdownHeadingRenderer.tsx +3 -3
  69. package/src/markdown/MarkdownImageRenderer.test.ts +8 -1
  70. package/src/markdown/MarkdownImageRenderer.tsx +2 -1
  71. package/src/markdown/MarkdownLinkRenderer.test.tsx +9 -0
  72. package/src/markdown/MarkdownLinkRenderer.tsx +6 -4
  73. package/src/markdown/MarkdownView.stories.tsx +224 -72
  74. package/src/markdown/markdownExtensions.ts +6 -4
  75. package/src/modals/ActiveModalCountContextProvider.tsx +2 -2
  76. package/src/modals/ConfirmAlert.stories.tsx +133 -44
  77. package/src/modals/ConfirmAlert.tsx +5 -4
  78. package/src/modals/Modal.stories.tsx +461 -252
  79. package/src/modals/Modal.tsx +12 -11
  80. package/src/modals/useRegisterModal.test.tsx +5 -4
  81. package/src/navigation/MenuContext.tsx +2 -2
  82. package/src/navigation/NavLink.tsx +4 -7
  83. package/src/navigation/PrimaryNavigation.tsx +2 -2
  84. package/src/navigation/PrimaryNavigationLink.tsx +6 -5
  85. package/src/navigation/RoutingProps.ts +1 -3
  86. package/src/navigation/SecondaryNavigation.stories.tsx +157 -45
  87. package/src/navigation/SecondaryNavigation.tsx +17 -16
  88. package/src/navigation/SecondaryNavigationItem.tsx +2 -1
  89. package/src/navigation/SubNavigation.tsx +4 -8
  90. package/src/navigation/useActiveMatch.ts +20 -22
  91. package/src/navigation/useNavigationLock.ts +1 -0
  92. package/src/popover/Popover.stories.tsx +111 -41
  93. package/src/popover/Popover.tsx +2 -5
  94. package/src/repos/Diff.stories.tsx +418 -223
  95. package/src/repos/Diff.tsx +1 -5
  96. package/src/repos/DiffTypes.ts +2 -14
  97. package/src/repos/HunkExpandDivider.tsx +2 -2
  98. package/src/repos/LoadingDiff.tsx +11 -6
  99. package/src/repos/RepositoryEntry.stories.tsx +217 -53
  100. package/src/repos/RepositoryFlag.tsx +2 -1
  101. package/src/repos/TokenizedDiffView.tsx +4 -2
  102. package/src/repos/annotate/Annotate.stories.tsx +225 -111
  103. package/src/repos/annotate/AnnotateLine.tsx +2 -1
  104. package/src/repos/changesets/ChangesetAuthor.tsx +4 -4
  105. package/src/repos/changesets/ChangesetButtonGroup.test.tsx +9 -5
  106. package/src/repos/changesets/ChangesetDiff.test.ts +10 -2
  107. package/src/repos/changesets/Changesets.stories.tsx +388 -197
  108. package/src/repos/changesets/Contributor.tsx +1 -1
  109. package/src/repos/changesets/ContributorRow.tsx +2 -2
  110. package/src/repos/changesets/SignatureIcon.tsx +1 -0
  111. package/src/repos/changesets/SingleChangeset.tsx +0 -1
  112. package/src/repos/diff/DiffFileTree.tsx +37 -89
  113. package/src/repos/diff/styledElements.tsx +4 -3
  114. package/src/repos/index.ts +15 -15
  115. package/src/search/Hit.tsx +3 -2
  116. package/src/search/TextHitField.stories.tsx +131 -43
  117. package/src/search/TextHitField.tsx +3 -2
  118. package/src/search/index.ts +1 -1
  119. package/src/storyshots.test.ts +66 -60
  120. package/src/table/Table.stories.tsx +146 -48
  121. package/src/table/Table.tsx +7 -8
  122. package/src/table/TextColumn.tsx +0 -9
  123. package/src/toast/Toast.tsx +2 -1
  124. package/src/toast/ToastArea.tsx +2 -2
  125. package/src/toast/ToastButton.tsx +2 -1
  126. package/src/toast/ToastButtons.tsx +4 -2
  127. package/src/toast/ToastNotification.tsx +2 -1
  128. package/src/toast/index.stories.tsx +144 -39
  129. package/src/toast/index.ts +1 -1
  130. package/src/buttons/index.stories.tsx +0 -85
@@ -14,12 +14,99 @@
14
14
  * along with this program. If not, see https://www.gnu.org/licenses/.
15
15
  */
16
16
 
17
+ // import React from "react";
18
+ // import { storiesOf } from "@storybook/react";
19
+ // import Table from "./Table";
20
+ // import Column from "./Column";
21
+ // import TextColumn from "./TextColumn";
22
+ // import styled from "styled-components";
23
+ //
24
+ // const StyledTable = styled(Table)`
25
+ // width: 400px;
26
+ // border: 1px dashed black;
27
+ // padding: 4px;
28
+ // margin: 4px;
29
+ // td {
30
+ // word-break: break-word;
31
+ // }
32
+ // `;
33
+ //
34
+ // storiesOf("Table", module)
35
+ // .add("Default", () => (
36
+ // <Table
37
+ // data={[
38
+ // { firstname: "Tricia", lastname: "McMillan", email: "tricia@hitchhiker.com" },
39
+ // { firstname: "Arthur", lastname: "Dent", email: "arthur@hitchhiker.com" },
40
+ // ]}
41
+ // >
42
+ // <Column header={"First Name"}>{(row: any) => <h4>{row.firstname}</h4>}</Column>
43
+ // <Column
44
+ // className="has-background-success"
45
+ // header={"Last Name"}
46
+ // createComparator={() => {
47
+ // return (a: any, b: any) => {
48
+ // if (a.lastname > b.lastname) {
49
+ // return -1;
50
+ // } else if (a.lastname < b.lastname) {
51
+ // return 1;
52
+ // } else {
53
+ // return 0;
54
+ // }
55
+ // };
56
+ // }}
57
+ // >
58
+ // {(row: any) => <b className="has-text-danger">{row.lastname}</b>}
59
+ // </Column>
60
+ // <Column header={"E-Mail"}>{(row: any) => <span>{row.email}</span>}</Column>
61
+ // </Table>
62
+ // ))
63
+ // .add("TextColumn", () => (
64
+ // <Table
65
+ // data={[
66
+ // { id: "21", title: "Pommes", desc: "Fried potato sticks" },
67
+ // { id: "42", title: "Quarter-Pounder", desc: "Big burger" },
68
+ // { id: "-84", title: "Icecream", desc: "Cold dessert" },
69
+ // ]}
70
+ // >
71
+ // <TextColumn header="Id" dataKey="id" />
72
+ // <TextColumn header="Name" dataKey="title" />
73
+ // <TextColumn header="Description" dataKey="desc" />
74
+ // </Table>
75
+ // ))
76
+ // .add("Empty", () => (
77
+ // <Table data={[]} emptyMessage="No data found.">
78
+ // <TextColumn header="Id" dataKey="id" />
79
+ // <TextColumn header="Name" dataKey="name" />
80
+ // </Table>
81
+ // ))
82
+ // .add("Table with Word-Break", () => (
83
+ // <StyledTable
84
+ // data={[
85
+ // {
86
+ // id: "42",
87
+ // name: "herp_derp_schlerp_ferp_gerp_nerp_terp_ierp_perp_lerp_merp_oerp_zerp_serp_verp_herp",
88
+ // },
89
+ // {
90
+ // id: "17",
91
+ // name: "herp_derp_schlerp_ferp_gerp_nerp_terp_ierp_perp_lerp_merp_oerp_zerp_serp_verp",
92
+ // },
93
+ // ]}
94
+ // emptyMessage="No data found."
95
+ // >
96
+ // <TextColumn header="Id" dataKey="id" />
97
+ // <TextColumn header="Name" dataKey="name" />
98
+ // </StyledTable>
99
+ // ));
100
+
17
101
  import React from "react";
18
- import { storiesOf } from "@storybook/react";
102
+ import styled from "styled-components";
103
+ import type { Meta, StoryObj } from "@storybook/react";
104
+
19
105
  import Table from "./Table";
20
106
  import Column from "./Column";
21
107
  import TextColumn from "./TextColumn";
22
- import styled from "styled-components";
108
+
109
+ // --- Helper Components & Mock Data (preserved from your original story) ---
23
110
 
24
111
  const StyledTable = styled(Table)`
25
112
  width: 400px;
@@ -31,69 +118,80 @@ const StyledTable = styled(Table)`
31
118
  }
32
119
  `;
33
120
 
34
- storiesOf("Table", module)
35
- .add("Default", () => (
36
- <Table
37
- data={[
38
- { firstname: "Tricia", lastname: "McMillan", email: "tricia@hitchhiker.com" },
39
- { firstname: "Arthur", lastname: "Dent", email: "arthur@hitchhiker.com" },
40
- ]}
41
- >
121
+ const tableData = [
122
+ { firstname: "Tricia", lastname: "McMillan", email: "tricia@hitchhiker.com" },
123
+ { firstname: "Arthur", lastname: "Dent", email: "arthur@hitchhiker.com" },
124
+ ];
125
+
126
+ const textColumnData = [
127
+ { id: "21", title: "Pommes", desc: "Fried potato sticks" },
128
+ { id: "42", title: "Quarter-Pounder", desc: "Big burger" },
129
+ { id: "-84", title: "Icecream", desc: "Cold dessert" },
130
+ ];
131
+
132
+ const wordBreakData = [
133
+ { id: "42", name: "herp_derp_schlerp_ferp_gerp_nerp_terp_ierp_perp_lerp_merp_oerp_zerp_serp_verp_herp" },
134
+ { id: "17", name: "herp_derp_schlerp_ferp_gerp_nerp_terp_ierp_perp_lerp_merp_oerp_zerp_serp_verp" },
135
+ ];
136
+
137
+ // --- Storybook Metadata ---
138
+
139
+ const meta: Meta<typeof Table> = {
140
+ title: "Components/Table",
141
+ component: Table,
142
+ decorators: [(Story) => <div style={{ margin: "2rem" }}>{Story()}</div>],
143
+ tags: ["autodocs"],
144
+ };
145
+
146
+ export default meta;
147
+
148
+ // --- Story Definitions ---
149
+
150
+ type Story = StoryObj<typeof meta>;
151
+
152
+ // For stories with complex children or custom components, we use a `render` function.
153
+ export const Default: Story = {
154
+ render: () => (
155
+ <Table data={tableData}>
42
156
  <Column header={"First Name"}>{(row: any) => <h4>{row.firstname}</h4>}</Column>
43
157
  <Column
44
158
  className="has-background-success"
45
159
  header={"Last Name"}
46
- createComparator={() => {
47
- return (a: any, b: any) => {
48
- if (a.lastname > b.lastname) {
49
- return -1;
50
- } else if (a.lastname < b.lastname) {
51
- return 1;
52
- } else {
53
- return 0;
54
- }
55
- };
56
- }}
160
+ createComparator={() => (a: any, b: any) => b.lastname.localeCompare(a.lastname)}
57
161
  >
58
162
  {(row: any) => <b className="has-text-danger">{row.lastname}</b>}
59
163
  </Column>
60
164
  <Column header={"E-Mail"}>{(row: any) => <span>{row.email}</span>}</Column>
61
165
  </Table>
62
- ))
63
- .add("TextColumn", () => (
64
- <Table
65
- data={[
66
- { id: "21", title: "Pommes", desc: "Fried potato sticks" },
67
- { id: "42", title: "Quarter-Pounder", desc: "Big burger" },
68
- { id: "-84", title: "Icecream", desc: "Cold dessert" },
69
- ]}
70
- >
166
+ ),
167
+ };
168
+
169
+ export const WithTextColumn: Story = {
170
+ name: "With TextColumn",
171
+ render: () => (
172
+ <Table data={textColumnData}>
71
173
  <TextColumn header="Id" dataKey="id" />
72
174
  <TextColumn header="Name" dataKey="title" />
73
175
  <TextColumn header="Description" dataKey="desc" />
74
176
  </Table>
75
- ))
76
- .add("Empty", () => (
177
+ ),
178
+ };
179
+
180
+ export const Empty: Story = {
181
+ render: () => (
77
182
  <Table data={[]} emptyMessage="No data found.">
78
183
  <TextColumn header="Id" dataKey="id" />
79
184
  <TextColumn header="Name" dataKey="name" />
80
185
  </Table>
81
- ))
82
- .add("Table with Word-Break", () => (
83
- <StyledTable
84
- data={[
85
- {
86
- id: "42",
87
- name: "herp_derp_schlerp_ferp_gerp_nerp_terp_ierp_perp_lerp_merp_oerp_zerp_serp_verp_herp",
88
- },
89
- {
90
- id: "17",
91
- name: "herp_derp_schlerp_ferp_gerp_nerp_terp_ierp_perp_lerp_merp_oerp_zerp_serp_verp",
92
- },
93
- ]}
94
- emptyMessage="No data found."
95
- >
186
+ ),
187
+ };
188
+
189
+ export const WithWordBreak: Story = {
190
+ name: "With Word-Break",
191
+ render: () => (
192
+ <StyledTable data={wordBreakData} emptyMessage="No data found.">
96
193
  <TextColumn header="Id" dataKey="id" />
97
194
  <TextColumn header="Name" dataKey="name" />
98
195
  </StyledTable>
99
- ));
196
+ ),
197
+ };
@@ -28,10 +28,7 @@ type Props = {
28
28
  className?: string;
29
29
  };
30
30
 
31
- /**
32
- * @deprecated
33
- */
34
- const Table: FC<Props> = ({ data, sortable, children, emptyMessage, className }) => {
31
+ const Table: FC<Props> = ({ data, sortable = true, children, emptyMessage, className }) => {
35
32
  const [tableData, setTableData] = useState(data);
36
33
  useEffect(() => {
37
34
  setTableData(data);
@@ -41,12 +38,14 @@ const Table: FC<Props> = ({ data, sortable, children, emptyMessage, className })
41
38
  const [hoveredColumnIndex, setHoveredColumnIndex] = useState<number | undefined>();
42
39
 
43
40
  const isSortable = (child: ReactElement) => {
41
+ // @ts-ignore
44
42
  return sortable && child.props.createComparator;
45
43
  };
46
44
 
47
45
  const sortFunctions: Comparator | undefined[] = [];
48
46
  React.Children.forEach(children, (child, index) => {
49
47
  if (child && isSortable(child as ReactElement)) {
48
+ // @ts-ignore
50
49
  sortFunctions.push((child as ReactElement).props.createComparator((child as ReactElement).props, index));
51
50
  } else {
52
51
  sortFunctions.push(undefined);
@@ -57,6 +56,7 @@ const Table: FC<Props> = ({ data, sortable, children, emptyMessage, className })
57
56
  return (
58
57
  <tr key={rowIndex}>
59
58
  {React.Children.map(children, (child, columnIndex) => {
59
+ // @ts-ignore
60
60
  const { className: columnClassName, ...childProperties } = (child as ReactElement).props;
61
61
  return (
62
62
  <td className={columnClassName}>
@@ -116,6 +116,7 @@ const Table: FC<Props> = ({ data, sortable, children, emptyMessage, className })
116
116
  onMouseLeave={() => setHoveredColumnIndex(undefined)}
117
117
  key={index}
118
118
  >
119
+ {/* @ts-ignore */}
119
120
  {(child as ReactElement).props.header}
120
121
  {isSortable(child as ReactElement) &&
121
122
  renderSortIcon(child as ReactElement, ascending, shouldShowIcon(index))}
@@ -128,12 +129,10 @@ const Table: FC<Props> = ({ data, sortable, children, emptyMessage, className })
128
129
  );
129
130
  };
130
131
 
131
- Table.defaultProps = {
132
- sortable: true,
133
- };
134
-
135
132
  const renderSortIcon = (child: ReactElement, ascending: boolean, showIcon: boolean) => {
133
+ // @ts-ignore
136
134
  if (child.props.ascendingIcon && child.props.descendingIcon) {
135
+ // @ts-ignore
137
136
  return <SortIcon name={ascending ? child.props.ascendingIcon : child.props.descendingIcon} isVisible={showIcon} />;
138
137
  } else {
139
138
  return <SortIcon name={ascending ? "sort-amount-down-alt" : "sort-amount-down"} isVisible={showIcon} />;
@@ -16,7 +16,6 @@
16
16
 
17
17
  import React, { FC } from "react";
18
18
  import { ColumnProps } from "./types";
19
- import comparators from "../comparators";
20
19
 
21
20
  type Props = ColumnProps & {
22
21
  dataKey: string;
@@ -29,12 +28,4 @@ const TextColumn: FC<Props> = ({ row, dataKey }) => {
29
28
  return row[dataKey];
30
29
  };
31
30
 
32
- TextColumn.defaultProps = {
33
- createComparator: (props: Props) => {
34
- return comparators.byKey(props.dataKey);
35
- },
36
- ascendingIcon: "sort-alpha-down-alt",
37
- descendingIcon: "sort-alpha-down",
38
- };
39
-
40
31
  export default TextColumn;
@@ -14,7 +14,7 @@
14
14
  * along with this program. If not, see https://www.gnu.org/licenses/.
15
15
  */
16
16
 
17
- import React, { FC } from "react";
17
+ import React, { FC, ReactNode } from "react";
18
18
  import { createPortal } from "react-dom";
19
19
  import styled from "styled-components";
20
20
  import { getTheme, Themeable, ToastThemeContext, Type } from "./themes";
@@ -23,6 +23,7 @@ import usePortalRootElement from "../usePortalRootElement";
23
23
  type Props = {
24
24
  type: Type;
25
25
  title: string;
26
+ children?: ReactNode;
26
27
  };
27
28
 
28
29
  const Container = styled.div<Themeable>`
@@ -14,7 +14,7 @@
14
14
  * along with this program. If not, see https://www.gnu.org/licenses/.
15
15
  */
16
16
 
17
- import React, { FC } from "react";
17
+ import React, { FC, ReactNode } from "react";
18
18
  import usePortalRootElement from "../usePortalRootElement";
19
19
  import { createPortal } from "react-dom";
20
20
  import styled from "styled-components";
@@ -37,7 +37,7 @@ const Container = styled.div`
37
37
  }
38
38
  `;
39
39
 
40
- const ToastArea: FC = ({ children }) => {
40
+ const ToastArea: FC<{ children?: ReactNode }> = ({ children }) => {
41
41
  const rootElement = usePortalRootElement("toastRoot");
42
42
  if (!rootElement) {
43
43
  // portal not yet ready
@@ -14,7 +14,7 @@
14
14
  * along with this program. If not, see https://www.gnu.org/licenses/.
15
15
  */
16
16
 
17
- import React, { FC, useContext, MouseEvent } from "react";
17
+ import React, { FC, useContext, MouseEvent, ReactNode } from "react";
18
18
  import { ToastThemeContext, Themeable } from "./themes";
19
19
  import classNames from "classnames";
20
20
  import styled from "styled-components";
@@ -22,6 +22,7 @@ import styled from "styled-components";
22
22
  type Props = {
23
23
  icon?: string;
24
24
  onClick?: () => void;
25
+ children?: ReactNode;
25
26
  };
26
27
 
27
28
  const ThemedButton = styled.button.attrs((props) => ({
@@ -14,7 +14,7 @@
14
14
  * along with this program. If not, see https://www.gnu.org/licenses/.
15
15
  */
16
16
 
17
- import React, { FC } from "react";
17
+ import React, { FC, ReactNode } from "react";
18
18
  import styled from "styled-components";
19
19
 
20
20
  const FullWidthDiv = styled.div`
@@ -29,6 +29,8 @@ const FullWidthDiv = styled.div`
29
29
  }
30
30
  `;
31
31
 
32
- const ToastButtons: FC = ({ children }) => <FullWidthDiv className="is-flex pt-2">{children}</FullWidthDiv>;
32
+ const ToastButtons: FC<{ children?: ReactNode }> = ({ children }) => (
33
+ <FullWidthDiv className="is-flex pt-2">{children}</FullWidthDiv>
34
+ );
33
35
 
34
36
  export default ToastButtons;
@@ -14,7 +14,7 @@
14
14
  * along with this program. If not, see https://www.gnu.org/licenses/.
15
15
  */
16
16
 
17
- import React, { FC } from "react";
17
+ import React, { FC, ReactNode } from "react";
18
18
  import classNames from "classnames";
19
19
  import styled from "styled-components";
20
20
  import { getTheme, Themeable, ToastThemeContext, Type } from "./themes";
@@ -23,6 +23,7 @@ type Props = {
23
23
  type: Type;
24
24
  title: string;
25
25
  close?: () => void;
26
+ children?: ReactNode;
26
27
  };
27
28
 
28
29
  const Container = styled.div<Themeable>`
@@ -14,8 +14,87 @@
14
14
  * along with this program. If not, see https://www.gnu.org/licenses/.
15
15
  */
16
16
 
17
+ // import React, { useState } from "react";
18
+ // import { storiesOf } from "@storybook/react";
19
+ // import Toast from "./Toast";
20
+ // import ToastButtons from "./ToastButtons";
21
+ // import ToastButton from "./ToastButton";
22
+ // import { types } from "./themes";
23
+ // import ToastArea from "./ToastArea";
24
+ // import ToastNotification from "./ToastNotification";
25
+ //
26
+ // const toastStories = storiesOf("Toast", module);
27
+ //
28
+ // const AnimatedToast = () => (
29
+ // <Toast type="primary" title="Animated">
30
+ // Awesome animated Toast
31
+ // </Toast>
32
+ // );
33
+ //
34
+ // const Animator = () => {
35
+ // const [display, setDisplay] = useState(false);
36
+ //
37
+ // return (
38
+ // <div style={{ padding: "2rem" }}>
39
+ // {display && <AnimatedToast />}
40
+ // <button className="button is-primary" onClick={() => setDisplay(!display)}>
41
+ // {display ? "Close" : "Open"} Toast
42
+ // </button>
43
+ // </div>
44
+ // );
45
+ // };
46
+ //
47
+ // const Closeable = () => {
48
+ // const [show, setShow] = useState(true);
49
+ //
50
+ // const hide = () => {
51
+ // setShow(false);
52
+ // };
53
+ //
54
+ // if (!show) {
55
+ // return null;
56
+ // }
57
+ //
58
+ // return (
59
+ // <Toast type="success" title="Awesome feature">
60
+ // <p>Close the message with a click</p>
61
+ // <ToastButtons>
62
+ // <ToastButton icon="times" onClick={hide}>
63
+ // Click to close
64
+ // </ToastButton>
65
+ // </ToastButtons>
66
+ // </Toast>
67
+ // );
68
+ // };
69
+ //
70
+ // toastStories.add("Open/Close", () => <Animator />);
71
+ // toastStories.add("Click to close", () => <Closeable />);
72
+ //
73
+ // types.forEach((type) => {
74
+ // toastStories.add(type.charAt(0).toUpperCase() + type.slice(1), () => (
75
+ // <Toast type={type} title="New Changes">
76
+ // <p>The underlying Pull-Request has changed. Press reload to see the changes.</p>
77
+ // <p>Warning: Non saved modification will be lost.</p>
78
+ // <ToastButtons>
79
+ // <ToastButton icon="redo">Reload</ToastButton>
80
+ // <ToastButton icon="times">Ignore</ToastButton>
81
+ // </ToastButtons>
82
+ // </Toast>
83
+ // ));
84
+ // });
85
+ //
86
+ // toastStories.add("Multiple", () => (
87
+ // <ToastArea>
88
+ // {types.map((type) => (
89
+ // <ToastNotification key={type} type={type} title="New notification">
90
+ // <p>The notification received.</p>
91
+ // </ToastNotification>
92
+ // ))}
93
+ // </ToastArea>
94
+ // ));
95
+
17
96
  import React, { useState } from "react";
18
- import { storiesOf } from "@storybook/react";
97
+ import type { Meta, StoryObj } from "@storybook/react";
19
98
  import Toast from "./Toast";
20
99
  import ToastButtons from "./ToastButtons";
21
100
  import ToastButton from "./ToastButton";
@@ -23,43 +102,34 @@ import { types } from "./themes";
23
102
  import ToastArea from "./ToastArea";
24
103
  import ToastNotification from "./ToastNotification";
25
104
 
26
- const toastStories = storiesOf("Toast", module);
27
-
28
- const AnimatedToast = () => (
29
- <Toast type="primary" title="Animated">
30
- Awesome animated Toast
31
- </Toast>
32
- );
105
+ // --- Helfer-Komponenten (aus der Original-Story übernommen) ---
33
106
 
34
- const Animator = () => {
107
+ const OpenCloseExample = () => {
35
108
  const [display, setDisplay] = useState(false);
36
-
37
109
  return (
38
- <div style={{ padding: "2rem" }}>
39
- {display && <AnimatedToast />}
40
- <button className="button is-primary" onClick={() => setDisplay(!display)}>
110
+ <div>
111
+ {display && (
112
+ <Toast type="primary" title="Animated">
113
+ Awesome animated Toast
114
+ </Toast>
115
+ )}
116
+ <button className="button is-primary mt-2" onClick={() => setDisplay(!display)}>
41
117
  {display ? "Close" : "Open"} Toast
42
118
  </button>
43
119
  </div>
44
120
  );
45
121
  };
46
122
 
47
- const Closeable = () => {
123
+ const ClickToCloseExample = () => {
48
124
  const [show, setShow] = useState(true);
49
-
50
- const hide = () => {
51
- setShow(false);
52
- };
53
-
54
125
  if (!show) {
55
- return null;
126
+ return <p>Toast wurde geschlossen.</p>;
56
127
  }
57
-
58
128
  return (
59
129
  <Toast type="success" title="Awesome feature">
60
130
  <p>Close the message with a click</p>
61
131
  <ToastButtons>
62
- <ToastButton icon="times" onClick={hide}>
132
+ <ToastButton icon="times" onClick={() => setShow(false)}>
63
133
  Click to close
64
134
  </ToastButton>
65
135
  </ToastButtons>
@@ -67,28 +137,63 @@ const Closeable = () => {
67
137
  );
68
138
  };
69
139
 
70
- toastStories.add("Open/Close", () => <Animator />);
71
- toastStories.add("Click to close", () => <Closeable />);
140
+ // --- Storybook Metadaten ---
141
+
142
+ const meta: Meta<typeof Toast> = {
143
+ title: "Components/Toast",
144
+ component: Toast,
145
+ decorators: [(Story) => <div style={{ padding: "2rem" }}>{Story()}</div>],
146
+ tags: ["autodocs"],
147
+ };
148
+
149
+ export default meta;
150
+
151
+ // --- Story-Definitionen ---
72
152
 
73
- types.forEach((type) => {
74
- toastStories.add(type.charAt(0).toUpperCase() + type.slice(1), () => (
75
- <Toast type={type} title="New Changes">
153
+ type Story = StoryObj<typeof meta>;
154
+
155
+ // Basis-Props für die verschiedenen Toast-Typen
156
+ const baseArgs = {
157
+ title: "New Changes",
158
+ children: (
159
+ <>
76
160
  <p>The underlying Pull-Request has changed. Press reload to see the changes.</p>
77
161
  <p>Warning: Non saved modification will be lost.</p>
78
162
  <ToastButtons>
79
163
  <ToastButton icon="redo">Reload</ToastButton>
80
164
  <ToastButton icon="times">Ignore</ToastButton>
81
165
  </ToastButtons>
82
- </Toast>
83
- ));
84
- });
166
+ </>
167
+ ),
168
+ };
85
169
 
86
- toastStories.add("Multiple", () => (
87
- <ToastArea>
88
- {types.map((type) => (
89
- <ToastNotification key={type} type={type} title="New notification">
90
- <p>The notification received.</p>
91
- </ToastNotification>
92
- ))}
93
- </ToastArea>
94
- ));
170
+ // Anstatt einer Schleife erstellen wir explizite Stories für jeden Typ
171
+ export const Primary: Story = { args: { ...baseArgs, type: "primary" } };
172
+ export const Success: Story = { args: { ...baseArgs, type: "success" } };
173
+ export const Info: Story = { args: { ...baseArgs, type: "info" } };
174
+ export const Warning: Story = { args: { ...baseArgs, type: "warning" } };
175
+ export const Danger: Story = { args: { ...baseArgs, type: "danger" } };
176
+
177
+ // Stories mit eigener Logik verwenden eine `render`-Funktion
178
+ export const OpenClose: Story = {
179
+ name: "Open and Close Animation",
180
+ render: () => <OpenCloseExample />,
181
+ };
182
+
183
+ export const ClickToClose: Story = {
184
+ name: "Click to Close",
185
+ render: () => <ClickToCloseExample />,
186
+ };
187
+
188
+ export const Multiple: Story = {
189
+ name: "Multiple Toasts in an Area",
190
+ render: () => (
191
+ <ToastArea>
192
+ {types.map((type) => (
193
+ <ToastNotification key={type} type={type} title="New notification">
194
+ <p>The notification received.</p>
195
+ </ToastNotification>
196
+ ))}
197
+ </ToastArea>
198
+ ),
199
+ };
@@ -17,6 +17,6 @@
17
17
  export { default as Toast } from "./Toast";
18
18
  export { default as ToastArea } from "./ToastArea";
19
19
  export { default as ToastNotification } from "./ToastNotification";
20
- export { Type as ToastType } from "./themes";
20
+ export { type Type as ToastType } from "./themes";
21
21
  export { default as ToastButton } from "./ToastButton";
22
22
  export { default as ToastButtons } from "./ToastButtons";