@zentauri-ui/zentauri-components 1.7.5 → 1.7.7
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 +11 -6
- package/cli/registry.json +3 -0
- package/dist/chunk-DEZRB6DS.mjs +83 -0
- package/dist/chunk-DEZRB6DS.mjs.map +1 -0
- package/dist/chunk-GRJFGIZC.mjs +417 -0
- package/dist/chunk-GRJFGIZC.mjs.map +1 -0
- package/dist/chunk-QHEHBC6M.js +421 -0
- package/dist/chunk-QHEHBC6M.js.map +1 -0
- package/dist/chunk-V5JTDRV5.mjs +278 -0
- package/dist/chunk-V5JTDRV5.mjs.map +1 -0
- package/dist/chunk-Z4KHAD6Y.js +295 -0
- package/dist/chunk-Z4KHAD6Y.js.map +1 -0
- package/dist/chunk-ZX2IBIZT.js +92 -0
- package/dist/chunk-ZX2IBIZT.js.map +1 -0
- package/dist/design-system/context-menu.d.ts +41 -0
- package/dist/design-system/context-menu.d.ts.map +1 -0
- package/dist/design-system/index.d.ts +3 -0
- package/dist/design-system/index.d.ts.map +1 -1
- package/dist/design-system/timeline.d.ts +56 -0
- package/dist/design-system/timeline.d.ts.map +1 -0
- package/dist/design-system/tree-view.d.ts +66 -0
- package/dist/design-system/tree-view.d.ts.map +1 -0
- package/dist/ui/context-menu/context-menu.d.ts +11 -0
- package/dist/ui/context-menu/context-menu.d.ts.map +1 -0
- package/dist/ui/context-menu/index.d.ts +4 -0
- package/dist/ui/context-menu/index.d.ts.map +1 -0
- package/dist/ui/context-menu/types.d.ts +81 -0
- package/dist/ui/context-menu/types.d.ts.map +1 -0
- package/dist/ui/context-menu/variants.d.ts +7 -0
- package/dist/ui/context-menu/variants.d.ts.map +1 -0
- package/dist/ui/context-menu.js +500 -0
- package/dist/ui/context-menu.js.map +1 -0
- package/dist/ui/context-menu.mjs +488 -0
- package/dist/ui/context-menu.mjs.map +1 -0
- package/dist/ui/dropdown.js +9 -89
- package/dist/ui/dropdown.js.map +1 -1
- package/dist/ui/dropdown.mjs +1 -81
- package/dist/ui/dropdown.mjs.map +1 -1
- package/dist/ui/scroll-area/scroll-area.d.ts.map +1 -1
- package/dist/ui/scroll-area.js.map +1 -1
- package/dist/ui/scroll-area.mjs.map +1 -1
- package/dist/ui/timeline/animated/animations.d.ts +8 -0
- package/dist/ui/timeline/animated/animations.d.ts.map +1 -0
- package/dist/ui/timeline/animated/index.d.ts +6 -0
- package/dist/ui/timeline/animated/index.d.ts.map +1 -0
- package/dist/ui/timeline/animated/timeline-item-animated.d.ts +8 -0
- package/dist/ui/timeline/animated/timeline-item-animated.d.ts.map +1 -0
- package/dist/ui/timeline/animated/types.d.ts +12 -0
- package/dist/ui/timeline/animated/types.d.ts.map +1 -0
- package/dist/ui/timeline/animated.js +94 -0
- package/dist/ui/timeline/animated.js.map +1 -0
- package/dist/ui/timeline/animated.mjs +71 -0
- package/dist/ui/timeline/animated.mjs.map +1 -0
- package/dist/ui/timeline/index.d.ts +4 -0
- package/dist/ui/timeline/index.d.ts.map +1 -0
- package/dist/ui/timeline/timeline-base.d.ts +37 -0
- package/dist/ui/timeline/timeline-base.d.ts.map +1 -0
- package/dist/ui/timeline/timeline.d.ts +8 -0
- package/dist/ui/timeline/timeline.d.ts.map +1 -0
- package/dist/ui/timeline/types.d.ts +38 -0
- package/dist/ui/timeline/types.d.ts.map +1 -0
- package/dist/ui/timeline/variants.d.ts +19 -0
- package/dist/ui/timeline/variants.d.ts.map +1 -0
- package/dist/ui/timeline.js +63 -0
- package/dist/ui/timeline.js.map +1 -0
- package/dist/ui/timeline.mjs +14 -0
- package/dist/ui/timeline.mjs.map +1 -0
- package/dist/ui/tree-view/animated/animations.d.ts +6 -0
- package/dist/ui/tree-view/animated/animations.d.ts.map +1 -0
- package/dist/ui/tree-view/animated/index.d.ts +5 -0
- package/dist/ui/tree-view/animated/index.d.ts.map +1 -0
- package/dist/ui/tree-view/animated/tree-view-animated.d.ts +6 -0
- package/dist/ui/tree-view/animated/tree-view-animated.d.ts.map +1 -0
- package/dist/ui/tree-view/animated/types.d.ts +6 -0
- package/dist/ui/tree-view/animated/types.d.ts.map +1 -0
- package/dist/ui/tree-view/animated.js +53 -0
- package/dist/ui/tree-view/animated.js.map +1 -0
- package/dist/ui/tree-view/animated.mjs +50 -0
- package/dist/ui/tree-view/animated.mjs.map +1 -0
- package/dist/ui/tree-view/index.d.ts +5 -0
- package/dist/ui/tree-view/index.d.ts.map +1 -0
- package/dist/ui/tree-view/tree-view-base.d.ts +15 -0
- package/dist/ui/tree-view/tree-view-base.d.ts.map +1 -0
- package/dist/ui/tree-view/tree-view.d.ts +6 -0
- package/dist/ui/tree-view/tree-view.d.ts.map +1 -0
- package/dist/ui/tree-view/types.d.ts +61 -0
- package/dist/ui/tree-view/types.d.ts.map +1 -0
- package/dist/ui/tree-view/variants.d.ts +9 -0
- package/dist/ui/tree-view/variants.d.ts.map +1 -0
- package/dist/ui/tree-view.js +27 -0
- package/dist/ui/tree-view.js.map +1 -0
- package/dist/ui/tree-view.mjs +14 -0
- package/dist/ui/tree-view.mjs.map +1 -0
- package/package.json +1 -1
- package/src/design-system/context-menu.ts +44 -0
- package/src/design-system/index.ts +3 -0
- package/src/design-system/timeline.ts +87 -0
- package/src/design-system/tree-view.ts +113 -0
- package/src/ui/context-menu/context-menu.test.tsx +176 -0
- package/src/ui/context-menu/context-menu.tsx +536 -0
- package/src/ui/context-menu/index.ts +29 -0
- package/src/ui/context-menu/types.ts +110 -0
- package/src/ui/context-menu/variants.ts +26 -0
- package/src/ui/scroll-area/scroll-area.tsx +0 -2
- package/src/ui/timeline/animated/animations.ts +16 -0
- package/src/ui/timeline/animated/index.ts +22 -0
- package/src/ui/timeline/animated/timeline-item-animated.tsx +76 -0
- package/src/ui/timeline/animated/types.ts +21 -0
- package/src/ui/timeline/index.ts +30 -0
- package/src/ui/timeline/timeline-base.tsx +232 -0
- package/src/ui/timeline/timeline.test.tsx +262 -0
- package/src/ui/timeline/timeline.tsx +24 -0
- package/src/ui/timeline/types.ts +61 -0
- package/src/ui/timeline/variants.ts +60 -0
- package/src/ui/tree-view/animated/animations.ts +13 -0
- package/src/ui/tree-view/animated/index.ts +6 -0
- package/src/ui/tree-view/animated/tree-view-animated.tsx +52 -0
- package/src/ui/tree-view/animated/types.ts +6 -0
- package/src/ui/tree-view/index.ts +13 -0
- package/src/ui/tree-view/tree-view-base.tsx +496 -0
- package/src/ui/tree-view/tree-view.test.tsx +136 -0
- package/src/ui/tree-view/tree-view.tsx +9 -0
- package/src/ui/tree-view/types.ts +68 -0
- package/src/ui/tree-view/variants.ts +32 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Transition } from "framer-motion";
|
|
2
|
+
|
|
3
|
+
/** Entrance presets for animated timeline items (fade + slide, connector draw). */
|
|
4
|
+
export type TimelineTransition = "none" | "default" | "smooth" | "slow";
|
|
5
|
+
|
|
6
|
+
export type TimelineTransitionPresets = Record<TimelineTransition, Transition>;
|
|
7
|
+
|
|
8
|
+
export const timelineItemTransitionPresets: TimelineTransitionPresets = {
|
|
9
|
+
none: { duration: 0 },
|
|
10
|
+
default: { duration: 0.4, ease: [0.22, 1, 0.36, 1] },
|
|
11
|
+
smooth: { duration: 0.55, ease: [0.22, 1, 0.36, 1] },
|
|
12
|
+
slow: { duration: 0.8, ease: [0.4, 0, 0.2, 1] },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Default per-item entrance delay multiplier (seconds) for staggered reveals. */
|
|
16
|
+
export const TIMELINE_DEFAULT_STAGGER = 0.08;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
Timeline,
|
|
5
|
+
TimelineContent,
|
|
6
|
+
TimelineDescription,
|
|
7
|
+
TimelineIndicator,
|
|
8
|
+
TimelineTitle,
|
|
9
|
+
} from "../timeline-base";
|
|
10
|
+
export { TimelineItem } from "./timeline-item-animated";
|
|
11
|
+
export type {
|
|
12
|
+
TimelineAnimatedProps,
|
|
13
|
+
TimelineItemAnimatedProps,
|
|
14
|
+
} from "./types";
|
|
15
|
+
export {
|
|
16
|
+
TIMELINE_DEFAULT_STAGGER,
|
|
17
|
+
timelineItemTransitionPresets,
|
|
18
|
+
} from "./animations";
|
|
19
|
+
export type {
|
|
20
|
+
TimelineTransition,
|
|
21
|
+
TimelineTransitionPresets,
|
|
22
|
+
} from "./animations";
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { motion, useReducedMotion } from "framer-motion";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../../lib/utils";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
TimelineItemContext,
|
|
10
|
+
useTimelineContext,
|
|
11
|
+
useTimelineIndex,
|
|
12
|
+
} from "../timeline-base";
|
|
13
|
+
import type { TimelineItemCtx } from "../types";
|
|
14
|
+
import {
|
|
15
|
+
timelineConnectorVariants,
|
|
16
|
+
timelineItemVariants,
|
|
17
|
+
} from "../variants";
|
|
18
|
+
import {
|
|
19
|
+
TIMELINE_DEFAULT_STAGGER,
|
|
20
|
+
timelineItemTransitionPresets,
|
|
21
|
+
} from "./animations";
|
|
22
|
+
import type { TimelineItemAnimatedProps } from "./types";
|
|
23
|
+
|
|
24
|
+
export function TimelineItem({
|
|
25
|
+
className,
|
|
26
|
+
children,
|
|
27
|
+
transitionVariant = "default",
|
|
28
|
+
stagger = TIMELINE_DEFAULT_STAGGER,
|
|
29
|
+
ref,
|
|
30
|
+
...rest
|
|
31
|
+
}: TimelineItemAnimatedProps & { ref?: React.Ref<HTMLLIElement> }) {
|
|
32
|
+
const { size, total } = useTimelineContext("TimelineItem");
|
|
33
|
+
const index = useTimelineIndex("TimelineItem");
|
|
34
|
+
const isLast = index === total - 1;
|
|
35
|
+
const itemCtx = useMemo<TimelineItemCtx>(
|
|
36
|
+
() => ({ index, isLast }),
|
|
37
|
+
[index, isLast],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const reducedMotion = useReducedMotion();
|
|
41
|
+
const motionless = transitionVariant === "none" || Boolean(reducedMotion);
|
|
42
|
+
const transition = timelineItemTransitionPresets[transitionVariant];
|
|
43
|
+
const delay = index * stagger;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<TimelineItemContext.Provider value={itemCtx}>
|
|
47
|
+
<motion.li
|
|
48
|
+
ref={ref}
|
|
49
|
+
data-slot="timeline-item"
|
|
50
|
+
data-last={isLast ? "" : undefined}
|
|
51
|
+
className={cn(timelineItemVariants(), className)}
|
|
52
|
+
initial={motionless ? false : { opacity: 0, y: 12 }}
|
|
53
|
+
whileInView={motionless ? undefined : { opacity: 1, y: 0 }}
|
|
54
|
+
viewport={{ once: true, amount: 0.35 }}
|
|
55
|
+
transition={{ ...transition, delay }}
|
|
56
|
+
{...rest}
|
|
57
|
+
>
|
|
58
|
+
{!isLast ? (
|
|
59
|
+
<motion.span
|
|
60
|
+
aria-hidden="true"
|
|
61
|
+
data-slot="timeline-connector"
|
|
62
|
+
className={timelineConnectorVariants({ size })}
|
|
63
|
+
style={{ originY: 0 }}
|
|
64
|
+
initial={motionless ? false : { scaleY: 0, x: "-50%" }}
|
|
65
|
+
whileInView={motionless ? undefined : { scaleY: 1, x: "-50%" }}
|
|
66
|
+
viewport={{ once: true, amount: 0.35 }}
|
|
67
|
+
transition={{ ...transition, delay: delay + 0.12 }}
|
|
68
|
+
/>
|
|
69
|
+
) : null}
|
|
70
|
+
{children}
|
|
71
|
+
</motion.li>
|
|
72
|
+
</TimelineItemContext.Provider>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
TimelineItem.displayName = "TimelineItem";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { TimelineItemProps, TimelineProps } from "../types";
|
|
2
|
+
import type { TimelineTransition } from "./animations";
|
|
3
|
+
|
|
4
|
+
export type TimelineAnimatedProps = TimelineProps;
|
|
5
|
+
|
|
6
|
+
/** Motion `li` uses different handler types than some React DOM events. */
|
|
7
|
+
export type TimelineItemMotionConflictProps =
|
|
8
|
+
| "onAnimationStart"
|
|
9
|
+
| "onDrag"
|
|
10
|
+
| "onDragEnd"
|
|
11
|
+
| "onDragStart";
|
|
12
|
+
|
|
13
|
+
export type TimelineItemAnimatedProps = Omit<
|
|
14
|
+
TimelineItemProps,
|
|
15
|
+
TimelineItemMotionConflictProps
|
|
16
|
+
> & {
|
|
17
|
+
/** Entrance easing/duration preset. `none` renders without motion. */
|
|
18
|
+
transitionVariant?: TimelineTransition;
|
|
19
|
+
/** Per-item entrance delay multiplier in seconds (index × stagger). */
|
|
20
|
+
stagger?: number;
|
|
21
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
Timeline,
|
|
5
|
+
TimelineContent,
|
|
6
|
+
TimelineDescription,
|
|
7
|
+
TimelineIndicator,
|
|
8
|
+
TimelineItem,
|
|
9
|
+
TimelineTitle,
|
|
10
|
+
} from "./timeline";
|
|
11
|
+
export type {
|
|
12
|
+
TimelineAppearance,
|
|
13
|
+
TimelineContentProps,
|
|
14
|
+
TimelineDescriptionProps,
|
|
15
|
+
TimelineIndicatorProps,
|
|
16
|
+
TimelineItemProps,
|
|
17
|
+
TimelineProps,
|
|
18
|
+
TimelineSize,
|
|
19
|
+
TimelineTitleProps,
|
|
20
|
+
TimelineTransition,
|
|
21
|
+
} from "./types";
|
|
22
|
+
export {
|
|
23
|
+
timelineConnectorVariants,
|
|
24
|
+
timelineContentVariants,
|
|
25
|
+
timelineDescriptionVariants,
|
|
26
|
+
timelineIndicatorVariants,
|
|
27
|
+
timelineItemVariants,
|
|
28
|
+
timelineTitleVariants,
|
|
29
|
+
timelineVariants,
|
|
30
|
+
} from "./variants";
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Children,
|
|
5
|
+
createContext,
|
|
6
|
+
isValidElement,
|
|
7
|
+
useContext,
|
|
8
|
+
useMemo,
|
|
9
|
+
} from "react";
|
|
10
|
+
|
|
11
|
+
import { cn } from "../../lib/utils";
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
TimelineContentProps,
|
|
15
|
+
TimelineCtx,
|
|
16
|
+
TimelineDescriptionProps,
|
|
17
|
+
TimelineIndicatorProps,
|
|
18
|
+
TimelineItemCtx,
|
|
19
|
+
TimelineItemProps,
|
|
20
|
+
TimelineProps,
|
|
21
|
+
TimelineSize,
|
|
22
|
+
TimelineTitleProps,
|
|
23
|
+
} from "./types";
|
|
24
|
+
import {
|
|
25
|
+
timelineConnectorVariants,
|
|
26
|
+
timelineContentVariants,
|
|
27
|
+
timelineDescriptionVariants,
|
|
28
|
+
timelineIndicatorVariants,
|
|
29
|
+
timelineItemVariants,
|
|
30
|
+
timelineTitleVariants,
|
|
31
|
+
timelineVariants,
|
|
32
|
+
} from "./variants";
|
|
33
|
+
|
|
34
|
+
const TimelineContext = createContext<TimelineCtx | null>(null);
|
|
35
|
+
|
|
36
|
+
const TimelineIndexContext = createContext<number | null>(null);
|
|
37
|
+
|
|
38
|
+
export const TimelineItemContext = createContext<TimelineItemCtx | null>(null);
|
|
39
|
+
|
|
40
|
+
export function useTimelineContext(component: string): TimelineCtx {
|
|
41
|
+
const ctx = useContext(TimelineContext);
|
|
42
|
+
if (!ctx) {
|
|
43
|
+
throw new Error(`${component} must be used within <Timeline>`);
|
|
44
|
+
}
|
|
45
|
+
return ctx;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function useTimelineIndex(component: string): number {
|
|
49
|
+
const index = useContext(TimelineIndexContext);
|
|
50
|
+
if (index === null) {
|
|
51
|
+
throw new Error(`${component} must be used within <Timeline>`);
|
|
52
|
+
}
|
|
53
|
+
return index;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function useTimelineSize(): TimelineSize {
|
|
57
|
+
return useContext(TimelineContext)?.size ?? "md";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function useTimelineIsLast(): boolean {
|
|
61
|
+
return useContext(TimelineItemContext)?.isLast ?? false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function Timeline({
|
|
65
|
+
appearance = "default",
|
|
66
|
+
size = "md",
|
|
67
|
+
className,
|
|
68
|
+
children,
|
|
69
|
+
ref,
|
|
70
|
+
...rest
|
|
71
|
+
}: TimelineProps) {
|
|
72
|
+
const items = useMemo(
|
|
73
|
+
() => Children.toArray(children).filter(isValidElement),
|
|
74
|
+
[children],
|
|
75
|
+
);
|
|
76
|
+
const ctx = useMemo<TimelineCtx>(
|
|
77
|
+
() => ({
|
|
78
|
+
appearance: appearance ?? "default",
|
|
79
|
+
size: size ?? "md",
|
|
80
|
+
total: items.length,
|
|
81
|
+
}),
|
|
82
|
+
[appearance, size, items.length],
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<TimelineContext.Provider value={ctx}>
|
|
87
|
+
<ol
|
|
88
|
+
ref={ref}
|
|
89
|
+
data-slot="timeline"
|
|
90
|
+
className={cn(timelineVariants(), "m-0 list-none p-0", className)}
|
|
91
|
+
{...rest}
|
|
92
|
+
>
|
|
93
|
+
{items.map((child, index) => (
|
|
94
|
+
<TimelineIndexContext.Provider key={child.key} value={index}>
|
|
95
|
+
{child}
|
|
96
|
+
</TimelineIndexContext.Provider>
|
|
97
|
+
))}
|
|
98
|
+
</ol>
|
|
99
|
+
</TimelineContext.Provider>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
Timeline.displayName = "Timeline";
|
|
104
|
+
|
|
105
|
+
export function TimelineItem({
|
|
106
|
+
className,
|
|
107
|
+
children,
|
|
108
|
+
ref,
|
|
109
|
+
...rest
|
|
110
|
+
}: TimelineItemProps) {
|
|
111
|
+
const { size, total } = useTimelineContext("TimelineItem");
|
|
112
|
+
const index = useTimelineIndex("TimelineItem");
|
|
113
|
+
const isLast = index === total - 1;
|
|
114
|
+
const itemCtx = useMemo<TimelineItemCtx>(
|
|
115
|
+
() => ({ index, isLast }),
|
|
116
|
+
[index, isLast],
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<TimelineItemContext.Provider value={itemCtx}>
|
|
121
|
+
<li
|
|
122
|
+
ref={ref}
|
|
123
|
+
data-slot="timeline-item"
|
|
124
|
+
data-last={isLast ? "" : undefined}
|
|
125
|
+
className={cn(timelineItemVariants(), className)}
|
|
126
|
+
{...rest}
|
|
127
|
+
>
|
|
128
|
+
{!isLast ? (
|
|
129
|
+
<span
|
|
130
|
+
aria-hidden="true"
|
|
131
|
+
data-slot="timeline-connector"
|
|
132
|
+
className={timelineConnectorVariants({ size })}
|
|
133
|
+
/>
|
|
134
|
+
) : null}
|
|
135
|
+
{children}
|
|
136
|
+
</li>
|
|
137
|
+
</TimelineItemContext.Provider>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
TimelineItem.displayName = "TimelineItem";
|
|
142
|
+
|
|
143
|
+
export function TimelineIndicator({
|
|
144
|
+
className,
|
|
145
|
+
appearance,
|
|
146
|
+
children,
|
|
147
|
+
ref,
|
|
148
|
+
...rest
|
|
149
|
+
}: TimelineIndicatorProps & { ref?: React.Ref<HTMLDivElement> }) {
|
|
150
|
+
const { appearance: rootAppearance, size } =
|
|
151
|
+
useTimelineContext("TimelineIndicator");
|
|
152
|
+
return (
|
|
153
|
+
<div
|
|
154
|
+
ref={ref}
|
|
155
|
+
data-slot="timeline-indicator"
|
|
156
|
+
className={cn(
|
|
157
|
+
timelineIndicatorVariants({
|
|
158
|
+
appearance: appearance ?? rootAppearance,
|
|
159
|
+
size,
|
|
160
|
+
}),
|
|
161
|
+
className,
|
|
162
|
+
)}
|
|
163
|
+
{...rest}
|
|
164
|
+
>
|
|
165
|
+
{children}
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
TimelineIndicator.displayName = "TimelineIndicator";
|
|
171
|
+
|
|
172
|
+
export function TimelineContent({
|
|
173
|
+
className,
|
|
174
|
+
children,
|
|
175
|
+
ref,
|
|
176
|
+
...rest
|
|
177
|
+
}: TimelineContentProps & { ref?: React.Ref<HTMLDivElement> }) {
|
|
178
|
+
const size = useTimelineSize();
|
|
179
|
+
const isLast = useTimelineIsLast();
|
|
180
|
+
return (
|
|
181
|
+
<div
|
|
182
|
+
ref={ref}
|
|
183
|
+
data-slot="timeline-content"
|
|
184
|
+
className={cn(
|
|
185
|
+
timelineContentVariants({ size }),
|
|
186
|
+
isLast && "pb-0",
|
|
187
|
+
className,
|
|
188
|
+
)}
|
|
189
|
+
{...rest}
|
|
190
|
+
>
|
|
191
|
+
{children}
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
TimelineContent.displayName = "TimelineContent";
|
|
197
|
+
|
|
198
|
+
export function TimelineTitle({
|
|
199
|
+
className,
|
|
200
|
+
ref,
|
|
201
|
+
...rest
|
|
202
|
+
}: TimelineTitleProps & { ref?: React.Ref<HTMLDivElement> }) {
|
|
203
|
+
const size = useTimelineSize();
|
|
204
|
+
return (
|
|
205
|
+
<div
|
|
206
|
+
ref={ref}
|
|
207
|
+
data-slot="timeline-title"
|
|
208
|
+
className={cn(timelineTitleVariants({ size }), className)}
|
|
209
|
+
{...rest}
|
|
210
|
+
/>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
TimelineTitle.displayName = "TimelineTitle";
|
|
215
|
+
|
|
216
|
+
export function TimelineDescription({
|
|
217
|
+
className,
|
|
218
|
+
ref,
|
|
219
|
+
...rest
|
|
220
|
+
}: TimelineDescriptionProps & { ref?: React.Ref<HTMLParagraphElement> }) {
|
|
221
|
+
const size = useTimelineSize();
|
|
222
|
+
return (
|
|
223
|
+
<p
|
|
224
|
+
ref={ref}
|
|
225
|
+
data-slot="timeline-description"
|
|
226
|
+
className={cn(timelineDescriptionVariants({ size }), className)}
|
|
227
|
+
{...rest}
|
|
228
|
+
/>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
TimelineDescription.displayName = "TimelineDescription";
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { createRef } from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Timeline,
|
|
7
|
+
TimelineContent,
|
|
8
|
+
TimelineDescription,
|
|
9
|
+
TimelineIndicator,
|
|
10
|
+
TimelineItem,
|
|
11
|
+
TimelineTitle,
|
|
12
|
+
} from "./timeline";
|
|
13
|
+
|
|
14
|
+
describe("Timeline", () => {
|
|
15
|
+
it("should expose displayName", () => {
|
|
16
|
+
expect(Timeline.displayName).toBe("Timeline");
|
|
17
|
+
expect(TimelineItem.displayName).toBe("TimelineItem");
|
|
18
|
+
expect(TimelineIndicator.displayName).toBe("TimelineIndicator");
|
|
19
|
+
expect(TimelineContent.displayName).toBe("TimelineContent");
|
|
20
|
+
expect(TimelineTitle.displayName).toBe("TimelineTitle");
|
|
21
|
+
expect(TimelineDescription.displayName).toBe("TimelineDescription");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should stamp data-slot on timeline root and use ordered list markup", () => {
|
|
25
|
+
render(
|
|
26
|
+
<Timeline>
|
|
27
|
+
<TimelineItem>
|
|
28
|
+
<TimelineIndicator />
|
|
29
|
+
<TimelineContent>
|
|
30
|
+
<TimelineTitle>One</TimelineTitle>
|
|
31
|
+
</TimelineContent>
|
|
32
|
+
</TimelineItem>
|
|
33
|
+
</Timeline>,
|
|
34
|
+
);
|
|
35
|
+
const root = document.querySelector('[data-slot="timeline"]');
|
|
36
|
+
expect(root).toBeTruthy();
|
|
37
|
+
expect(root?.tagName).toBe("OL");
|
|
38
|
+
expect(document.querySelector('[data-slot="timeline-item"]')?.tagName).toBe(
|
|
39
|
+
"LI",
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should apply default indicator appearance from the root", () => {
|
|
44
|
+
render(
|
|
45
|
+
<Timeline>
|
|
46
|
+
<TimelineItem>
|
|
47
|
+
<TimelineIndicator />
|
|
48
|
+
<TimelineContent>
|
|
49
|
+
<TimelineTitle>Cart</TimelineTitle>
|
|
50
|
+
</TimelineContent>
|
|
51
|
+
</TimelineItem>
|
|
52
|
+
</Timeline>,
|
|
53
|
+
);
|
|
54
|
+
const indicator = document.querySelector(
|
|
55
|
+
'[data-slot="timeline-indicator"]',
|
|
56
|
+
);
|
|
57
|
+
expect(indicator?.className).toContain(
|
|
58
|
+
"--zui-timeline-indicator-default-border",
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should inherit appearance from the Timeline root", () => {
|
|
63
|
+
render(
|
|
64
|
+
<Timeline appearance="sky">
|
|
65
|
+
<TimelineItem>
|
|
66
|
+
<TimelineIndicator />
|
|
67
|
+
<TimelineContent>
|
|
68
|
+
<TimelineTitle>Sky</TimelineTitle>
|
|
69
|
+
</TimelineContent>
|
|
70
|
+
</TimelineItem>
|
|
71
|
+
</Timeline>,
|
|
72
|
+
);
|
|
73
|
+
const indicator = document.querySelector(
|
|
74
|
+
'[data-slot="timeline-indicator"]',
|
|
75
|
+
);
|
|
76
|
+
expect(indicator?.className).toContain(
|
|
77
|
+
"--zui-timeline-indicator-sky-border",
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should let an indicator override the root appearance", () => {
|
|
82
|
+
render(
|
|
83
|
+
<Timeline appearance="sky">
|
|
84
|
+
<TimelineItem>
|
|
85
|
+
<TimelineIndicator />
|
|
86
|
+
<TimelineContent>
|
|
87
|
+
<TimelineTitle>Default sky</TimelineTitle>
|
|
88
|
+
</TimelineContent>
|
|
89
|
+
</TimelineItem>
|
|
90
|
+
<TimelineItem>
|
|
91
|
+
<TimelineIndicator appearance="rose" />
|
|
92
|
+
<TimelineContent>
|
|
93
|
+
<TimelineTitle>Override rose</TimelineTitle>
|
|
94
|
+
</TimelineContent>
|
|
95
|
+
</TimelineItem>
|
|
96
|
+
</Timeline>,
|
|
97
|
+
);
|
|
98
|
+
const indicators = document.querySelectorAll(
|
|
99
|
+
'[data-slot="timeline-indicator"]',
|
|
100
|
+
);
|
|
101
|
+
expect(indicators[0]?.className).toContain(
|
|
102
|
+
"--zui-timeline-indicator-sky-border",
|
|
103
|
+
);
|
|
104
|
+
expect(indicators[1]?.className).toContain(
|
|
105
|
+
"--zui-timeline-indicator-rose-border",
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should pass size from Timeline context to indicators", () => {
|
|
110
|
+
render(
|
|
111
|
+
<Timeline size="sm">
|
|
112
|
+
<TimelineItem>
|
|
113
|
+
<TimelineIndicator />
|
|
114
|
+
<TimelineContent>
|
|
115
|
+
<TimelineTitle>Small</TimelineTitle>
|
|
116
|
+
</TimelineContent>
|
|
117
|
+
</TimelineItem>
|
|
118
|
+
</Timeline>,
|
|
119
|
+
);
|
|
120
|
+
const indicator = document.querySelector(
|
|
121
|
+
'[data-slot="timeline-indicator"]',
|
|
122
|
+
);
|
|
123
|
+
expect(indicator?.className).toMatch(/size-5/);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should render a connector on every item except the last", () => {
|
|
127
|
+
render(
|
|
128
|
+
<Timeline>
|
|
129
|
+
<TimelineItem>
|
|
130
|
+
<TimelineIndicator />
|
|
131
|
+
<TimelineContent>
|
|
132
|
+
<TimelineTitle>First</TimelineTitle>
|
|
133
|
+
</TimelineContent>
|
|
134
|
+
</TimelineItem>
|
|
135
|
+
<TimelineItem>
|
|
136
|
+
<TimelineIndicator />
|
|
137
|
+
<TimelineContent>
|
|
138
|
+
<TimelineTitle>Second</TimelineTitle>
|
|
139
|
+
</TimelineContent>
|
|
140
|
+
</TimelineItem>
|
|
141
|
+
<TimelineItem>
|
|
142
|
+
<TimelineIndicator />
|
|
143
|
+
<TimelineContent>
|
|
144
|
+
<TimelineTitle>Third</TimelineTitle>
|
|
145
|
+
</TimelineContent>
|
|
146
|
+
</TimelineItem>
|
|
147
|
+
</Timeline>,
|
|
148
|
+
);
|
|
149
|
+
const connectors = document.querySelectorAll(
|
|
150
|
+
'[data-slot="timeline-connector"]',
|
|
151
|
+
);
|
|
152
|
+
expect(connectors).toHaveLength(2);
|
|
153
|
+
const items = document.querySelectorAll('[data-slot="timeline-item"]');
|
|
154
|
+
expect(items[2]?.hasAttribute("data-last")).toBe(true);
|
|
155
|
+
expect(items[0]?.hasAttribute("data-last")).toBe(false);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should drop bottom padding on the last item's content", () => {
|
|
159
|
+
render(
|
|
160
|
+
<Timeline>
|
|
161
|
+
<TimelineItem>
|
|
162
|
+
<TimelineIndicator />
|
|
163
|
+
<TimelineContent data-testid="first">
|
|
164
|
+
<TimelineTitle>First</TimelineTitle>
|
|
165
|
+
</TimelineContent>
|
|
166
|
+
</TimelineItem>
|
|
167
|
+
<TimelineItem>
|
|
168
|
+
<TimelineIndicator />
|
|
169
|
+
<TimelineContent data-testid="last">
|
|
170
|
+
<TimelineTitle>Last</TimelineTitle>
|
|
171
|
+
</TimelineContent>
|
|
172
|
+
</TimelineItem>
|
|
173
|
+
</Timeline>,
|
|
174
|
+
);
|
|
175
|
+
expect(screen.getByTestId("first").className).toMatch(/pb-6/);
|
|
176
|
+
expect(screen.getByTestId("last").className).toMatch(/pb-0/);
|
|
177
|
+
expect(screen.getByTestId("last").className).not.toMatch(/pb-6/);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("should render custom indicator children", () => {
|
|
181
|
+
render(
|
|
182
|
+
<Timeline>
|
|
183
|
+
<TimelineItem>
|
|
184
|
+
<TimelineIndicator>✓</TimelineIndicator>
|
|
185
|
+
<TimelineContent>
|
|
186
|
+
<TimelineTitle>Check</TimelineTitle>
|
|
187
|
+
</TimelineContent>
|
|
188
|
+
</TimelineItem>
|
|
189
|
+
</Timeline>,
|
|
190
|
+
);
|
|
191
|
+
expect(screen.getByText("✓")).toBeInTheDocument();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("should stamp data-slot on content, title and description", () => {
|
|
195
|
+
render(
|
|
196
|
+
<Timeline>
|
|
197
|
+
<TimelineItem>
|
|
198
|
+
<TimelineIndicator />
|
|
199
|
+
<TimelineContent data-testid="c">
|
|
200
|
+
<TimelineTitle data-testid="t">T</TimelineTitle>
|
|
201
|
+
<TimelineDescription data-testid="d">D</TimelineDescription>
|
|
202
|
+
</TimelineContent>
|
|
203
|
+
</TimelineItem>
|
|
204
|
+
</Timeline>,
|
|
205
|
+
);
|
|
206
|
+
expect(document.querySelector('[data-slot="timeline-content"]')).toBe(
|
|
207
|
+
screen.getByTestId("c"),
|
|
208
|
+
);
|
|
209
|
+
expect(document.querySelector('[data-slot="timeline-title"]')).toBe(
|
|
210
|
+
screen.getByTestId("t"),
|
|
211
|
+
);
|
|
212
|
+
expect(document.querySelector('[data-slot="timeline-description"]')).toBe(
|
|
213
|
+
screen.getByTestId("d"),
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("should forward ref on Timeline", () => {
|
|
218
|
+
const ref = createRef<HTMLOListElement>();
|
|
219
|
+
render(
|
|
220
|
+
<Timeline ref={ref}>
|
|
221
|
+
<TimelineItem>
|
|
222
|
+
<TimelineIndicator />
|
|
223
|
+
<TimelineContent>
|
|
224
|
+
<TimelineTitle>A</TimelineTitle>
|
|
225
|
+
</TimelineContent>
|
|
226
|
+
</TimelineItem>
|
|
227
|
+
</Timeline>,
|
|
228
|
+
);
|
|
229
|
+
expect(ref.current?.getAttribute("data-slot")).toBe("timeline");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("should forward ref on TimelineItem", () => {
|
|
233
|
+
const ref = createRef<HTMLLIElement>();
|
|
234
|
+
render(
|
|
235
|
+
<Timeline>
|
|
236
|
+
<TimelineItem ref={ref}>
|
|
237
|
+
<TimelineIndicator />
|
|
238
|
+
<TimelineContent>
|
|
239
|
+
<TimelineTitle>A</TimelineTitle>
|
|
240
|
+
</TimelineContent>
|
|
241
|
+
</TimelineItem>
|
|
242
|
+
</Timeline>,
|
|
243
|
+
);
|
|
244
|
+
expect(ref.current?.getAttribute("data-slot")).toBe("timeline-item");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("should throw when TimelineItem is used outside Timeline", () => {
|
|
248
|
+
expect(() =>
|
|
249
|
+
render(
|
|
250
|
+
<TimelineItem>
|
|
251
|
+
<TimelineIndicator />
|
|
252
|
+
</TimelineItem>,
|
|
253
|
+
),
|
|
254
|
+
).toThrow(/must be used within <Timeline>/);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("should throw when TimelineIndicator is used outside Timeline", () => {
|
|
258
|
+
expect(() => render(<TimelineIndicator />)).toThrow(
|
|
259
|
+
/must be used within <Timeline>/,
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// timeline.tsx — default static entry (no framer-motion)
|
|
2
|
+
import {
|
|
3
|
+
Timeline as TimelineBase,
|
|
4
|
+
TimelineContent,
|
|
5
|
+
TimelineDescription,
|
|
6
|
+
TimelineIndicator,
|
|
7
|
+
TimelineItem,
|
|
8
|
+
TimelineTitle,
|
|
9
|
+
} from "./timeline-base";
|
|
10
|
+
import type { TimelineProps } from "./types";
|
|
11
|
+
|
|
12
|
+
export function Timeline(props: TimelineProps) {
|
|
13
|
+
return <TimelineBase {...props} />;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
Timeline.displayName = "Timeline";
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
TimelineContent,
|
|
20
|
+
TimelineDescription,
|
|
21
|
+
TimelineIndicator,
|
|
22
|
+
TimelineItem,
|
|
23
|
+
TimelineTitle,
|
|
24
|
+
};
|