@boyernick/standard-ui-react 0.1.1-canary.20 → 0.1.1-canary.22
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/package.json +1 -1
- package/src/index.ts +25 -0
- package/src/timeline.tsx +321 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -891,6 +891,31 @@ export {
|
|
|
891
891
|
type TickerProps,
|
|
892
892
|
type TickerItemProps,
|
|
893
893
|
} from "./ticker";
|
|
894
|
+
export {
|
|
895
|
+
Timeline,
|
|
896
|
+
TimelineTrack,
|
|
897
|
+
TimelineItem,
|
|
898
|
+
TimelineMarker,
|
|
899
|
+
TimelineTime,
|
|
900
|
+
TimelineContent,
|
|
901
|
+
TimelineTitle,
|
|
902
|
+
TimelineDescription,
|
|
903
|
+
TimelineMedia,
|
|
904
|
+
timelineVariants,
|
|
905
|
+
timelineTrackVariants,
|
|
906
|
+
timelineItemVariants,
|
|
907
|
+
timelineMarkerVariants,
|
|
908
|
+
timelineTimeVariants,
|
|
909
|
+
type TimelineProps,
|
|
910
|
+
type TimelineTrackProps,
|
|
911
|
+
type TimelineItemProps,
|
|
912
|
+
type TimelineMarkerProps,
|
|
913
|
+
type TimelineTimeProps,
|
|
914
|
+
type TimelineContentProps,
|
|
915
|
+
type TimelineTitleProps,
|
|
916
|
+
type TimelineDescriptionProps,
|
|
917
|
+
type TimelineMediaProps,
|
|
918
|
+
} from "./timeline";
|
|
894
919
|
export {
|
|
895
920
|
Table,
|
|
896
921
|
TableHeader,
|
package/src/timeline.tsx
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
+
import {
|
|
5
|
+
createContext,
|
|
6
|
+
type ComponentProps,
|
|
7
|
+
type KeyboardEvent,
|
|
8
|
+
type WheelEvent,
|
|
9
|
+
useContext,
|
|
10
|
+
} from "react"
|
|
11
|
+
import { cn } from "./lib/cn"
|
|
12
|
+
|
|
13
|
+
const timelineVariants = cva("group/timeline relative text-fg-primary", {
|
|
14
|
+
variants: {
|
|
15
|
+
orientation: {
|
|
16
|
+
vertical: "w-full",
|
|
17
|
+
horizontal:
|
|
18
|
+
"w-full snap-x snap-proximity overflow-x-auto overscroll-x-contain rounded-xl border border-border-primary bg-surface px-5 py-6 scroll-smooth focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-offset-1 focus-visible:ring-offset-background-primary focus-visible:ring-ring/20 focus-visible:outline-none",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
orientation: "vertical",
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const timelineTrackVariants = cva("relative m-0 list-none p-0", {
|
|
27
|
+
variants: {
|
|
28
|
+
orientation: {
|
|
29
|
+
vertical: "ml-1.5 border-l border-dashed border-border-primary",
|
|
30
|
+
horizontal:
|
|
31
|
+
"grid min-w-max auto-cols-64 grid-flow-col before:absolute before:inset-x-0 before:top-10 before:border-t before:border-dashed before:border-border-primary before:content-['']",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
defaultVariants: {
|
|
35
|
+
orientation: "vertical",
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const timelineItemVariants = cva("relative", {
|
|
40
|
+
variants: {
|
|
41
|
+
orientation: {
|
|
42
|
+
vertical: "pb-8 pl-7 last:pb-0",
|
|
43
|
+
horizontal: "w-64 snap-start pt-16 pr-8 last:pr-0",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
defaultVariants: {
|
|
47
|
+
orientation: "vertical",
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const timelineMarkerVariants = cva(
|
|
52
|
+
"absolute z-[1] size-2.5 rounded-full border-2 border-surface shadow-hairline",
|
|
53
|
+
{
|
|
54
|
+
variants: {
|
|
55
|
+
orientation: {
|
|
56
|
+
vertical: "top-1 left-0 -translate-x-1/2",
|
|
57
|
+
horizontal: "top-10 left-0 -translate-y-1/2",
|
|
58
|
+
},
|
|
59
|
+
tone: {
|
|
60
|
+
neutral: "bg-fg-tertiary",
|
|
61
|
+
accent: "bg-brand-primary",
|
|
62
|
+
info: "bg-status-info",
|
|
63
|
+
success: "bg-status-success",
|
|
64
|
+
warning: "bg-status-warning",
|
|
65
|
+
critical: "bg-status-critical",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
defaultVariants: {
|
|
69
|
+
orientation: "vertical",
|
|
70
|
+
tone: "neutral",
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const timelineTimeVariants = cva(
|
|
76
|
+
"text-xs-strong block text-fg-tertiary tabular-nums",
|
|
77
|
+
{
|
|
78
|
+
variants: {
|
|
79
|
+
orientation: {
|
|
80
|
+
vertical: "mb-1",
|
|
81
|
+
horizontal: "absolute top-0 left-0",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
defaultVariants: {
|
|
85
|
+
orientation: "vertical",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
type TimelineOrientation = "horizontal" | "vertical"
|
|
91
|
+
|
|
92
|
+
const TimelineContext = createContext<TimelineOrientation>("vertical")
|
|
93
|
+
|
|
94
|
+
export type TimelineProps = ComponentProps<"div"> &
|
|
95
|
+
VariantProps<typeof timelineVariants> & {
|
|
96
|
+
/** Distance moved by the horizontal timeline's arrow keys. */
|
|
97
|
+
scrollStep?: number
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type TimelineTrackProps = ComponentProps<"ol">
|
|
101
|
+
export type TimelineItemProps = ComponentProps<"li">
|
|
102
|
+
export type TimelineMarkerProps = ComponentProps<"span"> &
|
|
103
|
+
VariantProps<typeof timelineMarkerVariants>
|
|
104
|
+
export type TimelineTimeProps = ComponentProps<"time">
|
|
105
|
+
export type TimelineContentProps = ComponentProps<"div">
|
|
106
|
+
export type TimelineTitleProps = ComponentProps<"h3">
|
|
107
|
+
export type TimelineDescriptionProps = ComponentProps<"p">
|
|
108
|
+
export type TimelineMediaProps = ComponentProps<"div">
|
|
109
|
+
|
|
110
|
+
export const Timeline = ({
|
|
111
|
+
orientation = "vertical",
|
|
112
|
+
scrollStep = 256,
|
|
113
|
+
className,
|
|
114
|
+
children,
|
|
115
|
+
tabIndex,
|
|
116
|
+
onKeyDown,
|
|
117
|
+
onWheel,
|
|
118
|
+
"aria-label": ariaLabel = "Timeline",
|
|
119
|
+
...props
|
|
120
|
+
}: TimelineProps) => {
|
|
121
|
+
const resolvedOrientation = orientation ?? "vertical"
|
|
122
|
+
|
|
123
|
+
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
124
|
+
onKeyDown?.(event)
|
|
125
|
+
if (
|
|
126
|
+
event.defaultPrevented ||
|
|
127
|
+
resolvedOrientation !== "horizontal" ||
|
|
128
|
+
event.target !== event.currentTarget
|
|
129
|
+
) {
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const node = event.currentTarget
|
|
134
|
+
const prefersReducedMotion = window.matchMedia(
|
|
135
|
+
"(prefers-reduced-motion: reduce)",
|
|
136
|
+
).matches
|
|
137
|
+
const behavior = prefersReducedMotion ? "auto" : "smooth"
|
|
138
|
+
|
|
139
|
+
if (event.key === "ArrowRight" || event.key === "ArrowLeft") {
|
|
140
|
+
event.preventDefault()
|
|
141
|
+
node.scrollBy({
|
|
142
|
+
left: event.key === "ArrowRight" ? scrollStep : -scrollStep,
|
|
143
|
+
behavior,
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (event.key === "Home" || event.key === "End") {
|
|
148
|
+
event.preventDefault()
|
|
149
|
+
node.scrollTo({
|
|
150
|
+
left: event.key === "Home" ? 0 : node.scrollWidth,
|
|
151
|
+
behavior,
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const handleWheel = (event: WheelEvent<HTMLDivElement>) => {
|
|
157
|
+
onWheel?.(event)
|
|
158
|
+
if (
|
|
159
|
+
event.defaultPrevented ||
|
|
160
|
+
resolvedOrientation !== "horizontal" ||
|
|
161
|
+
Math.abs(event.deltaX) >= Math.abs(event.deltaY)
|
|
162
|
+
) {
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const node = event.currentTarget
|
|
167
|
+
const maxScrollLeft = node.scrollWidth - node.clientWidth
|
|
168
|
+
const canScroll =
|
|
169
|
+
event.deltaY > 0 ? node.scrollLeft < maxScrollLeft : node.scrollLeft > 0
|
|
170
|
+
|
|
171
|
+
if (canScroll) {
|
|
172
|
+
event.preventDefault()
|
|
173
|
+
node.scrollLeft += event.deltaY
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<TimelineContext.Provider value={resolvedOrientation}>
|
|
179
|
+
<div
|
|
180
|
+
data-slot="timeline"
|
|
181
|
+
data-orientation={resolvedOrientation}
|
|
182
|
+
role="region"
|
|
183
|
+
aria-label={ariaLabel}
|
|
184
|
+
tabIndex={
|
|
185
|
+
tabIndex ?? (resolvedOrientation === "horizontal" ? 0 : undefined)
|
|
186
|
+
}
|
|
187
|
+
className={cn(
|
|
188
|
+
timelineVariants({ orientation: resolvedOrientation }),
|
|
189
|
+
className,
|
|
190
|
+
)}
|
|
191
|
+
onKeyDown={handleKeyDown}
|
|
192
|
+
onWheel={handleWheel}
|
|
193
|
+
{...props}
|
|
194
|
+
>
|
|
195
|
+
{children}
|
|
196
|
+
</div>
|
|
197
|
+
</TimelineContext.Provider>
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export const TimelineTrack = ({
|
|
202
|
+
className,
|
|
203
|
+
...props
|
|
204
|
+
}: TimelineTrackProps) => {
|
|
205
|
+
const orientation = useContext(TimelineContext)
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<ol
|
|
209
|
+
data-slot="timeline-track"
|
|
210
|
+
data-orientation={orientation}
|
|
211
|
+
className={cn(timelineTrackVariants({ orientation }), className)}
|
|
212
|
+
{...props}
|
|
213
|
+
/>
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export const TimelineItem = ({
|
|
218
|
+
className,
|
|
219
|
+
...props
|
|
220
|
+
}: TimelineItemProps) => {
|
|
221
|
+
const orientation = useContext(TimelineContext)
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<li
|
|
225
|
+
data-slot="timeline-item"
|
|
226
|
+
data-orientation={orientation}
|
|
227
|
+
className={cn(timelineItemVariants({ orientation }), className)}
|
|
228
|
+
{...props}
|
|
229
|
+
/>
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export const TimelineMarker = ({
|
|
234
|
+
className,
|
|
235
|
+
tone,
|
|
236
|
+
...props
|
|
237
|
+
}: TimelineMarkerProps) => {
|
|
238
|
+
const orientation = useContext(TimelineContext)
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<span
|
|
242
|
+
data-slot="timeline-marker"
|
|
243
|
+
aria-hidden
|
|
244
|
+
className={cn(
|
|
245
|
+
timelineMarkerVariants({ orientation, tone }),
|
|
246
|
+
className,
|
|
247
|
+
)}
|
|
248
|
+
{...props}
|
|
249
|
+
/>
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export const TimelineTime = ({
|
|
254
|
+
className,
|
|
255
|
+
...props
|
|
256
|
+
}: TimelineTimeProps) => {
|
|
257
|
+
const orientation = useContext(TimelineContext)
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<time
|
|
261
|
+
data-slot="timeline-time"
|
|
262
|
+
className={cn(timelineTimeVariants({ orientation }), className)}
|
|
263
|
+
{...props}
|
|
264
|
+
/>
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export const TimelineContent = ({
|
|
269
|
+
className,
|
|
270
|
+
...props
|
|
271
|
+
}: TimelineContentProps) => (
|
|
272
|
+
<div
|
|
273
|
+
data-slot="timeline-content"
|
|
274
|
+
className={cn("min-w-0", className)}
|
|
275
|
+
{...props}
|
|
276
|
+
/>
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
export const TimelineTitle = ({
|
|
280
|
+
className,
|
|
281
|
+
...props
|
|
282
|
+
}: TimelineTitleProps) => (
|
|
283
|
+
<h3
|
|
284
|
+
data-slot="timeline-title"
|
|
285
|
+
className={cn("text-sm-strong text-fg-primary", className)}
|
|
286
|
+
{...props}
|
|
287
|
+
/>
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
export const TimelineDescription = ({
|
|
291
|
+
className,
|
|
292
|
+
...props
|
|
293
|
+
}: TimelineDescriptionProps) => (
|
|
294
|
+
<p
|
|
295
|
+
data-slot="timeline-description"
|
|
296
|
+
className={cn("text-sm mt-1 leading-relaxed text-fg-secondary", className)}
|
|
297
|
+
{...props}
|
|
298
|
+
/>
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
export const TimelineMedia = ({
|
|
302
|
+
className,
|
|
303
|
+
...props
|
|
304
|
+
}: TimelineMediaProps) => (
|
|
305
|
+
<div
|
|
306
|
+
data-slot="timeline-media"
|
|
307
|
+
className={cn(
|
|
308
|
+
"mt-4 overflow-hidden rounded-xl border border-border-primary bg-background-secondary [&>img]:block [&>img]:aspect-4/3 [&>img]:w-full [&>img]:object-cover",
|
|
309
|
+
className,
|
|
310
|
+
)}
|
|
311
|
+
{...props}
|
|
312
|
+
/>
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
export {
|
|
316
|
+
timelineVariants,
|
|
317
|
+
timelineItemVariants,
|
|
318
|
+
timelineMarkerVariants,
|
|
319
|
+
timelineTimeVariants,
|
|
320
|
+
timelineTrackVariants,
|
|
321
|
+
}
|