@boyernick/standard-ui-react 0.1.1-canary.9 → 0.2.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/package.json +5 -3
- package/src/accordion.tsx +5 -2
- package/src/alert-dialog.tsx +1 -1
- package/src/attachment.tsx +5 -2
- package/src/autocomplete.tsx +4 -3
- package/src/block-editor.tsx +1747 -0
- package/src/brand.tsx +2 -2
- package/src/breadcrumb.tsx +14 -7
- package/src/button.tsx +22 -11
- package/src/calendar.tsx +6 -3
- package/src/carousel.tsx +220 -53
- package/src/checkbox.tsx +3 -2
- package/src/code-block.tsx +66 -5
- package/src/collapsible.tsx +4 -2
- package/src/combobox.tsx +4 -3
- package/src/command.tsx +27 -12
- package/src/dialog.tsx +33 -10
- package/src/empty.tsx +4 -4
- package/src/field.tsx +4 -3
- package/src/filter-group.tsx +143 -0
- package/src/icons.tsx +28 -1
- package/src/illustrations.tsx +219 -141
- package/src/index.ts +238 -18
- package/src/input.tsx +8 -5
- package/src/kbd.tsx +10 -4
- package/src/lib/focus.ts +41 -0
- package/src/lib/popup.ts +1 -1
- package/src/lifeline/company-icon.tsx +71 -0
- package/src/lifeline/index.ts +42 -0
- package/src/lifeline/lifeline-data.ts +97 -0
- package/src/lifeline/lifeline-desktop.tsx +204 -0
- package/src/lifeline/lifeline-event.tsx +108 -0
- package/src/lifeline/lifeline-fireworks.tsx +302 -0
- package/src/lifeline/lifeline-hover-image.tsx +269 -0
- package/src/lifeline/lifeline-icons.tsx +39 -0
- package/src/lifeline/lifeline-intro-timing.ts +105 -0
- package/src/lifeline/lifeline-labels.tsx +24 -0
- package/src/lifeline/lifeline-layout.ts +1 -0
- package/src/lifeline/lifeline-legend.tsx +31 -0
- package/src/lifeline/lifeline-lightbox.tsx +309 -0
- package/src/lifeline/lifeline-marker.tsx +183 -0
- package/src/lifeline/lifeline-people.tsx +99 -0
- package/src/lifeline/lifeline-photos.tsx +366 -0
- package/src/lifeline/lifeline-shell.tsx +135 -0
- package/src/lifeline/lifeline-utils.ts +83 -0
- package/src/lifeline/lifeline-vertical.tsx +482 -0
- package/src/lifeline/lifeline.tsx +69 -0
- package/src/lifeline/types.ts +112 -0
- package/src/lifeline/use-lifeline-intro.ts +130 -0
- package/src/lifeline/use-lifeline-scroll.ts +1011 -0
- package/src/lifeline/use-lifeline-vertical-scroll.ts +271 -0
- package/src/menubar.tsx +9 -2
- package/src/minimap.tsx +296 -0
- package/src/modal.tsx +608 -0
- package/src/navigation-menu.tsx +338 -67
- package/src/number-field.tsx +336 -61
- package/src/otp-field.tsx +4 -3
- package/src/pagination.tsx +262 -42
- package/src/password-protection.tsx +345 -0
- package/src/popover.tsx +1 -1
- package/src/progress.tsx +5 -0
- package/src/questionnaire.tsx +284 -0
- package/src/radio.tsx +4 -2
- package/src/scroll-area.tsx +4 -1
- package/src/select.tsx +5 -2
- package/src/sidebar.tsx +113 -28
- package/src/slider.tsx +66 -6
- package/src/sounds.tsx +6 -10
- package/src/spinner.tsx +105 -32
- package/src/switch.tsx +4 -1
- package/src/table.tsx +82 -15
- package/src/tabs.tsx +189 -38
- package/src/text-animate.tsx +71 -28
- package/src/textarea.tsx +6 -1
- package/src/timeline.tsx +378 -0
- package/src/toast.tsx +214 -35
- package/src/toggle.tsx +4 -2
- package/src/toolbar.tsx +12 -7
- package/src/tooltip.tsx +43 -3
- package/src/video-player.tsx +396 -127
- package/src/image-modal.tsx +0 -110
- package/src/markdown-editor.tsx +0 -302
package/src/text-animate.tsx
CHANGED
|
@@ -8,12 +8,18 @@ import {
|
|
|
8
8
|
} from "react"
|
|
9
9
|
import { cn } from "./lib/cn"
|
|
10
10
|
|
|
11
|
+
/** How many glyphs are scrambling at once. The window travels the line; behind
|
|
12
|
+
* it the text is settled, ahead of it nothing is drawn yet. 8 is the reference
|
|
13
|
+
* default from baffle.js, which this effect follows. */
|
|
14
|
+
const DECODE_WINDOW = 8
|
|
15
|
+
|
|
11
16
|
const DECODE_GLYPHS =
|
|
12
17
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#@$%&*"
|
|
13
18
|
|
|
14
19
|
export type TextAnimateEffect =
|
|
15
20
|
| "typewriter"
|
|
16
21
|
| "decode"
|
|
22
|
+
| "shimmer"
|
|
17
23
|
| "fade"
|
|
18
24
|
| "blur"
|
|
19
25
|
|
|
@@ -32,20 +38,25 @@ export type TextAnimateProps = HTMLAttributes<HTMLSpanElement> & {
|
|
|
32
38
|
export const TextAnimate = ({
|
|
33
39
|
text,
|
|
34
40
|
effect = "typewriter",
|
|
35
|
-
speed =
|
|
41
|
+
speed = 60,
|
|
36
42
|
delay = 0,
|
|
37
43
|
replay = true,
|
|
38
44
|
as: Tag = "span",
|
|
39
45
|
className,
|
|
40
46
|
...props
|
|
41
47
|
}: TextAnimateProps) => {
|
|
42
|
-
const [output, setOutput] = useState(
|
|
43
|
-
|
|
48
|
+
const [output, setOutput] = useState(
|
|
49
|
+
effect === "typewriter" || effect === "decode" ? "" : text,
|
|
50
|
+
)
|
|
51
|
+
const [done, setDone] = useState(
|
|
52
|
+
effect === "fade" || effect === "blur" || effect === "shimmer",
|
|
53
|
+
)
|
|
44
54
|
const chars = useMemo(() => [...text], [text])
|
|
45
55
|
|
|
46
56
|
useEffect(() => {
|
|
47
57
|
let cancelled = false
|
|
48
58
|
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
59
|
+
let frameId: number | undefined
|
|
49
60
|
|
|
50
61
|
const start = () => {
|
|
51
62
|
if (
|
|
@@ -57,7 +68,7 @@ export const TextAnimate = ({
|
|
|
57
68
|
return
|
|
58
69
|
}
|
|
59
70
|
|
|
60
|
-
if (effect === "fade" || effect === "blur") {
|
|
71
|
+
if (effect === "fade" || effect === "blur" || effect === "shimmer") {
|
|
61
72
|
setOutput(text)
|
|
62
73
|
setDone(true)
|
|
63
74
|
return
|
|
@@ -82,30 +93,59 @@ export const TextAnimate = ({
|
|
|
82
93
|
return
|
|
83
94
|
}
|
|
84
95
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
96
|
+
// Decode follows baffle.js's `reveal`, which is the reference for this
|
|
97
|
+
// effect: a fixed-width window of scrambled glyphs travels left to
|
|
98
|
+
// right, the real text sits behind it, and nothing at all is drawn ahead
|
|
99
|
+
// of it — so the line grows in with a churning leading edge rather than
|
|
100
|
+
// resolving out of a full-width block of noise.
|
|
101
|
+
//
|
|
102
|
+
// It runs on rAF with a time accumulator rather than `setTimeout(speed)`.
|
|
103
|
+
// A 28ms timer does not divide into a ~16.7ms frame, so steps land on
|
|
104
|
+
// uneven frames and the text visibly stutters; accumulating elapsed time
|
|
105
|
+
// keeps the reference's cadence while landing each step on a real frame.
|
|
106
|
+
const slots = chars.reduce<number[]>((acc, char, index) => {
|
|
107
|
+
if (char !== " ") acc.push(index)
|
|
108
|
+
return acc
|
|
109
|
+
}, [])
|
|
110
|
+
|
|
111
|
+
// The window starts off the front of the line so the first glyph is
|
|
112
|
+
// already scrambling as it arrives.
|
|
113
|
+
let position = -DECODE_WINDOW
|
|
114
|
+
let previousStep = -Infinity
|
|
115
|
+
const startedAt = performance.now()
|
|
116
|
+
|
|
117
|
+
const frame = (now: number) => {
|
|
91
118
|
if (cancelled) return
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
119
|
+
|
|
120
|
+
const step = Math.floor((now - startedAt) / speed) - DECODE_WINDOW
|
|
121
|
+
if (step !== previousStep) {
|
|
122
|
+
previousStep = step
|
|
123
|
+
position = step
|
|
124
|
+
|
|
125
|
+
if (position > slots.length) {
|
|
126
|
+
setOutput(text)
|
|
127
|
+
setDone(true)
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const next = [...chars]
|
|
132
|
+
for (let p = Math.max(position, 0); p < slots.length; p += 1) {
|
|
133
|
+
next[slots[p]] =
|
|
134
|
+
p < position + DECODE_WINDOW
|
|
135
|
+
? DECODE_GLYPHS[
|
|
136
|
+
Math.floor(Math.random() * DECODE_GLYPHS.length)
|
|
137
|
+
]
|
|
138
|
+
: ""
|
|
139
|
+
}
|
|
140
|
+
setOutput(next.join(""))
|
|
106
141
|
}
|
|
142
|
+
|
|
143
|
+
frameId = requestAnimationFrame(frame)
|
|
107
144
|
}
|
|
108
|
-
|
|
145
|
+
|
|
146
|
+
setOutput("")
|
|
147
|
+
frameId = requestAnimationFrame(frame)
|
|
148
|
+
return
|
|
109
149
|
}
|
|
110
150
|
|
|
111
151
|
timeoutId = setTimeout(start, delay)
|
|
@@ -113,18 +153,21 @@ export const TextAnimate = ({
|
|
|
113
153
|
return () => {
|
|
114
154
|
cancelled = true
|
|
115
155
|
if (timeoutId) clearTimeout(timeoutId)
|
|
156
|
+
if (frameId) cancelAnimationFrame(frameId)
|
|
116
157
|
}
|
|
117
158
|
}, [chars, delay, effect, replay, speed, text])
|
|
118
159
|
|
|
119
|
-
if (effect === "fade" || effect === "blur") {
|
|
160
|
+
if (effect === "fade" || effect === "blur" || effect === "shimmer") {
|
|
120
161
|
return (
|
|
121
162
|
<Tag
|
|
122
163
|
className={cn(
|
|
123
164
|
"inline-block text-fg-primary",
|
|
124
165
|
effect === "fade" &&
|
|
125
|
-
"animate-[text-fade-in_0.
|
|
166
|
+
"animate-[text-fade-in_0.9s_var(--ease-enter)_both] motion-reduce:animate-none",
|
|
126
167
|
effect === "blur" &&
|
|
127
|
-
"animate-[text-blur-
|
|
168
|
+
"animate-[text-blur-in_1s_var(--ease-enter)_both] motion-reduce:animate-none",
|
|
169
|
+
effect === "shimmer" &&
|
|
170
|
+
"bg-[linear-gradient(110deg,var(--color-fg-tertiary)_35%,var(--color-fg-primary)_50%,var(--color-fg-tertiary)_65%)] bg-[length:250%_100%] bg-clip-text text-transparent animate-[text-shimmer_3s_linear_infinite] motion-reduce:animate-none motion-reduce:bg-none motion-reduce:text-fg-primary",
|
|
128
171
|
className,
|
|
129
172
|
)}
|
|
130
173
|
style={{ animationDelay: `${delay}ms` }}
|
package/src/textarea.tsx
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
2
2
|
import type { TextareaHTMLAttributes } from "react"
|
|
3
3
|
import { cn } from "./lib/cn"
|
|
4
|
+
import { focusRing, focusRingInvalid } from "./lib/focus"
|
|
4
5
|
|
|
5
6
|
const textareaVariants = cva(
|
|
6
|
-
|
|
7
|
+
cn(
|
|
8
|
+
"flex min-h-20 w-full cursor-text resize-y rounded-md px-3 py-2 text-sm text-fg-primary transition-[color,box-shadow] duration-[var(--duration-sm)] ease-enter placeholder:text-fg-quaternary disabled:cursor-not-allowed disabled:opacity-50",
|
|
9
|
+
focusRing,
|
|
10
|
+
focusRingInvalid,
|
|
11
|
+
),
|
|
7
12
|
{
|
|
8
13
|
variants: {
|
|
9
14
|
variant: {
|
package/src/timeline.tsx
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
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
|
+
import { focusRing, focusRingBorder } from "./lib/focus"
|
|
13
|
+
import { motion } from "./lib/motion"
|
|
14
|
+
|
|
15
|
+
const timelineVariants = cva("group/timeline relative text-fg-primary", {
|
|
16
|
+
variants: {
|
|
17
|
+
orientation: {
|
|
18
|
+
vertical: "w-full",
|
|
19
|
+
horizontal: cn(
|
|
20
|
+
"w-full min-h-0 overflow-x-auto overscroll-x-contain scroll-smooth [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden focus-visible:rounded-md",
|
|
21
|
+
focusRingBorder,
|
|
22
|
+
focusRing,
|
|
23
|
+
),
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultVariants: {
|
|
27
|
+
orientation: "vertical",
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const timelineTrackVariants = cva("relative m-0 list-none p-0", {
|
|
32
|
+
variants: {
|
|
33
|
+
orientation: {
|
|
34
|
+
vertical:
|
|
35
|
+
"before:pointer-events-none before:absolute before:top-0 before:bottom-0 before:left-[calc(4.5rem+0.75rem+0.5rem)] before:w-px before:-translate-x-1/2 before:border-l before:border-dashed before:border-border-primary before:content-['']",
|
|
36
|
+
horizontal:
|
|
37
|
+
"flex w-max items-start before:pointer-events-none before:absolute before:inset-x-0 before:top-[var(--timeline-rail)] before:border-t before:border-dashed before:border-border-primary before:content-['']",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
defaultVariants: {
|
|
41
|
+
orientation: "vertical",
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const timelineItemVariants = cva("group/timeline-item relative", {
|
|
46
|
+
variants: {
|
|
47
|
+
orientation: {
|
|
48
|
+
vertical: "grid grid-cols-[4.5rem_1rem_minmax(0,1fr)] gap-x-3 pb-8 last:pb-0",
|
|
49
|
+
horizontal:
|
|
50
|
+
"w-[min(18rem,70vw)] shrink-0 pr-10 last:pr-0 [--timeline-rail:2.75rem]",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
defaultVariants: {
|
|
54
|
+
orientation: "vertical",
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const timelineMarkerVariants = cva("z-[1] shrink-0", {
|
|
59
|
+
variants: {
|
|
60
|
+
orientation: {
|
|
61
|
+
vertical: "col-start-2 row-start-1 mt-2 justify-self-center",
|
|
62
|
+
horizontal:
|
|
63
|
+
"absolute top-[var(--timeline-rail)] left-0 -translate-y-1/2",
|
|
64
|
+
},
|
|
65
|
+
shape: {
|
|
66
|
+
tick: "bg-fg-quaternary group-hover/timeline-item:bg-fg-tertiary",
|
|
67
|
+
dot: "size-2.5 rounded-full border-2 border-background-primary shadow-hairline",
|
|
68
|
+
},
|
|
69
|
+
tone: {
|
|
70
|
+
neutral: "",
|
|
71
|
+
accent: "",
|
|
72
|
+
info: "",
|
|
73
|
+
success: "",
|
|
74
|
+
warning: "",
|
|
75
|
+
critical: "",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
compoundVariants: [
|
|
79
|
+
{
|
|
80
|
+
shape: "tick",
|
|
81
|
+
orientation: "vertical",
|
|
82
|
+
class: "h-px w-2.5",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
shape: "tick",
|
|
86
|
+
orientation: "horizontal",
|
|
87
|
+
class: "h-2.5 w-px",
|
|
88
|
+
},
|
|
89
|
+
{ shape: "tick", tone: "neutral", class: "bg-fg-quaternary" },
|
|
90
|
+
{ shape: "tick", tone: "accent", class: "bg-brand-primary" },
|
|
91
|
+
{ shape: "tick", tone: "info", class: "bg-status-info" },
|
|
92
|
+
{ shape: "tick", tone: "success", class: "bg-status-success" },
|
|
93
|
+
{ shape: "tick", tone: "warning", class: "bg-status-warning" },
|
|
94
|
+
{ shape: "tick", tone: "critical", class: "bg-status-critical" },
|
|
95
|
+
{ shape: "dot", tone: "neutral", class: "bg-fg-tertiary" },
|
|
96
|
+
{ shape: "dot", tone: "accent", class: "bg-brand-primary" },
|
|
97
|
+
{ shape: "dot", tone: "info", class: "bg-status-info" },
|
|
98
|
+
{ shape: "dot", tone: "success", class: "bg-status-success" },
|
|
99
|
+
{ shape: "dot", tone: "warning", class: "bg-status-warning" },
|
|
100
|
+
{ shape: "dot", tone: "critical", class: "bg-status-critical" },
|
|
101
|
+
],
|
|
102
|
+
defaultVariants: {
|
|
103
|
+
orientation: "vertical",
|
|
104
|
+
shape: "tick",
|
|
105
|
+
tone: "neutral",
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const timelineTimeVariants = cva(
|
|
110
|
+
cn(
|
|
111
|
+
"text-sm-strong block tabular-nums text-fg-secondary",
|
|
112
|
+
motion.colors,
|
|
113
|
+
"group-hover/timeline-item:text-fg-primary",
|
|
114
|
+
),
|
|
115
|
+
{
|
|
116
|
+
variants: {
|
|
117
|
+
orientation: {
|
|
118
|
+
vertical: "col-start-1 row-start-1 pt-0.5 text-right leading-5",
|
|
119
|
+
horizontal: "mb-6 h-5 leading-5",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
defaultVariants: {
|
|
123
|
+
orientation: "vertical",
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
type TimelineOrientation = "horizontal" | "vertical"
|
|
129
|
+
|
|
130
|
+
const TimelineContext = createContext<TimelineOrientation>("vertical")
|
|
131
|
+
|
|
132
|
+
export type TimelineProps = ComponentProps<"div"> &
|
|
133
|
+
VariantProps<typeof timelineVariants> & {
|
|
134
|
+
/** Distance moved by the horizontal timeline's arrow keys. */
|
|
135
|
+
scrollStep?: number
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export type TimelineTrackProps = ComponentProps<"ol">
|
|
139
|
+
export type TimelineItemProps = ComponentProps<"li">
|
|
140
|
+
export type TimelineMarkerProps = ComponentProps<"span"> &
|
|
141
|
+
VariantProps<typeof timelineMarkerVariants>
|
|
142
|
+
export type TimelineTimeProps = ComponentProps<"time">
|
|
143
|
+
export type TimelineContentProps = ComponentProps<"div">
|
|
144
|
+
export type TimelineTitleProps = ComponentProps<"h3">
|
|
145
|
+
export type TimelineDescriptionProps = ComponentProps<"p">
|
|
146
|
+
export type TimelineMediaProps = ComponentProps<"div">
|
|
147
|
+
|
|
148
|
+
export const Timeline = ({
|
|
149
|
+
orientation = "vertical",
|
|
150
|
+
scrollStep = 256,
|
|
151
|
+
className,
|
|
152
|
+
children,
|
|
153
|
+
tabIndex,
|
|
154
|
+
onKeyDown,
|
|
155
|
+
onWheel,
|
|
156
|
+
"aria-label": ariaLabel = "Timeline",
|
|
157
|
+
...props
|
|
158
|
+
}: TimelineProps) => {
|
|
159
|
+
const resolvedOrientation = orientation ?? "vertical"
|
|
160
|
+
|
|
161
|
+
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
162
|
+
onKeyDown?.(event)
|
|
163
|
+
if (
|
|
164
|
+
event.defaultPrevented ||
|
|
165
|
+
resolvedOrientation !== "horizontal" ||
|
|
166
|
+
event.target !== event.currentTarget
|
|
167
|
+
) {
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const node = event.currentTarget
|
|
172
|
+
const prefersReducedMotion = window.matchMedia(
|
|
173
|
+
"(prefers-reduced-motion: reduce)",
|
|
174
|
+
).matches
|
|
175
|
+
const behavior = prefersReducedMotion ? "auto" : "smooth"
|
|
176
|
+
|
|
177
|
+
if (event.key === "ArrowRight" || event.key === "ArrowLeft") {
|
|
178
|
+
event.preventDefault()
|
|
179
|
+
node.scrollBy({
|
|
180
|
+
left: event.key === "ArrowRight" ? scrollStep : -scrollStep,
|
|
181
|
+
behavior,
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (event.key === "Home" || event.key === "End") {
|
|
186
|
+
event.preventDefault()
|
|
187
|
+
node.scrollTo({
|
|
188
|
+
left: event.key === "Home" ? 0 : node.scrollWidth,
|
|
189
|
+
behavior,
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const handleWheel = (event: WheelEvent<HTMLDivElement>) => {
|
|
195
|
+
onWheel?.(event)
|
|
196
|
+
if (
|
|
197
|
+
event.defaultPrevented ||
|
|
198
|
+
resolvedOrientation !== "horizontal" ||
|
|
199
|
+
Math.abs(event.deltaX) >= Math.abs(event.deltaY)
|
|
200
|
+
) {
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const node = event.currentTarget
|
|
205
|
+
const maxScrollLeft = node.scrollWidth - node.clientWidth
|
|
206
|
+
const canScroll =
|
|
207
|
+
event.deltaY > 0 ? node.scrollLeft < maxScrollLeft : node.scrollLeft > 0
|
|
208
|
+
|
|
209
|
+
if (canScroll) {
|
|
210
|
+
event.preventDefault()
|
|
211
|
+
node.scrollLeft += event.deltaY
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<TimelineContext.Provider value={resolvedOrientation}>
|
|
217
|
+
<div
|
|
218
|
+
data-slot="timeline"
|
|
219
|
+
data-orientation={resolvedOrientation}
|
|
220
|
+
role="region"
|
|
221
|
+
aria-label={ariaLabel}
|
|
222
|
+
tabIndex={
|
|
223
|
+
tabIndex ?? (resolvedOrientation === "horizontal" ? 0 : undefined)
|
|
224
|
+
}
|
|
225
|
+
className={cn(
|
|
226
|
+
timelineVariants({ orientation: resolvedOrientation }),
|
|
227
|
+
className,
|
|
228
|
+
)}
|
|
229
|
+
onKeyDown={handleKeyDown}
|
|
230
|
+
onWheel={handleWheel}
|
|
231
|
+
{...props}
|
|
232
|
+
>
|
|
233
|
+
{children}
|
|
234
|
+
</div>
|
|
235
|
+
</TimelineContext.Provider>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export const TimelineTrack = ({
|
|
240
|
+
className,
|
|
241
|
+
...props
|
|
242
|
+
}: TimelineTrackProps) => {
|
|
243
|
+
const orientation = useContext(TimelineContext)
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<ol
|
|
247
|
+
data-slot="timeline-track"
|
|
248
|
+
data-orientation={orientation}
|
|
249
|
+
className={cn(
|
|
250
|
+
timelineTrackVariants({ orientation }),
|
|
251
|
+
orientation === "horizontal" && "[--timeline-rail:2.75rem]",
|
|
252
|
+
className,
|
|
253
|
+
)}
|
|
254
|
+
{...props}
|
|
255
|
+
/>
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export const TimelineItem = ({
|
|
260
|
+
className,
|
|
261
|
+
...props
|
|
262
|
+
}: TimelineItemProps) => {
|
|
263
|
+
const orientation = useContext(TimelineContext)
|
|
264
|
+
|
|
265
|
+
return (
|
|
266
|
+
<li
|
|
267
|
+
data-slot="timeline-item"
|
|
268
|
+
data-orientation={orientation}
|
|
269
|
+
className={cn(timelineItemVariants({ orientation }), className)}
|
|
270
|
+
{...props}
|
|
271
|
+
/>
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export const TimelineMarker = ({
|
|
276
|
+
className,
|
|
277
|
+
tone,
|
|
278
|
+
shape,
|
|
279
|
+
...props
|
|
280
|
+
}: TimelineMarkerProps) => {
|
|
281
|
+
const orientation = useContext(TimelineContext)
|
|
282
|
+
|
|
283
|
+
return (
|
|
284
|
+
<span
|
|
285
|
+
data-slot="timeline-marker"
|
|
286
|
+
aria-hidden
|
|
287
|
+
className={cn(
|
|
288
|
+
timelineMarkerVariants({ orientation, shape, tone }),
|
|
289
|
+
motion.colors,
|
|
290
|
+
className,
|
|
291
|
+
)}
|
|
292
|
+
{...props}
|
|
293
|
+
/>
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export const TimelineTime = ({
|
|
298
|
+
className,
|
|
299
|
+
...props
|
|
300
|
+
}: TimelineTimeProps) => {
|
|
301
|
+
const orientation = useContext(TimelineContext)
|
|
302
|
+
|
|
303
|
+
return (
|
|
304
|
+
<time
|
|
305
|
+
data-slot="timeline-time"
|
|
306
|
+
className={cn(timelineTimeVariants({ orientation }), className)}
|
|
307
|
+
{...props}
|
|
308
|
+
/>
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export const TimelineContent = ({
|
|
313
|
+
className,
|
|
314
|
+
...props
|
|
315
|
+
}: TimelineContentProps) => {
|
|
316
|
+
const orientation = useContext(TimelineContext)
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<div
|
|
320
|
+
data-slot="timeline-content"
|
|
321
|
+
className={cn(
|
|
322
|
+
"min-w-0",
|
|
323
|
+
orientation === "vertical" && "col-start-3 row-start-1",
|
|
324
|
+
orientation === "horizontal" && "pt-6",
|
|
325
|
+
className,
|
|
326
|
+
)}
|
|
327
|
+
{...props}
|
|
328
|
+
/>
|
|
329
|
+
)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export const TimelineTitle = ({
|
|
333
|
+
className,
|
|
334
|
+
...props
|
|
335
|
+
}: TimelineTitleProps) => (
|
|
336
|
+
<h3
|
|
337
|
+
data-slot="timeline-title"
|
|
338
|
+
className={cn(
|
|
339
|
+
"text-sm-strong text-fg-primary",
|
|
340
|
+
motion.colors,
|
|
341
|
+
className,
|
|
342
|
+
)}
|
|
343
|
+
{...props}
|
|
344
|
+
/>
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
export const TimelineDescription = ({
|
|
348
|
+
className,
|
|
349
|
+
...props
|
|
350
|
+
}: TimelineDescriptionProps) => (
|
|
351
|
+
<p
|
|
352
|
+
data-slot="timeline-description"
|
|
353
|
+
className={cn("text-sm mt-1 leading-relaxed text-fg-secondary", className)}
|
|
354
|
+
{...props}
|
|
355
|
+
/>
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
export const TimelineMedia = ({
|
|
359
|
+
className,
|
|
360
|
+
...props
|
|
361
|
+
}: TimelineMediaProps) => (
|
|
362
|
+
<div
|
|
363
|
+
data-slot="timeline-media"
|
|
364
|
+
className={cn(
|
|
365
|
+
"mt-4 overflow-hidden rounded-xl ring-1 ring-border-primary [&>img]:block [&>img]:aspect-4/3 [&>img]:w-full [&>img]:object-cover",
|
|
366
|
+
className,
|
|
367
|
+
)}
|
|
368
|
+
{...props}
|
|
369
|
+
/>
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
export {
|
|
373
|
+
timelineVariants,
|
|
374
|
+
timelineItemVariants,
|
|
375
|
+
timelineMarkerVariants,
|
|
376
|
+
timelineTimeVariants,
|
|
377
|
+
timelineTrackVariants,
|
|
378
|
+
}
|