@zentauri-ui/zentauri-components 2.1.8 → 2.2.0

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/README.md +7 -5
  2. package/cli/props.json +381 -0
  3. package/cli/registry.json +9 -1
  4. package/dist/{chunk-4PAHLHYF.mjs → chunk-37KMH77M.mjs} +3 -3
  5. package/dist/{chunk-4PAHLHYF.mjs.map → chunk-37KMH77M.mjs.map} +1 -1
  6. package/dist/chunk-4KOQS4DT.mjs +96 -0
  7. package/dist/chunk-4KOQS4DT.mjs.map +1 -0
  8. package/dist/{chunk-D7ZTSAA6.mjs → chunk-5XSW5JYA.mjs} +4 -4
  9. package/dist/{chunk-D7ZTSAA6.mjs.map → chunk-5XSW5JYA.mjs.map} +1 -1
  10. package/dist/{chunk-UVP3MUBU.mjs → chunk-AMNJ35TT.mjs} +29 -7
  11. package/dist/chunk-AMNJ35TT.mjs.map +1 -0
  12. package/dist/chunk-DXNIAFBG.js +103 -0
  13. package/dist/chunk-DXNIAFBG.js.map +1 -0
  14. package/dist/chunk-EMZC6ICD.mjs +55 -0
  15. package/dist/chunk-EMZC6ICD.mjs.map +1 -0
  16. package/dist/{chunk-BAAXQPZ7.js → chunk-EPJYLBXV.js} +6 -6
  17. package/dist/{chunk-BAAXQPZ7.js.map → chunk-EPJYLBXV.js.map} +1 -1
  18. package/dist/{chunk-DPNTQ4AK.js → chunk-F3G3RL2N.js} +29 -7
  19. package/dist/chunk-F3G3RL2N.js.map +1 -0
  20. package/dist/chunk-GP3FUS2H.mjs +26 -0
  21. package/dist/chunk-GP3FUS2H.mjs.map +1 -0
  22. package/dist/chunk-HNPGWFVY.js +65 -0
  23. package/dist/chunk-HNPGWFVY.js.map +1 -0
  24. package/dist/chunk-PAISX7YL.js +38 -0
  25. package/dist/chunk-PAISX7YL.js.map +1 -0
  26. package/dist/{chunk-OWVQVAOY.mjs → chunk-TACF7MJE.mjs} +3 -3
  27. package/dist/{chunk-OWVQVAOY.mjs.map → chunk-TACF7MJE.mjs.map} +1 -1
  28. package/dist/chunk-VQJXOJ7G.js +19 -0
  29. package/dist/{chunk-6OVDBAMI.js.map → chunk-VQJXOJ7G.js.map} +1 -1
  30. package/dist/{chunk-L5QORCUO.js → chunk-YTCVWOBC.js} +12 -12
  31. package/dist/{chunk-L5QORCUO.js.map → chunk-YTCVWOBC.js.map} +1 -1
  32. package/dist/design-system/code-diff.d.ts +18 -0
  33. package/dist/design-system/code-diff.d.ts.map +1 -0
  34. package/dist/design-system/facade.js +9 -7
  35. package/dist/design-system/facade.js.map +1 -1
  36. package/dist/design-system/facade.mjs +8 -6
  37. package/dist/design-system/facade.mjs.map +1 -1
  38. package/dist/design-system/index.d.ts +2 -0
  39. package/dist/design-system/index.d.ts.map +1 -1
  40. package/dist/design-system/typing-indicator.d.ts +47 -0
  41. package/dist/design-system/typing-indicator.d.ts.map +1 -0
  42. package/dist/ui/buttons/animated.js +11 -9
  43. package/dist/ui/buttons/animated.js.map +1 -1
  44. package/dist/ui/buttons/animated.mjs +9 -7
  45. package/dist/ui/buttons/animated.mjs.map +1 -1
  46. package/dist/ui/buttons.js +12 -10
  47. package/dist/ui/buttons.mjs +10 -8
  48. package/dist/ui/code-diff/code-diff-base.d.ts +6 -0
  49. package/dist/ui/code-diff/code-diff-base.d.ts.map +1 -0
  50. package/dist/ui/code-diff/code-diff.d.ts +6 -0
  51. package/dist/ui/code-diff/code-diff.d.ts.map +1 -0
  52. package/dist/ui/code-diff/index.d.ts +4 -0
  53. package/dist/ui/code-diff/index.d.ts.map +1 -0
  54. package/dist/ui/code-diff/types.d.ts +26 -0
  55. package/dist/ui/code-diff/types.d.ts.map +1 -0
  56. package/dist/ui/code-diff/variants.d.ts +11 -0
  57. package/dist/ui/code-diff/variants.d.ts.map +1 -0
  58. package/dist/ui/code-diff.js +302 -0
  59. package/dist/ui/code-diff.js.map +1 -0
  60. package/dist/ui/code-diff.mjs +297 -0
  61. package/dist/ui/code-diff.mjs.map +1 -0
  62. package/dist/ui/data-table.js +23 -21
  63. package/dist/ui/data-table.js.map +1 -1
  64. package/dist/ui/data-table.mjs +13 -11
  65. package/dist/ui/data-table.mjs.map +1 -1
  66. package/dist/ui/dynamic-stepper.js +21 -19
  67. package/dist/ui/dynamic-stepper.js.map +1 -1
  68. package/dist/ui/dynamic-stepper.mjs +10 -8
  69. package/dist/ui/dynamic-stepper.mjs.map +1 -1
  70. package/dist/ui/pagination.js +13 -11
  71. package/dist/ui/pagination.mjs +10 -8
  72. package/dist/ui/split-button.js +23 -21
  73. package/dist/ui/split-button.js.map +1 -1
  74. package/dist/ui/split-button.mjs +10 -8
  75. package/dist/ui/split-button.mjs.map +1 -1
  76. package/dist/ui/typing-indicator/animated/animations.d.ts +8 -0
  77. package/dist/ui/typing-indicator/animated/animations.d.ts.map +1 -0
  78. package/dist/ui/typing-indicator/animated/index.d.ts +4 -0
  79. package/dist/ui/typing-indicator/animated/index.d.ts.map +1 -0
  80. package/dist/ui/typing-indicator/animated/types.d.ts +9 -0
  81. package/dist/ui/typing-indicator/animated/types.d.ts.map +1 -0
  82. package/dist/ui/typing-indicator/animated/typing-indicator-animated.d.ts +6 -0
  83. package/dist/ui/typing-indicator/animated/typing-indicator-animated.d.ts.map +1 -0
  84. package/dist/ui/typing-indicator/animated.js +119 -0
  85. package/dist/ui/typing-indicator/animated.js.map +1 -0
  86. package/dist/ui/typing-indicator/animated.mjs +116 -0
  87. package/dist/ui/typing-indicator/animated.mjs.map +1 -0
  88. package/dist/ui/typing-indicator/index.d.ts +4 -0
  89. package/dist/ui/typing-indicator/index.d.ts.map +1 -0
  90. package/dist/ui/typing-indicator/types.d.ts +13 -0
  91. package/dist/ui/typing-indicator/types.d.ts.map +1 -0
  92. package/dist/ui/typing-indicator/typing-indicator-base.d.ts +13 -0
  93. package/dist/ui/typing-indicator/typing-indicator-base.d.ts.map +1 -0
  94. package/dist/ui/typing-indicator/typing-indicator.d.ts +2 -0
  95. package/dist/ui/typing-indicator/typing-indicator.d.ts.map +1 -0
  96. package/dist/ui/typing-indicator/variants.d.ts +16 -0
  97. package/dist/ui/typing-indicator/variants.d.ts.map +1 -0
  98. package/dist/ui/typing-indicator.js +32 -0
  99. package/dist/ui/typing-indicator.js.map +1 -0
  100. package/dist/ui/typing-indicator.mjs +7 -0
  101. package/dist/ui/typing-indicator.mjs.map +1 -0
  102. package/package.json +4 -1
  103. package/src/design-system/code-diff.ts +37 -0
  104. package/src/design-system/index.ts +2 -0
  105. package/src/design-system/typing-indicator.ts +74 -0
  106. package/src/ui/code-diff/code-diff-base.tsx +284 -0
  107. package/src/ui/code-diff/code-diff.test.tsx +50 -0
  108. package/src/ui/code-diff/code-diff.tsx +8 -0
  109. package/src/ui/code-diff/index.ts +15 -0
  110. package/src/ui/code-diff/types.ts +31 -0
  111. package/src/ui/code-diff/variants.ts +49 -0
  112. package/src/ui/typing-indicator/animated/animations.ts +58 -0
  113. package/src/ui/typing-indicator/animated/index.ts +8 -0
  114. package/src/ui/typing-indicator/animated/types.ts +11 -0
  115. package/src/ui/typing-indicator/animated/typing-indicator-animated.tsx +79 -0
  116. package/src/ui/typing-indicator/index.ts +15 -0
  117. package/src/ui/typing-indicator/types.ts +20 -0
  118. package/src/ui/typing-indicator/typing-indicator-base.tsx +75 -0
  119. package/src/ui/typing-indicator/typing-indicator.test.tsx +76 -0
  120. package/src/ui/typing-indicator/typing-indicator.tsx +2 -0
  121. package/src/ui/typing-indicator/variants.ts +49 -0
  122. package/dist/chunk-6OVDBAMI.js +0 -19
  123. package/dist/chunk-DPNTQ4AK.js.map +0 -1
  124. package/dist/chunk-UVP3MUBU.mjs.map +0 -1
@@ -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
+ });
@@ -0,0 +1,58 @@
1
+ import type { Transition, Variants } from "framer-motion";
2
+
3
+ export type TypingIndicatorAnimation = "none" | "bounce" | "pulse" | "wave";
4
+
5
+ export type TypingIndicatorAnimationPresets = Record<
6
+ TypingIndicatorAnimation,
7
+ {
8
+ transition: Transition;
9
+ variants: Variants;
10
+ }
11
+ >;
12
+
13
+ export const typingIndicatorAnimationPresets: TypingIndicatorAnimationPresets =
14
+ {
15
+ none: {
16
+ transition: { duration: 0 },
17
+ variants: {
18
+ initial: { y: 0 },
19
+ animate: { y: 0 },
20
+ },
21
+ },
22
+ bounce: {
23
+ transition: {
24
+ type: "spring",
25
+ stiffness: 500,
26
+ damping: 10,
27
+ mass: 0.5,
28
+ },
29
+ variants: {
30
+ initial: { y: 0 },
31
+ animate: { y: -6 },
32
+ },
33
+ },
34
+ pulse: {
35
+ transition: {
36
+ duration: 0.8,
37
+ repeat: Infinity,
38
+ repeatType: "reverse",
39
+ ease: "easeInOut",
40
+ },
41
+ variants: {
42
+ initial: { scale: 1, opacity: 0.4 },
43
+ animate: { scale: 1.3, opacity: 1 },
44
+ },
45
+ },
46
+ wave: {
47
+ transition: {
48
+ type: "spring",
49
+ stiffness: 400,
50
+ damping: 8,
51
+ mass: 0.4,
52
+ },
53
+ variants: {
54
+ initial: { y: 0, scale: 1 },
55
+ animate: { y: -8, scale: 0.9 },
56
+ },
57
+ },
58
+ };
@@ -0,0 +1,8 @@
1
+ "use client";
2
+
3
+ export { TypingIndicatorAnimated } from "./typing-indicator-animated";
4
+ export type {
5
+ TypingIndicatorAnimation,
6
+ TypingIndicatorAnimatedProps,
7
+ } from "./types";
8
+ export { typingIndicatorAnimationPresets } from "./animations";
@@ -0,0 +1,11 @@
1
+ import type { Ref } from "react";
2
+
3
+ import type { TypingIndicatorBaseProps } from "../types";
4
+ import type { TypingIndicatorAnimation } from "./animations";
5
+
6
+ export type { TypingIndicatorAnimation };
7
+
8
+ export type TypingIndicatorAnimatedProps = TypingIndicatorBaseProps & {
9
+ animation?: TypingIndicatorAnimation;
10
+ ref?: Ref<HTMLSpanElement>;
11
+ };
@@ -0,0 +1,79 @@
1
+ "use client";
2
+
3
+ import { motion } from "framer-motion";
4
+ import { useMemo } from "react";
5
+
6
+ import { cn } from "../../../lib/utils";
7
+
8
+ import {
9
+ typingIndicatorDotVariants,
10
+ typingIndicatorDotsVariants,
11
+ typingIndicatorVariants,
12
+ } from "../variants";
13
+
14
+ import { typingIndicatorAnimationPresets } from "./animations";
15
+ import type { TypingIndicatorAnimatedProps } from "./types";
16
+ import { TypingIndicatorLabel } from "../typing-indicator-base";
17
+
18
+ export function TypingIndicatorAnimated({
19
+ appearance,
20
+ size,
21
+ dots = 3,
22
+ label,
23
+ labelPosition = "before",
24
+ animation = "bounce",
25
+ className,
26
+ ref,
27
+ ...rest
28
+ }: TypingIndicatorAnimatedProps) {
29
+ const preset = typingIndicatorAnimationPresets[animation];
30
+
31
+ const dotTransitionOverrides = useMemo(
32
+ () =>
33
+ Array.from({ length: dots }).map((_, i) => ({
34
+ delay: i * 0.12,
35
+ ...(animation !== "none"
36
+ ? { repeat: Infinity, repeatType: "reverse" as const }
37
+ : {}),
38
+ ...preset.transition,
39
+ })),
40
+ [dots, animation, preset.transition],
41
+ );
42
+
43
+ return (
44
+ <span
45
+ ref={ref}
46
+ data-slot="typing-indicator"
47
+ className={cn(typingIndicatorVariants({ size }), className)}
48
+ {...rest}
49
+ >
50
+ {label && labelPosition === "before" && (
51
+ <TypingIndicatorLabel size={size}>{label}</TypingIndicatorLabel>
52
+ )}
53
+ <span
54
+ data-slot="typing-indicator-dots"
55
+ className={typingIndicatorDotsVariants({ size })}
56
+ >
57
+ {Array.from({ length: dots }).map((_, i) => (
58
+ <motion.span
59
+ key={i}
60
+ data-slot="typing-indicator-dot"
61
+ className={cn(
62
+ typingIndicatorDotVariants({ appearance, size }),
63
+ "animate-none",
64
+ )}
65
+ initial="initial"
66
+ animate="animate"
67
+ variants={preset.variants}
68
+ transition={dotTransitionOverrides[i]}
69
+ />
70
+ ))}
71
+ </span>
72
+ {label && labelPosition === "after" && (
73
+ <TypingIndicatorLabel size={size}>{label}</TypingIndicatorLabel>
74
+ )}
75
+ </span>
76
+ );
77
+ }
78
+
79
+ TypingIndicatorAnimated.displayName = "TypingIndicatorAnimated";
@@ -0,0 +1,15 @@
1
+ "use client";
2
+
3
+ export { TypingIndicator } from "./typing-indicator";
4
+ export type {
5
+ TypingIndicatorBaseProps,
6
+ TypingIndicatorDots,
7
+ TypingIndicatorProps,
8
+ TypingIndicatorVariantProps,
9
+ } from "./types";
10
+ export {
11
+ typingIndicatorDotVariants,
12
+ typingIndicatorDotsVariants,
13
+ typingIndicatorLabelVariants,
14
+ typingIndicatorVariants,
15
+ } from "./variants";
@@ -0,0 +1,20 @@
1
+ import type { VariantProps } from "class-variance-authority";
2
+ import type { ComponentPropsWithRef, ReactNode } from "react";
3
+
4
+ import type { typingIndicatorDotVariants } from "./variants";
5
+
6
+ export type TypingIndicatorVariantProps = VariantProps<
7
+ typeof typingIndicatorDotVariants
8
+ >;
9
+
10
+ export type TypingIndicatorDots = 3 | 4 | 5;
11
+
12
+ export type TypingIndicatorBaseProps = TypingIndicatorVariantProps &
13
+ ComponentPropsWithRef<"span"> & {
14
+ dots?: TypingIndicatorDots;
15
+ label?: ReactNode;
16
+ labelPosition?: "before" | "after";
17
+ children?: ReactNode;
18
+ };
19
+
20
+ export type TypingIndicatorProps = TypingIndicatorBaseProps;