panelui-native 0.62.0 → 0.65.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 -0
- package/lib/module/components/area-chart/index.js +14 -4
- package/lib/module/components/area-chart/index.js.map +1 -1
- package/lib/module/components/badge/index.js +1 -1
- package/lib/module/components/badge/index.js.map +1 -1
- package/lib/module/components/bar-chart/index.js +53 -6
- package/lib/module/components/bar-chart/index.js.map +1 -1
- package/lib/module/components/bottom-sheet/index.js +2 -2
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/button/index.js +19 -9
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/candlestick-chart/index.js +14 -4
- package/lib/module/components/candlestick-chart/index.js.map +1 -1
- package/lib/module/components/card/index.js +26 -1
- package/lib/module/components/card/index.js.map +1 -1
- package/lib/module/components/carousel/index.js +28 -14
- package/lib/module/components/carousel/index.js.map +1 -1
- package/lib/module/components/checkbox/index.js +2 -3
- package/lib/module/components/checkbox/index.js.map +1 -1
- package/lib/module/components/dialog/index.js +51 -5
- package/lib/module/components/dialog/index.js.map +1 -1
- package/lib/module/components/drawer/index.js +2 -2
- package/lib/module/components/drawer/index.js.map +1 -1
- package/lib/module/components/fab/index.js +4 -3
- package/lib/module/components/fab/index.js.map +1 -1
- package/lib/module/components/field/index.js +41 -6
- package/lib/module/components/field/index.js.map +1 -1
- package/lib/module/components/flow/flow-accessibility.js +84 -0
- package/lib/module/components/flow/flow-accessibility.js.map +1 -0
- package/lib/module/components/flow/index.js +100 -39
- package/lib/module/components/flow/index.js.map +1 -1
- package/lib/module/components/form/use-form.js +93 -66
- package/lib/module/components/form/use-form.js.map +1 -1
- package/lib/module/components/heatmap-chart/index.js +2 -2
- package/lib/module/components/heatmap-chart/index.js.map +1 -1
- package/lib/module/components/hex-chart/index.js +4 -4
- package/lib/module/components/hex-chart/index.js.map +1 -1
- package/lib/module/components/kpi/index.js +3 -1
- package/lib/module/components/kpi/index.js.map +1 -1
- package/lib/module/components/line-chart/index.js +17 -7
- package/lib/module/components/line-chart/index.js.map +1 -1
- package/lib/module/components/map/index.js +71 -8
- package/lib/module/components/map/index.js.map +1 -1
- package/lib/module/components/map/map-accessibility.js +20 -0
- package/lib/module/components/map/map-accessibility.js.map +1 -0
- package/lib/module/components/map/maplibre.js.map +1 -1
- package/lib/module/components/menu/index.js +4 -1
- package/lib/module/components/menu/index.js.map +1 -1
- package/lib/module/components/number-input/index.js +1 -23
- package/lib/module/components/number-input/index.js.map +1 -1
- package/lib/module/components/number-input/number-input-math.js +44 -0
- package/lib/module/components/number-input/number-input-math.js.map +1 -0
- package/lib/module/components/pie-chart/index.js +2 -2
- package/lib/module/components/pie-chart/index.js.map +1 -1
- package/lib/module/components/plot/index.js +1329 -0
- package/lib/module/components/plot/index.js.map +1 -0
- package/lib/module/components/polar-area-chart/index.js +2 -2
- package/lib/module/components/polar-area-chart/index.js.map +1 -1
- package/lib/module/components/radar-chart/index.js +14 -4
- package/lib/module/components/radar-chart/index.js.map +1 -1
- package/lib/module/components/radio-group/index.js +1 -2
- package/lib/module/components/radio-group/index.js.map +1 -1
- package/lib/module/components/ring-chart/index.js +2 -2
- package/lib/module/components/ring-chart/index.js.map +1 -1
- package/lib/module/components/section-rail/index.js +120 -15
- package/lib/module/components/section-rail/index.js.map +1 -1
- package/lib/module/components/selection-mode/index.js +129 -18
- package/lib/module/components/selection-mode/index.js.map +1 -1
- package/lib/module/components/sortable/index.js +20 -60
- package/lib/module/components/sortable/index.js.map +1 -1
- package/lib/module/components/sortable/reorder.js +63 -0
- package/lib/module/components/sortable/reorder.js.map +1 -0
- package/lib/module/components/swipe/index.js +4 -4
- package/lib/module/components/swipe/index.js.map +1 -1
- package/lib/module/components/switch/index.js +36 -3
- package/lib/module/components/switch/index.js.map +1 -1
- package/lib/module/components/time-picker/accessibility.js +21 -0
- package/lib/module/components/time-picker/accessibility.js.map +1 -0
- package/lib/module/components/time-picker/index.js +34 -8
- package/lib/module/components/time-picker/index.js.map +1 -1
- package/lib/module/components/waterfall-chart/index.js +1325 -0
- package/lib/module/components/waterfall-chart/index.js.map +1 -0
- package/lib/module/index.js +8 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/primitives/modal-isolation-store.js +29 -0
- package/lib/module/primitives/modal-isolation-store.js.map +1 -0
- package/lib/module/primitives/portal.js +36 -4
- package/lib/module/primitives/portal.js.map +1 -1
- package/lib/module/providers/panel-ui-provider.js +27 -6
- package/lib/module/providers/panel-ui-provider.js.map +1 -1
- package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/badge/index.d.ts.map +1 -1
- package/lib/typescript/src/components/bar-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/candlestick-chart/index.d.ts +6 -3
- package/lib/typescript/src/components/candlestick-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/card/index.d.ts +48 -1
- package/lib/typescript/src/components/card/index.d.ts.map +1 -1
- package/lib/typescript/src/components/carousel/index.d.ts +1 -1
- package/lib/typescript/src/components/carousel/index.d.ts.map +1 -1
- package/lib/typescript/src/components/checkbox/index.d.ts.map +1 -1
- package/lib/typescript/src/components/dialog/index.d.ts +47 -2
- package/lib/typescript/src/components/dialog/index.d.ts.map +1 -1
- package/lib/typescript/src/components/fab/index.d.ts.map +1 -1
- package/lib/typescript/src/components/field/index.d.ts +8 -1
- package/lib/typescript/src/components/field/index.d.ts.map +1 -1
- package/lib/typescript/src/components/flow/flow-accessibility.d.ts +49 -0
- package/lib/typescript/src/components/flow/flow-accessibility.d.ts.map +1 -0
- package/lib/typescript/src/components/flow/index.d.ts +14 -2
- package/lib/typescript/src/components/flow/index.d.ts.map +1 -1
- package/lib/typescript/src/components/form/use-form.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/kpi/index.d.ts.map +1 -1
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/map/index.d.ts +13 -5
- package/lib/typescript/src/components/map/index.d.ts.map +1 -1
- package/lib/typescript/src/components/map/map-accessibility.d.ts +19 -0
- package/lib/typescript/src/components/map/map-accessibility.d.ts.map +1 -0
- package/lib/typescript/src/components/map/maplibre.d.ts +2 -0
- package/lib/typescript/src/components/map/maplibre.d.ts.map +1 -1
- package/lib/typescript/src/components/menu/index.d.ts.map +1 -1
- package/lib/typescript/src/components/number-input/index.d.ts.map +1 -1
- package/lib/typescript/src/components/number-input/number-input-math.d.ts +9 -0
- package/lib/typescript/src/components/number-input/number-input-math.d.ts.map +1 -0
- package/lib/typescript/src/components/plot/index.d.ts +471 -0
- package/lib/typescript/src/components/plot/index.d.ts.map +1 -0
- package/lib/typescript/src/components/radar-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/radio-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/section-rail/index.d.ts +8 -1
- package/lib/typescript/src/components/section-rail/index.d.ts.map +1 -1
- package/lib/typescript/src/components/selection-mode/index.d.ts +82 -6
- package/lib/typescript/src/components/selection-mode/index.d.ts.map +1 -1
- package/lib/typescript/src/components/sortable/index.d.ts.map +1 -1
- package/lib/typescript/src/components/sortable/reorder.d.ts +14 -0
- package/lib/typescript/src/components/sortable/reorder.d.ts.map +1 -0
- package/lib/typescript/src/components/swipe/index.d.ts.map +1 -1
- package/lib/typescript/src/components/switch/index.d.ts +3 -3
- package/lib/typescript/src/components/switch/index.d.ts.map +1 -1
- package/lib/typescript/src/components/time-picker/accessibility.d.ts +10 -0
- package/lib/typescript/src/components/time-picker/accessibility.d.ts.map +1 -0
- package/lib/typescript/src/components/time-picker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/waterfall-chart/index.d.ts +379 -0
- package/lib/typescript/src/components/waterfall-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/modal-isolation-store.d.ts +10 -0
- package/lib/typescript/src/primitives/modal-isolation-store.d.ts.map +1 -0
- package/lib/typescript/src/primitives/portal.d.ts +6 -0
- package/lib/typescript/src/primitives/portal.d.ts.map +1 -1
- package/lib/typescript/src/providers/panel-ui-provider.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/components/area-chart/index.tsx +14 -4
- package/src/components/badge/index.tsx +4 -1
- package/src/components/bar-chart/index.tsx +54 -7
- package/src/components/bottom-sheet/index.tsx +3 -3
- package/src/components/button/index.tsx +15 -21
- package/src/components/candlestick-chart/index.tsx +20 -7
- package/src/components/card/index.tsx +32 -7
- package/src/components/carousel/index.tsx +42 -20
- package/src/components/checkbox/index.tsx +1 -2
- package/src/components/dialog/index.tsx +58 -5
- package/src/components/drawer/index.tsx +3 -3
- package/src/components/fab/index.tsx +8 -12
- package/src/components/field/index.tsx +52 -10
- package/src/components/flow/flow-accessibility.ts +109 -0
- package/src/components/flow/index.tsx +162 -50
- package/src/components/form/use-form.ts +101 -65
- package/src/components/heatmap-chart/index.tsx +2 -3
- package/src/components/hex-chart/index.tsx +4 -5
- package/src/components/kpi/index.tsx +3 -1
- package/src/components/line-chart/index.tsx +17 -6
- package/src/components/map/index.tsx +110 -7
- package/src/components/map/map-accessibility.ts +41 -0
- package/src/components/map/maplibre.ts +2 -0
- package/src/components/menu/index.tsx +4 -1
- package/src/components/number-input/index.tsx +1 -26
- package/src/components/number-input/number-input-math.ts +50 -0
- package/src/components/pie-chart/index.tsx +2 -2
- package/src/components/plot/index.tsx +1565 -0
- package/src/components/polar-area-chart/index.tsx +2 -2
- package/src/components/radar-chart/index.tsx +14 -4
- package/src/components/radio-group/index.tsx +1 -2
- package/src/components/ring-chart/index.tsx +2 -2
- package/src/components/section-rail/index.tsx +133 -14
- package/src/components/selection-mode/index.tsx +163 -26
- package/src/components/sortable/index.tsx +27 -69
- package/src/components/sortable/reorder.ts +88 -0
- package/src/components/swipe/index.tsx +7 -4
- package/src/components/switch/index.tsx +42 -6
- package/src/components/time-picker/accessibility.ts +24 -0
- package/src/components/time-picker/index.tsx +48 -2
- package/src/components/waterfall-chart/index.tsx +1589 -0
- package/src/index.ts +69 -0
- package/src/primitives/modal-isolation-store.ts +31 -0
- package/src/primitives/portal.tsx +34 -4
- package/src/providers/panel-ui-provider.tsx +41 -9
- package/theme.css +54 -3
|
@@ -245,7 +245,7 @@ const BarChartRoot = forwardRef<BarChartHandle, BarChartProps>(function BarChart
|
|
|
245
245
|
xDataKey = 'name',
|
|
246
246
|
status = 'ready',
|
|
247
247
|
aspectRatio = 2,
|
|
248
|
-
animationDuration =
|
|
248
|
+
animationDuration = 700,
|
|
249
249
|
domainDuration = 500,
|
|
250
250
|
yDomain,
|
|
251
251
|
orientation = 'vertical',
|
|
@@ -374,17 +374,27 @@ const BarChartRoot = forwardRef<BarChartHandle, BarChartProps>(function BarChart
|
|
|
374
374
|
reveal.value = 0;
|
|
375
375
|
reveal.value = withTiming(1, {
|
|
376
376
|
duration: animationDuration,
|
|
377
|
-
easing: Easing.
|
|
377
|
+
easing: Easing.out(Easing.cubic),
|
|
378
378
|
});
|
|
379
379
|
},
|
|
380
380
|
[reducedMotion, animationDuration, reveal]
|
|
381
381
|
);
|
|
382
382
|
|
|
383
383
|
useEffect(() => {
|
|
384
|
-
|
|
384
|
+
/*
|
|
385
|
+
* Going back to `loading` arms the reveal again. Without this a chart that
|
|
386
|
+
* is refetched comes back fully drawn on the frame the data lands, which
|
|
387
|
+
* reads as the loading state having been for nothing.
|
|
388
|
+
*/
|
|
389
|
+
if (loading) {
|
|
390
|
+
revealed.current = false;
|
|
391
|
+
reveal.value = 0;
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (revealed.current || plot.width <= 0 || !data.length) return;
|
|
385
395
|
revealed.current = true;
|
|
386
396
|
playReveal();
|
|
387
|
-
}, [loading, plot.width, data.length, playReveal]);
|
|
397
|
+
}, [loading, plot.width, data.length, playReveal, reveal]);
|
|
388
398
|
|
|
389
399
|
useImperativeHandle(ref, () => ({ replay: playReveal }), [playReveal]);
|
|
390
400
|
|
|
@@ -1073,6 +1083,15 @@ function BarChartTooltip({ formatValue, formatX, className }: BarChartTooltipPro
|
|
|
1073
1083
|
const width = plot.width;
|
|
1074
1084
|
const height = plot.height;
|
|
1075
1085
|
|
|
1086
|
+
/*
|
|
1087
|
+
* The readout's own height, measured rather than assumed. Sideways it has to
|
|
1088
|
+
* be clamped inside the plot vertically, and how tall it is depends on how
|
|
1089
|
+
* many series are listed in it — a constant here would either let a
|
|
1090
|
+
* three-series readout hang off the bottom or reserve room a one-series
|
|
1091
|
+
* readout never uses.
|
|
1092
|
+
*/
|
|
1093
|
+
const labelHeight = useSharedValue(0);
|
|
1094
|
+
|
|
1076
1095
|
/*
|
|
1077
1096
|
* Declared inside the memo, next to its callers: a worklet may only call
|
|
1078
1097
|
* another worklet, and the rule is enforced by crashing rather than warning.
|
|
@@ -1108,8 +1127,17 @@ function BarChartTooltip({ formatValue, formatX, className }: BarChartTooltipPro
|
|
|
1108
1127
|
});
|
|
1109
1128
|
}, [total, left, top, width, height, horizontal, activeIndex, setActiveIndexJS]);
|
|
1110
1129
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1130
|
+
/*
|
|
1131
|
+
* The readout centres over its band and is clamped inside the plot, so it
|
|
1132
|
+
* never runs off the edge at the first or last one.
|
|
1133
|
+
*
|
|
1134
|
+
* Which axis the band runs along is the whole difference between the two
|
|
1135
|
+
* orientations. Upright, the bands are side by side, so the readout slides
|
|
1136
|
+
* across and sits above them all. Sideways, the bands are stacked down the
|
|
1137
|
+
* plot, so it slides *down* to the row it is describing and stays centred
|
|
1138
|
+
* across — held at the top instead, it would name the row under the finger
|
|
1139
|
+
* while covering the first one, which is the row a reader checks it against.
|
|
1140
|
+
*/
|
|
1113
1141
|
const labelStyle = useAnimatedStyle(() => {
|
|
1114
1142
|
const index = activeIndex.value;
|
|
1115
1143
|
if (index < 0 || !total) return { opacity: 0 };
|
|
@@ -1120,7 +1148,23 @@ function BarChartTooltip({ formatValue, formatX, className }: BarChartTooltipPro
|
|
|
1120
1148
|
plot.left + plot.width - half,
|
|
1121
1149
|
Math.max(plot.left + half, horizontal ? plot.left + plot.width / 2 : centre)
|
|
1122
1150
|
);
|
|
1123
|
-
|
|
1151
|
+
|
|
1152
|
+
if (!horizontal) {
|
|
1153
|
+
return { opacity: 1, transform: [{ translateX: clamped - half }] };
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Until the first measurement lands the height is zero, which clamps to
|
|
1157
|
+
// the top of the plot — the same place it used to sit, rather than a jump
|
|
1158
|
+
// from somewhere it never was.
|
|
1159
|
+
const tall = labelHeight.value;
|
|
1160
|
+
const y = Math.min(
|
|
1161
|
+
plot.top + Math.max(plot.height - tall, 0),
|
|
1162
|
+
Math.max(plot.top, centre - tall / 2)
|
|
1163
|
+
);
|
|
1164
|
+
return {
|
|
1165
|
+
opacity: 1,
|
|
1166
|
+
transform: [{ translateX: clamped - half }, { translateY: y }],
|
|
1167
|
+
};
|
|
1124
1168
|
});
|
|
1125
1169
|
|
|
1126
1170
|
const active = activeIndexJS >= 0 ? data[activeIndexJS] : null;
|
|
@@ -1141,6 +1185,9 @@ function BarChartTooltip({ formatValue, formatX, className }: BarChartTooltipPro
|
|
|
1141
1185
|
>
|
|
1142
1186
|
{active ? (
|
|
1143
1187
|
<View
|
|
1188
|
+
onLayout={(event) => {
|
|
1189
|
+
labelHeight.value = event.nativeEvent.layout.height;
|
|
1190
|
+
}}
|
|
1144
1191
|
className={cn(
|
|
1145
1192
|
'rounded-xl border border-border bg-popover px-2.5 py-1.5 shadow-lg',
|
|
1146
1193
|
className
|
|
@@ -31,7 +31,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
31
31
|
import { tv } from 'tailwind-variants';
|
|
32
32
|
import { getNativeUI } from '../../native';
|
|
33
33
|
import { XIcon } from '../../icons';
|
|
34
|
-
import {
|
|
34
|
+
import { ModalPortal } from '../../primitives/portal';
|
|
35
35
|
import { Scrim } from '../../primitives/scrim';
|
|
36
36
|
import { Text, textChildren } from '../../primitives/text';
|
|
37
37
|
import { useBackHandler } from '../../hooks/use-back-handler';
|
|
@@ -435,7 +435,7 @@ function BottomSheetContent({
|
|
|
435
435
|
if (!open) return null;
|
|
436
436
|
|
|
437
437
|
return (
|
|
438
|
-
<
|
|
438
|
+
<ModalPortal>
|
|
439
439
|
{/* Portal content mounts under PortalHost, outside this provider's
|
|
440
440
|
subtree — re-provide the context so nested consumers keep working. */}
|
|
441
441
|
<BottomSheetContext.Provider value={context}>
|
|
@@ -507,7 +507,7 @@ function BottomSheetContent({
|
|
|
507
507
|
</GestureDetector>
|
|
508
508
|
</View>
|
|
509
509
|
</BottomSheetContext.Provider>
|
|
510
|
-
</
|
|
510
|
+
</ModalPortal>
|
|
511
511
|
);
|
|
512
512
|
}
|
|
513
513
|
|
|
@@ -42,8 +42,9 @@ const buttonVariants = tv({
|
|
|
42
42
|
},
|
|
43
43
|
destructive: {
|
|
44
44
|
root: 'border-destructive bg-destructive shadow-sm',
|
|
45
|
-
label: 'text-
|
|
46
|
-
spinner:
|
|
45
|
+
label: 'text-destructive-solid-foreground',
|
|
46
|
+
spinner:
|
|
47
|
+
'border-destructive-solid-foreground/32 border-t-destructive-solid-foreground',
|
|
47
48
|
},
|
|
48
49
|
/** Neutral surface for third-party sign-in, sized for a full-width stack. */
|
|
49
50
|
social: {
|
|
@@ -53,8 +54,8 @@ const buttonVariants = tv({
|
|
|
53
54
|
},
|
|
54
55
|
},
|
|
55
56
|
size: {
|
|
56
|
-
sm: { root: 'h-9 gap-1.5 px-2.5', label: 'text-sm' },
|
|
57
|
-
md: { root: 'h-11 px-4', label: 'text-base' },
|
|
57
|
+
sm: { root: 'h-9 min-w-9 gap-1.5 px-2.5', label: 'text-sm' },
|
|
58
|
+
md: { root: 'h-11 min-w-11 px-4', label: 'text-base' },
|
|
58
59
|
lg: { root: 'h-12 px-6', label: 'text-lg' },
|
|
59
60
|
icon: { root: 'h-11 w-11 px-0' },
|
|
60
61
|
},
|
|
@@ -134,18 +135,16 @@ const ATTACHED = 'rounded-none border-transparent shadow-none active:bg-accent';
|
|
|
134
135
|
|
|
135
136
|
const SPINNER_SIZE = { sm: 'sm', md: 'sm', lg: 'md', icon: 'sm' } as const;
|
|
136
137
|
|
|
138
|
+
/** Extra room around the styled sizes so even the compact button reaches 48dp. */
|
|
139
|
+
const BUTTON_HIT_SLOP = { sm: 6, md: 2, lg: 0, icon: 2 } as const;
|
|
140
|
+
|
|
137
141
|
/**
|
|
138
142
|
* The theme token each variant's content reads against. Icons in the content
|
|
139
143
|
* slots inherit the resolved value, so they follow the theme automatically.
|
|
140
|
-
*
|
|
141
|
-
* `destructive` is absent on purpose: its background is a saturated red in
|
|
142
|
-
* every theme, so its content is always white rather than a themed token.
|
|
143
144
|
*/
|
|
144
|
-
const CONTENT_COLOR_VAR: Record<
|
|
145
|
-
Exclude<NonNullable<ButtonVariantProps['variant']>, 'destructive'>,
|
|
146
|
-
string
|
|
147
|
-
> = {
|
|
145
|
+
const CONTENT_COLOR_VAR: Record<NonNullable<ButtonVariantProps['variant']>, string> = {
|
|
148
146
|
primary: '--color-primary-foreground',
|
|
147
|
+
destructive: '--color-destructive-solid-foreground',
|
|
149
148
|
secondary: '--color-secondary-foreground',
|
|
150
149
|
outline: '--color-foreground',
|
|
151
150
|
ghost: '--color-foreground',
|
|
@@ -328,17 +327,9 @@ export const Button = forwardRef<View, ButtonProps>(
|
|
|
328
327
|
// Icons in the content slots inherit this, so they stay legible when the
|
|
329
328
|
// theme inverts the button's background. Without it every caller has to
|
|
330
329
|
// hardcode a hex that is wrong in one theme or the other.
|
|
331
|
-
const themedColor = useCSSVariable(
|
|
332
|
-
CONTENT_COLOR_VAR[
|
|
333
|
-
resolvedVariant === 'destructive' ? 'primary' : (resolvedVariant ?? 'primary')
|
|
334
|
-
]
|
|
335
|
-
);
|
|
330
|
+
const themedColor = useCSSVariable(CONTENT_COLOR_VAR[resolvedVariant ?? 'primary']);
|
|
336
331
|
const contentColor =
|
|
337
|
-
|
|
338
|
-
? '#ffffff'
|
|
339
|
-
: typeof themedColor === 'string'
|
|
340
|
-
? themedColor
|
|
341
|
-
: undefined;
|
|
332
|
+
typeof themedColor === 'string' ? themedColor : undefined;
|
|
342
333
|
|
|
343
334
|
if (nativeUI) {
|
|
344
335
|
const { Host, Button: NativeButton, RNHostView } = nativeUI;
|
|
@@ -446,6 +437,9 @@ export const Button = forwardRef<View, ButtonProps>(
|
|
|
446
437
|
accessibilityRole="button"
|
|
447
438
|
accessibilityState={{ disabled: isDisabled, busy: loading }}
|
|
448
439
|
disabled={isDisabled}
|
|
440
|
+
// Joined segments cannot borrow room from each other without making
|
|
441
|
+
// two actions claim the same pixels. ButtonGroup owns that layout.
|
|
442
|
+
hitSlop={attached ? undefined : BUTTON_HIT_SLOP[resolvedSize ?? 'md']}
|
|
449
443
|
// Before the spread, so a caller can still ask for the scale back.
|
|
450
444
|
pressScale={attached ? 1 : undefined}
|
|
451
445
|
className={root({ className: cn(attached && ATTACHED, className) })}
|
|
@@ -249,9 +249,12 @@ export interface CandlestickChartProps extends ViewProps {
|
|
|
249
249
|
/** Key holding the closing price. */
|
|
250
250
|
closeDataKey?: string;
|
|
251
251
|
/**
|
|
252
|
-
* `loading`
|
|
253
|
-
*
|
|
254
|
-
*
|
|
252
|
+
* `loading` leaves the plot empty — the frame, the grid and the header stay,
|
|
253
|
+
* and no candles are drawn. Turning `ready` grows them in left to right.
|
|
254
|
+
*
|
|
255
|
+
* Nothing stands in for the candles while they are missing. A placeholder
|
|
256
|
+
* candle is four made-up prices, and a reader has no way to tell an invented
|
|
257
|
+
* one from a real one until it changes under them.
|
|
255
258
|
*/
|
|
256
259
|
status?: CandlestickChartStatus;
|
|
257
260
|
/** Width ÷ height. `1.6` suits a chart this dense better than `2`. */
|
|
@@ -323,7 +326,7 @@ const CandlestickChartRoot = forwardRef<CandlestickChartHandle, CandlestickChart
|
|
|
323
326
|
closeDataKey = 'close',
|
|
324
327
|
status = 'ready',
|
|
325
328
|
aspectRatio = 1.6,
|
|
326
|
-
animationDuration =
|
|
329
|
+
animationDuration = 700,
|
|
327
330
|
domainDuration = 500,
|
|
328
331
|
yDomain,
|
|
329
332
|
candleGap = 0.3,
|
|
@@ -427,17 +430,27 @@ const CandlestickChartRoot = forwardRef<CandlestickChartHandle, CandlestickChart
|
|
|
427
430
|
reveal.value = 0;
|
|
428
431
|
reveal.value = withTiming(1, {
|
|
429
432
|
duration: animationDuration,
|
|
430
|
-
easing: Easing.
|
|
433
|
+
easing: Easing.out(Easing.cubic),
|
|
431
434
|
});
|
|
432
435
|
},
|
|
433
436
|
[reducedMotion, animationDuration, reveal]
|
|
434
437
|
);
|
|
435
438
|
|
|
436
439
|
useEffect(() => {
|
|
437
|
-
|
|
440
|
+
/*
|
|
441
|
+
* Going back to `loading` arms the reveal again. Without this a chart
|
|
442
|
+
* that is refetched comes back fully drawn on the frame the data lands,
|
|
443
|
+
* which reads as the loading state having been for nothing.
|
|
444
|
+
*/
|
|
445
|
+
if (loading) {
|
|
446
|
+
revealed.current = false;
|
|
447
|
+
reveal.value = 0;
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
if (revealed.current || plot.width <= 0 || !data.length) return;
|
|
438
451
|
revealed.current = true;
|
|
439
452
|
playReveal();
|
|
440
|
-
}, [loading, plot.width, data.length, playReveal]);
|
|
453
|
+
}, [loading, plot.width, data.length, playReveal, reveal]);
|
|
441
454
|
|
|
442
455
|
useImperativeHandle(ref, () => ({ replay: playReveal }), [playReveal]);
|
|
443
456
|
|
|
@@ -23,13 +23,40 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { forwardRef } from 'react';
|
|
25
25
|
import { View, type ViewProps } from 'react-native';
|
|
26
|
+
import { tv, type VariantProps } from 'tailwind-variants';
|
|
26
27
|
import { cn } from '../../utils/cn';
|
|
27
28
|
import { Text, type TextProps } from '../../primitives/text';
|
|
28
29
|
|
|
30
|
+
const footerVariants = tv({
|
|
31
|
+
base: 'flex-row items-center gap-2',
|
|
32
|
+
variants: {
|
|
33
|
+
/**
|
|
34
|
+
* Whether the footer draws a surface of its own.
|
|
35
|
+
*
|
|
36
|
+
* `plain` is part of the card: same background, padding continuing from the
|
|
37
|
+
* content above it. `panel` is a band set into it — a rule across the top,
|
|
38
|
+
* a step darker, and the card's own bottom corners — so the buttons read as
|
|
39
|
+
* the card's actions rather than as the last thing in its body. Use it when
|
|
40
|
+
* the footer is what somebody does with the card, not more of what it says.
|
|
41
|
+
*/
|
|
42
|
+
variant: {
|
|
43
|
+
plain: 'p-6 pt-0',
|
|
44
|
+
panel: 'rounded-b-2xl border-t border-border bg-inset p-6',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
defaultVariants: {
|
|
48
|
+
variant: 'plain',
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
29
52
|
export interface CardProps extends ViewProps {
|
|
30
53
|
className?: string;
|
|
31
54
|
}
|
|
32
55
|
|
|
56
|
+
export interface CardFooterProps
|
|
57
|
+
extends CardProps,
|
|
58
|
+
VariantProps<typeof footerVariants> {}
|
|
59
|
+
|
|
33
60
|
const CardRoot = forwardRef<View, CardProps>(({ className, ...props }, ref) => (
|
|
34
61
|
<View
|
|
35
62
|
ref={ref}
|
|
@@ -69,13 +96,11 @@ const CardContent = forwardRef<View, CardProps>(({ className, ...props }, ref) =
|
|
|
69
96
|
));
|
|
70
97
|
CardContent.displayName = 'Card.Content';
|
|
71
98
|
|
|
72
|
-
const CardFooter = forwardRef<View,
|
|
73
|
-
|
|
74
|
-
ref={ref}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
/>
|
|
78
|
-
));
|
|
99
|
+
const CardFooter = forwardRef<View, CardFooterProps>(
|
|
100
|
+
({ className, variant, ...props }, ref) => (
|
|
101
|
+
<View ref={ref} className={cn(footerVariants({ variant }), className)} {...props} />
|
|
102
|
+
)
|
|
103
|
+
);
|
|
79
104
|
CardFooter.displayName = 'Card.Footer';
|
|
80
105
|
|
|
81
106
|
export const Card = Object.assign(CardRoot, {
|
|
@@ -207,7 +207,7 @@ export interface CarouselProps extends ViewProps {
|
|
|
207
207
|
autoplay?: boolean;
|
|
208
208
|
/** Milliseconds each slide is held when `autoplay` is set. */
|
|
209
209
|
autoplayInterval?: number;
|
|
210
|
-
/** Controlled active slide. */
|
|
210
|
+
/** Controlled active slide. Requests move visually only after this value changes. */
|
|
211
211
|
index?: number;
|
|
212
212
|
/** Starting slide when uncontrolled. */
|
|
213
213
|
defaultIndex?: number;
|
|
@@ -269,28 +269,37 @@ const CarouselRoot = forwardRef<CarouselHandle, CarouselProps>(function Carousel
|
|
|
269
269
|
[isControlled, onIndexChange]
|
|
270
270
|
);
|
|
271
271
|
|
|
272
|
-
const
|
|
272
|
+
const animateTo = useCallback(
|
|
273
273
|
(target: number) => {
|
|
274
|
-
if (count <= 0) return;
|
|
275
|
-
const settled = loop ? wrap(target, count) : Math.max(0, Math.min(target, count - 1));
|
|
276
|
-
|
|
277
274
|
if (reducedMotion) {
|
|
278
|
-
progress.value =
|
|
275
|
+
progress.value = target;
|
|
279
276
|
} else if (loop) {
|
|
280
277
|
// Spring to the nearest representation of the target rather than to the
|
|
281
278
|
// target itself, so a wrap from the last slide to the first travels one
|
|
282
279
|
// step forward instead of winding all the way back through the run.
|
|
283
|
-
const shortest = progress.value + distance(
|
|
280
|
+
const shortest = progress.value + distance(target, progress.value, count, true);
|
|
284
281
|
progress.value = withSpring(shortest, SPRING, (finished) => {
|
|
285
282
|
if (finished) progress.value = wrap(progress.value, count);
|
|
286
283
|
});
|
|
287
284
|
} else {
|
|
288
|
-
progress.value = withSpring(
|
|
285
|
+
progress.value = withSpring(target, SPRING);
|
|
289
286
|
}
|
|
287
|
+
},
|
|
288
|
+
[count, loop, progress, reducedMotion]
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const scrollTo = useCallback(
|
|
292
|
+
(target: number) => {
|
|
293
|
+
if (count <= 0) return;
|
|
294
|
+
const settled = loop ? wrap(target, count) : Math.max(0, Math.min(target, count - 1));
|
|
290
295
|
|
|
296
|
+
// A controlled request belongs to its owner. The finger may move the run,
|
|
297
|
+
// but after release it returns to the current prop until the owner accepts
|
|
298
|
+
// the request by changing that prop.
|
|
299
|
+
animateTo(isControlled ? index : settled);
|
|
291
300
|
setIndex(settled);
|
|
292
301
|
},
|
|
293
|
-
[count,
|
|
302
|
+
[animateTo, count, index, isControlled, loop, setIndex]
|
|
294
303
|
);
|
|
295
304
|
|
|
296
305
|
const next = useCallback(() => scrollTo(index + 1), [index, scrollTo]);
|
|
@@ -302,8 +311,8 @@ const CarouselRoot = forwardRef<CarouselHandle, CarouselProps>(function Carousel
|
|
|
302
311
|
useEffect(() => {
|
|
303
312
|
if (!isControlled || indexProp === undefined || count <= 0) return;
|
|
304
313
|
if (Math.abs(progress.value - indexProp) < 0.001) return;
|
|
305
|
-
|
|
306
|
-
}, [isControlled, indexProp, count, progress
|
|
314
|
+
animateTo(indexProp);
|
|
315
|
+
}, [animateTo, isControlled, indexProp, count, progress]);
|
|
307
316
|
|
|
308
317
|
/*
|
|
309
318
|
* Autoplay stops for good the first time a finger lands, rather than pausing.
|
|
@@ -688,16 +697,30 @@ function CarouselDots({
|
|
|
688
697
|
key={dot}
|
|
689
698
|
disabled={!interactive}
|
|
690
699
|
onPress={() => scrollTo(dot)}
|
|
691
|
-
hitSlop={6}
|
|
692
700
|
accessibilityRole="tab"
|
|
693
701
|
accessibilityLabel={`Slide ${dot + 1} of ${count}`}
|
|
694
702
|
accessibilityState={{ selected: active }}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
703
|
+
// 24, not the 48 the buttons get. Dots sit a few points apart, so
|
|
704
|
+
// a 48 box would either overlap its neighbours — making a tap near
|
|
705
|
+
// the join land on the wrong slide — or push a five-slide run out
|
|
706
|
+
// to the width of the screen. 24 clears the minimum with the pitch
|
|
707
|
+
// still wider than the target, so no two dots contend for a touch.
|
|
708
|
+
className={cn('items-center justify-center', interactive && 'h-6 w-6')}
|
|
709
|
+
>
|
|
710
|
+
<View
|
|
711
|
+
className={cn(
|
|
712
|
+
'rounded-full',
|
|
713
|
+
horizontal
|
|
714
|
+
? active
|
|
715
|
+
? 'h-1 w-4'
|
|
716
|
+
: 'h-1 w-1'
|
|
717
|
+
: active
|
|
718
|
+
? 'h-4 w-1'
|
|
719
|
+
: 'h-1 w-1',
|
|
720
|
+
active ? 'bg-foreground' : 'bg-foreground/30'
|
|
721
|
+
)}
|
|
722
|
+
/>
|
|
723
|
+
</Pressable>
|
|
701
724
|
);
|
|
702
725
|
})}
|
|
703
726
|
</View>
|
|
@@ -723,12 +746,11 @@ function makeArrow(direction: 'previous' | 'next') {
|
|
|
723
746
|
<Pressable
|
|
724
747
|
disabled={disabled}
|
|
725
748
|
onPress={direction === 'next' ? next : previous}
|
|
726
|
-
hitSlop={8}
|
|
727
749
|
accessibilityRole="button"
|
|
728
750
|
accessibilityLabel={direction === 'next' ? 'Next slide' : 'Previous slide'}
|
|
729
751
|
accessibilityState={{ disabled }}
|
|
730
752
|
className={cn(
|
|
731
|
-
'items-center justify-center rounded-full
|
|
753
|
+
'h-12 w-12 items-center justify-center rounded-full',
|
|
732
754
|
disabled ? 'opacity-30' : 'active:bg-foreground/10',
|
|
733
755
|
className
|
|
734
756
|
)}
|
|
@@ -13,7 +13,7 @@ import { Text } from '../../primitives/text';
|
|
|
13
13
|
|
|
14
14
|
const checkboxVariants = tv({
|
|
15
15
|
slots: {
|
|
16
|
-
row: 'flex-row items-center gap-2.5',
|
|
16
|
+
row: 'min-h-12 min-w-12 flex-row items-center gap-2.5',
|
|
17
17
|
box: 'h-5 w-5 items-center justify-center rounded-md border border-input bg-background',
|
|
18
18
|
fill: 'absolute inset-0 items-center justify-center rounded-md bg-primary',
|
|
19
19
|
content: 'flex-1 gap-0.5',
|
|
@@ -130,7 +130,6 @@ export const Checkbox = forwardRef<View, CheckboxProps>(
|
|
|
130
130
|
disabled={disabled}
|
|
131
131
|
// An indeterminate box resolves upward: the press turns the group on.
|
|
132
132
|
onPress={() => onCheckedChange?.(indeterminate ? true : !checked)}
|
|
133
|
-
hitSlop={8}
|
|
134
133
|
// A description makes the row two lines tall — align the box to the
|
|
135
134
|
// label rather than centring it against the whole block.
|
|
136
135
|
className={slots.row({
|
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
} from 'react';
|
|
13
13
|
import { Pressable, View, type ViewProps } from 'react-native';
|
|
14
14
|
import Animated, { FadeIn, FadeOut, ZoomIn } from 'react-native-reanimated';
|
|
15
|
-
import {
|
|
15
|
+
import { tv, type VariantProps } from 'tailwind-variants';
|
|
16
|
+
import { ModalPortal } from '../../primitives/portal';
|
|
16
17
|
import { Scrim } from '../../primitives/scrim';
|
|
17
18
|
import { Text, type TextProps, textChildren } from '../../primitives/text';
|
|
18
19
|
import { useBackHandler } from '../../hooks/use-back-handler';
|
|
@@ -110,7 +111,7 @@ function DialogContent({
|
|
|
110
111
|
if (!open) return null;
|
|
111
112
|
|
|
112
113
|
return (
|
|
113
|
-
<
|
|
114
|
+
<ModalPortal>
|
|
114
115
|
{/* Portal content mounts under PortalHost, outside this provider's
|
|
115
116
|
subtree — re-provide the context so Dialog.Close etc. keep working. */}
|
|
116
117
|
<DialogContext.Provider value={context}>
|
|
@@ -129,6 +130,8 @@ function DialogContent({
|
|
|
129
130
|
entering={ZoomIn.springify().damping(18).stiffness(250).mass(0.6)}
|
|
130
131
|
exiting={FadeOut.duration(120)}
|
|
131
132
|
accessibilityViewIsModal
|
|
133
|
+
// `p-5` is `DIALOG_PADDING` above — a panel footer bleeds back out
|
|
134
|
+
// through exactly this much, so the two move together.
|
|
132
135
|
className={cn(
|
|
133
136
|
'w-full max-w-sm gap-1.5 rounded-2xl border border-border bg-popover p-5 shadow-lg',
|
|
134
137
|
className
|
|
@@ -139,7 +142,7 @@ function DialogContent({
|
|
|
139
142
|
</Animated.View>
|
|
140
143
|
</Animated.View>
|
|
141
144
|
</DialogContext.Provider>
|
|
142
|
-
</
|
|
145
|
+
</ModalPortal>
|
|
143
146
|
);
|
|
144
147
|
}
|
|
145
148
|
|
|
@@ -163,10 +166,60 @@ const DialogDescription = forwardRef<React.ElementRef<typeof Text>, TextProps>(
|
|
|
163
166
|
);
|
|
164
167
|
DialogDescription.displayName = 'Dialog.Description';
|
|
165
168
|
|
|
166
|
-
|
|
169
|
+
/**
|
|
170
|
+
* The dialog's padding, in points. The panel footer bleeds back out through it,
|
|
171
|
+
* so the two have to agree — a mismatch is a band inset from one edge.
|
|
172
|
+
*/
|
|
173
|
+
const DIALOG_PADDING = 20;
|
|
174
|
+
|
|
175
|
+
const dialogFooterVariants = tv({
|
|
176
|
+
base: 'flex-row items-center justify-end gap-2',
|
|
177
|
+
variants: {
|
|
178
|
+
/**
|
|
179
|
+
* Whether the footer draws a surface of its own.
|
|
180
|
+
*
|
|
181
|
+
* `plain` is part of the dialog: same background, sitting under the content
|
|
182
|
+
* with a gap above it. `panel` is a band set into it — a rule across the
|
|
183
|
+
* top, a step darker, and the dialog's own bottom corners — which separates
|
|
184
|
+
* what the dialog says from what you can do about it. Worth it on a dialog
|
|
185
|
+
* with a form in it, where the buttons are otherwise one more row of it.
|
|
186
|
+
*/
|
|
187
|
+
variant: {
|
|
188
|
+
plain: 'mt-4',
|
|
189
|
+
panel: 'mt-5 rounded-b-2xl border-t border-border bg-inset',
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
defaultVariants: {
|
|
193
|
+
variant: 'plain',
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
export interface DialogFooterProps extends ViewProps, VariantProps<typeof dialogFooterVariants> {
|
|
198
|
+
className?: string;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function DialogFooter({ className, variant, style, ...props }: DialogFooterProps) {
|
|
167
202
|
return (
|
|
168
203
|
<View
|
|
169
|
-
|
|
204
|
+
/*
|
|
205
|
+
* The bleed is a style rather than a class because it is arithmetic on the
|
|
206
|
+
* dialog's own padding, and a class would be a second copy of that number
|
|
207
|
+
* waiting to disagree with the first.
|
|
208
|
+
*/
|
|
209
|
+
style={
|
|
210
|
+
variant === 'panel'
|
|
211
|
+
? [
|
|
212
|
+
{
|
|
213
|
+
marginHorizontal: -DIALOG_PADDING,
|
|
214
|
+
marginBottom: -DIALOG_PADDING,
|
|
215
|
+
paddingHorizontal: DIALOG_PADDING,
|
|
216
|
+
paddingVertical: 16,
|
|
217
|
+
},
|
|
218
|
+
style,
|
|
219
|
+
]
|
|
220
|
+
: style
|
|
221
|
+
}
|
|
222
|
+
className={cn(dialogFooterVariants({ variant }), className)}
|
|
170
223
|
{...props}
|
|
171
224
|
/>
|
|
172
225
|
);
|
|
@@ -84,7 +84,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
84
84
|
import { tv } from 'tailwind-variants';
|
|
85
85
|
import { useCSSVariable } from 'uniwind';
|
|
86
86
|
import { XIcon } from '../../icons';
|
|
87
|
-
import {
|
|
87
|
+
import { ModalPortal } from '../../primitives/portal';
|
|
88
88
|
import { Scrim } from '../../primitives/scrim';
|
|
89
89
|
import { Text, textChildren } from '../../primitives/text';
|
|
90
90
|
import { useBackHandler } from '../../hooks/use-back-handler';
|
|
@@ -458,7 +458,7 @@ function DrawerContent({
|
|
|
458
458
|
};
|
|
459
459
|
|
|
460
460
|
return (
|
|
461
|
-
<
|
|
461
|
+
<ModalPortal>
|
|
462
462
|
{/* Portal content mounts under PortalHost, outside this provider's
|
|
463
463
|
subtree — re-provide the context so Drawer.Close keeps working. */}
|
|
464
464
|
<DrawerContext.Provider value={{ open, setOpen }}>
|
|
@@ -561,7 +561,7 @@ function DrawerContent({
|
|
|
561
561
|
</View>
|
|
562
562
|
</DirectionContext.Provider>
|
|
563
563
|
</DrawerContext.Provider>
|
|
564
|
-
</
|
|
564
|
+
</ModalPortal>
|
|
565
565
|
);
|
|
566
566
|
}
|
|
567
567
|
|
|
@@ -129,7 +129,10 @@ const fabVariants = tv({
|
|
|
129
129
|
primary: { root: 'bg-primary', label: 'text-primary-foreground' },
|
|
130
130
|
secondary: { root: 'bg-secondary', label: 'text-secondary-foreground' },
|
|
131
131
|
surface: { root: 'border border-border bg-popover', label: 'text-foreground' },
|
|
132
|
-
destructive: {
|
|
132
|
+
destructive: {
|
|
133
|
+
root: 'bg-destructive',
|
|
134
|
+
label: 'text-destructive-solid-foreground',
|
|
135
|
+
},
|
|
133
136
|
},
|
|
134
137
|
disabled: {
|
|
135
138
|
true: { root: 'opacity-[0.64] shadow-none' },
|
|
@@ -158,8 +161,9 @@ export type FabSize = NonNullable<FabVariantProps['size']>;
|
|
|
158
161
|
export type FabVariant = NonNullable<FabVariantProps['variant']>;
|
|
159
162
|
|
|
160
163
|
/** The theme token each variant's icon reads against. */
|
|
161
|
-
const CONTENT_COLOR_VAR: Record<
|
|
164
|
+
const CONTENT_COLOR_VAR: Record<FabVariant, string> = {
|
|
162
165
|
primary: '--color-primary-foreground',
|
|
166
|
+
destructive: '--color-destructive-solid-foreground',
|
|
163
167
|
secondary: '--color-secondary-foreground',
|
|
164
168
|
surface: '--color-foreground',
|
|
165
169
|
};
|
|
@@ -234,17 +238,9 @@ const FabRoot = forwardRef<View, FabProps>(
|
|
|
234
238
|
disabled,
|
|
235
239
|
});
|
|
236
240
|
|
|
237
|
-
const themed = useCSSVariable(
|
|
238
|
-
CONTENT_COLOR_VAR[
|
|
239
|
-
variant === 'destructive' ? 'primary' : (variant ?? 'primary')
|
|
240
|
-
]
|
|
241
|
-
);
|
|
241
|
+
const themed = useCSSVariable(CONTENT_COLOR_VAR[variant ?? 'primary']);
|
|
242
242
|
const contentColor =
|
|
243
|
-
|
|
244
|
-
? '#ffffff'
|
|
245
|
-
: typeof themed === 'string'
|
|
246
|
-
? themed
|
|
247
|
-
: undefined;
|
|
243
|
+
typeof themed === 'string' ? themed : undefined;
|
|
248
244
|
|
|
249
245
|
const handlePress = useCallback(() => {
|
|
250
246
|
if (haptics) selectionTick();
|