@zentauri-ui/zentauri-components 1.7.8 → 1.8.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.
- package/README.md +7 -4
- package/cli/registry.json +2 -0
- package/dist/design-system/animated-number.d.ts +32 -0
- package/dist/design-system/animated-number.d.ts.map +1 -0
- package/dist/design-system/index.d.ts +2 -0
- package/dist/design-system/index.d.ts.map +1 -1
- package/dist/design-system/marquee.d.ts +40 -0
- package/dist/design-system/marquee.d.ts.map +1 -0
- package/dist/ui/animated-number/animated-number.d.ts +4 -0
- package/dist/ui/animated-number/animated-number.d.ts.map +1 -0
- package/dist/ui/animated-number/animations.d.ts +59 -0
- package/dist/ui/animated-number/animations.d.ts.map +1 -0
- package/dist/ui/animated-number/index.d.ts +4 -0
- package/dist/ui/animated-number/index.d.ts.map +1 -0
- package/dist/ui/animated-number/types.d.ts +31 -0
- package/dist/ui/animated-number/types.d.ts.map +1 -0
- package/dist/ui/animated-number/variants.d.ts +5 -0
- package/dist/ui/animated-number/variants.d.ts.map +1 -0
- package/dist/ui/animated-number.js +181 -0
- package/dist/ui/animated-number.js.map +1 -0
- package/dist/ui/animated-number.mjs +177 -0
- package/dist/ui/animated-number.mjs.map +1 -0
- package/dist/ui/marquee/index.d.ts +4 -0
- package/dist/ui/marquee/index.d.ts.map +1 -0
- package/dist/ui/marquee/marquee.d.ts +6 -0
- package/dist/ui/marquee/marquee.d.ts.map +1 -0
- package/dist/ui/marquee/types.d.ts +15 -0
- package/dist/ui/marquee/types.d.ts.map +1 -0
- package/dist/ui/marquee/variants.d.ts +7 -0
- package/dist/ui/marquee/variants.d.ts.map +1 -0
- package/dist/ui/marquee.js +171 -0
- package/dist/ui/marquee.js.map +1 -0
- package/dist/ui/marquee.mjs +168 -0
- package/dist/ui/marquee.mjs.map +1 -0
- package/package.json +1 -1
- package/src/design-system/animated-number.ts +53 -0
- package/src/design-system/index.ts +2 -0
- package/src/design-system/marquee.ts +62 -0
- package/src/ui/animated-number/animated-number.test.tsx +64 -0
- package/src/ui/animated-number/animated-number.tsx +120 -0
- package/src/ui/animated-number/animations.ts +22 -0
- package/src/ui/animated-number/index.ts +4 -0
- package/src/ui/animated-number/types.ts +39 -0
- package/src/ui/animated-number/variants.ts +14 -0
- package/src/ui/marquee/index.ts +9 -0
- package/src/ui/marquee/marquee.test.tsx +138 -0
- package/src/ui/marquee/marquee.tsx +114 -0
- package/src/ui/marquee/types.ts +19 -0
- package/src/ui/marquee/variants.ts +24 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { Marquee } from "./marquee";
|
|
5
|
+
|
|
6
|
+
describe("Marquee", () => {
|
|
7
|
+
it("exposes a display name", () => {
|
|
8
|
+
expect(Marquee.displayName).toBe("Marquee");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("renders children into duplicated marquee groups", () => {
|
|
12
|
+
render(
|
|
13
|
+
<Marquee>
|
|
14
|
+
<span>Acme</span>
|
|
15
|
+
</Marquee>,
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
expect(document.querySelector('[data-slot="marquee"]')).toBeTruthy();
|
|
19
|
+
expect(document.querySelector('[data-slot="marquee-track"]')).toBeTruthy();
|
|
20
|
+
expect(screen.getAllByText("Acme")).toHaveLength(2);
|
|
21
|
+
|
|
22
|
+
const groups = document.querySelectorAll(
|
|
23
|
+
'[data-slot="marquee-item-group"]',
|
|
24
|
+
);
|
|
25
|
+
expect(groups[1]).toHaveAttribute("aria-hidden", "true");
|
|
26
|
+
expect(groups[1]).toHaveAttribute("inert");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("applies horizontal metadata by default", () => {
|
|
30
|
+
render(
|
|
31
|
+
<Marquee data-testid="marquee">
|
|
32
|
+
<span>Default direction</span>
|
|
33
|
+
</Marquee>,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const marquee = screen.getByTestId("marquee");
|
|
37
|
+
expect(marquee).toHaveAttribute("data-orientation", "horizontal");
|
|
38
|
+
expect(marquee).toHaveAttribute("data-direction", "left");
|
|
39
|
+
expect(marquee.className).toContain("w-full");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("applies vertical metadata and down direction", () => {
|
|
43
|
+
render(
|
|
44
|
+
<Marquee orientation="vertical" direction="down" data-testid="marquee">
|
|
45
|
+
<span>Vertical direction</span>
|
|
46
|
+
</Marquee>,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const marquee = screen.getByTestId("marquee");
|
|
50
|
+
const track = document.querySelector('[data-slot="marquee-track"]');
|
|
51
|
+
expect(marquee).toHaveAttribute("data-orientation", "vertical");
|
|
52
|
+
expect(marquee).toHaveAttribute("data-direction", "down");
|
|
53
|
+
expect(track?.className).toContain("[animation-direction:reverse]");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("infers vertical orientation from up or down directions", () => {
|
|
57
|
+
render(
|
|
58
|
+
<Marquee direction="up" data-testid="marquee">
|
|
59
|
+
<span>Inferred vertical</span>
|
|
60
|
+
</Marquee>,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
expect(screen.getByTestId("marquee")).toHaveAttribute(
|
|
64
|
+
"data-orientation",
|
|
65
|
+
"vertical",
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("maps speed to track animation styles and gap to a CSS custom property", () => {
|
|
70
|
+
render(
|
|
71
|
+
<Marquee speed={42} gap={24} data-testid="marquee">
|
|
72
|
+
<span>Timing</span>
|
|
73
|
+
</Marquee>,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const marquee = screen.getByTestId("marquee");
|
|
77
|
+
expect(marquee).toHaveStyle({
|
|
78
|
+
"--zui-marquee-gap": "24px",
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(document.querySelector('[data-slot="marquee-track"]')).toHaveStyle({
|
|
82
|
+
animationDuration: "42s",
|
|
83
|
+
animationName: "zui-marquee-x",
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("accepts string gap values", () => {
|
|
88
|
+
render(
|
|
89
|
+
<Marquee gap="2rem" data-testid="marquee">
|
|
90
|
+
<span>String gap</span>
|
|
91
|
+
</Marquee>,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(screen.getByTestId("marquee")).toHaveStyle({
|
|
95
|
+
"--zui-marquee-gap": "2rem",
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("adds pause-on-hover animation control when requested", () => {
|
|
100
|
+
render(
|
|
101
|
+
<Marquee pauseOnHover>
|
|
102
|
+
<span>Pause</span>
|
|
103
|
+
</Marquee>,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
expect(
|
|
107
|
+
document.querySelector('[data-slot="marquee-track"]')?.className,
|
|
108
|
+
).toContain("group-hover/marquee:[animation-play-state:paused]");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("applies appearance, size, fade, and custom classes", () => {
|
|
112
|
+
render(
|
|
113
|
+
<Marquee
|
|
114
|
+
appearance="sky"
|
|
115
|
+
className="rounded-2xl"
|
|
116
|
+
fade={false}
|
|
117
|
+
itemClassName="min-w-max"
|
|
118
|
+
size="lg"
|
|
119
|
+
trackClassName="tracking-wide"
|
|
120
|
+
data-testid="marquee"
|
|
121
|
+
>
|
|
122
|
+
<span>Variants</span>
|
|
123
|
+
</Marquee>,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const marquee = screen.getByTestId("marquee");
|
|
127
|
+
expect(marquee.className).toContain("rounded-2xl");
|
|
128
|
+
expect(marquee.className).toContain("p-4");
|
|
129
|
+
expect(marquee.className).toContain("--zui-marquee-sky-border");
|
|
130
|
+
expect(marquee.className).not.toContain("mask-image");
|
|
131
|
+
expect(
|
|
132
|
+
document.querySelector('[data-slot="marquee-track"]')?.className,
|
|
133
|
+
).toContain("tracking-wide");
|
|
134
|
+
expect(
|
|
135
|
+
document.querySelector('[data-slot="marquee-item-group"]')?.className,
|
|
136
|
+
).toContain("min-w-max");
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { CSSProperties } from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils";
|
|
6
|
+
|
|
7
|
+
import type { MarqueeProps } from "./types";
|
|
8
|
+
import { marqueeVariants } from "./variants";
|
|
9
|
+
|
|
10
|
+
const marqueeKeyframes = `@keyframes zui-marquee-x{from{transform:translate3d(0,0,0)}to{transform:translate3d(calc(-50% - var(--zui-marquee-gap)/2),0,0)}}@keyframes zui-marquee-y{from{transform:translate3d(0,0,0)}to{transform:translate3d(0,calc(-50% - var(--zui-marquee-gap)/2),0)}}`;
|
|
11
|
+
|
|
12
|
+
function toCssLength(value: number | string | undefined) {
|
|
13
|
+
if (value === undefined) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
return typeof value === "number" ? `${value}px` : value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function Marquee(props: MarqueeProps) {
|
|
20
|
+
const {
|
|
21
|
+
appearance,
|
|
22
|
+
children,
|
|
23
|
+
className,
|
|
24
|
+
direction,
|
|
25
|
+
fade,
|
|
26
|
+
gap,
|
|
27
|
+
itemClassName,
|
|
28
|
+
orientation,
|
|
29
|
+
pauseOnHover = false,
|
|
30
|
+
ref,
|
|
31
|
+
size,
|
|
32
|
+
speed = 30,
|
|
33
|
+
style,
|
|
34
|
+
trackClassName,
|
|
35
|
+
...rest
|
|
36
|
+
} = props;
|
|
37
|
+
|
|
38
|
+
const resolvedOrientation =
|
|
39
|
+
orientation ??
|
|
40
|
+
(direction === "up" || direction === "down" ? "vertical" : "horizontal");
|
|
41
|
+
const resolvedDirection =
|
|
42
|
+
direction ?? (resolvedOrientation === "vertical" ? "up" : "left");
|
|
43
|
+
const isReverse =
|
|
44
|
+
resolvedDirection === "right" || resolvedDirection === "down";
|
|
45
|
+
const animationName =
|
|
46
|
+
resolvedOrientation === "vertical" ? "zui-marquee-y" : "zui-marquee-x";
|
|
47
|
+
const marqueeStyle = {
|
|
48
|
+
...(gap !== undefined ? { "--zui-marquee-gap": toCssLength(gap) } : null),
|
|
49
|
+
...style,
|
|
50
|
+
} as CSSProperties;
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
ref={ref}
|
|
55
|
+
data-direction={resolvedDirection}
|
|
56
|
+
data-orientation={resolvedOrientation}
|
|
57
|
+
data-slot="marquee"
|
|
58
|
+
className={cn(
|
|
59
|
+
marqueeVariants({
|
|
60
|
+
appearance,
|
|
61
|
+
fade,
|
|
62
|
+
orientation: resolvedOrientation,
|
|
63
|
+
size,
|
|
64
|
+
}),
|
|
65
|
+
className,
|
|
66
|
+
)}
|
|
67
|
+
style={marqueeStyle}
|
|
68
|
+
{...rest}
|
|
69
|
+
>
|
|
70
|
+
<style>{marqueeKeyframes}</style>
|
|
71
|
+
<div
|
|
72
|
+
data-slot="marquee-track"
|
|
73
|
+
className={cn(
|
|
74
|
+
"flex shrink-0 gap-[var(--zui-marquee-gap)] will-change-transform [animation-iteration-count:infinite] [animation-timing-function:linear] motion-reduce:[animation-play-state:paused]",
|
|
75
|
+
resolvedOrientation === "vertical" ? "flex-col" : "w-max flex-row",
|
|
76
|
+
pauseOnHover && "group-hover/marquee:[animation-play-state:paused]",
|
|
77
|
+
isReverse && "[animation-direction:reverse]",
|
|
78
|
+
trackClassName,
|
|
79
|
+
)}
|
|
80
|
+
style={
|
|
81
|
+
{
|
|
82
|
+
animationDuration: `${speed}s`,
|
|
83
|
+
animationName,
|
|
84
|
+
} as CSSProperties
|
|
85
|
+
}
|
|
86
|
+
>
|
|
87
|
+
<div
|
|
88
|
+
data-slot="marquee-item-group"
|
|
89
|
+
className={cn(
|
|
90
|
+
"flex shrink-0 items-center justify-around gap-[var(--zui-marquee-gap)]",
|
|
91
|
+
resolvedOrientation === "vertical" ? "flex-col" : "flex-row",
|
|
92
|
+
itemClassName,
|
|
93
|
+
)}
|
|
94
|
+
>
|
|
95
|
+
{children}
|
|
96
|
+
</div>
|
|
97
|
+
<div
|
|
98
|
+
aria-hidden="true"
|
|
99
|
+
inert
|
|
100
|
+
data-slot="marquee-item-group"
|
|
101
|
+
className={cn(
|
|
102
|
+
"flex shrink-0 items-center justify-around gap-[var(--zui-marquee-gap)]",
|
|
103
|
+
resolvedOrientation === "vertical" ? "flex-col" : "flex-row",
|
|
104
|
+
itemClassName,
|
|
105
|
+
)}
|
|
106
|
+
>
|
|
107
|
+
{children}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
Marquee.displayName = "Marquee";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { VariantProps } from "class-variance-authority";
|
|
2
|
+
import type { ComponentPropsWithRef, ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
import type { marqueeVariants } from "./variants";
|
|
5
|
+
|
|
6
|
+
export type MarqueeVariantProps = VariantProps<typeof marqueeVariants>;
|
|
7
|
+
|
|
8
|
+
export type MarqueeDirection = "left" | "right" | "up" | "down";
|
|
9
|
+
|
|
10
|
+
export type MarqueeProps = MarqueeVariantProps &
|
|
11
|
+
Omit<ComponentPropsWithRef<"div">, "children"> & {
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
direction?: MarqueeDirection;
|
|
14
|
+
gap?: number | string;
|
|
15
|
+
pauseOnHover?: boolean;
|
|
16
|
+
speed?: number;
|
|
17
|
+
trackClassName?: string;
|
|
18
|
+
itemClassName?: string;
|
|
19
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cva } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
zuiMarqueeAppearances,
|
|
5
|
+
zuiMarqueeBase,
|
|
6
|
+
zuiMarqueeFade,
|
|
7
|
+
zuiMarqueeOrientations,
|
|
8
|
+
zuiMarqueeSizes,
|
|
9
|
+
} from "../../design-system/marquee";
|
|
10
|
+
|
|
11
|
+
export const marqueeVariants = cva(zuiMarqueeBase, {
|
|
12
|
+
variants: {
|
|
13
|
+
appearance: zuiMarqueeAppearances,
|
|
14
|
+
fade: zuiMarqueeFade,
|
|
15
|
+
orientation: zuiMarqueeOrientations,
|
|
16
|
+
size: zuiMarqueeSizes,
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: {
|
|
19
|
+
appearance: "default",
|
|
20
|
+
fade: true,
|
|
21
|
+
orientation: "horizontal",
|
|
22
|
+
size: "md",
|
|
23
|
+
},
|
|
24
|
+
});
|