panelui-native 0.80.0 → 0.82.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/lib/module/components/bottom-sheet/index.js +90 -17
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/button/index.js +13 -0
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/button-group/index.js +3 -0
- package/lib/module/components/button-group/index.js.map +1 -1
- package/lib/module/components/image-generation/dot-field.js +210 -0
- package/lib/module/components/image-generation/dot-field.js.map +1 -0
- package/lib/module/components/image-generation/index.js +417 -0
- package/lib/module/components/image-generation/index.js.map +1 -0
- package/lib/module/components/kpi/index.js +2 -4
- package/lib/module/components/kpi/index.js.map +1 -1
- package/lib/module/components/menu/index.js +8 -16
- package/lib/module/components/menu/index.js.map +1 -1
- package/lib/module/components/panelside/index.js +340 -33
- package/lib/module/components/panelside/index.js.map +1 -1
- package/lib/module/components/planner/index.js +446 -32
- package/lib/module/components/planner/index.js.map +1 -1
- package/lib/module/components/planner/planner-entries.js +36 -0
- package/lib/module/components/planner/planner-entries.js.map +1 -1
- package/lib/module/components/planner/planner-grid-navigation.js +34 -2
- package/lib/module/components/planner/planner-grid-navigation.js.map +1 -1
- package/lib/module/components/planner/planner-weeks.js +95 -0
- package/lib/module/components/planner/planner-weeks.js.map +1 -0
- package/lib/module/components/select/index.js +127 -23
- package/lib/module/components/select/index.js.map +1 -1
- package/lib/module/components/slide-button/index.js +610 -0
- package/lib/module/components/slide-button/index.js.map +1 -0
- package/lib/module/components/slide-button/slide-button-track.js +98 -0
- package/lib/module/components/slide-button/slide-button-track.js.map +1 -0
- package/lib/module/icons/index.js +553 -1934
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +3 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/primitives/portal.js +15 -1
- package/lib/module/primitives/portal.js.map +1 -1
- package/lib/module/utils/date.js +10 -3
- package/lib/module/utils/date.js.map +1 -1
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/button/index.d.ts +12 -0
- package/lib/typescript/src/components/button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/button-group/index.d.ts +9 -0
- package/lib/typescript/src/components/button-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/image-generation/dot-field.d.ts +61 -0
- package/lib/typescript/src/components/image-generation/dot-field.d.ts.map +1 -0
- package/lib/typescript/src/components/image-generation/index.d.ts +126 -0
- package/lib/typescript/src/components/image-generation/index.d.ts.map +1 -0
- package/lib/typescript/src/components/kpi/index.d.ts.map +1 -1
- package/lib/typescript/src/components/menu/index.d.ts.map +1 -1
- package/lib/typescript/src/components/panelside/index.d.ts +194 -10
- package/lib/typescript/src/components/panelside/index.d.ts.map +1 -1
- package/lib/typescript/src/components/planner/index.d.ts +65 -4
- package/lib/typescript/src/components/planner/index.d.ts.map +1 -1
- package/lib/typescript/src/components/planner/planner-entries.d.ts +18 -0
- package/lib/typescript/src/components/planner/planner-entries.d.ts.map +1 -1
- package/lib/typescript/src/components/planner/planner-grid-navigation.d.ts +7 -1
- package/lib/typescript/src/components/planner/planner-grid-navigation.d.ts.map +1 -1
- package/lib/typescript/src/components/planner/planner-weeks.d.ts +50 -0
- package/lib/typescript/src/components/planner/planner-weeks.d.ts.map +1 -0
- package/lib/typescript/src/components/select/index.d.ts +75 -2
- package/lib/typescript/src/components/select/index.d.ts.map +1 -1
- package/lib/typescript/src/components/slide-button/index.d.ts +317 -0
- package/lib/typescript/src/components/slide-button/index.d.ts.map +1 -0
- package/lib/typescript/src/components/slide-button/slide-button-track.d.ts +70 -0
- package/lib/typescript/src/components/slide-button/slide-button-track.d.ts.map +1 -0
- package/lib/typescript/src/icons/index.d.ts +136 -189
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +5 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/portal.d.ts.map +1 -1
- package/lib/typescript/src/utils/date.d.ts +1 -1
- package/lib/typescript/src/utils/date.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/components/bottom-sheet/index.tsx +97 -17
- package/src/components/button/index.tsx +10 -2
- package/src/components/button-group/index.tsx +1 -0
- package/src/components/image-generation/dot-field.ts +226 -0
- package/src/components/image-generation/index.tsx +488 -0
- package/src/components/kpi/index.tsx +2 -4
- package/src/components/menu/index.tsx +10 -15
- package/src/components/panelside/index.tsx +498 -33
- package/src/components/planner/index.tsx +516 -38
- package/src/components/planner/planner-entries.ts +33 -0
- package/src/components/planner/planner-grid-navigation.ts +37 -2
- package/src/components/planner/planner-weeks.ts +90 -0
- package/src/components/select/index.tsx +158 -31
- package/src/components/slide-button/index.tsx +681 -0
- package/src/components/slide-button/slide-button-track.ts +100 -0
- package/src/icons/index.tsx +338 -1327
- package/src/index.ts +30 -0
- package/src/primitives/portal.tsx +15 -1
- package/src/utils/date.ts +13 -3
|
@@ -78,17 +78,23 @@
|
|
|
78
78
|
* accessibility setting on, every spring here resolves instantly to its target
|
|
79
79
|
* rather than travelling.
|
|
80
80
|
*/
|
|
81
|
-
import { cloneElement, createContext, isValidElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
82
|
-
import { Platform, Pressable, ScrollView, StyleSheet, TextInput, useWindowDimensions, View } from 'react-native';
|
|
81
|
+
import { Children, cloneElement, createContext, isValidElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
82
|
+
import { Keyboard, Platform, Pressable, ScrollView, StyleSheet, TextInput, useWindowDimensions, View } from 'react-native';
|
|
83
83
|
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
84
84
|
import Animated, { runOnJS, useAnimatedProps, useAnimatedStyle, useDerivedValue, useSharedValue, withSpring } from 'react-native-reanimated';
|
|
85
85
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
86
86
|
import { tv } from 'tailwind-variants';
|
|
87
87
|
import { useCSSVariable } from 'uniwind';
|
|
88
88
|
import { LinearGradient } from 'expo-linear-gradient';
|
|
89
|
-
import {
|
|
89
|
+
import { HugeiconsIcon } from '@hugeicons/react-native';
|
|
90
|
+
import Cancel01Icon from '@hugeicons/core-free-icons/Cancel01Icon';
|
|
91
|
+
import Menu01Icon from '@hugeicons/core-free-icons/Menu01Icon';
|
|
92
|
+
import MoreHorizontalIcon from '@hugeicons/core-free-icons/MoreHorizontalIcon';
|
|
93
|
+
import Search01Icon from '@hugeicons/core-free-icons/Search01Icon';
|
|
94
|
+
import { IconColorProvider, useIconColor } from "../../icons/index.js";
|
|
90
95
|
import { BottomSheet, bottomSheetDetentHeight } from "../bottom-sheet/index.js";
|
|
91
96
|
import { Button } from "../button/index.js";
|
|
97
|
+
import { Menu } from "../menu/index.js";
|
|
92
98
|
import { Tabs } from "../tabs/index.js";
|
|
93
99
|
import { getNativeUI } from "../../native/index.js";
|
|
94
100
|
import { AnimatedPressable } from "../../primitives/animated-pressable.js";
|
|
@@ -242,6 +248,53 @@ const clamp = (value, min, max) => {
|
|
|
242
248
|
return Math.min(Math.max(value, min), max);
|
|
243
249
|
};
|
|
244
250
|
|
|
251
|
+
/**
|
|
252
|
+
* The stroke weight the panel's glyphs are drawn at.
|
|
253
|
+
*
|
|
254
|
+
* A shade heavier than the icon set's own default. The panel sits over a
|
|
255
|
+
* dimmed screen and its rows are quiet by design, so a hairline glyph goes
|
|
256
|
+
* soft against them at the sizes used here.
|
|
257
|
+
*/
|
|
258
|
+
const GLYPH_STROKE = 1.8;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* What a glyph is drawn in when nothing has said. The same neutral the rest of
|
|
262
|
+
* the chrome falls back to — visible against either theme, and never the thing
|
|
263
|
+
* that decides how a panel looks, since every surface here provides a colour.
|
|
264
|
+
*/
|
|
265
|
+
const GLYPH_FALLBACK = '#737373';
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Floor for a row's action menu.
|
|
269
|
+
*
|
|
270
|
+
* Wide enough for a verb and its glyph with the label doing the growing. Left
|
|
271
|
+
* to size itself, a menu takes its width from the one thing in each row that
|
|
272
|
+
* is not flexible — the glyph — and comes up as a column of icons.
|
|
273
|
+
*/
|
|
274
|
+
const ACTIONS_MIN_WIDTH = 220;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* One of the panel's own glyphs.
|
|
278
|
+
*
|
|
279
|
+
* It exists to keep the colour contract the rest of the library has: an icon
|
|
280
|
+
* takes an explicit colour, then the one an enclosing surface is providing,
|
|
281
|
+
* and only then falls back. The drawing component underneath knows nothing
|
|
282
|
+
* about that inheritance and needs the resolved value handed to it.
|
|
283
|
+
*/
|
|
284
|
+
function Glyph({
|
|
285
|
+
icon,
|
|
286
|
+
size = 20,
|
|
287
|
+
color
|
|
288
|
+
}) {
|
|
289
|
+
const inherited = useIconColor();
|
|
290
|
+
return /*#__PURE__*/_jsx(HugeiconsIcon, {
|
|
291
|
+
icon: icon,
|
|
292
|
+
size: size,
|
|
293
|
+
color: color ?? inherited ?? GLYPH_FALLBACK,
|
|
294
|
+
strokeWidth: GLYPH_STROKE
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
245
298
|
/**
|
|
246
299
|
* What a header or a footer paints behind itself.
|
|
247
300
|
*
|
|
@@ -251,6 +304,8 @@ const clamp = (value, min, max) => {
|
|
|
251
304
|
* that is a row of the layout rather than something floating over one.
|
|
252
305
|
*/
|
|
253
306
|
|
|
307
|
+
/** How the panel's small round controls are drawn: a fill, or a ring. */
|
|
308
|
+
|
|
254
309
|
const itemVariants = tv({
|
|
255
310
|
// No width: in a group it stretches on its own, and pinning it to full width
|
|
256
311
|
// would stop it sharing a footer row with anything else. `shrink` because
|
|
@@ -293,7 +348,8 @@ const ctaVariants = tv({
|
|
|
293
348
|
},
|
|
294
349
|
size: {
|
|
295
350
|
default: 'h-11 gap-2 px-6',
|
|
296
|
-
lg: 'h-13 gap-2.5 px-7'
|
|
351
|
+
lg: 'h-13 gap-2.5 px-7',
|
|
352
|
+
xl: 'h-14 gap-3 px-8'
|
|
297
353
|
}
|
|
298
354
|
},
|
|
299
355
|
defaultVariants: {
|
|
@@ -322,8 +378,17 @@ export function usePanelside() {
|
|
|
322
378
|
progress,
|
|
323
379
|
docked,
|
|
324
380
|
searchOpen,
|
|
325
|
-
setSearchOpen
|
|
381
|
+
setSearchOpen,
|
|
382
|
+
route,
|
|
383
|
+
navigate
|
|
326
384
|
} = usePanelsideContext('usePanelside');
|
|
385
|
+
|
|
386
|
+
// `navigate` closes the panel. The root cannot do it there without making
|
|
387
|
+
// its own `setOpen` a dependency of the callback every row holds.
|
|
388
|
+
const go = useCallback(next => {
|
|
389
|
+
navigate(next);
|
|
390
|
+
setOpen(false);
|
|
391
|
+
}, [navigate, setOpen]);
|
|
327
392
|
return {
|
|
328
393
|
open,
|
|
329
394
|
setOpen,
|
|
@@ -331,7 +396,9 @@ export function usePanelside() {
|
|
|
331
396
|
progress,
|
|
332
397
|
docked,
|
|
333
398
|
searchOpen,
|
|
334
|
-
setSearchOpen
|
|
399
|
+
setSearchOpen,
|
|
400
|
+
route,
|
|
401
|
+
navigate: go
|
|
335
402
|
};
|
|
336
403
|
}
|
|
337
404
|
|
|
@@ -359,6 +426,9 @@ function PanelsideRoot({
|
|
|
359
426
|
scale,
|
|
360
427
|
radius,
|
|
361
428
|
dim,
|
|
429
|
+
route: controlledRoute,
|
|
430
|
+
defaultRoute = '',
|
|
431
|
+
onRouteChange,
|
|
362
432
|
className
|
|
363
433
|
}) {
|
|
364
434
|
const {
|
|
@@ -369,6 +439,19 @@ function PanelsideRoot({
|
|
|
369
439
|
const controlled = controlledOpen !== undefined;
|
|
370
440
|
const open = controlled ? controlledOpen : uncontrolledOpen;
|
|
371
441
|
const [searchOpen, setSearchOpen] = useState(false);
|
|
442
|
+
const [uncontrolledRoute, setUncontrolledRoute] = useState(defaultRoute);
|
|
443
|
+
const route = controlledRoute ?? uncontrolledRoute;
|
|
444
|
+
|
|
445
|
+
/*
|
|
446
|
+
* Navigating closes the panel, and does so here rather than at each call
|
|
447
|
+
* site. The thing you just moved to would otherwise be behind the thing you
|
|
448
|
+
* moved from, which is the one arrangement no app wants — and a row that has
|
|
449
|
+
* to be told to close is a row every app writes the same three lines for.
|
|
450
|
+
*/
|
|
451
|
+
const navigate = useCallback(next => {
|
|
452
|
+
if (controlledRoute === undefined) setUncontrolledRoute(next);
|
|
453
|
+
onRouteChange?.(next);
|
|
454
|
+
}, [controlledRoute, onRouteChange]);
|
|
372
455
|
|
|
373
456
|
/*
|
|
374
457
|
* Measured rather than taken from the window, because Panelside does not
|
|
@@ -505,8 +588,10 @@ function PanelsideRoot({
|
|
|
505
588
|
radius,
|
|
506
589
|
dim,
|
|
507
590
|
searchOpen,
|
|
508
|
-
setSearchOpen
|
|
509
|
-
|
|
591
|
+
setSearchOpen,
|
|
592
|
+
route,
|
|
593
|
+
navigate
|
|
594
|
+
}), [dim, dismissible, docked, mode, navigate, open, progress, radius, route, scale, searchOpen, setOpen, toggle, width]);
|
|
510
595
|
return /*#__PURE__*/_jsx(PanelsideContext.Provider, {
|
|
511
596
|
value: context,
|
|
512
597
|
children: /*#__PURE__*/_jsx(View, {
|
|
@@ -669,7 +754,8 @@ function PanelsideSearch({
|
|
|
669
754
|
const muted = typeof placeholderTint === 'string' ? placeholderTint : undefined;
|
|
670
755
|
return /*#__PURE__*/_jsxs(View, {
|
|
671
756
|
className: cn('h-10 flex-row items-center gap-2 rounded-xl bg-secondary px-3', containerClassName),
|
|
672
|
-
children: [/*#__PURE__*/_jsx(
|
|
757
|
+
children: [/*#__PURE__*/_jsx(Glyph, {
|
|
758
|
+
icon: Search01Icon,
|
|
673
759
|
size: 16,
|
|
674
760
|
color: muted
|
|
675
761
|
}), /*#__PURE__*/_jsx(TextInput, {
|
|
@@ -785,15 +871,29 @@ function PanelsideItem({
|
|
|
785
871
|
className,
|
|
786
872
|
icon,
|
|
787
873
|
label,
|
|
788
|
-
|
|
874
|
+
to,
|
|
875
|
+
closeOnNavigate = true,
|
|
876
|
+
active: activeProp,
|
|
789
877
|
badge,
|
|
790
878
|
disabled = false,
|
|
791
879
|
size = 'default',
|
|
880
|
+
onPress,
|
|
792
881
|
children,
|
|
793
882
|
...props
|
|
794
883
|
}) {
|
|
884
|
+
const panel = useContext(PanelsideContext);
|
|
795
885
|
const restTint = useCSSVariable('--color-muted-foreground');
|
|
796
886
|
const activeTint = useCSSVariable('--color-foreground');
|
|
887
|
+
|
|
888
|
+
// A row outside a `<Panelside>` still works — the parts are usable on their
|
|
889
|
+
// own — it simply has no route to match against.
|
|
890
|
+
const active = activeProp ?? (to !== undefined && panel?.route === to);
|
|
891
|
+
const press = useCallback(event => {
|
|
892
|
+
onPress?.(event);
|
|
893
|
+
if (to === undefined || !panel) return;
|
|
894
|
+
panel.navigate(to);
|
|
895
|
+
if (closeOnNavigate) panel.setOpen(false);
|
|
896
|
+
}, [closeOnNavigate, onPress, panel, to]);
|
|
797
897
|
const tint = active ? typeof activeTint === 'string' ? activeTint : undefined : typeof restTint === 'string' ? restTint : undefined;
|
|
798
898
|
const context = useMemo(() => ({
|
|
799
899
|
active,
|
|
@@ -808,8 +908,16 @@ function PanelsideItem({
|
|
|
808
908
|
* that, and a written-out `Panelside.ItemLabel` supplies it too — so the
|
|
809
909
|
* spacer is only for the row that has neither, which is a row of nothing but
|
|
810
910
|
* an icon and a badge.
|
|
911
|
+
*
|
|
912
|
+
* And only where there is something to push. A row of nothing but an icon
|
|
913
|
+
* has no trailing content, and a spacer in it is not inert: laid out against
|
|
914
|
+
* the width available rather than against the row's own contents, it expands,
|
|
915
|
+
* takes the space with it, and squeezes whatever shares the line — which is
|
|
916
|
+
* how an icon-only row in a footer beside a compose button cropped that
|
|
917
|
+
* button's label and left its own icon short of the trailing edge.
|
|
811
918
|
*/
|
|
812
919
|
const filled = label !== undefined || children !== undefined;
|
|
920
|
+
const trailing = badge !== undefined && badge !== null;
|
|
813
921
|
return /*#__PURE__*/_jsx(PanelsideItemContext.Provider, {
|
|
814
922
|
value: context,
|
|
815
923
|
children: /*#__PURE__*/_jsxs(AnimatedPressable, {
|
|
@@ -827,12 +935,13 @@ function PanelsideItem({
|
|
|
827
935
|
},
|
|
828
936
|
accessibilityLabel: label,
|
|
829
937
|
pressScale: 0.985,
|
|
938
|
+
onPress: to !== undefined || onPress ? press : undefined,
|
|
830
939
|
...props,
|
|
831
940
|
children: [icon ? /*#__PURE__*/_jsx(PanelsideItemIcon, {
|
|
832
941
|
children: icon
|
|
833
942
|
}) : null, label !== undefined ? /*#__PURE__*/_jsx(PanelsideItemLabel, {
|
|
834
943
|
children: label
|
|
835
|
-
}) : null, filled ? null : /*#__PURE__*/_jsx(View, {
|
|
944
|
+
}) : null, filled || !trailing ? null : /*#__PURE__*/_jsx(View, {
|
|
836
945
|
className: "flex-1"
|
|
837
946
|
}), badge !== undefined && badge !== null ? /*#__PURE__*/_jsx(PanelsideItemBadge, {
|
|
838
947
|
children: badge
|
|
@@ -923,6 +1032,7 @@ function PanelsideAction({
|
|
|
923
1032
|
className,
|
|
924
1033
|
label = 'More options',
|
|
925
1034
|
children,
|
|
1035
|
+
onPress,
|
|
926
1036
|
...props
|
|
927
1037
|
}) {
|
|
928
1038
|
const tint = useCSSVariable('--color-muted-foreground');
|
|
@@ -934,14 +1044,71 @@ function PanelsideAction({
|
|
|
934
1044
|
// The glyph is small and sits next to a row-sized target, so it takes
|
|
935
1045
|
// the difference back as slop rather than as layout.
|
|
936
1046
|
,
|
|
937
|
-
hitSlop: 8
|
|
1047
|
+
hitSlop: 8
|
|
1048
|
+
// The row underneath is pressable and usually navigates. A press that
|
|
1049
|
+
// reached both would open the menu and leave the thing it is about.
|
|
1050
|
+
,
|
|
1051
|
+
onPress: event => {
|
|
1052
|
+
event.stopPropagation();
|
|
1053
|
+
onPress?.(event);
|
|
1054
|
+
},
|
|
938
1055
|
...props,
|
|
939
|
-
children: children ?? /*#__PURE__*/_jsx(
|
|
1056
|
+
children: children ?? /*#__PURE__*/_jsx(Glyph, {
|
|
1057
|
+
icon: MoreHorizontalIcon,
|
|
940
1058
|
size: 18,
|
|
941
1059
|
color: color
|
|
942
1060
|
})
|
|
943
1061
|
});
|
|
944
1062
|
}
|
|
1063
|
+
/**
|
|
1064
|
+
* A row's actions, behind an overflow button at the end of it.
|
|
1065
|
+
*
|
|
1066
|
+
* ```tsx
|
|
1067
|
+
* <Panelside.Item label={chat.title} to={chat.id}>
|
|
1068
|
+
* <Panelside.ItemActions>
|
|
1069
|
+
* <Menu.Item onSelect={rename}>Rename</Menu.Item>
|
|
1070
|
+
* <Menu.Separator />
|
|
1071
|
+
* <Menu.Item variant="destructive" onSelect={remove}>Delete</Menu.Item>
|
|
1072
|
+
* </Panelside.ItemActions>
|
|
1073
|
+
* </Panelside.Item>
|
|
1074
|
+
* ```
|
|
1075
|
+
*
|
|
1076
|
+
* The panel is anchored to the button rather than presented from the bottom of
|
|
1077
|
+
* the screen, so it lines up with the row it belongs to and the list it came
|
|
1078
|
+
* from stays readable behind it. It is also narrow: the panel it opens in is a
|
|
1079
|
+
* fraction of the screen, and a sheet covering that to offer four verbs costs
|
|
1080
|
+
* more than it says.
|
|
1081
|
+
*
|
|
1082
|
+
* Pressing the button does not press the row. A row that navigates would
|
|
1083
|
+
* otherwise navigate away from the thing the menu is about.
|
|
1084
|
+
*/
|
|
1085
|
+
function PanelsideItemActions({
|
|
1086
|
+
className,
|
|
1087
|
+
label = 'More options',
|
|
1088
|
+
icon,
|
|
1089
|
+
placement = 'bottom',
|
|
1090
|
+
align = 'end',
|
|
1091
|
+
minWidth = ACTIONS_MIN_WIDTH,
|
|
1092
|
+
contentProps,
|
|
1093
|
+
children
|
|
1094
|
+
}) {
|
|
1095
|
+
return /*#__PURE__*/_jsxs(Menu, {
|
|
1096
|
+
children: [/*#__PURE__*/_jsx(Menu.Trigger, {
|
|
1097
|
+
children: /*#__PURE__*/_jsx(PanelsideAction, {
|
|
1098
|
+
className: className,
|
|
1099
|
+
label: label,
|
|
1100
|
+
children: icon
|
|
1101
|
+
})
|
|
1102
|
+
}), /*#__PURE__*/_jsx(Menu.Content, {
|
|
1103
|
+
placement: placement,
|
|
1104
|
+
align: align,
|
|
1105
|
+
width: "content-fit",
|
|
1106
|
+
minWidth: minWidth,
|
|
1107
|
+
...contentProps,
|
|
1108
|
+
children: children
|
|
1109
|
+
})]
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
945
1112
|
function PanelsideFooter({
|
|
946
1113
|
className,
|
|
947
1114
|
floating = true,
|
|
@@ -1016,6 +1183,9 @@ const styles = StyleSheet.create({
|
|
|
1016
1183
|
bottom: 0,
|
|
1017
1184
|
left: 0,
|
|
1018
1185
|
right: 0
|
|
1186
|
+
},
|
|
1187
|
+
hidden: {
|
|
1188
|
+
display: 'none'
|
|
1019
1189
|
}
|
|
1020
1190
|
});
|
|
1021
1191
|
function PanelsideCta({
|
|
@@ -1048,7 +1218,7 @@ function PanelsideCta({
|
|
|
1048
1218
|
// The platform sizes a native button from its label, so a height means
|
|
1049
1219
|
// nothing here — the step is asked for as a control size instead.
|
|
1050
1220
|
,
|
|
1051
|
-
size: size === '
|
|
1221
|
+
size: size === 'default' ? 'md' : size,
|
|
1052
1222
|
variant: variant,
|
|
1053
1223
|
accessibilityLabel: label
|
|
1054
1224
|
// Pressable allows `null` for disabled; Button does not.
|
|
@@ -1210,6 +1380,92 @@ function PanelsideScene({
|
|
|
1210
1380
|
}) : null]
|
|
1211
1381
|
});
|
|
1212
1382
|
}
|
|
1383
|
+
/**
|
|
1384
|
+
* The pages the panel navigates between.
|
|
1385
|
+
*
|
|
1386
|
+
* Put it inside `Panelside.Scene` and give each page a `value` that a row's
|
|
1387
|
+
* `to` matches. Nothing is wired between the two: the row sets the panel's
|
|
1388
|
+
* route, and the page whose value equals it is the one shown.
|
|
1389
|
+
*
|
|
1390
|
+
* ```tsx
|
|
1391
|
+
* <Panelside.Scene>
|
|
1392
|
+
* <Panelside.Pages>
|
|
1393
|
+
* <Panelside.Page value="inbox"><Inbox /></Panelside.Page>
|
|
1394
|
+
* <Panelside.Page value="drafts"><Drafts /></Panelside.Page>
|
|
1395
|
+
* </Panelside.Pages>
|
|
1396
|
+
* </Panelside.Scene>
|
|
1397
|
+
* ```
|
|
1398
|
+
*
|
|
1399
|
+
* A page is mounted the first time it is visited and stays mounted after
|
|
1400
|
+
* that, hidden rather than removed. Going back to one is then a style change
|
|
1401
|
+
* rather than a mount: its list does not rebuild, its scroll position is where
|
|
1402
|
+
* you left it, and whatever it was fetching is already there. A page whose
|
|
1403
|
+
* contents go stale — or whose data is large enough that keeping it is worse
|
|
1404
|
+
* than fetching it again — takes `keepAlive={false}`.
|
|
1405
|
+
*/
|
|
1406
|
+
function PanelsidePages({
|
|
1407
|
+
className,
|
|
1408
|
+
children,
|
|
1409
|
+
...props
|
|
1410
|
+
}) {
|
|
1411
|
+
const {
|
|
1412
|
+
route
|
|
1413
|
+
} = usePanelsideContext('Panelside.Pages');
|
|
1414
|
+
|
|
1415
|
+
/*
|
|
1416
|
+
* Which pages have ever been the route.
|
|
1417
|
+
*
|
|
1418
|
+
* A `Set` in state rather than a ref: mounting a page for the first time has
|
|
1419
|
+
* to be a render, and the ref would not cause one. It only ever grows, and
|
|
1420
|
+
* only by one entry per page, so the identity change per first visit costs
|
|
1421
|
+
* nothing after the pages have all been seen once.
|
|
1422
|
+
*/
|
|
1423
|
+
const [visited, setVisited] = useState(() => [route]);
|
|
1424
|
+
useEffect(() => {
|
|
1425
|
+
setVisited(current => current.includes(route) ? current : [...current, route]);
|
|
1426
|
+
}, [route]);
|
|
1427
|
+
return /*#__PURE__*/_jsx(View, {
|
|
1428
|
+
className: cn('flex-1', className),
|
|
1429
|
+
...props,
|
|
1430
|
+
children: Children.map(children, child => {
|
|
1431
|
+
if (! /*#__PURE__*/isValidElement(child) || child.type !== PanelsidePage) {
|
|
1432
|
+
return child;
|
|
1433
|
+
}
|
|
1434
|
+
const {
|
|
1435
|
+
value,
|
|
1436
|
+
keepAlive = true
|
|
1437
|
+
} = child.props;
|
|
1438
|
+
const current = value === route;
|
|
1439
|
+
if (!current && (!keepAlive || !visited.includes(value))) return null;
|
|
1440
|
+
return /*#__PURE__*/cloneElement(child, {
|
|
1441
|
+
hidden: !current
|
|
1442
|
+
});
|
|
1443
|
+
})
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
/** One page. Only meaningful inside `Panelside.Pages`. */
|
|
1447
|
+
function PanelsidePage({
|
|
1448
|
+
className,
|
|
1449
|
+
value,
|
|
1450
|
+
keepAlive,
|
|
1451
|
+
hidden = false,
|
|
1452
|
+
children,
|
|
1453
|
+
style,
|
|
1454
|
+
...props
|
|
1455
|
+
}) {
|
|
1456
|
+
// `display: none` rather than unmounting, and rather than opacity: it takes
|
|
1457
|
+
// the page out of layout entirely, so a hidden page costs no measurement,
|
|
1458
|
+
// while its component tree — and everything it is holding — stays.
|
|
1459
|
+
return /*#__PURE__*/_jsx(View, {
|
|
1460
|
+
accessibilityElementsHidden: hidden,
|
|
1461
|
+
importantForAccessibility: hidden ? 'no-hide-descendants' : 'auto',
|
|
1462
|
+
pointerEvents: hidden ? 'none' : 'auto',
|
|
1463
|
+
style: [hidden ? styles.hidden : null, style],
|
|
1464
|
+
className: cn('flex-1', className),
|
|
1465
|
+
...props,
|
|
1466
|
+
children: textChildren(children)
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1213
1469
|
function PanelsideTrigger({
|
|
1214
1470
|
className,
|
|
1215
1471
|
label = 'Open navigation panel',
|
|
@@ -1245,7 +1501,8 @@ function PanelsideTrigger({
|
|
|
1245
1501
|
className: cn('h-10 w-10 items-center justify-center rounded-full', className),
|
|
1246
1502
|
accessibilityRole: "button",
|
|
1247
1503
|
accessibilityLabel: label,
|
|
1248
|
-
children: /*#__PURE__*/_jsx(
|
|
1504
|
+
children: /*#__PURE__*/_jsx(Glyph, {
|
|
1505
|
+
icon: Menu01Icon,
|
|
1249
1506
|
size: 20,
|
|
1250
1507
|
color: color
|
|
1251
1508
|
})
|
|
@@ -1283,6 +1540,7 @@ function PanelsideTrigger({
|
|
|
1283
1540
|
function PanelsideSearchTrigger({
|
|
1284
1541
|
className,
|
|
1285
1542
|
label = 'Search',
|
|
1543
|
+
variant = 'filled',
|
|
1286
1544
|
children,
|
|
1287
1545
|
native = false,
|
|
1288
1546
|
glass = false,
|
|
@@ -1298,7 +1556,8 @@ function PanelsideSearchTrigger({
|
|
|
1298
1556
|
onPress?.(event);
|
|
1299
1557
|
setSearchOpen(true);
|
|
1300
1558
|
}, [onPress, setSearchOpen]);
|
|
1301
|
-
const glyph = children ?? /*#__PURE__*/_jsx(
|
|
1559
|
+
const glyph = children ?? /*#__PURE__*/_jsx(Glyph, {
|
|
1560
|
+
icon: Search01Icon,
|
|
1302
1561
|
size: 18,
|
|
1303
1562
|
color: native ? color : undefined
|
|
1304
1563
|
});
|
|
@@ -1316,11 +1575,12 @@ function PanelsideSearchTrigger({
|
|
|
1316
1575
|
return /*#__PURE__*/_jsx(AnimatedPressable, {
|
|
1317
1576
|
...props,
|
|
1318
1577
|
onPress: open,
|
|
1319
|
-
className: cn(
|
|
1320
|
-
// Filled
|
|
1321
|
-
//
|
|
1322
|
-
//
|
|
1323
|
-
|
|
1578
|
+
className: cn('h-10 w-10 items-center justify-center rounded-full',
|
|
1579
|
+
// Filled by default. It sits on the panel's own surface with nothing
|
|
1580
|
+
// else on that row, so an outline at this size reads as an empty
|
|
1581
|
+
// circle before it reads as a control — which stops being true the
|
|
1582
|
+
// moment the rest of the screen's controls are outlined as well.
|
|
1583
|
+
variant === 'outline' ? 'border border-border' : 'bg-secondary', className),
|
|
1324
1584
|
accessibilityRole: "button",
|
|
1325
1585
|
accessibilityLabel: label,
|
|
1326
1586
|
children: /*#__PURE__*/_jsx(IconColorProvider, {
|
|
@@ -1377,6 +1637,9 @@ function PanelsideSearchSheet({
|
|
|
1377
1637
|
keyboardGap = 10,
|
|
1378
1638
|
showClose = true,
|
|
1379
1639
|
closeLabel = 'Close search',
|
|
1640
|
+
closeVariant = 'filled',
|
|
1641
|
+
closeNative = false,
|
|
1642
|
+
closeGlass = false,
|
|
1380
1643
|
children
|
|
1381
1644
|
}) {
|
|
1382
1645
|
const {
|
|
@@ -1394,7 +1657,20 @@ function PanelsideSearchSheet({
|
|
|
1394
1657
|
const open = openProp ?? searchOpen;
|
|
1395
1658
|
const query = value ?? uncontrolledQuery;
|
|
1396
1659
|
const activeTab = tab ?? uncontrolledTab;
|
|
1660
|
+
|
|
1661
|
+
/*
|
|
1662
|
+
* Closing takes the keyboard with it, wherever the close came from — the
|
|
1663
|
+
* button, a drag on the sheet, or the system back gesture.
|
|
1664
|
+
*
|
|
1665
|
+
* Leaving it to the field's own blur is too late. The field blurs when
|
|
1666
|
+
* `open` goes false, but the sheet is already on its way out by then: the
|
|
1667
|
+
* styled one unmounts its portal after the exit animation, and the platform
|
|
1668
|
+
* one keeps its content mounted and only stops presenting it. Either way the
|
|
1669
|
+
* blur lands on a field nothing can see, and the keyboard stays up until the
|
|
1670
|
+
* system notices for itself, some seconds later, that nothing is focused.
|
|
1671
|
+
*/
|
|
1397
1672
|
const setOpen = useCallback(next => {
|
|
1673
|
+
if (!next) Keyboard.dismiss();
|
|
1398
1674
|
if (openProp === undefined) setSearchOpen(next);
|
|
1399
1675
|
onOpenChange?.(next);
|
|
1400
1676
|
}, [onOpenChange, openProp, setSearchOpen]);
|
|
@@ -1461,12 +1737,25 @@ function PanelsideSearchSheet({
|
|
|
1461
1737
|
},
|
|
1462
1738
|
children: [showClose ? /*#__PURE__*/_jsx(View, {
|
|
1463
1739
|
className: "flex-row px-4 pb-1",
|
|
1464
|
-
children: /*#__PURE__*/_jsx(
|
|
1740
|
+
children: closeNative ? /*#__PURE__*/_jsx(Button, {
|
|
1741
|
+
native: true,
|
|
1742
|
+
glass: closeGlass,
|
|
1743
|
+
size: "icon",
|
|
1744
|
+
variant: "ghost",
|
|
1745
|
+
accessibilityLabel: closeLabel,
|
|
1746
|
+
onPress: close,
|
|
1747
|
+
children: /*#__PURE__*/_jsx(Glyph, {
|
|
1748
|
+
icon: Cancel01Icon,
|
|
1749
|
+
size: 17,
|
|
1750
|
+
color: glyph
|
|
1751
|
+
})
|
|
1752
|
+
}) : /*#__PURE__*/_jsx(AnimatedPressable, {
|
|
1465
1753
|
onPress: close,
|
|
1466
1754
|
accessibilityRole: "button",
|
|
1467
1755
|
accessibilityLabel: closeLabel,
|
|
1468
|
-
className:
|
|
1469
|
-
children: /*#__PURE__*/_jsx(
|
|
1756
|
+
className: cn('h-9 w-9 items-center justify-center rounded-full', closeVariant === 'outline' ? 'border border-border' : 'bg-secondary'),
|
|
1757
|
+
children: /*#__PURE__*/_jsx(Glyph, {
|
|
1758
|
+
icon: Cancel01Icon,
|
|
1470
1759
|
size: 17,
|
|
1471
1760
|
color: glyph
|
|
1472
1761
|
})
|
|
@@ -1489,6 +1778,8 @@ function PanelsideSearchSheet({
|
|
|
1489
1778
|
*/
|
|
1490
1779
|
function PanelsideSearchTabs({
|
|
1491
1780
|
className,
|
|
1781
|
+
align = 'center',
|
|
1782
|
+
listClassName,
|
|
1492
1783
|
children
|
|
1493
1784
|
}) {
|
|
1494
1785
|
const {
|
|
@@ -1502,6 +1793,7 @@ function PanelsideSearchTabs({
|
|
|
1502
1793
|
defaultValue: tab,
|
|
1503
1794
|
className: cn('px-4 pt-1', className),
|
|
1504
1795
|
children: /*#__PURE__*/_jsx(Tabs.List, {
|
|
1796
|
+
className: cn(align === 'center' && 'justify-center', listClassName),
|
|
1505
1797
|
children: children
|
|
1506
1798
|
})
|
|
1507
1799
|
});
|
|
@@ -1512,12 +1804,19 @@ function PanelsideSearchTab({
|
|
|
1512
1804
|
icon,
|
|
1513
1805
|
children
|
|
1514
1806
|
}) {
|
|
1515
|
-
return
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1807
|
+
return (
|
|
1808
|
+
/*#__PURE__*/
|
|
1809
|
+
// A step of padding above the tab row's own. This row is the only control
|
|
1810
|
+
// on a surface that is otherwise a list and a field, and a closed tab here
|
|
1811
|
+
// is a glyph with nothing else to be aimed at by — so it is sized to be
|
|
1812
|
+
// hit rather than to the density a row of labelled tabs can afford.
|
|
1813
|
+
_jsx(Tabs.Trigger, {
|
|
1814
|
+
value: value,
|
|
1815
|
+
icon: icon,
|
|
1816
|
+
className: cn('px-4 py-3', className),
|
|
1817
|
+
children: children
|
|
1818
|
+
})
|
|
1819
|
+
);
|
|
1521
1820
|
}
|
|
1522
1821
|
/**
|
|
1523
1822
|
* The scrolling middle of the search sheet.
|
|
@@ -1634,8 +1933,9 @@ function PanelsideSearchField({
|
|
|
1634
1933
|
bottomInset: bottomInset,
|
|
1635
1934
|
className: "w-full px-4 pb-1",
|
|
1636
1935
|
children: /*#__PURE__*/_jsxs(View, {
|
|
1637
|
-
className: cn('h-12 w-full flex-row items-center gap-2 rounded-full bg-
|
|
1638
|
-
children: [/*#__PURE__*/_jsx(
|
|
1936
|
+
className: cn('h-12 w-full flex-row items-center gap-2 rounded-full border border-border bg-surface px-4 shadow-lg', containerClassName),
|
|
1937
|
+
children: [/*#__PURE__*/_jsx(Glyph, {
|
|
1938
|
+
icon: Search01Icon,
|
|
1639
1939
|
size: 17,
|
|
1640
1940
|
color: muted
|
|
1641
1941
|
}), /*#__PURE__*/_jsx(TextInput, {
|
|
@@ -1658,7 +1958,8 @@ function PanelsideSearchField({
|
|
|
1658
1958
|
accessibilityRole: "button",
|
|
1659
1959
|
accessibilityLabel: "Clear search",
|
|
1660
1960
|
className: "h-5 w-5 items-center justify-center rounded-full bg-muted",
|
|
1661
|
-
children: /*#__PURE__*/_jsx(
|
|
1961
|
+
children: /*#__PURE__*/_jsx(Glyph, {
|
|
1962
|
+
icon: Cancel01Icon,
|
|
1662
1963
|
size: 12,
|
|
1663
1964
|
color: muted
|
|
1664
1965
|
})
|
|
@@ -1677,9 +1978,12 @@ PanelsideItemIcon.displayName = 'Panelside.ItemIcon';
|
|
|
1677
1978
|
PanelsideItemLabel.displayName = 'Panelside.ItemLabel';
|
|
1678
1979
|
PanelsideItemBadge.displayName = 'Panelside.ItemBadge';
|
|
1679
1980
|
PanelsideAction.displayName = 'Panelside.Action';
|
|
1981
|
+
PanelsideItemActions.displayName = 'Panelside.ItemActions';
|
|
1680
1982
|
PanelsideFooter.displayName = 'Panelside.Footer';
|
|
1681
1983
|
PanelsideCta.displayName = 'Panelside.Cta';
|
|
1682
1984
|
PanelsideScene.displayName = 'Panelside.Scene';
|
|
1985
|
+
PanelsidePages.displayName = 'Panelside.Pages';
|
|
1986
|
+
PanelsidePage.displayName = 'Panelside.Page';
|
|
1683
1987
|
PanelsideTrigger.displayName = 'Panelside.Trigger';
|
|
1684
1988
|
PanelsideSearchTrigger.displayName = 'Panelside.SearchTrigger';
|
|
1685
1989
|
PanelsideSearchSheet.displayName = 'Panelside.SearchSheet';
|
|
@@ -1700,9 +2004,12 @@ export const Panelside = Object.assign(PanelsideRoot, {
|
|
|
1700
2004
|
ItemLabel: PanelsideItemLabel,
|
|
1701
2005
|
ItemBadge: PanelsideItemBadge,
|
|
1702
2006
|
Action: PanelsideAction,
|
|
2007
|
+
ItemActions: PanelsideItemActions,
|
|
1703
2008
|
Footer: PanelsideFooter,
|
|
1704
2009
|
Cta: PanelsideCta,
|
|
1705
2010
|
Scene: PanelsideScene,
|
|
2011
|
+
Pages: PanelsidePages,
|
|
2012
|
+
Page: PanelsidePage,
|
|
1706
2013
|
Trigger: PanelsideTrigger,
|
|
1707
2014
|
SearchTrigger: PanelsideSearchTrigger,
|
|
1708
2015
|
SearchSheet: PanelsideSearchSheet,
|