panelui-native 0.71.0 → 0.75.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 +5 -2
- 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/avatar/avatar-group.js +64 -0
- package/lib/module/components/avatar/avatar-group.js.map +1 -0
- package/lib/module/components/avatar/index.js +84 -2
- package/lib/module/components/avatar/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/color-picker-accessibility.js +12 -0
- package/lib/module/components/color-picker/color-picker-accessibility.js.map +1 -0
- package/lib/module/components/color-picker/index.js +9 -5
- package/lib/module/components/color-picker/index.js.map +1 -1
- package/lib/module/components/date-time-picker/index.js +11 -3
- package/lib/module/components/date-time-picker/index.js.map +1 -1
- package/lib/module/components/drawer/index.js +58 -13
- package/lib/module/components/drawer/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/index.js +7 -4
- package/lib/module/components/input/index.js.map +1 -1
- package/lib/module/components/input/input-content-padding.js +6 -0
- package/lib/module/components/input/input-content-padding.js.map +1 -0
- package/lib/module/components/input-group/index.js +21 -11
- package/lib/module/components/input-group/index.js.map +1 -1
- package/lib/module/components/input-group/input-group-measurements.js +60 -0
- package/lib/module/components/input-group/input-group-measurements.js.map +1 -0
- 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/otp-input/index.js +3 -1
- package/lib/module/components/otp-input/index.js.map +1 -1
- package/lib/module/components/otp-input/otp-input-state.js +7 -0
- package/lib/module/components/otp-input/otp-input-state.js.map +1 -0
- 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 +46 -11
- package/lib/module/components/planner/index.js.map +1 -1
- package/lib/module/components/planner/planner-today.js +8 -0
- package/lib/module/components/planner/planner-today.js.map +1 -0
- package/lib/module/components/plot/index.js +113 -53
- package/lib/module/components/plot/index.js.map +1 -1
- package/lib/module/components/plot/plot-series-registry.js +32 -0
- package/lib/module/components/plot/plot-series-registry.js.map +1 -0
- package/lib/module/components/post/index.js +4 -4
- package/lib/module/components/post/index.js.map +1 -1
- package/lib/module/components/radio-group/index.js +2 -1
- package/lib/module/components/radio-group/index.js.map +1 -1
- package/lib/module/components/radio-group/radio-group-value.js +8 -0
- package/lib/module/components/radio-group/radio-group-value.js.map +1 -0
- package/lib/module/components/rating/index.js +7 -4
- package/lib/module/components/rating/index.js.map +1 -1
- package/lib/module/components/rating/rating-inputs.js +15 -0
- package/lib/module/components/rating/rating-inputs.js.map +1 -0
- 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/search-bar/search-bar-debounce.js +23 -0
- package/lib/module/components/search-bar/search-bar-debounce.js.map +1 -0
- package/lib/module/components/select/index.js +9 -1
- package/lib/module/components/select/index.js.map +1 -1
- package/lib/module/components/select/native-select-contract.js +11 -0
- package/lib/module/components/select/native-select-contract.js.map +1 -0
- package/lib/module/components/split-view/index.js +439 -0
- package/lib/module/components/split-view/index.js.map +1 -0
- package/lib/module/components/split-view/split-view-math.js +132 -0
- package/lib/module/components/split-view/split-view-math.js.map +1 -0
- package/lib/module/components/splitter/index.js +433 -0
- package/lib/module/components/splitter/index.js.map +1 -0
- package/lib/module/components/splitter/splitter-math.js +206 -0
- package/lib/module/components/splitter/splitter-math.js.map +1 -0
- package/lib/module/components/steps/index.js +16 -5
- package/lib/module/components/steps/index.js.map +1 -1
- package/lib/module/components/steps/steps-trigger.js +6 -0
- package/lib/module/components/steps/steps-trigger.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/tag-input/index.js +15 -10
- package/lib/module/components/tag-input/index.js.map +1 -1
- package/lib/module/components/tag-input/tag-input-state.js +14 -0
- package/lib/module/components/tag-input/tag-input-state.js.map +1 -0
- package/lib/module/components/textarea/index.js +9 -4
- package/lib/module/components/textarea/index.js.map +1 -1
- package/lib/module/components/textarea/textarea-disabled.js +6 -0
- package/lib/module/components/textarea/textarea-disabled.js.map +1 -0
- package/lib/module/components/timeline/index.js +352 -47
- package/lib/module/components/timeline/index.js.map +1 -1
- package/lib/module/components/timeline/timeline-geometry.js +11 -0
- package/lib/module/components/timeline/timeline-geometry.js.map +1 -0
- package/lib/module/components/toast/index.js +18 -1
- package/lib/module/components/toast/index.js.map +1 -1
- package/lib/module/components/toast/toast-store.js +36 -4
- package/lib/module/components/toast/toast-store.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/tour/index.js +4 -2
- package/lib/module/components/tour/index.js.map +1 -1
- package/lib/module/components/tour/tour-card-measurement.js +24 -0
- package/lib/module/components/tour/tour-card-measurement.js.map +1 -0
- 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 +3 -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/avatar/avatar-group.d.ts +46 -0
- package/lib/typescript/src/components/avatar/avatar-group.d.ts.map +1 -0
- package/lib/typescript/src/components/avatar/index.d.ts +23 -1
- package/lib/typescript/src/components/avatar/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/color-picker-accessibility.d.ts +8 -0
- package/lib/typescript/src/components/color-picker/color-picker-accessibility.d.ts.map +1 -0
- package/lib/typescript/src/components/color-picker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/date-time-picker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/drawer/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/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input/input-content-padding.d.ts +2 -0
- package/lib/typescript/src/components/input/input-content-padding.d.ts.map +1 -0
- package/lib/typescript/src/components/input-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input-group/input-group-measurements.d.ts +16 -0
- package/lib/typescript/src/components/input-group/input-group-measurements.d.ts.map +1 -0
- 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/otp-input/index.d.ts.map +1 -1
- package/lib/typescript/src/components/otp-input/otp-input-state.d.ts +3 -0
- package/lib/typescript/src/components/otp-input/otp-input-state.d.ts.map +1 -0
- 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/planner/planner-today.d.ts +2 -0
- package/lib/typescript/src/components/planner/planner-today.d.ts.map +1 -0
- package/lib/typescript/src/components/plot/index.d.ts +60 -6
- package/lib/typescript/src/components/plot/index.d.ts.map +1 -1
- package/lib/typescript/src/components/plot/plot-series-registry.d.ts +8 -0
- package/lib/typescript/src/components/plot/plot-series-registry.d.ts.map +1 -0
- package/lib/typescript/src/components/radio-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/radio-group/radio-group-value.d.ts +2 -0
- package/lib/typescript/src/components/radio-group/radio-group-value.d.ts.map +1 -0
- package/lib/typescript/src/components/rating/index.d.ts.map +1 -1
- package/lib/typescript/src/components/rating/rating-inputs.d.ts +4 -0
- package/lib/typescript/src/components/rating/rating-inputs.d.ts.map +1 -0
- 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/search-bar/search-bar-debounce.d.ts +7 -0
- package/lib/typescript/src/components/search-bar/search-bar-debounce.d.ts.map +1 -0
- package/lib/typescript/src/components/select/index.d.ts.map +1 -1
- package/lib/typescript/src/components/select/native-select-contract.d.ts +11 -0
- package/lib/typescript/src/components/select/native-select-contract.d.ts.map +1 -0
- package/lib/typescript/src/components/split-view/index.d.ts +159 -0
- package/lib/typescript/src/components/split-view/index.d.ts.map +1 -0
- package/lib/typescript/src/components/split-view/split-view-math.d.ts +54 -0
- package/lib/typescript/src/components/split-view/split-view-math.d.ts.map +1 -0
- package/lib/typescript/src/components/splitter/index.d.ts +78 -0
- package/lib/typescript/src/components/splitter/index.d.ts.map +1 -0
- package/lib/typescript/src/components/splitter/splitter-math.d.ts +67 -0
- package/lib/typescript/src/components/splitter/splitter-math.d.ts.map +1 -0
- package/lib/typescript/src/components/steps/index.d.ts +2 -2
- package/lib/typescript/src/components/steps/index.d.ts.map +1 -1
- package/lib/typescript/src/components/steps/steps-trigger.d.ts +2 -0
- package/lib/typescript/src/components/steps/steps-trigger.d.ts.map +1 -0
- package/lib/typescript/src/components/tag-input/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tag-input/tag-input-state.d.ts +12 -0
- package/lib/typescript/src/components/tag-input/tag-input-state.d.ts.map +1 -0
- package/lib/typescript/src/components/textarea/index.d.ts.map +1 -1
- package/lib/typescript/src/components/textarea/textarea-disabled.d.ts +2 -0
- package/lib/typescript/src/components/textarea/textarea-disabled.d.ts.map +1 -0
- package/lib/typescript/src/components/timeline/index.d.ts +36 -2
- package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
- package/lib/typescript/src/components/timeline/timeline-geometry.d.ts +4 -0
- package/lib/typescript/src/components/timeline/timeline-geometry.d.ts.map +1 -0
- package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
- package/lib/typescript/src/components/toast/toast-store.d.ts +7 -2
- package/lib/typescript/src/components/toast/toast-store.d.ts.map +1 -1
- package/lib/typescript/src/components/tour/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tour/tour-card-measurement.d.ts +14 -0
- package/lib/typescript/src/components/tour/tour-card-measurement.d.ts.map +1 -0
- 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 +5 -2
- 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/avatar/avatar-group.ts +78 -0
- package/src/components/avatar/index.tsx +121 -1
- 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/color-picker-accessibility.ts +12 -0
- package/src/components/color-picker/index.tsx +30 -10
- package/src/components/date-time-picker/index.tsx +11 -3
- package/src/components/drawer/index.tsx +42 -14
- 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/index.tsx +6 -2
- package/src/components/input/input-content-padding.ts +3 -0
- package/src/components/input-group/index.tsx +45 -12
- package/src/components/input-group/input-group-measurements.ts +98 -0
- 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/otp-input/index.tsx +3 -1
- package/src/components/otp-input/otp-input-state.ts +7 -0
- 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 +44 -11
- package/src/components/planner/planner-today.ts +5 -0
- package/src/components/plot/index.tsx +199 -51
- package/src/components/plot/plot-series-registry.ts +41 -0
- package/src/components/post/index.tsx +4 -4
- package/src/components/radio-group/index.tsx +4 -1
- package/src/components/radio-group/radio-group-value.ts +9 -0
- package/src/components/rating/index.tsx +11 -4
- package/src/components/rating/rating-inputs.ts +19 -0
- package/src/components/search-bar/index.tsx +441 -0
- package/src/components/search-bar/search-bar-debounce.ts +35 -0
- package/src/components/select/index.tsx +10 -1
- package/src/components/select/native-select-contract.ts +12 -0
- package/src/components/split-view/index.tsx +565 -0
- package/src/components/split-view/split-view-math.ts +146 -0
- package/src/components/splitter/index.tsx +589 -0
- package/src/components/splitter/splitter-math.ts +244 -0
- package/src/components/steps/index.tsx +35 -9
- package/src/components/steps/steps-trigger.ts +6 -0
- package/src/components/table/index.tsx +2 -2
- package/src/components/tag-input/index.tsx +13 -11
- package/src/components/tag-input/tag-input-state.ts +18 -0
- package/src/components/textarea/index.tsx +8 -4
- package/src/components/textarea/textarea-disabled.ts +6 -0
- package/src/components/timeline/index.tsx +405 -46
- package/src/components/timeline/timeline-geometry.ts +9 -0
- package/src/components/toast/index.tsx +26 -1
- package/src/components/toast/toast-store.ts +43 -8
- package/src/components/toggle-button/index.tsx +1 -1
- package/src/components/tour/index.tsx +11 -3
- package/src/components/tour/tour-card-measurement.ts +29 -0
- package/src/components/tree/index.tsx +17 -12
- package/src/hooks/use-keyboard-avoidance.ts +7 -2
- package/src/index.ts +19 -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,21 +44,69 @@
|
|
|
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';
|
|
47
67
|
import { Text, type TextProps, textChildren } from '../../primitives/text';
|
|
68
|
+
import { timelineColumnWidth } from './timeline-geometry';
|
|
48
69
|
|
|
49
70
|
export type TimelineVariant = 'dot' | 'icon' | 'numbered' | 'card' | 'compact';
|
|
50
71
|
/** Semantic colour for a single event, independent of progress. */
|
|
51
72
|
export type TimelineTone = 'default' | 'info' | 'success' | 'warning' | 'danger';
|
|
73
|
+
/** Which way the sequence runs. */
|
|
74
|
+
export type TimelineOrientation = 'vertical' | 'horizontal';
|
|
75
|
+
|
|
76
|
+
const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView);
|
|
77
|
+
|
|
78
|
+
/*
|
|
79
|
+
* Horizontal geometry.
|
|
80
|
+
*
|
|
81
|
+
* The rail has to land on exactly the same line in every column, and the
|
|
82
|
+
* simplest way to guarantee that is to make the band above it a fixed height
|
|
83
|
+
* rather than to position the rail against measured content. So the aside is
|
|
84
|
+
* `HORIZONTAL_RAIL_TOP` tall in every column, the rail is drawn once across the
|
|
85
|
+
* whole track at that offset, and each column's tick is pulled up by half its
|
|
86
|
+
* own height to sit centred on it.
|
|
87
|
+
*/
|
|
88
|
+
const HORIZONTAL_RAIL_TOP = 64;
|
|
89
|
+
const HORIZONTAL_TICK_HEIGHT = 10;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* How wide a column is: what it was given, else what its contents ask for.
|
|
93
|
+
*
|
|
94
|
+
* Read by the item to size itself and by the root to build the snap offsets, so
|
|
95
|
+
* it lives here rather than in either — the two disagreeing would put every
|
|
96
|
+
* snap point half a column out.
|
|
97
|
+
*/
|
|
98
|
+
function itemWidth(props: TimelineItemProps): number {
|
|
99
|
+
let filled = false;
|
|
100
|
+
Children.forEach(props.children, (child) => {
|
|
101
|
+
if (filled || !isValidElement(child)) return;
|
|
102
|
+
const displayName = (child.type as { displayName?: string })?.displayName;
|
|
103
|
+
if (displayName !== 'Timeline.Content') return;
|
|
104
|
+
const inner = (child.props as { children?: ReactNode }).children;
|
|
105
|
+
if (inner !== undefined && inner !== null && inner !== false) filled = true;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return timelineColumnWidth(props.width, filled);
|
|
109
|
+
}
|
|
52
110
|
|
|
53
111
|
/** Variants whose node is a filled disc rather than an outlined ring. */
|
|
54
112
|
const SOLID_VARIANTS: TimelineVariant[] = ['dot', 'card'];
|
|
@@ -107,11 +165,29 @@ const timelineVariants = tv({
|
|
|
107
165
|
true: { separator: 'bg-primary' },
|
|
108
166
|
false: { separator: 'bg-muted' },
|
|
109
167
|
},
|
|
168
|
+
/*
|
|
169
|
+
* Horizontal turns the item from a row into a column and the rail from a
|
|
170
|
+
* line down the side into a line across the top. The aside is a fixed
|
|
171
|
+
* height because that height *is* where the rail sits — see
|
|
172
|
+
* HORIZONTAL_RAIL_TOP — and it is left-aligned rather than right, since a
|
|
173
|
+
* column reads from its own left edge rather than towards a rail beside it.
|
|
174
|
+
*/
|
|
175
|
+
orientation: {
|
|
176
|
+
vertical: {},
|
|
177
|
+
horizontal: {
|
|
178
|
+
item: 'w-auto shrink-0 flex-col gap-0 pr-6',
|
|
179
|
+
aside: 'w-auto items-start justify-end gap-1 pb-2 pt-0',
|
|
180
|
+
rail: 'w-full items-start',
|
|
181
|
+
body: 'w-auto flex-none pb-0 pt-5',
|
|
182
|
+
title: 'text-sm',
|
|
183
|
+
},
|
|
184
|
+
},
|
|
110
185
|
},
|
|
111
186
|
defaultVariants: {
|
|
112
187
|
variant: 'dot',
|
|
113
188
|
tone: 'default',
|
|
114
189
|
completed: false,
|
|
190
|
+
orientation: 'vertical',
|
|
115
191
|
},
|
|
116
192
|
});
|
|
117
193
|
|
|
@@ -169,6 +245,13 @@ const TONE_ICON_VAR: Record<TimelineTone, string> = {
|
|
|
169
245
|
interface TimelineContextValue {
|
|
170
246
|
activeStep: number;
|
|
171
247
|
variant: TimelineVariant;
|
|
248
|
+
orientation: TimelineOrientation;
|
|
249
|
+
/** Horizontal only: where the track has been scrolled to, on the UI thread. */
|
|
250
|
+
scrollX: SharedValue<number>;
|
|
251
|
+
/** Horizontal only: each column's left edge, indexed by its `step`. */
|
|
252
|
+
offsets: number[];
|
|
253
|
+
/** Horizontal only: false when the reader has asked for less movement. */
|
|
254
|
+
animate: boolean;
|
|
172
255
|
}
|
|
173
256
|
|
|
174
257
|
interface TimelineItemContextValue {
|
|
@@ -177,6 +260,15 @@ interface TimelineItemContextValue {
|
|
|
177
260
|
tone: TimelineTone;
|
|
178
261
|
/** False on the last item, so its rail does not trail into nothing. */
|
|
179
262
|
showSeparator: boolean;
|
|
263
|
+
/**
|
|
264
|
+
* Horizontal only: where this column starts, in points, and how wide it is.
|
|
265
|
+
*
|
|
266
|
+
* Both are worked out by the item, and the parts inside it need the same two
|
|
267
|
+
* numbers to fade against the scroll. Passing them down beats deriving them
|
|
268
|
+
* twice from measurements that would then be free to disagree.
|
|
269
|
+
*/
|
|
270
|
+
offset: number;
|
|
271
|
+
columnWidth: number;
|
|
180
272
|
}
|
|
181
273
|
|
|
182
274
|
const TimelineContext = createContext<TimelineContextValue | null>(null);
|
|
@@ -199,18 +291,120 @@ export interface TimelineProps extends ViewProps {
|
|
|
199
291
|
/** Steps at or below this index render as completed. */
|
|
200
292
|
value?: number;
|
|
201
293
|
variant?: TimelineVariant;
|
|
294
|
+
/**
|
|
295
|
+
* Which way the sequence runs. `horizontal` lays the items out as columns on
|
|
296
|
+
* a rail wider than the screen, swiped through rather than scrolled down.
|
|
297
|
+
*/
|
|
298
|
+
orientation?: TimelineOrientation;
|
|
299
|
+
/**
|
|
300
|
+
* Horizontal only: land a flick on a column rather than between two.
|
|
301
|
+
*
|
|
302
|
+
* On by default, because the thing being moved between is a column — stopping
|
|
303
|
+
* halfway shows two half-columns and no whole one.
|
|
304
|
+
*/
|
|
305
|
+
snap?: boolean;
|
|
202
306
|
children?: ReactNode;
|
|
203
307
|
}
|
|
204
308
|
|
|
205
309
|
const TimelineRoot = forwardRef<View, TimelineProps>(
|
|
206
|
-
(
|
|
207
|
-
|
|
208
|
-
|
|
310
|
+
(
|
|
311
|
+
{
|
|
312
|
+
className,
|
|
313
|
+
value = 0,
|
|
314
|
+
variant = 'dot',
|
|
315
|
+
orientation = 'vertical',
|
|
316
|
+
snap = true,
|
|
317
|
+
children,
|
|
318
|
+
...props
|
|
319
|
+
},
|
|
320
|
+
ref
|
|
321
|
+
) => {
|
|
322
|
+
const horizontal = orientation === 'horizontal';
|
|
323
|
+
const { root } = timelineVariants({ variant, orientation });
|
|
324
|
+
const reducedMotion = useReducedMotion();
|
|
325
|
+
const scrollX = useSharedValue(0);
|
|
326
|
+
const border = useCSSVariable('--color-border');
|
|
327
|
+
|
|
328
|
+
/*
|
|
329
|
+
* The columns' left edges, from the same width rule the items size
|
|
330
|
+
* themselves by. These are the snap points, and they are what an item reads
|
|
331
|
+
* to know how far it is from the reading edge.
|
|
332
|
+
*/
|
|
333
|
+
const offsets = useMemo(() => {
|
|
334
|
+
if (!horizontal) return [];
|
|
335
|
+
const result: number[] = [];
|
|
336
|
+
let sum = 0;
|
|
337
|
+
Children.forEach(children, (child) => {
|
|
338
|
+
if (!isValidElement(child)) return;
|
|
339
|
+
result.push(sum);
|
|
340
|
+
sum += itemWidth(child.props as TimelineItemProps);
|
|
341
|
+
});
|
|
342
|
+
return result;
|
|
343
|
+
}, [children, horizontal]);
|
|
344
|
+
|
|
345
|
+
const context = useMemo(
|
|
346
|
+
() => ({
|
|
347
|
+
activeStep: value,
|
|
348
|
+
variant,
|
|
349
|
+
orientation,
|
|
350
|
+
scrollX,
|
|
351
|
+
offsets,
|
|
352
|
+
animate: !reducedMotion,
|
|
353
|
+
}),
|
|
354
|
+
[value, variant, orientation, scrollX, offsets, reducedMotion]
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const onScroll = useAnimatedScrollHandler((event) => {
|
|
358
|
+
scrollX.value = event.contentOffset.x;
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
if (!horizontal) {
|
|
362
|
+
return (
|
|
363
|
+
<TimelineContext.Provider value={context}>
|
|
364
|
+
<View ref={ref} className={root({ className })} {...props}>
|
|
365
|
+
{textChildren(children)}
|
|
366
|
+
</View>
|
|
367
|
+
</TimelineContext.Provider>
|
|
368
|
+
);
|
|
369
|
+
}
|
|
209
370
|
|
|
210
371
|
return (
|
|
211
372
|
<TimelineContext.Provider value={context}>
|
|
212
373
|
<View ref={ref} className={root({ className })} {...props}>
|
|
213
|
-
|
|
374
|
+
<AnimatedScrollView
|
|
375
|
+
horizontal
|
|
376
|
+
onScroll={onScroll}
|
|
377
|
+
scrollEventThrottle={16}
|
|
378
|
+
showsHorizontalScrollIndicator={false}
|
|
379
|
+
decelerationRate="fast"
|
|
380
|
+
snapToAlignment="start"
|
|
381
|
+
snapToOffsets={snap ? offsets : undefined}
|
|
382
|
+
>
|
|
383
|
+
{/*
|
|
384
|
+
The track sizes itself to its columns, so the rail stretched
|
|
385
|
+
between its own left and right edges runs the whole length of the
|
|
386
|
+
sequence rather than the width of the screen.
|
|
387
|
+
*/}
|
|
388
|
+
<View className="relative flex-row">
|
|
389
|
+
<View
|
|
390
|
+
accessibilityElementsHidden
|
|
391
|
+
importantForAccessibility="no-hide-descendants"
|
|
392
|
+
pointerEvents="none"
|
|
393
|
+
style={{
|
|
394
|
+
position: 'absolute',
|
|
395
|
+
left: 0,
|
|
396
|
+
right: 0,
|
|
397
|
+
top: HORIZONTAL_RAIL_TOP,
|
|
398
|
+
borderTopWidth: 1,
|
|
399
|
+
// Dashed borders only render at a radius of zero, which this
|
|
400
|
+
// has, and the rule is the whole element.
|
|
401
|
+
borderStyle: 'dashed',
|
|
402
|
+
borderColor: typeof border === 'string' ? border : undefined,
|
|
403
|
+
}}
|
|
404
|
+
/>
|
|
405
|
+
{textChildren(children)}
|
|
406
|
+
</View>
|
|
407
|
+
</AnimatedScrollView>
|
|
214
408
|
</View>
|
|
215
409
|
</TimelineContext.Provider>
|
|
216
410
|
);
|
|
@@ -228,28 +422,108 @@ export interface TimelineItemProps extends ViewProps {
|
|
|
228
422
|
tone?: TimelineTone;
|
|
229
423
|
/** Set on the final item so its rail stops at the indicator. */
|
|
230
424
|
last?: boolean;
|
|
425
|
+
/**
|
|
426
|
+
* Horizontal only: how wide this column is, in points.
|
|
427
|
+
*
|
|
428
|
+
* Left out, a column that carries content takes a readable width and one that
|
|
429
|
+
* carries none collapses to a tick — so a quiet stretch of the sequence
|
|
430
|
+
* compresses instead of paying full width for nothing. Set it to override
|
|
431
|
+
* that for a column that needs more or less room than its contents suggest.
|
|
432
|
+
* It must be finite and greater than zero; invalid values use the content default.
|
|
433
|
+
*/
|
|
434
|
+
width?: number;
|
|
231
435
|
children?: ReactNode;
|
|
232
436
|
}
|
|
233
437
|
|
|
234
438
|
const TimelineItem = forwardRef<View, TimelineItemProps>(
|
|
235
439
|
(
|
|
236
|
-
{
|
|
440
|
+
{
|
|
441
|
+
className,
|
|
442
|
+
step,
|
|
443
|
+
completed,
|
|
444
|
+
tone = 'default',
|
|
445
|
+
last = false,
|
|
446
|
+
width,
|
|
447
|
+
style,
|
|
448
|
+
children,
|
|
449
|
+
...props
|
|
450
|
+
},
|
|
237
451
|
ref
|
|
238
452
|
) => {
|
|
239
|
-
const { activeStep, variant } =
|
|
453
|
+
const { activeStep, variant, orientation, scrollX, offsets, animate } =
|
|
454
|
+
useTimeline('Timeline.Item');
|
|
240
455
|
const isCompleted = completed ?? step <= activeStep;
|
|
241
|
-
const { item } = timelineVariants({
|
|
456
|
+
const { item } = timelineVariants({
|
|
457
|
+
variant,
|
|
458
|
+
tone,
|
|
459
|
+
completed: isCompleted,
|
|
460
|
+
orientation,
|
|
461
|
+
});
|
|
462
|
+
const horizontal = orientation === 'horizontal';
|
|
463
|
+
|
|
464
|
+
const columnWidth = horizontal
|
|
465
|
+
? itemWidth({ step, width, children } as TimelineItemProps)
|
|
466
|
+
: undefined;
|
|
467
|
+
const offset = offsets[step] ?? 0;
|
|
242
468
|
|
|
243
469
|
const context = useMemo(
|
|
244
|
-
() => ({
|
|
245
|
-
|
|
470
|
+
() => ({
|
|
471
|
+
step,
|
|
472
|
+
completed: isCompleted,
|
|
473
|
+
tone,
|
|
474
|
+
showSeparator: !last,
|
|
475
|
+
offset,
|
|
476
|
+
columnWidth: columnWidth ?? 0,
|
|
477
|
+
}),
|
|
478
|
+
[step, isCompleted, tone, last, offset, columnWidth]
|
|
246
479
|
);
|
|
247
480
|
|
|
481
|
+
/*
|
|
482
|
+
* A column recedes as it leaves the reading edge, so the one being read is
|
|
483
|
+
* the one that looks read.
|
|
484
|
+
*
|
|
485
|
+
* One column's width either side, not two: a window wide enough to hold
|
|
486
|
+
* three columns near full strength is a row where nothing is picked out,
|
|
487
|
+
* and picking one out is the whole job. The neighbours stay legible at the
|
|
488
|
+
* far end of it — a timeline is worth reading for what came before and
|
|
489
|
+
* after — they simply stop competing.
|
|
490
|
+
*
|
|
491
|
+
* The scale and the drop are what turn a fade into a focus. Four points and
|
|
492
|
+
* four percent is under the threshold where anybody would call it movement,
|
|
493
|
+
* and over the one where the column at the edge stops looking flat against
|
|
494
|
+
* the rest.
|
|
495
|
+
*/
|
|
496
|
+
const columnStyle = useAnimatedStyle(() => {
|
|
497
|
+
if (!horizontal || !animate) return { opacity: 1, transform: [] };
|
|
498
|
+
const distance = Math.abs(scrollX.value - offset);
|
|
499
|
+
const window = Math.max(columnWidth ?? timelineColumnWidth(undefined, true), 1);
|
|
500
|
+
const away = interpolate(distance, [0, window], [0, 1], 'clamp');
|
|
501
|
+
return {
|
|
502
|
+
opacity: 1 - away * 0.4,
|
|
503
|
+
transform: [{ scale: 1 - away * 0.04 }, { translateY: away * 4 }],
|
|
504
|
+
};
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
if (!horizontal) {
|
|
508
|
+
return (
|
|
509
|
+
<TimelineItemContext.Provider value={context}>
|
|
510
|
+
<View ref={ref} className={item({ className })} style={style} {...props}>
|
|
511
|
+
{textChildren(children)}
|
|
512
|
+
</View>
|
|
513
|
+
</TimelineItemContext.Provider>
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
|
|
248
517
|
return (
|
|
249
518
|
<TimelineItemContext.Provider value={context}>
|
|
250
|
-
<View
|
|
519
|
+
<Animated.View
|
|
520
|
+
ref={ref}
|
|
521
|
+
className={item({ className })}
|
|
522
|
+
{...props}
|
|
523
|
+
style={[style, { width: columnWidth }, columnStyle]}
|
|
524
|
+
>
|
|
251
525
|
{textChildren(children)}
|
|
252
|
-
</View>
|
|
526
|
+
</Animated.View>
|
|
253
527
|
</TimelineItemContext.Provider>
|
|
254
528
|
);
|
|
255
529
|
}
|
|
@@ -261,10 +535,28 @@ TimelineItem.displayName = 'Timeline.Item';
|
|
|
261
535
|
* person. Place it before `Timeline.Indicator`.
|
|
262
536
|
*/
|
|
263
537
|
const TimelineAside = forwardRef<View, ViewProps & { className?: string }>(
|
|
264
|
-
({ className, ...props }, ref) => {
|
|
265
|
-
const { variant } = useTimeline('Timeline.Aside');
|
|
266
|
-
const { aside } = timelineVariants({ variant });
|
|
267
|
-
|
|
538
|
+
({ className, style, ...props }, ref) => {
|
|
539
|
+
const { variant, orientation } = useTimeline('Timeline.Aside');
|
|
540
|
+
const { aside } = timelineVariants({ variant, orientation });
|
|
541
|
+
|
|
542
|
+
return (
|
|
543
|
+
<View
|
|
544
|
+
ref={ref}
|
|
545
|
+
className={aside({ className })}
|
|
546
|
+
/*
|
|
547
|
+
* Horizontal: this band's height is where the rail lands, so it is
|
|
548
|
+
* fixed rather than sized to whatever the column happens to put in it.
|
|
549
|
+
* A column with a longer label would otherwise push its own tick below
|
|
550
|
+
* everybody else's and the rail would stop being a line.
|
|
551
|
+
*/
|
|
552
|
+
style={
|
|
553
|
+
orientation === 'horizontal'
|
|
554
|
+
? [{ height: HORIZONTAL_RAIL_TOP }, style]
|
|
555
|
+
: style
|
|
556
|
+
}
|
|
557
|
+
{...props}
|
|
558
|
+
/>
|
|
559
|
+
);
|
|
268
560
|
}
|
|
269
561
|
);
|
|
270
562
|
TimelineAside.displayName = 'Timeline.Aside';
|
|
@@ -284,15 +576,43 @@ export interface TimelineIndicatorProps extends ViewProps {
|
|
|
284
576
|
*/
|
|
285
577
|
const TimelineIndicator = forwardRef<View, TimelineIndicatorProps>(
|
|
286
578
|
({ className, children, ...props }, ref) => {
|
|
287
|
-
const { variant } = useTimeline('Timeline.Indicator');
|
|
579
|
+
const { variant, orientation } = useTimeline('Timeline.Indicator');
|
|
288
580
|
const { step, completed, tone, showSeparator } =
|
|
289
581
|
useTimelineItem('Timeline.Indicator');
|
|
290
582
|
const { rail, indicator, indicatorLabel, separator } = timelineVariants({
|
|
291
583
|
variant,
|
|
292
584
|
tone,
|
|
293
585
|
completed,
|
|
586
|
+
orientation,
|
|
294
587
|
});
|
|
295
588
|
|
|
589
|
+
/*
|
|
590
|
+
* Horizontal: a tick sitting on the rail, not a node with a connector
|
|
591
|
+
* running out of it. The rail is already drawn across the whole track by
|
|
592
|
+
* the root, so what a column owes it is the mark saying where this entry
|
|
593
|
+
* falls — pulled up by half its height to straddle the line rather than
|
|
594
|
+
* hang off it.
|
|
595
|
+
*/
|
|
596
|
+
if (orientation === 'horizontal') {
|
|
597
|
+
return (
|
|
598
|
+
<View className={rail()}>
|
|
599
|
+
<View
|
|
600
|
+
ref={ref}
|
|
601
|
+
accessibilityElementsHidden
|
|
602
|
+
importantForAccessibility="no-hide-descendants"
|
|
603
|
+
className={indicator({
|
|
604
|
+
className: `w-px rounded-none ${completed ? 'bg-primary' : 'bg-muted-foreground'} ${className ?? ''}`,
|
|
605
|
+
})}
|
|
606
|
+
style={{
|
|
607
|
+
height: HORIZONTAL_TICK_HEIGHT,
|
|
608
|
+
marginTop: -HORIZONTAL_TICK_HEIGHT / 2,
|
|
609
|
+
}}
|
|
610
|
+
{...props}
|
|
611
|
+
/>
|
|
612
|
+
</View>
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
|
|
296
616
|
// dot/card nodes are small discs with nothing inside, so they keep full
|
|
297
617
|
// saturation — a soft tint at 16px would disappear. Nodes that hold an
|
|
298
618
|
// icon or a number take the tinted fill instead.
|
|
@@ -351,15 +671,54 @@ TimelineIndicator.displayName = 'Timeline.Indicator';
|
|
|
351
671
|
* so the connector runs unbroken.
|
|
352
672
|
*/
|
|
353
673
|
const TimelineContent = forwardRef<View, ViewProps & { className?: string }>(
|
|
354
|
-
({ className, children, ...props }, ref) => {
|
|
355
|
-
const { variant } = useTimeline('Timeline.Content');
|
|
356
|
-
const { completed, tone } =
|
|
357
|
-
|
|
674
|
+
({ className, children, style, ...props }, ref) => {
|
|
675
|
+
const { variant, orientation, scrollX, animate } = useTimeline('Timeline.Content');
|
|
676
|
+
const { completed, tone, offset, columnWidth } =
|
|
677
|
+
useTimelineItem('Timeline.Content');
|
|
678
|
+
const { body, panel } = timelineVariants({ variant, tone, completed, orientation });
|
|
679
|
+
const horizontal = orientation === 'horizontal';
|
|
680
|
+
|
|
681
|
+
/*
|
|
682
|
+
* Horizontal: the body recedes further than the column around it.
|
|
683
|
+
*
|
|
684
|
+
* The rail is a continuous thing and has to stay readable across the whole
|
|
685
|
+
* track — the years either side of the one being read are half of what a
|
|
686
|
+
* timeline is for. The prose under them is not: several columns of it at
|
|
687
|
+
* equal weight is a wall, and no amount of fading the column as a whole
|
|
688
|
+
* fixes that without taking the years down with it. So the body takes a
|
|
689
|
+
* second, steeper curve and the dates keep the first one.
|
|
690
|
+
*/
|
|
691
|
+
const bodyStyle = useAnimatedStyle(() => {
|
|
692
|
+
if (!horizontal || !animate) return { opacity: 1 };
|
|
693
|
+
const distance = Math.abs(scrollX.value - offset);
|
|
694
|
+
const window = Math.max(columnWidth, 1);
|
|
695
|
+
return { opacity: interpolate(distance, [0, window], [1, 0.3], 'clamp') };
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
const inner =
|
|
699
|
+
variant === 'card' ? (
|
|
700
|
+
<View className={panel()}>{textChildren(children)}</View>
|
|
701
|
+
) : (
|
|
702
|
+
children
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
if (!horizontal) {
|
|
706
|
+
return (
|
|
707
|
+
<View ref={ref} className={body({ className })} style={style} {...props}>
|
|
708
|
+
{inner}
|
|
709
|
+
</View>
|
|
710
|
+
);
|
|
711
|
+
}
|
|
358
712
|
|
|
359
713
|
return (
|
|
360
|
-
<View
|
|
361
|
-
|
|
362
|
-
|
|
714
|
+
<Animated.View
|
|
715
|
+
ref={ref}
|
|
716
|
+
className={body({ className })}
|
|
717
|
+
{...props}
|
|
718
|
+
style={[style, bodyStyle]}
|
|
719
|
+
>
|
|
720
|
+
{inner}
|
|
721
|
+
</Animated.View>
|
|
363
722
|
);
|
|
364
723
|
}
|
|
365
724
|
);
|
|
@@ -368,8 +727,8 @@ TimelineContent.displayName = 'Timeline.Content';
|
|
|
368
727
|
/** Title row: heading on the left, `Timeline.Trailing` on the right. */
|
|
369
728
|
const TimelineHeader = forwardRef<View, ViewProps & { className?: string }>(
|
|
370
729
|
({ className, ...props }, ref) => {
|
|
371
|
-
const { variant } = useTimeline('Timeline.Header');
|
|
372
|
-
const { header } = timelineVariants({ variant });
|
|
730
|
+
const { variant, orientation } = useTimeline('Timeline.Header');
|
|
731
|
+
const { header } = timelineVariants({ variant, orientation });
|
|
373
732
|
return <View ref={ref} className={header({ className })} {...props} />;
|
|
374
733
|
}
|
|
375
734
|
);
|
|
@@ -378,40 +737,40 @@ TimelineHeader.displayName = 'Timeline.Header';
|
|
|
378
737
|
/** Wraps a title and anything stacked under it inside the header row. */
|
|
379
738
|
const TimelineHeading = forwardRef<View, ViewProps & { className?: string }>(
|
|
380
739
|
({ className, ...props }, ref) => {
|
|
381
|
-
const { variant } = useTimeline('Timeline.Heading');
|
|
382
|
-
const { heading } = timelineVariants({ variant });
|
|
740
|
+
const { variant, orientation } = useTimeline('Timeline.Heading');
|
|
741
|
+
const { heading } = timelineVariants({ variant, orientation });
|
|
383
742
|
return <View ref={ref} className={heading({ className })} {...props} />;
|
|
384
743
|
}
|
|
385
744
|
);
|
|
386
745
|
TimelineHeading.displayName = 'Timeline.Heading';
|
|
387
746
|
|
|
388
747
|
const TimelineDate = forwardRef<RNText, TextProps>(({ className, ...props }, ref) => {
|
|
389
|
-
const { variant } = useTimeline('Timeline.Date');
|
|
390
|
-
const { date } = timelineVariants({ variant });
|
|
748
|
+
const { variant, orientation } = useTimeline('Timeline.Date');
|
|
749
|
+
const { date } = timelineVariants({ variant, orientation });
|
|
391
750
|
return <Text ref={ref} className={date({ className })} {...props} />;
|
|
392
751
|
});
|
|
393
752
|
TimelineDate.displayName = 'Timeline.Date';
|
|
394
753
|
|
|
395
754
|
/** Category line in the aside, coloured by the item's tone. */
|
|
396
755
|
const TimelineLabel = forwardRef<RNText, TextProps>(({ className, ...props }, ref) => {
|
|
397
|
-
const { variant } = useTimeline('Timeline.Label');
|
|
756
|
+
const { variant, orientation } = useTimeline('Timeline.Label');
|
|
398
757
|
const { tone } = useTimelineItem('Timeline.Label');
|
|
399
|
-
const { label } = timelineVariants({ variant, tone });
|
|
758
|
+
const { label } = timelineVariants({ variant, tone, orientation });
|
|
400
759
|
return <Text ref={ref} className={label({ className })} {...props} />;
|
|
401
760
|
});
|
|
402
761
|
TimelineLabel.displayName = 'Timeline.Label';
|
|
403
762
|
|
|
404
763
|
/** Muted supporting line — a person's name, a source. */
|
|
405
764
|
const TimelineMeta = forwardRef<RNText, TextProps>(({ className, ...props }, ref) => {
|
|
406
|
-
const { variant } = useTimeline('Timeline.Meta');
|
|
407
|
-
const { meta } = timelineVariants({ variant });
|
|
765
|
+
const { variant, orientation } = useTimeline('Timeline.Meta');
|
|
766
|
+
const { meta } = timelineVariants({ variant, orientation });
|
|
408
767
|
return <Text ref={ref} className={meta({ className })} {...props} />;
|
|
409
768
|
});
|
|
410
769
|
TimelineMeta.displayName = 'Timeline.Meta';
|
|
411
770
|
|
|
412
771
|
const TimelineTitle = forwardRef<RNText, TextProps>(({ className, ...props }, ref) => {
|
|
413
|
-
const { variant } = useTimeline('Timeline.Title');
|
|
414
|
-
const { title } = timelineVariants({ variant });
|
|
772
|
+
const { variant, orientation } = useTimeline('Timeline.Title');
|
|
773
|
+
const { title } = timelineVariants({ variant, orientation });
|
|
415
774
|
return <Text ref={ref} className={title({ className })} {...props} />;
|
|
416
775
|
});
|
|
417
776
|
TimelineTitle.displayName = 'Timeline.Title';
|
|
@@ -419,8 +778,8 @@ TimelineTitle.displayName = 'Timeline.Title';
|
|
|
419
778
|
/** Right-hand slot in the header row — a timestamp, usually. */
|
|
420
779
|
const TimelineTrailing = forwardRef<RNText, TextProps>(
|
|
421
780
|
({ className, ...props }, ref) => {
|
|
422
|
-
const { variant } = useTimeline('Timeline.Trailing');
|
|
423
|
-
const { trailing } = timelineVariants({ variant });
|
|
781
|
+
const { variant, orientation } = useTimeline('Timeline.Trailing');
|
|
782
|
+
const { trailing } = timelineVariants({ variant, orientation });
|
|
424
783
|
return <Text ref={ref} className={trailing({ className })} {...props} />;
|
|
425
784
|
}
|
|
426
785
|
);
|
|
@@ -428,8 +787,8 @@ TimelineTrailing.displayName = 'Timeline.Trailing';
|
|
|
428
787
|
|
|
429
788
|
const TimelineDescription = forwardRef<RNText, TextProps>(
|
|
430
789
|
({ className, ...props }, ref) => {
|
|
431
|
-
const { variant } = useTimeline('Timeline.Description');
|
|
432
|
-
const { content } = timelineVariants({ variant });
|
|
790
|
+
const { variant, orientation } = useTimeline('Timeline.Description');
|
|
791
|
+
const { content } = timelineVariants({ variant, orientation });
|
|
433
792
|
return <Text ref={ref} className={content({ className })} {...props} />;
|
|
434
793
|
}
|
|
435
794
|
);
|
|
@@ -438,8 +797,8 @@ TimelineDescription.displayName = 'Timeline.Description';
|
|
|
438
797
|
/** Bordered strip of label/value pairs under a title. */
|
|
439
798
|
const TimelineStats = forwardRef<View, ViewProps & { className?: string }>(
|
|
440
799
|
({ className, ...props }, ref) => {
|
|
441
|
-
const { variant } = useTimeline('Timeline.Stats');
|
|
442
|
-
const { stats } = timelineVariants({ variant });
|
|
800
|
+
const { variant, orientation } = useTimeline('Timeline.Stats');
|
|
801
|
+
const { stats } = timelineVariants({ variant, orientation });
|
|
443
802
|
return <View ref={ref} className={stats({ className })} {...props} />;
|
|
444
803
|
}
|
|
445
804
|
);
|
|
@@ -453,8 +812,8 @@ export interface TimelineStatProps extends ViewProps {
|
|
|
453
812
|
|
|
454
813
|
const TimelineStat = forwardRef<View, TimelineStatProps>(
|
|
455
814
|
({ className, label, value, ...props }, ref) => {
|
|
456
|
-
const { variant } = useTimeline('Timeline.Stat');
|
|
457
|
-
const { statLabel, statValue } = timelineVariants({ variant });
|
|
815
|
+
const { variant, orientation } = useTimeline('Timeline.Stat');
|
|
816
|
+
const { statLabel, statValue } = timelineVariants({ variant, orientation });
|
|
458
817
|
return (
|
|
459
818
|
<View ref={ref} className={className} {...props}>
|
|
460
819
|
<Text className={statLabel()}>{label}</Text>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const TIMELINE_WIDE_COLUMN = 268;
|
|
2
|
+
export const TIMELINE_NARROW_COLUMN = 76;
|
|
3
|
+
|
|
4
|
+
export function timelineColumnWidth(requested: number | undefined, filled: boolean): number {
|
|
5
|
+
if (requested !== undefined && Number.isFinite(requested) && requested > 0) {
|
|
6
|
+
return requested;
|
|
7
|
+
}
|
|
8
|
+
return filled ? TIMELINE_WIDE_COLUMN : TIMELINE_NARROW_COLUMN;
|
|
9
|
+
}
|
|
@@ -18,7 +18,12 @@ import {
|
|
|
18
18
|
useSyncExternalStore,
|
|
19
19
|
type ReactNode,
|
|
20
20
|
} from 'react';
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
AppState,
|
|
23
|
+
View,
|
|
24
|
+
type AppStateStatus,
|
|
25
|
+
type ViewProps,
|
|
26
|
+
} from 'react-native';
|
|
22
27
|
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
23
28
|
import Animated, {
|
|
24
29
|
Easing,
|
|
@@ -538,6 +543,26 @@ function ToastStack({
|
|
|
538
543
|
export function ToastViewport() {
|
|
539
544
|
const items = useSyncExternalStore(toastStore.subscribe, toastStore.getSnapshot);
|
|
540
545
|
|
|
546
|
+
useEffect(() => {
|
|
547
|
+
/*
|
|
548
|
+
* Pause on the two states that mean nobody can see the toast, rather than
|
|
549
|
+
* on everything that is not `active`. Android reports `unknown` before it
|
|
550
|
+
* has decided, and a toast raised in that window would otherwise have its
|
|
551
|
+
* countdown parked with nothing coming to restart it.
|
|
552
|
+
*/
|
|
553
|
+
const syncTimers = (state: AppStateStatus) => {
|
|
554
|
+
if (state === 'background' || state === 'inactive') toastStore.pauseTimers();
|
|
555
|
+
else toastStore.resumeTimers();
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
syncTimers(AppState.currentState);
|
|
559
|
+
const subscription = AppState.addEventListener('change', syncTimers);
|
|
560
|
+
return () => {
|
|
561
|
+
subscription.remove();
|
|
562
|
+
toastStore.pauseTimers();
|
|
563
|
+
};
|
|
564
|
+
}, []);
|
|
565
|
+
|
|
541
566
|
if (items.length === 0) return null;
|
|
542
567
|
|
|
543
568
|
const top = items.filter((item) => item.placement === 'top');
|