panelui-native 0.92.0 → 0.94.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 +19 -2
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/bubble-chart/index.js +23 -9
- package/lib/module/components/bubble-chart/index.js.map +1 -1
- package/lib/module/components/frame/index.js +178 -34
- package/lib/module/components/frame/index.js.map +1 -1
- package/lib/module/components/page-header/index.js +577 -0
- package/lib/module/components/page-header/index.js.map +1 -0
- package/lib/module/components/questionnaire/index.js +242 -22
- package/lib/module/components/questionnaire/index.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/bubble-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/frame/index.d.ts +19 -2
- package/lib/typescript/src/components/frame/index.d.ts.map +1 -1
- package/lib/typescript/src/components/page-header/index.d.ts +278 -0
- package/lib/typescript/src/components/page-header/index.d.ts.map +1 -0
- package/lib/typescript/src/components/questionnaire/index.d.ts +19 -5
- package/lib/typescript/src/components/questionnaire/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/bottom-sheet/index.tsx +29 -3
- package/src/components/bubble-chart/index.tsx +29 -9
- package/src/components/frame/index.tsx +180 -29
- package/src/components/page-header/index.tsx +674 -0
- package/src/components/questionnaire/index.tsx +249 -19
- package/src/index.ts +17 -0
|
@@ -586,6 +586,17 @@ function BottomSheetContent({
|
|
|
586
586
|
const pan = useMemo(
|
|
587
587
|
() =>
|
|
588
588
|
Gesture.Pan()
|
|
589
|
+
/*
|
|
590
|
+
* A locked sheet does not take the drag at all.
|
|
591
|
+
*
|
|
592
|
+
* Refusing the close is not enough on its own: the drag still moves
|
|
593
|
+
* the sheet, and a controlled caller that declines `onOpenChange`
|
|
594
|
+
* leaves it parked off the bottom of the screen — open as far as the
|
|
595
|
+
* caller is concerned, invisible, and still taking every touch behind
|
|
596
|
+
* a fully transparent backdrop. The backdrop press and the back
|
|
597
|
+
* gesture were already gated; this is the third way out.
|
|
598
|
+
*/
|
|
599
|
+
.enabled(dismissible)
|
|
589
600
|
/*
|
|
590
601
|
* A drag has to travel before it takes the touch. This detector wraps
|
|
591
602
|
* the whole sheet, close button included, and an unqualified Pan
|
|
@@ -626,9 +637,16 @@ function BottomSheetContent({
|
|
|
626
637
|
* your hand from one that merely goes where you put it.
|
|
627
638
|
*/
|
|
628
639
|
const projected = translateY.value + project(event.velocityY);
|
|
640
|
+
/*
|
|
641
|
+
* `dismissible` again, behind `.enabled()` rather than instead of
|
|
642
|
+
* it. Disabling a gesture does not cancel a touch already in flight,
|
|
643
|
+
* so a sheet locked mid-drag still arrives here — and without this
|
|
644
|
+
* it would fling itself away on the strength of a decision the
|
|
645
|
+
* caller has since revoked. Falling through snaps it back instead.
|
|
646
|
+
*/
|
|
629
647
|
if (
|
|
630
|
-
|
|
631
|
-
event.velocityY > DISMISS_VELOCITY
|
|
648
|
+
dismissible &&
|
|
649
|
+
(projected > DISMISS_DISTANCE || event.velocityY > DISMISS_VELOCITY)
|
|
632
650
|
) {
|
|
633
651
|
translateY.value = withSpring(
|
|
634
652
|
screenHeight,
|
|
@@ -647,7 +665,15 @@ function BottomSheetContent({
|
|
|
647
665
|
// Rebuilt only when one of these changes. Built inline it would be a new
|
|
648
666
|
// gesture on every render — and the sheet re-renders while it is being
|
|
649
667
|
// used, each time re-attaching the handler and dropping the live touch.
|
|
650
|
-
[
|
|
668
|
+
[
|
|
669
|
+
close,
|
|
670
|
+
dismissible,
|
|
671
|
+
screenHeight,
|
|
672
|
+
translateY,
|
|
673
|
+
scrollGesture,
|
|
674
|
+
scrollOffset,
|
|
675
|
+
hasScrollable,
|
|
676
|
+
]
|
|
651
677
|
);
|
|
652
678
|
|
|
653
679
|
const sheetStyle = useAnimatedStyle(() => ({
|
|
@@ -165,6 +165,20 @@ const AXIS_TITLE_HEIGHT = 18;
|
|
|
165
165
|
/** Gap between a quadrant's caption and the corner it is written in. */
|
|
166
166
|
const QUADRANT_LABEL_INSET = 6;
|
|
167
167
|
|
|
168
|
+
/**
|
|
169
|
+
* How strongly the overlays draw: the quadrant crosshair, and the size key's
|
|
170
|
+
* rings.
|
|
171
|
+
*
|
|
172
|
+
* Both are furniture rather than data, and both were faint enough to be missed
|
|
173
|
+
* — the crosshair at 0.4 and the rings at 0.6, on a token that is already a
|
|
174
|
+
* mid-grey chosen to stay behind the circles. The restraint has to come from
|
|
175
|
+
* the token, not from a second reduction on top of it, or the mark is one
|
|
176
|
+
* nobody finds. `Trend` has drawn at 0.7 all along and reads correctly in both
|
|
177
|
+
* themes, so that is the number.
|
|
178
|
+
*/
|
|
179
|
+
const CROSSHAIR_OPACITY = 0.7;
|
|
180
|
+
const KEY_RING_OPACITY = 0.85;
|
|
181
|
+
|
|
168
182
|
/** Column the size key's values are written in, beside its circles. */
|
|
169
183
|
const SIZE_KEY_LABEL_WIDTH = 44;
|
|
170
184
|
|
|
@@ -1307,6 +1321,12 @@ function BubbleChartQuadrants({
|
|
|
1307
1321
|
/>
|
|
1308
1322
|
</>
|
|
1309
1323
|
) : null}
|
|
1324
|
+
{/*
|
|
1325
|
+
The same weight `Trend` draws at, because it is the same kind of mark:
|
|
1326
|
+
a reference laid over the field rather than a reading taken from it.
|
|
1327
|
+
At 0.4 of a mid-grey the split was a line you had to already know was
|
|
1328
|
+
there, which leaves four labelled corners and nothing dividing them.
|
|
1329
|
+
*/}
|
|
1310
1330
|
<Animated.View
|
|
1311
1331
|
style={[
|
|
1312
1332
|
{
|
|
@@ -1315,7 +1335,7 @@ function BubbleChartQuadrants({
|
|
|
1315
1335
|
height: plot.height,
|
|
1316
1336
|
width: 1,
|
|
1317
1337
|
backgroundColor: stroke,
|
|
1318
|
-
opacity:
|
|
1338
|
+
opacity: CROSSHAIR_OPACITY,
|
|
1319
1339
|
},
|
|
1320
1340
|
verticalStyle,
|
|
1321
1341
|
]}
|
|
@@ -1328,7 +1348,7 @@ function BubbleChartQuadrants({
|
|
|
1328
1348
|
width: plot.width,
|
|
1329
1349
|
height: 1,
|
|
1330
1350
|
backgroundColor: stroke,
|
|
1331
|
-
opacity:
|
|
1351
|
+
opacity: CROSSHAIR_OPACITY,
|
|
1332
1352
|
},
|
|
1333
1353
|
horizontalStyle,
|
|
1334
1354
|
]}
|
|
@@ -1341,7 +1361,7 @@ function BubbleChartQuadrants({
|
|
|
1341
1361
|
{labels?.topLeft ? (
|
|
1342
1362
|
<Text
|
|
1343
1363
|
size="xs"
|
|
1344
|
-
|
|
1364
|
+
weight="medium"
|
|
1345
1365
|
numberOfLines={1}
|
|
1346
1366
|
style={{
|
|
1347
1367
|
...corner,
|
|
@@ -1355,7 +1375,7 @@ function BubbleChartQuadrants({
|
|
|
1355
1375
|
{labels?.topRight ? (
|
|
1356
1376
|
<Text
|
|
1357
1377
|
size="xs"
|
|
1358
|
-
|
|
1378
|
+
weight="medium"
|
|
1359
1379
|
numberOfLines={1}
|
|
1360
1380
|
style={{
|
|
1361
1381
|
...corner,
|
|
@@ -1370,7 +1390,7 @@ function BubbleChartQuadrants({
|
|
|
1370
1390
|
{labels?.bottomLeft ? (
|
|
1371
1391
|
<Text
|
|
1372
1392
|
size="xs"
|
|
1373
|
-
|
|
1393
|
+
weight="medium"
|
|
1374
1394
|
numberOfLines={1}
|
|
1375
1395
|
style={{
|
|
1376
1396
|
...corner,
|
|
@@ -1384,7 +1404,7 @@ function BubbleChartQuadrants({
|
|
|
1384
1404
|
{labels?.bottomRight ? (
|
|
1385
1405
|
<Text
|
|
1386
1406
|
size="xs"
|
|
1387
|
-
|
|
1407
|
+
weight="medium"
|
|
1388
1408
|
numberOfLines={1}
|
|
1389
1409
|
style={{
|
|
1390
1410
|
...corner,
|
|
@@ -1467,7 +1487,7 @@ function BubbleChartSizeKey({
|
|
|
1467
1487
|
className={cn(className)}
|
|
1468
1488
|
>
|
|
1469
1489
|
{label ? (
|
|
1470
|
-
<Text size="xs"
|
|
1490
|
+
<Text size="xs" weight="medium" numberOfLines={1}>
|
|
1471
1491
|
{label}
|
|
1472
1492
|
</Text>
|
|
1473
1493
|
) : null}
|
|
@@ -1486,7 +1506,7 @@ function BubbleChartSizeKey({
|
|
|
1486
1506
|
borderRadius: step.r,
|
|
1487
1507
|
borderWidth: 1,
|
|
1488
1508
|
borderColor: stroke,
|
|
1489
|
-
opacity:
|
|
1509
|
+
opacity: KEY_RING_OPACITY,
|
|
1490
1510
|
}}
|
|
1491
1511
|
/>
|
|
1492
1512
|
))}
|
|
@@ -1494,7 +1514,7 @@ function BubbleChartSizeKey({
|
|
|
1494
1514
|
<Text
|
|
1495
1515
|
key={`v${step.value}`}
|
|
1496
1516
|
size="xs"
|
|
1497
|
-
|
|
1517
|
+
weight="medium"
|
|
1498
1518
|
numberOfLines={1}
|
|
1499
1519
|
style={{
|
|
1500
1520
|
position: 'absolute',
|
|
@@ -50,6 +50,17 @@
|
|
|
50
50
|
* </Frame>
|
|
51
51
|
* ```
|
|
52
52
|
*
|
|
53
|
+
* `inset` is the other way to nest the two. The panel floats clear of the
|
|
54
|
+
* shell on all four sides rather than sitting flush against three, and the band
|
|
55
|
+
* left around it carries `Frame.Footer`. The band is a recess: the shell is the
|
|
56
|
+
* popover surface with `--color-inset` laid over it rather than a colour of its
|
|
57
|
+
* own, so it always comes out darker than the panel it holds. The surface
|
|
58
|
+
* ladder cannot do that job — it runs darker in a light theme and lighter in a
|
|
59
|
+
* dark one, and a recess has to read the same way in both.
|
|
60
|
+
*
|
|
61
|
+
* There is no shadow under it. A recessed band and a drop shadow are opposite
|
|
62
|
+
* claims about where a surface sits, and this one is set into the page.
|
|
63
|
+
*
|
|
53
64
|
* The panel draws the hairlines between its own rows. React Native has no
|
|
54
65
|
* `:first-child`, so the alternative is every caller writing
|
|
55
66
|
* `divided={index > 0}` on every row and getting it wrong once.
|
|
@@ -83,27 +94,64 @@ import { ChevronRightIcon } from '../../icons';
|
|
|
83
94
|
import { Text, type TextProps, textChildren } from '../../primitives/text';
|
|
84
95
|
import { cn } from '../../utils/cn';
|
|
85
96
|
|
|
97
|
+
/**
|
|
98
|
+
* The `inset` shell's geometry. Numbers rather than classes, because an
|
|
99
|
+
* arbitrary Tailwind value a running dev server has not already compiled turns
|
|
100
|
+
* into nothing at all — no error, no warning, the corner simply squares off.
|
|
101
|
+
*
|
|
102
|
+
* The panel's radius is the shell's less the shell's padding, so the two curves
|
|
103
|
+
* are concentric. A panel radius that ignored the band would leave a crescent
|
|
104
|
+
* of shell thicker at the corners than along the sides.
|
|
105
|
+
*/
|
|
106
|
+
const SHELL_PADDING = 8;
|
|
107
|
+
const SHELL_RADIUS = 38;
|
|
108
|
+
const PANEL_RADIUS = SHELL_RADIUS - SHELL_PADDING;
|
|
109
|
+
const FOOTER_INSET = 26;
|
|
110
|
+
const FOOTER_GAP = 16;
|
|
111
|
+
|
|
112
|
+
/** What the band does to an action put in it: equal width, and a full pill. */
|
|
113
|
+
const FOOTER_ACTION = 'h-11 flex-1 rounded-full';
|
|
114
|
+
|
|
86
115
|
const frameVariants = tv({
|
|
87
116
|
slots: {
|
|
88
117
|
root: '',
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
panel: 'overflow-hidden rounded-t-2xl border-t border-border bg-card',
|
|
118
|
+
recess: 'absolute inset-0 bg-inset',
|
|
119
|
+
panel: 'overflow-hidden bg-card',
|
|
120
|
+
header: 'flex-row items-center justify-between gap-3 px-4 pb-3 pt-2.5',
|
|
121
|
+
footer: 'flex-row items-center gap-2 px-4 pb-3.5 pt-3',
|
|
94
122
|
},
|
|
95
123
|
variants: {
|
|
96
124
|
variant: {
|
|
97
125
|
// `overflow-hidden` is load-bearing — it is what makes the panel's
|
|
98
126
|
// bottom corners take the shell's radius instead of squaring off
|
|
99
127
|
// against it.
|
|
128
|
+
//
|
|
129
|
+
// The panel is flush left, right and bottom: no side or bottom border,
|
|
130
|
+
// because the shell's own edge is already there, and no bottom radius,
|
|
131
|
+
// because the shell clips it. Only the top corners and the rule under
|
|
132
|
+
// the header are the panel's to draw.
|
|
100
133
|
default: {
|
|
101
134
|
root: 'overflow-hidden rounded-3xl border border-border bg-surface',
|
|
135
|
+
panel: 'rounded-t-2xl border-t border-border',
|
|
102
136
|
},
|
|
103
137
|
// No shell: the panel is the whole widget. For a Frame nested inside a
|
|
104
138
|
// card that already draws a border, where the shell's own edge sitting
|
|
105
139
|
// just inside it reads as a double line.
|
|
106
|
-
plain: {
|
|
140
|
+
plain: {
|
|
141
|
+
root: '',
|
|
142
|
+
panel: 'rounded-3xl border border-border',
|
|
143
|
+
// Nothing to be held in from — the footer lines up with the panel.
|
|
144
|
+
footer: 'px-0',
|
|
145
|
+
},
|
|
146
|
+
// The panel floats inside the shell on all four sides instead of sitting
|
|
147
|
+
// flush against three of them, and the band around it is a recess rather
|
|
148
|
+
// than a lighter tray.
|
|
149
|
+
inset: {
|
|
150
|
+
root: 'overflow-hidden bg-popover',
|
|
151
|
+
panel: 'bg-popover',
|
|
152
|
+
header: 'px-3 pb-2.5 pt-1',
|
|
153
|
+
footer: 'gap-3.5 p-0',
|
|
154
|
+
},
|
|
107
155
|
},
|
|
108
156
|
},
|
|
109
157
|
defaultVariants: {
|
|
@@ -111,7 +159,7 @@ const frameVariants = tv({
|
|
|
111
159
|
},
|
|
112
160
|
});
|
|
113
161
|
|
|
114
|
-
export type FrameVariant = 'default' | 'plain';
|
|
162
|
+
export type FrameVariant = 'default' | 'plain' | 'inset';
|
|
115
163
|
|
|
116
164
|
/**
|
|
117
165
|
* True inside a `Frame.Content`. The header's caption and a row's title are the
|
|
@@ -121,6 +169,14 @@ export type FrameVariant = 'default' | 'plain';
|
|
|
121
169
|
*/
|
|
122
170
|
const FrameSlotContext = createContext(false);
|
|
123
171
|
|
|
172
|
+
/**
|
|
173
|
+
* The root's variant, for the parts that draw differently under each one. The
|
|
174
|
+
* panel, the header and the footer all need it, and none of them can be told
|
|
175
|
+
* directly — a caller writes `<Frame variant="inset">` once and expects the
|
|
176
|
+
* shape to follow.
|
|
177
|
+
*/
|
|
178
|
+
const FrameVariantContext = createContext<FrameVariant>('default');
|
|
179
|
+
|
|
124
180
|
export interface FrameProps extends ViewProps {
|
|
125
181
|
className?: string;
|
|
126
182
|
}
|
|
@@ -128,15 +184,35 @@ export interface FrameProps extends ViewProps {
|
|
|
128
184
|
export interface FrameRootProps extends FrameProps {
|
|
129
185
|
/**
|
|
130
186
|
* `plain` drops the outer shell so the panel is the widget — for a Frame
|
|
131
|
-
* inside a container that already draws its own border.
|
|
187
|
+
* inside a container that already draws its own border. `inset` sets the
|
|
188
|
+
* panel into a recessed band on all four sides instead, and gives
|
|
189
|
+
* `Frame.Footer` somewhere to sit.
|
|
132
190
|
*/
|
|
133
191
|
variant?: FrameVariant;
|
|
134
192
|
}
|
|
135
193
|
|
|
136
194
|
const FrameRoot = forwardRef<View, FrameRootProps>(
|
|
137
|
-
({ className, variant, ...props }, ref) =>
|
|
138
|
-
|
|
139
|
-
|
|
195
|
+
({ className, variant = 'default', children, style, ...props }, ref) => {
|
|
196
|
+
const slots = frameVariants({ variant });
|
|
197
|
+
const inset = variant === 'inset';
|
|
198
|
+
return (
|
|
199
|
+
<View
|
|
200
|
+
{...props}
|
|
201
|
+
ref={ref}
|
|
202
|
+
className={slots.root({ className })}
|
|
203
|
+
style={
|
|
204
|
+
inset
|
|
205
|
+
? [{ borderRadius: SHELL_RADIUS, padding: SHELL_PADDING }, style]
|
|
206
|
+
: style
|
|
207
|
+
}
|
|
208
|
+
>
|
|
209
|
+
{inset ? <View pointerEvents="none" className={slots.recess()} /> : null}
|
|
210
|
+
<FrameVariantContext.Provider value={variant}>
|
|
211
|
+
{children}
|
|
212
|
+
</FrameVariantContext.Provider>
|
|
213
|
+
</View>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
140
216
|
);
|
|
141
217
|
FrameRoot.displayName = 'Frame';
|
|
142
218
|
|
|
@@ -150,18 +226,22 @@ export interface FrameHeaderProps extends FrameProps {
|
|
|
150
226
|
* when you want a description underneath.
|
|
151
227
|
*/
|
|
152
228
|
const FrameHeader = forwardRef<View, FrameHeaderProps>(
|
|
153
|
-
({ className, ...props }, ref) =>
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
229
|
+
({ className, ...props }, ref) => {
|
|
230
|
+
// `min-w-0` on nothing here — the title itself takes the flexible side, so
|
|
231
|
+
// a long one truncates instead of shoving the action off the edge.
|
|
232
|
+
//
|
|
233
|
+
// The padding follows the variant: under `inset` the shell already holds
|
|
234
|
+
// everything in by its own padding, so repeating the full inset here would
|
|
235
|
+
// set the title further from the edge than the panel below it.
|
|
236
|
+
const variant = useContext(FrameVariantContext);
|
|
237
|
+
return (
|
|
238
|
+
<View
|
|
239
|
+
{...props}
|
|
240
|
+
ref={ref}
|
|
241
|
+
className={frameVariants({ variant }).header({ className })}
|
|
242
|
+
/>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
165
245
|
);
|
|
166
246
|
FrameHeader.displayName = 'Frame.Header';
|
|
167
247
|
|
|
@@ -336,14 +416,23 @@ export interface FramePanelProps extends FrameProps {
|
|
|
336
416
|
|
|
337
417
|
/**
|
|
338
418
|
* The card holding the frame's content — flush to the shell on three sides,
|
|
339
|
-
* with the header strip above it.
|
|
419
|
+
* with the header strip above it. Under `inset` it floats clear of all four
|
|
420
|
+
* instead, at the radius that keeps it concentric with the shell.
|
|
340
421
|
*/
|
|
341
422
|
const FramePanel = forwardRef<View, FramePanelProps>(
|
|
342
|
-
({ className, dividers = true, children, ...props }, ref) =>
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
423
|
+
({ className, dividers = true, children, style, ...props }, ref) => {
|
|
424
|
+
const variant = useContext(FrameVariantContext);
|
|
425
|
+
return (
|
|
426
|
+
<View
|
|
427
|
+
{...props}
|
|
428
|
+
ref={ref}
|
|
429
|
+
className={frameVariants({ variant }).panel({ className })}
|
|
430
|
+
style={variant === 'inset' ? [{ borderRadius: PANEL_RADIUS }, style] : style}
|
|
431
|
+
>
|
|
432
|
+
{dividers ? divideChildren(children) : children}
|
|
433
|
+
</View>
|
|
434
|
+
);
|
|
435
|
+
}
|
|
347
436
|
);
|
|
348
437
|
FramePanel.displayName = 'Frame.Panel';
|
|
349
438
|
|
|
@@ -455,6 +544,67 @@ const FrameSection = forwardRef<View, FrameSectionProps>(
|
|
|
455
544
|
);
|
|
456
545
|
FrameSection.displayName = 'Frame.Section';
|
|
457
546
|
|
|
547
|
+
export interface FrameFooterProps extends FrameProps {
|
|
548
|
+
children?: ReactNode;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Shapes an `inset` footer's actions into the band's pills.
|
|
553
|
+
*
|
|
554
|
+
* The band is a row of equal decisions, so the actions are equal widths and
|
|
555
|
+
* full circles rather than whatever radius each one arrived with. Doing it
|
|
556
|
+
* here rather than asking every caller for three classes is the difference
|
|
557
|
+
* between a variant that looks a certain way and one that can be made to.
|
|
558
|
+
*
|
|
559
|
+
* The caller's own `className` is merged last, so any of it can still be
|
|
560
|
+
* overridden — a trailing icon button that should stay square, say.
|
|
561
|
+
*/
|
|
562
|
+
function pillChildren(children: ReactNode) {
|
|
563
|
+
return Children.map(children, (child) => {
|
|
564
|
+
if (!isValidElement<{ className?: string }>(child)) return child;
|
|
565
|
+
return cloneElement(child, {
|
|
566
|
+
className: cn(FOOTER_ACTION, child.props.className),
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* The row of actions under the panel — what somebody does with the widget,
|
|
573
|
+
* rather than more of what it says.
|
|
574
|
+
*
|
|
575
|
+
* Under `inset` it sits in the band, held further in than the panel is. A row
|
|
576
|
+
* running the full width of the shell reads as another edge of it rather than
|
|
577
|
+
* as things to press. Under the other two variants the panel stops being flush
|
|
578
|
+
* at the bottom, which is what having a footer means there.
|
|
579
|
+
*/
|
|
580
|
+
const FrameFooter = forwardRef<View, FrameFooterProps>(
|
|
581
|
+
({ className, style, children, ...props }, ref) => {
|
|
582
|
+
const variant = useContext(FrameVariantContext);
|
|
583
|
+
const inset = variant === 'inset';
|
|
584
|
+
return (
|
|
585
|
+
<View
|
|
586
|
+
{...props}
|
|
587
|
+
ref={ref}
|
|
588
|
+
className={frameVariants({ variant }).footer({ className })}
|
|
589
|
+
style={
|
|
590
|
+
inset
|
|
591
|
+
? [
|
|
592
|
+
{
|
|
593
|
+
marginTop: FOOTER_GAP,
|
|
594
|
+
marginHorizontal: FOOTER_INSET - SHELL_PADDING,
|
|
595
|
+
},
|
|
596
|
+
style,
|
|
597
|
+
]
|
|
598
|
+
: style
|
|
599
|
+
}
|
|
600
|
+
>
|
|
601
|
+
{inset ? pillChildren(children) : children}
|
|
602
|
+
</View>
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
);
|
|
606
|
+
FrameFooter.displayName = 'Frame.Footer';
|
|
607
|
+
|
|
458
608
|
/** Parts the panel divides. Declared after them, since it holds references. */
|
|
459
609
|
const DIVIDABLE = new Set<unknown>([FrameRow, FrameSection]);
|
|
460
610
|
|
|
@@ -464,6 +614,7 @@ export const Frame = Object.assign(FrameRoot, {
|
|
|
464
614
|
Action: FrameAction,
|
|
465
615
|
Description: FrameDescription,
|
|
466
616
|
Panel: FramePanel,
|
|
617
|
+
Footer: FrameFooter,
|
|
467
618
|
Section: FrameSection,
|
|
468
619
|
Row: FrameRow,
|
|
469
620
|
Media: FrameMedia,
|