panelui-native 0.4.0 → 0.5.1
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 +14 -7
- package/lib/module/components/accordion/index.js +312 -0
- package/lib/module/components/accordion/index.js.map +1 -0
- package/lib/module/components/avatar/index.js +58 -17
- package/lib/module/components/avatar/index.js.map +1 -1
- package/lib/module/components/badge/index.js +26 -5
- package/lib/module/components/badge/index.js.map +1 -1
- package/lib/module/components/button/index.js +51 -19
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/checkbox/index.js +42 -14
- package/lib/module/components/checkbox/index.js.map +1 -1
- package/lib/module/components/frame/index.js +64 -13
- package/lib/module/components/frame/index.js.map +1 -1
- package/lib/module/components/timeline/index.js +633 -0
- package/lib/module/components/timeline/index.js.map +1 -0
- package/lib/module/components/toast/index.js +145 -55
- package/lib/module/components/toast/index.js.map +1 -1
- package/lib/module/icons/index.js +388 -27
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +3 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/components/accordion/index.d.ts +56 -0
- package/lib/typescript/src/components/accordion/index.d.ts.map +1 -0
- package/lib/typescript/src/components/avatar/index.d.ts +8 -1
- package/lib/typescript/src/components/avatar/index.d.ts.map +1 -1
- package/lib/typescript/src/components/badge/index.d.ts +38 -0
- package/lib/typescript/src/components/badge/index.d.ts.map +1 -1
- package/lib/typescript/src/components/button/index.d.ts +18 -0
- package/lib/typescript/src/components/button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/checkbox/index.d.ts +42 -0
- package/lib/typescript/src/components/checkbox/index.d.ts.map +1 -1
- package/lib/typescript/src/components/frame/index.d.ts +39 -5
- package/lib/typescript/src/components/frame/index.d.ts.map +1 -1
- package/lib/typescript/src/components/timeline/index.d.ts +96 -0
- package/lib/typescript/src/components/timeline/index.d.ts.map +1 -0
- package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
- package/lib/typescript/src/icons/index.d.ts +38 -3
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/accordion/index.tsx +344 -0
- package/src/components/avatar/index.tsx +68 -20
- package/src/components/badge/index.tsx +35 -6
- package/src/components/button/index.tsx +63 -20
- package/src/components/checkbox/index.tsx +49 -11
- package/src/components/frame/index.tsx +101 -31
- package/src/components/timeline/index.tsx +456 -0
- package/src/components/toast/index.tsx +166 -45
- package/src/icons/index.tsx +292 -27
- package/src/index.ts +38 -1
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timeline — a vertical sequence of events.
|
|
3
|
+
*
|
|
4
|
+
* Adapted from: origin-space/originui apps/origin/registry/default/ui/timeline.tsx
|
|
5
|
+
* (the Item / Indicator / Separator / Header / Date / Title / Content anatomy
|
|
6
|
+
* and the completed-up-to-active state rule), shaped to match this repo's
|
|
7
|
+
* `Steps` component so the two read as siblings.
|
|
8
|
+
*
|
|
9
|
+
* Upstream drives styling through `data-*` attributes, which React Native has
|
|
10
|
+
* no equivalent for; state is resolved in JS and passed through context into
|
|
11
|
+
* `tv()` variants instead.
|
|
12
|
+
*
|
|
13
|
+
* Vertical only — a horizontal timeline gives each item roughly a fifth of a
|
|
14
|
+
* phone's width, which is not enough for a date and a title.
|
|
15
|
+
*
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <Timeline variant="icon" value={2}>
|
|
18
|
+
* <Timeline.Item step={0} tone="info">
|
|
19
|
+
* <Timeline.Aside>
|
|
20
|
+
* <Timeline.Date>09:12</Timeline.Date>
|
|
21
|
+
* <Timeline.Label>Design</Timeline.Label>
|
|
22
|
+
* </Timeline.Aside>
|
|
23
|
+
* <Timeline.Indicator><SendIcon /></Timeline.Indicator>
|
|
24
|
+
* <Timeline.Content>
|
|
25
|
+
* <Timeline.Header>
|
|
26
|
+
* <Timeline.Title>Checkout language approved</Timeline.Title>
|
|
27
|
+
* <Timeline.Trailing>10:18</Timeline.Trailing>
|
|
28
|
+
* </Timeline.Header>
|
|
29
|
+
* <Timeline.Description>…</Timeline.Description>
|
|
30
|
+
* </Timeline.Content>
|
|
31
|
+
* </Timeline.Item>
|
|
32
|
+
* </Timeline>
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
import {
|
|
36
|
+
createContext,
|
|
37
|
+
forwardRef,
|
|
38
|
+
useContext,
|
|
39
|
+
useMemo,
|
|
40
|
+
type ReactNode,
|
|
41
|
+
} from 'react';
|
|
42
|
+
import { View, type Text as RNText, type ViewProps } from 'react-native';
|
|
43
|
+
import { tv } from 'tailwind-variants';
|
|
44
|
+
import { useCSSVariable } from 'uniwind';
|
|
45
|
+
import { IconColorProvider } from '../../icons';
|
|
46
|
+
import { Text, type TextProps } from '../../primitives/text';
|
|
47
|
+
|
|
48
|
+
export type TimelineVariant = 'dot' | 'icon' | 'numbered' | 'card' | 'compact';
|
|
49
|
+
/** Semantic colour for a single event, independent of progress. */
|
|
50
|
+
export type TimelineTone = 'default' | 'info' | 'success' | 'warning' | 'danger';
|
|
51
|
+
|
|
52
|
+
/** Variants whose node is a filled disc rather than an outlined ring. */
|
|
53
|
+
const SOLID_VARIANTS: TimelineVariant[] = ['dot', 'card'];
|
|
54
|
+
|
|
55
|
+
const timelineVariants = tv({
|
|
56
|
+
slots: {
|
|
57
|
+
root: 'w-full flex-col',
|
|
58
|
+
item: 'w-full flex-row gap-3',
|
|
59
|
+
aside: 'w-20 items-end gap-0.5 pt-0.5',
|
|
60
|
+
rail: 'items-center',
|
|
61
|
+
indicator: 'items-center justify-center rounded-full border-2',
|
|
62
|
+
indicatorLabel: 'text-xs font-medium',
|
|
63
|
+
separator: 'w-0.5 flex-1 rounded-full',
|
|
64
|
+
body: 'flex-1 pb-6',
|
|
65
|
+
panel: '',
|
|
66
|
+
header: 'flex-row items-center gap-2',
|
|
67
|
+
heading: 'flex-1 gap-0.5',
|
|
68
|
+
date: 'text-xs font-medium text-muted-foreground',
|
|
69
|
+
label: 'text-xs font-medium',
|
|
70
|
+
meta: 'text-xs text-muted-foreground',
|
|
71
|
+
title: 'text-sm font-medium text-foreground',
|
|
72
|
+
trailing: 'text-xs text-muted-foreground',
|
|
73
|
+
content: 'pt-1 text-sm text-muted-foreground',
|
|
74
|
+
stats: 'mt-2 flex-row gap-6 rounded-xl border border-border px-3.5 py-2.5',
|
|
75
|
+
statLabel: 'text-xs text-muted-foreground',
|
|
76
|
+
statValue: 'text-sm font-medium text-foreground',
|
|
77
|
+
},
|
|
78
|
+
variants: {
|
|
79
|
+
variant: {
|
|
80
|
+
dot: { indicator: 'h-4 w-4' },
|
|
81
|
+
icon: { indicator: 'h-8 w-8' },
|
|
82
|
+
numbered: { indicator: 'h-7 w-7' },
|
|
83
|
+
card: {
|
|
84
|
+
indicator: 'h-4 w-4',
|
|
85
|
+
// The gap stays on `body` so the connector runs through it; the card
|
|
86
|
+
// chrome lives on an inner panel. Putting the border on `body` made
|
|
87
|
+
// consecutive cards sit flush against each other.
|
|
88
|
+
panel: 'rounded-xl border border-border bg-card p-3.5',
|
|
89
|
+
},
|
|
90
|
+
compact: {
|
|
91
|
+
indicator: 'h-6 w-6',
|
|
92
|
+
body: 'flex-1 pb-3',
|
|
93
|
+
title: 'text-base',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
tone: {
|
|
97
|
+
default: {},
|
|
98
|
+
info: { label: 'text-info-foreground' },
|
|
99
|
+
success: { label: 'text-success-foreground' },
|
|
100
|
+
warning: { label: 'text-warning-foreground' },
|
|
101
|
+
danger: { label: 'text-destructive-foreground' },
|
|
102
|
+
},
|
|
103
|
+
completed: {
|
|
104
|
+
true: { separator: 'bg-primary' },
|
|
105
|
+
false: { separator: 'bg-muted' },
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
defaultVariants: {
|
|
109
|
+
variant: 'dot',
|
|
110
|
+
tone: 'default',
|
|
111
|
+
completed: false,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/** Node fill and border per tone, split by whether the node is solid. */
|
|
116
|
+
const TONE_NODE: Record<TimelineTone, { solid: string; outline: string }> = {
|
|
117
|
+
default: { solid: 'border-primary bg-primary', outline: 'border-primary' },
|
|
118
|
+
info: { solid: 'border-info bg-info', outline: 'border-info' },
|
|
119
|
+
success: { solid: 'border-success bg-success', outline: 'border-success' },
|
|
120
|
+
warning: { solid: 'border-warning bg-warning', outline: 'border-warning' },
|
|
121
|
+
danger: { solid: 'border-destructive bg-destructive', outline: 'border-destructive' },
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** The node an incomplete, untoned step gets. */
|
|
125
|
+
const PENDING_NODE = 'border-muted bg-background';
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* CSS variable an outlined node's icon takes its colour from. The
|
|
129
|
+
* `-foreground` tokens are the ones tuned to read against the page in both
|
|
130
|
+
* light and dark, which is what an outlined node's contents sit on.
|
|
131
|
+
*/
|
|
132
|
+
const TONE_ICON_VAR: Record<TimelineTone, string> = {
|
|
133
|
+
default: '--color-primary',
|
|
134
|
+
info: '--color-info-foreground',
|
|
135
|
+
success: '--color-success-foreground',
|
|
136
|
+
warning: '--color-warning-foreground',
|
|
137
|
+
danger: '--color-destructive-foreground',
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
interface TimelineContextValue {
|
|
141
|
+
activeStep: number;
|
|
142
|
+
variant: TimelineVariant;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
interface TimelineItemContextValue {
|
|
146
|
+
step: number;
|
|
147
|
+
completed: boolean;
|
|
148
|
+
tone: TimelineTone;
|
|
149
|
+
/** False on the last item, so its rail does not trail into nothing. */
|
|
150
|
+
showSeparator: boolean;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const TimelineContext = createContext<TimelineContextValue | null>(null);
|
|
154
|
+
const TimelineItemContext = createContext<TimelineItemContextValue | null>(null);
|
|
155
|
+
|
|
156
|
+
function useTimeline(component: string): TimelineContextValue {
|
|
157
|
+
const context = useContext(TimelineContext);
|
|
158
|
+
if (!context) throw new Error(`${component} must be used within a <Timeline>`);
|
|
159
|
+
return context;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function useTimelineItem(component: string): TimelineItemContextValue {
|
|
163
|
+
const context = useContext(TimelineItemContext);
|
|
164
|
+
if (!context) throw new Error(`${component} must be used within a <Timeline.Item>`);
|
|
165
|
+
return context;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface TimelineProps extends ViewProps {
|
|
169
|
+
className?: string;
|
|
170
|
+
/** Steps at or below this index render as completed. */
|
|
171
|
+
value?: number;
|
|
172
|
+
variant?: TimelineVariant;
|
|
173
|
+
children?: ReactNode;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const TimelineRoot = forwardRef<View, TimelineProps>(
|
|
177
|
+
({ className, value = 0, variant = 'dot', children, ...props }, ref) => {
|
|
178
|
+
const { root } = timelineVariants({ variant });
|
|
179
|
+
const context = useMemo(() => ({ activeStep: value, variant }), [value, variant]);
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<TimelineContext.Provider value={context}>
|
|
183
|
+
<View ref={ref} className={root({ className })} {...props}>
|
|
184
|
+
{children}
|
|
185
|
+
</View>
|
|
186
|
+
</TimelineContext.Provider>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
TimelineRoot.displayName = 'Timeline';
|
|
191
|
+
|
|
192
|
+
export interface TimelineItemProps extends ViewProps {
|
|
193
|
+
className?: string;
|
|
194
|
+
/** Position in the sequence, zero-based. */
|
|
195
|
+
step: number;
|
|
196
|
+
/** Force the completed state regardless of the timeline's value. */
|
|
197
|
+
completed?: boolean;
|
|
198
|
+
/** Colours the node and label — for event kind rather than progress. */
|
|
199
|
+
tone?: TimelineTone;
|
|
200
|
+
/** Set on the final item so its rail stops at the indicator. */
|
|
201
|
+
last?: boolean;
|
|
202
|
+
children?: ReactNode;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const TimelineItem = forwardRef<View, TimelineItemProps>(
|
|
206
|
+
(
|
|
207
|
+
{ className, step, completed, tone = 'default', last = false, children, ...props },
|
|
208
|
+
ref
|
|
209
|
+
) => {
|
|
210
|
+
const { activeStep, variant } = useTimeline('Timeline.Item');
|
|
211
|
+
const isCompleted = completed ?? step <= activeStep;
|
|
212
|
+
const { item } = timelineVariants({ variant, tone, completed: isCompleted });
|
|
213
|
+
|
|
214
|
+
const context = useMemo(
|
|
215
|
+
() => ({ step, completed: isCompleted, tone, showSeparator: !last }),
|
|
216
|
+
[step, isCompleted, tone, last]
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<TimelineItemContext.Provider value={context}>
|
|
221
|
+
<View ref={ref} className={item({ className })} {...props}>
|
|
222
|
+
{children}
|
|
223
|
+
</View>
|
|
224
|
+
</TimelineItemContext.Provider>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
TimelineItem.displayName = 'Timeline.Item';
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Right-aligned meta column to the left of the rail — a time, a category, a
|
|
232
|
+
* person. Place it before `Timeline.Indicator`.
|
|
233
|
+
*/
|
|
234
|
+
const TimelineAside = forwardRef<View, ViewProps & { className?: string }>(
|
|
235
|
+
({ className, ...props }, ref) => {
|
|
236
|
+
const { variant } = useTimeline('Timeline.Aside');
|
|
237
|
+
const { aside } = timelineVariants({ variant });
|
|
238
|
+
return <View ref={ref} className={aside({ className })} {...props} />;
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
TimelineAside.displayName = 'Timeline.Aside';
|
|
242
|
+
|
|
243
|
+
export interface TimelineIndicatorProps extends ViewProps {
|
|
244
|
+
className?: string;
|
|
245
|
+
/** Replaces the default node contents — an icon, say. */
|
|
246
|
+
children?: ReactNode;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* The node on the rail, with the connector running below it.
|
|
251
|
+
*
|
|
252
|
+
* Children are wrapped in an `IconColorProvider` carrying the colour that
|
|
253
|
+
* reads against this node, so an icon inside follows the theme instead of
|
|
254
|
+
* disappearing when the fill inverts.
|
|
255
|
+
*/
|
|
256
|
+
const TimelineIndicator = forwardRef<View, TimelineIndicatorProps>(
|
|
257
|
+
({ className, children, ...props }, ref) => {
|
|
258
|
+
const { variant } = useTimeline('Timeline.Indicator');
|
|
259
|
+
const { step, completed, tone, showSeparator } =
|
|
260
|
+
useTimelineItem('Timeline.Indicator');
|
|
261
|
+
const { rail, indicator, indicatorLabel, separator } = timelineVariants({
|
|
262
|
+
variant,
|
|
263
|
+
tone,
|
|
264
|
+
completed,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const isSolid = SOLID_VARIANTS.includes(variant);
|
|
268
|
+
const toned = tone !== 'default';
|
|
269
|
+
// An untoned, unfinished step stays neutral; everything else takes its
|
|
270
|
+
// tone (or primary, once complete).
|
|
271
|
+
const nodeClass =
|
|
272
|
+
toned || completed
|
|
273
|
+
? TONE_NODE[tone][isSolid ? 'solid' : 'outline']
|
|
274
|
+
: PENDING_NODE;
|
|
275
|
+
|
|
276
|
+
const themedIcon = useCSSVariable(
|
|
277
|
+
completed || toned ? TONE_ICON_VAR[tone] : '--color-muted-foreground'
|
|
278
|
+
);
|
|
279
|
+
const outlineColor = typeof themedIcon === 'string' ? themedIcon : undefined;
|
|
280
|
+
|
|
281
|
+
// On a solid node the contents sit on the fill, so they need the colour
|
|
282
|
+
// that reads against it: the theme's own foreground for `default`, and
|
|
283
|
+
// white for the saturated status fills, which stay saturated in both
|
|
284
|
+
// themes.
|
|
285
|
+
const primaryForeground = useCSSVariable('--color-primary-foreground');
|
|
286
|
+
const solidColor =
|
|
287
|
+
tone === 'default'
|
|
288
|
+
? typeof primaryForeground === 'string'
|
|
289
|
+
? primaryForeground
|
|
290
|
+
: undefined
|
|
291
|
+
: '#ffffff';
|
|
292
|
+
|
|
293
|
+
const contentColor = isSolid && (completed || toned) ? solidColor : outlineColor;
|
|
294
|
+
|
|
295
|
+
return (
|
|
296
|
+
<View className={rail()}>
|
|
297
|
+
<IconColorProvider color={contentColor}>
|
|
298
|
+
<View ref={ref} className={indicator({ className: `${nodeClass} ${className ?? ''}` })} {...props}>
|
|
299
|
+
{variant === 'numbered' ? (
|
|
300
|
+
<Text className={indicatorLabel()} style={{ color: contentColor }}>
|
|
301
|
+
{step + 1}
|
|
302
|
+
</Text>
|
|
303
|
+
) : variant === 'icon' || variant === 'compact' ? (
|
|
304
|
+
children
|
|
305
|
+
) : null}
|
|
306
|
+
</View>
|
|
307
|
+
</IconColorProvider>
|
|
308
|
+
{showSeparator ? (
|
|
309
|
+
<View
|
|
310
|
+
accessibilityElementsHidden
|
|
311
|
+
importantForAccessibility="no-hide-descendants"
|
|
312
|
+
className={separator()}
|
|
313
|
+
/>
|
|
314
|
+
) : null}
|
|
315
|
+
</View>
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
);
|
|
319
|
+
TimelineIndicator.displayName = 'Timeline.Indicator';
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Everything to the right of the rail. Under `variant="card"` the children are
|
|
323
|
+
* wrapped in the card panel, while the spacing between items stays outside it
|
|
324
|
+
* so the connector runs unbroken.
|
|
325
|
+
*/
|
|
326
|
+
const TimelineContent = forwardRef<View, ViewProps & { className?: string }>(
|
|
327
|
+
({ className, children, ...props }, ref) => {
|
|
328
|
+
const { variant } = useTimeline('Timeline.Content');
|
|
329
|
+
const { completed, tone } = useTimelineItem('Timeline.Content');
|
|
330
|
+
const { body, panel } = timelineVariants({ variant, tone, completed });
|
|
331
|
+
|
|
332
|
+
return (
|
|
333
|
+
<View ref={ref} className={body({ className })} {...props}>
|
|
334
|
+
{variant === 'card' ? <View className={panel()}>{children}</View> : children}
|
|
335
|
+
</View>
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
);
|
|
339
|
+
TimelineContent.displayName = 'Timeline.Content';
|
|
340
|
+
|
|
341
|
+
/** Title row: heading on the left, `Timeline.Trailing` on the right. */
|
|
342
|
+
const TimelineHeader = forwardRef<View, ViewProps & { className?: string }>(
|
|
343
|
+
({ className, ...props }, ref) => {
|
|
344
|
+
const { variant } = useTimeline('Timeline.Header');
|
|
345
|
+
const { header } = timelineVariants({ variant });
|
|
346
|
+
return <View ref={ref} className={header({ className })} {...props} />;
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
TimelineHeader.displayName = 'Timeline.Header';
|
|
350
|
+
|
|
351
|
+
/** Wraps a title and anything stacked under it inside the header row. */
|
|
352
|
+
const TimelineHeading = forwardRef<View, ViewProps & { className?: string }>(
|
|
353
|
+
({ className, ...props }, ref) => {
|
|
354
|
+
const { variant } = useTimeline('Timeline.Heading');
|
|
355
|
+
const { heading } = timelineVariants({ variant });
|
|
356
|
+
return <View ref={ref} className={heading({ className })} {...props} />;
|
|
357
|
+
}
|
|
358
|
+
);
|
|
359
|
+
TimelineHeading.displayName = 'Timeline.Heading';
|
|
360
|
+
|
|
361
|
+
const TimelineDate = forwardRef<RNText, TextProps>(({ className, ...props }, ref) => {
|
|
362
|
+
const { variant } = useTimeline('Timeline.Date');
|
|
363
|
+
const { date } = timelineVariants({ variant });
|
|
364
|
+
return <Text ref={ref} className={date({ className })} {...props} />;
|
|
365
|
+
});
|
|
366
|
+
TimelineDate.displayName = 'Timeline.Date';
|
|
367
|
+
|
|
368
|
+
/** Category line in the aside, coloured by the item's tone. */
|
|
369
|
+
const TimelineLabel = forwardRef<RNText, TextProps>(({ className, ...props }, ref) => {
|
|
370
|
+
const { variant } = useTimeline('Timeline.Label');
|
|
371
|
+
const { tone } = useTimelineItem('Timeline.Label');
|
|
372
|
+
const { label } = timelineVariants({ variant, tone });
|
|
373
|
+
return <Text ref={ref} className={label({ className })} {...props} />;
|
|
374
|
+
});
|
|
375
|
+
TimelineLabel.displayName = 'Timeline.Label';
|
|
376
|
+
|
|
377
|
+
/** Muted supporting line — a person's name, a source. */
|
|
378
|
+
const TimelineMeta = forwardRef<RNText, TextProps>(({ className, ...props }, ref) => {
|
|
379
|
+
const { variant } = useTimeline('Timeline.Meta');
|
|
380
|
+
const { meta } = timelineVariants({ variant });
|
|
381
|
+
return <Text ref={ref} className={meta({ className })} {...props} />;
|
|
382
|
+
});
|
|
383
|
+
TimelineMeta.displayName = 'Timeline.Meta';
|
|
384
|
+
|
|
385
|
+
const TimelineTitle = forwardRef<RNText, TextProps>(({ className, ...props }, ref) => {
|
|
386
|
+
const { variant } = useTimeline('Timeline.Title');
|
|
387
|
+
const { title } = timelineVariants({ variant });
|
|
388
|
+
return <Text ref={ref} className={title({ className })} {...props} />;
|
|
389
|
+
});
|
|
390
|
+
TimelineTitle.displayName = 'Timeline.Title';
|
|
391
|
+
|
|
392
|
+
/** Right-hand slot in the header row — a timestamp, usually. */
|
|
393
|
+
const TimelineTrailing = forwardRef<RNText, TextProps>(
|
|
394
|
+
({ className, ...props }, ref) => {
|
|
395
|
+
const { variant } = useTimeline('Timeline.Trailing');
|
|
396
|
+
const { trailing } = timelineVariants({ variant });
|
|
397
|
+
return <Text ref={ref} className={trailing({ className })} {...props} />;
|
|
398
|
+
}
|
|
399
|
+
);
|
|
400
|
+
TimelineTrailing.displayName = 'Timeline.Trailing';
|
|
401
|
+
|
|
402
|
+
const TimelineDescription = forwardRef<RNText, TextProps>(
|
|
403
|
+
({ className, ...props }, ref) => {
|
|
404
|
+
const { variant } = useTimeline('Timeline.Description');
|
|
405
|
+
const { content } = timelineVariants({ variant });
|
|
406
|
+
return <Text ref={ref} className={content({ className })} {...props} />;
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
TimelineDescription.displayName = 'Timeline.Description';
|
|
410
|
+
|
|
411
|
+
/** Bordered strip of label/value pairs under a title. */
|
|
412
|
+
const TimelineStats = forwardRef<View, ViewProps & { className?: string }>(
|
|
413
|
+
({ className, ...props }, ref) => {
|
|
414
|
+
const { variant } = useTimeline('Timeline.Stats');
|
|
415
|
+
const { stats } = timelineVariants({ variant });
|
|
416
|
+
return <View ref={ref} className={stats({ className })} {...props} />;
|
|
417
|
+
}
|
|
418
|
+
);
|
|
419
|
+
TimelineStats.displayName = 'Timeline.Stats';
|
|
420
|
+
|
|
421
|
+
export interface TimelineStatProps extends ViewProps {
|
|
422
|
+
className?: string;
|
|
423
|
+
label: string;
|
|
424
|
+
value: string;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const TimelineStat = forwardRef<View, TimelineStatProps>(
|
|
428
|
+
({ className, label, value, ...props }, ref) => {
|
|
429
|
+
const { variant } = useTimeline('Timeline.Stat');
|
|
430
|
+
const { statLabel, statValue } = timelineVariants({ variant });
|
|
431
|
+
return (
|
|
432
|
+
<View ref={ref} className={className} {...props}>
|
|
433
|
+
<Text className={statLabel()}>{label}</Text>
|
|
434
|
+
<Text className={statValue()}>{value}</Text>
|
|
435
|
+
</View>
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
);
|
|
439
|
+
TimelineStat.displayName = 'Timeline.Stat';
|
|
440
|
+
|
|
441
|
+
export const Timeline = Object.assign(TimelineRoot, {
|
|
442
|
+
Item: TimelineItem,
|
|
443
|
+
Aside: TimelineAside,
|
|
444
|
+
Indicator: TimelineIndicator,
|
|
445
|
+
Content: TimelineContent,
|
|
446
|
+
Header: TimelineHeader,
|
|
447
|
+
Heading: TimelineHeading,
|
|
448
|
+
Date: TimelineDate,
|
|
449
|
+
Label: TimelineLabel,
|
|
450
|
+
Meta: TimelineMeta,
|
|
451
|
+
Title: TimelineTitle,
|
|
452
|
+
Trailing: TimelineTrailing,
|
|
453
|
+
Description: TimelineDescription,
|
|
454
|
+
Stats: TimelineStats,
|
|
455
|
+
Stat: TimelineStat,
|
|
456
|
+
});
|