panelui-native 0.81.0 → 0.83.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 +189 -21
- 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 +267 -0
- package/lib/module/components/image-generation/dot-field.js.map +1 -0
- package/lib/module/components/image-generation/index.js +459 -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/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/native/index.js +24 -0
- package/lib/module/native/index.js.map +1 -1
- package/lib/module/primitives/portal.js +15 -1
- package/lib/module/primitives/portal.js.map +1 -1
- package/lib/typescript/src/components/bottom-sheet/index.d.ts +17 -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 +96 -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 +134 -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/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 +4 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native/index.d.ts +26 -0
- package/lib/typescript/src/native/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/portal.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/components/bottom-sheet/index.tsx +223 -20
- 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 +282 -0
- package/src/components/image-generation/index.tsx +537 -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/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 +28 -0
- package/src/native/index.ts +45 -0
- package/src/primitives/portal.tsx +15 -1
|
@@ -89,7 +89,33 @@ interface SwiftUIModifiers {
|
|
|
89
89
|
buttonStyle: (style: 'automatic' | 'bordered' | 'borderedProminent' | 'borderless' | 'glass' | 'glassProminent' | 'plain') => unknown;
|
|
90
90
|
buttonBorderShape: (shape: 'automatic' | 'capsule' | 'roundedRectangle' | 'circle', cornerRadius?: number) => unknown;
|
|
91
91
|
controlSize: (size: 'mini' | 'small' | 'regular' | 'large' | 'extraLarge') => unknown;
|
|
92
|
+
/**
|
|
93
|
+
* Paints a sheet's own surface, rather than a background behind its content.
|
|
94
|
+
*
|
|
95
|
+
* It is the only way out of the material a sheet is drawn in by default, and
|
|
96
|
+
* it reaches what an ordinary background cannot: the grabber's strip at the
|
|
97
|
+
* top and the safe-area inset at the bottom, which belong to the sheet's
|
|
98
|
+
* chrome and not to anything hosted inside it.
|
|
99
|
+
*
|
|
100
|
+
* **iOS 16.4 and up.** Below that it is inert and the sheet keeps its
|
|
101
|
+
* material — the same shape as glass being inert below iOS 26, and the same
|
|
102
|
+
* trap: indistinguishable from the prop not working. Check the OS before
|
|
103
|
+
* changing any code on a report of "the colour did nothing".
|
|
104
|
+
*/
|
|
105
|
+
presentationBackground: (color: string) => unknown;
|
|
92
106
|
}
|
|
93
107
|
export declare function getSwiftUIModifiers(): SwiftUIModifiers | null;
|
|
108
|
+
/**
|
|
109
|
+
* The Compose view modifiers — the other half of the same door.
|
|
110
|
+
*
|
|
111
|
+
* Android's toolkit has its own modifier system and its own vocabulary, so a
|
|
112
|
+
* modifier built for one platform is not a modifier the other can read. Where
|
|
113
|
+
* both are asked the same question the answers are written separately, here and
|
|
114
|
+
* above, rather than one being sent to both.
|
|
115
|
+
*/
|
|
116
|
+
interface ComposeModifiers {
|
|
117
|
+
background: (color: string) => unknown;
|
|
118
|
+
}
|
|
119
|
+
export declare function getComposeModifiers(): ComposeModifiers | null;
|
|
94
120
|
export {};
|
|
95
121
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/native/index.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEtD,UAAU,cAAc;IACtB,IAAI,EAAE,aAAa,CAAC;QAClB,QAAQ,CAAC,EAAE,SAAS,CAAC;QACrB;;;;;;;;;;;;WAYG;QACH,aAAa,CAAC,EAAE,OAAO,GAAG;YAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;YAAC,UAAU,CAAC,EAAE,OAAO,CAAA;SAAE,CAAC;QACvE;;;;;;;;;;;;;;;WAeG;QACH,cAAc,CAAC,EAAE,KAAK,GAAG,WAAW,GAAG,UAAU,CAAC;QAClD,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC,CAAC;IACH;;;;OAIG;IACH,UAAU,EAAE,aAAa,CAAC;QACxB,QAAQ,CAAC,EAAE,SAAS,CAAC;QACrB,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,CAAC,CAAC;IACH;;;;;OAKG;IACH,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/C,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/C,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/C,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG;QAC/C,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KAC9C,CAAC;IACF,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACrD;AAKD;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,cAAc,GAAG,IAAI,CAgBnD;AAED,gEAAgE;AAChE,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED;;;;;;;;;;;;;GAaG;AACH,UAAU,gBAAgB;IACxB,WAAW,EAAE,CACX,KAAK,EACD,WAAW,GACX,UAAU,GACV,mBAAmB,GACnB,YAAY,GACZ,OAAO,GACP,gBAAgB,GAChB,OAAO,KACR,OAAO,CAAC;IACb,iBAAiB,EAAE,CACjB,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,kBAAkB,GAAG,QAAQ,EAC9D,YAAY,CAAC,EAAE,MAAM,KAClB,OAAO,CAAC;IACb,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,YAAY,KAAK,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/native/index.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEtD,UAAU,cAAc;IACtB,IAAI,EAAE,aAAa,CAAC;QAClB,QAAQ,CAAC,EAAE,SAAS,CAAC;QACrB;;;;;;;;;;;;WAYG;QACH,aAAa,CAAC,EAAE,OAAO,GAAG;YAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;YAAC,UAAU,CAAC,EAAE,OAAO,CAAA;SAAE,CAAC;QACvE;;;;;;;;;;;;;;;WAeG;QACH,cAAc,CAAC,EAAE,KAAK,GAAG,WAAW,GAAG,UAAU,CAAC;QAClD,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC,CAAC;IACH;;;;OAIG;IACH,UAAU,EAAE,aAAa,CAAC;QACxB,QAAQ,CAAC,EAAE,SAAS,CAAC;QACrB,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,CAAC,CAAC;IACH;;;;;OAKG;IACH,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/C,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/C,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/C,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG;QAC/C,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KAC9C,CAAC;IACF,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACrD;AAKD;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,cAAc,GAAG,IAAI,CAgBnD;AAED,gEAAgE;AAChE,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED;;;;;;;;;;;;;GAaG;AACH,UAAU,gBAAgB;IACxB,WAAW,EAAE,CACX,KAAK,EACD,WAAW,GACX,UAAU,GACV,mBAAmB,GACnB,YAAY,GACZ,OAAO,GACP,gBAAgB,GAChB,OAAO,KACR,OAAO,CAAC;IACb,iBAAiB,EAAE,CACjB,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,kBAAkB,GAAG,QAAQ,EAC9D,YAAY,CAAC,EAAE,MAAM,KAClB,OAAO,CAAC;IACb,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,YAAY,KAAK,OAAO,CAAC;IACtF;;;;;;;;;;;;OAYG;IACH,sBAAsB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;CACpD;AAKD,wBAAgB,mBAAmB,IAAI,gBAAgB,GAAG,IAAI,CAc7D;AAED;;;;;;;GAOG;AACH,UAAU,gBAAgB;IACxB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;CACxC;AAKD,wBAAgB,mBAAmB,IAAI,gBAAgB,GAAG,IAAI,CAc7D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../../../src/primitives/portal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAsCf,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAgBnE;AAUD,4EAA4E;AAC5E,wBAAgB,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../../../src/primitives/portal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAsCf,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAgBnE;AAUD,4EAA4E;AAC5E,wBAAgB,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,QAwB3D;AAED,wDAAwD;AACxD,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAehE;AAED,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAOvE;AAED,qDAAqD;AACrD,wBAAgB,uBAAuB,IAAI,OAAO,CAGjD;AAkBD,mFAAmF;AACnF,wBAAgB,UAAU,gCAWzB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "panelui-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.83.0",
|
|
4
4
|
"description": "High-performance React Native UI components for Expo. Semantic design tokens, powered by Uniwind (Tailwind v4) and Reanimated.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"module": "./lib/module/index.js",
|
|
@@ -149,8 +149,9 @@
|
|
|
149
149
|
"uniwind": ">=1.0.0"
|
|
150
150
|
},
|
|
151
151
|
"dependencies": {
|
|
152
|
+
"@hugeicons/core-free-icons": "^4.3.0",
|
|
153
|
+
"@hugeicons/react-native": "^1.0.16",
|
|
152
154
|
"clsx": "^2.1.1",
|
|
153
|
-
"lucide-react-native": "^1.28.0",
|
|
154
155
|
"tailwind-merge": "^3.3.1",
|
|
155
156
|
"tailwind-variants": "^3.2.2"
|
|
156
157
|
},
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
useContext,
|
|
7
7
|
useEffect,
|
|
8
8
|
useMemo,
|
|
9
|
+
useRef,
|
|
9
10
|
useState,
|
|
10
11
|
type ComponentProps,
|
|
11
12
|
type ReactElement,
|
|
@@ -23,10 +24,9 @@ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
|
23
24
|
import Animated, {
|
|
24
25
|
Extrapolation,
|
|
25
26
|
FadeIn,
|
|
27
|
+
cancelAnimation,
|
|
26
28
|
interpolate,
|
|
27
|
-
FadeOut,
|
|
28
29
|
SlideInDown,
|
|
29
|
-
SlideOutDown,
|
|
30
30
|
runOnJS,
|
|
31
31
|
useAnimatedScrollHandler,
|
|
32
32
|
useAnimatedStyle,
|
|
@@ -38,7 +38,7 @@ import Animated, {
|
|
|
38
38
|
} from 'react-native-reanimated';
|
|
39
39
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
40
40
|
import { tv } from 'tailwind-variants';
|
|
41
|
-
import { getNativeUI } from '../../native';
|
|
41
|
+
import { getComposeModifiers, getNativeUI, getSwiftUIModifiers } from '../../native';
|
|
42
42
|
import { XIcon } from '../../icons';
|
|
43
43
|
import { ModalPortal } from '../../primitives/portal';
|
|
44
44
|
import { Scrim } from '../../primitives/scrim';
|
|
@@ -80,10 +80,6 @@ const DISMISS_VELOCITY = 800;
|
|
|
80
80
|
* is a new object every commit, and the sheet re-renders while it is open.
|
|
81
81
|
*/
|
|
82
82
|
const ENTERING = SlideInDown.springify().dampingRatio(0.9).duration(420);
|
|
83
|
-
// Leaving is quicker than arriving, but not by much: the sheet is still
|
|
84
|
-
// crossing the whole screen, and a surface that big snapping out of existence
|
|
85
|
-
// is more startling than the delay it saves is worth.
|
|
86
|
-
const EXITING = SlideOutDown.springify().dampingRatio(1).duration(340);
|
|
87
83
|
|
|
88
84
|
/**
|
|
89
85
|
* Where a flick would come to rest if it kept decelerating — Apple's
|
|
@@ -153,6 +149,22 @@ export interface BottomSheetProps {
|
|
|
153
149
|
* nearest of `half` / `full`.
|
|
154
150
|
*/
|
|
155
151
|
snapPoints?: ('half' | 'full' | { fraction: number } | { height: number })[];
|
|
152
|
+
/**
|
|
153
|
+
* Paint the native sheet a solid colour instead of the material the platform
|
|
154
|
+
* draws it in by default.
|
|
155
|
+
*
|
|
156
|
+
* The platform's sheet is translucent — on iOS 26 that is Liquid Glass — and
|
|
157
|
+
* what is behind it shows through. That is right for a sheet laid over
|
|
158
|
+
* content worth glimpsing and wrong for one that is a surface of the app's
|
|
159
|
+
* own, where the app's ground shifting under it reads as a mistake.
|
|
160
|
+
*
|
|
161
|
+
* `true` uses the theme's popover surface, so the sheet matches the rest of
|
|
162
|
+
* the app in both schemes. A string paints that colour exactly.
|
|
163
|
+
*
|
|
164
|
+
* It only reaches the platform's sheet, so it does nothing without `native`.
|
|
165
|
+
* On iOS below 16.4 the sheet keeps its material.
|
|
166
|
+
*/
|
|
167
|
+
nativeBackground?: boolean | string;
|
|
156
168
|
}
|
|
157
169
|
|
|
158
170
|
/**
|
|
@@ -162,6 +174,22 @@ export interface BottomSheetProps {
|
|
|
162
174
|
*/
|
|
163
175
|
const DETENT_FRACTION = { half: 0.5, full: 0.9 } as const;
|
|
164
176
|
|
|
177
|
+
/**
|
|
178
|
+
* How long to treat a dismissal *we* asked for as still in flight.
|
|
179
|
+
*
|
|
180
|
+
* A timer, because there is nothing to wait on. The platform reports a
|
|
181
|
+
* dismissal the reader performed — that report is the binding writing its new
|
|
182
|
+
* value back — but not one we asked for, where the binding already holds the
|
|
183
|
+
* value it would have written and the change is suppressed at the source. So
|
|
184
|
+
* the only sheet whose departure can be observed is the one we did not ask to
|
|
185
|
+
* leave.
|
|
186
|
+
*
|
|
187
|
+
* Comfortably past the system's own sheet transition, since the cost of being
|
|
188
|
+
* late is a present held a little longer than it needed to be, and the cost of
|
|
189
|
+
* being early is the platform dropping it and no sheet at all.
|
|
190
|
+
*/
|
|
191
|
+
const NATIVE_DISMISS_MS = 400;
|
|
192
|
+
|
|
165
193
|
/**
|
|
166
194
|
* The height the content should at least fill for a given set of detents.
|
|
167
195
|
*
|
|
@@ -195,6 +223,29 @@ export function bottomSheetDetentHeight(
|
|
|
195
223
|
return undefined;
|
|
196
224
|
}
|
|
197
225
|
|
|
226
|
+
/**
|
|
227
|
+
* The modifier that paints a native sheet's own surface, for whichever toolkit
|
|
228
|
+
* is drawing it — or nothing, where neither is reachable.
|
|
229
|
+
*
|
|
230
|
+
* A sheet's surface is not the background of anything hosted in it. The
|
|
231
|
+
* grabber's strip at the top and the safe-area inset at the bottom are the
|
|
232
|
+
* sheet's own chrome, and a colour put behind the content stops short of both
|
|
233
|
+
* of them — so the sheet arrives two-tone, and shifts as it moves between
|
|
234
|
+
* detents. These two reach the surface itself.
|
|
235
|
+
*
|
|
236
|
+
* The toolkits do not share a vocabulary, so the question is asked of each in
|
|
237
|
+
* its own terms rather than one answer being sent to both.
|
|
238
|
+
*/
|
|
239
|
+
function nativeSheetSurface(color: string): unknown[] | undefined {
|
|
240
|
+
const swiftUI = getSwiftUIModifiers();
|
|
241
|
+
if (swiftUI) return [swiftUI.presentationBackground(color)];
|
|
242
|
+
|
|
243
|
+
const compose = getComposeModifiers();
|
|
244
|
+
if (compose) return [compose.background(color)];
|
|
245
|
+
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
|
|
198
249
|
/**
|
|
199
250
|
* Set by the root so Content knows the platform is drawing the sheet, and with
|
|
200
251
|
* which detents. Null means the styled sheet renders.
|
|
@@ -202,6 +253,7 @@ export function bottomSheetDetentHeight(
|
|
|
202
253
|
const NativeSheetContext = createContext<{
|
|
203
254
|
nativeUI: NonNullable<ReturnType<typeof getNativeUI>>;
|
|
204
255
|
snapPoints: BottomSheetProps['snapPoints'];
|
|
256
|
+
background: BottomSheetProps['nativeBackground'];
|
|
205
257
|
} | null>(null);
|
|
206
258
|
|
|
207
259
|
/**
|
|
@@ -229,6 +281,7 @@ function BottomSheetRoot({
|
|
|
229
281
|
defaultOpen = false,
|
|
230
282
|
native,
|
|
231
283
|
snapPoints,
|
|
284
|
+
nativeBackground,
|
|
232
285
|
}: BottomSheetProps) {
|
|
233
286
|
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
234
287
|
const isControlled = open !== undefined;
|
|
@@ -249,8 +302,8 @@ function BottomSheetRoot({
|
|
|
249
302
|
|
|
250
303
|
const nativeUI = native ? getNativeUI() : null;
|
|
251
304
|
const nativeSheet = useMemo(
|
|
252
|
-
() => (nativeUI ? { nativeUI, snapPoints } : null),
|
|
253
|
-
[nativeUI, snapPoints]
|
|
305
|
+
() => (nativeUI ? { nativeUI, snapPoints, background: nativeBackground } : null),
|
|
306
|
+
[nativeBackground, nativeUI, snapPoints]
|
|
254
307
|
);
|
|
255
308
|
|
|
256
309
|
return (
|
|
@@ -356,6 +409,10 @@ function BottomSheetContent({
|
|
|
356
409
|
const insets = useSafeAreaInsets();
|
|
357
410
|
const translateY = useSharedValue(0);
|
|
358
411
|
const closeTint = useCSSVariable('--color-muted-foreground');
|
|
412
|
+
// Read unconditionally, used only by the native branch: the platform draws
|
|
413
|
+
// that sheet's container, so a token can only reach it as a colour handed
|
|
414
|
+
// over, never as a class.
|
|
415
|
+
const popoverSurface = useCSSVariable('--color-popover');
|
|
359
416
|
|
|
360
417
|
/*
|
|
361
418
|
* The scrolling body's gesture, if there is one. It is built here rather
|
|
@@ -375,15 +432,143 @@ function BottomSheetContent({
|
|
|
375
432
|
useBackHandler(open && dismissible, close);
|
|
376
433
|
|
|
377
434
|
/*
|
|
378
|
-
* The
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
435
|
+
* The sheet outlives `open`, and this is what keeps it alive.
|
|
436
|
+
*
|
|
437
|
+
* It used to be removed from the tree in the same commit `open` flipped,
|
|
438
|
+
* while an `exiting` layout animation was still declared on it. Reanimated
|
|
439
|
+
* then held the detached views until that spring settled — and a `duration`
|
|
440
|
+
* on a `springify()` animation is a target rather than a stop, so how long
|
|
441
|
+
* that took was not something this file decided. Reopening inside that window
|
|
442
|
+
* put a second sheet on screen with nothing coordinating the two.
|
|
443
|
+
*
|
|
444
|
+
* Now one animation owns the whole travel. Closing springs the sheet down and
|
|
445
|
+
* clears `presented` from the spring's own completion callback, so the tree
|
|
446
|
+
* comes down *after* the movement rather than during it. The backdrop is
|
|
447
|
+
* derived from the same value, so it goes with it.
|
|
448
|
+
*
|
|
449
|
+
* Reopening mid-exit is the case this really fixes: `presented` is still
|
|
450
|
+
* true, so nothing remounts and nothing new is scheduled — the spring is
|
|
451
|
+
* cancelled and the same sheet is caught on its way down.
|
|
452
|
+
*/
|
|
453
|
+
const [presented, setPresented] = useState(open);
|
|
454
|
+
|
|
455
|
+
useEffect(() => {
|
|
456
|
+
if (open) {
|
|
457
|
+
cancelAnimation(translateY);
|
|
458
|
+
if (!presented) {
|
|
459
|
+
// A fresh mount starts at rest and ENTERING does the travel. Catching
|
|
460
|
+
// one that is still on screen must not reset it under the finger, so
|
|
461
|
+
// this only runs when the sheet had actually gone.
|
|
462
|
+
translateY.value = 0;
|
|
463
|
+
setPresented(true);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
translateY.value = reducedMotion
|
|
467
|
+
? withTiming(0, { duration: 150 })
|
|
468
|
+
: withSpring(0, SPRING);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (!presented) return;
|
|
473
|
+
|
|
474
|
+
const land = (finished?: boolean) => {
|
|
475
|
+
'worklet';
|
|
476
|
+
if (finished) runOnJS(setPresented)(false);
|
|
477
|
+
};
|
|
478
|
+
translateY.value = reducedMotion
|
|
479
|
+
? withTiming(screenHeight, { duration: 150 }, land)
|
|
480
|
+
: withSpring(screenHeight, EXIT_SPRING, land);
|
|
481
|
+
}, [open, presented, reducedMotion, screenHeight, translateY]);
|
|
482
|
+
|
|
483
|
+
/*
|
|
484
|
+
* The native sheet's own presentation queue, which the platform does not have.
|
|
485
|
+
*
|
|
486
|
+
* UIKit refuses to present a sheet while the previous one is still being
|
|
487
|
+
* dismissed, and the request is dropped rather than queued — so a close
|
|
488
|
+
* followed immediately by an open leaves nothing on screen, and the reader
|
|
489
|
+
* sees a sheet that "took a moment" or never came at all.
|
|
490
|
+
*
|
|
491
|
+
* `isPresented` is therefore driven from here rather than straight from
|
|
492
|
+
* `open`: a present that arrives during a dismissal is held, and let through
|
|
493
|
+
* once that dismissal is over.
|
|
494
|
+
*
|
|
495
|
+
* Knowing when it is over is the hard half, because it depends on who asked.
|
|
496
|
+
* A dismissal the reader performed is reported — that report is the platform
|
|
497
|
+
* writing the new value back to us. One we asked for is not: the value is
|
|
498
|
+
* already what the platform would have written, so the change is suppressed
|
|
499
|
+
* at the source and no report is ever sent. A queue that waits for one
|
|
500
|
+
* regardless waits for ever, and every present after the first is held —
|
|
501
|
+
* which is a sheet that opens once and then never again, whether the flag was
|
|
502
|
+
* raised by the reader's swipe or by a Close button inside the sheet.
|
|
503
|
+
*
|
|
504
|
+
* So the two are ended by their own means: the reader's by the report, ours
|
|
505
|
+
* by {@link NATIVE_DISMISS_MS} — and by the report as well, if one turns up,
|
|
506
|
+
* since arriving early is only ever an improvement.
|
|
383
507
|
*/
|
|
508
|
+
const [nativePresented, setNativePresented] = useState(open);
|
|
509
|
+
const nativeOnScreen = useRef(open);
|
|
510
|
+
const nativeDismissing = useRef(false);
|
|
511
|
+
const nativeDismissTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
512
|
+
const openRef = useRef(open);
|
|
513
|
+
openRef.current = open;
|
|
514
|
+
|
|
515
|
+
/** The dismissal is over: let a held present through, if one is waiting. */
|
|
516
|
+
const endNativeDismissal = useCallback(() => {
|
|
517
|
+
if (nativeDismissTimer.current !== null) {
|
|
518
|
+
clearTimeout(nativeDismissTimer.current);
|
|
519
|
+
nativeDismissTimer.current = null;
|
|
520
|
+
}
|
|
521
|
+
if (!nativeDismissing.current) return;
|
|
522
|
+
nativeDismissing.current = false;
|
|
523
|
+
|
|
524
|
+
if (openRef.current) {
|
|
525
|
+
nativeOnScreen.current = true;
|
|
526
|
+
setNativePresented(true);
|
|
527
|
+
}
|
|
528
|
+
}, []);
|
|
529
|
+
|
|
384
530
|
useEffect(() => {
|
|
385
|
-
if (
|
|
386
|
-
|
|
531
|
+
if (!nativeSheet) return;
|
|
532
|
+
|
|
533
|
+
if (open) {
|
|
534
|
+
if (nativeDismissing.current) return;
|
|
535
|
+
nativeOnScreen.current = true;
|
|
536
|
+
setNativePresented(true);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Only a sheet the platform still has on screen can be in the middle of
|
|
541
|
+
// leaving. One the reader already swiped away has reported itself gone.
|
|
542
|
+
if (nativeOnScreen.current) {
|
|
543
|
+
nativeOnScreen.current = false;
|
|
544
|
+
nativeDismissing.current = true;
|
|
545
|
+
if (nativeDismissTimer.current !== null) clearTimeout(nativeDismissTimer.current);
|
|
546
|
+
nativeDismissTimer.current = setTimeout(endNativeDismissal, NATIVE_DISMISS_MS);
|
|
547
|
+
}
|
|
548
|
+
setNativePresented(false);
|
|
549
|
+
}, [endNativeDismissal, nativeSheet, open]);
|
|
550
|
+
|
|
551
|
+
useEffect(
|
|
552
|
+
() => () => {
|
|
553
|
+
if (nativeDismissTimer.current !== null) clearTimeout(nativeDismissTimer.current);
|
|
554
|
+
},
|
|
555
|
+
[]
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
const onNativeDismiss = useCallback(() => {
|
|
559
|
+
// The platform reporting that its sheet has gone. Whatever asked for it,
|
|
560
|
+
// there is nothing on screen now.
|
|
561
|
+
nativeOnScreen.current = false;
|
|
562
|
+
|
|
563
|
+
// Ours, or the reader's? A dismissal we asked for hands the queue back
|
|
564
|
+
// ahead of its window; one the reader performed is a close to report.
|
|
565
|
+
if (nativeDismissing.current) {
|
|
566
|
+
endNativeDismissal();
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (dismissible) close();
|
|
571
|
+
}, [close, dismissible, endNativeDismissal]);
|
|
387
572
|
|
|
388
573
|
const pan = useMemo(
|
|
389
574
|
() =>
|
|
@@ -492,6 +677,22 @@ function BottomSheetContent({
|
|
|
492
677
|
*/
|
|
493
678
|
const snapPoints =
|
|
494
679
|
nativeSheet.snapPoints ?? (size === 'auto' ? undefined : [size]);
|
|
680
|
+
/*
|
|
681
|
+
* `true` means the theme's surface, and a string means itself. A token
|
|
682
|
+
* that did not resolve is dropped rather than passed on — an unresolved
|
|
683
|
+
* colour reaching the platform is a sheet painted some default, which is
|
|
684
|
+
* further from the material than leaving the material alone.
|
|
685
|
+
*/
|
|
686
|
+
const requested = nativeSheet.background;
|
|
687
|
+
const surface =
|
|
688
|
+
requested === true
|
|
689
|
+
? typeof popoverSurface === 'string'
|
|
690
|
+
? popoverSurface
|
|
691
|
+
: undefined
|
|
692
|
+
: typeof requested === 'string'
|
|
693
|
+
? requested
|
|
694
|
+
: undefined;
|
|
695
|
+
const modifiers = surface ? nativeSheetSurface(surface) : undefined;
|
|
495
696
|
// The platform owns presentation, so this stays mounted and toggles
|
|
496
697
|
// isPresented rather than unmounting on close.
|
|
497
698
|
//
|
|
@@ -501,9 +702,10 @@ function BottomSheetContent({
|
|
|
501
702
|
return (
|
|
502
703
|
<Host matchContents ignoreSafeArea="keyboard" style={{ position: 'absolute' }}>
|
|
503
704
|
<NativeBottomSheet
|
|
504
|
-
isPresented={
|
|
505
|
-
onDismiss={
|
|
705
|
+
isPresented={nativePresented}
|
|
706
|
+
onDismiss={onNativeDismiss}
|
|
506
707
|
snapPoints={snapPoints}
|
|
708
|
+
modifiers={modifiers}
|
|
507
709
|
>
|
|
508
710
|
<RNHostView matchContents>
|
|
509
711
|
<BottomSheetContext.Provider value={context}>
|
|
@@ -536,7 +738,9 @@ function BottomSheetContent({
|
|
|
536
738
|
);
|
|
537
739
|
}
|
|
538
740
|
|
|
539
|
-
|
|
741
|
+
// `presented`, not `open`: the sheet stays in the tree until its own exit
|
|
742
|
+
// animation has finished putting it away.
|
|
743
|
+
if (!presented) return null;
|
|
540
744
|
|
|
541
745
|
return (
|
|
542
746
|
<ModalPortal>
|
|
@@ -571,7 +775,6 @@ function BottomSheetContent({
|
|
|
571
775
|
* setting is about.
|
|
572
776
|
*/
|
|
573
777
|
entering={reducedMotion ? FadeIn.duration(150) : ENTERING}
|
|
574
|
-
exiting={reducedMotion ? FadeOut.duration(150) : EXITING}
|
|
575
778
|
accessibilityViewIsModal
|
|
576
779
|
className={sheetVariants({ detached, className })}
|
|
577
780
|
{...props}
|
|
@@ -63,6 +63,12 @@ const buttonVariants = tv({
|
|
|
63
63
|
sm: { root: 'min-h-9 min-w-9 gap-1.5 px-2.5 py-2', label: 'text-[14px]' },
|
|
64
64
|
md: { root: 'min-h-11 min-w-11 px-4 py-2.5', label: 'text-[16px]' },
|
|
65
65
|
lg: { root: 'min-h-12 px-6 py-2.5', label: 'text-[18px]' },
|
|
66
|
+
// A step above `lg`, for the one control a screen is built around — a
|
|
67
|
+
// compose button in a navigation panel, a primary action alone at the
|
|
68
|
+
// bottom of a sheet. It maps onto the platform's own extra-large control
|
|
69
|
+
// size under `native`, which is the size it exists to reach: below it,
|
|
70
|
+
// `large` is the biggest a platform button can be asked to be.
|
|
71
|
+
xl: { root: 'min-h-14 px-8 py-3', label: 'text-[18px]' },
|
|
66
72
|
// Icon-only controls have no scalable label, so their square is stable.
|
|
67
73
|
icon: { root: 'h-11 w-11 px-0' },
|
|
68
74
|
},
|
|
@@ -140,10 +146,10 @@ export function useButtonGroup(): ButtonGroupContextValue | null {
|
|
|
140
146
|
*/
|
|
141
147
|
const ATTACHED = 'rounded-none border-transparent shadow-none active:bg-accent';
|
|
142
148
|
|
|
143
|
-
const SPINNER_SIZE = { sm: 'sm', md: 'sm', lg: 'md', icon: 'sm' } as const;
|
|
149
|
+
const SPINNER_SIZE = { sm: 'sm', md: 'sm', lg: 'md', xl: 'md', icon: 'sm' } as const;
|
|
144
150
|
|
|
145
151
|
/** Extra room around the styled sizes so even the compact button reaches 48dp. */
|
|
146
|
-
const BUTTON_HIT_SLOP = { sm: 6, md: 2, lg: 0, icon: 2 } as const;
|
|
152
|
+
const BUTTON_HIT_SLOP = { sm: 6, md: 2, lg: 0, xl: 0, icon: 2 } as const;
|
|
147
153
|
|
|
148
154
|
/**
|
|
149
155
|
* The theme token each variant's content reads against. Icons in the content
|
|
@@ -239,6 +245,7 @@ const NATIVE_HEIGHT: Record<NonNullable<ButtonVariantProps['size']>, number> = {
|
|
|
239
245
|
sm: 36,
|
|
240
246
|
md: 44,
|
|
241
247
|
lg: 48,
|
|
248
|
+
xl: 56,
|
|
242
249
|
icon: 44,
|
|
243
250
|
};
|
|
244
251
|
|
|
@@ -283,6 +290,7 @@ const NATIVE_CONTROL_SIZE = {
|
|
|
283
290
|
sm: 'small',
|
|
284
291
|
md: 'regular',
|
|
285
292
|
lg: 'large',
|
|
293
|
+
xl: 'extraLarge',
|
|
286
294
|
icon: 'regular',
|
|
287
295
|
} as const;
|
|
288
296
|
|