panelui-native 0.92.0 → 0.93.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/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/index.js +1 -0
- package/lib/module/index.js.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/index.d.ts +2 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/frame/index.tsx +180 -29
- package/src/components/page-header/index.tsx +674 -0
- package/src/index.ts +17 -0
|
@@ -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,
|