@gryt/ui-native 0.8.1 → 0.9.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/dist/components/Sheet/Sheet.d.ts.map +1 -1
- package/dist/components/Sheet/Sheet.js +21 -7
- package/dist/components/Sheet/presentation.d.ts +27 -0
- package/dist/components/Sheet/presentation.d.ts.map +1 -0
- package/dist/components/Sheet/presentation.js +7 -0
- package/dist/components/Toast/Toast.d.ts +16 -0
- package/dist/components/Toast/Toast.d.ts.map +1 -1
- package/dist/components/Toast/Toast.js +36 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sheet.d.ts","sourceRoot":"","sources":["../../../src/components/Sheet/Sheet.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAAyB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAerF,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"Sheet.d.ts","sourceRoot":"","sources":["../../../src/components/Sheet/Sheet.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAAyB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAerF,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAI/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,EAAE,kBAAkB,+BAE7D;AAeD,MAAM,WAAW,UAAW,SAAQ,cAAc;IAChD;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IACjC,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,iBAAS,IAAI,CAAC,EACZ,UAAoB,EACpB,QAAQ,EACR,GAAG,SAAS,EACb,EAAE,UAAU,+BAqBZ;AAWD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED;;;;;GAKG;AACH,iBAAS,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,iBAAiB,+BAOtD;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,iBAAS,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,iBAAiB,+BAuNtD;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,iBAAS,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,eAAe,+BAOlD;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,iBAAS,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,eAAe,+BAiBlD;AAED,eAAO,MAAM,KAAK;;;;;CAA0D,CAAC"}
|
|
@@ -7,6 +7,7 @@ import { grytDrawerBleed } from "@gryt/theme";
|
|
|
7
7
|
import { durations, easeSpringTight } from "../../motion/index.js";
|
|
8
8
|
import { useOpenState } from "../../overlay/useOpenState.js";
|
|
9
9
|
import { useTheme } from "../../theme/index.js";
|
|
10
|
+
import { nextPresentation } from "./presentation.js";
|
|
10
11
|
/** Mount once, at the root. See the note above for why it is required. */
|
|
11
12
|
export function SheetProvider({ children }) {
|
|
12
13
|
return _jsx(BottomSheetModalProvider, { children: children });
|
|
@@ -87,17 +88,23 @@ function Content({ children, style }) {
|
|
|
87
88
|
* has never been presented takes it *out* of the provider's registry — after
|
|
88
89
|
* which `present()` is a no-op and the sheet never opens again. It looks like
|
|
89
90
|
* the open prop being ignored, and it is not: it is the close that ran first.
|
|
91
|
+
*
|
|
92
|
+
* **It has to be cleared by `onDismiss` as well**, which is the other way a
|
|
93
|
+
* sheet stops being presented and the one this missed. A flick down dismisses
|
|
94
|
+
* the modal itself and *then* tells React, so the effect that follows finds
|
|
95
|
+
* `presented` still true and calls `dismiss()` on a modal that is already
|
|
96
|
+
* gone — the same unregistering call the ref exists to prevent, arriving by
|
|
97
|
+
* the other door. Every sheet in the app so far was opened by a trigger and
|
|
98
|
+
* closed for good, so nothing noticed until something wanted one back.
|
|
90
99
|
*/
|
|
91
100
|
const presented = useRef(false);
|
|
92
101
|
useEffect(() => {
|
|
93
|
-
|
|
94
|
-
|
|
102
|
+
const next = nextPresentation(isOpen, presented.current);
|
|
103
|
+
presented.current = next.presented;
|
|
104
|
+
if (next.action === "present")
|
|
95
105
|
ref.current?.present();
|
|
96
|
-
|
|
97
|
-
else if (presented.current) {
|
|
98
|
-
presented.current = false;
|
|
106
|
+
else if (next.action === "dismiss")
|
|
99
107
|
ref.current?.dismiss();
|
|
100
|
-
}
|
|
101
108
|
}, [isOpen, ref]);
|
|
102
109
|
// The modal renders nothing until it is presented, so there is no invisible
|
|
103
110
|
// backdrop sitting over the screen eating taps while it is closed. That was
|
|
@@ -184,7 +191,14 @@ function Content({ children, style }) {
|
|
|
184
191
|
// React asking, so the state has to be told. Uncontrolled, this is what
|
|
185
192
|
// makes the next `present` work; controlled, it is how the parent finds
|
|
186
193
|
// out its sheet is gone.
|
|
187
|
-
|
|
194
|
+
//
|
|
195
|
+
// `presented` first, and not as a tidy-up: the modal has already
|
|
196
|
+
// dismissed itself by the time this runs, and leaving the ref true lets
|
|
197
|
+
// the effect below dismiss it a second time. See the note on the ref.
|
|
198
|
+
onDismiss: () => {
|
|
199
|
+
presented.current = false;
|
|
200
|
+
setOpen(false);
|
|
201
|
+
}, animationConfigs: animationConfigs, backdropComponent: renderBackdrop, backgroundComponent: renderBackground, handleIndicatorStyle: {
|
|
188
202
|
backgroundColor: theme.color.border,
|
|
189
203
|
width: 36,
|
|
190
204
|
height: 4,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a sheet should be presented, dismissed, or left alone.
|
|
3
|
+
*
|
|
4
|
+
* A three-line decision in its own file, for the reason `sliderValue.ts` and
|
|
5
|
+
* `placePopup.ts` are: it is the part a screenshot cannot check, and it is the
|
|
6
|
+
* part that has now been wrong twice.
|
|
7
|
+
*
|
|
8
|
+
* The rule the whole thing exists for is that **`dismiss()` must only be
|
|
9
|
+
* called on a modal that is actually presented**. `@gorhom/bottom-sheet`'s
|
|
10
|
+
* provider keeps a registry of presented sheets, and dismissing one that is not
|
|
11
|
+
* in it takes it out — after which `present()` is a no-op and the sheet never
|
|
12
|
+
* opens again. It looks exactly like the `open` prop being ignored.
|
|
13
|
+
*
|
|
14
|
+
* There are two ways to end up asking. One is a sheet that has never been on
|
|
15
|
+
* screen: it runs its effect once on mount with `open` false, and without this
|
|
16
|
+
* it would dismiss a modal the provider has never heard of. The other is a
|
|
17
|
+
* flick down, which dismisses the modal itself and *then* tells React — so the
|
|
18
|
+
* effect that follows would dismiss it a second time.
|
|
19
|
+
*/
|
|
20
|
+
export type SheetAction = "present" | "dismiss" | "none";
|
|
21
|
+
export interface SheetPresentation {
|
|
22
|
+
action: SheetAction;
|
|
23
|
+
/** What `presented` becomes. The caller stores it. */
|
|
24
|
+
presented: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare function nextPresentation(open: boolean, presented: boolean): SheetPresentation;
|
|
27
|
+
//# sourceMappingURL=presentation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentation.d.ts","sourceRoot":"","sources":["../../../src/components/Sheet/presentation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAEzD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,WAAW,CAAC;IACpB,sDAAsD;IACtD,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,iBAAiB,CAIrF"}
|
|
@@ -16,9 +16,25 @@ interface ToastContextValue {
|
|
|
16
16
|
* Toasts need somewhere to live, which on the web is a portal at the document
|
|
17
17
|
* root and here is a provider near the top of the tree.
|
|
18
18
|
*
|
|
19
|
+
* **Mount it above everything else it has to cover.** The viewport is rendered
|
|
20
|
+
* as a sibling *after* `children`, so it paints over anything inside them —
|
|
21
|
+
* including a `Sheet`, whose content goes through `@gorhom/portal` to a host
|
|
22
|
+
* that is itself inside those children. Put `ToastProvider` below
|
|
23
|
+
* `SheetProvider` and the sheet wins instead, which is the wrong way round: a
|
|
24
|
+
* sheet is a surface you are working in, and a toast is the app telling you
|
|
25
|
+
* something happened while you work.
|
|
26
|
+
*
|
|
19
27
|
* They are deliberately not built on `Modal` like the dialogs are. A modal
|
|
20
28
|
* intercepts touches for the whole screen, and a toast that blocked the app
|
|
21
29
|
* until it faded would be worse than no toast.
|
|
30
|
+
*
|
|
31
|
+
* **What that costs, stated rather than discovered:** a `Modal` is a separate
|
|
32
|
+
* native window, so anything built on one — `Dialog`, `AlertDialog`, `Drawer`,
|
|
33
|
+
* and the platform's own `ActionSheetIOS` — draws *over* a toast, whatever the
|
|
34
|
+
* z-index says. React Native has no z-index across windows. A flow that raises
|
|
35
|
+
* a dialog and toasts at the same moment should toast after the dialog closes;
|
|
36
|
+
* there is nothing this component can do about it from inside the tree, and
|
|
37
|
+
* the alternative is a toast that can block the app, which is worse.
|
|
22
38
|
*/
|
|
23
39
|
export declare function ToastProvider({ children }: {
|
|
24
40
|
children?: ReactNode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../../src/components/Toast/Toast.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC5G,OAAO,EAAyB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../../src/components/Toast/Toast.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC5G,OAAO,EAAyB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AASrF,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAErE,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAMD,UAAU,iBAAiB;IACzB,IAAI,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,CAAC;IACtC,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAID;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,+BAoBnE;AAED,wBAAgB,QAAQ,IAAI,iBAAiB,CAI5C;AAyHD,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
3
3
|
import { Pressable, Text, View } from "react-native";
|
|
4
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
4
5
|
import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
5
6
|
import { grytScaleSteps } from "@gryt/theme";
|
|
6
7
|
import { useReducedMotion } from "../../hooks/useReducedMotion.js";
|
|
@@ -11,9 +12,25 @@ const ToastContext = createContext(null);
|
|
|
11
12
|
* Toasts need somewhere to live, which on the web is a portal at the document
|
|
12
13
|
* root and here is a provider near the top of the tree.
|
|
13
14
|
*
|
|
15
|
+
* **Mount it above everything else it has to cover.** The viewport is rendered
|
|
16
|
+
* as a sibling *after* `children`, so it paints over anything inside them —
|
|
17
|
+
* including a `Sheet`, whose content goes through `@gorhom/portal` to a host
|
|
18
|
+
* that is itself inside those children. Put `ToastProvider` below
|
|
19
|
+
* `SheetProvider` and the sheet wins instead, which is the wrong way round: a
|
|
20
|
+
* sheet is a surface you are working in, and a toast is the app telling you
|
|
21
|
+
* something happened while you work.
|
|
22
|
+
*
|
|
14
23
|
* They are deliberately not built on `Modal` like the dialogs are. A modal
|
|
15
24
|
* intercepts touches for the whole screen, and a toast that blocked the app
|
|
16
25
|
* until it faded would be worse than no toast.
|
|
26
|
+
*
|
|
27
|
+
* **What that costs, stated rather than discovered:** a `Modal` is a separate
|
|
28
|
+
* native window, so anything built on one — `Dialog`, `AlertDialog`, `Drawer`,
|
|
29
|
+
* and the platform's own `ActionSheetIOS` — draws *over* a toast, whatever the
|
|
30
|
+
* z-index says. React Native has no z-index across windows. A flow that raises
|
|
31
|
+
* a dialog and toasts at the same moment should toast after the dialog closes;
|
|
32
|
+
* there is nothing this component can do about it from inside the tree, and
|
|
33
|
+
* the alternative is a toast that can block the app, which is worse.
|
|
17
34
|
*/
|
|
18
35
|
export function ToastProvider({ children }) {
|
|
19
36
|
const [toasts, setToasts] = useState([]);
|
|
@@ -42,6 +59,7 @@ const RAMP = {
|
|
|
42
59
|
};
|
|
43
60
|
function Viewport({ toasts, onDismiss, }) {
|
|
44
61
|
const theme = useTheme();
|
|
62
|
+
const insets = useSafeAreaInsets();
|
|
45
63
|
if (toasts.length === 0)
|
|
46
64
|
return null;
|
|
47
65
|
return (_jsx(View
|
|
@@ -52,9 +70,26 @@ function Viewport({ toasts, onDismiss, }) {
|
|
|
52
70
|
position: "absolute",
|
|
53
71
|
left: 0,
|
|
54
72
|
right: 0,
|
|
55
|
-
|
|
73
|
+
/* The top, which on a phone is the only edge that is reliably free.
|
|
74
|
+
*
|
|
75
|
+
* The bottom is where a tab bar sits, where the home indicator sits,
|
|
76
|
+
* and where every sheet in the app rises from — so a toast there is
|
|
77
|
+
* either under the chrome or in the way of the gesture. It is also
|
|
78
|
+
* where iOS puts nothing of its own, precisely because that edge is
|
|
79
|
+
* the user's.
|
|
80
|
+
*
|
|
81
|
+
* Below the status bar rather than over it: the clock and the battery
|
|
82
|
+
* are not ours to cover, and a toast that starts under the notch reads
|
|
83
|
+
* as a system banner rather than as this app talking. */
|
|
84
|
+
top: insets.top + theme.space(2),
|
|
56
85
|
paddingHorizontal: theme.space(4),
|
|
57
86
|
gap: theme.space(2),
|
|
87
|
+
/* Explicit rather than relying on paint order. Within this tree the
|
|
88
|
+
* viewport is already last, but a caller can put something absolutely
|
|
89
|
+
* positioned after it — a floating bar, a call pill — and the toast
|
|
90
|
+
* has to win. `elevation` is the Android half of the same statement. */
|
|
91
|
+
zIndex: 1000,
|
|
92
|
+
elevation: 24,
|
|
58
93
|
}, children: toasts.map((toast) => (_jsx(ToastItem, { toast: toast, onDismiss: onDismiss }, toast.id))) }));
|
|
59
94
|
}
|
|
60
95
|
function ToastItem({ toast, onDismiss, }) {
|