panelui-native 0.43.0 → 0.46.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/card/index.js +8 -154
- package/lib/module/components/card/index.js.map +1 -1
- package/lib/module/components/combobox/index.js +7 -1
- package/lib/module/components/combobox/index.js.map +1 -1
- package/lib/module/components/date-time-picker/index.js +254 -0
- package/lib/module/components/date-time-picker/index.js.map +1 -0
- package/lib/module/components/grid-item/index.js +486 -0
- package/lib/module/components/grid-item/index.js.map +1 -0
- package/lib/module/components/{kpi-chart → kpi}/index.js +62 -62
- package/lib/module/components/kpi/index.js.map +1 -0
- package/lib/module/components/line-chart/index.js +92 -10
- package/lib/module/components/line-chart/index.js.map +1 -1
- package/lib/module/components/pie-chart/index.js +625 -0
- package/lib/module/components/pie-chart/index.js.map +1 -0
- package/lib/module/components/scatter-chart/index.js +1173 -0
- package/lib/module/components/scatter-chart/index.js.map +1 -0
- package/lib/module/components/tabs/index.js +275 -33
- package/lib/module/components/tabs/index.js.map +1 -1
- package/lib/module/index.js +5 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/chart.js +81 -0
- package/lib/module/utils/chart.js.map +1 -1
- package/lib/typescript/src/components/card/index.d.ts +0 -20
- package/lib/typescript/src/components/card/index.d.ts.map +1 -1
- package/lib/typescript/src/components/combobox/index.d.ts.map +1 -1
- package/lib/typescript/src/components/date-time-picker/index.d.ts +121 -0
- package/lib/typescript/src/components/date-time-picker/index.d.ts.map +1 -0
- package/lib/typescript/src/components/grid-item/index.d.ts +292 -0
- package/lib/typescript/src/components/grid-item/index.d.ts.map +1 -0
- package/lib/typescript/src/components/{kpi-chart → kpi}/index.d.ts +65 -65
- package/lib/typescript/src/components/kpi/index.d.ts.map +1 -0
- package/lib/typescript/src/components/line-chart/index.d.ts +25 -1
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/pie-chart/index.d.ts +245 -0
- package/lib/typescript/src/components/pie-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/scatter-chart/index.d.ts +309 -0
- package/lib/typescript/src/components/scatter-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/tabs/index.d.ts +20 -2
- package/lib/typescript/src/components/tabs/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +7 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/chart.d.ts +34 -0
- package/lib/typescript/src/utils/chart.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/card/index.tsx +7 -209
- package/src/components/combobox/index.tsx +7 -1
- package/src/components/date-time-picker/index.tsx +375 -0
- package/src/components/grid-item/index.tsx +515 -0
- package/src/components/{kpi-chart → kpi}/index.tsx +85 -85
- package/src/components/line-chart/index.tsx +98 -8
- package/src/components/pie-chart/index.tsx +863 -0
- package/src/components/scatter-chart/index.tsx +1401 -0
- package/src/components/tabs/index.tsx +305 -35
- package/src/index.ts +66 -18
- package/src/utils/chart.ts +110 -0
- package/lib/module/components/kpi-chart/index.js.map +0 -1
- package/lib/typescript/src/components/kpi-chart/index.d.ts.map +0 -1
|
@@ -15,6 +15,15 @@
|
|
|
15
15
|
* <Tabs.Content value="account">…</Tabs.Content>
|
|
16
16
|
* </Tabs>
|
|
17
17
|
* ```
|
|
18
|
+
*
|
|
19
|
+
* **Swiping.** Only the visible panel is mounted, so a drag has nothing behind
|
|
20
|
+
* it to reveal. The panel still tracks the finger one to one and dims as it
|
|
21
|
+
* goes, and the panel arriving comes back up through the same fade — the
|
|
22
|
+
* movement carries the change and the dissolve covers the gap. On release the
|
|
23
|
+
* arriving panel picks up a whole panel's width from wherever the outgoing one
|
|
24
|
+
* was let go, so the two views draw one continuous movement between them. The
|
|
25
|
+
* displacement lives on the root for exactly that reason: a value belonging to
|
|
26
|
+
* either panel would be destroyed at the handover.
|
|
18
27
|
*/
|
|
19
28
|
import {
|
|
20
29
|
createContext,
|
|
@@ -35,11 +44,16 @@ import {
|
|
|
35
44
|
} from 'react-native';
|
|
36
45
|
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
37
46
|
import Animated, {
|
|
47
|
+
Easing,
|
|
48
|
+
Extrapolation,
|
|
38
49
|
FadeIn,
|
|
50
|
+
interpolate,
|
|
39
51
|
runOnJS,
|
|
40
52
|
useAnimatedStyle,
|
|
53
|
+
useReducedMotion,
|
|
41
54
|
useSharedValue,
|
|
42
55
|
withSpring,
|
|
56
|
+
withTiming,
|
|
43
57
|
type SharedValue,
|
|
44
58
|
} from 'react-native-reanimated';
|
|
45
59
|
import { tv } from 'tailwind-variants';
|
|
@@ -64,32 +78,56 @@ const SWIPE_DISTANCE_RATIO = 0.25;
|
|
|
64
78
|
const SWIPE_VELOCITY = 500;
|
|
65
79
|
|
|
66
80
|
/**
|
|
67
|
-
* How much of the finger's travel the panel
|
|
81
|
+
* How much of the finger's travel the panel follows at the ends of the row,
|
|
82
|
+
* where there is nowhere for it to go. Everywhere else it follows all of it.
|
|
83
|
+
*/
|
|
84
|
+
const SWIPE_RESISTANCE_AT_END = 0.16;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* How far the panel fades as it is dragged, at a full panel's width.
|
|
68
88
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
89
|
+
* Tracking the finger one to one leaves the space behind the panel empty,
|
|
90
|
+
* because the panels are separate views and only the visible one is mounted.
|
|
91
|
+
* Dimming as it goes is what keeps that from reading as a hole punched in the
|
|
92
|
+
* card: a panel on its way out is on its way out, and the one arriving comes
|
|
93
|
+
* back up through the same fade. The two together read as a dissolve carried
|
|
94
|
+
* by the movement, rather than as a gap between two slides.
|
|
73
95
|
*/
|
|
74
|
-
const
|
|
75
|
-
/** …and a fraction of that again at the ends, where there is nowhere to go. */
|
|
76
|
-
const SWIPE_FOLLOW_AT_END = 0.1;
|
|
96
|
+
const SWIPE_FADE = 0.75;
|
|
77
97
|
|
|
78
98
|
/**
|
|
79
|
-
* How far off its resting place
|
|
80
|
-
* panel's width.
|
|
99
|
+
* How far off its resting place a panel arriving by a *press* starts, as a
|
|
100
|
+
* share of the panel's width.
|
|
81
101
|
*
|
|
82
|
-
* A
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* roughly where the outgoing one was let go, on the far side, and travels in.
|
|
102
|
+
* A press has no finger travel to continue from, so it is given a throw of its
|
|
103
|
+
* own. A swipe does not use this: the panel it hands over to picks up exactly
|
|
104
|
+
* where the outgoing one was let go — see `setValue`.
|
|
86
105
|
*/
|
|
87
106
|
const SWIPE_ENTER = 0.3;
|
|
88
107
|
|
|
89
108
|
/** The arriving panel is springier than the one being dragged back into place. */
|
|
90
109
|
const ENTER_SPRING = { damping: 22, stiffness: 240, mass: 0.6 } as const;
|
|
91
110
|
|
|
92
|
-
|
|
111
|
+
/** How far the label's reveal is from its own width, in points — the gap after the icon. */
|
|
112
|
+
const LABEL_GAP = 6;
|
|
113
|
+
|
|
114
|
+
/** Points of room over the measured label, so a rounding error cannot truncate it. */
|
|
115
|
+
const LABEL_SLACK = 2;
|
|
116
|
+
|
|
117
|
+
/** Milliseconds for a tab to open its label, and for the one before it to close. */
|
|
118
|
+
const EXPAND_DURATION = 260;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* A width the ghost label is measured inside.
|
|
122
|
+
*
|
|
123
|
+
* A `Text` measures against the box it is in, and the box it is really in is a
|
|
124
|
+
* pill whose width is the thing being measured. So the copy that gets measured
|
|
125
|
+
* sits in a box wide enough not to be the constraint, and reports the width the
|
|
126
|
+
* label actually wants.
|
|
127
|
+
*/
|
|
128
|
+
const MEASURE_WIDTH = 400;
|
|
129
|
+
|
|
130
|
+
export type TabsVariant = 'segmented' | 'underline' | 'pill' | 'expanding';
|
|
93
131
|
|
|
94
132
|
const tabsVariants = tv({
|
|
95
133
|
slots: {
|
|
@@ -120,6 +158,22 @@ const tabsVariants = tv({
|
|
|
120
158
|
indicator: 'bottom-0 top-0 rounded-full bg-primary',
|
|
121
159
|
trigger: 'rounded-full py-2',
|
|
122
160
|
},
|
|
161
|
+
/*
|
|
162
|
+
* A row of icon pills, one of which is open.
|
|
163
|
+
*
|
|
164
|
+
* Every tab draws its own pill here, so there is no indicator to slide
|
|
165
|
+
* between them — the shape that moves is the open tab itself, widening
|
|
166
|
+
* to let its label out and closing again behind it. An indicator sliding
|
|
167
|
+
* under pills that already have backgrounds would be invisible anyway.
|
|
168
|
+
*
|
|
169
|
+
* The open pill is a step further from the page than the closed ones:
|
|
170
|
+
* lighter in a dark theme, darker in a light one, which is what the
|
|
171
|
+
* tertiary surface token means and why it is used rather than a colour.
|
|
172
|
+
*/
|
|
173
|
+
expanding: {
|
|
174
|
+
list: 'gap-1.5',
|
|
175
|
+
trigger: 'rounded-full bg-muted px-3.5 py-2.5',
|
|
176
|
+
},
|
|
123
177
|
},
|
|
124
178
|
active: {
|
|
125
179
|
true: { label: 'text-foreground' },
|
|
@@ -136,6 +190,11 @@ const tabsVariants = tv({
|
|
|
136
190
|
},
|
|
137
191
|
compoundVariants: [
|
|
138
192
|
{ variant: 'pill', active: true, class: { label: 'text-primary-foreground' } },
|
|
193
|
+
// A pill is as wide as what is inside it. Equal shares would give every
|
|
194
|
+
// closed tab the width of the open one, which is the layout this variant
|
|
195
|
+
// exists to avoid.
|
|
196
|
+
{ variant: 'expanding', class: { trigger: 'flex-none' } },
|
|
197
|
+
{ variant: 'expanding', active: true, class: { trigger: 'bg-surface-tertiary' } },
|
|
139
198
|
],
|
|
140
199
|
defaultVariants: {
|
|
141
200
|
variant: 'segmented',
|
|
@@ -148,9 +207,19 @@ interface TabLayout {
|
|
|
148
207
|
width: number;
|
|
149
208
|
}
|
|
150
209
|
|
|
210
|
+
/**
|
|
211
|
+
* What a swipe hands over to the tab it commits to: where the finger left the
|
|
212
|
+
* outgoing panel, and how fast it was still going. Absent when the tab was
|
|
213
|
+
* changed by pressing a trigger, which has neither.
|
|
214
|
+
*/
|
|
215
|
+
interface SwipeHandover {
|
|
216
|
+
/** Points per second at the moment of release, in the panel's own axis. */
|
|
217
|
+
velocity: number;
|
|
218
|
+
}
|
|
219
|
+
|
|
151
220
|
interface TabsContextValue {
|
|
152
221
|
value: string;
|
|
153
|
-
setValue: (value: string) => void;
|
|
222
|
+
setValue: (value: string, handover?: SwipeHandover) => void;
|
|
154
223
|
registerLayout: (value: string, layout: TabLayout) => void;
|
|
155
224
|
layouts: Record<string, TabLayout>;
|
|
156
225
|
/**
|
|
@@ -200,6 +269,12 @@ export interface TabsProps extends ViewProps {
|
|
|
200
269
|
/**
|
|
201
270
|
* `segmented` is a chip travelling inside a recessed track, `underline` is a
|
|
202
271
|
* rule under the active tab, `pill` is a filled chip on the page.
|
|
272
|
+
*
|
|
273
|
+
* `expanding` is a row of icon pills where only the selected one is open:
|
|
274
|
+
* it widens to let its label out and closes again behind it. For a short row
|
|
275
|
+
* of destinations that are recognisable by their icons, where the labels
|
|
276
|
+
* would otherwise take the whole width to say things nobody rereads. Give
|
|
277
|
+
* every trigger an `icon` — a closed tab has nothing else.
|
|
203
278
|
*/
|
|
204
279
|
variant?: TabsVariant;
|
|
205
280
|
/**
|
|
@@ -245,7 +320,7 @@ function TabsRoot({
|
|
|
245
320
|
const resolvedValue = isControlled ? value : internalValue;
|
|
246
321
|
|
|
247
322
|
const setValue = useCallback(
|
|
248
|
-
(next: string) => {
|
|
323
|
+
(next: string, handover?: SwipeHandover) => {
|
|
249
324
|
/*
|
|
250
325
|
* The arriving panel starts on the side it is arriving from, and travels
|
|
251
326
|
* in. Done here rather than in the panel because only the root knows
|
|
@@ -258,9 +333,28 @@ function TabsRoot({
|
|
|
258
333
|
const from = tabs.indexOf(resolvedValue);
|
|
259
334
|
const to = tabs.indexOf(next);
|
|
260
335
|
if (from !== -1 && to !== -1 && from !== to) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
336
|
+
const direction = to > from ? 1 : -1;
|
|
337
|
+
|
|
338
|
+
if (handover) {
|
|
339
|
+
/*
|
|
340
|
+
* A swipe hands over mid-movement, so the arriving panel starts a
|
|
341
|
+
* whole panel's width from wherever the outgoing one was let go —
|
|
342
|
+
* which is where it *would* have been all along, had both been
|
|
343
|
+
* mounted. Adding to the displacement rather than replacing it is
|
|
344
|
+
* the entire difference between one continuous movement and a jump
|
|
345
|
+
* at the moment the finger lifts.
|
|
346
|
+
*/
|
|
347
|
+
swipeOffset.value += direction * sign * panelWidth.value;
|
|
348
|
+
swipeOffset.value = withSpring(0, {
|
|
349
|
+
...ENTER_SPRING,
|
|
350
|
+
// Carried across too, so a flick keeps its speed instead of
|
|
351
|
+
// stopping dead and starting again from rest.
|
|
352
|
+
velocity: handover.velocity,
|
|
353
|
+
});
|
|
354
|
+
} else {
|
|
355
|
+
swipeOffset.value = direction * sign * panelWidth.value * SWIPE_ENTER;
|
|
356
|
+
swipeOffset.value = withSpring(0, ENTER_SPRING);
|
|
357
|
+
}
|
|
264
358
|
}
|
|
265
359
|
}
|
|
266
360
|
|
|
@@ -414,7 +508,9 @@ function TabsList({ className, scrollable = false, children, ...props }: TabsLis
|
|
|
414
508
|
|
|
415
509
|
const row = (
|
|
416
510
|
<View accessibilityRole="tablist" className={cn(list(), className)} {...props}>
|
|
417
|
-
|
|
511
|
+
{/* Nothing to slide: in `expanding` every tab draws its own pill, and the
|
|
512
|
+
open one is the shape that moves. */}
|
|
513
|
+
{variant === 'expanding' ? null : <TabsIndicator />}
|
|
418
514
|
{textChildren(children)}
|
|
419
515
|
</View>
|
|
420
516
|
);
|
|
@@ -438,7 +534,10 @@ function TabsList({ className, scrollable = false, children, ...props }: TabsLis
|
|
|
438
534
|
export interface TabsTriggerProps {
|
|
439
535
|
className?: string;
|
|
440
536
|
value: string;
|
|
441
|
-
/**
|
|
537
|
+
/**
|
|
538
|
+
* Rendered before the label. Required by `variant="expanding"`, where it is
|
|
539
|
+
* the only thing a closed tab has left to identify it by.
|
|
540
|
+
*/
|
|
442
541
|
icon?: ReactNode;
|
|
443
542
|
/** Rendered after the label — a count, a dot, a status. */
|
|
444
543
|
badge?: ReactNode;
|
|
@@ -481,6 +580,23 @@ function TabsTrigger({
|
|
|
481
580
|
return () => unregisterTab(value);
|
|
482
581
|
}, [value, registerTab, unregisterTab]);
|
|
483
582
|
|
|
583
|
+
if (context.variant === 'expanding') {
|
|
584
|
+
return (
|
|
585
|
+
<ExpandingTrigger
|
|
586
|
+
active={active}
|
|
587
|
+
disabled={disabled}
|
|
588
|
+
icon={icon}
|
|
589
|
+
badge={badge}
|
|
590
|
+
labelClassName={slots.label()}
|
|
591
|
+
className={cn(slots.trigger(), className)}
|
|
592
|
+
onLayout={handleLayout}
|
|
593
|
+
onPress={() => context.setValue(value)}
|
|
594
|
+
>
|
|
595
|
+
{children}
|
|
596
|
+
</ExpandingTrigger>
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
|
|
484
600
|
return (
|
|
485
601
|
<Pressable
|
|
486
602
|
accessibilityRole="tab"
|
|
@@ -501,6 +617,142 @@ function TabsTrigger({
|
|
|
501
617
|
);
|
|
502
618
|
}
|
|
503
619
|
|
|
620
|
+
/**
|
|
621
|
+
* A tab that is an icon until it is selected, and then an icon and its label.
|
|
622
|
+
*
|
|
623
|
+
* The label is never unmounted, only closed over. Two reasons, and both matter:
|
|
624
|
+
* a screen reader walking a row of unlabelled icons has nothing to read out,
|
|
625
|
+
* and a label that mounts on selection has no width to animate *from*, so the
|
|
626
|
+
* pill would jump to its open size and the text would fade in inside it.
|
|
627
|
+
*
|
|
628
|
+
* So the width is animated instead, which needs a number — and the number is
|
|
629
|
+
* the one thing that cannot be measured in place, because the box the label
|
|
630
|
+
* sits in is the box being resized. A second copy, laid out once in a box wide
|
|
631
|
+
* enough not to constrain it, reports the width and is never seen.
|
|
632
|
+
*/
|
|
633
|
+
function ExpandingTrigger({
|
|
634
|
+
active,
|
|
635
|
+
disabled,
|
|
636
|
+
icon,
|
|
637
|
+
badge,
|
|
638
|
+
className,
|
|
639
|
+
labelClassName,
|
|
640
|
+
onLayout,
|
|
641
|
+
onPress,
|
|
642
|
+
children,
|
|
643
|
+
}: {
|
|
644
|
+
active: boolean;
|
|
645
|
+
disabled: boolean;
|
|
646
|
+
icon?: ReactNode;
|
|
647
|
+
badge?: ReactNode;
|
|
648
|
+
className: string;
|
|
649
|
+
labelClassName: string;
|
|
650
|
+
onLayout: (event: LayoutChangeEvent) => void;
|
|
651
|
+
onPress: () => void;
|
|
652
|
+
children: ReactNode;
|
|
653
|
+
}) {
|
|
654
|
+
const [labelWidth, setLabelWidth] = useState(0);
|
|
655
|
+
const reducedMotion = useReducedMotion();
|
|
656
|
+
const open = useSharedValue(active ? 1 : 0);
|
|
657
|
+
|
|
658
|
+
useEffect(() => {
|
|
659
|
+
if (reducedMotion) {
|
|
660
|
+
open.value = active ? 1 : 0;
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
/*
|
|
664
|
+
* A curve, not a spring.
|
|
665
|
+
*
|
|
666
|
+
* This animates a *width*, so every frame of it is a layout pass — and the
|
|
667
|
+
* row is centred, so every other pill moves with it. A spring overshoots
|
|
668
|
+
* past its target and settles back, which on a box that is clipping text
|
|
669
|
+
* means the last word slides in, out and in again, and the whole row
|
|
670
|
+
* wobbles with it. The curve leaves quickly and arrives slowly, and it
|
|
671
|
+
* arrives once.
|
|
672
|
+
*/
|
|
673
|
+
open.value = withTiming(active ? 1 : 0, {
|
|
674
|
+
duration: EXPAND_DURATION,
|
|
675
|
+
easing: Easing.bezier(0.2, 0, 0, 1),
|
|
676
|
+
});
|
|
677
|
+
}, [active, reducedMotion, open]);
|
|
678
|
+
|
|
679
|
+
const reveal = useAnimatedStyle(() => ({
|
|
680
|
+
width: (labelWidth + LABEL_GAP) * open.value,
|
|
681
|
+
/*
|
|
682
|
+
* Behind the width, not ahead of it. Text drawn into a box narrower than
|
|
683
|
+
* itself is text with its end cut off, and doing that on purpose for the
|
|
684
|
+
* first half of the animation is the difference between a label arriving
|
|
685
|
+
* and a label being wiped on.
|
|
686
|
+
*/
|
|
687
|
+
opacity: interpolate(open.value, [0.35, 0.9], [0, 1], Extrapolation.CLAMP),
|
|
688
|
+
}));
|
|
689
|
+
|
|
690
|
+
const label = textChildren(children, (text) => (
|
|
691
|
+
<Text size="sm" weight="medium" numberOfLines={1} className={labelClassName}>
|
|
692
|
+
{text}
|
|
693
|
+
</Text>
|
|
694
|
+
));
|
|
695
|
+
|
|
696
|
+
return (
|
|
697
|
+
<Pressable
|
|
698
|
+
accessibilityRole="tab"
|
|
699
|
+
accessibilityState={{ selected: active, disabled }}
|
|
700
|
+
// The label may be closed to nothing, and a tab announced as an unlabelled
|
|
701
|
+
// icon is a tab nobody can choose.
|
|
702
|
+
accessibilityLabel={typeof children === 'string' ? children : undefined}
|
|
703
|
+
disabled={disabled}
|
|
704
|
+
onPress={onPress}
|
|
705
|
+
onLayout={onLayout}
|
|
706
|
+
className={cn('flex-row items-center', className)}
|
|
707
|
+
>
|
|
708
|
+
{icon}
|
|
709
|
+
<Animated.View style={[{ overflow: 'hidden' }, reveal]}>
|
|
710
|
+
{/*
|
|
711
|
+
* Pinned to the measured width so the text does not reflow as the box
|
|
712
|
+
* around it closes — a label that rewraps on its way out reads as a
|
|
713
|
+
* glitch rather than as a reveal.
|
|
714
|
+
*
|
|
715
|
+
* The gap after the icon is padding on this box, so it has to be added
|
|
716
|
+
* to the width rather than taken out of it. Set to the measured width
|
|
717
|
+
* alone, the padding comes off the inside and the label is handed six
|
|
718
|
+
* points less than it asked for, which a single-line `Text` answers by
|
|
719
|
+
* truncating: "Inbox" arrives as "Inbo…" and stays that way.
|
|
720
|
+
*/}
|
|
721
|
+
<View style={{ width: labelWidth + LABEL_GAP, paddingStart: LABEL_GAP }}>
|
|
722
|
+
{label}
|
|
723
|
+
</View>
|
|
724
|
+
</Animated.View>
|
|
725
|
+
{/* Measured once, never seen, and out of the layout so it cannot widen
|
|
726
|
+
the pill it is measuring. */}
|
|
727
|
+
<View
|
|
728
|
+
pointerEvents="none"
|
|
729
|
+
style={{ position: 'absolute', opacity: 0, width: MEASURE_WIDTH }}
|
|
730
|
+
>
|
|
731
|
+
<View
|
|
732
|
+
style={{ alignSelf: 'flex-start' }}
|
|
733
|
+
onLayout={(event: LayoutChangeEvent) => {
|
|
734
|
+
/*
|
|
735
|
+
* Rounded up and given a couple of points over.
|
|
736
|
+
*
|
|
737
|
+
* The measurement and the box it is put back into are two different
|
|
738
|
+
* layout passes, and a fraction of a point between them is enough
|
|
739
|
+
* for a single-line `Text` to decide it does not fit and truncate —
|
|
740
|
+
* which shows up as a label permanently missing its last letter.
|
|
741
|
+
*/
|
|
742
|
+
const measured = Math.ceil(event.nativeEvent.layout.width) + LABEL_SLACK;
|
|
743
|
+
if (measured > LABEL_SLACK && measured !== labelWidth) {
|
|
744
|
+
setLabelWidth(measured);
|
|
745
|
+
}
|
|
746
|
+
}}
|
|
747
|
+
>
|
|
748
|
+
{label}
|
|
749
|
+
</View>
|
|
750
|
+
</View>
|
|
751
|
+
{badge}
|
|
752
|
+
</Pressable>
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
|
|
504
756
|
export interface TabsContentProps extends ViewProps {
|
|
505
757
|
className?: string;
|
|
506
758
|
value: string;
|
|
@@ -530,10 +782,10 @@ function TabsContent({ className, value, children, style, ...props }: TabsConten
|
|
|
530
782
|
}, [position, tabs.length, index, count]);
|
|
531
783
|
|
|
532
784
|
const step = useCallback(
|
|
533
|
-
(delta: number) => {
|
|
785
|
+
(delta: number, velocity: number) => {
|
|
534
786
|
const from = tabs.indexOf(value);
|
|
535
787
|
const next = tabs[from + delta];
|
|
536
|
-
if (next) setValue(next);
|
|
788
|
+
if (next) setValue(next, { velocity });
|
|
537
789
|
},
|
|
538
790
|
[tabs, value, setValue]
|
|
539
791
|
);
|
|
@@ -552,8 +804,16 @@ function TabsContent({ className, value, children, style, ...props }: TabsConten
|
|
|
552
804
|
const atEnd =
|
|
553
805
|
(travel > 0 && index.value === 0) ||
|
|
554
806
|
(travel < 0 && index.value >= count.value - 1);
|
|
555
|
-
|
|
556
|
-
|
|
807
|
+
/*
|
|
808
|
+
* One to one, except at the ends. The panel is under the finger for
|
|
809
|
+
* the whole of the drag, which is what lets the handover on release
|
|
810
|
+
* continue the movement rather than restart it — and a panel that
|
|
811
|
+
* moved a third as far as the finger never felt attached to it in
|
|
812
|
+
* the first place.
|
|
813
|
+
*/
|
|
814
|
+
offset.value = atEnd
|
|
815
|
+
? event.translationX * SWIPE_RESISTANCE_AT_END
|
|
816
|
+
: event.translationX;
|
|
557
817
|
})
|
|
558
818
|
.onEnd((event) => {
|
|
559
819
|
const travel = event.translationX * sign;
|
|
@@ -569,15 +829,20 @@ function TabsContent({ className, value, children, style, ...props }: TabsConten
|
|
|
569
829
|
// reads as a cancel — so the direction comes from whichever of the
|
|
570
830
|
// two crossed its threshold, speed first.
|
|
571
831
|
//
|
|
572
|
-
// No spring back to zero here: `setValue` on the root
|
|
573
|
-
// displacement across to the
|
|
574
|
-
//
|
|
575
|
-
runOnJS(step)(
|
|
832
|
+
// No spring back to zero here: `setValue` on the root carries the
|
|
833
|
+
// displacement across to the arriving panel, and springing to rest
|
|
834
|
+
// first would undo it a frame before it happens.
|
|
835
|
+
runOnJS(step)(
|
|
836
|
+
fast ? (speed < 0 ? 1 : -1) : travel < 0 ? 1 : -1,
|
|
837
|
+
event.velocityX
|
|
838
|
+
);
|
|
576
839
|
return;
|
|
577
840
|
}
|
|
578
841
|
|
|
579
|
-
// Nothing changed hands, so this panel simply comes back
|
|
580
|
-
|
|
842
|
+
// Nothing changed hands, so this panel simply comes back — with the
|
|
843
|
+
// speed it was let go at, so a cancelled flick decelerates rather
|
|
844
|
+
// than stopping and being pulled.
|
|
845
|
+
offset.value = withSpring(0, { ...SPRING, velocity: event.velocityX });
|
|
581
846
|
})
|
|
582
847
|
.onFinalize((_event, success) => {
|
|
583
848
|
// A cancelled gesture never reaches `onEnd`, and would otherwise
|
|
@@ -587,9 +852,14 @@ function TabsContent({ className, value, children, style, ...props }: TabsConten
|
|
|
587
852
|
[sign, step, offset, width, index, count]
|
|
588
853
|
);
|
|
589
854
|
|
|
590
|
-
const followStyle = useAnimatedStyle(() =>
|
|
591
|
-
|
|
592
|
-
|
|
855
|
+
const followStyle = useAnimatedStyle(() => {
|
|
856
|
+
const span = width.value || 1;
|
|
857
|
+
const travelled = Math.min(1, Math.abs(offset.value) / span);
|
|
858
|
+
return {
|
|
859
|
+
transform: [{ translateX: offset.value }],
|
|
860
|
+
opacity: 1 - SWIPE_FADE * travelled,
|
|
861
|
+
};
|
|
862
|
+
});
|
|
593
863
|
|
|
594
864
|
if (!active && !context.keepMounted) return null;
|
|
595
865
|
|
package/src/index.ts
CHANGED
|
@@ -141,6 +141,11 @@ export {
|
|
|
141
141
|
type DatePickerProps,
|
|
142
142
|
type DatePickerMode,
|
|
143
143
|
} from './components/date-picker';
|
|
144
|
+
export {
|
|
145
|
+
DateTimePicker,
|
|
146
|
+
type DateTimePickerProps,
|
|
147
|
+
type DateTimePickerPresentation,
|
|
148
|
+
} from './components/date-time-picker';
|
|
144
149
|
export {
|
|
145
150
|
Dialog,
|
|
146
151
|
type DialogProps,
|
|
@@ -191,6 +196,18 @@ export {
|
|
|
191
196
|
type FrameActionsProps,
|
|
192
197
|
type FrameVariant,
|
|
193
198
|
} from './components/frame';
|
|
199
|
+
export {
|
|
200
|
+
GridItem,
|
|
201
|
+
type GridItemProps,
|
|
202
|
+
type GridItemGroupProps,
|
|
203
|
+
type GridItemBackgroundProps,
|
|
204
|
+
type GridItemMediaProps,
|
|
205
|
+
type GridItemTitleProps,
|
|
206
|
+
type GridItemValueProps,
|
|
207
|
+
type GridItemDescriptionProps,
|
|
208
|
+
type GridItemFooterProps,
|
|
209
|
+
type GridItemActionsProps,
|
|
210
|
+
} from './components/grid-item';
|
|
194
211
|
export {
|
|
195
212
|
HeatmapChart,
|
|
196
213
|
useHeatmapChart,
|
|
@@ -309,7 +326,7 @@ export {
|
|
|
309
326
|
type CalendarSelection,
|
|
310
327
|
type DateRange,
|
|
311
328
|
} from './components/calendar';
|
|
312
|
-
export { Card, type CardProps
|
|
329
|
+
export { Card, type CardProps } from './components/card';
|
|
313
330
|
export { Checkbox, type CheckboxProps } from './components/checkbox';
|
|
314
331
|
export {
|
|
315
332
|
Chip,
|
|
@@ -437,6 +454,19 @@ export {
|
|
|
437
454
|
type PaginationSize,
|
|
438
455
|
type PaginationItemValue,
|
|
439
456
|
} from './components/pagination';
|
|
457
|
+
export {
|
|
458
|
+
PieChart,
|
|
459
|
+
usePieChart,
|
|
460
|
+
type PieChartProps,
|
|
461
|
+
type PieChartHandle,
|
|
462
|
+
type PieChartHeaderProps,
|
|
463
|
+
type PieChartSlicesProps,
|
|
464
|
+
type PieChartCenterProps,
|
|
465
|
+
type PieChartLegendProps,
|
|
466
|
+
type PieChartSkeletonProps,
|
|
467
|
+
type PieChartStatus,
|
|
468
|
+
type PieDatum,
|
|
469
|
+
} from './components/pie-chart';
|
|
440
470
|
export { Textarea, type TextareaProps } from './components/textarea';
|
|
441
471
|
export {
|
|
442
472
|
Item,
|
|
@@ -452,25 +482,25 @@ export {
|
|
|
452
482
|
type ItemFooterProps,
|
|
453
483
|
} from './components/item';
|
|
454
484
|
export {
|
|
455
|
-
|
|
456
|
-
type
|
|
457
|
-
type
|
|
458
|
-
type
|
|
459
|
-
type
|
|
460
|
-
type
|
|
461
|
-
type
|
|
462
|
-
type
|
|
463
|
-
type
|
|
464
|
-
type
|
|
465
|
-
type
|
|
466
|
-
type
|
|
467
|
-
type
|
|
468
|
-
type
|
|
469
|
-
type
|
|
470
|
-
type
|
|
485
|
+
Kpi,
|
|
486
|
+
type KpiProps,
|
|
487
|
+
type KpiHeaderProps,
|
|
488
|
+
type KpiIconProps,
|
|
489
|
+
type KpiTitleProps,
|
|
490
|
+
type KpiStatProps,
|
|
491
|
+
type KpiActionsProps,
|
|
492
|
+
type KpiContentProps,
|
|
493
|
+
type KpiValueProps,
|
|
494
|
+
type KpiTrendProps,
|
|
495
|
+
type KpiSparklineProps,
|
|
496
|
+
type KpiProgressProps,
|
|
497
|
+
type KpiFooterProps,
|
|
498
|
+
type KpiSeparatorProps,
|
|
499
|
+
type KpiGroupProps,
|
|
500
|
+
type KpiGroupOrientation,
|
|
471
501
|
type KpiGoodDirection,
|
|
472
502
|
type KpiTone,
|
|
473
|
-
} from './components/kpi
|
|
503
|
+
} from './components/kpi';
|
|
474
504
|
export { Label, type LabelProps, type LabelTextProps } from './components/label';
|
|
475
505
|
export {
|
|
476
506
|
LineChart,
|
|
@@ -483,6 +513,7 @@ export {
|
|
|
483
513
|
type LineChartAreaProps,
|
|
484
514
|
type LineChartSkeletonProps,
|
|
485
515
|
type LineChartXAxisProps,
|
|
516
|
+
type LineChartYAxisProps,
|
|
486
517
|
type LineChartTooltipProps,
|
|
487
518
|
type LineChartLegendProps,
|
|
488
519
|
type LineChartDatum,
|
|
@@ -590,6 +621,23 @@ export {
|
|
|
590
621
|
type RingChartLegendProps,
|
|
591
622
|
type RingDatum,
|
|
592
623
|
} from './components/ring-chart';
|
|
624
|
+
export {
|
|
625
|
+
ScatterChart,
|
|
626
|
+
useScatterChart,
|
|
627
|
+
type ScatterChartProps,
|
|
628
|
+
type ScatterChartHandle,
|
|
629
|
+
type ScatterChartHeaderProps,
|
|
630
|
+
type ScatterChartGridProps,
|
|
631
|
+
type ScatterChartPointsProps,
|
|
632
|
+
type ScatterChartSkeletonProps,
|
|
633
|
+
type ScatterChartXAxisProps,
|
|
634
|
+
type ScatterChartYAxisProps,
|
|
635
|
+
type ScatterChartTooltipProps,
|
|
636
|
+
type ScatterChartLegendProps,
|
|
637
|
+
type ScatterChartDatum,
|
|
638
|
+
type ScatterChartPoint,
|
|
639
|
+
type ScatterChartStatus,
|
|
640
|
+
} from './components/scatter-chart';
|
|
593
641
|
export {
|
|
594
642
|
ScrollFade,
|
|
595
643
|
type ScrollFadeProps,
|