@syscore/ui-library 1.27.3 → 2.0.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.
@@ -0,0 +1,181 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ // ─── Context ──────────────────────────────────────────────────────────────────
5
+
6
+ interface TimelineContextValue {
7
+ direction: "vertical" | "horizontal";
8
+ side: "start" | "end" | "alternate";
9
+ align: "center" | "start";
10
+ density: "default" | "compact";
11
+ }
12
+
13
+ const TimelineContext = React.createContext<TimelineContextValue>({
14
+ direction: "vertical",
15
+ side: "alternate",
16
+ align: "center",
17
+ density: "default",
18
+ });
19
+
20
+ // ─── Timeline Root ────────────────────────────────────────────────────────────
21
+
22
+ export interface TimelineProps extends React.HTMLAttributes<HTMLDivElement> {
23
+ /**
24
+ * Layout direction.
25
+ * - "vertical" → items stack top-to-bottom (default)
26
+ * - "horizontal" → items flow left-to-right
27
+ */
28
+ direction?: "vertical" | "horizontal";
29
+ /**
30
+ * Which side content renders on relative to the stem.
31
+ * - "alternate" → alternates left / right (default)
32
+ * - "end" → all content on the right (vertical) or below (horizontal)
33
+ * - "start" → all content on the left (vertical) or above (horizontal)
34
+ */
35
+ side?: "start" | "end" | "alternate";
36
+ /**
37
+ * Vertical alignment of content inside each item.
38
+ * - "center" → content is centered on the dot (default)
39
+ * - "start" → content aligns to the top of the dot
40
+ */
41
+ align?: "center" | "start";
42
+ /**
43
+ * Reduces spacing between items.
44
+ */
45
+ density?: "default" | "compact";
46
+ }
47
+
48
+ const Timeline = React.forwardRef<HTMLDivElement, TimelineProps>(
49
+ (
50
+ {
51
+ className,
52
+ children,
53
+ direction = "vertical",
54
+ side = "alternate",
55
+ align = "center",
56
+ density = "default",
57
+ ...props
58
+ },
59
+ ref
60
+ ) => (
61
+ <TimelineContext.Provider value={{ direction, side, align, density }}>
62
+ <div
63
+ ref={ref}
64
+ className={cn(
65
+ "timeline",
66
+ direction === "horizontal" && "timeline--horizontal",
67
+ `timeline--side-${side}`,
68
+ `timeline--align-${align}`,
69
+ density === "compact" && "timeline--compact",
70
+ className
71
+ )}
72
+ {...props}
73
+ >
74
+ {children}
75
+ </div>
76
+ </TimelineContext.Provider>
77
+ )
78
+ );
79
+ Timeline.displayName = "Timeline";
80
+
81
+ // ─── TimelineItem ─────────────────────────────────────────────────────────────
82
+
83
+ export interface TimelineItemProps extends React.HTMLAttributes<HTMLDivElement> {
84
+ /**
85
+ * Background color of the dot — any CSS color or var() token.
86
+ */
87
+ dotColor?: string;
88
+ /**
89
+ * Size of the dot.
90
+ * - "x-small" → 12px
91
+ * - "small" → 16px (default)
92
+ * - "default" → 24px
93
+ * - "large" → 32px
94
+ * - "x-large" → 40px
95
+ */
96
+ size?: "x-small" | "small" | "default" | "large" | "x-large";
97
+ /**
98
+ * When true the dot background is filled with `dotColor`
99
+ * (instead of just a border ring).
100
+ */
101
+ fillDot?: boolean;
102
+ /**
103
+ * Completely hides the dot and stem connector.
104
+ */
105
+ hideDot?: boolean;
106
+ /**
107
+ * Icon rendered inside the dot. Pass any React node.
108
+ * For best results use with size="large" or size="x-large".
109
+ */
110
+ icon?: React.ReactNode;
111
+ /**
112
+ * Content rendered on the opposite side of the stem from the main content.
113
+ * Equivalent to Vuetify's v-slot:opposite.
114
+ */
115
+ opposite?: React.ReactNode;
116
+ }
117
+
118
+ const TimelineItem = React.forwardRef<HTMLDivElement, TimelineItemProps>(
119
+ (
120
+ {
121
+ className,
122
+ children,
123
+ dotColor,
124
+ size = "small",
125
+ fillDot = false,
126
+ hideDot = false,
127
+ icon,
128
+ opposite,
129
+ ...props
130
+ },
131
+ ref
132
+ ) => {
133
+ const { align } = React.useContext(TimelineContext);
134
+
135
+ return (
136
+ <div
137
+ ref={ref}
138
+ className={cn(
139
+ "timeline-item",
140
+ `timeline-item--size-${size}`,
141
+ fillDot && "timeline-item--fill-dot",
142
+ hideDot && "timeline-item--hide-dot",
143
+ align === "start" && "timeline-item--align-start",
144
+ className
145
+ )}
146
+ {...props}
147
+ >
148
+ {/* Opposite slot */}
149
+ <div className="timeline-item-opposite">
150
+ {opposite}
151
+ </div>
152
+
153
+ {/* Stem + dot column */}
154
+ <div className="timeline-item-stem">
155
+ <div className="timeline-item-stem-line timeline-item-stem-line--before" />
156
+ {!hideDot && (
157
+ <div
158
+ className="timeline-item-dot"
159
+ style={dotColor ? { backgroundColor: dotColor, borderColor: dotColor } : undefined}
160
+ >
161
+ {icon && (
162
+ <span className="timeline-item-dot-icon">{icon}</span>
163
+ )}
164
+ </div>
165
+ )}
166
+ <div className="timeline-item-stem-line timeline-item-stem-line--after" />
167
+ </div>
168
+
169
+ {/* Main content */}
170
+ <div className="timeline-item-body">
171
+ {children}
172
+ </div>
173
+ </div>
174
+ );
175
+ }
176
+ );
177
+ TimelineItem.displayName = "TimelineItem";
178
+
179
+ // ─── Exports ──────────────────────────────────────────────────────────────────
180
+
181
+ export { Timeline, TimelineItem };