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
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
* rather than travelling.
|
|
78
78
|
*/
|
|
79
79
|
import {
|
|
80
|
+
Children,
|
|
80
81
|
cloneElement,
|
|
81
82
|
createContext,
|
|
82
83
|
isValidElement,
|
|
@@ -90,6 +91,7 @@ import {
|
|
|
90
91
|
type ReactNode,
|
|
91
92
|
} from 'react';
|
|
92
93
|
import {
|
|
94
|
+
Keyboard,
|
|
93
95
|
Platform,
|
|
94
96
|
Pressable,
|
|
95
97
|
ScrollView,
|
|
@@ -117,7 +119,12 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
117
119
|
import { tv } from 'tailwind-variants';
|
|
118
120
|
import { useCSSVariable } from 'uniwind';
|
|
119
121
|
import { LinearGradient } from 'expo-linear-gradient';
|
|
120
|
-
import {
|
|
122
|
+
import { HugeiconsIcon, type IconSvgElement } from '@hugeicons/react-native';
|
|
123
|
+
import Cancel01Icon from '@hugeicons/core-free-icons/Cancel01Icon';
|
|
124
|
+
import Menu01Icon from '@hugeicons/core-free-icons/Menu01Icon';
|
|
125
|
+
import MoreHorizontalIcon from '@hugeicons/core-free-icons/MoreHorizontalIcon';
|
|
126
|
+
import Search01Icon from '@hugeicons/core-free-icons/Search01Icon';
|
|
127
|
+
import { IconColorProvider, useIconColor } from '../../icons';
|
|
121
128
|
import {
|
|
122
129
|
BottomSheet,
|
|
123
130
|
bottomSheetDetentHeight,
|
|
@@ -125,6 +132,7 @@ import {
|
|
|
125
132
|
type BottomSheetProps,
|
|
126
133
|
} from '../bottom-sheet';
|
|
127
134
|
import { Button } from '../button';
|
|
135
|
+
import { Menu, type MenuContentProps } from '../menu';
|
|
128
136
|
import { Tabs } from '../tabs';
|
|
129
137
|
import { getNativeUI } from '../../native';
|
|
130
138
|
import { AnimatedPressable } from '../../primitives/animated-pressable';
|
|
@@ -268,10 +276,64 @@ const clamp = (value: number, min: number, max: number) => {
|
|
|
268
276
|
return Math.min(Math.max(value, min), max);
|
|
269
277
|
};
|
|
270
278
|
|
|
279
|
+
/**
|
|
280
|
+
* The stroke weight the panel's glyphs are drawn at.
|
|
281
|
+
*
|
|
282
|
+
* A shade heavier than the icon set's own default. The panel sits over a
|
|
283
|
+
* dimmed screen and its rows are quiet by design, so a hairline glyph goes
|
|
284
|
+
* soft against them at the sizes used here.
|
|
285
|
+
*/
|
|
286
|
+
const GLYPH_STROKE = 1.8;
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* What a glyph is drawn in when nothing has said. The same neutral the rest of
|
|
290
|
+
* the chrome falls back to — visible against either theme, and never the thing
|
|
291
|
+
* that decides how a panel looks, since every surface here provides a colour.
|
|
292
|
+
*/
|
|
293
|
+
const GLYPH_FALLBACK = '#737373';
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Floor for a row's action menu.
|
|
297
|
+
*
|
|
298
|
+
* Wide enough for a verb and its glyph with the label doing the growing. Left
|
|
299
|
+
* to size itself, a menu takes its width from the one thing in each row that
|
|
300
|
+
* is not flexible — the glyph — and comes up as a column of icons.
|
|
301
|
+
*/
|
|
302
|
+
const ACTIONS_MIN_WIDTH = 220;
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* One of the panel's own glyphs.
|
|
306
|
+
*
|
|
307
|
+
* It exists to keep the colour contract the rest of the library has: an icon
|
|
308
|
+
* takes an explicit colour, then the one an enclosing surface is providing,
|
|
309
|
+
* and only then falls back. The drawing component underneath knows nothing
|
|
310
|
+
* about that inheritance and needs the resolved value handed to it.
|
|
311
|
+
*/
|
|
312
|
+
function Glyph({
|
|
313
|
+
icon,
|
|
314
|
+
size = 20,
|
|
315
|
+
color,
|
|
316
|
+
}: {
|
|
317
|
+
icon: IconSvgElement;
|
|
318
|
+
size?: number;
|
|
319
|
+
color?: string;
|
|
320
|
+
}) {
|
|
321
|
+
const inherited = useIconColor();
|
|
322
|
+
|
|
323
|
+
return (
|
|
324
|
+
<HugeiconsIcon
|
|
325
|
+
icon={icon}
|
|
326
|
+
size={size}
|
|
327
|
+
color={color ?? inherited ?? GLYPH_FALLBACK}
|
|
328
|
+
strokeWidth={GLYPH_STROKE}
|
|
329
|
+
/>
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
271
333
|
export type PanelsideMode = 'push' | 'overlay';
|
|
272
334
|
export type PanelsideSwipeFrom = 'anywhere' | 'edge';
|
|
273
335
|
export type PanelsideItemSize = 'default' | 'sm';
|
|
274
|
-
export type PanelsideCtaSize = 'default' | 'lg';
|
|
336
|
+
export type PanelsideCtaSize = 'default' | 'lg' | 'xl';
|
|
275
337
|
/**
|
|
276
338
|
* What a header or a footer paints behind itself.
|
|
277
339
|
*
|
|
@@ -281,6 +343,8 @@ export type PanelsideCtaSize = 'default' | 'lg';
|
|
|
281
343
|
* that is a row of the layout rather than something floating over one.
|
|
282
344
|
*/
|
|
283
345
|
export type PanelsideSurface = 'transparent' | 'fade' | 'solid';
|
|
346
|
+
/** How the panel's small round controls are drawn: a fill, or a ring. */
|
|
347
|
+
export type PanelsideControlVariant = 'filled' | 'outline';
|
|
284
348
|
|
|
285
349
|
const itemVariants = tv({
|
|
286
350
|
// No width: in a group it stretches on its own, and pinning it to full width
|
|
@@ -322,6 +386,7 @@ const ctaVariants = tv({
|
|
|
322
386
|
size: {
|
|
323
387
|
default: 'h-11 gap-2 px-6',
|
|
324
388
|
lg: 'h-13 gap-2.5 px-7',
|
|
389
|
+
xl: 'h-14 gap-3 px-8',
|
|
325
390
|
},
|
|
326
391
|
},
|
|
327
392
|
defaultVariants: {
|
|
@@ -356,6 +421,15 @@ interface PanelsideContextValue {
|
|
|
356
421
|
*/
|
|
357
422
|
searchOpen: boolean;
|
|
358
423
|
setSearchOpen: (open: boolean) => void;
|
|
424
|
+
/**
|
|
425
|
+
* The page the scene is showing.
|
|
426
|
+
*
|
|
427
|
+
* On the root for the same reason `searchOpen` is: the rows that navigate
|
|
428
|
+
* are in the panel and the pages are in the scene, which are different
|
|
429
|
+
* subtrees. Anything else is one `useState` every app threads through both.
|
|
430
|
+
*/
|
|
431
|
+
route: string;
|
|
432
|
+
navigate: (route: string) => void;
|
|
359
433
|
}
|
|
360
434
|
|
|
361
435
|
const PanelsideContext = createContext<PanelsideContextValue | null>(null);
|
|
@@ -383,6 +457,10 @@ export interface UsePanelsideResult {
|
|
|
383
457
|
/** Whether the search surface is up. `Panelside.SearchTrigger` sets it. */
|
|
384
458
|
searchOpen: boolean;
|
|
385
459
|
setSearchOpen: (open: boolean) => void;
|
|
460
|
+
/** The page the scene is showing — a `Panelside.Page`'s `value`. */
|
|
461
|
+
route: string;
|
|
462
|
+
/** Go to a page, closing the panel on the way. */
|
|
463
|
+
navigate: (route: string) => void;
|
|
386
464
|
}
|
|
387
465
|
|
|
388
466
|
/**
|
|
@@ -391,9 +469,20 @@ export interface UsePanelsideResult {
|
|
|
391
469
|
* lives.
|
|
392
470
|
*/
|
|
393
471
|
export function usePanelside(): UsePanelsideResult {
|
|
394
|
-
const { open, setOpen, toggle, progress, docked, searchOpen, setSearchOpen } =
|
|
472
|
+
const { open, setOpen, toggle, progress, docked, searchOpen, setSearchOpen, route, navigate } =
|
|
395
473
|
usePanelsideContext('usePanelside');
|
|
396
|
-
|
|
474
|
+
|
|
475
|
+
// `navigate` closes the panel. The root cannot do it there without making
|
|
476
|
+
// its own `setOpen` a dependency of the callback every row holds.
|
|
477
|
+
const go = useCallback(
|
|
478
|
+
(next: string) => {
|
|
479
|
+
navigate(next);
|
|
480
|
+
setOpen(false);
|
|
481
|
+
},
|
|
482
|
+
[navigate, setOpen]
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
return { open, setOpen, toggle, progress, docked, searchOpen, setSearchOpen, route, navigate: go };
|
|
397
486
|
}
|
|
398
487
|
|
|
399
488
|
/**
|
|
@@ -489,6 +578,19 @@ export interface PanelsideProps {
|
|
|
489
578
|
radius?: number;
|
|
490
579
|
/** How far the scene is dimmed at full open, 0 to 1. */
|
|
491
580
|
dim?: number;
|
|
581
|
+
/**
|
|
582
|
+
* Which page the scene is showing. Controlled; pair it with `onRouteChange`.
|
|
583
|
+
*
|
|
584
|
+
* A route is any string you choose. It is matched against
|
|
585
|
+
* `Panelside.Page`'s `value` and against `Panelside.Item`'s `to`, so a row
|
|
586
|
+
* marks itself as the current destination and the scene swaps to the page
|
|
587
|
+
* without either being wired to the other.
|
|
588
|
+
*/
|
|
589
|
+
route?: string;
|
|
590
|
+
/** Which page the scene starts on, when the panel is not controlling `route`. */
|
|
591
|
+
defaultRoute?: string;
|
|
592
|
+
/** Called with the route a row navigated to. */
|
|
593
|
+
onRouteChange?: (route: string) => void;
|
|
492
594
|
className?: string;
|
|
493
595
|
}
|
|
494
596
|
|
|
@@ -508,6 +610,9 @@ function PanelsideRoot({
|
|
|
508
610
|
scale,
|
|
509
611
|
radius,
|
|
510
612
|
dim,
|
|
613
|
+
route: controlledRoute,
|
|
614
|
+
defaultRoute = '',
|
|
615
|
+
onRouteChange,
|
|
511
616
|
className,
|
|
512
617
|
}: PanelsideProps) {
|
|
513
618
|
const { width: windowWidth } = useWindowDimensions();
|
|
@@ -518,6 +623,23 @@ function PanelsideRoot({
|
|
|
518
623
|
const open = controlled ? controlledOpen : uncontrolledOpen;
|
|
519
624
|
const [searchOpen, setSearchOpen] = useState(false);
|
|
520
625
|
|
|
626
|
+
const [uncontrolledRoute, setUncontrolledRoute] = useState(defaultRoute);
|
|
627
|
+
const route = controlledRoute ?? uncontrolledRoute;
|
|
628
|
+
|
|
629
|
+
/*
|
|
630
|
+
* Navigating closes the panel, and does so here rather than at each call
|
|
631
|
+
* site. The thing you just moved to would otherwise be behind the thing you
|
|
632
|
+
* moved from, which is the one arrangement no app wants — and a row that has
|
|
633
|
+
* to be told to close is a row every app writes the same three lines for.
|
|
634
|
+
*/
|
|
635
|
+
const navigate = useCallback(
|
|
636
|
+
(next: string) => {
|
|
637
|
+
if (controlledRoute === undefined) setUncontrolledRoute(next);
|
|
638
|
+
onRouteChange?.(next);
|
|
639
|
+
},
|
|
640
|
+
[controlledRoute, onRouteChange]
|
|
641
|
+
);
|
|
642
|
+
|
|
521
643
|
/*
|
|
522
644
|
* Measured rather than taken from the window, because Panelside does not
|
|
523
645
|
* have to be the whole screen — it can be one tab of a larger layout, and
|
|
@@ -695,15 +817,19 @@ function PanelsideRoot({
|
|
|
695
817
|
dim,
|
|
696
818
|
searchOpen,
|
|
697
819
|
setSearchOpen,
|
|
820
|
+
route,
|
|
821
|
+
navigate,
|
|
698
822
|
}),
|
|
699
823
|
[
|
|
700
824
|
dim,
|
|
701
825
|
dismissible,
|
|
702
826
|
docked,
|
|
703
827
|
mode,
|
|
828
|
+
navigate,
|
|
704
829
|
open,
|
|
705
830
|
progress,
|
|
706
831
|
radius,
|
|
832
|
+
route,
|
|
707
833
|
scale,
|
|
708
834
|
searchOpen,
|
|
709
835
|
setOpen,
|
|
@@ -908,7 +1034,7 @@ function PanelsideSearch({
|
|
|
908
1034
|
containerClassName
|
|
909
1035
|
)}
|
|
910
1036
|
>
|
|
911
|
-
<
|
|
1037
|
+
<Glyph icon={Search01Icon} size={16} color={muted} />
|
|
912
1038
|
<TextInput
|
|
913
1039
|
placeholder={placeholder}
|
|
914
1040
|
placeholderTextColor={muted}
|
|
@@ -1032,7 +1158,20 @@ export interface PanelsideItemProps extends Omit<PressableProps, 'children'> {
|
|
|
1032
1158
|
* shorthand for `Panelside.ItemLabel`.
|
|
1033
1159
|
*/
|
|
1034
1160
|
label?: string;
|
|
1035
|
-
/**
|
|
1161
|
+
/**
|
|
1162
|
+
* The page this row goes to — a `Panelside.Page`'s `value`.
|
|
1163
|
+
*
|
|
1164
|
+
* Pressing it sets the panel's route, and the row marks itself active while
|
|
1165
|
+
* that route is the current one. It also closes the panel, since the thing
|
|
1166
|
+
* you just moved to would otherwise be behind the thing you moved from.
|
|
1167
|
+
*
|
|
1168
|
+
* `active` and `onPress` still win where they are passed, so a row can
|
|
1169
|
+
* navigate and do something else as well.
|
|
1170
|
+
*/
|
|
1171
|
+
to?: string;
|
|
1172
|
+
/** Leave the panel open after navigating. Off by default. */
|
|
1173
|
+
closeOnNavigate?: boolean;
|
|
1174
|
+
/** Marks the row as the current destination. Derived from `to` when given. */
|
|
1036
1175
|
active?: boolean;
|
|
1037
1176
|
/**
|
|
1038
1177
|
* Trailing count or status. A number or string renders as a pill; anything
|
|
@@ -1081,16 +1220,34 @@ function PanelsideItem({
|
|
|
1081
1220
|
className,
|
|
1082
1221
|
icon,
|
|
1083
1222
|
label,
|
|
1084
|
-
|
|
1223
|
+
to,
|
|
1224
|
+
closeOnNavigate = true,
|
|
1225
|
+
active: activeProp,
|
|
1085
1226
|
badge,
|
|
1086
1227
|
disabled = false,
|
|
1087
1228
|
size = 'default',
|
|
1229
|
+
onPress,
|
|
1088
1230
|
children,
|
|
1089
1231
|
...props
|
|
1090
1232
|
}: PanelsideItemProps) {
|
|
1233
|
+
const panel = useContext(PanelsideContext);
|
|
1091
1234
|
const restTint = useCSSVariable('--color-muted-foreground');
|
|
1092
1235
|
const activeTint = useCSSVariable('--color-foreground');
|
|
1093
1236
|
|
|
1237
|
+
// A row outside a `<Panelside>` still works — the parts are usable on their
|
|
1238
|
+
// own — it simply has no route to match against.
|
|
1239
|
+
const active = activeProp ?? (to !== undefined && panel?.route === to);
|
|
1240
|
+
|
|
1241
|
+
const press = useCallback(
|
|
1242
|
+
(event: Parameters<NonNullable<PressableProps['onPress']>>[0]) => {
|
|
1243
|
+
onPress?.(event);
|
|
1244
|
+
if (to === undefined || !panel) return;
|
|
1245
|
+
panel.navigate(to);
|
|
1246
|
+
if (closeOnNavigate) panel.setOpen(false);
|
|
1247
|
+
},
|
|
1248
|
+
[closeOnNavigate, onPress, panel, to]
|
|
1249
|
+
);
|
|
1250
|
+
|
|
1094
1251
|
const tint = active
|
|
1095
1252
|
? typeof activeTint === 'string'
|
|
1096
1253
|
? activeTint
|
|
@@ -1110,8 +1267,16 @@ function PanelsideItem({
|
|
|
1110
1267
|
* that, and a written-out `Panelside.ItemLabel` supplies it too — so the
|
|
1111
1268
|
* spacer is only for the row that has neither, which is a row of nothing but
|
|
1112
1269
|
* an icon and a badge.
|
|
1270
|
+
*
|
|
1271
|
+
* And only where there is something to push. A row of nothing but an icon
|
|
1272
|
+
* has no trailing content, and a spacer in it is not inert: laid out against
|
|
1273
|
+
* the width available rather than against the row's own contents, it expands,
|
|
1274
|
+
* takes the space with it, and squeezes whatever shares the line — which is
|
|
1275
|
+
* how an icon-only row in a footer beside a compose button cropped that
|
|
1276
|
+
* button's label and left its own icon short of the trailing edge.
|
|
1113
1277
|
*/
|
|
1114
1278
|
const filled = label !== undefined || children !== undefined;
|
|
1279
|
+
const trailing = badge !== undefined && badge !== null;
|
|
1115
1280
|
|
|
1116
1281
|
return (
|
|
1117
1282
|
<PanelsideItemContext.Provider value={context}>
|
|
@@ -1122,13 +1287,14 @@ function PanelsideItem({
|
|
|
1122
1287
|
accessibilityState={{ selected: active, disabled }}
|
|
1123
1288
|
accessibilityLabel={label}
|
|
1124
1289
|
pressScale={0.985}
|
|
1290
|
+
onPress={to !== undefined || onPress ? press : undefined}
|
|
1125
1291
|
{...props}
|
|
1126
1292
|
>
|
|
1127
1293
|
{icon ? <PanelsideItemIcon>{icon}</PanelsideItemIcon> : null}
|
|
1128
1294
|
|
|
1129
1295
|
{label !== undefined ? <PanelsideItemLabel>{label}</PanelsideItemLabel> : null}
|
|
1130
1296
|
|
|
1131
|
-
{filled ? null : <View className="flex-1" />}
|
|
1297
|
+
{filled || !trailing ? null : <View className="flex-1" />}
|
|
1132
1298
|
|
|
1133
1299
|
{badge !== undefined && badge !== null ? (
|
|
1134
1300
|
<PanelsideItemBadge>{badge}</PanelsideItemBadge>
|
|
@@ -1233,6 +1399,7 @@ function PanelsideAction({
|
|
|
1233
1399
|
className,
|
|
1234
1400
|
label = 'More options',
|
|
1235
1401
|
children,
|
|
1402
|
+
onPress,
|
|
1236
1403
|
...props
|
|
1237
1404
|
}: PanelsideActionProps) {
|
|
1238
1405
|
const tint = useCSSVariable('--color-muted-foreground');
|
|
@@ -1246,13 +1413,97 @@ function PanelsideAction({
|
|
|
1246
1413
|
// The glyph is small and sits next to a row-sized target, so it takes
|
|
1247
1414
|
// the difference back as slop rather than as layout.
|
|
1248
1415
|
hitSlop={8}
|
|
1416
|
+
// The row underneath is pressable and usually navigates. A press that
|
|
1417
|
+
// reached both would open the menu and leave the thing it is about.
|
|
1418
|
+
onPress={(event) => {
|
|
1419
|
+
event.stopPropagation();
|
|
1420
|
+
onPress?.(event);
|
|
1421
|
+
}}
|
|
1249
1422
|
{...props}
|
|
1250
1423
|
>
|
|
1251
|
-
{children ?? <
|
|
1424
|
+
{children ?? <Glyph icon={MoreHorizontalIcon} size={18} color={color} />}
|
|
1252
1425
|
</AnimatedPressable>
|
|
1253
1426
|
);
|
|
1254
1427
|
}
|
|
1255
1428
|
|
|
1429
|
+
export interface PanelsideItemActionsProps {
|
|
1430
|
+
className?: string;
|
|
1431
|
+
/**
|
|
1432
|
+
* What a screen reader announces for the button. The control is an
|
|
1433
|
+
* unlabelled glyph, so this is the only description it has.
|
|
1434
|
+
*/
|
|
1435
|
+
label?: string;
|
|
1436
|
+
/** Replaces the default overflow glyph. */
|
|
1437
|
+
icon?: ReactNode;
|
|
1438
|
+
/** Where the panel opens relative to the button. Defaults to below it. */
|
|
1439
|
+
placement?: MenuContentProps['placement'];
|
|
1440
|
+
/** How it lines up on that edge. Defaults to the button's trailing edge. */
|
|
1441
|
+
align?: MenuContentProps['align'];
|
|
1442
|
+
/**
|
|
1443
|
+
* Floor for the menu's width. A panel sized to its contents takes its width
|
|
1444
|
+
* from whatever inside it is not flexible — in a row of a flexible label and
|
|
1445
|
+
* a fixed glyph, that is the glyph, and the menu comes up as a column of
|
|
1446
|
+
* icons with the words squeezed out of it.
|
|
1447
|
+
*/
|
|
1448
|
+
minWidth?: number;
|
|
1449
|
+
/** Passed through to the panel — `width`, `maxHeight`, `offset` and the rest. */
|
|
1450
|
+
contentProps?: Omit<MenuContentProps, 'children' | 'placement' | 'align' | 'minWidth'>;
|
|
1451
|
+
/** The rows: `Menu.Item`, `Menu.Separator`, `Menu.Label`. */
|
|
1452
|
+
children?: ReactNode;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
/**
|
|
1456
|
+
* A row's actions, behind an overflow button at the end of it.
|
|
1457
|
+
*
|
|
1458
|
+
* ```tsx
|
|
1459
|
+
* <Panelside.Item label={chat.title} to={chat.id}>
|
|
1460
|
+
* <Panelside.ItemActions>
|
|
1461
|
+
* <Menu.Item onSelect={rename}>Rename</Menu.Item>
|
|
1462
|
+
* <Menu.Separator />
|
|
1463
|
+
* <Menu.Item variant="destructive" onSelect={remove}>Delete</Menu.Item>
|
|
1464
|
+
* </Panelside.ItemActions>
|
|
1465
|
+
* </Panelside.Item>
|
|
1466
|
+
* ```
|
|
1467
|
+
*
|
|
1468
|
+
* The panel is anchored to the button rather than presented from the bottom of
|
|
1469
|
+
* the screen, so it lines up with the row it belongs to and the list it came
|
|
1470
|
+
* from stays readable behind it. It is also narrow: the panel it opens in is a
|
|
1471
|
+
* fraction of the screen, and a sheet covering that to offer four verbs costs
|
|
1472
|
+
* more than it says.
|
|
1473
|
+
*
|
|
1474
|
+
* Pressing the button does not press the row. A row that navigates would
|
|
1475
|
+
* otherwise navigate away from the thing the menu is about.
|
|
1476
|
+
*/
|
|
1477
|
+
function PanelsideItemActions({
|
|
1478
|
+
className,
|
|
1479
|
+
label = 'More options',
|
|
1480
|
+
icon,
|
|
1481
|
+
placement = 'bottom',
|
|
1482
|
+
align = 'end',
|
|
1483
|
+
minWidth = ACTIONS_MIN_WIDTH,
|
|
1484
|
+
contentProps,
|
|
1485
|
+
children,
|
|
1486
|
+
}: PanelsideItemActionsProps) {
|
|
1487
|
+
return (
|
|
1488
|
+
<Menu>
|
|
1489
|
+
<Menu.Trigger>
|
|
1490
|
+
<PanelsideAction className={className} label={label}>
|
|
1491
|
+
{icon}
|
|
1492
|
+
</PanelsideAction>
|
|
1493
|
+
</Menu.Trigger>
|
|
1494
|
+
<Menu.Content
|
|
1495
|
+
placement={placement}
|
|
1496
|
+
align={align}
|
|
1497
|
+
width="content-fit"
|
|
1498
|
+
minWidth={minWidth}
|
|
1499
|
+
{...contentProps}
|
|
1500
|
+
>
|
|
1501
|
+
{children}
|
|
1502
|
+
</Menu.Content>
|
|
1503
|
+
</Menu>
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1256
1507
|
export interface PanelsideFooterProps extends ViewProps {
|
|
1257
1508
|
className?: string;
|
|
1258
1509
|
/**
|
|
@@ -1364,6 +1615,7 @@ function PanelsideFooter({
|
|
|
1364
1615
|
const styles = StyleSheet.create({
|
|
1365
1616
|
fade: { position: 'absolute', top: 0, left: 0, right: 0 },
|
|
1366
1617
|
rise: { position: 'absolute', bottom: 0, left: 0, right: 0 },
|
|
1618
|
+
hidden: { display: 'none' },
|
|
1367
1619
|
});
|
|
1368
1620
|
|
|
1369
1621
|
export interface PanelsideCtaProps extends Omit<PressableProps, 'children'> {
|
|
@@ -1378,10 +1630,11 @@ export interface PanelsideCtaProps extends Omit<PressableProps, 'children'> {
|
|
|
1378
1630
|
* How tall the pill is. `default` is 44pt — a step above the account button
|
|
1379
1631
|
* beside it, so the footer reads as one primary control and one secondary
|
|
1380
1632
|
* one. `lg` is 52pt, for a panel where the call to action is the only thing
|
|
1381
|
-
* in the row.
|
|
1633
|
+
* in the row, and `xl` is 56pt.
|
|
1382
1634
|
*
|
|
1383
1635
|
* Ignored under `native` — the platform sizes its own button, and asks for a
|
|
1384
|
-
* control size rather than a height.
|
|
1636
|
+
* control size rather than a height. The three steps reach the platform's
|
|
1637
|
+
* regular, large and extra-large controls.
|
|
1385
1638
|
*/
|
|
1386
1639
|
size?: PanelsideCtaSize;
|
|
1387
1640
|
/**
|
|
@@ -1430,7 +1683,7 @@ function PanelsideCta({
|
|
|
1430
1683
|
glass={glass}
|
|
1431
1684
|
// The platform sizes a native button from its label, so a height means
|
|
1432
1685
|
// nothing here — the step is asked for as a control size instead.
|
|
1433
|
-
size={size === '
|
|
1686
|
+
size={size === 'default' ? 'md' : size}
|
|
1434
1687
|
variant={variant}
|
|
1435
1688
|
accessibilityLabel={label}
|
|
1436
1689
|
// Pressable allows `null` for disabled; Button does not.
|
|
@@ -1448,7 +1701,7 @@ function PanelsideCta({
|
|
|
1448
1701
|
React view in it to pad, and hosting one to get a view leaves a width
|
|
1449
1702
|
nothing knows in advance.
|
|
1450
1703
|
|
|
1451
|
-
So this one is sized rather than padded —
|
|
1704
|
+
So this one is sized rather than padded — the size reaches the
|
|
1452
1705
|
platform as a control size, which scales the room the style leaves
|
|
1453
1706
|
around the label and the label with it.
|
|
1454
1707
|
*/}
|
|
@@ -1661,6 +1914,115 @@ function PanelsideScene({
|
|
|
1661
1914
|
);
|
|
1662
1915
|
}
|
|
1663
1916
|
|
|
1917
|
+
export interface PanelsidePagesProps extends ViewProps {
|
|
1918
|
+
className?: string;
|
|
1919
|
+
/** `Panelside.Page` elements. Anything else is rendered as given. */
|
|
1920
|
+
children?: ReactNode;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
/**
|
|
1924
|
+
* The pages the panel navigates between.
|
|
1925
|
+
*
|
|
1926
|
+
* Put it inside `Panelside.Scene` and give each page a `value` that a row's
|
|
1927
|
+
* `to` matches. Nothing is wired between the two: the row sets the panel's
|
|
1928
|
+
* route, and the page whose value equals it is the one shown.
|
|
1929
|
+
*
|
|
1930
|
+
* ```tsx
|
|
1931
|
+
* <Panelside.Scene>
|
|
1932
|
+
* <Panelside.Pages>
|
|
1933
|
+
* <Panelside.Page value="inbox"><Inbox /></Panelside.Page>
|
|
1934
|
+
* <Panelside.Page value="drafts"><Drafts /></Panelside.Page>
|
|
1935
|
+
* </Panelside.Pages>
|
|
1936
|
+
* </Panelside.Scene>
|
|
1937
|
+
* ```
|
|
1938
|
+
*
|
|
1939
|
+
* A page is mounted the first time it is visited and stays mounted after
|
|
1940
|
+
* that, hidden rather than removed. Going back to one is then a style change
|
|
1941
|
+
* rather than a mount: its list does not rebuild, its scroll position is where
|
|
1942
|
+
* you left it, and whatever it was fetching is already there. A page whose
|
|
1943
|
+
* contents go stale — or whose data is large enough that keeping it is worse
|
|
1944
|
+
* than fetching it again — takes `keepAlive={false}`.
|
|
1945
|
+
*/
|
|
1946
|
+
function PanelsidePages({ className, children, ...props }: PanelsidePagesProps) {
|
|
1947
|
+
const { route } = usePanelsideContext('Panelside.Pages');
|
|
1948
|
+
|
|
1949
|
+
/*
|
|
1950
|
+
* Which pages have ever been the route.
|
|
1951
|
+
*
|
|
1952
|
+
* A `Set` in state rather than a ref: mounting a page for the first time has
|
|
1953
|
+
* to be a render, and the ref would not cause one. It only ever grows, and
|
|
1954
|
+
* only by one entry per page, so the identity change per first visit costs
|
|
1955
|
+
* nothing after the pages have all been seen once.
|
|
1956
|
+
*/
|
|
1957
|
+
const [visited, setVisited] = useState<readonly string[]>(() => [route]);
|
|
1958
|
+
|
|
1959
|
+
useEffect(() => {
|
|
1960
|
+
setVisited((current) => (current.includes(route) ? current : [...current, route]));
|
|
1961
|
+
}, [route]);
|
|
1962
|
+
|
|
1963
|
+
return (
|
|
1964
|
+
<View className={cn('flex-1', className)} {...props}>
|
|
1965
|
+
{Children.map(children, (child) => {
|
|
1966
|
+
if (!isValidElement<PanelsidePageProps>(child) || child.type !== PanelsidePage) {
|
|
1967
|
+
return child;
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
const { value, keepAlive = true } = child.props;
|
|
1971
|
+
const current = value === route;
|
|
1972
|
+
if (!current && (!keepAlive || !visited.includes(value))) return null;
|
|
1973
|
+
|
|
1974
|
+
return cloneElement(child, { hidden: !current });
|
|
1975
|
+
})}
|
|
1976
|
+
</View>
|
|
1977
|
+
);
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
export interface PanelsidePageProps extends ViewProps {
|
|
1981
|
+
className?: string;
|
|
1982
|
+
/** What a row's `to` has to equal for this page to be the one shown. */
|
|
1983
|
+
value: string;
|
|
1984
|
+
/**
|
|
1985
|
+
* Keep the page mounted once it has been visited. Default true, which is
|
|
1986
|
+
* what makes going back to it instant. Off, it is torn down on the way out
|
|
1987
|
+
* and rebuilt on the way in.
|
|
1988
|
+
*/
|
|
1989
|
+
keepAlive?: boolean;
|
|
1990
|
+
/**
|
|
1991
|
+
* Set by `Panelside.Pages`. A hidden page is laid out by nobody, is not in
|
|
1992
|
+
* the accessibility tree, and takes no touches — but it is still mounted,
|
|
1993
|
+
* which is the whole point of it.
|
|
1994
|
+
*/
|
|
1995
|
+
hidden?: boolean;
|
|
1996
|
+
children?: ReactNode;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
/** One page. Only meaningful inside `Panelside.Pages`. */
|
|
2000
|
+
function PanelsidePage({
|
|
2001
|
+
className,
|
|
2002
|
+
value,
|
|
2003
|
+
keepAlive,
|
|
2004
|
+
hidden = false,
|
|
2005
|
+
children,
|
|
2006
|
+
style,
|
|
2007
|
+
...props
|
|
2008
|
+
}: PanelsidePageProps) {
|
|
2009
|
+
// `display: none` rather than unmounting, and rather than opacity: it takes
|
|
2010
|
+
// the page out of layout entirely, so a hidden page costs no measurement,
|
|
2011
|
+
// while its component tree — and everything it is holding — stays.
|
|
2012
|
+
return (
|
|
2013
|
+
<View
|
|
2014
|
+
accessibilityElementsHidden={hidden}
|
|
2015
|
+
importantForAccessibility={hidden ? 'no-hide-descendants' : 'auto'}
|
|
2016
|
+
pointerEvents={hidden ? 'none' : 'auto'}
|
|
2017
|
+
style={[hidden ? styles.hidden : null, style]}
|
|
2018
|
+
className={cn('flex-1', className)}
|
|
2019
|
+
{...props}
|
|
2020
|
+
>
|
|
2021
|
+
{textChildren(children)}
|
|
2022
|
+
</View>
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
2025
|
+
|
|
1664
2026
|
export interface PanelsideTriggerProps extends Omit<PressableProps, 'children'> {
|
|
1665
2027
|
className?: string;
|
|
1666
2028
|
/** What a screen reader announces. */
|
|
@@ -1708,7 +2070,7 @@ function PanelsideTrigger({
|
|
|
1708
2070
|
accessibilityRole="button"
|
|
1709
2071
|
accessibilityLabel={label}
|
|
1710
2072
|
>
|
|
1711
|
-
<
|
|
2073
|
+
<Glyph icon={Menu01Icon} size={20} color={color} />
|
|
1712
2074
|
</AnimatedPressable>
|
|
1713
2075
|
);
|
|
1714
2076
|
}
|
|
@@ -1738,6 +2100,17 @@ export interface PanelsideSearchTriggerProps extends Omit<PressableProps, 'child
|
|
|
1738
2100
|
className?: string;
|
|
1739
2101
|
/** What a screen reader announces. */
|
|
1740
2102
|
label?: string;
|
|
2103
|
+
/**
|
|
2104
|
+
* `filled` is the default: a circle in the secondary surface, which is what
|
|
2105
|
+
* a control sitting alone on the panel's own surface needs to read as one.
|
|
2106
|
+
*
|
|
2107
|
+
* `outline` is a ring and no fill, for a panel whose other controls are
|
|
2108
|
+
* outlined too — a filled circle among them is the only thing on the screen
|
|
2109
|
+
* claiming to be a second primary.
|
|
2110
|
+
*
|
|
2111
|
+
* Ignored under `native`, where the platform owns the button's chrome.
|
|
2112
|
+
*/
|
|
2113
|
+
variant?: PanelsideControlVariant;
|
|
1741
2114
|
/** Replaces the default magnifier. */
|
|
1742
2115
|
children?: ReactNode;
|
|
1743
2116
|
/**
|
|
@@ -1762,6 +2135,7 @@ export interface PanelsideSearchTriggerProps extends Omit<PressableProps, 'child
|
|
|
1762
2135
|
function PanelsideSearchTrigger({
|
|
1763
2136
|
className,
|
|
1764
2137
|
label = 'Search',
|
|
2138
|
+
variant = 'filled',
|
|
1765
2139
|
children,
|
|
1766
2140
|
native = false,
|
|
1767
2141
|
glass = false,
|
|
@@ -1780,7 +2154,7 @@ function PanelsideSearchTrigger({
|
|
|
1780
2154
|
[onPress, setSearchOpen]
|
|
1781
2155
|
);
|
|
1782
2156
|
|
|
1783
|
-
const glyph = children ?? <
|
|
2157
|
+
const glyph = children ?? <Glyph icon={Search01Icon} size={18} color={native ? color : undefined} />;
|
|
1784
2158
|
|
|
1785
2159
|
if (native) {
|
|
1786
2160
|
return (
|
|
@@ -1802,10 +2176,12 @@ function PanelsideSearchTrigger({
|
|
|
1802
2176
|
{...props}
|
|
1803
2177
|
onPress={open}
|
|
1804
2178
|
className={cn(
|
|
1805
|
-
|
|
1806
|
-
//
|
|
1807
|
-
//
|
|
1808
|
-
|
|
2179
|
+
'h-10 w-10 items-center justify-center rounded-full',
|
|
2180
|
+
// Filled by default. It sits on the panel's own surface with nothing
|
|
2181
|
+
// else on that row, so an outline at this size reads as an empty
|
|
2182
|
+
// circle before it reads as a control — which stops being true the
|
|
2183
|
+
// moment the rest of the screen's controls are outlined as well.
|
|
2184
|
+
variant === 'outline' ? 'border border-border' : 'bg-secondary',
|
|
1809
2185
|
className
|
|
1810
2186
|
)}
|
|
1811
2187
|
accessibilityRole="button"
|
|
@@ -1885,6 +2261,22 @@ export interface PanelsideSearchSheetProps {
|
|
|
1885
2261
|
showClose?: boolean;
|
|
1886
2262
|
/** What a screen reader announces for that button. */
|
|
1887
2263
|
closeLabel?: string;
|
|
2264
|
+
/**
|
|
2265
|
+
* How that button is drawn. Matches `Panelside.SearchTrigger`'s `variant`,
|
|
2266
|
+
* so the control that opens the surface and the one that closes it are the
|
|
2267
|
+
* same shape.
|
|
2268
|
+
*/
|
|
2269
|
+
closeVariant?: PanelsideControlVariant;
|
|
2270
|
+
/**
|
|
2271
|
+
* Draw that button as the platform's own, the way `Panelside.SearchTrigger`
|
|
2272
|
+
* does. Independent of `native` on the sheet itself: the surface can be ours
|
|
2273
|
+
* while the two controls on either end of it are the platform's, which is
|
|
2274
|
+
* the arrangement a native screen wants when the sheet's own presentation
|
|
2275
|
+
* cannot hold this content.
|
|
2276
|
+
*/
|
|
2277
|
+
closeNative?: boolean;
|
|
2278
|
+
/** Draw that native button in the platform's Liquid Glass material. */
|
|
2279
|
+
closeGlass?: boolean;
|
|
1888
2280
|
children?: ReactNode;
|
|
1889
2281
|
}
|
|
1890
2282
|
|
|
@@ -1930,6 +2322,9 @@ function PanelsideSearchSheet({
|
|
|
1930
2322
|
keyboardGap = 10,
|
|
1931
2323
|
showClose = true,
|
|
1932
2324
|
closeLabel = 'Close search',
|
|
2325
|
+
closeVariant = 'filled',
|
|
2326
|
+
closeNative = false,
|
|
2327
|
+
closeGlass = false,
|
|
1933
2328
|
children,
|
|
1934
2329
|
}: PanelsideSearchSheetProps) {
|
|
1935
2330
|
const { searchOpen, setSearchOpen } = usePanelsideContext('Panelside.SearchSheet');
|
|
@@ -1945,8 +2340,20 @@ function PanelsideSearchSheet({
|
|
|
1945
2340
|
const query = value ?? uncontrolledQuery;
|
|
1946
2341
|
const activeTab = tab ?? uncontrolledTab;
|
|
1947
2342
|
|
|
2343
|
+
/*
|
|
2344
|
+
* Closing takes the keyboard with it, wherever the close came from — the
|
|
2345
|
+
* button, a drag on the sheet, or the system back gesture.
|
|
2346
|
+
*
|
|
2347
|
+
* Leaving it to the field's own blur is too late. The field blurs when
|
|
2348
|
+
* `open` goes false, but the sheet is already on its way out by then: the
|
|
2349
|
+
* styled one unmounts its portal after the exit animation, and the platform
|
|
2350
|
+
* one keeps its content mounted and only stops presenting it. Either way the
|
|
2351
|
+
* blur lands on a field nothing can see, and the keyboard stays up until the
|
|
2352
|
+
* system notices for itself, some seconds later, that nothing is focused.
|
|
2353
|
+
*/
|
|
1948
2354
|
const setOpen = useCallback(
|
|
1949
2355
|
(next: boolean) => {
|
|
2356
|
+
if (!next) Keyboard.dismiss();
|
|
1950
2357
|
if (openProp === undefined) setSearchOpen(next);
|
|
1951
2358
|
onOpenChange?.(next);
|
|
1952
2359
|
},
|
|
@@ -2039,14 +2446,30 @@ function PanelsideSearchSheet({
|
|
|
2039
2446
|
>
|
|
2040
2447
|
{showClose ? (
|
|
2041
2448
|
<View className="flex-row px-4 pb-1">
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2449
|
+
{closeNative ? (
|
|
2450
|
+
<Button
|
|
2451
|
+
native
|
|
2452
|
+
glass={closeGlass}
|
|
2453
|
+
size="icon"
|
|
2454
|
+
variant="ghost"
|
|
2455
|
+
accessibilityLabel={closeLabel}
|
|
2456
|
+
onPress={close}
|
|
2457
|
+
>
|
|
2458
|
+
<Glyph icon={Cancel01Icon} size={17} color={glyph} />
|
|
2459
|
+
</Button>
|
|
2460
|
+
) : (
|
|
2461
|
+
<AnimatedPressable
|
|
2462
|
+
onPress={close}
|
|
2463
|
+
accessibilityRole="button"
|
|
2464
|
+
accessibilityLabel={closeLabel}
|
|
2465
|
+
className={cn(
|
|
2466
|
+
'h-9 w-9 items-center justify-center rounded-full',
|
|
2467
|
+
closeVariant === 'outline' ? 'border border-border' : 'bg-secondary'
|
|
2468
|
+
)}
|
|
2469
|
+
>
|
|
2470
|
+
<Glyph icon={Cancel01Icon} size={17} color={glyph} />
|
|
2471
|
+
</AnimatedPressable>
|
|
2472
|
+
)}
|
|
2050
2473
|
</View>
|
|
2051
2474
|
) : null}
|
|
2052
2475
|
{children}
|
|
@@ -2059,6 +2482,20 @@ function PanelsideSearchSheet({
|
|
|
2059
2482
|
|
|
2060
2483
|
export interface PanelsideSearchTabsProps {
|
|
2061
2484
|
className?: string;
|
|
2485
|
+
/**
|
|
2486
|
+
* Where the row sits across the sheet. Default `center`.
|
|
2487
|
+
*
|
|
2488
|
+
* Centred because only the selected tab is open and the rest are their
|
|
2489
|
+
* icons, so the row's width changes as you move through it — anchored to the
|
|
2490
|
+
* leading edge that change reads as the row growing and shrinking, and
|
|
2491
|
+
* centred it reads as the selection moving.
|
|
2492
|
+
*
|
|
2493
|
+
* `start` anchors it to the leading edge, for a sheet whose tabs are wide
|
|
2494
|
+
* enough to fill the row anyway.
|
|
2495
|
+
*/
|
|
2496
|
+
align?: 'start' | 'center';
|
|
2497
|
+
/** Applied to the row itself rather than to the box around it. */
|
|
2498
|
+
listClassName?: string;
|
|
2062
2499
|
children?: ReactNode;
|
|
2063
2500
|
}
|
|
2064
2501
|
|
|
@@ -2072,7 +2509,12 @@ export interface PanelsideSearchTabsProps {
|
|
|
2072
2509
|
* Every `Panelside.SearchTab` therefore needs an `icon` — a closed tab has
|
|
2073
2510
|
* nothing else to be.
|
|
2074
2511
|
*/
|
|
2075
|
-
function PanelsideSearchTabs({
|
|
2512
|
+
function PanelsideSearchTabs({
|
|
2513
|
+
className,
|
|
2514
|
+
align = 'center',
|
|
2515
|
+
listClassName,
|
|
2516
|
+
children,
|
|
2517
|
+
}: PanelsideSearchTabsProps) {
|
|
2076
2518
|
const { tab, setTab } = usePanelsideSearchSheet('Panelside.SearchTabs');
|
|
2077
2519
|
|
|
2078
2520
|
return (
|
|
@@ -2083,7 +2525,9 @@ function PanelsideSearchTabs({ className, children }: PanelsideSearchTabsProps)
|
|
|
2083
2525
|
defaultValue={tab}
|
|
2084
2526
|
className={cn('px-4 pt-1', className)}
|
|
2085
2527
|
>
|
|
2086
|
-
<Tabs.List
|
|
2528
|
+
<Tabs.List className={cn(align === 'center' && 'justify-center', listClassName)}>
|
|
2529
|
+
{children}
|
|
2530
|
+
</Tabs.List>
|
|
2087
2531
|
</Tabs>
|
|
2088
2532
|
);
|
|
2089
2533
|
}
|
|
@@ -2099,7 +2543,11 @@ export interface PanelsideSearchTabProps {
|
|
|
2099
2543
|
|
|
2100
2544
|
function PanelsideSearchTab({ className, value, icon, children }: PanelsideSearchTabProps) {
|
|
2101
2545
|
return (
|
|
2102
|
-
|
|
2546
|
+
// A step of padding above the tab row's own. This row is the only control
|
|
2547
|
+
// on a surface that is otherwise a list and a field, and a closed tab here
|
|
2548
|
+
// is a glyph with nothing else to be aimed at by — so it is sized to be
|
|
2549
|
+
// hit rather than to the density a row of labelled tabs can afford.
|
|
2550
|
+
<Tabs.Trigger value={value} icon={icon} className={cn('px-4 py-3', className)}>
|
|
2103
2551
|
{children}
|
|
2104
2552
|
</Tabs.Trigger>
|
|
2105
2553
|
);
|
|
@@ -2253,13 +2701,24 @@ function PanelsideSearchField({
|
|
|
2253
2701
|
bottomInset={bottomInset}
|
|
2254
2702
|
className="w-full px-4 pb-1"
|
|
2255
2703
|
>
|
|
2704
|
+
{/*
|
|
2705
|
+
An opaque surface with a ring, not the secondary tint the rest of the
|
|
2706
|
+
panel's controls use.
|
|
2707
|
+
|
|
2708
|
+
The field rides the keyboard by translating rather than by taking a row
|
|
2709
|
+
of the layout, so once it is up the results are directly behind it —
|
|
2710
|
+
and the secondary token is a few percent of a colour, which the list
|
|
2711
|
+
then reads straight through, taking the placeholder with it. The ring
|
|
2712
|
+
is what a floating pill needs to have an edge once its fill is the same
|
|
2713
|
+
colour as most of the sheet.
|
|
2714
|
+
*/}
|
|
2256
2715
|
<View
|
|
2257
2716
|
className={cn(
|
|
2258
|
-
'h-12 w-full flex-row items-center gap-2 rounded-full bg-
|
|
2717
|
+
'h-12 w-full flex-row items-center gap-2 rounded-full border border-border bg-surface px-4 shadow-lg',
|
|
2259
2718
|
containerClassName
|
|
2260
2719
|
)}
|
|
2261
2720
|
>
|
|
2262
|
-
<
|
|
2721
|
+
<Glyph icon={Search01Icon} size={17} color={muted} />
|
|
2263
2722
|
<TextInput
|
|
2264
2723
|
ref={field}
|
|
2265
2724
|
value={text}
|
|
@@ -2281,7 +2740,7 @@ function PanelsideSearchField({
|
|
|
2281
2740
|
accessibilityLabel="Clear search"
|
|
2282
2741
|
className="h-5 w-5 items-center justify-center rounded-full bg-muted"
|
|
2283
2742
|
>
|
|
2284
|
-
<
|
|
2743
|
+
<Glyph icon={Cancel01Icon} size={12} color={muted} />
|
|
2285
2744
|
</AnimatedPressable>
|
|
2286
2745
|
) : null}
|
|
2287
2746
|
</View>
|
|
@@ -2301,9 +2760,12 @@ PanelsideItemIcon.displayName = 'Panelside.ItemIcon';
|
|
|
2301
2760
|
PanelsideItemLabel.displayName = 'Panelside.ItemLabel';
|
|
2302
2761
|
PanelsideItemBadge.displayName = 'Panelside.ItemBadge';
|
|
2303
2762
|
PanelsideAction.displayName = 'Panelside.Action';
|
|
2763
|
+
PanelsideItemActions.displayName = 'Panelside.ItemActions';
|
|
2304
2764
|
PanelsideFooter.displayName = 'Panelside.Footer';
|
|
2305
2765
|
PanelsideCta.displayName = 'Panelside.Cta';
|
|
2306
2766
|
PanelsideScene.displayName = 'Panelside.Scene';
|
|
2767
|
+
PanelsidePages.displayName = 'Panelside.Pages';
|
|
2768
|
+
PanelsidePage.displayName = 'Panelside.Page';
|
|
2307
2769
|
PanelsideTrigger.displayName = 'Panelside.Trigger';
|
|
2308
2770
|
PanelsideSearchTrigger.displayName = 'Panelside.SearchTrigger';
|
|
2309
2771
|
PanelsideSearchSheet.displayName = 'Panelside.SearchSheet';
|
|
@@ -2325,9 +2787,12 @@ export const Panelside = Object.assign(PanelsideRoot, {
|
|
|
2325
2787
|
ItemLabel: PanelsideItemLabel,
|
|
2326
2788
|
ItemBadge: PanelsideItemBadge,
|
|
2327
2789
|
Action: PanelsideAction,
|
|
2790
|
+
ItemActions: PanelsideItemActions,
|
|
2328
2791
|
Footer: PanelsideFooter,
|
|
2329
2792
|
Cta: PanelsideCta,
|
|
2330
2793
|
Scene: PanelsideScene,
|
|
2794
|
+
Pages: PanelsidePages,
|
|
2795
|
+
Page: PanelsidePage,
|
|
2331
2796
|
Trigger: PanelsideTrigger,
|
|
2332
2797
|
SearchTrigger: PanelsideSearchTrigger,
|
|
2333
2798
|
SearchSheet: PanelsideSearchSheet,
|