@zentauri-ui/zentauri-components 1.8.1 → 1.8.2
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 +21 -10
- package/cli/registry.json +10 -0
- package/dist/charts/area.js +9 -10
- package/dist/charts/area.js.map +1 -1
- package/dist/charts/area.mjs +2 -3
- package/dist/charts/area.mjs.map +1 -1
- package/dist/charts/bar.js +10 -95
- package/dist/charts/bar.js.map +1 -1
- package/dist/charts/bar.mjs +2 -95
- package/dist/charts/bar.mjs.map +1 -1
- package/dist/charts/bubble.js +8 -9
- package/dist/charts/bubble.js.map +1 -1
- package/dist/charts/bubble.mjs +2 -3
- package/dist/charts/bubble.mjs.map +1 -1
- package/dist/charts/funnel/Funnel.d.ts +6 -0
- package/dist/charts/funnel/Funnel.d.ts.map +1 -0
- package/dist/charts/funnel/index.d.ts +4 -0
- package/dist/charts/funnel/index.d.ts.map +1 -0
- package/dist/charts/funnel.js +102 -0
- package/dist/charts/funnel.js.map +1 -0
- package/dist/charts/funnel.mjs +89 -0
- package/dist/charts/funnel.mjs.map +1 -0
- package/dist/charts/line.js +8 -9
- package/dist/charts/line.js.map +1 -1
- package/dist/charts/line.mjs +2 -3
- package/dist/charts/line.mjs.map +1 -1
- package/dist/charts/pie/Pie.d.ts +1 -1
- package/dist/charts/pie/Pie.d.ts.map +1 -1
- package/dist/charts/pie.js +19 -6
- package/dist/charts/pie.js.map +1 -1
- package/dist/charts/pie.mjs +17 -4
- package/dist/charts/pie.mjs.map +1 -1
- package/dist/charts/radar/Radar.d.ts +6 -0
- package/dist/charts/radar/Radar.d.ts.map +1 -0
- package/dist/charts/radar/index.d.ts +4 -0
- package/dist/charts/radar/index.d.ts.map +1 -0
- package/dist/charts/radar.js +94 -0
- package/dist/charts/radar.js.map +1 -0
- package/dist/charts/radar.mjs +81 -0
- package/dist/charts/radar.mjs.map +1 -0
- package/dist/charts/scatter/Scatter.d.ts +6 -0
- package/dist/charts/scatter/Scatter.d.ts.map +1 -0
- package/dist/charts/scatter/index.d.ts +4 -0
- package/dist/charts/scatter/index.d.ts.map +1 -0
- package/dist/charts/scatter.js +116 -0
- package/dist/charts/scatter.js.map +1 -0
- package/dist/charts/scatter.mjs +103 -0
- package/dist/charts/scatter.mjs.map +1 -0
- package/dist/charts/shared/chart-frame.d.ts +2 -1
- package/dist/charts/shared/chart-frame.d.ts.map +1 -1
- package/dist/charts/shared/types.d.ts +22 -2
- package/dist/charts/shared/types.d.ts.map +1 -1
- package/dist/charts/stacked-bar/StackedBar.d.ts +6 -0
- package/dist/charts/stacked-bar/StackedBar.d.ts.map +1 -0
- package/dist/charts/stacked-bar/index.d.ts +4 -0
- package/dist/charts/stacked-bar/index.d.ts.map +1 -0
- package/dist/charts/stacked-bar.js +29 -0
- package/dist/charts/stacked-bar.js.map +1 -0
- package/dist/charts/stacked-bar.mjs +15 -0
- package/dist/charts/stacked-bar.mjs.map +1 -0
- package/dist/chunk-F3V4POW3.mjs +8 -0
- package/dist/chunk-F3V4POW3.mjs.map +1 -0
- package/dist/{chunk-G2WARVAM.mjs → chunk-HZIRD3SR.mjs} +35 -15
- package/dist/chunk-HZIRD3SR.mjs.map +1 -0
- package/dist/{chunk-G66SXATZ.js → chunk-IL4LH2XX.js} +50 -4
- package/dist/chunk-IL4LH2XX.js.map +1 -0
- package/dist/chunk-LREMK2XR.js +97 -0
- package/dist/chunk-LREMK2XR.js.map +1 -0
- package/dist/chunk-O2KM3ETC.mjs +95 -0
- package/dist/chunk-O2KM3ETC.mjs.map +1 -0
- package/dist/{chunk-ZIFMIS7D.mjs → chunk-OL3BJSRC.mjs} +51 -5
- package/dist/chunk-OL3BJSRC.mjs.map +1 -0
- package/dist/{chunk-QNUDODDX.js → chunk-PWPMKXEG.js} +36 -14
- package/dist/chunk-PWPMKXEG.js.map +1 -0
- package/dist/chunk-XRM7GOIE.js +10 -0
- package/dist/chunk-XRM7GOIE.js.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useIsomorphicLayoutEffect.js +6 -4
- package/dist/hooks/useIsomorphicLayoutEffect.js.map +1 -1
- package/dist/hooks/useIsomorphicLayoutEffect.mjs +1 -6
- package/dist/hooks/useIsomorphicLayoutEffect.mjs.map +1 -1
- package/dist/hooks/useTableFilter/index.d.ts +3 -0
- package/dist/hooks/useTableFilter/index.d.ts.map +1 -0
- package/dist/hooks/useTableFilter/types.d.ts +20 -0
- package/dist/hooks/useTableFilter/types.d.ts.map +1 -0
- package/dist/hooks/useTableFilter/useTableFilter.d.ts +3 -0
- package/dist/hooks/useTableFilter/useTableFilter.d.ts.map +1 -0
- package/dist/hooks/useTableFilter.js +124 -0
- package/dist/hooks/useTableFilter.js.map +1 -0
- package/dist/hooks/useTableFilter.mjs +122 -0
- package/dist/hooks/useTableFilter.mjs.map +1 -0
- package/dist/hooks/useTableSort/index.d.ts +3 -0
- package/dist/hooks/useTableSort/index.d.ts.map +1 -0
- package/dist/hooks/useTableSort/types.d.ts +15 -0
- package/dist/hooks/useTableSort/types.d.ts.map +1 -0
- package/dist/hooks/useTableSort/useTableSort.d.ts +3 -0
- package/dist/hooks/useTableSort/useTableSort.d.ts.map +1 -0
- package/dist/hooks/useTableSort.js +99 -0
- package/dist/hooks/useTableSort.js.map +1 -0
- package/dist/hooks/useTableSort.mjs +97 -0
- package/dist/hooks/useTableSort.mjs.map +1 -0
- package/dist/ui/marquee/marquee.d.ts.map +1 -1
- package/dist/ui/marquee.js +82 -21
- package/dist/ui/marquee.js.map +1 -1
- package/dist/ui/marquee.mjs +83 -22
- package/dist/ui/marquee.mjs.map +1 -1
- package/dist/ui/table/animated.js +8 -8
- package/dist/ui/table/animated.mjs +2 -2
- package/dist/ui/table/index.d.ts +1 -1
- package/dist/ui/table/index.d.ts.map +1 -1
- package/dist/ui/table/table-base.d.ts +2 -2
- package/dist/ui/table/table-base.d.ts.map +1 -1
- package/dist/ui/table/types.d.ts +9 -1
- package/dist/ui/table/types.d.ts.map +1 -1
- package/dist/ui/table.js +14 -14
- package/dist/ui/table.mjs +1 -1
- package/package.json +1 -1
- package/src/charts/charts.test.tsx +80 -0
- package/src/charts/funnel/Funnel.tsx +105 -0
- package/src/charts/funnel/index.ts +14 -0
- package/src/charts/pie/Pie.tsx +28 -1
- package/src/charts/radar/Radar.tsx +84 -0
- package/src/charts/radar/index.ts +16 -0
- package/src/charts/scatter/Scatter.tsx +104 -0
- package/src/charts/scatter/index.ts +16 -0
- package/src/charts/shared/chart-frame.tsx +4 -2
- package/src/charts/shared/types.ts +42 -2
- package/src/charts/stacked-bar/StackedBar.tsx +12 -0
- package/src/charts/stacked-bar/index.ts +16 -0
- package/src/hooks/index.ts +12 -0
- package/src/hooks/useTableFilter/index.ts +7 -0
- package/src/hooks/useTableFilter/types.ts +28 -0
- package/src/hooks/useTableFilter/useTableFilter.test.ts +141 -0
- package/src/hooks/useTableFilter/useTableFilter.ts +153 -0
- package/src/hooks/useTableSort/index.ts +5 -0
- package/src/hooks/useTableSort/types.ts +23 -0
- package/src/hooks/useTableSort/useTableSort.test.ts +150 -0
- package/src/hooks/useTableSort/useTableSort.ts +121 -0
- package/src/ui/divider/divider.test.tsx +55 -0
- package/src/ui/empty-state/empty-state.test.tsx +88 -0
- package/src/ui/marquee/marquee.test.tsx +45 -4
- package/src/ui/marquee/marquee.tsx +100 -18
- package/src/ui/skeleton/skeleton.test.tsx +85 -0
- package/src/ui/table/index.ts +3 -0
- package/src/ui/table/table-base.tsx +69 -4
- package/src/ui/table/table.test.tsx +207 -0
- package/src/ui/table/types.ts +13 -1
- package/dist/chunk-G2WARVAM.mjs.map +0 -1
- package/dist/chunk-G66SXATZ.js.map +0 -1
- package/dist/chunk-OULU7OC4.mjs +0 -21
- package/dist/chunk-OULU7OC4.mjs.map +0 -1
- package/dist/chunk-QNUDODDX.js.map +0 -1
- package/dist/chunk-Z6S36PDD.js +0 -24
- package/dist/chunk-Z6S36PDD.js.map +0 -1
- package/dist/chunk-ZIFMIS7D.mjs.map +0 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { Children, useCallback, useMemo, useRef, useState } from "react";
|
|
4
|
+
import type { CSSProperties, Ref } from "react";
|
|
4
5
|
|
|
6
|
+
import { useIsomorphicLayoutEffect } from "../../hooks/useIsomorphicLayoutEffect";
|
|
5
7
|
import { cn } from "../../lib/utils";
|
|
6
8
|
|
|
7
9
|
import type { MarqueeProps } from "./types";
|
|
@@ -16,6 +18,17 @@ function toCssLength(value: number | string | undefined) {
|
|
|
16
18
|
return typeof value === "number" ? `${value}px` : value;
|
|
17
19
|
}
|
|
18
20
|
|
|
21
|
+
function assignRef<TElement>(
|
|
22
|
+
ref: Ref<TElement> | undefined,
|
|
23
|
+
value: TElement | null,
|
|
24
|
+
) {
|
|
25
|
+
if (typeof ref === "function") {
|
|
26
|
+
ref(value);
|
|
27
|
+
} else if (ref) {
|
|
28
|
+
(ref as { current: TElement | null }).current = value;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
19
32
|
export function Marquee(props: MarqueeProps) {
|
|
20
33
|
const {
|
|
21
34
|
appearance,
|
|
@@ -44,14 +57,81 @@ export function Marquee(props: MarqueeProps) {
|
|
|
44
57
|
resolvedDirection === "right" || resolvedDirection === "down";
|
|
45
58
|
const animationName =
|
|
46
59
|
resolvedOrientation === "vertical" ? "zui-marquee-y" : "zui-marquee-x";
|
|
60
|
+
const rootRef = useRef<HTMLDivElement | null>(null);
|
|
61
|
+
const measureRef = useRef<HTMLDivElement | null>(null);
|
|
62
|
+
const [copyCount, setCopyCount] = useState(1);
|
|
63
|
+
const childArray = useMemo(() => Children.toArray(children), [children]);
|
|
64
|
+
const setRootRef = useCallback(
|
|
65
|
+
(node: HTMLDivElement | null) => {
|
|
66
|
+
rootRef.current = node;
|
|
67
|
+
assignRef(ref, node);
|
|
68
|
+
},
|
|
69
|
+
[ref],
|
|
70
|
+
);
|
|
47
71
|
const marqueeStyle = {
|
|
48
72
|
...(gap !== undefined ? { "--zui-marquee-gap": toCssLength(gap) } : null),
|
|
49
73
|
...style,
|
|
50
74
|
} as CSSProperties;
|
|
75
|
+
const groupClassName = cn(
|
|
76
|
+
"flex shrink-0 items-center justify-around gap-(--zui-marquee-gap)",
|
|
77
|
+
resolvedOrientation === "vertical" ? "flex-col" : "flex-row",
|
|
78
|
+
itemClassName,
|
|
79
|
+
);
|
|
80
|
+
const fillerChildren = Array.from(
|
|
81
|
+
{ length: Math.max(0, copyCount - 1) },
|
|
82
|
+
(_, index) => (
|
|
83
|
+
<div key={index} aria-hidden="true" inert className="contents">
|
|
84
|
+
{childArray}
|
|
85
|
+
</div>
|
|
86
|
+
),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
useIsomorphicLayoutEffect(() => {
|
|
90
|
+
const updateCopyCount = () => {
|
|
91
|
+
const root = rootRef.current;
|
|
92
|
+
const measure = measureRef.current;
|
|
93
|
+
|
|
94
|
+
if (!root || !measure) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const rootSize =
|
|
99
|
+
resolvedOrientation === "vertical"
|
|
100
|
+
? root.offsetHeight
|
|
101
|
+
: root.offsetWidth;
|
|
102
|
+
const contentSize =
|
|
103
|
+
resolvedOrientation === "vertical"
|
|
104
|
+
? measure.scrollHeight
|
|
105
|
+
: measure.scrollWidth;
|
|
106
|
+
|
|
107
|
+
if (!rootSize || !contentSize) {
|
|
108
|
+
setCopyCount(1);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
setCopyCount(Math.max(1, Math.ceil(rootSize / contentSize)));
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
updateCopyCount();
|
|
116
|
+
|
|
117
|
+
if (typeof ResizeObserver === "undefined") {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const observer = new ResizeObserver(updateCopyCount);
|
|
122
|
+
if (rootRef.current) {
|
|
123
|
+
observer.observe(rootRef.current);
|
|
124
|
+
}
|
|
125
|
+
if (measureRef.current) {
|
|
126
|
+
observer.observe(measureRef.current);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return () => observer.disconnect();
|
|
130
|
+
}, [childArray, gap, resolvedOrientation]);
|
|
51
131
|
|
|
52
132
|
return (
|
|
53
133
|
<div
|
|
54
|
-
ref={
|
|
134
|
+
ref={setRootRef}
|
|
55
135
|
data-direction={resolvedDirection}
|
|
56
136
|
data-orientation={resolvedOrientation}
|
|
57
137
|
data-slot="marquee"
|
|
@@ -68,10 +148,21 @@ export function Marquee(props: MarqueeProps) {
|
|
|
68
148
|
{...rest}
|
|
69
149
|
>
|
|
70
150
|
<style>{marqueeKeyframes}</style>
|
|
151
|
+
<div
|
|
152
|
+
aria-hidden="true"
|
|
153
|
+
data-slot="marquee-measure"
|
|
154
|
+
ref={measureRef}
|
|
155
|
+
className={cn(
|
|
156
|
+
"pointer-events-none invisible absolute -z-10",
|
|
157
|
+
groupClassName,
|
|
158
|
+
)}
|
|
159
|
+
>
|
|
160
|
+
{childArray}
|
|
161
|
+
</div>
|
|
71
162
|
<div
|
|
72
163
|
data-slot="marquee-track"
|
|
73
164
|
className={cn(
|
|
74
|
-
"flex shrink-0 gap-
|
|
165
|
+
"flex shrink-0 gap-(--zui-marquee-gap) will-change-transform [animation-iteration-count:infinite] [animation-timing-function:linear] motion-reduce:[animation-play-state:paused]",
|
|
75
166
|
resolvedOrientation === "vertical" ? "flex-col" : "w-max flex-row",
|
|
76
167
|
pauseOnHover && "group-hover/marquee:[animation-play-state:paused]",
|
|
77
168
|
isReverse && "[animation-direction:reverse]",
|
|
@@ -84,27 +175,18 @@ export function Marquee(props: MarqueeProps) {
|
|
|
84
175
|
} as CSSProperties
|
|
85
176
|
}
|
|
86
177
|
>
|
|
87
|
-
<div
|
|
88
|
-
|
|
89
|
-
|
|
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}
|
|
178
|
+
<div data-slot="marquee-item-group" className={groupClassName}>
|
|
179
|
+
{childArray}
|
|
180
|
+
{fillerChildren}
|
|
96
181
|
</div>
|
|
97
182
|
<div
|
|
98
183
|
aria-hidden="true"
|
|
99
184
|
inert
|
|
100
185
|
data-slot="marquee-item-group"
|
|
101
|
-
className={
|
|
102
|
-
"flex shrink-0 items-center justify-around gap-[var(--zui-marquee-gap)]",
|
|
103
|
-
resolvedOrientation === "vertical" ? "flex-col" : "flex-row",
|
|
104
|
-
itemClassName,
|
|
105
|
-
)}
|
|
186
|
+
className={groupClassName}
|
|
106
187
|
>
|
|
107
|
-
{
|
|
188
|
+
{childArray}
|
|
189
|
+
{fillerChildren}
|
|
108
190
|
</div>
|
|
109
191
|
</div>
|
|
110
192
|
</div>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createRef } from "react";
|
|
2
|
+
import { render } from "@testing-library/react";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Skeleton,
|
|
7
|
+
SkeletonAvatar,
|
|
8
|
+
SkeletonButton,
|
|
9
|
+
SkeletonCard,
|
|
10
|
+
SkeletonText,
|
|
11
|
+
} from "./skeleton";
|
|
12
|
+
|
|
13
|
+
describe("Skeleton", () => {
|
|
14
|
+
it("should set displayName on skeleton primitives", () => {
|
|
15
|
+
expect(Skeleton.displayName).toBe("Skeleton");
|
|
16
|
+
expect(SkeletonText.displayName).toBe("SkeletonText");
|
|
17
|
+
expect(SkeletonAvatar.displayName).toBe("SkeletonAvatar");
|
|
18
|
+
expect(SkeletonCard.displayName).toBe("SkeletonCard");
|
|
19
|
+
expect(SkeletonButton.displayName).toBe("SkeletonButton");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should stamp data-slot and hide the placeholder from assistive tech", () => {
|
|
23
|
+
const { container } = render(<Skeleton busy />);
|
|
24
|
+
const root = container.querySelector('[data-slot="skeleton"]');
|
|
25
|
+
expect(root).toHaveAttribute("aria-hidden", "true");
|
|
26
|
+
expect(root).toHaveAttribute("aria-busy", "true");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should apply pulse motion when animation is not none", () => {
|
|
30
|
+
const { container } = render(<Skeleton animation="pulse" />);
|
|
31
|
+
expect(
|
|
32
|
+
container.querySelector('[data-slot="skeleton"]')?.className,
|
|
33
|
+
).toMatch(/animate-pulse/);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should omit motion classes when animation is none", () => {
|
|
37
|
+
const { container } = render(<Skeleton animation="none" />);
|
|
38
|
+
expect(
|
|
39
|
+
container.querySelector('[data-slot="skeleton"]')?.className,
|
|
40
|
+
).not.toMatch(/animate-pulse/);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should render the requested number of text lines", () => {
|
|
44
|
+
const { container } = render(<SkeletonText lines={4} />);
|
|
45
|
+
const root = container.querySelector('[data-slot="skeleton-text"]');
|
|
46
|
+
expect(root).toHaveAttribute("aria-hidden", "true");
|
|
47
|
+
expect(root?.children.length).toBe(4);
|
|
48
|
+
expect(root?.lastElementChild?.className).toMatch(/w-3\/5/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should share parent animation with nested text placeholders", () => {
|
|
52
|
+
const { container } = render(
|
|
53
|
+
<Skeleton animation="none">
|
|
54
|
+
<SkeletonText lines={2} />
|
|
55
|
+
</Skeleton>,
|
|
56
|
+
);
|
|
57
|
+
const text = container.querySelector('[data-slot="skeleton-text"]');
|
|
58
|
+
expect(text?.firstElementChild?.className).not.toMatch(/animate-pulse/);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should render avatar, button, and card slots", () => {
|
|
62
|
+
const { container } = render(
|
|
63
|
+
<>
|
|
64
|
+
<SkeletonAvatar avatarSize="xl" />
|
|
65
|
+
<SkeletonButton buttonSize="lg" />
|
|
66
|
+
<SkeletonCard />
|
|
67
|
+
</>,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(
|
|
71
|
+
container.querySelector('[data-slot="skeleton-avatar"]'),
|
|
72
|
+
).toHaveClass("size-14");
|
|
73
|
+
expect(
|
|
74
|
+
container.querySelector('[data-slot="skeleton-button"]'),
|
|
75
|
+
).toHaveClass("h-12");
|
|
76
|
+
expect(container.querySelector('[data-slot="skeleton-card"]')).toBeTruthy();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should forward refs to the base skeleton element", () => {
|
|
80
|
+
const ref = createRef<HTMLDivElement>();
|
|
81
|
+
render(<Skeleton ref={ref} />);
|
|
82
|
+
expect(ref.current).toBeInstanceOf(HTMLElement);
|
|
83
|
+
expect(ref.current?.getAttribute("data-slot")).toBe("skeleton");
|
|
84
|
+
});
|
|
85
|
+
});
|
package/src/ui/table/index.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useMemo,
|
|
8
|
+
type KeyboardEvent,
|
|
9
|
+
} from "react";
|
|
4
10
|
|
|
5
11
|
import { cn } from "../../lib/utils";
|
|
6
12
|
|
|
@@ -10,6 +16,7 @@ import type {
|
|
|
10
16
|
TableHeadCellProps,
|
|
11
17
|
TableProps,
|
|
12
18
|
TableSectionProps,
|
|
19
|
+
TableSortDirection,
|
|
13
20
|
} from "./types";
|
|
14
21
|
import { tableCellVariants, tableRowVariants, tableVariants } from "./variants";
|
|
15
22
|
|
|
@@ -122,19 +129,20 @@ export function TableRow({
|
|
|
122
129
|
children,
|
|
123
130
|
ref,
|
|
124
131
|
as: Wrapper = "tr",
|
|
132
|
+
rowAnimation: _rowAnimation,
|
|
125
133
|
...rest
|
|
126
134
|
}: TableSectionProps & { ref?: React.Ref<HTMLTableRowElement> }) {
|
|
127
135
|
const { appearance } = useTableContext("TableRow");
|
|
128
136
|
|
|
129
137
|
return (
|
|
130
|
-
<
|
|
138
|
+
<Wrapper
|
|
131
139
|
ref={ref}
|
|
132
140
|
data-slot="table-row"
|
|
133
141
|
className={cn(tableRowVariants({ appearance }), className)}
|
|
134
142
|
{...rest}
|
|
135
143
|
>
|
|
136
144
|
{children}
|
|
137
|
-
</
|
|
145
|
+
</Wrapper>
|
|
138
146
|
);
|
|
139
147
|
}
|
|
140
148
|
|
|
@@ -144,21 +152,78 @@ export function TableHead({
|
|
|
144
152
|
className,
|
|
145
153
|
children,
|
|
146
154
|
scope = "col",
|
|
155
|
+
sortKey,
|
|
147
156
|
sortDirection,
|
|
157
|
+
onSortChange,
|
|
158
|
+
onClick,
|
|
159
|
+
onKeyDown,
|
|
160
|
+
tabIndex,
|
|
148
161
|
ref,
|
|
149
162
|
...rest
|
|
150
163
|
}: TableHeadCellProps) {
|
|
151
164
|
const { appearance, size, textAlign } = useTableContext("TableHead");
|
|
165
|
+
const isSortable = Boolean(sortKey && onSortChange);
|
|
166
|
+
const sortableDirection: TableSortDirection = sortDirection ?? "none";
|
|
167
|
+
|
|
168
|
+
const handleSort = useCallback(() => {
|
|
169
|
+
if (!sortKey || !onSortChange) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const nextDirection: TableSortDirection =
|
|
174
|
+
sortableDirection === "ascending"
|
|
175
|
+
? "descending"
|
|
176
|
+
: sortableDirection === "descending"
|
|
177
|
+
? "none"
|
|
178
|
+
: "ascending";
|
|
179
|
+
|
|
180
|
+
onSortChange({
|
|
181
|
+
sortKey,
|
|
182
|
+
sortDirection: nextDirection,
|
|
183
|
+
});
|
|
184
|
+
}, [onSortChange, sortKey, sortableDirection]);
|
|
185
|
+
|
|
186
|
+
const handleClick = useCallback<NonNullable<TableHeadCellProps["onClick"]>>(
|
|
187
|
+
(event) => {
|
|
188
|
+
onClick?.(event);
|
|
189
|
+
if (!event.defaultPrevented) {
|
|
190
|
+
handleSort();
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
[handleSort, onClick],
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const handleKeyDown = useCallback(
|
|
197
|
+
(event: KeyboardEvent<HTMLTableCellElement>) => {
|
|
198
|
+
onKeyDown?.(event);
|
|
199
|
+
if (event.defaultPrevented || !isSortable) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
203
|
+
event.preventDefault();
|
|
204
|
+
handleSort();
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
[handleSort, isSortable, onKeyDown],
|
|
208
|
+
);
|
|
209
|
+
|
|
152
210
|
return (
|
|
153
211
|
<th
|
|
154
212
|
ref={ref}
|
|
155
213
|
data-slot="table-head"
|
|
156
214
|
scope={scope}
|
|
157
|
-
aria-sort={sortDirection}
|
|
215
|
+
aria-sort={isSortable ? sortableDirection : sortDirection}
|
|
216
|
+
data-sort-key={sortKey}
|
|
217
|
+
data-sort-direction={sortDirection}
|
|
218
|
+
tabIndex={isSortable ? (tabIndex ?? 0) : tabIndex}
|
|
158
219
|
className={cn(
|
|
159
220
|
tableCellVariants({ appearance, size, textAlign }),
|
|
221
|
+
isSortable &&
|
|
222
|
+
"cursor-pointer select-none outline-none focus-visible:ring-2 focus-visible:ring-slate-300 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950",
|
|
160
223
|
className,
|
|
161
224
|
)}
|
|
225
|
+
onClick={isSortable ? handleClick : onClick}
|
|
226
|
+
onKeyDown={isSortable ? handleKeyDown : onKeyDown}
|
|
162
227
|
{...rest}
|
|
163
228
|
>
|
|
164
229
|
{children}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { createRef } from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
import { describe, expect, it, vi } from "vitest";
|
|
5
|
+
|
|
6
|
+
import { Table } from "./table";
|
|
7
|
+
import {
|
|
8
|
+
TableBody,
|
|
9
|
+
TableCaption,
|
|
10
|
+
TableCell,
|
|
11
|
+
TableFooter,
|
|
12
|
+
TableHead,
|
|
13
|
+
TableHeader,
|
|
14
|
+
TableRow,
|
|
15
|
+
} from "./table-base";
|
|
16
|
+
|
|
17
|
+
function renderBasicTable() {
|
|
18
|
+
return render(
|
|
19
|
+
<Table>
|
|
20
|
+
<TableCaption>Quarterly revenue</TableCaption>
|
|
21
|
+
<TableHeader>
|
|
22
|
+
<TableRow>
|
|
23
|
+
<TableHead>Company</TableHead>
|
|
24
|
+
<TableHead>ARR</TableHead>
|
|
25
|
+
</TableRow>
|
|
26
|
+
</TableHeader>
|
|
27
|
+
<TableBody>
|
|
28
|
+
<TableRow>
|
|
29
|
+
<TableCell scope="row">Zentauri</TableCell>
|
|
30
|
+
<TableCell>$42k</TableCell>
|
|
31
|
+
</TableRow>
|
|
32
|
+
</TableBody>
|
|
33
|
+
<TableFooter>
|
|
34
|
+
<TableRow>
|
|
35
|
+
<TableCell>Total</TableCell>
|
|
36
|
+
<TableCell>$42k</TableCell>
|
|
37
|
+
</TableRow>
|
|
38
|
+
</TableFooter>
|
|
39
|
+
</Table>,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("Table", () => {
|
|
44
|
+
it("should set displayName on compound parts", () => {
|
|
45
|
+
expect(Table.displayName).toBe("Table");
|
|
46
|
+
expect(TableHeader.displayName).toBe("TableHeader");
|
|
47
|
+
expect(TableBody.displayName).toBe("TableBody");
|
|
48
|
+
expect(TableFooter.displayName).toBe("TableFooter");
|
|
49
|
+
expect(TableRow.displayName).toBe("TableRow");
|
|
50
|
+
expect(TableHead.displayName).toBe("TableHead");
|
|
51
|
+
expect(TableCell.displayName).toBe("TableCell");
|
|
52
|
+
expect(TableCaption.displayName).toBe("TableCaption");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should render semantic table structure inside a focusable scroll region", () => {
|
|
56
|
+
const { container } = renderBasicTable();
|
|
57
|
+
expect(
|
|
58
|
+
screen.getByRole("region", { name: "Scrollable table" }),
|
|
59
|
+
).toHaveAttribute("data-slot", "table-scroll");
|
|
60
|
+
expect(screen.getByRole("table")).toHaveAttribute("data-slot", "table");
|
|
61
|
+
expect(
|
|
62
|
+
container.querySelector('[data-slot="table-caption"]'),
|
|
63
|
+
).toHaveTextContent("Quarterly revenue");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should support a custom scroll region label", () => {
|
|
67
|
+
render(<Table scrollAreaAriaLabel="Usage table" />);
|
|
68
|
+
expect(screen.getByRole("region", { name: "Usage table" })).toBeVisible();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should apply sticky header classes when stickyHeader is enabled", () => {
|
|
72
|
+
const { container } = render(
|
|
73
|
+
<Table stickyHeader>
|
|
74
|
+
<TableHeader>
|
|
75
|
+
<TableRow>
|
|
76
|
+
<TableHead>Name</TableHead>
|
|
77
|
+
</TableRow>
|
|
78
|
+
</TableHeader>
|
|
79
|
+
</Table>,
|
|
80
|
+
);
|
|
81
|
+
expect(
|
|
82
|
+
container.querySelector('[data-slot="table-header"]')?.className,
|
|
83
|
+
).toMatch(/sticky/);
|
|
84
|
+
expect(container.querySelector('[data-slot="table"]')?.className).toMatch(
|
|
85
|
+
/table-auto/,
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should expose sort state on sortable header cells", () => {
|
|
90
|
+
render(
|
|
91
|
+
<Table>
|
|
92
|
+
<TableHeader>
|
|
93
|
+
<TableRow>
|
|
94
|
+
<TableHead sortKey="name" sortDirection="ascending">
|
|
95
|
+
Name
|
|
96
|
+
</TableHead>
|
|
97
|
+
</TableRow>
|
|
98
|
+
</TableHeader>
|
|
99
|
+
</Table>,
|
|
100
|
+
);
|
|
101
|
+
const header = screen.getByRole("columnheader", { name: "Name" });
|
|
102
|
+
expect(header).toHaveAttribute("aria-sort", "ascending");
|
|
103
|
+
expect(header).toHaveAttribute("data-sort-key", "name");
|
|
104
|
+
expect(header).toHaveAttribute("data-sort-direction", "ascending");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should mark sortable unsorted header cells with aria-sort none", () => {
|
|
108
|
+
render(
|
|
109
|
+
<Table>
|
|
110
|
+
<TableHeader>
|
|
111
|
+
<TableRow>
|
|
112
|
+
<TableHead sortKey="name" onSortChange={vi.fn()}>
|
|
113
|
+
Name
|
|
114
|
+
</TableHead>
|
|
115
|
+
</TableRow>
|
|
116
|
+
</TableHeader>
|
|
117
|
+
</Table>,
|
|
118
|
+
);
|
|
119
|
+
expect(screen.getByRole("columnheader", { name: "Name" })).toHaveAttribute(
|
|
120
|
+
"aria-sort",
|
|
121
|
+
"none",
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("should call onSortChange with the next direction on click", async () => {
|
|
126
|
+
const user = userEvent.setup();
|
|
127
|
+
const handleSortChange = vi.fn();
|
|
128
|
+
render(
|
|
129
|
+
<Table>
|
|
130
|
+
<TableHeader>
|
|
131
|
+
<TableRow>
|
|
132
|
+
<TableHead
|
|
133
|
+
sortKey="name"
|
|
134
|
+
sortDirection="ascending"
|
|
135
|
+
onSortChange={handleSortChange}
|
|
136
|
+
>
|
|
137
|
+
Name
|
|
138
|
+
</TableHead>
|
|
139
|
+
</TableRow>
|
|
140
|
+
</TableHeader>
|
|
141
|
+
</Table>,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
await user.click(screen.getByRole("columnheader", { name: "Name" }));
|
|
145
|
+
expect(handleSortChange).toHaveBeenCalledWith({
|
|
146
|
+
sortKey: "name",
|
|
147
|
+
sortDirection: "descending",
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should support keyboard sorting", async () => {
|
|
152
|
+
const user = userEvent.setup();
|
|
153
|
+
const handleSortChange = vi.fn();
|
|
154
|
+
render(
|
|
155
|
+
<Table>
|
|
156
|
+
<TableHeader>
|
|
157
|
+
<TableRow>
|
|
158
|
+
<TableHead
|
|
159
|
+
sortKey="createdAt"
|
|
160
|
+
sortDirection="none"
|
|
161
|
+
onSortChange={handleSortChange}
|
|
162
|
+
>
|
|
163
|
+
Created
|
|
164
|
+
</TableHead>
|
|
165
|
+
</TableRow>
|
|
166
|
+
</TableHeader>
|
|
167
|
+
</Table>,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
screen.getByRole("columnheader", { name: "Created" }).focus();
|
|
171
|
+
await user.keyboard("{Enter}");
|
|
172
|
+
expect(handleSortChange).toHaveBeenCalledWith({
|
|
173
|
+
sortKey: "createdAt",
|
|
174
|
+
sortDirection: "ascending",
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("should render scoped body cells as row headers", () => {
|
|
179
|
+
renderBasicTable();
|
|
180
|
+
expect(screen.getByRole("rowheader", { name: "Zentauri" })).toHaveAttribute(
|
|
181
|
+
"data-slot",
|
|
182
|
+
"table-cell",
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("should not leak rowAnimation onto the rendered row", () => {
|
|
187
|
+
const { container } = render(
|
|
188
|
+
<Table>
|
|
189
|
+
<TableBody>
|
|
190
|
+
<TableRow rowAnimation="hover">
|
|
191
|
+
<TableCell>Animated elsewhere</TableCell>
|
|
192
|
+
</TableRow>
|
|
193
|
+
</TableBody>
|
|
194
|
+
</Table>,
|
|
195
|
+
);
|
|
196
|
+
expect(
|
|
197
|
+
container.querySelector('[data-slot="table-row"]'),
|
|
198
|
+
).not.toHaveAttribute("rowAnimation");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("should forward refs to the table element", () => {
|
|
202
|
+
const ref = createRef<HTMLTableElement>();
|
|
203
|
+
render(<Table ref={ref} />);
|
|
204
|
+
expect(ref.current).toBeInstanceOf(HTMLTableElement);
|
|
205
|
+
expect(ref.current?.getAttribute("data-slot")).toBe("table");
|
|
206
|
+
});
|
|
207
|
+
});
|
package/src/ui/table/types.ts
CHANGED
|
@@ -11,6 +11,16 @@ import type {
|
|
|
11
11
|
import type { tableVariants } from "./variants";
|
|
12
12
|
|
|
13
13
|
export type TableAnimation = "none" | "hover";
|
|
14
|
+
export type TableSortDirection = "ascending" | "descending" | "none";
|
|
15
|
+
|
|
16
|
+
export type TableSortState<TKey extends string = string> = {
|
|
17
|
+
sortKey?: TKey;
|
|
18
|
+
sortDirection: TableSortDirection;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type TableSortChangeHandler<TKey extends string = string> = (
|
|
22
|
+
nextSort: TableSortState<TKey>,
|
|
23
|
+
) => void;
|
|
14
24
|
|
|
15
25
|
type TableVariantProps = VariantProps<typeof tableVariants>;
|
|
16
26
|
|
|
@@ -31,7 +41,9 @@ export type TableSectionProps = {
|
|
|
31
41
|
};
|
|
32
42
|
|
|
33
43
|
export type TableHeadCellProps = ThHTMLAttributes<HTMLTableCellElement> & {
|
|
34
|
-
|
|
44
|
+
sortKey?: string;
|
|
45
|
+
sortDirection?: TableSortDirection;
|
|
46
|
+
onSortChange?: TableSortChangeHandler;
|
|
35
47
|
ref?: Ref<HTMLTableCellElement>;
|
|
36
48
|
};
|
|
37
49
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/charts/shared/variants.ts","../src/charts/shared/chart-frame.tsx"],"names":[],"mappings":"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","file":"chunk-G2WARVAM.mjs","sourcesContent":["import { cva } from \"class-variance-authority\";\n\nexport const chartVariants = cva(\n [\n \"relative w-full min-w-0 overflow-hidden rounded-xl\",\n \"h-[var(--chart-height)] min-h-64 sm:min-h-72 md:min-h-80\",\n \"[&_.recharts-default-tooltip]:rounded-lg [&_.recharts-default-tooltip]:border [&_.recharts-default-tooltip]:border-slate-200\",\n \"[&_.recharts-default-tooltip]:bg-white/95 [&_.recharts-default-tooltip]:shadow-lg\",\n \"[&_.recharts-default-tooltip]:text-slate-900\",\n ],\n {\n variants: {\n appearance: {\n default:\n \"bg-white text-slate-600 [&_.recharts-cartesian-axis-tick-value]:fill-slate-900\",\n muted:\n \"bg-slate-50 text-slate-500 [&_.recharts-cartesian-axis-tick-value]:fill-slate-900\",\n outline:\n \"border border-slate-200 bg-white text-slate-600 [&_.recharts-cartesian-axis-tick-value]:fill-slate-900\",\n glass:\n \"border border-white/15 bg-white/10 text-slate-100 backdrop-blur-md [&_.recharts-cartesian-axis-tick-value]:fill-white\",\n sky: \"bg-sky-50 text-sky-500 [&_.recharts-cartesian-axis-tick-value]:fill-slate-900\",\n emerald:\n \"bg-emerald-50 text-emerald-500 [&_.recharts-cartesian-axis-tick-value]:fill-slate-900\",\n violet:\n \"bg-violet-50 text-violet-500 [&_.recharts-cartesian-axis-tick-value]:fill-slate-900\",\n amber:\n \"bg-amber-50 text-amber-500 [&_.recharts-cartesian-axis-tick-value]:fill-slate-900\",\n rose: \"bg-rose-50 text-rose-500 [&_.recharts-cartesian-axis-tick-value]:fill-slate-900\",\n slate:\n \"bg-slate-50 text-slate-500 [&_.recharts-cartesian-axis-tick-value]:fill-slate-900\",\n gray: \"bg-gray-50 text-gray-500 [&_.recharts-cartesian-axis-tick-value]:fill-slate-900\",\n indigo:\n \"bg-indigo-50 text-indigo-500 [&_.recharts-cartesian-axis-tick-value]:fill-slate-900\",\n \"gradient-cyan-violet\":\n \"bg-gradient-to-r from-cyan-500 to-violet-500 text-white [&_.recharts-cartesian-axis-tick-value]:fill-white\",\n \"gradient-emerald-violet\":\n \"bg-gradient-to-r from-emerald-500 to-violet-500 text-white [&_.recharts-cartesian-axis-tick-value]:fill-white\",\n \"gradient-amber-rose\":\n \"bg-gradient-to-r from-amber-500 to-rose-500 text-white [&_.recharts-cartesian-axis-tick-value]:fill-white\",\n \"gradient-slate-gray\":\n \"bg-gradient-to-r from-slate-500 to-gray-500 text-white [&_.recharts-cartesian-axis-tick-value]:fill-white\",\n \"gradient-indigo-purple\":\n \"bg-gradient-to-r from-indigo-500 to-purple-500 text-white [&_.recharts-cartesian-axis-tick-value]:fill-white\",\n \"gradient-cyan-blue\":\n \"bg-gradient-to-r from-cyan-500 to-blue-500 text-white [&_.recharts-cartesian-axis-tick-value]:fill-white\",\n \"gradient-emerald-blue\":\n \"bg-gradient-to-r from-emerald-500 to-blue-500 text-white [&_.recharts-cartesian-axis-tick-value]:fill-white\",\n \"gradient-amber-blue\":\n \"bg-gradient-to-r from-amber-500 to-blue-500 text-white [&_.recharts-cartesian-axis-tick-value]:fill-white\",\n },\n density: {\n compact: \"p-2 sm:p-3\",\n comfortable: \"p-3 sm:p-4\",\n spacious: \"p-4 sm:p-5 md:p-6\",\n },\n },\n defaultVariants: {\n appearance: \"default\",\n density: \"comfortable\",\n },\n },\n);\n\nexport const chartPalette = {\n glass: { stroke: \"#0f172a\", fill: \"#0f172a\", textColor: \"#0f172a\" },\n outline: { stroke: \"#0f172a\", fill: \"#0f172a\", textColor: \"#0f172a\" },\n muted: { stroke: \"#0f172a\", fill: \"#0f172a\", textColor: \"#0f172a\" },\n default: { stroke: \"#0f172a\", fill: \"#0f172a\", textColor: \"#0f172a\" },\n sky: { stroke: \"#0f172a\", fill: \"#0f172a\", textColor: \"#0f172a\" },\n cyan: { stroke: \"#0891b2\", fill: \"#67e8f9\", textColor: \"#0891b2\" },\n emerald: { stroke: \"#059669\", fill: \"#6ee7b7\", textColor: \"#059669\" },\n violet: { stroke: \"#7c3aed\", fill: \"#c4b5fd\", textColor: \"#7c3aed\" },\n amber: { stroke: \"#d97706\", fill: \"#fcd34d\", textColor: \"#d97706\" },\n rose: { stroke: \"#e11d48\", fill: \"#fda4af\", textColor: \"#e11d48\" },\n slate: { stroke: \"#475569\", fill: \"#cbd5e1\", textColor: \"#475569\" },\n gray: { stroke: \"#6b7280\", fill: \"#d1d5db\", textColor: \"#6b7280\" },\n white: { stroke: \"#ffffff\", fill: \"#ffffff\", textColor: \"#ffffff\" },\n indigo: { stroke: \"#6366f1\", fill: \"#c7d2fe\", textColor: \"#6366f1\" },\n \"gradient-cyan-violet\": {\n stroke: \"#0891b2\",\n fill: \"#67e8f9\",\n textColor: \"#0891b2\",\n },\n \"gradient-emerald-violet\": {\n stroke: \"#059669\",\n fill: \"#6ee7b7\",\n textColor: \"#059669\",\n },\n \"gradient-amber-rose\": {\n stroke: \"#d97706\",\n fill: \"#fcd34d\",\n textColor: \"#d97706\",\n },\n \"gradient-slate-gray\": {\n stroke: \"#475569\",\n fill: \"#cbd5e1\",\n textColor: \"#475569\",\n },\n \"gradient-indigo-purple\": {\n stroke: \"#6366f1\",\n fill: \"#c7d2fe\",\n textColor: \"#6366f1\",\n },\n \"gradient-cyan-blue\": {\n stroke: \"#0891b2\",\n fill: \"#67e8f9\",\n textColor: \"#0891b2\",\n },\n \"gradient-emerald-blue\": {\n stroke: \"#059669\",\n fill: \"#6ee7b7\",\n textColor: \"#059669\",\n },\n \"gradient-amber-blue\": {\n stroke: \"#d97706\",\n fill: \"#fcd34d\",\n textColor: \"#d97706\",\n },\n} as const;\n\nexport const chartColorValues = Object.keys(chartPalette) as Array<\n keyof typeof chartPalette\n>;\n","\"use client\";\n\nimport type { CSSProperties, HTMLAttributes, ReactNode } from \"react\";\nimport { CartesianGrid, Legend, ResponsiveContainer, Tooltip } from \"recharts\";\n\nimport { useResizeObserver } from \"../../hooks/useResizeObserver/useResizeObserver\";\nimport { cn } from \"../../lib/utils\";\nimport { chartVariants } from \"./variants\";\nimport { VariantProps } from \"class-variance-authority\";\n\n/** Chart-level props that may be forwarded from *Chart `...rest` and must not reach a DOM node. */\nconst CHART_ONLY_DIV_PROP_KEYS = new Set([\n \"data\",\n \"margin\",\n \"series\",\n \"showGrid\",\n \"showLegend\",\n \"showTooltip\",\n \"stacked\",\n \"strokeDasharray\",\n \"syncId\",\n \"tooltipColor\",\n \"xKey\",\n]);\n\nfunction filterDivProps(\n props: HTMLAttributes<HTMLDivElement>,\n): HTMLAttributes<HTMLDivElement> {\n const entries = Object.entries(props).filter(\n ([key]) => !CHART_ONLY_DIV_PROP_KEYS.has(key),\n );\n return Object.fromEntries(entries) as HTMLAttributes<HTMLDivElement>;\n}\n\ntype ChartFrameProps = HTMLAttributes<HTMLDivElement> & {\n appearance?: VariantProps<typeof chartVariants>[\"appearance\"];\n containerStyle?: CSSProperties;\n density?: \"compact\" | \"comfortable\" | \"spacious\" | null;\n emptyState?: ReactNode;\n hasData: boolean;\n height: number;\n style?: CSSProperties;\n children: ReactNode;\n};\n\nexport const defaultChartMargin = { top: 16, right: 16, bottom: 8, left: 0 };\n\nexport function ChartFrame({\n appearance,\n children,\n className,\n containerStyle,\n density,\n emptyState = null,\n hasData,\n height,\n style,\n ...props\n}: ChartFrameProps) {\n const divProps = filterDivProps(props);\n const [measureRef, size] = useResizeObserver<HTMLDivElement>();\n const chartStyle = {\n \"--chart-height\": `${height}px`,\n ...style,\n } as CSSProperties;\n const canRenderChart =\n (size?.width ?? 0) > 0 && (size?.height ?? 0) > 0;\n\n if (!hasData) {\n return (\n <div\n className={cn(chartVariants({ appearance, density }), className)}\n style={chartStyle}\n {...divProps}\n >\n <div className=\"flex h-full min-h-48 items-center justify-center text-sm text-slate-500\">\n {emptyState}\n </div>\n </div>\n );\n }\n\n return (\n <div\n className={cn(chartVariants({ appearance, density }), className)}\n style={chartStyle}\n {...divProps}\n >\n <div ref={measureRef} className=\"h-full min-h-0 w-full min-w-0\">\n {canRenderChart ? (\n <ResponsiveContainer\n width=\"100%\"\n height=\"100%\"\n minWidth={0}\n debounce={80}\n style={containerStyle}\n >\n {children}\n </ResponsiveContainer>\n ) : null}\n </div>\n </div>\n );\n}\n\nexport function ChartDecorators({\n axis,\n showGrid,\n showLegend,\n showTooltip,\n tooltipColor = \"#0f172a\",\n}: {\n axis: ReactNode;\n showGrid: boolean;\n showLegend: boolean;\n showTooltip: boolean;\n tooltipColor?: string;\n}) {\n return (\n <>\n {showGrid ? (\n <CartesianGrid\n strokeDasharray=\"3 3\"\n stroke=\"currentColor\"\n opacity={0.16}\n />\n ) : null}\n {axis}\n {showTooltip ? (\n <Tooltip\n cursor={{ opacity: 0.12 }}\n contentStyle={{ color: tooltipColor }}\n labelStyle={{ color: tooltipColor }}\n itemStyle={{ color: tooltipColor }}\n />\n ) : null}\n {showLegend ? <Legend /> : null}\n </>\n );\n}\n"]}
|