panelui-native 0.84.0 → 0.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -8
- package/lib/module/components/bottom-sheet/index.js +3 -2
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/button/index.js +37 -16
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/flip-card/index.js +338 -0
- package/lib/module/components/flip-card/index.js.map +1 -0
- package/lib/module/components/popover/index.js +3 -2
- package/lib/module/components/popover/index.js.map +1 -1
- package/lib/module/components/select/index.js +3 -2
- package/lib/module/components/select/index.js.map +1 -1
- package/lib/module/components/slider/index.js +16 -5
- package/lib/module/components/slider/index.js.map +1 -1
- package/lib/module/components/switch/index.js +28 -7
- package/lib/module/components/switch/index.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/native/index.js +7 -0
- package/lib/module/native/index.js.map +1 -1
- package/lib/module/native/native-host.js +62 -0
- package/lib/module/native/native-host.js.map +1 -0
- package/lib/module/primitives/glass.js +14 -0
- package/lib/module/primitives/glass.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.map +1 -1
- package/lib/typescript/src/components/flip-card/index.d.ts +116 -0
- package/lib/typescript/src/components/flip-card/index.d.ts.map +1 -0
- package/lib/typescript/src/components/slider/index.d.ts.map +1 -1
- package/lib/typescript/src/components/switch/index.d.ts +0 -4
- package/lib/typescript/src/components/switch/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native/index.d.ts +28 -11
- package/lib/typescript/src/native/index.d.ts.map +1 -1
- package/lib/typescript/src/native/native-host.d.ts +71 -0
- package/lib/typescript/src/native/native-host.d.ts.map +1 -0
- package/lib/typescript/src/primitives/glass.d.ts.map +1 -1
- package/package.json +11 -1
- package/src/components/bottom-sheet/index.tsx +8 -3
- package/src/components/button/index.tsx +36 -15
- package/src/components/flip-card/index.tsx +419 -0
- package/src/components/popover/index.tsx +3 -3
- package/src/components/select/index.tsx +3 -3
- package/src/components/slider/index.tsx +28 -6
- package/src/components/switch/index.tsx +28 -7
- package/src/index.ts +9 -0
- package/src/native/index.ts +35 -10
- package/src/native/native-host.tsx +75 -0
- package/src/primitives/glass.tsx +12 -0
|
@@ -8,7 +8,7 @@ import Animated, {
|
|
|
8
8
|
} from 'react-native-reanimated';
|
|
9
9
|
import { tv, type VariantProps } from 'tailwind-variants';
|
|
10
10
|
import { useDirectionSign } from '../../hooks/use-direction';
|
|
11
|
-
import { getNativeUI } from '../../native';
|
|
11
|
+
import { NativeHost, getNativeUI } from '../../native';
|
|
12
12
|
import { selectionTick } from '../../utils/haptics';
|
|
13
13
|
import { useFieldLabelledBy } from '../field';
|
|
14
14
|
|
|
@@ -79,6 +79,15 @@ export interface SwitchProps
|
|
|
79
79
|
* Animated switch. Thumb position and active-track opacity are driven on the
|
|
80
80
|
* UI thread; toggling never re-renders beyond the value change itself.
|
|
81
81
|
*/
|
|
82
|
+
/**
|
|
83
|
+
* The box the platform toggle is given, in points.
|
|
84
|
+
*
|
|
85
|
+
* Taller than either platform's switch — 31 on iOS, 32 on Android — so the
|
|
86
|
+
* control is never clipped by the box it is centred in, and short enough that
|
|
87
|
+
* a row built around it is still a row.
|
|
88
|
+
*/
|
|
89
|
+
const NATIVE_TOGGLE_HEIGHT = 32;
|
|
90
|
+
|
|
82
91
|
export const Switch = forwardRef<View, SwitchProps>(
|
|
83
92
|
(
|
|
84
93
|
{
|
|
@@ -124,18 +133,30 @@ export const Switch = forwardRef<View, SwitchProps>(
|
|
|
124
133
|
if (nativeUI) {
|
|
125
134
|
const { Host, Switch: NativeSwitch } = nativeUI;
|
|
126
135
|
return (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
136
|
+
/*
|
|
137
|
+
* The height is stated; only the width is matched.
|
|
138
|
+
*
|
|
139
|
+
* `matchContents` hands an axis to the platform for good — the host
|
|
140
|
+
* writes the measured size back into the layout every time the
|
|
141
|
+
* platform's geometry changes, not once on mount — and the vertical
|
|
142
|
+
* axis is the one that moves everything below it when it does. A
|
|
143
|
+
* toggle's height is the one number here that does not vary, so it is
|
|
144
|
+
* given rather than asked for; its width is the platform's and stays
|
|
145
|
+
* the platform's.
|
|
146
|
+
*/
|
|
147
|
+
<NativeHost
|
|
148
|
+
host={Host}
|
|
149
|
+
matchContents={{ horizontal: true }}
|
|
150
|
+
ignoreSafeArea="keyboard"
|
|
151
|
+
style={{ height: NATIVE_TOGGLE_HEIGHT }}
|
|
152
|
+
>
|
|
132
153
|
<NativeSwitch
|
|
133
154
|
value={value}
|
|
134
155
|
onValueChange={(next: boolean) => onValueChange?.(next)}
|
|
135
156
|
label={label ?? accessibilityLabel}
|
|
136
157
|
disabled={disabled}
|
|
137
158
|
/>
|
|
138
|
-
</
|
|
159
|
+
</NativeHost>
|
|
139
160
|
);
|
|
140
161
|
}
|
|
141
162
|
|
package/src/index.ts
CHANGED
|
@@ -222,6 +222,15 @@ export {
|
|
|
222
222
|
type FlowRect,
|
|
223
223
|
type FlowPoint,
|
|
224
224
|
} from './components/flow';
|
|
225
|
+
export {
|
|
226
|
+
FlipCard,
|
|
227
|
+
useFlipCard,
|
|
228
|
+
type FlipCardProps,
|
|
229
|
+
type FlipCardFaceProps,
|
|
230
|
+
type FlipCardDirection,
|
|
231
|
+
type FlipCardRotation,
|
|
232
|
+
type FlipCardTrigger,
|
|
233
|
+
} from './components/flip-card';
|
|
225
234
|
export {
|
|
226
235
|
FunnelChart,
|
|
227
236
|
useFunnelChart,
|
package/src/native/index.ts
CHANGED
|
@@ -18,25 +18,40 @@
|
|
|
18
18
|
* **Theme tokens do not apply in native mode.** The platform draws the control
|
|
19
19
|
* with its own colours, metrics and typography — that is the entire point, and
|
|
20
20
|
* it means `className` and the variant props are ignored on those components.
|
|
21
|
+
*
|
|
22
|
+
* The one exception is which appearance it draws: `colorScheme` is the single
|
|
23
|
+
* theme signal the toolkit accepts, and `NativeHost` is what passes it. Mount
|
|
24
|
+
* every host through that rather than reaching for `Host` directly, or the
|
|
25
|
+
* control resolves its own appearance from the system and stops tracking the
|
|
26
|
+
* app's theme.
|
|
21
27
|
*/
|
|
22
28
|
import { Platform } from 'react-native';
|
|
23
29
|
import type { ComponentType, ReactNode } from 'react';
|
|
24
30
|
|
|
31
|
+
export { NativeHost, type NativeHostProps } from './native-host';
|
|
32
|
+
|
|
25
33
|
interface NativeUIModule {
|
|
26
34
|
Host: ComponentType<{
|
|
27
35
|
children?: ReactNode;
|
|
28
36
|
/**
|
|
29
|
-
*
|
|
37
|
+
* Which axes the platform is allowed to size.
|
|
30
38
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* the
|
|
34
|
-
*
|
|
35
|
-
*
|
|
39
|
+
* **It is not a one-off measurement.** An axis given to `matchContents` is
|
|
40
|
+
* given for good: the host writes the platform's measured size straight
|
|
41
|
+
* into the layout every time the platform's geometry changes, and dirties
|
|
42
|
+
* the layout when it does. So a control that lays itself out again under a
|
|
43
|
+
* press drags its box with it, and everything below it moves — which is
|
|
44
|
+
* the defect this spent three attempts on, twice reasoning about the first
|
|
45
|
+
* measurement when the problem was every one after it.
|
|
36
46
|
*
|
|
37
|
-
* The
|
|
38
|
-
*
|
|
39
|
-
*
|
|
47
|
+
* The rule that comes out of that: **never hand over an axis whose size
|
|
48
|
+
* you already know.** State it in `style` instead and match only what is
|
|
49
|
+
* genuinely the platform's — a labelled button's width, a toggle's width.
|
|
50
|
+
* An axis left out is never written to, so an explicit size on it is safe.
|
|
51
|
+
*
|
|
52
|
+
* Where nothing can be stated the axis has to stay matched. A picker is
|
|
53
|
+
* the honest example: a menu is a compact button and a wheel is a rotor,
|
|
54
|
+
* and only the platform knows which it drew.
|
|
40
55
|
*/
|
|
41
56
|
matchContents?: boolean | { vertical?: boolean; horizontal?: boolean };
|
|
42
57
|
/**
|
|
@@ -56,6 +71,14 @@ interface NativeUIModule {
|
|
|
56
71
|
* platform's business and stay its business.
|
|
57
72
|
*/
|
|
58
73
|
ignoreSafeArea?: 'all' | 'container' | 'keyboard';
|
|
74
|
+
/**
|
|
75
|
+
* The appearance the platform draws the hosted control in.
|
|
76
|
+
*
|
|
77
|
+
* Passed by `NativeHost` from the app's own theme, because the host would
|
|
78
|
+
* otherwise resolve it from the system — which is a different question,
|
|
79
|
+
* and one whose answer does not change when the theme does.
|
|
80
|
+
*/
|
|
81
|
+
colorScheme?: 'light' | 'dark';
|
|
59
82
|
style?: unknown;
|
|
60
83
|
[key: string]: unknown;
|
|
61
84
|
}>;
|
|
@@ -175,7 +198,9 @@ interface SwiftUIComponents {
|
|
|
175
198
|
Host: ComponentType<{
|
|
176
199
|
children?: ReactNode;
|
|
177
200
|
matchContents?: boolean | { vertical?: boolean; horizontal?: boolean };
|
|
178
|
-
ignoreSafeArea?:
|
|
201
|
+
ignoreSafeArea?: 'all' | 'container' | 'keyboard';
|
|
202
|
+
/** As on the portable host above — see `NativeHost`. */
|
|
203
|
+
colorScheme?: 'light' | 'dark';
|
|
179
204
|
style?: unknown;
|
|
180
205
|
}>;
|
|
181
206
|
RNHostView: ComponentType<{ children?: ReactNode; matchContents?: boolean }>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host every native control is mounted in, told which appearance to draw.
|
|
3
|
+
*
|
|
4
|
+
* A hosting controller resolves its colour scheme from the trait environment
|
|
5
|
+
* it is placed in, which is the system appearance — not the theme the app is
|
|
6
|
+
* running. Those are the same thing only by coincidence: an app in a dark
|
|
7
|
+
* theme on a phone set to light gets a light platform control beside dark
|
|
8
|
+
* content, and a theme changed at runtime leaves the control where it was,
|
|
9
|
+
* because nothing in the trait environment moved.
|
|
10
|
+
*
|
|
11
|
+
* So the appearance is passed rather than inferred. `colorScheme` is the one
|
|
12
|
+
* theme signal the platform toolkit accepts, and this is the single place it
|
|
13
|
+
* is given.
|
|
14
|
+
*
|
|
15
|
+
* ```tsx
|
|
16
|
+
* const { Host, Switch: NativeSwitch } = nativeUI;
|
|
17
|
+
* <NativeHost host={Host} matchContents ignoreSafeArea="keyboard">
|
|
18
|
+
* <NativeSwitch value={on} onValueChange={setOn} />
|
|
19
|
+
* </NativeHost>
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* ## Why the host arrives as a prop
|
|
23
|
+
*
|
|
24
|
+
* There are two of them. The universal `Host` comes from `getNativeUI()` and
|
|
25
|
+
* the SwiftUI-only one from `getSwiftUI()`, and a caller has already resolved
|
|
26
|
+
* whichever it needs before it renders. Taking the component rather than
|
|
27
|
+
* resolving it again keeps this file free of the module bridge, which is what
|
|
28
|
+
* lets the bridge re-export it without the two importing each other.
|
|
29
|
+
*
|
|
30
|
+
* ## Why this is a component rather than a hook at each call site
|
|
31
|
+
*
|
|
32
|
+
* `useThemeMode` subscribes to theme changes, and a hook cannot be called
|
|
33
|
+
* conditionally — so reading it inside `Button` would put a subscription on
|
|
34
|
+
* every button in a list for a branch almost none of them take. Here the
|
|
35
|
+
* subscription exists only where a native host is actually mounted.
|
|
36
|
+
*/
|
|
37
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
38
|
+
import { useThemeMode } from '../theme/use-theme';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* What this renders the host with. A type alias rather than an interface, and
|
|
42
|
+
* that is load-bearing: only an alias gets an implicit index signature, which
|
|
43
|
+
* is what makes it assignable to the bridge's own `Host` prop type.
|
|
44
|
+
*/
|
|
45
|
+
type NativeHostRenderProps = {
|
|
46
|
+
children?: ReactNode;
|
|
47
|
+
colorScheme?: 'light' | 'dark';
|
|
48
|
+
matchContents?: boolean | { vertical?: boolean; horizontal?: boolean };
|
|
49
|
+
ignoreSafeArea?: 'all' | 'container' | 'keyboard';
|
|
50
|
+
style?: unknown;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export interface NativeHostProps {
|
|
54
|
+
/**
|
|
55
|
+
* The host component to render — `Host` from `getNativeUI()` for a portable
|
|
56
|
+
* control, or from `getSwiftUI()` for an iOS-only one.
|
|
57
|
+
*/
|
|
58
|
+
host: ComponentType<NativeHostRenderProps>;
|
|
59
|
+
children?: ReactNode;
|
|
60
|
+
/** Whether the host resizes itself to the platform content. */
|
|
61
|
+
matchContents?: boolean | { vertical?: boolean; horizontal?: boolean };
|
|
62
|
+
/** Which safe areas the host lets the platform inset its content for. */
|
|
63
|
+
ignoreSafeArea?: 'all' | 'container' | 'keyboard';
|
|
64
|
+
style?: unknown;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function NativeHost({ host: Host, children, ...props }: NativeHostProps) {
|
|
68
|
+
const { mode } = useThemeMode();
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<Host colorScheme={mode} {...props}>
|
|
72
|
+
{children}
|
|
73
|
+
</Host>
|
|
74
|
+
);
|
|
75
|
+
}
|
package/src/primitives/glass.tsx
CHANGED
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
*/
|
|
45
45
|
import type { ComponentType, ReactNode } from 'react';
|
|
46
46
|
import { Platform, StyleSheet, View, type StyleProp, type ViewProps, type ViewStyle } from 'react-native';
|
|
47
|
+
import { useThemeMode } from '../theme/use-theme';
|
|
47
48
|
import { cn } from '../utils/cn';
|
|
48
49
|
import { useReduceTransparency } from './scrim';
|
|
49
50
|
|
|
@@ -144,6 +145,16 @@ export function Glass({
|
|
|
144
145
|
...props
|
|
145
146
|
}: GlassProps) {
|
|
146
147
|
const reduceTransparency = useReduceTransparency();
|
|
148
|
+
/*
|
|
149
|
+
* Which appearance the material is drawn in, from the app's theme rather
|
|
150
|
+
* than the phone's.
|
|
151
|
+
*
|
|
152
|
+
* The material's own default follows the system, so an app running a dark
|
|
153
|
+
* theme on a phone set to light draws light glass over dark content — and a
|
|
154
|
+
* theme changed at runtime leaves it where it was, because the system
|
|
155
|
+
* appearance never moved.
|
|
156
|
+
*/
|
|
157
|
+
const { mode } = useThemeMode();
|
|
147
158
|
// Not knowing yet counts as "do not draw it": the material arriving a frame
|
|
148
159
|
// late is invisible, and one flashing at somebody who opted out is not.
|
|
149
160
|
const material = GlassView !== null && reduceTransparency === false;
|
|
@@ -159,6 +170,7 @@ export function Glass({
|
|
|
159
170
|
<GlassView
|
|
160
171
|
glassEffectStyle={variant}
|
|
161
172
|
tintColor={tint}
|
|
173
|
+
colorScheme={mode}
|
|
162
174
|
pointerEvents="none"
|
|
163
175
|
style={[StyleSheet.absoluteFill, shape]}
|
|
164
176
|
/>
|