@zentauri-ui/zentauri-components 2.1.9 → 2.2.1

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 (144) hide show
  1. package/README.md +7 -5
  2. package/cli/props.json +620 -0
  3. package/cli/registry.json +13 -1
  4. package/dist/{chunk-OLT7P7JO.mjs → chunk-45ZHGDT2.mjs} +3 -3
  5. package/dist/{chunk-OLT7P7JO.mjs.map → chunk-45ZHGDT2.mjs.map} +1 -1
  6. package/dist/chunk-46HJFCP7.js +87 -0
  7. package/dist/chunk-46HJFCP7.js.map +1 -0
  8. package/dist/chunk-4KOQS4DT.mjs +96 -0
  9. package/dist/chunk-4KOQS4DT.mjs.map +1 -0
  10. package/dist/{chunk-3RC5IG6O.mjs → chunk-5HLEHSPM.mjs} +24 -5
  11. package/dist/chunk-5HLEHSPM.mjs.map +1 -0
  12. package/dist/{chunk-7CZDJTPD.js → chunk-DUH2YLH2.js} +12 -12
  13. package/dist/{chunk-7CZDJTPD.js.map → chunk-DUH2YLH2.js.map} +1 -1
  14. package/dist/chunk-DXNIAFBG.js +103 -0
  15. package/dist/chunk-DXNIAFBG.js.map +1 -0
  16. package/dist/chunk-EMZC6ICD.mjs +55 -0
  17. package/dist/chunk-EMZC6ICD.mjs.map +1 -0
  18. package/dist/chunk-HNPGWFVY.js +65 -0
  19. package/dist/chunk-HNPGWFVY.js.map +1 -0
  20. package/dist/chunk-HR6MGXNI.mjs +212 -0
  21. package/dist/chunk-HR6MGXNI.mjs.map +1 -0
  22. package/dist/chunk-LOEKM3FL.mjs +78 -0
  23. package/dist/chunk-LOEKM3FL.mjs.map +1 -0
  24. package/dist/{chunk-7DGPRPWM.js → chunk-NW5BSLR2.js} +6 -6
  25. package/dist/{chunk-7DGPRPWM.js.map → chunk-NW5BSLR2.js.map} +1 -1
  26. package/dist/{chunk-4TPE5DEG.js → chunk-UJZ7JQBQ.js} +24 -5
  27. package/dist/chunk-UJZ7JQBQ.js.map +1 -0
  28. package/dist/chunk-WMM42MAC.js +222 -0
  29. package/dist/chunk-WMM42MAC.js.map +1 -0
  30. package/dist/chunk-YBKNXDZU.js +19 -0
  31. package/dist/{chunk-MWG7LHAK.js.map → chunk-YBKNXDZU.js.map} +1 -1
  32. package/dist/{chunk-3HBC34NF.mjs → chunk-YSQW56JX.mjs} +4 -4
  33. package/dist/{chunk-3HBC34NF.mjs.map → chunk-YSQW56JX.mjs.map} +1 -1
  34. package/dist/{chunk-VN7FE5RR.mjs → chunk-Z4Y5IPR3.mjs} +3 -3
  35. package/dist/{chunk-VN7FE5RR.mjs.map → chunk-Z4Y5IPR3.mjs.map} +1 -1
  36. package/dist/design-system/facade.js +7 -5
  37. package/dist/design-system/facade.js.map +1 -1
  38. package/dist/design-system/facade.mjs +6 -4
  39. package/dist/design-system/facade.mjs.map +1 -1
  40. package/dist/design-system/index.d.ts +2 -0
  41. package/dist/design-system/index.d.ts.map +1 -1
  42. package/dist/design-system/password-strength-meter.d.ts +74 -0
  43. package/dist/design-system/password-strength-meter.d.ts.map +1 -0
  44. package/dist/design-system/typing-indicator.d.ts +47 -0
  45. package/dist/design-system/typing-indicator.d.ts.map +1 -0
  46. package/dist/ui/buttons/animated.js +9 -7
  47. package/dist/ui/buttons/animated.js.map +1 -1
  48. package/dist/ui/buttons/animated.mjs +7 -5
  49. package/dist/ui/buttons/animated.mjs.map +1 -1
  50. package/dist/ui/buttons.js +10 -8
  51. package/dist/ui/buttons.mjs +8 -6
  52. package/dist/ui/data-table.js +20 -18
  53. package/dist/ui/data-table.js.map +1 -1
  54. package/dist/ui/data-table.mjs +10 -8
  55. package/dist/ui/data-table.mjs.map +1 -1
  56. package/dist/ui/dynamic-stepper.js +19 -17
  57. package/dist/ui/dynamic-stepper.js.map +1 -1
  58. package/dist/ui/dynamic-stepper.mjs +8 -6
  59. package/dist/ui/dynamic-stepper.mjs.map +1 -1
  60. package/dist/ui/pagination.js +11 -9
  61. package/dist/ui/pagination.mjs +8 -6
  62. package/dist/ui/password-strength-meter/animated/animations.d.ts +3 -0
  63. package/dist/ui/password-strength-meter/animated/animations.d.ts.map +1 -0
  64. package/dist/ui/password-strength-meter/animated/index.d.ts +4 -0
  65. package/dist/ui/password-strength-meter/animated/index.d.ts.map +1 -0
  66. package/dist/ui/password-strength-meter/animated/password-strength-meter-animated.d.ts +14 -0
  67. package/dist/ui/password-strength-meter/animated/password-strength-meter-animated.d.ts.map +1 -0
  68. package/dist/ui/password-strength-meter/animated/types.d.ts +21 -0
  69. package/dist/ui/password-strength-meter/animated/types.d.ts.map +1 -0
  70. package/dist/ui/password-strength-meter/animated.js +169 -0
  71. package/dist/ui/password-strength-meter/animated.js.map +1 -0
  72. package/dist/ui/password-strength-meter/animated.mjs +165 -0
  73. package/dist/ui/password-strength-meter/animated.mjs.map +1 -0
  74. package/dist/ui/password-strength-meter/index.d.ts +5 -0
  75. package/dist/ui/password-strength-meter/index.d.ts.map +1 -0
  76. package/dist/ui/password-strength-meter/password-strength-meter-base.d.ts +17 -0
  77. package/dist/ui/password-strength-meter/password-strength-meter-base.d.ts.map +1 -0
  78. package/dist/ui/password-strength-meter/password-strength-meter.d.ts +6 -0
  79. package/dist/ui/password-strength-meter/password-strength-meter.d.ts.map +1 -0
  80. package/dist/ui/password-strength-meter/types.d.ts +33 -0
  81. package/dist/ui/password-strength-meter/types.d.ts.map +1 -0
  82. package/dist/ui/password-strength-meter/variants.d.ts +13 -0
  83. package/dist/ui/password-strength-meter/variants.d.ts.map +1 -0
  84. package/dist/ui/password-strength-meter.js +37 -0
  85. package/dist/ui/password-strength-meter.js.map +1 -0
  86. package/dist/ui/password-strength-meter.mjs +16 -0
  87. package/dist/ui/password-strength-meter.mjs.map +1 -0
  88. package/dist/ui/split-button.js +21 -19
  89. package/dist/ui/split-button.js.map +1 -1
  90. package/dist/ui/split-button.mjs +8 -6
  91. package/dist/ui/split-button.mjs.map +1 -1
  92. package/dist/ui/typing-indicator/animated/animations.d.ts +8 -0
  93. package/dist/ui/typing-indicator/animated/animations.d.ts.map +1 -0
  94. package/dist/ui/typing-indicator/animated/index.d.ts +4 -0
  95. package/dist/ui/typing-indicator/animated/index.d.ts.map +1 -0
  96. package/dist/ui/typing-indicator/animated/types.d.ts +9 -0
  97. package/dist/ui/typing-indicator/animated/types.d.ts.map +1 -0
  98. package/dist/ui/typing-indicator/animated/typing-indicator-animated.d.ts +6 -0
  99. package/dist/ui/typing-indicator/animated/typing-indicator-animated.d.ts.map +1 -0
  100. package/dist/ui/typing-indicator/animated.js +119 -0
  101. package/dist/ui/typing-indicator/animated.js.map +1 -0
  102. package/dist/ui/typing-indicator/animated.mjs +116 -0
  103. package/dist/ui/typing-indicator/animated.mjs.map +1 -0
  104. package/dist/ui/typing-indicator/index.d.ts +4 -0
  105. package/dist/ui/typing-indicator/index.d.ts.map +1 -0
  106. package/dist/ui/typing-indicator/types.d.ts +13 -0
  107. package/dist/ui/typing-indicator/types.d.ts.map +1 -0
  108. package/dist/ui/typing-indicator/typing-indicator-base.d.ts +13 -0
  109. package/dist/ui/typing-indicator/typing-indicator-base.d.ts.map +1 -0
  110. package/dist/ui/typing-indicator/typing-indicator.d.ts +2 -0
  111. package/dist/ui/typing-indicator/typing-indicator.d.ts.map +1 -0
  112. package/dist/ui/typing-indicator/variants.d.ts +16 -0
  113. package/dist/ui/typing-indicator/variants.d.ts.map +1 -0
  114. package/dist/ui/typing-indicator.js +32 -0
  115. package/dist/ui/typing-indicator.js.map +1 -0
  116. package/dist/ui/typing-indicator.mjs +7 -0
  117. package/dist/ui/typing-indicator.mjs.map +1 -0
  118. package/package.json +1 -1
  119. package/src/design-system/index.ts +2 -0
  120. package/src/design-system/password-strength-meter.ts +115 -0
  121. package/src/design-system/typing-indicator.ts +74 -0
  122. package/src/ui/password-strength-meter/animated/animations.ts +10 -0
  123. package/src/ui/password-strength-meter/animated/index.ts +14 -0
  124. package/src/ui/password-strength-meter/animated/password-strength-meter-animated.tsx +186 -0
  125. package/src/ui/password-strength-meter/animated/types.ts +35 -0
  126. package/src/ui/password-strength-meter/index.ts +18 -0
  127. package/src/ui/password-strength-meter/password-strength-meter-base.tsx +202 -0
  128. package/src/ui/password-strength-meter/password-strength-meter.test.tsx +103 -0
  129. package/src/ui/password-strength-meter/password-strength-meter.tsx +8 -0
  130. package/src/ui/password-strength-meter/types.ts +40 -0
  131. package/src/ui/password-strength-meter/variants.ts +49 -0
  132. package/src/ui/typing-indicator/animated/animations.ts +58 -0
  133. package/src/ui/typing-indicator/animated/index.ts +8 -0
  134. package/src/ui/typing-indicator/animated/types.ts +11 -0
  135. package/src/ui/typing-indicator/animated/typing-indicator-animated.tsx +79 -0
  136. package/src/ui/typing-indicator/index.ts +15 -0
  137. package/src/ui/typing-indicator/types.ts +20 -0
  138. package/src/ui/typing-indicator/typing-indicator-base.tsx +75 -0
  139. package/src/ui/typing-indicator/typing-indicator.test.tsx +76 -0
  140. package/src/ui/typing-indicator/typing-indicator.tsx +2 -0
  141. package/src/ui/typing-indicator/variants.ts +49 -0
  142. package/dist/chunk-3RC5IG6O.mjs.map +0 -1
  143. package/dist/chunk-4TPE5DEG.js.map +0 -1
  144. package/dist/chunk-MWG7LHAK.js +0 -19
@@ -0,0 +1,202 @@
1
+ "use client";
2
+
3
+ import { createContext, useContext, useMemo, useId } from "react";
4
+
5
+ import { cn, clamp } from "../../lib/utils";
6
+
7
+ import type {
8
+ PasswordStrengthMeterProps,
9
+ PasswordStrengthMeterSectionProps,
10
+ PasswordStrengthMeterCtx,
11
+ } from "./types";
12
+ import {
13
+ passwordStrengthMeterBarVariants,
14
+ passwordStrengthMeterTrackVariants,
15
+ passwordStrengthMeterVariants,
16
+ } from "./variants";
17
+
18
+ export const PasswordStrengthMeterContext =
19
+ createContext<PasswordStrengthMeterCtx | null>(null);
20
+
21
+ export function usePasswordStrengthMeterContext(
22
+ component: string,
23
+ ): PasswordStrengthMeterCtx {
24
+ const ctx = useContext(PasswordStrengthMeterContext);
25
+ if (!ctx) {
26
+ throw new Error(`${component} must be used within <PasswordStrengthMeter>`);
27
+ }
28
+ return ctx;
29
+ }
30
+
31
+ export function getStrengthLabel(percent: number): string {
32
+ if (percent <= 20) return "Weak";
33
+ if (percent <= 40) return "Fair";
34
+ if (percent <= 60) return "Good";
35
+ if (percent <= 80) return "Strong";
36
+ return "Very strong";
37
+ }
38
+
39
+ export function getStrengthColor(percent: number): string {
40
+ if (percent <= 20)
41
+ return "text-[var(--zui-status-error,#dc2626)] dark:text-[var(--zui-status-error-dark,#ef4444)]";
42
+ if (percent <= 40)
43
+ return "text-[var(--zui-color-orange,#ea580c)] dark:text-[var(--zui-color-orange-dark,#fb923c)]";
44
+ if (percent <= 60)
45
+ return "text-[var(--zui-color-yellow,#ca8a04)] dark:text-[var(--zui-color-yellow-dark,#facc15)]";
46
+ if (percent <= 80)
47
+ return "text-[var(--zui-color-emerald,#16a34a)] dark:text-[var(--zui-color-emerald-dark,#22c55e)]";
48
+ return "text-[var(--zui-color-indigo,#4338ca)] dark:text-[var(--zui-color-indigo-dark,#818cf8)]";
49
+ }
50
+
51
+ export function PasswordStrengthMeterBase(props: PasswordStrengthMeterProps) {
52
+ const {
53
+ className,
54
+ appearance = "default",
55
+ size = "md",
56
+ shape = "rounded",
57
+ animated = false,
58
+ segmented = false,
59
+ value = 0,
60
+ min = 0,
61
+ max = 100,
62
+ label,
63
+ scoreLabel,
64
+ showScoreLabel = true,
65
+ children,
66
+ ref,
67
+ as: Wrapper = "div",
68
+ ...rest
69
+ } = props;
70
+ const clamped = clamp(value, min, max);
71
+ const percent = max === min ? 0 : ((clamped - min) / (max - min)) * 100;
72
+ const labelSlotId = `${useId()}-password-strength-meter-label`;
73
+ const hasInlineLabelProp = Boolean(label?.trim().length);
74
+
75
+ const labelingProps = useMemo(() => {
76
+ if (hasInlineLabelProp) {
77
+ return { "aria-label": label?.trim() ?? "Password strength" };
78
+ }
79
+ return { "aria-label": "Password strength" };
80
+ }, [hasInlineLabelProp, label]);
81
+
82
+ const ctx = useMemo(
83
+ () => ({
84
+ value: clamped,
85
+ min,
86
+ max,
87
+ size: size ?? "md",
88
+ shape: shape ?? "rounded",
89
+ animated: Boolean(animated),
90
+ segmented: Boolean(segmented),
91
+ appearance: appearance ?? "default",
92
+ labelSlotId,
93
+ }),
94
+ [
95
+ animated,
96
+ appearance,
97
+ clamped,
98
+ labelSlotId,
99
+ max,
100
+ min,
101
+ shape,
102
+ size,
103
+ segmented,
104
+ ],
105
+ );
106
+
107
+ return (
108
+ <PasswordStrengthMeterContext.Provider value={ctx}>
109
+ <Wrapper
110
+ ref={ref}
111
+ data-slot="password-strength-meter"
112
+ role="meter"
113
+ aria-valuemin={min}
114
+ aria-valuemax={max}
115
+ aria-valuenow={clamped}
116
+ aria-valuetext={getStrengthLabel(percent)}
117
+ {...labelingProps}
118
+ className={cn(
119
+ passwordStrengthMeterVariants({ appearance, size, shape }),
120
+ className,
121
+ )}
122
+ {...rest}
123
+ >
124
+ {children ?? (
125
+ <>
126
+ {(label || showScoreLabel) && (
127
+ <div className="flex items-center justify-between mb-1.5">
128
+ {label && (
129
+ <span
130
+ id={labelSlotId}
131
+ data-slot="password-strength-meter-label"
132
+ className="text-xs font-medium"
133
+ >
134
+ {label}
135
+ </span>
136
+ )}
137
+ {showScoreLabel && (
138
+ <span
139
+ data-slot="password-strength-meter-score-label"
140
+ className={cn(
141
+ "text-xs font-semibold",
142
+ getStrengthColor(percent),
143
+ )}
144
+ >
145
+ {scoreLabel ?? getStrengthLabel(percent)}
146
+ </span>
147
+ )}
148
+ </div>
149
+ )}
150
+ <PasswordStrengthMeterBar
151
+ style={{ transform: `scaleX(${percent / 100})` }}
152
+ />
153
+ </>
154
+ )}
155
+ </Wrapper>
156
+ </PasswordStrengthMeterContext.Provider>
157
+ );
158
+ }
159
+
160
+ PasswordStrengthMeterBase.displayName = "PasswordStrengthMeter";
161
+
162
+ export function PasswordStrengthMeterBar({
163
+ className,
164
+ style,
165
+ ref,
166
+ as: Wrapper = "div",
167
+ ...rest
168
+ }: PasswordStrengthMeterSectionProps & {
169
+ style?: React.CSSProperties;
170
+ ref?: React.Ref<HTMLDivElement>;
171
+ }) {
172
+ const { size, shape, animated, segmented } = usePasswordStrengthMeterContext(
173
+ "PasswordStrengthMeterBar",
174
+ );
175
+
176
+ return (
177
+ <Wrapper
178
+ data-slot="password-strength-meter-track"
179
+ className={cn(
180
+ passwordStrengthMeterTrackVariants({ size, shape }),
181
+ "text-current",
182
+ )}
183
+ >
184
+ <div
185
+ ref={ref}
186
+ data-slot="password-strength-meter-bar"
187
+ className={cn(
188
+ passwordStrengthMeterBarVariants({ segmented }),
189
+ animated ? "animate-pulse" : undefined,
190
+ className,
191
+ )}
192
+ style={{
193
+ transformOrigin: "left center",
194
+ ...style,
195
+ }}
196
+ {...rest}
197
+ />
198
+ </Wrapper>
199
+ );
200
+ }
201
+
202
+ PasswordStrengthMeterBar.displayName = "PasswordStrengthMeterBar";
@@ -0,0 +1,103 @@
1
+ import { createRef } from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { describe, expect, it } from "vitest";
4
+
5
+ import { PasswordStrengthMeter } from "./password-strength-meter";
6
+ import { PasswordStrengthMeterBar } from "./password-strength-meter-base";
7
+
8
+ describe("PasswordStrengthMeter", () => {
9
+ it("should expose displayName on compound parts", () => {
10
+ expect(PasswordStrengthMeter.displayName).toBe("PasswordStrengthMeter");
11
+ expect(PasswordStrengthMeterBar.displayName).toBe(
12
+ "PasswordStrengthMeterBar",
13
+ );
14
+ });
15
+
16
+ it("should stamp data-slot on the root and default bar track", () => {
17
+ render(<PasswordStrengthMeter value={40} />);
18
+ expect(
19
+ document.querySelector('[data-slot="password-strength-meter"]'),
20
+ ).toBeTruthy();
21
+ expect(
22
+ document.querySelector('[data-slot="password-strength-meter-track"]'),
23
+ ).toBeTruthy();
24
+ expect(
25
+ document.querySelector('[data-slot="password-strength-meter-bar"]'),
26
+ ).toBeTruthy();
27
+ });
28
+
29
+ it("should expose meter semantics with clamped value", () => {
30
+ render(<PasswordStrengthMeter value={120} min={0} max={100} />);
31
+ const meter = screen.getByRole("meter");
32
+ expect(meter.getAttribute("aria-valuemin")).toBe("0");
33
+ expect(meter.getAttribute("aria-valuemax")).toBe("100");
34
+ expect(meter.getAttribute("aria-valuenow")).toBe("100");
35
+ });
36
+
37
+ it("should clamp below the minimum", () => {
38
+ render(<PasswordStrengthMeter value={-5} min={0} max={100} />);
39
+ expect(screen.getByRole("meter").getAttribute("aria-valuenow")).toBe("0");
40
+ });
41
+
42
+ it("should render strength label based on value", () => {
43
+ render(<PasswordStrengthMeter value={10} showScoreLabel />);
44
+ expect(
45
+ document.querySelector(
46
+ '[data-slot="password-strength-meter-score-label"]',
47
+ ),
48
+ ).toHaveTextContent("Weak");
49
+ });
50
+
51
+ it("should render 'Very strong' for high values", () => {
52
+ render(<PasswordStrengthMeter value={95} showScoreLabel />);
53
+ expect(
54
+ document.querySelector(
55
+ '[data-slot="password-strength-meter-score-label"]',
56
+ ),
57
+ ).toHaveTextContent("Very strong");
58
+ });
59
+
60
+ it("should not show score label when showScoreLabel is false", () => {
61
+ render(<PasswordStrengthMeter value={50} showScoreLabel={false} />);
62
+ expect(
63
+ document.querySelector(
64
+ '[data-slot="password-strength-meter-score-label"]',
65
+ ),
66
+ ).toBeNull();
67
+ });
68
+
69
+ it("should render custom label", () => {
70
+ render(<PasswordStrengthMeter value={50} label="Password" />);
71
+ expect(
72
+ document.querySelector('[data-slot="password-strength-meter-label"]'),
73
+ ).toHaveTextContent("Password");
74
+ });
75
+
76
+ it("should render custom scoreLabel", () => {
77
+ render(<PasswordStrengthMeter value={50} scoreLabel="Medium" />);
78
+ expect(
79
+ document.querySelector(
80
+ '[data-slot="password-strength-meter-score-label"]',
81
+ ),
82
+ ).toHaveTextContent("Medium");
83
+ });
84
+
85
+ it("should apply appearance classes from the variant recipe", () => {
86
+ render(<PasswordStrengthMeter value={50} appearance="emerald" />);
87
+ const root = document.querySelector(
88
+ '[data-slot="password-strength-meter"]',
89
+ ) as HTMLElement;
90
+ expect(root.className).toMatch(/--psm-fill/);
91
+ });
92
+
93
+ describe("ref forwarding", () => {
94
+ it("should forward ref to the root", () => {
95
+ const ref = createRef<HTMLDivElement>();
96
+ render(<PasswordStrengthMeter ref={ref} value={5} />);
97
+ expect(ref.current).toBeInstanceOf(HTMLDivElement);
98
+ expect(ref.current?.getAttribute("data-slot")).toBe(
99
+ "password-strength-meter",
100
+ );
101
+ });
102
+ });
103
+ });
@@ -0,0 +1,8 @@
1
+ import { PasswordStrengthMeterBase } from "./password-strength-meter-base";
2
+ import type { PasswordStrengthMeterProps } from "./types";
3
+
4
+ export const PasswordStrengthMeter = (props: PasswordStrengthMeterProps) => {
5
+ return <PasswordStrengthMeterBase {...props} />;
6
+ };
7
+
8
+ PasswordStrengthMeter.displayName = "PasswordStrengthMeter";
@@ -0,0 +1,40 @@
1
+ import type { VariantProps } from "class-variance-authority";
2
+ import type { ComponentPropsWithRef, ElementType, ReactNode } from "react";
3
+
4
+ import type { passwordStrengthMeterVariants } from "./variants";
5
+
6
+ export type PasswordStrengthMeterVariantProps = VariantProps<
7
+ typeof passwordStrengthMeterVariants
8
+ >;
9
+
10
+ export type PasswordStrengthMeterProps = PasswordStrengthMeterVariantProps &
11
+ (Omit<ComponentPropsWithRef<"div">, "children"> & {
12
+ value: number;
13
+ min?: number;
14
+ max?: number;
15
+ label?: string;
16
+ scoreLabel?: string;
17
+ showScoreLabel?: boolean;
18
+ animated?: boolean;
19
+ segmented?: boolean;
20
+ children?: ReactNode;
21
+ as?: ElementType;
22
+ });
23
+
24
+ export type PasswordStrengthMeterSectionProps = {
25
+ className?: string;
26
+ children?: ReactNode;
27
+ as?: ElementType;
28
+ };
29
+
30
+ export type PasswordStrengthMeterCtx = {
31
+ value: number;
32
+ min: number;
33
+ max: number;
34
+ size: NonNullable<PasswordStrengthMeterProps["size"]>;
35
+ shape: NonNullable<PasswordStrengthMeterProps["shape"]>;
36
+ animated: boolean;
37
+ segmented: boolean;
38
+ appearance: NonNullable<PasswordStrengthMeterProps["appearance"]>;
39
+ labelSlotId: string;
40
+ };
@@ -0,0 +1,49 @@
1
+ import { cva } from "class-variance-authority";
2
+
3
+ import {
4
+ zuiPasswordStrengthMeterAppearances,
5
+ zuiPasswordStrengthMeterBarBase,
6
+ zuiPasswordStrengthMeterBarSegmented,
7
+ zuiPasswordStrengthMeterBase,
8
+ zuiPasswordStrengthMeterShapes,
9
+ zuiPasswordStrengthMeterSizes,
10
+ zuiPasswordStrengthMeterTrackBase,
11
+ zuiPasswordStrengthMeterTrackSizes,
12
+ } from "../../design-system/password-strength-meter";
13
+
14
+ export const passwordStrengthMeterVariants = cva(zuiPasswordStrengthMeterBase, {
15
+ variants: {
16
+ appearance: zuiPasswordStrengthMeterAppearances,
17
+ size: zuiPasswordStrengthMeterSizes,
18
+ shape: zuiPasswordStrengthMeterShapes,
19
+ },
20
+ defaultVariants: {
21
+ appearance: "default",
22
+ size: "md",
23
+ shape: "rounded",
24
+ },
25
+ });
26
+
27
+ export const passwordStrengthMeterTrackVariants = cva(
28
+ zuiPasswordStrengthMeterTrackBase,
29
+ {
30
+ variants: {
31
+ size: zuiPasswordStrengthMeterTrackSizes,
32
+ shape: zuiPasswordStrengthMeterShapes,
33
+ },
34
+ defaultVariants: {
35
+ size: "md",
36
+ shape: "rounded",
37
+ },
38
+ },
39
+ );
40
+
41
+ export const passwordStrengthMeterBarVariants = cva(
42
+ zuiPasswordStrengthMeterBarBase,
43
+ {
44
+ variants: {
45
+ segmented: zuiPasswordStrengthMeterBarSegmented,
46
+ },
47
+ defaultVariants: { segmented: false },
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;
@@ -0,0 +1,75 @@
1
+ "use client";
2
+
3
+ import { cn } from "../../lib/utils";
4
+
5
+ import type { TypingIndicatorBaseProps } from "./types";
6
+ import {
7
+ typingIndicatorDotDelays,
8
+ typingIndicatorDotVariants,
9
+ typingIndicatorDotsVariants,
10
+ typingIndicatorLabelVariants,
11
+ typingIndicatorVariants,
12
+ } from "./variants";
13
+
14
+ export function TypingIndicatorBase({
15
+ appearance,
16
+ size,
17
+ dots = 3,
18
+ label,
19
+ labelPosition = "before",
20
+ className,
21
+ ref,
22
+ ...rest
23
+ }: TypingIndicatorBaseProps) {
24
+ return (
25
+ <span
26
+ ref={ref}
27
+ data-slot="typing-indicator"
28
+ className={cn(typingIndicatorVariants({ size }), className)}
29
+ {...rest}
30
+ >
31
+ {label && labelPosition === "before" && (
32
+ <TypingIndicatorLabel size={size}>{label}</TypingIndicatorLabel>
33
+ )}
34
+ <span
35
+ data-slot="typing-indicator-dots"
36
+ className={typingIndicatorDotsVariants({ size })}
37
+ >
38
+ {Array.from({ length: dots }).map((_, i) => (
39
+ <span
40
+ key={i}
41
+ data-slot="typing-indicator-dot"
42
+ className={cn(
43
+ typingIndicatorDotVariants({ appearance, size }),
44
+ typingIndicatorDotDelays[i % typingIndicatorDotDelays.length],
45
+ )}
46
+ />
47
+ ))}
48
+ </span>
49
+ {label && labelPosition === "after" && (
50
+ <TypingIndicatorLabel size={size}>{label}</TypingIndicatorLabel>
51
+ )}
52
+ </span>
53
+ );
54
+ }
55
+
56
+ TypingIndicatorBase.displayName = "TypingIndicator";
57
+
58
+ export function TypingIndicatorLabel({
59
+ size,
60
+ children,
61
+ }: {
62
+ size?: TypingIndicatorBaseProps["size"];
63
+ children: React.ReactNode;
64
+ }) {
65
+ return (
66
+ <span
67
+ data-slot="typing-indicator-label"
68
+ className={typingIndicatorLabelVariants({ size })}
69
+ >
70
+ {children}
71
+ </span>
72
+ );
73
+ }
74
+
75
+ TypingIndicatorLabel.displayName = "TypingIndicatorLabel";