@scm-manager/ui-components 4.0.0-REACT18-20250701-125025 → 4.0.0-REACT19

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 (124) hide show
  1. package/package.json +46 -51
  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/LinkPaginator.tsx +9 -6
  16. package/src/Loading.stories.tsx +26 -6
  17. package/src/Logo.stories.tsx +44 -13
  18. package/src/Notification.stories.tsx +111 -23
  19. package/src/OverviewPageActions.tsx +5 -5
  20. package/src/Paginator.test.tsx +115 -94
  21. package/src/PdfViewer.stories.tsx +83 -5
  22. package/src/PreformattedCodeBlock.stories.tsx +97 -26
  23. package/src/ProtectedRoute.tsx +14 -39
  24. package/src/SmallLoadingSpinner.stories.tsx +26 -6
  25. package/src/SyntaxHighlighter.stories.tsx +122 -40
  26. package/src/SyntaxHighlighterRenderer.tsx +7 -7
  27. package/src/Tag.stories.tsx +135 -18
  28. package/src/Tag.tsx +2 -1
  29. package/src/Tooltip.stories.tsx +147 -34
  30. package/src/__snapshots__/storyshots.test.ts.snap +6 -112
  31. package/src/avatar/Avatar.ts +1 -1
  32. package/src/avatar/AvatarImage.tsx +1 -1
  33. package/src/avatar/AvatarWrapper.tsx +2 -2
  34. package/src/buttons/Button.stories.tsx +159 -0
  35. package/src/buttons/Button.tsx +5 -5
  36. package/src/config/ConfigurationBinder.tsx +39 -39
  37. package/src/config/ConfigurationForm.tsx +2 -1
  38. package/src/config/TitledSettings.tsx +4 -1
  39. package/src/forms/AddKeyValueEntryToTableField.stories.tsx +60 -25
  40. package/src/forms/Checkbox.stories.tsx +165 -27
  41. package/src/forms/Checkbox.tsx +1 -0
  42. package/src/forms/DropDown.stories.tsx +81 -35
  43. package/src/forms/FileInput.stories.tsx +85 -10
  44. package/src/forms/InputField.stories.tsx +210 -37
  45. package/src/forms/InputField.tsx +1 -0
  46. package/src/forms/Radio.stories.tsx +181 -34
  47. package/src/forms/Radio.tsx +1 -0
  48. package/src/forms/Select.stories.tsx +266 -46
  49. package/src/forms/Select.tsx +1 -0
  50. package/src/forms/Textarea.stories.tsx +99 -53
  51. package/src/forms/Textarea.tsx +1 -0
  52. package/src/forms/index.ts +3 -1
  53. package/src/index.ts +5 -5
  54. package/src/jest-dom.d.ts +17 -0
  55. package/src/layout/Footer.stories.tsx +114 -22
  56. package/src/layout/FooterSection.tsx +1 -0
  57. package/src/layout/Header.tsx +2 -1
  58. package/src/layout/Page.tsx +1 -3
  59. package/src/layout/PrimaryContentColumn.tsx +2 -2
  60. package/src/layout/SecondaryNavigationColumn.tsx +3 -3
  61. package/src/layout/SubSubtitle.tsx +2 -1
  62. package/src/layout/Subtitle.tsx +2 -1
  63. package/src/layout/Title.tsx +2 -5
  64. package/src/markdown/LazyMarkdownView.tsx +16 -5
  65. package/src/markdown/MarkdownHeadingRenderer.test.ts +9 -1
  66. package/src/markdown/MarkdownHeadingRenderer.tsx +3 -3
  67. package/src/markdown/MarkdownImageRenderer.test.ts +8 -1
  68. package/src/markdown/MarkdownImageRenderer.tsx +2 -1
  69. package/src/markdown/MarkdownLinkRenderer.test.tsx +9 -0
  70. package/src/markdown/MarkdownLinkRenderer.tsx +6 -4
  71. package/src/markdown/MarkdownView.stories.tsx +224 -72
  72. package/src/markdown/markdownExtensions.ts +6 -4
  73. package/src/modals/ConfirmAlert.stories.tsx +133 -44
  74. package/src/modals/ConfirmAlert.tsx +5 -4
  75. package/src/modals/Modal.stories.tsx +461 -252
  76. package/src/modals/Modal.tsx +12 -11
  77. package/src/modals/useRegisterModal.test.tsx +5 -4
  78. package/src/navigation/MenuContext.tsx +2 -2
  79. package/src/navigation/NavLink.tsx +4 -7
  80. package/src/navigation/PrimaryNavigation.tsx +2 -2
  81. package/src/navigation/PrimaryNavigationLink.tsx +6 -5
  82. package/src/navigation/RoutingProps.ts +1 -3
  83. package/src/navigation/SecondaryNavigation.stories.tsx +157 -45
  84. package/src/navigation/SecondaryNavigation.tsx +17 -16
  85. package/src/navigation/SecondaryNavigationItem.tsx +2 -1
  86. package/src/navigation/SubNavigation.tsx +4 -8
  87. package/src/navigation/useActiveMatch.ts +20 -22
  88. package/src/navigation/useNavigationLock.ts +1 -0
  89. package/src/popover/Popover.stories.tsx +111 -41
  90. package/src/popover/Popover.tsx +2 -5
  91. package/src/repos/Diff.stories.tsx +418 -223
  92. package/src/repos/Diff.tsx +1 -5
  93. package/src/repos/HunkExpandDivider.tsx +2 -2
  94. package/src/repos/LoadingDiff.tsx +11 -6
  95. package/src/repos/RepositoryEntry.stories.tsx +217 -53
  96. package/src/repos/RepositoryFlag.tsx +2 -1
  97. package/src/repos/TokenizedDiffView.tsx +4 -2
  98. package/src/repos/annotate/Annotate.stories.tsx +225 -111
  99. package/src/repos/annotate/AnnotateLine.tsx +2 -1
  100. package/src/repos/changesets/ChangesetAuthor.tsx +2 -2
  101. package/src/repos/changesets/ChangesetButtonGroup.test.tsx +9 -5
  102. package/src/repos/changesets/ChangesetDiff.test.ts +10 -2
  103. package/src/repos/changesets/Changesets.stories.tsx +388 -197
  104. package/src/repos/changesets/ContributorRow.tsx +2 -2
  105. package/src/repos/changesets/SignatureIcon.tsx +1 -0
  106. package/src/repos/diff/DiffFileTree.tsx +1 -1
  107. package/src/repos/diff/styledElements.tsx +4 -3
  108. package/src/repos/index.ts +15 -15
  109. package/src/search/Hit.tsx +3 -2
  110. package/src/search/TextHitField.stories.tsx +131 -43
  111. package/src/search/TextHitField.tsx +3 -2
  112. package/src/search/index.ts +1 -1
  113. package/src/storyshots.test.ts +66 -60
  114. package/src/table/Table.stories.tsx +146 -48
  115. package/src/table/Table.tsx +7 -8
  116. package/src/table/TextColumn.tsx +0 -9
  117. package/src/toast/Toast.tsx +2 -1
  118. package/src/toast/ToastArea.tsx +2 -2
  119. package/src/toast/ToastButton.tsx +2 -1
  120. package/src/toast/ToastButtons.tsx +4 -2
  121. package/src/toast/ToastNotification.tsx +2 -1
  122. package/src/toast/index.stories.tsx +144 -39
  123. package/src/toast/index.ts +1 -1
  124. package/src/buttons/index.stories.tsx +0 -85
@@ -14,47 +14,314 @@
14
14
  * along with this program. If not, see https://www.gnu.org/licenses/.
15
15
  */
16
16
 
17
+ // import React, { ReactNode, useEffect, useState } from "react";
18
+ // import { storiesOf } from "@storybook/react";
19
+ // import Diff from "./Diff";
20
+ // import parser from "gitdiff-parser";
21
+ // import simpleDiff from "../__resources__/Diff.simple";
22
+ // import hunksDiff from "../__resources__/Diff.hunks";
23
+ // import binaryDiff from "../__resources__/Diff.binary";
24
+ // import markdownDiff from "../__resources__/Diff.markdown";
25
+ // import { DiffEventContext, FileControlFactory } from "./DiffTypes";
26
+ // import Toast from "../toast/Toast";
27
+ // import { getPath } from "./diffs";
28
+ // import DiffButton from "./DiffButton";
29
+ // import styled from "styled-components";
30
+ // import { MemoryRouter } from "react-router-dom";
31
+ // import { two } from "../__resources__/changesets";
32
+ // import { Changeset, FileDiff } from "@scm-manager/ui-types";
33
+ // import JumpToFileButton from "./JumpToFileButton";
34
+ // import Button from "../buttons/Button";
35
+ // // @ts-ignore ignore unknown png
36
+ // import hitchhikerImg from "../__resources__/hitchhiker.png";
37
+ // // @ts-ignore ignore unknown jpg
38
+ // import marvinImg from "../__resources__/marvin.jpg";
39
+ //
40
+ // const diffFiles = parser.parse(simpleDiff);
41
+ //
42
+ // const Container = styled.div`
43
+ // padding: 2rem 6rem;
44
+ // `;
45
+ //
46
+ // type ExternalDiffState = {
47
+ // [key: string]: boolean;
48
+ // };
49
+ //
50
+ // const RoutingDecorator = (story: () => ReactNode) => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>;
51
+ //
52
+ // const fileControlFactory: (changeset: Changeset) => FileControlFactory = (changeset) => (file) => {
53
+ // const baseUrl = "/repo/hitchhiker/heartOfGold/code/changeset";
54
+ // const sourceLink = {
55
+ // url: `${baseUrl}/${changeset.id}/${file.newPath}/`,
56
+ // label: "Jump to source",
57
+ // };
58
+ // const targetLink = changeset._embedded?.parents?.length === 1 && {
59
+ // url: `${baseUrl}/${changeset._embedded.parents[0].id}/${file.oldPath}`,
60
+ // label: "Jump to target",
61
+ // };
62
+ //
63
+ // const links = [];
64
+ // switch (file.type) {
65
+ // case "add":
66
+ // links.push(sourceLink);
67
+ // break;
68
+ // case "delete":
69
+ // if (targetLink) {
70
+ // links.push(targetLink);
71
+ // }
72
+ // break;
73
+ // default:
74
+ // if (targetLink) {
75
+ // links.push(sourceLink, targetLink);
76
+ // } else {
77
+ // links.push(sourceLink);
78
+ // }
79
+ // }
80
+ //
81
+ // return links.map(({ url, label }) => <JumpToFileButton tooltip={label} link={url} />);
82
+ // };
83
+ //
84
+ // storiesOf("Repositories/Diff", module)
85
+ // .addDecorator(RoutingDecorator)
86
+ // .addDecorator((storyFn) => <Container>{storyFn()}</Container>)
87
+ // .add("Default", () => <Diff diff={diffFiles} />)
88
+ // .add("Side-By-Side", () => <Diff diff={diffFiles} sideBySide={true} />)
89
+ // .add("Whitespace", () => <Diff diff={diffFiles} whitespace={true} />)
90
+ // .add("Collapsed", () => <Diff diff={diffFiles} defaultCollapse={true} fileControlFactory={fileControlFactory(two)} />)
91
+ // .add("File Controls", () => (
92
+ // <Diff
93
+ // diff={diffFiles}
94
+ // fileControlFactory={() => (
95
+ // <DiffButton
96
+ // tooltip="A skull and crossbones or death's head is a symbol consisting of a human skull and two long bones crossed together under or behind the skull. The design originates in the Late Middle Ages as a symbol of death and especially as a memento mori on tombstones."
97
+ // icon="skull-crossbones"
98
+ // onClick={() => alert("Arrrgggghhhh ...")}
99
+ // />
100
+ // )}
101
+ // />
102
+ // ))
103
+ // .add("File Annotation", () => (
104
+ // <Diff
105
+ // diff={diffFiles}
106
+ // fileAnnotationFactory={(file) => [<p key={file.newPath}>Custom File annotation for {file.newPath}</p>]}
107
+ // />
108
+ // ))
109
+ // .add("Line Annotation", () => (
110
+ // <Diff
111
+ // diff={diffFiles}
112
+ // annotationFactory={(ctx) => {
113
+ // return {
114
+ // N2: <p key="N2">Line Annotation</p>,
115
+ // };
116
+ // }}
117
+ // />
118
+ // ))
119
+ // .add("OnClick", () => {
120
+ // const OnClickDemo = () => {
121
+ // const [changeId, setChangeId] = useState();
122
+ // useEffect(() => {
123
+ // const interval = setInterval(() => setChangeId(undefined), 2000);
124
+ // return () => clearInterval(interval);
125
+ // });
126
+ // // @ts-ignore
127
+ // const onClick = (context: DiffEventContext) => setChangeId(context.changeId);
128
+ // return (
129
+ // <>
130
+ // {changeId && <Toast type="info" title={"Change " + changeId} />}
131
+ // <Diff diff={diffFiles} onClick={onClick} />
132
+ // </>
133
+ // );
134
+ // };
135
+ // return <OnClickDemo />;
136
+ // })
137
+ // .add("Hunks", () => {
138
+ // const hunkDiffFiles = parser.parse(hunksDiff);
139
+ // return <Diff diff={hunkDiffFiles} />;
140
+ // })
141
+ // .add("Hunk gutter hover icon", () => {
142
+ // const hunkDiffFiles = parser.parse(hunksDiff);
143
+ // return <Diff diff={hunkDiffFiles} hunkGutterHoverIcon="\f075" />;
144
+ // })
145
+ // .add("Highlight line on hover", () => {
146
+ // const hunkDiffFiles = parser.parse(hunksDiff);
147
+ // return <Diff diff={hunkDiffFiles} highlightLineOnHover />;
148
+ // })
149
+ // .add("Binaries", () => {
150
+ // const binaryDiffFiles = parser.parse(binaryDiff);
151
+ // return <Diff diff={binaryDiffFiles} />;
152
+ // })
153
+ // .add("Images", () => {
154
+ // const binaryDiffFiles: FileDiff[] = [
155
+ // {
156
+ // type: "add",
157
+ // newPath: "test.png",
158
+ // oldPath: "/dev/null",
159
+ // isBinary: true,
160
+ // newEndingNewLine: false,
161
+ // oldEndingNewLine: false,
162
+ // _links: {
163
+ // newFile: {
164
+ // href: `${window.location.protocol}//${window.location.host}/${hitchhikerImg}`,
165
+ // },
166
+ // },
167
+ // },
168
+ // {
169
+ // type: "delete",
170
+ // newPath: "/dev/null",
171
+ // oldPath: "test.png",
172
+ // isBinary: true,
173
+ // newEndingNewLine: false,
174
+ // oldEndingNewLine: false,
175
+ // _links: {
176
+ // oldFile: {
177
+ // href: `${window.location.protocol}//${window.location.host}/${hitchhikerImg}`,
178
+ // },
179
+ // },
180
+ // },
181
+ // {
182
+ // type: "modify",
183
+ // newPath: "test.png",
184
+ // oldPath: "test.png",
185
+ // isBinary: true,
186
+ // newEndingNewLine: false,
187
+ // oldEndingNewLine: false,
188
+ // _links: {
189
+ // oldFile: {
190
+ // href: `${window.location.protocol}//${window.location.host}/${hitchhikerImg}`,
191
+ // },
192
+ // newFile: {
193
+ // href: `${window.location.protocol}//${window.location.host}/${marvinImg}`,
194
+ // },
195
+ // },
196
+ // },
197
+ // {
198
+ // type: "rename",
199
+ // newPath: "test.png",
200
+ // oldPath: "newFileName.png",
201
+ // isBinary: true,
202
+ // newEndingNewLine: false,
203
+ // oldEndingNewLine: false,
204
+ // _links: {
205
+ // oldFile: {
206
+ // href: `${window.location.protocol}//${window.location.host}/${hitchhikerImg}`,
207
+ // },
208
+ // newFile: {
209
+ // href: `${window.location.protocol}//${window.location.host}/${hitchhikerImg}`,
210
+ // },
211
+ // },
212
+ // },
213
+ // {
214
+ // type: "copy",
215
+ // newPath: "test.png",
216
+ // oldPath: "newFileName.png",
217
+ // isBinary: true,
218
+ // newEndingNewLine: false,
219
+ // oldEndingNewLine: false,
220
+ // _links: {
221
+ // oldFile: {
222
+ // href: `${window.location.protocol}//${window.location.host}/${hitchhikerImg}`,
223
+ // },
224
+ // newFile: {
225
+ // href: `${window.location.protocol}//${window.location.host}/${hitchhikerImg}`,
226
+ // },
227
+ // },
228
+ // },
229
+ // ];
230
+ // return <Diff diff={binaryDiffFiles} />;
231
+ // })
232
+ // .add("SyntaxHighlighting", () => {
233
+ // const filesWithLanguage = diffFiles.map((file: FileDiff) => {
234
+ // const ext = getPath(file).split(".")[1];
235
+ // if (ext === "tsx") {
236
+ // file.language = "typescript";
237
+ // } else {
238
+ // file.language = ext;
239
+ // }
240
+ // return file;
241
+ // });
242
+ // return <Diff diff={filesWithLanguage} />;
243
+ // })
244
+ // .add("SyntaxHighlighting (Markdown)", () => {
245
+ // // @ts-ignore
246
+ // return <Diff diff={markdownDiff.files} />;
247
+ // })
248
+ // .add("CollapsingWithFunction", () => (
249
+ // <Diff diff={diffFiles} defaultCollapse={(oldPath, newPath) => oldPath.endsWith(".java")} />
250
+ // ))
251
+ // .add("Expandable", () => {
252
+ // const filesWithLanguage = diffFiles.map((file: FileDiff) => {
253
+ // file._links = { lines: { href: "http://example.com/" } };
254
+ // return file;
255
+ // });
256
+ // return <Diff diff={filesWithLanguage} />;
257
+ // })
258
+ // .add("WithLinkToFile", () => <Diff diff={diffFiles} />)
259
+ // .add("Changing Content", () => {
260
+ // const ChangingContentDiff = () => {
261
+ // const [markdown, setMarkdown] = useState(false);
262
+ // return (
263
+ // <div>
264
+ // <Button className="mb-5" action={() => setMarkdown((m) => !m)}>
265
+ // Change content
266
+ // </Button>
267
+ // {/* @ts-ignore */}
268
+ // <Diff diff={markdown ? markdownDiff.files : diffFiles} />
269
+ // </div>
270
+ // );
271
+ // };
272
+ //
273
+ // return <ChangingContentDiff />;
274
+ // })
275
+ // .add("External state management", () => {
276
+ // const [externalState, setExternalState] = useState<ExternalDiffState>({});
277
+ //
278
+ // const isCollapsed = (file: FileDiff) => {
279
+ // return externalState[file.newPath] || false;
280
+ // };
281
+ //
282
+ // const onCollapseStateChange = (file: FileDiff) => {
283
+ // setExternalState((current) => ({ ...current, [file.newPath]: !current[file.newPath] }));
284
+ // };
285
+ //
286
+ // return <Diff diff={diffFiles} isCollapsed={isCollapsed} onCollapseStateChange={onCollapseStateChange} />;
287
+ // });
288
+
17
289
  import React, { ReactNode, useEffect, useState } from "react";
18
- import { storiesOf } from "@storybook/react";
19
- import Diff from "./Diff";
290
+ import styled from "styled-components";
291
+ import type { Meta, StoryObj } from "@storybook/react";
20
292
  import parser from "gitdiff-parser";
293
+ import { Changeset, FileDiff } from "@scm-manager/ui-types";
294
+
295
+ import Diff from "./Diff";
296
+ import DiffButton from "./DiffButton";
297
+ import JumpToFileButton from "./JumpToFileButton";
298
+ import Toast from "../toast/Toast";
299
+ import Button from "../buttons/Button";
300
+ import { DiffEventContext, FileControlFactory } from "./DiffTypes";
301
+ import { getPath } from "./diffs";
302
+
21
303
  import simpleDiff from "../__resources__/Diff.simple";
22
304
  import hunksDiff from "../__resources__/Diff.hunks";
23
305
  import binaryDiff from "../__resources__/Diff.binary";
24
306
  import markdownDiff from "../__resources__/Diff.markdown";
25
- import { DiffEventContext, FileControlFactory } from "./DiffTypes";
26
- import Toast from "../toast/Toast";
27
- import { getPath } from "./diffs";
28
- import DiffButton from "./DiffButton";
29
- import styled from "styled-components";
30
- import { MemoryRouter } from "react-router-dom";
31
307
  import { two } from "../__resources__/changesets";
32
- import { Changeset, FileDiff } from "@scm-manager/ui-types";
33
- import JumpToFileButton from "./JumpToFileButton";
34
- import Button from "../buttons/Button";
35
308
  // @ts-ignore ignore unknown png
36
309
  import hitchhikerImg from "../__resources__/hitchhiker.png";
37
310
  // @ts-ignore ignore unknown jpg
38
311
  import marvinImg from "../__resources__/marvin.jpg";
312
+ import { MemoryRouter } from "react-router-dom";
39
313
 
40
- const diffFiles = parser.parse(simpleDiff);
314
+ // --- Helfer-Funktionen und Mock-Daten (aus der Original-Story übernommen) ---
41
315
 
42
316
  const Container = styled.div`
43
- padding: 2rem 6rem;
317
+ padding: 2rem 1rem; // Adjusted padding for better viewing
44
318
  `;
45
319
 
46
- type ExternalDiffState = {
47
- [key: string]: boolean;
48
- };
49
-
50
- const RoutingDecorator = (story: () => ReactNode) => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>;
320
+ const diffFiles = parser.parse(simpleDiff);
51
321
 
52
322
  const fileControlFactory: (changeset: Changeset) => FileControlFactory = (changeset) => (file) => {
53
323
  const baseUrl = "/repo/hitchhiker/heartOfGold/code/changeset";
54
- const sourceLink = {
55
- url: `${baseUrl}/${changeset.id}/${file.newPath}/`,
56
- label: "Jump to source",
57
- };
324
+ const sourceLink = { url: `${baseUrl}/${changeset.id}/${file.newPath}/`, label: "Jump to source" };
58
325
  const targetLink = changeset._embedded?.parents?.length === 1 && {
59
326
  url: `${baseUrl}/${changeset._embedded.parents[0].id}/${file.oldPath}`,
60
327
  label: "Jump to target",
@@ -77,211 +344,139 @@ const fileControlFactory: (changeset: Changeset) => FileControlFactory = (change
77
344
  links.push(sourceLink);
78
345
  }
79
346
  }
80
-
81
347
  return links.map(({ url, label }) => <JumpToFileButton tooltip={label} link={url} />);
82
348
  };
83
349
 
84
- storiesOf("Repositories/Diff", module)
85
- .addDecorator(RoutingDecorator)
86
- .addDecorator((storyFn) => <Container>{storyFn()}</Container>)
87
- .add("Default", () => <Diff diff={diffFiles} />)
88
- .add("Side-By-Side", () => <Diff diff={diffFiles} sideBySide={true} />)
89
- .add("Whitespace", () => <Diff diff={diffFiles} whitespace={true} />)
90
- .add("Collapsed", () => <Diff diff={diffFiles} defaultCollapse={true} fileControlFactory={fileControlFactory(two)} />)
91
- .add("File Controls", () => (
92
- <Diff
93
- diff={diffFiles}
94
- fileControlFactory={() => (
95
- <DiffButton
96
- tooltip="A skull and crossbones or death's head is a symbol consisting of a human skull and two long bones crossed together under or behind the skull. The design originates in the Late Middle Ages as a symbol of death and especially as a memento mori on tombstones."
97
- icon="skull-crossbones"
98
- onClick={() => alert("Arrrgggghhhh ...")}
99
- />
100
- )}
101
- />
102
- ))
103
- .add("File Annotation", () => (
104
- <Diff
105
- diff={diffFiles}
106
- fileAnnotationFactory={(file) => [<p key={file.newPath}>Custom File annotation for {file.newPath}</p>]}
107
- />
108
- ))
109
- .add("Line Annotation", () => (
110
- <Diff
111
- diff={diffFiles}
112
- annotationFactory={(ctx) => {
113
- return {
114
- N2: <p key="N2">Line Annotation</p>,
115
- };
116
- }}
117
- />
118
- ))
119
- .add("OnClick", () => {
120
- const OnClickDemo = () => {
121
- const [changeId, setChangeId] = useState();
122
- useEffect(() => {
123
- const interval = setInterval(() => setChangeId(undefined), 2000);
124
- return () => clearInterval(interval);
125
- });
126
- // @ts-ignore
127
- const onClick = (context: DiffEventContext) => setChangeId(context.changeId);
128
- return (
129
- <>
130
- {changeId && <Toast type="info" title={"Change " + changeId} />}
131
- <Diff diff={diffFiles} onClick={onClick} />
132
- </>
133
- );
134
- };
135
- return <OnClickDemo />;
136
- })
137
- .add("Hunks", () => {
138
- const hunkDiffFiles = parser.parse(hunksDiff);
139
- return <Diff diff={hunkDiffFiles} />;
140
- })
141
- .add("Hunk gutter hover icon", () => {
142
- const hunkDiffFiles = parser.parse(hunksDiff);
143
- return <Diff diff={hunkDiffFiles} hunkGutterHoverIcon="\f075" />;
144
- })
145
- .add("Highlight line on hover", () => {
146
- const hunkDiffFiles = parser.parse(hunksDiff);
147
- return <Diff diff={hunkDiffFiles} highlightLineOnHover />;
148
- })
149
- .add("Binaries", () => {
150
- const binaryDiffFiles = parser.parse(binaryDiff);
151
- return <Diff diff={binaryDiffFiles} />;
152
- })
153
- .add("Images", () => {
154
- const binaryDiffFiles: FileDiff[] = [
155
- {
156
- type: "add",
157
- newPath: "test.png",
158
- oldPath: "/dev/null",
159
- isBinary: true,
160
- newEndingNewLine: false,
161
- oldEndingNewLine: false,
162
- _links: {
163
- newFile: {
164
- href: `${window.location.protocol}//${window.location.host}/${hitchhikerImg}`,
165
- },
166
- },
167
- },
168
- {
169
- type: "delete",
170
- newPath: "/dev/null",
171
- oldPath: "test.png",
172
- isBinary: true,
173
- newEndingNewLine: false,
174
- oldEndingNewLine: false,
175
- _links: {
176
- oldFile: {
177
- href: `${window.location.protocol}//${window.location.host}/${hitchhikerImg}`,
178
- },
179
- },
180
- },
181
- {
182
- type: "modify",
183
- newPath: "test.png",
184
- oldPath: "test.png",
185
- isBinary: true,
186
- newEndingNewLine: false,
187
- oldEndingNewLine: false,
188
- _links: {
189
- oldFile: {
190
- href: `${window.location.protocol}//${window.location.host}/${hitchhikerImg}`,
191
- },
192
- newFile: {
193
- href: `${window.location.protocol}//${window.location.host}/${marvinImg}`,
194
- },
195
- },
196
- },
197
- {
198
- type: "rename",
199
- newPath: "test.png",
200
- oldPath: "newFileName.png",
201
- isBinary: true,
202
- newEndingNewLine: false,
203
- oldEndingNewLine: false,
204
- _links: {
205
- oldFile: {
206
- href: `${window.location.protocol}//${window.location.host}/${hitchhikerImg}`,
207
- },
208
- newFile: {
209
- href: `${window.location.protocol}//${window.location.host}/${hitchhikerImg}`,
210
- },
211
- },
212
- },
213
- {
214
- type: "copy",
215
- newPath: "test.png",
216
- oldPath: "newFileName.png",
217
- isBinary: true,
218
- newEndingNewLine: false,
219
- oldEndingNewLine: false,
220
- _links: {
221
- oldFile: {
222
- href: `${window.location.protocol}//${window.location.host}/${hitchhikerImg}`,
223
- },
224
- newFile: {
225
- href: `${window.location.protocol}//${window.location.host}/${hitchhikerImg}`,
226
- },
227
- },
228
- },
229
- ];
230
- return <Diff diff={binaryDiffFiles} />;
231
- })
232
- .add("SyntaxHighlighting", () => {
233
- const filesWithLanguage = diffFiles.map((file: FileDiff) => {
234
- const ext = getPath(file).split(".")[1];
235
- if (ext === "tsx") {
236
- file.language = "typescript";
237
- } else {
238
- file.language = ext;
239
- }
240
- return file;
241
- });
242
- return <Diff diff={filesWithLanguage} />;
243
- })
244
- .add("SyntaxHighlighting (Markdown)", () => {
245
- // @ts-ignore
246
- return <Diff diff={markdownDiff.files} />;
247
- })
248
- .add("CollapsingWithFunction", () => (
249
- <Diff diff={diffFiles} defaultCollapse={(oldPath, newPath) => oldPath.endsWith(".java")} />
250
- ))
251
- .add("Expandable", () => {
252
- const filesWithLanguage = diffFiles.map((file: FileDiff) => {
253
- file._links = { lines: { href: "http://example.com/" } };
254
- return file;
255
- });
256
- return <Diff diff={filesWithLanguage} />;
257
- })
258
- .add("WithLinkToFile", () => <Diff diff={diffFiles} />)
259
- .add("Changing Content", () => {
260
- const ChangingContentDiff = () => {
261
- const [markdown, setMarkdown] = useState(false);
262
- return (
263
- <div>
264
- <Button className="mb-5" action={() => setMarkdown((m) => !m)}>
265
- Change content
266
- </Button>
267
- {/* @ts-ignore */}
268
- <Diff diff={markdown ? markdownDiff.files : diffFiles} />
269
- </div>
270
- );
271
- };
350
+ // Helper-Komponenten für komplexe Stories
351
+ const OnClickExample = () => {
352
+ const [changeId, setChangeId] = useState<string | undefined>();
353
+ useEffect(() => {
354
+ const interval = setInterval(() => setChangeId(undefined), 2000);
355
+ return () => clearInterval(interval);
356
+ }, []);
357
+ const onClick = (context: DiffEventContext) => setChangeId(context.changeId);
358
+ return (
359
+ <>
360
+ {changeId && <Toast type="info" title={`Change ${changeId}`} />}
361
+ <Diff diff={diffFiles} onClick={onClick} />
362
+ </>
363
+ );
364
+ };
272
365
 
273
- return <ChangingContentDiff />;
274
- })
275
- .add("External state management", () => {
276
- const [externalState, setExternalState] = useState<ExternalDiffState>({});
366
+ const ChangingContentExample = () => {
367
+ const [markdown, setMarkdown] = useState(false);
368
+ return (
369
+ <div>
370
+ <Button className="mb-5" action={() => setMarkdown((m) => !m)}>
371
+ Change content
372
+ </Button>
373
+ {/* @ts-ignore */}
374
+ <Diff diff={markdown ? markdownDiff.files : diffFiles} />
375
+ </div>
376
+ );
377
+ };
378
+
379
+ const ExternalStateExample = () => {
380
+ const [externalState, setExternalState] = useState<{ [key: string]: boolean }>({});
381
+ const isCollapsed = (file: FileDiff) => externalState[file.newPath] || false;
382
+ const onCollapseStateChange = (file: FileDiff) => {
383
+ setExternalState((current) => ({ ...current, [file.newPath]: !current[file.newPath] }));
384
+ };
385
+ return <Diff diff={diffFiles} isCollapsed={isCollapsed} onCollapseStateChange={onCollapseStateChange} />;
386
+ };
277
387
 
278
- const isCollapsed = (file: FileDiff) => {
279
- return externalState[file.newPath] || false;
280
- };
388
+ // --- Storybook Metadaten ---
389
+
390
+ const meta: Meta<typeof Diff> = {
391
+ title: "Repositories/Diff",
392
+ component: Diff,
393
+ decorators: [
394
+ (Story) => <MemoryRouter initialEntries={["/"]}>{Story()}</MemoryRouter>,
395
+ (Story) => <Container>{Story()}</Container>],
396
+ tags: ["autodocs"],
397
+ };
398
+
399
+ export default meta;
400
+
401
+ // --- Story-Definitionen ---
402
+
403
+ type Story = StoryObj<typeof meta>;
404
+
405
+ export const Default: Story = { args: { diff: diffFiles } };
406
+ export const SideBySide: Story = { args: { diff: diffFiles, sideBySide: true } };
407
+ export const Whitespace: Story = { args: { diff: diffFiles, whitespace: true } };
408
+ export const Collapsed: Story = {
409
+ args: { diff: diffFiles, defaultCollapse: true, fileControlFactory: fileControlFactory(two) },
410
+ };
411
+ export const Hunks: Story = { args: { diff: parser.parse(hunksDiff) } };
412
+ export const Binaries: Story = { args: { diff: parser.parse(binaryDiff) } };
413
+ export const HunkGutterHoverIcon: Story = { args: { diff: parser.parse(hunksDiff), hunkGutterHoverIcon: "\f075" } };
414
+ export const HighlightLineOnHover: Story = { args: { diff: parser.parse(hunksDiff), highlightLineOnHover: true } };
415
+ export const Expandable: Story = {
416
+ args: { diff: diffFiles.map((file) => ({ ...file, _links: { lines: { href: "http://example.com/" } } })) },
417
+ };
418
+ export const WithLinkToFile: Story = { args: { diff: diffFiles } };
419
+
420
+ export const FileControls: Story = {
421
+ args: {
422
+ diff: diffFiles,
423
+ fileControlFactory: () => (
424
+ <DiffButton
425
+ tooltip="A skull and crossbones or death's head..."
426
+ icon="skull-crossbones"
427
+ onClick={() => alert("Arrrgggghhhh ...")}
428
+ />
429
+ ),
430
+ },
431
+ };
432
+
433
+ export const FileAnnotation: Story = {
434
+ args: {
435
+ diff: diffFiles,
436
+ fileAnnotationFactory: (file) => [<p key={file.newPath}>Custom File annotation for {file.newPath}</p>],
437
+ },
438
+ };
439
+
440
+ export const LineAnnotation: Story = {
441
+ args: {
442
+ diff: diffFiles,
443
+ annotationFactory: () => ({ N2: <p key="N2">Line Annotation</p> }),
444
+ },
445
+ };
446
+
447
+ export const CollapsingWithFunction: Story = {
448
+ args: {
449
+ diff: diffFiles,
450
+ defaultCollapse: (oldPath) => oldPath.endsWith(".java"),
451
+ },
452
+ };
453
+
454
+ export const SyntaxHighlighting: Story = {
455
+ args: {
456
+ diff: diffFiles.map((file) => {
457
+ const ext = getPath(file).split(".")[1];
458
+ return { ...file, language: ext === "tsx" ? "typescript" : ext };
459
+ }),
460
+ },
461
+ };
462
+
463
+ export const SyntaxHighlightingMarkdown: Story = {
464
+ name: "Syntax Highlighting (Markdown)",
465
+ // @ts-ignore
466
+ args: { diff: markdownDiff.files },
467
+ };
281
468
 
282
- const onCollapseStateChange = (file: FileDiff) => {
283
- setExternalState((current) => ({ ...current, [file.newPath]: !current[file.newPath] }));
284
- };
469
+ // export const Images: Story = {
470
+ // args: {
471
+ // diff: [
472
+ // { type: "add", newPath: "add.png", isBinary: true, _links: { newFile: { href: hitchhikerImg } } },
473
+ // { type: "delete", oldPath: "delete.png", isBinary: true, _links: { oldFile: { href: hitchhikerImg } } },
474
+ // { type: "modify", newPath: "modify.png", isBinary: true, _links: { oldFile: { href: hitchhikerImg }, newFile: { href: marvinImg } } },
475
+ // ] as FileDiff[],
476
+ // },
477
+ // };
285
478
 
286
- return <Diff diff={diffFiles} isCollapsed={isCollapsed} onCollapseStateChange={onCollapseStateChange} />;
287
- });
479
+ // Stories with stateful logic use `render`
480
+ export const OnClick: Story = { render: () => <OnClickExample /> };
481
+ export const ChangingContent: Story = { render: () => <ChangingContentExample /> };
482
+ export const ExternalStateManagement: Story = { render: () => <ExternalStateExample /> };