panelui-native 0.71.0 → 0.73.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/README.md +2 -1
- package/lib/module/components/accordion/index.js +10 -4
- package/lib/module/components/accordion/index.js.map +1 -1
- package/lib/module/components/area-chart/index.js +1 -0
- package/lib/module/components/area-chart/index.js.map +1 -1
- package/lib/module/components/breadcrumb/index.js +2 -1
- package/lib/module/components/breadcrumb/index.js.map +1 -1
- package/lib/module/components/button/index.js +7 -5
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/carousel/index.js +1 -1
- package/lib/module/components/carousel/index.js.map +1 -1
- package/lib/module/components/chip/index.js +2 -2
- package/lib/module/components/chip/index.js.map +1 -1
- package/lib/module/components/code-block/index.js.map +1 -1
- package/lib/module/components/collapsible/index.js +5 -1
- package/lib/module/components/collapsible/index.js.map +1 -1
- package/lib/module/components/color-picker/index.js.map +1 -1
- package/lib/module/components/fab/index.js +7 -6
- package/lib/module/components/fab/index.js.map +1 -1
- package/lib/module/components/flow/index.js +1 -1
- package/lib/module/components/flow/index.js.map +1 -1
- package/lib/module/components/frame/index.js +2 -2
- package/lib/module/components/frame/index.js.map +1 -1
- package/lib/module/components/grid-item/index.js +2 -2
- package/lib/module/components/grid-item/index.js.map +1 -1
- package/lib/module/components/heatmap-chart/index.js +1 -0
- package/lib/module/components/heatmap-chart/index.js.map +1 -1
- package/lib/module/components/hex-chart/index.js +1 -0
- package/lib/module/components/hex-chart/index.js.map +1 -1
- package/lib/module/components/input-group/index.js +7 -2
- package/lib/module/components/input-group/index.js.map +1 -1
- package/lib/module/components/item/index.js +2 -2
- package/lib/module/components/item/index.js.map +1 -1
- package/lib/module/components/line-chart/index.js +1 -0
- package/lib/module/components/line-chart/index.js.map +1 -1
- package/lib/module/components/marker/index.js +2 -2
- package/lib/module/components/marker/index.js.map +1 -1
- package/lib/module/components/marquee/index.js +9 -12
- package/lib/module/components/marquee/index.js.map +1 -1
- package/lib/module/components/marquee/marquee-math.js +39 -0
- package/lib/module/components/marquee/marquee-math.js.map +1 -0
- package/lib/module/components/menu/index.js +11 -3
- package/lib/module/components/menu/index.js.map +1 -1
- package/lib/module/components/pagination/index.js +27 -11
- package/lib/module/components/pagination/index.js.map +1 -1
- package/lib/module/components/panelside/index.js +7 -2
- package/lib/module/components/panelside/index.js.map +1 -1
- package/lib/module/components/planner/index.js +26 -5
- package/lib/module/components/planner/index.js.map +1 -1
- package/lib/module/components/plot/index.js +105 -42
- package/lib/module/components/plot/index.js.map +1 -1
- package/lib/module/components/post/index.js +4 -4
- package/lib/module/components/post/index.js.map +1 -1
- package/lib/module/components/search-bar/index.js +331 -0
- package/lib/module/components/search-bar/index.js.map +1 -0
- package/lib/module/components/table/index.js +3 -4
- package/lib/module/components/table/index.js.map +1 -1
- package/lib/module/components/timeline/index.js +288 -39
- package/lib/module/components/timeline/index.js.map +1 -1
- package/lib/module/components/toggle-button/index.js +1 -1
- package/lib/module/components/toggle-button/index.js.map +1 -1
- package/lib/module/components/tree/index.js +13 -8
- package/lib/module/components/tree/index.js.map +1 -1
- package/lib/module/hooks/use-keyboard-avoidance.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/chart.js +50 -7
- package/lib/module/utils/chart.js.map +1 -1
- package/lib/typescript/src/components/accordion/index.d.ts +2 -2
- package/lib/typescript/src/components/accordion/index.d.ts.map +1 -1
- package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/breadcrumb/index.d.ts.map +1 -1
- package/lib/typescript/src/components/button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/code-block/index.d.ts +3 -3
- package/lib/typescript/src/components/code-block/index.d.ts.map +1 -1
- package/lib/typescript/src/components/collapsible/index.d.ts +2 -2
- package/lib/typescript/src/components/collapsible/index.d.ts.map +1 -1
- package/lib/typescript/src/components/color-picker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/fab/index.d.ts +4 -2
- package/lib/typescript/src/components/fab/index.d.ts.map +1 -1
- package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/hex-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/marquee/index.d.ts +1 -1
- package/lib/typescript/src/components/marquee/index.d.ts.map +1 -1
- package/lib/typescript/src/components/marquee/marquee-math.d.ts +21 -0
- package/lib/typescript/src/components/marquee/marquee-math.d.ts.map +1 -0
- package/lib/typescript/src/components/menu/index.d.ts +1 -1
- package/lib/typescript/src/components/menu/index.d.ts.map +1 -1
- package/lib/typescript/src/components/pagination/index.d.ts.map +1 -1
- package/lib/typescript/src/components/panelside/index.d.ts +1 -1
- package/lib/typescript/src/components/panelside/index.d.ts.map +1 -1
- package/lib/typescript/src/components/plan/index.d.ts +2 -2
- package/lib/typescript/src/components/planner/index.d.ts.map +1 -1
- package/lib/typescript/src/components/plot/index.d.ts +59 -5
- package/lib/typescript/src/components/plot/index.d.ts.map +1 -1
- package/lib/typescript/src/components/search-bar/index.d.ts +216 -0
- package/lib/typescript/src/components/search-bar/index.d.ts.map +1 -0
- package/lib/typescript/src/components/timeline/index.d.ts +35 -2
- package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tree/index.d.ts +3 -4
- package/lib/typescript/src/components/tree/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts +2 -2
- package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/chart.d.ts +28 -3
- package/lib/typescript/src/utils/chart.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/accordion/index.tsx +17 -7
- package/src/components/area-chart/index.tsx +8 -0
- package/src/components/breadcrumb/index.tsx +2 -1
- package/src/components/button/index.tsx +3 -2
- package/src/components/carousel/index.tsx +1 -1
- package/src/components/chip/index.tsx +2 -2
- package/src/components/code-block/index.tsx +3 -3
- package/src/components/collapsible/index.tsx +13 -4
- package/src/components/color-picker/index.tsx +3 -2
- package/src/components/fab/index.tsx +21 -11
- package/src/components/flow/index.tsx +1 -1
- package/src/components/frame/index.tsx +2 -2
- package/src/components/grid-item/index.tsx +2 -2
- package/src/components/heatmap-chart/index.tsx +7 -1
- package/src/components/hex-chart/index.tsx +7 -1
- package/src/components/input-group/index.tsx +7 -2
- package/src/components/item/index.tsx +2 -2
- package/src/components/line-chart/index.tsx +9 -0
- package/src/components/marker/index.tsx +2 -2
- package/src/components/marquee/index.tsx +14 -12
- package/src/components/marquee/marquee-math.ts +37 -0
- package/src/components/menu/index.tsx +11 -3
- package/src/components/pagination/index.tsx +26 -13
- package/src/components/panelside/index.tsx +7 -2
- package/src/components/plan/index.tsx +2 -2
- package/src/components/planner/index.tsx +20 -5
- package/src/components/plot/index.tsx +183 -39
- package/src/components/post/index.tsx +4 -4
- package/src/components/search-bar/index.tsx +436 -0
- package/src/components/table/index.tsx +2 -2
- package/src/components/timeline/index.tsx +335 -39
- package/src/components/toggle-button/index.tsx +1 -1
- package/src/components/tree/index.tsx +17 -12
- package/src/hooks/use-keyboard-avoidance.ts +7 -2
- package/src/index.ts +2 -0
- package/src/utils/chart.ts +66 -7
|
@@ -11,8 +11,18 @@
|
|
|
11
11
|
* attribute selectors for a stylesheet to key off. `Steps` resolves its states
|
|
12
12
|
* the same way, so the two read as siblings.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* ## Two orientations
|
|
15
|
+
*
|
|
16
|
+
* Vertical is the default and the ordinary case. Horizontal lays the items out
|
|
17
|
+
* as columns on a rail that runs off the side of the screen and is swiped
|
|
18
|
+
* through, which is the arrangement a long span of time wants: a decade of
|
|
19
|
+
* entries read top to bottom is a page nobody reaches the end of.
|
|
20
|
+
*
|
|
21
|
+
* The objection to a horizontal timeline is that each item gets a fifth of a
|
|
22
|
+
* phone's width, which will not hold a date and a title. The answer is that it
|
|
23
|
+
* does not have to fit — the rail is wider than the screen, and a column takes
|
|
24
|
+
* the width its contents need. An item with nothing on it collapses to a tick,
|
|
25
|
+
* so a quiet stretch compresses and a busy one keeps its room.
|
|
16
26
|
*
|
|
17
27
|
* ```tsx
|
|
18
28
|
* <Timeline variant="icon" value={2}>
|
|
@@ -34,13 +44,23 @@
|
|
|
34
44
|
* ```
|
|
35
45
|
*/
|
|
36
46
|
import {
|
|
47
|
+
Children,
|
|
37
48
|
createContext,
|
|
38
49
|
forwardRef,
|
|
50
|
+
isValidElement,
|
|
39
51
|
useContext,
|
|
40
52
|
useMemo,
|
|
41
53
|
type ReactNode,
|
|
42
54
|
} from 'react';
|
|
43
|
-
import { View, type Text as RNText, type ViewProps } from 'react-native';
|
|
55
|
+
import { ScrollView, View, type Text as RNText, type ViewProps } from 'react-native';
|
|
56
|
+
import Animated, {
|
|
57
|
+
interpolate,
|
|
58
|
+
useAnimatedScrollHandler,
|
|
59
|
+
useAnimatedStyle,
|
|
60
|
+
useReducedMotion,
|
|
61
|
+
useSharedValue,
|
|
62
|
+
type SharedValue,
|
|
63
|
+
} from 'react-native-reanimated';
|
|
44
64
|
import { tv } from 'tailwind-variants';
|
|
45
65
|
import { useCSSVariable } from 'uniwind';
|
|
46
66
|
import { IconColorProvider } from '../../icons';
|
|
@@ -49,6 +69,49 @@ import { Text, type TextProps, textChildren } from '../../primitives/text';
|
|
|
49
69
|
export type TimelineVariant = 'dot' | 'icon' | 'numbered' | 'card' | 'compact';
|
|
50
70
|
/** Semantic colour for a single event, independent of progress. */
|
|
51
71
|
export type TimelineTone = 'default' | 'info' | 'success' | 'warning' | 'danger';
|
|
72
|
+
/** Which way the sequence runs. */
|
|
73
|
+
export type TimelineOrientation = 'vertical' | 'horizontal';
|
|
74
|
+
|
|
75
|
+
const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView);
|
|
76
|
+
|
|
77
|
+
/*
|
|
78
|
+
* Horizontal geometry.
|
|
79
|
+
*
|
|
80
|
+
* The rail has to land on exactly the same line in every column, and the
|
|
81
|
+
* simplest way to guarantee that is to make the band above it a fixed height
|
|
82
|
+
* rather than to position the rail against measured content. So the aside is
|
|
83
|
+
* `HORIZONTAL_RAIL_TOP` tall in every column, the rail is drawn once across the
|
|
84
|
+
* whole track at that offset, and each column's tick is pulled up by half its
|
|
85
|
+
* own height to sit centred on it.
|
|
86
|
+
*/
|
|
87
|
+
const HORIZONTAL_RAIL_TOP = 64;
|
|
88
|
+
const HORIZONTAL_TICK_HEIGHT = 10;
|
|
89
|
+
|
|
90
|
+
/** A column with something to say, and one with nothing. */
|
|
91
|
+
const HORIZONTAL_WIDE = 268;
|
|
92
|
+
const HORIZONTAL_NARROW = 76;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* How wide a column is: what it was given, else what its contents ask for.
|
|
96
|
+
*
|
|
97
|
+
* Read by the item to size itself and by the root to build the snap offsets, so
|
|
98
|
+
* it lives here rather than in either — the two disagreeing would put every
|
|
99
|
+
* snap point half a column out.
|
|
100
|
+
*/
|
|
101
|
+
function itemWidth(props: TimelineItemProps): number {
|
|
102
|
+
if (typeof props.width === 'number') return props.width;
|
|
103
|
+
|
|
104
|
+
let filled = false;
|
|
105
|
+
Children.forEach(props.children, (child) => {
|
|
106
|
+
if (filled || !isValidElement(child)) return;
|
|
107
|
+
const displayName = (child.type as { displayName?: string })?.displayName;
|
|
108
|
+
if (displayName !== 'Timeline.Content') return;
|
|
109
|
+
const inner = (child.props as { children?: ReactNode }).children;
|
|
110
|
+
if (inner !== undefined && inner !== null && inner !== false) filled = true;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return filled ? HORIZONTAL_WIDE : HORIZONTAL_NARROW;
|
|
114
|
+
}
|
|
52
115
|
|
|
53
116
|
/** Variants whose node is a filled disc rather than an outlined ring. */
|
|
54
117
|
const SOLID_VARIANTS: TimelineVariant[] = ['dot', 'card'];
|
|
@@ -107,11 +170,29 @@ const timelineVariants = tv({
|
|
|
107
170
|
true: { separator: 'bg-primary' },
|
|
108
171
|
false: { separator: 'bg-muted' },
|
|
109
172
|
},
|
|
173
|
+
/*
|
|
174
|
+
* Horizontal turns the item from a row into a column and the rail from a
|
|
175
|
+
* line down the side into a line across the top. The aside is a fixed
|
|
176
|
+
* height because that height *is* where the rail sits — see
|
|
177
|
+
* HORIZONTAL_RAIL_TOP — and it is left-aligned rather than right, since a
|
|
178
|
+
* column reads from its own left edge rather than towards a rail beside it.
|
|
179
|
+
*/
|
|
180
|
+
orientation: {
|
|
181
|
+
vertical: {},
|
|
182
|
+
horizontal: {
|
|
183
|
+
item: 'w-auto shrink-0 flex-col gap-0 pr-6',
|
|
184
|
+
aside: 'w-auto items-start justify-end gap-1 pb-2 pt-0',
|
|
185
|
+
rail: 'w-full items-start',
|
|
186
|
+
body: 'w-auto flex-none pb-0 pt-5',
|
|
187
|
+
title: 'text-sm',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
110
190
|
},
|
|
111
191
|
defaultVariants: {
|
|
112
192
|
variant: 'dot',
|
|
113
193
|
tone: 'default',
|
|
114
194
|
completed: false,
|
|
195
|
+
orientation: 'vertical',
|
|
115
196
|
},
|
|
116
197
|
});
|
|
117
198
|
|
|
@@ -169,6 +250,13 @@ const TONE_ICON_VAR: Record<TimelineTone, string> = {
|
|
|
169
250
|
interface TimelineContextValue {
|
|
170
251
|
activeStep: number;
|
|
171
252
|
variant: TimelineVariant;
|
|
253
|
+
orientation: TimelineOrientation;
|
|
254
|
+
/** Horizontal only: where the track has been scrolled to, on the UI thread. */
|
|
255
|
+
scrollX: SharedValue<number>;
|
|
256
|
+
/** Horizontal only: each column's left edge, indexed by its `step`. */
|
|
257
|
+
offsets: number[];
|
|
258
|
+
/** Horizontal only: false when the reader has asked for less movement. */
|
|
259
|
+
animate: boolean;
|
|
172
260
|
}
|
|
173
261
|
|
|
174
262
|
interface TimelineItemContextValue {
|
|
@@ -199,18 +287,120 @@ export interface TimelineProps extends ViewProps {
|
|
|
199
287
|
/** Steps at or below this index render as completed. */
|
|
200
288
|
value?: number;
|
|
201
289
|
variant?: TimelineVariant;
|
|
290
|
+
/**
|
|
291
|
+
* Which way the sequence runs. `horizontal` lays the items out as columns on
|
|
292
|
+
* a rail wider than the screen, swiped through rather than scrolled down.
|
|
293
|
+
*/
|
|
294
|
+
orientation?: TimelineOrientation;
|
|
295
|
+
/**
|
|
296
|
+
* Horizontal only: land a flick on a column rather than between two.
|
|
297
|
+
*
|
|
298
|
+
* On by default, because the thing being moved between is a column — stopping
|
|
299
|
+
* halfway shows two half-columns and no whole one.
|
|
300
|
+
*/
|
|
301
|
+
snap?: boolean;
|
|
202
302
|
children?: ReactNode;
|
|
203
303
|
}
|
|
204
304
|
|
|
205
305
|
const TimelineRoot = forwardRef<View, TimelineProps>(
|
|
206
|
-
(
|
|
207
|
-
|
|
208
|
-
|
|
306
|
+
(
|
|
307
|
+
{
|
|
308
|
+
className,
|
|
309
|
+
value = 0,
|
|
310
|
+
variant = 'dot',
|
|
311
|
+
orientation = 'vertical',
|
|
312
|
+
snap = true,
|
|
313
|
+
children,
|
|
314
|
+
...props
|
|
315
|
+
},
|
|
316
|
+
ref
|
|
317
|
+
) => {
|
|
318
|
+
const horizontal = orientation === 'horizontal';
|
|
319
|
+
const { root } = timelineVariants({ variant, orientation });
|
|
320
|
+
const reducedMotion = useReducedMotion();
|
|
321
|
+
const scrollX = useSharedValue(0);
|
|
322
|
+
const border = useCSSVariable('--color-border');
|
|
323
|
+
|
|
324
|
+
/*
|
|
325
|
+
* The columns' left edges, from the same width rule the items size
|
|
326
|
+
* themselves by. These are the snap points, and they are what an item reads
|
|
327
|
+
* to know how far it is from the reading edge.
|
|
328
|
+
*/
|
|
329
|
+
const offsets = useMemo(() => {
|
|
330
|
+
if (!horizontal) return [];
|
|
331
|
+
const result: number[] = [];
|
|
332
|
+
let sum = 0;
|
|
333
|
+
Children.forEach(children, (child) => {
|
|
334
|
+
if (!isValidElement(child)) return;
|
|
335
|
+
result.push(sum);
|
|
336
|
+
sum += itemWidth(child.props as TimelineItemProps);
|
|
337
|
+
});
|
|
338
|
+
return result;
|
|
339
|
+
}, [children, horizontal]);
|
|
340
|
+
|
|
341
|
+
const context = useMemo(
|
|
342
|
+
() => ({
|
|
343
|
+
activeStep: value,
|
|
344
|
+
variant,
|
|
345
|
+
orientation,
|
|
346
|
+
scrollX,
|
|
347
|
+
offsets,
|
|
348
|
+
animate: !reducedMotion,
|
|
349
|
+
}),
|
|
350
|
+
[value, variant, orientation, scrollX, offsets, reducedMotion]
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
const onScroll = useAnimatedScrollHandler((event) => {
|
|
354
|
+
scrollX.value = event.contentOffset.x;
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
if (!horizontal) {
|
|
358
|
+
return (
|
|
359
|
+
<TimelineContext.Provider value={context}>
|
|
360
|
+
<View ref={ref} className={root({ className })} {...props}>
|
|
361
|
+
{textChildren(children)}
|
|
362
|
+
</View>
|
|
363
|
+
</TimelineContext.Provider>
|
|
364
|
+
);
|
|
365
|
+
}
|
|
209
366
|
|
|
210
367
|
return (
|
|
211
368
|
<TimelineContext.Provider value={context}>
|
|
212
369
|
<View ref={ref} className={root({ className })} {...props}>
|
|
213
|
-
|
|
370
|
+
<AnimatedScrollView
|
|
371
|
+
horizontal
|
|
372
|
+
onScroll={onScroll}
|
|
373
|
+
scrollEventThrottle={16}
|
|
374
|
+
showsHorizontalScrollIndicator={false}
|
|
375
|
+
decelerationRate="fast"
|
|
376
|
+
snapToAlignment="start"
|
|
377
|
+
snapToOffsets={snap ? offsets : undefined}
|
|
378
|
+
>
|
|
379
|
+
{/*
|
|
380
|
+
The track sizes itself to its columns, so the rail stretched
|
|
381
|
+
between its own left and right edges runs the whole length of the
|
|
382
|
+
sequence rather than the width of the screen.
|
|
383
|
+
*/}
|
|
384
|
+
<View className="relative flex-row">
|
|
385
|
+
<View
|
|
386
|
+
accessibilityElementsHidden
|
|
387
|
+
importantForAccessibility="no-hide-descendants"
|
|
388
|
+
pointerEvents="none"
|
|
389
|
+
style={{
|
|
390
|
+
position: 'absolute',
|
|
391
|
+
left: 0,
|
|
392
|
+
right: 0,
|
|
393
|
+
top: HORIZONTAL_RAIL_TOP,
|
|
394
|
+
borderTopWidth: 1,
|
|
395
|
+
// Dashed borders only render at a radius of zero, which this
|
|
396
|
+
// has, and the rule is the whole element.
|
|
397
|
+
borderStyle: 'dashed',
|
|
398
|
+
borderColor: typeof border === 'string' ? border : undefined,
|
|
399
|
+
}}
|
|
400
|
+
/>
|
|
401
|
+
{textChildren(children)}
|
|
402
|
+
</View>
|
|
403
|
+
</AnimatedScrollView>
|
|
214
404
|
</View>
|
|
215
405
|
</TimelineContext.Provider>
|
|
216
406
|
);
|
|
@@ -228,28 +418,88 @@ export interface TimelineItemProps extends ViewProps {
|
|
|
228
418
|
tone?: TimelineTone;
|
|
229
419
|
/** Set on the final item so its rail stops at the indicator. */
|
|
230
420
|
last?: boolean;
|
|
421
|
+
/**
|
|
422
|
+
* Horizontal only: how wide this column is, in points.
|
|
423
|
+
*
|
|
424
|
+
* Left out, a column that carries content takes a readable width and one that
|
|
425
|
+
* carries none collapses to a tick — so a quiet stretch of the sequence
|
|
426
|
+
* compresses instead of paying full width for nothing. Set it to override
|
|
427
|
+
* that for a column that needs more or less room than its contents suggest.
|
|
428
|
+
*/
|
|
429
|
+
width?: number;
|
|
231
430
|
children?: ReactNode;
|
|
232
431
|
}
|
|
233
432
|
|
|
234
433
|
const TimelineItem = forwardRef<View, TimelineItemProps>(
|
|
235
434
|
(
|
|
236
|
-
{
|
|
435
|
+
{
|
|
436
|
+
className,
|
|
437
|
+
step,
|
|
438
|
+
completed,
|
|
439
|
+
tone = 'default',
|
|
440
|
+
last = false,
|
|
441
|
+
width,
|
|
442
|
+
children,
|
|
443
|
+
...props
|
|
444
|
+
},
|
|
237
445
|
ref
|
|
238
446
|
) => {
|
|
239
|
-
const { activeStep, variant } =
|
|
447
|
+
const { activeStep, variant, orientation, scrollX, offsets, animate } =
|
|
448
|
+
useTimeline('Timeline.Item');
|
|
240
449
|
const isCompleted = completed ?? step <= activeStep;
|
|
241
|
-
const { item } = timelineVariants({
|
|
450
|
+
const { item } = timelineVariants({
|
|
451
|
+
variant,
|
|
452
|
+
tone,
|
|
453
|
+
completed: isCompleted,
|
|
454
|
+
orientation,
|
|
455
|
+
});
|
|
456
|
+
const horizontal = orientation === 'horizontal';
|
|
242
457
|
|
|
243
458
|
const context = useMemo(
|
|
244
459
|
() => ({ step, completed: isCompleted, tone, showSeparator: !last }),
|
|
245
460
|
[step, isCompleted, tone, last]
|
|
246
461
|
);
|
|
247
462
|
|
|
463
|
+
const columnWidth = horizontal
|
|
464
|
+
? itemWidth({ step, width, children } as TimelineItemProps)
|
|
465
|
+
: undefined;
|
|
466
|
+
const offset = offsets[step] ?? 0;
|
|
467
|
+
|
|
468
|
+
/*
|
|
469
|
+
* A column recedes as it leaves the reading edge, so the one being read is
|
|
470
|
+
* the one that looks read. The window is a column's own width either side,
|
|
471
|
+
* which keeps the neighbours legible — this is a timeline, and the value of
|
|
472
|
+
* it is being able to see what came before and after at once.
|
|
473
|
+
*/
|
|
474
|
+
const columnStyle = useAnimatedStyle(() => {
|
|
475
|
+
if (!horizontal || !animate) return { opacity: 1 };
|
|
476
|
+
const distance = Math.abs(scrollX.value - offset);
|
|
477
|
+
const window = Math.max(columnWidth ?? HORIZONTAL_WIDE, 1) * 2;
|
|
478
|
+
return {
|
|
479
|
+
opacity: interpolate(distance, [0, window], [1, 0.45], 'clamp'),
|
|
480
|
+
};
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
if (!horizontal) {
|
|
484
|
+
return (
|
|
485
|
+
<TimelineItemContext.Provider value={context}>
|
|
486
|
+
<View ref={ref} className={item({ className })} {...props}>
|
|
487
|
+
{textChildren(children)}
|
|
488
|
+
</View>
|
|
489
|
+
</TimelineItemContext.Provider>
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
248
493
|
return (
|
|
249
494
|
<TimelineItemContext.Provider value={context}>
|
|
250
|
-
<View
|
|
495
|
+
<Animated.View
|
|
496
|
+
ref={ref}
|
|
497
|
+
className={item({ className })}
|
|
498
|
+
style={[{ width: columnWidth }, columnStyle]}
|
|
499
|
+
{...props}
|
|
500
|
+
>
|
|
251
501
|
{textChildren(children)}
|
|
252
|
-
</View>
|
|
502
|
+
</Animated.View>
|
|
253
503
|
</TimelineItemContext.Provider>
|
|
254
504
|
);
|
|
255
505
|
}
|
|
@@ -261,10 +511,28 @@ TimelineItem.displayName = 'Timeline.Item';
|
|
|
261
511
|
* person. Place it before `Timeline.Indicator`.
|
|
262
512
|
*/
|
|
263
513
|
const TimelineAside = forwardRef<View, ViewProps & { className?: string }>(
|
|
264
|
-
({ className, ...props }, ref) => {
|
|
265
|
-
const { variant } = useTimeline('Timeline.Aside');
|
|
266
|
-
const { aside } = timelineVariants({ variant });
|
|
267
|
-
|
|
514
|
+
({ className, style, ...props }, ref) => {
|
|
515
|
+
const { variant, orientation } = useTimeline('Timeline.Aside');
|
|
516
|
+
const { aside } = timelineVariants({ variant, orientation });
|
|
517
|
+
|
|
518
|
+
return (
|
|
519
|
+
<View
|
|
520
|
+
ref={ref}
|
|
521
|
+
className={aside({ className })}
|
|
522
|
+
/*
|
|
523
|
+
* Horizontal: this band's height is where the rail lands, so it is
|
|
524
|
+
* fixed rather than sized to whatever the column happens to put in it.
|
|
525
|
+
* A column with a longer label would otherwise push its own tick below
|
|
526
|
+
* everybody else's and the rail would stop being a line.
|
|
527
|
+
*/
|
|
528
|
+
style={
|
|
529
|
+
orientation === 'horizontal'
|
|
530
|
+
? [{ height: HORIZONTAL_RAIL_TOP }, style]
|
|
531
|
+
: style
|
|
532
|
+
}
|
|
533
|
+
{...props}
|
|
534
|
+
/>
|
|
535
|
+
);
|
|
268
536
|
}
|
|
269
537
|
);
|
|
270
538
|
TimelineAside.displayName = 'Timeline.Aside';
|
|
@@ -284,15 +552,43 @@ export interface TimelineIndicatorProps extends ViewProps {
|
|
|
284
552
|
*/
|
|
285
553
|
const TimelineIndicator = forwardRef<View, TimelineIndicatorProps>(
|
|
286
554
|
({ className, children, ...props }, ref) => {
|
|
287
|
-
const { variant } = useTimeline('Timeline.Indicator');
|
|
555
|
+
const { variant, orientation } = useTimeline('Timeline.Indicator');
|
|
288
556
|
const { step, completed, tone, showSeparator } =
|
|
289
557
|
useTimelineItem('Timeline.Indicator');
|
|
290
558
|
const { rail, indicator, indicatorLabel, separator } = timelineVariants({
|
|
291
559
|
variant,
|
|
292
560
|
tone,
|
|
293
561
|
completed,
|
|
562
|
+
orientation,
|
|
294
563
|
});
|
|
295
564
|
|
|
565
|
+
/*
|
|
566
|
+
* Horizontal: a tick sitting on the rail, not a node with a connector
|
|
567
|
+
* running out of it. The rail is already drawn across the whole track by
|
|
568
|
+
* the root, so what a column owes it is the mark saying where this entry
|
|
569
|
+
* falls — pulled up by half its height to straddle the line rather than
|
|
570
|
+
* hang off it.
|
|
571
|
+
*/
|
|
572
|
+
if (orientation === 'horizontal') {
|
|
573
|
+
return (
|
|
574
|
+
<View className={rail()}>
|
|
575
|
+
<View
|
|
576
|
+
ref={ref}
|
|
577
|
+
accessibilityElementsHidden
|
|
578
|
+
importantForAccessibility="no-hide-descendants"
|
|
579
|
+
className={indicator({
|
|
580
|
+
className: `w-px rounded-none ${completed ? 'bg-primary' : 'bg-muted-foreground'} ${className ?? ''}`,
|
|
581
|
+
})}
|
|
582
|
+
style={{
|
|
583
|
+
height: HORIZONTAL_TICK_HEIGHT,
|
|
584
|
+
marginTop: -HORIZONTAL_TICK_HEIGHT / 2,
|
|
585
|
+
}}
|
|
586
|
+
{...props}
|
|
587
|
+
/>
|
|
588
|
+
</View>
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
|
|
296
592
|
// dot/card nodes are small discs with nothing inside, so they keep full
|
|
297
593
|
// saturation — a soft tint at 16px would disappear. Nodes that hold an
|
|
298
594
|
// icon or a number take the tinted fill instead.
|
|
@@ -352,9 +648,9 @@ TimelineIndicator.displayName = 'Timeline.Indicator';
|
|
|
352
648
|
*/
|
|
353
649
|
const TimelineContent = forwardRef<View, ViewProps & { className?: string }>(
|
|
354
650
|
({ className, children, ...props }, ref) => {
|
|
355
|
-
const { variant } = useTimeline('Timeline.Content');
|
|
651
|
+
const { variant, orientation } = useTimeline('Timeline.Content');
|
|
356
652
|
const { completed, tone } = useTimelineItem('Timeline.Content');
|
|
357
|
-
const { body, panel } = timelineVariants({ variant, tone, completed });
|
|
653
|
+
const { body, panel } = timelineVariants({ variant, tone, completed, orientation });
|
|
358
654
|
|
|
359
655
|
return (
|
|
360
656
|
<View ref={ref} className={body({ className })} {...props}>
|
|
@@ -368,8 +664,8 @@ TimelineContent.displayName = 'Timeline.Content';
|
|
|
368
664
|
/** Title row: heading on the left, `Timeline.Trailing` on the right. */
|
|
369
665
|
const TimelineHeader = forwardRef<View, ViewProps & { className?: string }>(
|
|
370
666
|
({ className, ...props }, ref) => {
|
|
371
|
-
const { variant } = useTimeline('Timeline.Header');
|
|
372
|
-
const { header } = timelineVariants({ variant });
|
|
667
|
+
const { variant, orientation } = useTimeline('Timeline.Header');
|
|
668
|
+
const { header } = timelineVariants({ variant, orientation });
|
|
373
669
|
return <View ref={ref} className={header({ className })} {...props} />;
|
|
374
670
|
}
|
|
375
671
|
);
|
|
@@ -378,40 +674,40 @@ TimelineHeader.displayName = 'Timeline.Header';
|
|
|
378
674
|
/** Wraps a title and anything stacked under it inside the header row. */
|
|
379
675
|
const TimelineHeading = forwardRef<View, ViewProps & { className?: string }>(
|
|
380
676
|
({ className, ...props }, ref) => {
|
|
381
|
-
const { variant } = useTimeline('Timeline.Heading');
|
|
382
|
-
const { heading } = timelineVariants({ variant });
|
|
677
|
+
const { variant, orientation } = useTimeline('Timeline.Heading');
|
|
678
|
+
const { heading } = timelineVariants({ variant, orientation });
|
|
383
679
|
return <View ref={ref} className={heading({ className })} {...props} />;
|
|
384
680
|
}
|
|
385
681
|
);
|
|
386
682
|
TimelineHeading.displayName = 'Timeline.Heading';
|
|
387
683
|
|
|
388
684
|
const TimelineDate = forwardRef<RNText, TextProps>(({ className, ...props }, ref) => {
|
|
389
|
-
const { variant } = useTimeline('Timeline.Date');
|
|
390
|
-
const { date } = timelineVariants({ variant });
|
|
685
|
+
const { variant, orientation } = useTimeline('Timeline.Date');
|
|
686
|
+
const { date } = timelineVariants({ variant, orientation });
|
|
391
687
|
return <Text ref={ref} className={date({ className })} {...props} />;
|
|
392
688
|
});
|
|
393
689
|
TimelineDate.displayName = 'Timeline.Date';
|
|
394
690
|
|
|
395
691
|
/** Category line in the aside, coloured by the item's tone. */
|
|
396
692
|
const TimelineLabel = forwardRef<RNText, TextProps>(({ className, ...props }, ref) => {
|
|
397
|
-
const { variant } = useTimeline('Timeline.Label');
|
|
693
|
+
const { variant, orientation } = useTimeline('Timeline.Label');
|
|
398
694
|
const { tone } = useTimelineItem('Timeline.Label');
|
|
399
|
-
const { label } = timelineVariants({ variant, tone });
|
|
695
|
+
const { label } = timelineVariants({ variant, tone, orientation });
|
|
400
696
|
return <Text ref={ref} className={label({ className })} {...props} />;
|
|
401
697
|
});
|
|
402
698
|
TimelineLabel.displayName = 'Timeline.Label';
|
|
403
699
|
|
|
404
700
|
/** Muted supporting line — a person's name, a source. */
|
|
405
701
|
const TimelineMeta = forwardRef<RNText, TextProps>(({ className, ...props }, ref) => {
|
|
406
|
-
const { variant } = useTimeline('Timeline.Meta');
|
|
407
|
-
const { meta } = timelineVariants({ variant });
|
|
702
|
+
const { variant, orientation } = useTimeline('Timeline.Meta');
|
|
703
|
+
const { meta } = timelineVariants({ variant, orientation });
|
|
408
704
|
return <Text ref={ref} className={meta({ className })} {...props} />;
|
|
409
705
|
});
|
|
410
706
|
TimelineMeta.displayName = 'Timeline.Meta';
|
|
411
707
|
|
|
412
708
|
const TimelineTitle = forwardRef<RNText, TextProps>(({ className, ...props }, ref) => {
|
|
413
|
-
const { variant } = useTimeline('Timeline.Title');
|
|
414
|
-
const { title } = timelineVariants({ variant });
|
|
709
|
+
const { variant, orientation } = useTimeline('Timeline.Title');
|
|
710
|
+
const { title } = timelineVariants({ variant, orientation });
|
|
415
711
|
return <Text ref={ref} className={title({ className })} {...props} />;
|
|
416
712
|
});
|
|
417
713
|
TimelineTitle.displayName = 'Timeline.Title';
|
|
@@ -419,8 +715,8 @@ TimelineTitle.displayName = 'Timeline.Title';
|
|
|
419
715
|
/** Right-hand slot in the header row — a timestamp, usually. */
|
|
420
716
|
const TimelineTrailing = forwardRef<RNText, TextProps>(
|
|
421
717
|
({ className, ...props }, ref) => {
|
|
422
|
-
const { variant } = useTimeline('Timeline.Trailing');
|
|
423
|
-
const { trailing } = timelineVariants({ variant });
|
|
718
|
+
const { variant, orientation } = useTimeline('Timeline.Trailing');
|
|
719
|
+
const { trailing } = timelineVariants({ variant, orientation });
|
|
424
720
|
return <Text ref={ref} className={trailing({ className })} {...props} />;
|
|
425
721
|
}
|
|
426
722
|
);
|
|
@@ -428,8 +724,8 @@ TimelineTrailing.displayName = 'Timeline.Trailing';
|
|
|
428
724
|
|
|
429
725
|
const TimelineDescription = forwardRef<RNText, TextProps>(
|
|
430
726
|
({ className, ...props }, ref) => {
|
|
431
|
-
const { variant } = useTimeline('Timeline.Description');
|
|
432
|
-
const { content } = timelineVariants({ variant });
|
|
727
|
+
const { variant, orientation } = useTimeline('Timeline.Description');
|
|
728
|
+
const { content } = timelineVariants({ variant, orientation });
|
|
433
729
|
return <Text ref={ref} className={content({ className })} {...props} />;
|
|
434
730
|
}
|
|
435
731
|
);
|
|
@@ -438,8 +734,8 @@ TimelineDescription.displayName = 'Timeline.Description';
|
|
|
438
734
|
/** Bordered strip of label/value pairs under a title. */
|
|
439
735
|
const TimelineStats = forwardRef<View, ViewProps & { className?: string }>(
|
|
440
736
|
({ className, ...props }, ref) => {
|
|
441
|
-
const { variant } = useTimeline('Timeline.Stats');
|
|
442
|
-
const { stats } = timelineVariants({ variant });
|
|
737
|
+
const { variant, orientation } = useTimeline('Timeline.Stats');
|
|
738
|
+
const { stats } = timelineVariants({ variant, orientation });
|
|
443
739
|
return <View ref={ref} className={stats({ className })} {...props} />;
|
|
444
740
|
}
|
|
445
741
|
);
|
|
@@ -453,8 +749,8 @@ export interface TimelineStatProps extends ViewProps {
|
|
|
453
749
|
|
|
454
750
|
const TimelineStat = forwardRef<View, TimelineStatProps>(
|
|
455
751
|
({ className, label, value, ...props }, ref) => {
|
|
456
|
-
const { variant } = useTimeline('Timeline.Stat');
|
|
457
|
-
const { statLabel, statValue } = timelineVariants({ variant });
|
|
752
|
+
const { variant, orientation } = useTimeline('Timeline.Stat');
|
|
753
|
+
const { statLabel, statValue } = timelineVariants({ variant, orientation });
|
|
458
754
|
return (
|
|
459
755
|
<View ref={ref} className={className} {...props}>
|
|
460
756
|
<Text className={statLabel()}>{label}</Text>
|
|
@@ -333,6 +333,7 @@ const ToggleButtonRoot = forwardRef<View, ToggleButtonProps>(
|
|
|
333
333
|
<IconColorProvider color={typeof tint === 'string' ? tint : undefined}>
|
|
334
334
|
<AnimatedPressable
|
|
335
335
|
ref={ref}
|
|
336
|
+
{...props}
|
|
336
337
|
accessibilityRole="button"
|
|
337
338
|
// `checked` is what a screen reader reads out as "on"/"off"; the
|
|
338
339
|
// selected state is the entire point of this control, so it is
|
|
@@ -345,7 +346,6 @@ const ToggleButtonRoot = forwardRef<View, ToggleButtonProps>(
|
|
|
345
346
|
slots.root({ className }),
|
|
346
347
|
selected ? selectedClassName : unselectedClassName
|
|
347
348
|
)}
|
|
348
|
-
{...props}
|
|
349
349
|
>
|
|
350
350
|
{textChildren(children, (text) => (
|
|
351
351
|
<Text className={slots.label({ className: labelClassName })}>
|
|
@@ -54,7 +54,13 @@ import {
|
|
|
54
54
|
useState,
|
|
55
55
|
type ReactNode,
|
|
56
56
|
} from 'react';
|
|
57
|
-
import {
|
|
57
|
+
import {
|
|
58
|
+
Pressable,
|
|
59
|
+
View,
|
|
60
|
+
type PressableProps,
|
|
61
|
+
type Text as RNText,
|
|
62
|
+
type ViewProps,
|
|
63
|
+
} from 'react-native';
|
|
58
64
|
import Animated, {
|
|
59
65
|
LinearTransition,
|
|
60
66
|
useAnimatedStyle,
|
|
@@ -353,10 +359,9 @@ const TreeItem = forwardRef<View, TreeItemProps>(
|
|
|
353
359
|
);
|
|
354
360
|
TreeItem.displayName = 'Tree.Item';
|
|
355
361
|
|
|
356
|
-
|
|
362
|
+
/** Pressable props are forwarded; `onPress` runs after the tree updates its state. */
|
|
363
|
+
export interface TreeTriggerProps extends Omit<PressableProps, 'children'> {
|
|
357
364
|
className?: string;
|
|
358
|
-
/** Runs after the press has been handled, with the tree's own state already updated. */
|
|
359
|
-
onPress?: () => void;
|
|
360
365
|
children?: ReactNode;
|
|
361
366
|
}
|
|
362
367
|
|
|
@@ -370,20 +375,20 @@ export interface TreeTriggerProps extends ViewProps {
|
|
|
370
375
|
* actually survives to the user.
|
|
371
376
|
*/
|
|
372
377
|
const TreeTrigger = forwardRef<View, TreeTriggerProps>(
|
|
373
|
-
({ className, onPress, children, ...props }, ref) => {
|
|
378
|
+
({ className, onPress, disabled = false, children, ...props }, ref) => {
|
|
374
379
|
const { toggleExpanded, select, selectionMode, expandOnPress, size } =
|
|
375
380
|
useTree('Tree.Trigger');
|
|
376
381
|
const { value, level, isExpanded, isSelected, isDisabled, isBranch } =
|
|
377
382
|
useTreeItem('Tree.Trigger');
|
|
378
|
-
const { trigger } = treeVariants({ size, isSelected, isDisabled });
|
|
379
|
-
|
|
380
383
|
const accentForeground = useCSSVariable('--color-accent-foreground');
|
|
381
384
|
const selectedColor = typeof accentForeground === 'string' ? accentForeground : undefined;
|
|
382
385
|
|
|
383
|
-
const
|
|
386
|
+
const triggerDisabled = Boolean(isDisabled || disabled);
|
|
387
|
+
const { trigger } = treeVariants({ size, isSelected, isDisabled: triggerDisabled });
|
|
388
|
+
const handlePress: NonNullable<PressableProps['onPress']> = (event) => {
|
|
384
389
|
if (isBranch && expandOnPress) toggleExpanded(value);
|
|
385
390
|
select(value);
|
|
386
|
-
onPress?.();
|
|
391
|
+
onPress?.(event);
|
|
387
392
|
};
|
|
388
393
|
|
|
389
394
|
return (
|
|
@@ -393,13 +398,13 @@ const TreeTrigger = forwardRef<View, TreeTriggerProps>(
|
|
|
393
398
|
accessibilityState={{
|
|
394
399
|
expanded: isBranch ? isExpanded : undefined,
|
|
395
400
|
selected: selectionMode === 'none' ? undefined : isSelected,
|
|
396
|
-
disabled:
|
|
401
|
+
disabled: triggerDisabled,
|
|
397
402
|
}}
|
|
398
403
|
aria-level={level + 1}
|
|
399
|
-
disabled={
|
|
400
|
-
onPress={handlePress}
|
|
404
|
+
disabled={triggerDisabled}
|
|
401
405
|
className={trigger({ className })}
|
|
402
406
|
{...props}
|
|
407
|
+
onPress={handlePress}
|
|
403
408
|
>
|
|
404
409
|
<IconColorProvider color={isSelected ? selectedColor : undefined}>
|
|
405
410
|
{children}
|
|
@@ -45,7 +45,12 @@
|
|
|
45
45
|
* ```
|
|
46
46
|
*/
|
|
47
47
|
import { useCallback, useEffect, useState } from 'react';
|
|
48
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
useWindowDimensions,
|
|
50
|
+
type LayoutChangeEvent,
|
|
51
|
+
type View,
|
|
52
|
+
type ViewStyle,
|
|
53
|
+
} from 'react-native';
|
|
49
54
|
import {
|
|
50
55
|
measure,
|
|
51
56
|
runOnJS,
|
|
@@ -106,7 +111,7 @@ export interface UseKeyboardAvoidanceResult {
|
|
|
106
111
|
/** Attach to the same element, so a re-layout at rest cannot leave it offset. */
|
|
107
112
|
onLayout: (event: LayoutChangeEvent) => void;
|
|
108
113
|
/** Apply to the same element. */
|
|
109
|
-
animatedStyle: ReturnType<typeof useAnimatedStyle
|
|
114
|
+
animatedStyle: ReturnType<typeof useAnimatedStyle<ViewStyle>>;
|
|
110
115
|
}
|
|
111
116
|
|
|
112
117
|
type KeyboardHeightHook = () => SharedValue<number>;
|
package/src/index.ts
CHANGED
|
@@ -522,6 +522,7 @@ export {
|
|
|
522
522
|
type NumberInputProps,
|
|
523
523
|
} from './components/number-input';
|
|
524
524
|
export { OtpInput, type OtpInputProps } from './components/otp-input';
|
|
525
|
+
export { SearchBar, type SearchBarProps } from './components/search-bar';
|
|
525
526
|
export {
|
|
526
527
|
Panelside,
|
|
527
528
|
usePanelside,
|
|
@@ -1068,6 +1069,7 @@ export {
|
|
|
1068
1069
|
type TimelineStatProps,
|
|
1069
1070
|
type TimelineVariant,
|
|
1070
1071
|
type TimelineTone,
|
|
1072
|
+
type TimelineOrientation,
|
|
1071
1073
|
} from './components/timeline';
|
|
1072
1074
|
export {
|
|
1073
1075
|
Toast,
|