@zentauri-ui/zentauri-components 2.1.7 → 2.1.9

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 (110) hide show
  1. package/README.md +8 -5
  2. package/cli/props.json +350 -3
  3. package/cli/registry.json +11 -0
  4. package/dist/{chunk-DBNGLT5U.mjs → chunk-3HBC34NF.mjs} +4 -4
  5. package/dist/{chunk-DBNGLT5U.mjs.map → chunk-3HBC34NF.mjs.map} +1 -1
  6. package/dist/{chunk-FUCW5GPE.mjs → chunk-3RC5IG6O.mjs} +55 -8
  7. package/dist/chunk-3RC5IG6O.mjs.map +1 -0
  8. package/dist/chunk-4SLVTSHM.js +241 -0
  9. package/dist/chunk-4SLVTSHM.js.map +1 -0
  10. package/dist/{chunk-TJ2EWPER.js → chunk-4TPE5DEG.js} +63 -7
  11. package/dist/chunk-4TPE5DEG.js.map +1 -0
  12. package/dist/{chunk-G7FVHZRB.js → chunk-7CZDJTPD.js} +12 -12
  13. package/dist/{chunk-G7FVHZRB.js.map → chunk-7CZDJTPD.js.map} +1 -1
  14. package/dist/{chunk-5ELR6MIN.js → chunk-7DGPRPWM.js} +6 -6
  15. package/dist/{chunk-5ELR6MIN.js.map → chunk-7DGPRPWM.js.map} +1 -1
  16. package/dist/chunk-GP3FUS2H.mjs +26 -0
  17. package/dist/chunk-GP3FUS2H.mjs.map +1 -0
  18. package/dist/chunk-IHDM7AHY.mjs +233 -0
  19. package/dist/chunk-IHDM7AHY.mjs.map +1 -0
  20. package/dist/chunk-MWG7LHAK.js +19 -0
  21. package/dist/{chunk-5FU57ZVQ.js.map → chunk-MWG7LHAK.js.map} +1 -1
  22. package/dist/{chunk-7UXPXCKV.mjs → chunk-OLT7P7JO.mjs} +3 -3
  23. package/dist/{chunk-7UXPXCKV.mjs.map → chunk-OLT7P7JO.mjs.map} +1 -1
  24. package/dist/chunk-PAISX7YL.js +38 -0
  25. package/dist/chunk-PAISX7YL.js.map +1 -0
  26. package/dist/{chunk-KVSRUAXP.mjs → chunk-VN7FE5RR.mjs} +3 -3
  27. package/dist/{chunk-KVSRUAXP.mjs.map → chunk-VN7FE5RR.mjs.map} +1 -1
  28. package/dist/design-system/code-diff.d.ts +18 -0
  29. package/dist/design-system/code-diff.d.ts.map +1 -0
  30. package/dist/design-system/facade.js +8 -7
  31. package/dist/design-system/facade.js.map +1 -1
  32. package/dist/design-system/facade.mjs +7 -6
  33. package/dist/design-system/facade.mjs.map +1 -1
  34. package/dist/design-system/index.d.ts +2 -0
  35. package/dist/design-system/index.d.ts.map +1 -1
  36. package/dist/design-system/split-button.d.ts +25 -0
  37. package/dist/design-system/split-button.d.ts.map +1 -0
  38. package/dist/ui/buttons/animated.js +10 -9
  39. package/dist/ui/buttons/animated.js.map +1 -1
  40. package/dist/ui/buttons/animated.mjs +8 -7
  41. package/dist/ui/buttons/animated.mjs.map +1 -1
  42. package/dist/ui/buttons.js +11 -10
  43. package/dist/ui/buttons.mjs +9 -8
  44. package/dist/ui/code-diff/code-diff-base.d.ts +6 -0
  45. package/dist/ui/code-diff/code-diff-base.d.ts.map +1 -0
  46. package/dist/ui/code-diff/code-diff.d.ts +6 -0
  47. package/dist/ui/code-diff/code-diff.d.ts.map +1 -0
  48. package/dist/ui/code-diff/index.d.ts +4 -0
  49. package/dist/ui/code-diff/index.d.ts.map +1 -0
  50. package/dist/ui/code-diff/types.d.ts +26 -0
  51. package/dist/ui/code-diff/types.d.ts.map +1 -0
  52. package/dist/ui/code-diff/variants.d.ts +11 -0
  53. package/dist/ui/code-diff/variants.d.ts.map +1 -0
  54. package/dist/ui/code-diff.js +302 -0
  55. package/dist/ui/code-diff.js.map +1 -0
  56. package/dist/ui/code-diff.mjs +297 -0
  57. package/dist/ui/code-diff.mjs.map +1 -0
  58. package/dist/ui/data-table.js +22 -21
  59. package/dist/ui/data-table.js.map +1 -1
  60. package/dist/ui/data-table.mjs +12 -11
  61. package/dist/ui/data-table.mjs.map +1 -1
  62. package/dist/ui/dropdown/dropdown.d.ts +1 -1
  63. package/dist/ui/dropdown/dropdown.d.ts.map +1 -1
  64. package/dist/ui/dropdown/types.d.ts +2 -2
  65. package/dist/ui/dropdown/types.d.ts.map +1 -1
  66. package/dist/ui/dropdown.js +31 -231
  67. package/dist/ui/dropdown.js.map +1 -1
  68. package/dist/ui/dropdown.mjs +4 -229
  69. package/dist/ui/dropdown.mjs.map +1 -1
  70. package/dist/ui/dynamic-stepper.js +20 -19
  71. package/dist/ui/dynamic-stepper.js.map +1 -1
  72. package/dist/ui/dynamic-stepper.mjs +9 -8
  73. package/dist/ui/dynamic-stepper.mjs.map +1 -1
  74. package/dist/ui/pagination.js +12 -11
  75. package/dist/ui/pagination.mjs +9 -8
  76. package/dist/ui/split-button/index.d.ts +4 -0
  77. package/dist/ui/split-button/index.d.ts.map +1 -0
  78. package/dist/ui/split-button/split-button-base.d.ts +6 -0
  79. package/dist/ui/split-button/split-button-base.d.ts.map +1 -0
  80. package/dist/ui/split-button/split-button.d.ts +6 -0
  81. package/dist/ui/split-button/split-button.d.ts.map +1 -0
  82. package/dist/ui/split-button/types.d.ts +30 -0
  83. package/dist/ui/split-button/types.d.ts.map +1 -0
  84. package/dist/ui/split-button/variants.d.ts +16 -0
  85. package/dist/ui/split-button/variants.d.ts.map +1 -0
  86. package/dist/ui/split-button.js +288 -0
  87. package/dist/ui/split-button.js.map +1 -0
  88. package/dist/ui/split-button.mjs +279 -0
  89. package/dist/ui/split-button.mjs.map +1 -0
  90. package/package.json +4 -1
  91. package/src/design-system/code-diff.ts +37 -0
  92. package/src/design-system/index.ts +2 -0
  93. package/src/design-system/split-button.ts +38 -0
  94. package/src/ui/code-diff/code-diff-base.tsx +284 -0
  95. package/src/ui/code-diff/code-diff.test.tsx +50 -0
  96. package/src/ui/code-diff/code-diff.tsx +8 -0
  97. package/src/ui/code-diff/index.ts +15 -0
  98. package/src/ui/code-diff/types.ts +31 -0
  99. package/src/ui/code-diff/variants.ts +49 -0
  100. package/src/ui/dropdown/dropdown.tsx +7 -3
  101. package/src/ui/dropdown/types.ts +2 -2
  102. package/src/ui/split-button/index.ts +19 -0
  103. package/src/ui/split-button/split-button-base.tsx +232 -0
  104. package/src/ui/split-button/split-button.test.tsx +208 -0
  105. package/src/ui/split-button/split-button.tsx +9 -0
  106. package/src/ui/split-button/types.ts +46 -0
  107. package/src/ui/split-button/variants.ts +46 -0
  108. package/dist/chunk-5FU57ZVQ.js +0 -19
  109. package/dist/chunk-FUCW5GPE.mjs.map +0 -1
  110. package/dist/chunk-TJ2EWPER.js.map +0 -1
@@ -0,0 +1,37 @@
1
+ export const zuiCodeDiffBase = [
2
+ "relative overflow-auto rounded-lg border border-[color:var(--zui-code-diff-border,var(--zui-border,#0000001a))] dark:border-[color:var(--zui-code-diff-border-dark,var(--zui-border-dark,#ffffff1a))]",
3
+ "bg-[var(--zui-code-diff-bg,var(--zui-surface,oklch(98.4%_0.003_247.858)))] dark:bg-[var(--zui-code-diff-bg-dark,var(--zui-surface-dark,oklch(12.9%_0.042_264.695)))]",
4
+ "text-[color:var(--zui-code-diff-fg,var(--zui-fg,oklch(20.8%_0.042_265.755)))] dark:text-[color:var(--zui-code-diff-fg-dark,var(--zui-fg-dark,oklch(98.4%_0.003_247.858)))]",
5
+ ] as const;
6
+
7
+ export const zuiCodeDiffSizes = {
8
+ sm: "text-xs leading-5",
9
+ md: "text-sm leading-6",
10
+ lg: "text-base leading-7",
11
+ } as const;
12
+
13
+ export const zuiCodeDiffHeaderBase =
14
+ "sticky top-0 z-10 flex items-center justify-between border-b border-[color:var(--zui-code-diff-border,var(--zui-border,#0000001a))] dark:border-[color:var(--zui-code-diff-border-dark,var(--zui-border-dark,#ffffff1a))] bg-[var(--zui-code-diff-header-bg,var(--zui-surface-muted,oklch(92.9%_0.013_255.508)))] dark:bg-[var(--zui-code-diff-header-bg-dark,var(--zui-surface-muted-dark,oklch(27.9%_0.041_260.031)))] px-4 py-2";
15
+
16
+ export const zuiCodeDiffTableBase = "w-full border-collapse table-fixed";
17
+
18
+ export const zuiCodeDiffLineNumberBase =
19
+ "select-none text-right align-top whitespace-nowrap border-r border-[color:var(--zui-code-diff-border,var(--zui-border,#0000001a))] dark:border-[color:var(--zui-code-diff-border-dark,var(--zui-border-dark,#ffffff1a))] px-3 text-[color:var(--zui-code-diff-line-number-fg,var(--zui-fg-muted,oklch(55.2%_0.046_257.417)))] dark:text-[color:var(--zui-code-diff-line-number-fg-dark,var(--zui-fg-muted-dark,oklch(70.8%_0.015_256.243)))]";
20
+
21
+ export const zuiCodeDiffLineContentBase =
22
+ "whitespace-pre-wrap break-all px-4 align-top";
23
+
24
+ export const zuiCodeDiffLineAdded =
25
+ "bg-[var(--zui-code-diff-added-bg,oklch(92.8%_0.109_150.96))] dark:bg-[var(--zui-code-diff-added-bg-dark,oklch(26.8%_0.077_146.44))] text-[color:var(--zui-code-diff-added-fg,oklch(29.1%_0.065_148.99))] dark:text-[color:var(--zui-code-diff-added-fg-dark,oklch(74%_0.131_149.02))]";
26
+
27
+ export const zuiCodeDiffLineRemoved =
28
+ "bg-[var(--zui-code-diff-removed-bg,oklch(93.1%_0.08_22.4))] dark:bg-[var(--zui-code-diff-removed-bg-dark,oklch(26.9%_0.07_22.54))] text-[color:var(--zui-code-diff-removed-fg,oklch(30.7%_0.06_28.07))] dark:text-[color:var(--zui-code-diff-removed-fg-dark,oklch(74.2%_0.127_24.75))]";
29
+
30
+ export const zuiCodeDiffLineUnchanged = "";
31
+
32
+ export const zuiCodeDiffGutterMarker =
33
+ "inline-block w-4 text-center select-none";
34
+
35
+ export const zuiCodeDiffAppearances = {
36
+ default: "",
37
+ } as const;
@@ -7,6 +7,7 @@ export * from "./badge";
7
7
  export * from "./breadcrumb";
8
8
  export * from "./button";
9
9
  export * from "./card";
10
+ export * from "./code-diff";
10
11
  export * from "./checkbox";
11
12
  export * from "./combobox";
12
13
  export * from "./command";
@@ -32,6 +33,7 @@ export * from "./radio-group";
32
33
  export * from "./scroll-area";
33
34
  export * from "./select";
34
35
  export * from "./skeleton";
36
+ export * from "./split-button";
35
37
  export * from "./slider";
36
38
  export * from "./spinner";
37
39
  export * from "./table";
@@ -0,0 +1,38 @@
1
+ export const zuiSplitButtonRoot =
2
+ "inline-flex align-middle data-[full-width=true]:w-full";
3
+
4
+ export const zuiSplitButtonFullWidth = "w-full";
5
+
6
+ export const zuiSplitButtonDropdown =
7
+ "data-[full-width=true]:block data-[full-width=true]:w-full";
8
+
9
+ export const zuiSplitButtonGroup =
10
+ "inline-flex items-stretch data-[full-width=true]:w-full";
11
+
12
+ export const zuiSplitButtonPrimary =
13
+ "rounded-r-none data-[full-width=true]:min-w-0 data-[full-width=true]:flex-1";
14
+
15
+ export const zuiSplitButtonTrigger =
16
+ "rounded-l-none border-l border-[color:var(--zui-split-button-separator,var(--zui-border,#ffffff33))] dark:border-[color:var(--zui-split-button-separator-dark,var(--zui-border-dark,#00000033))] px-2.5";
17
+
18
+ export const zuiSplitButtonTriggerSizes = {
19
+ sm: "min-w-8 px-2",
20
+ md: "min-w-10 px-2.5",
21
+ lg: "min-w-11 px-3",
22
+ xl: "min-w-12 px-3.5",
23
+ "2xl": "min-w-14 px-4",
24
+ "3xl": "min-w-16 px-4",
25
+ "4xl": "min-w-18 px-5",
26
+ "5xl": "min-w-20 px-5",
27
+ "6xl": "min-w-22 px-6",
28
+ "7xl": "min-w-24 px-6",
29
+ "8xl": "min-w-26 px-7",
30
+ "9xl": "min-w-28 px-7",
31
+ "10xl": "min-w-30 px-8",
32
+ icon: "min-w-10 px-0",
33
+ } as const;
34
+
35
+ export const zuiSplitButtonContent =
36
+ "min-w-[var(--zui-split-button-menu-min-width,12rem)]";
37
+
38
+ export const zuiSplitButtonItemDisabled = "pointer-events-none opacity-50";
@@ -0,0 +1,284 @@
1
+ "use client";
2
+
3
+ import { diffLines } from "diff";
4
+ import { useMemo } from "react";
5
+
6
+ import { cn } from "../../lib/utils";
7
+
8
+ import type { CodeDiffBaseProps, DiffLine } from "./types";
9
+ import {
10
+ codeDiffLineContentVariants,
11
+ codeDiffLineNumberVariants,
12
+ codeDiffVariants,
13
+ } from "./variants";
14
+
15
+ function computeDiff(oldCode: string, newCode: string): DiffLine[] {
16
+ const changes = diffLines(oldCode, newCode);
17
+ const lines: DiffLine[] = [];
18
+ let oldLineNum = 1;
19
+ let newLineNum = 1;
20
+
21
+ for (const change of changes) {
22
+ const changeLines = change.value.replace(/\n$/, "").split("\n");
23
+ for (const content of changeLines) {
24
+ if (change.added) {
25
+ lines.push({
26
+ type: "added",
27
+ content,
28
+ oldLineNumber: null,
29
+ newLineNumber: newLineNum++,
30
+ });
31
+ } else if (change.removed) {
32
+ lines.push({
33
+ type: "removed",
34
+ content,
35
+ oldLineNumber: oldLineNum++,
36
+ newLineNumber: null,
37
+ });
38
+ } else {
39
+ lines.push({
40
+ type: "unchanged",
41
+ content,
42
+ oldLineNumber: oldLineNum++,
43
+ newLineNumber: newLineNum++,
44
+ });
45
+ }
46
+ }
47
+ }
48
+ return lines;
49
+ }
50
+
51
+ interface SplitRow {
52
+ oldLine: DiffLine | null;
53
+ newLine: DiffLine | null;
54
+ }
55
+
56
+ function toSplitRows(lines: DiffLine[]): SplitRow[] {
57
+ const rows: SplitRow[] = [];
58
+ let i = 0;
59
+
60
+ while (i < lines.length) {
61
+ const current = lines[i]!;
62
+
63
+ if (current.type === "unchanged") {
64
+ rows.push({ oldLine: current, newLine: current });
65
+ i++;
66
+ } else if (current.type === "removed") {
67
+ const removedBlock: DiffLine[] = [];
68
+ while (i < lines.length && lines[i]!.type === "removed") {
69
+ removedBlock.push(lines[i]!);
70
+ i++;
71
+ }
72
+ const addedBlock: DiffLine[] = [];
73
+ while (i < lines.length && lines[i]!.type === "added") {
74
+ addedBlock.push(lines[i]!);
75
+ i++;
76
+ }
77
+ const maxLen = Math.max(removedBlock.length, addedBlock.length);
78
+ for (let j = 0; j < maxLen; j++) {
79
+ rows.push({
80
+ oldLine: j < removedBlock.length ? removedBlock[j]! : null,
81
+ newLine: j < addedBlock.length ? addedBlock[j]! : null,
82
+ });
83
+ }
84
+ } else if (current.type === "added") {
85
+ const addedBlock: DiffLine[] = [];
86
+ while (i < lines.length && lines[i]!.type === "added") {
87
+ addedBlock.push(lines[i]!);
88
+ i++;
89
+ }
90
+ for (const added of addedBlock) {
91
+ rows.push({ oldLine: null, newLine: added });
92
+ }
93
+ } else {
94
+ i++;
95
+ }
96
+ }
97
+ return rows;
98
+ }
99
+
100
+ function LineNumberCell({
101
+ lineNumber,
102
+ type,
103
+ showLineNumbers,
104
+ }: {
105
+ lineNumber: number | null;
106
+ type: DiffLine["type"];
107
+ showLineNumbers: boolean;
108
+ }) {
109
+ return (
110
+ <td
111
+ className={codeDiffLineNumberVariants({ type })}
112
+ style={{ width: showLineNumbers ? "4.5rem" : "2rem" }}
113
+ >
114
+ {showLineNumbers && lineNumber !== null ? lineNumber : ""}
115
+ </td>
116
+ );
117
+ }
118
+
119
+ function LineContentCell({
120
+ line,
121
+ type,
122
+ showGutterMarkers,
123
+ }: {
124
+ line: string;
125
+ type: DiffLine["type"];
126
+ showGutterMarkers: boolean;
127
+ }) {
128
+ return (
129
+ <td className={codeDiffLineContentVariants({ type })}>
130
+ {showGutterMarkers ? (
131
+ <span className="inline-block w-4 select-none text-center">
132
+ {type === "added" ? "+" : type === "removed" ? "-" : " "}
133
+ </span>
134
+ ) : null}
135
+ <span>{line || " "}</span>
136
+ </td>
137
+ );
138
+ }
139
+
140
+ function UnifiedView({
141
+ lines,
142
+ showLineNumbers,
143
+ showGutterMarkers,
144
+ }: {
145
+ lines: DiffLine[];
146
+ showLineNumbers: boolean;
147
+ showGutterMarkers: boolean;
148
+ }) {
149
+ return (
150
+ <table className="w-full border-collapse table-fixed">
151
+ <tbody>
152
+ {lines.map((line, idx) => (
153
+ <tr key={idx}>
154
+ <LineNumberCell
155
+ lineNumber={line.oldLineNumber}
156
+ type={line.type}
157
+ showLineNumbers={showLineNumbers}
158
+ />
159
+ <LineNumberCell
160
+ lineNumber={line.newLineNumber}
161
+ type={line.type}
162
+ showLineNumbers={showLineNumbers}
163
+ />
164
+ <LineContentCell
165
+ line={line.content}
166
+ type={line.type}
167
+ showGutterMarkers={showGutterMarkers}
168
+ />
169
+ </tr>
170
+ ))}
171
+ </tbody>
172
+ </table>
173
+ );
174
+ }
175
+
176
+ function SplitView({
177
+ lines,
178
+ showLineNumbers,
179
+ showGutterMarkers,
180
+ }: {
181
+ lines: DiffLine[];
182
+ showLineNumbers: boolean;
183
+ showGutterMarkers: boolean;
184
+ }) {
185
+ const rows = toSplitRows(lines);
186
+
187
+ return (
188
+ <table className="w-full border-collapse table-fixed">
189
+ <colgroup>
190
+ <col style={{ width: showLineNumbers ? "4.5rem" : "2rem" }} />
191
+ <col />
192
+ <col style={{ width: showLineNumbers ? "4.5rem" : "2rem" }} />
193
+ <col />
194
+ </colgroup>
195
+ <tbody>
196
+ {rows.map((row, idx) => (
197
+ <tr key={idx}>
198
+ <LineNumberCell
199
+ lineNumber={row.oldLine?.oldLineNumber ?? null}
200
+ type={row.oldLine?.type ?? "unchanged"}
201
+ showLineNumbers={showLineNumbers}
202
+ />
203
+ <LineContentCell
204
+ line={row.oldLine?.content ?? ""}
205
+ type={row.oldLine?.type ?? "unchanged"}
206
+ showGutterMarkers={showGutterMarkers}
207
+ />
208
+ <LineNumberCell
209
+ lineNumber={row.newLine?.newLineNumber ?? null}
210
+ type={row.newLine?.type ?? "unchanged"}
211
+ showLineNumbers={showLineNumbers}
212
+ />
213
+ <LineContentCell
214
+ line={row.newLine?.content ?? ""}
215
+ type={row.newLine?.type ?? "unchanged"}
216
+ showGutterMarkers={showGutterMarkers}
217
+ />
218
+ </tr>
219
+ ))}
220
+ </tbody>
221
+ </table>
222
+ );
223
+ }
224
+
225
+ export function CodeDiffBase({
226
+ className,
227
+ appearance,
228
+ size,
229
+ oldCode,
230
+ newCode,
231
+ viewType = "unified",
232
+ showLineNumbers = true,
233
+ showGutterMarkers = true,
234
+ oldTitle = "Old",
235
+ newTitle = "New",
236
+ ref,
237
+ as: Wrapper = "div",
238
+ ...rest
239
+ }: CodeDiffBaseProps) {
240
+ const lines = useMemo(
241
+ () => computeDiff(oldCode ?? "", newCode ?? ""),
242
+ [oldCode, newCode],
243
+ );
244
+ const hasChanges = lines.some(
245
+ (l) => l.type === "added" || l.type === "removed",
246
+ );
247
+
248
+ return (
249
+ <Wrapper
250
+ ref={ref}
251
+ data-slot="code-diff"
252
+ className={cn(codeDiffVariants({ appearance, size }), className)}
253
+ {...rest}
254
+ >
255
+ {hasChanges ? (
256
+ <div className="sticky top-0 flex items-center justify-between border-b border-[color:var(--zui-code-diff-border,var(--zui-border,#0000001a))] dark:border-[color:var(--zui-code-diff-border-dark,var(--zui-border-dark,#ffffff1a))] bg-[var(--zui-code-diff-header-bg,var(--zui-surface-muted,oklch(92.9%_0.013_255.508)))] dark:bg-[var(--zui-code-diff-header-bg-dark,var(--zui-surface-muted-dark,oklch(27.9%_0.041_260.031)))] px-4 py-2">
257
+ <span className="text-xs font-medium">
258
+ {lines.filter((l) => l.type === "added").length} additions{" "}
259
+ <span className="mx-1">&bull;</span>{" "}
260
+ {lines.filter((l) => l.type === "removed").length} deletions
261
+ </span>
262
+ <span className="text-xs text-[color:var(--zui-code-diff-header-fg,var(--zui-fg-muted,oklch(55.2%_0.046_257.417)))] dark:text-[color:var(--zui-code-diff-header-fg-dark,var(--zui-fg-muted-dark,oklch(70.8%_0.015_256.243)))]">
263
+ {oldTitle} &rarr; {newTitle}
264
+ </span>
265
+ </div>
266
+ ) : null}
267
+ {viewType === "split" ? (
268
+ <SplitView
269
+ lines={lines}
270
+ showLineNumbers={showLineNumbers}
271
+ showGutterMarkers={showGutterMarkers}
272
+ />
273
+ ) : (
274
+ <UnifiedView
275
+ lines={lines}
276
+ showLineNumbers={showLineNumbers}
277
+ showGutterMarkers={showGutterMarkers}
278
+ />
279
+ )}
280
+ </Wrapper>
281
+ );
282
+ }
283
+
284
+ CodeDiffBase.displayName = "CodeDiff";
@@ -0,0 +1,50 @@
1
+ import { createRef } from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { describe, expect, it } from "vitest";
4
+
5
+ import { CodeDiff } from "./code-diff";
6
+
7
+ const oldCode = "line one\nline two\nline three";
8
+ const newCode = "line one\nline two modified\nline three\nline four";
9
+
10
+ describe("CodeDiff", () => {
11
+ it("should expose displayName", () => {
12
+ expect(CodeDiff.displayName).toBe("CodeDiff");
13
+ });
14
+
15
+ it("should stamp data-slot", () => {
16
+ render(<CodeDiff oldCode={oldCode} newCode={newCode} />);
17
+ const root = document.querySelector('[data-slot="code-diff"]');
18
+ expect(root).toBeTruthy();
19
+ expect(root?.getAttribute("data-slot")).toBe("code-diff");
20
+ });
21
+
22
+ it("should render diff lines", () => {
23
+ render(<CodeDiff oldCode={oldCode} newCode={newCode} />);
24
+ expect(screen.getByText("line one")).toBeInTheDocument();
25
+ expect(screen.getByText("line two modified")).toBeInTheDocument();
26
+ expect(screen.getByText("line four")).toBeInTheDocument();
27
+ });
28
+
29
+ it("should show additions and deletions count", () => {
30
+ render(<CodeDiff oldCode={oldCode} newCode={newCode} />);
31
+ expect(screen.getByText(/additions/)).toBeInTheDocument();
32
+ expect(screen.getByText(/deletions/)).toBeInTheDocument();
33
+ });
34
+
35
+ it("should render unchanged code when no changes", () => {
36
+ render(<CodeDiff oldCode="same" newCode="same" />);
37
+ expect(screen.getByText("same")).toBeInTheDocument();
38
+ });
39
+
40
+ it("should forward ref", () => {
41
+ const ref = createRef<HTMLDivElement>();
42
+ render(<CodeDiff ref={ref} oldCode={oldCode} newCode={newCode} />);
43
+ expect(ref.current?.getAttribute("data-slot")).toBe("code-diff");
44
+ });
45
+
46
+ it("should render in split view", () => {
47
+ render(<CodeDiff oldCode={oldCode} newCode={newCode} viewType="split" />);
48
+ expect(screen.getAllByText("line one").length).toBeGreaterThanOrEqual(1);
49
+ });
50
+ });
@@ -0,0 +1,8 @@
1
+ import { CodeDiffBase } from "./code-diff-base";
2
+ import type { CodeDiffProps } from "./types";
3
+
4
+ export function CodeDiff(props: CodeDiffProps) {
5
+ return <CodeDiffBase {...props} />;
6
+ }
7
+
8
+ CodeDiff.displayName = "CodeDiff";
@@ -0,0 +1,15 @@
1
+ "use client";
2
+
3
+ export { CodeDiff } from "./code-diff";
4
+ export type {
5
+ CodeDiffBaseProps,
6
+ CodeDiffProps,
7
+ CodeDiffVariantProps,
8
+ CodeDiffViewType,
9
+ DiffLine,
10
+ } from "./types";
11
+ export {
12
+ codeDiffLineContentVariants,
13
+ codeDiffLineNumberVariants,
14
+ codeDiffVariants,
15
+ } from "./variants";
@@ -0,0 +1,31 @@
1
+ import type { VariantProps } from "class-variance-authority";
2
+ import type { ComponentPropsWithRef, ElementType } from "react";
3
+
4
+ import type { codeDiffVariants } from "./variants";
5
+
6
+ export type CodeDiffVariantProps = VariantProps<typeof codeDiffVariants>;
7
+
8
+ export type CodeDiffViewType = "split" | "unified";
9
+
10
+ export interface CodeDiffBaseProps extends ComponentPropsWithRef<"div"> {
11
+ oldCode: string;
12
+ newCode: string;
13
+ language?: string;
14
+ viewType?: CodeDiffViewType;
15
+ showLineNumbers?: boolean;
16
+ showGutterMarkers?: boolean;
17
+ oldTitle?: string;
18
+ newTitle?: string;
19
+ appearance?: CodeDiffVariantProps["appearance"];
20
+ size?: CodeDiffVariantProps["size"];
21
+ as?: ElementType;
22
+ }
23
+
24
+ export type CodeDiffProps = Omit<CodeDiffBaseProps, "as">;
25
+
26
+ export interface DiffLine {
27
+ type: "added" | "removed" | "unchanged";
28
+ content: string;
29
+ oldLineNumber: number | null;
30
+ newLineNumber: number | null;
31
+ }
@@ -0,0 +1,49 @@
1
+ import { cva } from "class-variance-authority";
2
+
3
+ import {
4
+ zuiCodeDiffAppearances,
5
+ zuiCodeDiffBase,
6
+ zuiCodeDiffLineAdded,
7
+ zuiCodeDiffLineContentBase,
8
+ zuiCodeDiffLineNumberBase,
9
+ zuiCodeDiffLineRemoved,
10
+ zuiCodeDiffLineUnchanged,
11
+ zuiCodeDiffSizes,
12
+ } from "../../design-system/code-diff";
13
+
14
+ export const codeDiffVariants = cva(zuiCodeDiffBase, {
15
+ variants: {
16
+ appearance: zuiCodeDiffAppearances,
17
+ size: zuiCodeDiffSizes,
18
+ },
19
+ defaultVariants: {
20
+ appearance: "default",
21
+ size: "md",
22
+ },
23
+ });
24
+
25
+ export const codeDiffLineNumberVariants = cva(zuiCodeDiffLineNumberBase, {
26
+ variants: {
27
+ type: {
28
+ added: zuiCodeDiffLineAdded,
29
+ removed: zuiCodeDiffLineRemoved,
30
+ unchanged: zuiCodeDiffLineUnchanged,
31
+ },
32
+ },
33
+ defaultVariants: {
34
+ type: "unchanged",
35
+ },
36
+ });
37
+
38
+ export const codeDiffLineContentVariants = cva(zuiCodeDiffLineContentBase, {
39
+ variants: {
40
+ type: {
41
+ added: zuiCodeDiffLineAdded,
42
+ removed: zuiCodeDiffLineRemoved,
43
+ unchanged: zuiCodeDiffLineUnchanged,
44
+ },
45
+ },
46
+ defaultVariants: {
47
+ type: "unchanged",
48
+ },
49
+ });
@@ -37,10 +37,12 @@ const useDropdown = () => {
37
37
  ========================= */
38
38
  export const Dropdown = ({
39
39
  children,
40
+ className,
40
41
  defaultOpen = false,
41
42
  open: controlledOpen,
42
43
  onOpenChange,
43
44
  multiSelect = false,
45
+ ...props
44
46
  }: DropdownProps) => {
45
47
  const menuId = `${useId()}-menu`;
46
48
  const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
@@ -82,7 +84,9 @@ export const Dropdown = ({
82
84
  menuId,
83
85
  }}
84
86
  >
85
- <div className="relative inline-block">{children}</div>
87
+ <div className={cn("relative inline-block", className)} {...props}>
88
+ {children}
89
+ </div>
86
90
  </DropdownContext.Provider>
87
91
  );
88
92
  };
@@ -180,10 +184,10 @@ export const DropdownItem = ({
180
184
  ...props
181
185
  }: DropdownItemProps) => {
182
186
  const { toggleSelect, selectedValues } = useDropdown();
183
- const isSelected = selectedValues.includes(value);
187
+ const isSelected = value !== undefined && selectedValues.includes(value);
184
188
 
185
189
  const handleClick = () => {
186
- toggleSelect(value);
190
+ if (value !== undefined) toggleSelect(value);
187
191
  onSelect?.();
188
192
  };
189
193
 
@@ -14,7 +14,7 @@ export type DropdownContextType = {
14
14
 
15
15
  type Variant = keyof typeof zuiDropdownTriggerVariants;
16
16
 
17
- export type DropdownProps = {
17
+ export type DropdownProps = HTMLAttributes<HTMLDivElement> & {
18
18
  children: ReactNode;
19
19
  defaultOpen?: boolean;
20
20
  open?: boolean;
@@ -37,7 +37,7 @@ export type DropdownContentProps = HTMLAttributes<HTMLDivElement> & {
37
37
 
38
38
  export type DropdownItemProps = HTMLAttributes<HTMLDivElement> & {
39
39
  children: ReactNode;
40
- value: string;
40
+ value?: string;
41
41
  onSelect?: () => void;
42
42
  leftIcon?: ReactNode;
43
43
  rightIcon?: ReactNode;
@@ -0,0 +1,19 @@
1
+ "use client";
2
+
3
+ export { SplitButton } from "./split-button";
4
+ export type {
5
+ SplitButtonAppearance,
6
+ SplitButtonItem,
7
+ SplitButtonProps,
8
+ SplitButtonSize,
9
+ SplitButtonVariant,
10
+ } from "./types";
11
+ export {
12
+ splitButtonContentVariants,
13
+ splitButtonDropdownVariants,
14
+ splitButtonGroupVariants,
15
+ splitButtonItemDisabledVariants,
16
+ splitButtonPrimaryVariants,
17
+ splitButtonRootVariants,
18
+ splitButtonTriggerVariants,
19
+ } from "./variants";