@gryt/ui-native 0.3.1 → 0.4.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/AlertDialog/AlertDialog.d.ts.map +1 -1
- package/dist/components/Checkbox/Checkbox.d.ts +7 -1
- package/dist/components/Checkbox/Checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/Checkbox.js +47 -15
- package/dist/components/Dialog/Dialog.d.ts.map +1 -1
- package/dist/components/Dialog/Dialog.js +10 -1
- package/dist/components/Drawer/Drawer.d.ts.map +1 -1
- package/dist/components/Drawer/Drawer.js +28 -3
- package/dist/components/Radio/Radio.d.ts +9 -1
- package/dist/components/Radio/Radio.d.ts.map +1 -1
- package/dist/components/Radio/Radio.js +38 -14
- package/dist/components/ScrollArea/ScrollArea.d.ts +6 -4
- package/dist/components/ScrollArea/ScrollArea.d.ts.map +1 -1
- package/dist/components/ScrollArea/ScrollArea.js +19 -4
- package/dist/components/Sheet/Sheet.d.ts +90 -0
- package/dist/components/Sheet/Sheet.d.ts.map +1 -0
- package/dist/components/Sheet/Sheet.js +133 -0
- package/dist/components/Slider/Slider.d.ts.map +1 -1
- package/dist/components/Slider/Slider.js +33 -0
- package/dist/components/Switch/Switch.d.ts +4 -1
- package/dist/components/Switch/Switch.d.ts.map +1 -1
- package/dist/components/Switch/Switch.js +21 -17
- package/dist/components/Tabs/Tabs.d.ts +38 -13
- package/dist/components/Tabs/Tabs.d.ts.map +1 -1
- package/dist/components/Tabs/Tabs.js +157 -42
- package/dist/components/Toggle/Toggle.d.ts +0 -1
- package/dist/components/Toggle/Toggle.d.ts.map +1 -1
- package/dist/components/Toggle/Toggle.js +15 -1
- package/dist/components/internal/ControlRow.d.ts +54 -0
- package/dist/components/internal/ControlRow.d.ts.map +1 -0
- package/dist/components/internal/ControlRow.js +59 -0
- package/dist/components/internal/dragLock.d.ts +43 -0
- package/dist/components/internal/dragLock.d.ts.map +1 -0
- package/dist/components/internal/dragLock.js +18 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/package.json +9 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AlertDialog.d.ts","sourceRoot":"","sources":["../../../src/components/AlertDialog/AlertDialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEvF;;;;;;;;;;GAUG;AAIH,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;AAE1E,iBAAS,KAAK,CAAC,KAAK,EAAE,qBAAqB,+BAE1C;AAED,MAAM,MAAM,oBAAoB,GAAG,eAAe,CAAC;AAEnD,eAAO,MAAM,WAAW;;;;gBAUw0B,CAAC;;;;;
|
|
1
|
+
{"version":3,"file":"AlertDialog.d.ts","sourceRoot":"","sources":["../../../src/components/AlertDialog/AlertDialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEvF;;;;;;;;;;GAUG;AAIH,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;AAE1E,iBAAS,KAAK,CAAC,KAAK,EAAE,qBAAqB,+BAE1C;AAED,MAAM,MAAM,oBAAoB,GAAG,eAAe,CAAC;AAEnD,eAAO,MAAM,WAAW;;;;gBAUw0B,CAAC;;;;;gBAAovK,CAAC;aAAkB,CAAC;;;gBAAiS,CAAC;aAAkB,CAAC;;;gBAAkiB,CAAC;aAAkB,CAAC;;CADn9N,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
1
2
|
import { type StyleProp, type ViewStyle } from "react-native";
|
|
2
3
|
export type CheckboxTone = "primary" | "secondary" | "neutral" | "danger";
|
|
3
4
|
export interface CheckboxProps {
|
|
@@ -8,6 +9,11 @@ export interface CheckboxProps {
|
|
|
8
9
|
onCheckedChange?: (checked: boolean) => void;
|
|
9
10
|
disabled?: boolean;
|
|
10
11
|
tone?: CheckboxTone;
|
|
12
|
+
/**
|
|
13
|
+
* Tapping this toggles the box, which is what a `<label>` does on the web.
|
|
14
|
+
* See ControlRow for why it is a prop here rather than a wrapper element.
|
|
15
|
+
*/
|
|
16
|
+
label?: ReactNode;
|
|
11
17
|
style?: StyleProp<ViewStyle>;
|
|
12
18
|
accessibilityLabel?: string;
|
|
13
19
|
}
|
|
@@ -20,5 +26,5 @@ export interface CheckboxProps {
|
|
|
20
26
|
* react-native-svg on top. Both are decisions for whoever needs a full icon set,
|
|
21
27
|
* not for a checkbox.
|
|
22
28
|
*/
|
|
23
|
-
export declare function Checkbox({ checked: controlled, defaultChecked, indeterminate, onCheckedChange, disabled, tone, style, accessibilityLabel, }: CheckboxProps): import("react").JSX.Element;
|
|
29
|
+
export declare function Checkbox({ checked: controlled, defaultChecked, indeterminate, onCheckedChange, disabled, tone, label, style, accessibilityLabel, }: CheckboxProps): import("react").JSX.Element;
|
|
24
30
|
//# sourceMappingURL=Checkbox.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Checkbox.d.ts","sourceRoot":"","sources":["../../../src/components/Checkbox/Checkbox.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Checkbox.d.ts","sourceRoot":"","sources":["../../../src/components/Checkbox/Checkbox.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC5D,OAAO,EAAQ,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AASpE,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE1E,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,sEAAsE;IACtE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAID;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EAAE,UAAU,EACnB,cAAsB,EACtB,aAAqB,EACrB,eAAe,EACf,QAAQ,EACR,IAAgB,EAChB,KAAK,EACL,KAAK,EACL,kBAAkB,GACnB,EAAE,aAAa,+BAiFf"}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from "react";
|
|
3
|
-
import {
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Text } from "react-native";
|
|
4
|
+
import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
5
|
+
import { grytScaleSteps } from "@gryt/theme";
|
|
6
|
+
import { springy } from "../../motion/index.js";
|
|
4
7
|
import { toneRamp, useTheme } from "../../theme/index.js";
|
|
8
|
+
import { useReducedMotion } from "../../hooks/useReducedMotion.js";
|
|
9
|
+
import { ControlRow } from "../internal/ControlRow.js";
|
|
5
10
|
const SIZE = 20;
|
|
6
11
|
/**
|
|
7
12
|
* The tick is a text glyph rather than an icon.
|
|
@@ -12,32 +17,59 @@ const SIZE = 20;
|
|
|
12
17
|
* react-native-svg on top. Both are decisions for whoever needs a full icon set,
|
|
13
18
|
* not for a checkbox.
|
|
14
19
|
*/
|
|
15
|
-
export function Checkbox({ checked: controlled, defaultChecked = false, indeterminate = false, onCheckedChange, disabled, tone = "primary", style, accessibilityLabel, }) {
|
|
20
|
+
export function Checkbox({ checked: controlled, defaultChecked = false, indeterminate = false, onCheckedChange, disabled, tone = "primary", label, style, accessibilityLabel, }) {
|
|
16
21
|
const theme = useTheme();
|
|
22
|
+
const reducedMotion = useReducedMotion();
|
|
17
23
|
const [uncontrolled, setUncontrolled] = useState(defaultChecked);
|
|
18
24
|
const checked = controlled ?? uncontrolled;
|
|
19
25
|
const ramp = toneRamp(theme, tone);
|
|
20
26
|
const filled = checked || indeterminate;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
/**
|
|
28
|
+
* The tick scales from 0, not from something near 1.
|
|
29
|
+
*
|
|
30
|
+
* Straight from the web's comment, and it is not a detail: the spring's
|
|
31
|
+
* overshoot is a percentage of the travel, so 0 → 1 overshoots to 1.12 and
|
|
32
|
+
* visibly springs, while 0.95 → 1 overshoots by 0.006 and does nothing at
|
|
33
|
+
* all. It used to appear instantly here, which is the same bug with the
|
|
34
|
+
* travel set to zero.
|
|
35
|
+
*/
|
|
36
|
+
const tick = useSharedValue(filled ? 1 : 0);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const to = filled ? 1 : 0;
|
|
39
|
+
// eslint-disable-next-line react-hooks/immutability
|
|
40
|
+
tick.value = reducedMotion ? to : springy(to);
|
|
41
|
+
}, [filled, tick, reducedMotion]);
|
|
42
|
+
const tickStyle = useAnimatedStyle(() => ({
|
|
43
|
+
transform: [{ scale: tick.value }],
|
|
44
|
+
opacity: tick.value,
|
|
45
|
+
}));
|
|
46
|
+
return (_jsx(ControlRow, { label: label, onPress: () => {
|
|
25
47
|
const next = !checked;
|
|
26
48
|
if (controlled === undefined)
|
|
27
49
|
setUncontrolled(next);
|
|
28
50
|
onCheckedChange?.(next);
|
|
29
|
-
},
|
|
30
|
-
|
|
51
|
+
}, disabled: disabled, pressScale: grytScaleSteps.checkbox.press, accessibilityRole: "checkbox", accessibilityState: {
|
|
52
|
+
checked: indeterminate ? "mixed" : checked,
|
|
53
|
+
disabled: !!disabled,
|
|
54
|
+
}, accessibilityLabel: accessibilityLabel, style: style, children: _jsx(Animated.View, { style: {
|
|
31
55
|
width: SIZE,
|
|
32
56
|
height: SIZE,
|
|
33
57
|
borderRadius: theme.radius.sm,
|
|
34
58
|
alignItems: "center",
|
|
35
59
|
justifyContent: "center",
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
60
|
+
// The web keeps the border at 1 always and makes it transparent when
|
|
61
|
+
// checked, rather than dropping to 0 — a border that disappears
|
|
62
|
+
// changes the box's size mid-transition.
|
|
63
|
+
borderWidth: 1,
|
|
64
|
+
borderColor: filled ? "transparent" : theme.color.border,
|
|
65
|
+
// Was `transparent`, which is not what the web does: an unchecked box
|
|
66
|
+
// is a filled surface with an outline, not a hole.
|
|
67
|
+
backgroundColor: filled ? ramp[8] : theme.color.surfaceRaised,
|
|
39
68
|
opacity: disabled ? 0.5 : 1,
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
|
|
69
|
+
}, children: _jsx(Animated.View, { style: tickStyle, children: _jsx(Text, { style: {
|
|
70
|
+
color: theme.color.onAccent,
|
|
71
|
+
fontSize: 13,
|
|
72
|
+
fontWeight: "900",
|
|
73
|
+
lineHeight: 15,
|
|
74
|
+
}, children: indeterminate ? "–" : "✓" }) }) }) }));
|
|
43
75
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../../../src/components/Dialog/Dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,EAML,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AA2B/E,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,iBAAS,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,EAAE,EAAE,eAAe,+BAKxD;AAED,MAAM,WAAW,kBAAkB;IACjC,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,kBAAkB,+BAOvD;AAED;;;;;;GAMG;AACH,iBAAS,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,+BAErD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gEAAgE;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,iBAAS,KAAK,CAAC,EACb,QAAQ,EACR,KAAK,EACL,WAAkB,EAClB,UAAiB,GAClB,EAAE,gBAAgB,+
|
|
1
|
+
{"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../../../src/components/Dialog/Dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,EAML,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AA2B/E,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,iBAAS,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,EAAE,EAAE,eAAe,+BAKxD;AAED,MAAM,WAAW,kBAAkB;IACjC,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,kBAAkB,+BAOvD;AAED;;;;;;GAMG;AACH,iBAAS,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,+BAErD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gEAAgE;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,iBAAS,KAAK,CAAC,EACb,QAAQ,EACR,KAAK,EACL,WAAkB,EAClB,UAAiB,GAClB,EAAE,gBAAgB,+BA4GlB;AAYD;;;;;;GAMG;AACH,iBAAS,QAAQ,IAAI,IAAI,CAExB;AAED,iBAAS,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CAAE,+BAUzF;AAED,iBAAS,WAAW,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CAAE,+BAO/F;AAED,iBAAS,MAAM,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CAAE,+BAY1F;AAED,iBAAS,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CAAE,+BAOzF;AAED,eAAO,MAAM,MAAM;;;;;;;;;;CAUlB,CAAC"}
|
|
@@ -81,7 +81,16 @@ function Popup({ children, style, dismissible = true, scrollable = true, }) {
|
|
|
81
81
|
justifyContent: "center",
|
|
82
82
|
padding: theme.space(5),
|
|
83
83
|
backgroundColor: "rgba(0,0,0,0.6)",
|
|
84
|
-
}, children: _jsx(
|
|
84
|
+
}, children: _jsx(View, { onStartShouldSetResponder: claimTouch, style: { width: "100%", alignItems: "center", maxHeight: "80%" }, children: body }) }) }));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Claim a touch that no child wanted.
|
|
88
|
+
*
|
|
89
|
+
* Declared once rather than as an inline arrow, so every Dialog is not handing
|
|
90
|
+
* the responder system a new function identity on every render.
|
|
91
|
+
*/
|
|
92
|
+
function claimTouch() {
|
|
93
|
+
return true;
|
|
85
94
|
}
|
|
86
95
|
/**
|
|
87
96
|
* Not rendered.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Drawer.d.ts","sourceRoot":"","sources":["../../../src/components/Drawer/Drawer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"Drawer.d.ts","sourceRoot":"","sources":["../../../src/components/Drawer/Drawer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAStB,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG/E,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAgBrD,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,iBAAS,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,EAAE,EAAE,eAAe,+BAKxD;AAED,iBAAS,OAAO,CAAC,EACf,QAAQ,EACR,KAAK,EACN,EAAE;IACD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,+BAWA;AAED,iBAAS,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,+BAErD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,iBAAS,KAAK,CAAC,EACb,QAAQ,EACR,IAAa,EACb,IAAU,EACV,WAAkB,EAClB,KAAK,EACN,EAAE,gBAAgB,+BAqGlB;AAED,iBAAS,KAAK,CAAC,EACb,QAAQ,EACR,KAAK,EACN,EAAE;IACD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,+BAWA;AAED,eAAO,MAAM,MAAM;;;;;;CAA0C,CAAC"}
|
|
@@ -3,6 +3,7 @@ import { createContext, useContext, useEffect } from "react";
|
|
|
3
3
|
import { Dimensions, Modal, Pressable } from "react-native";
|
|
4
4
|
import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
5
5
|
import { useReducedMotion } from "../../hooks/useReducedMotion.js";
|
|
6
|
+
import { grytDrawerBleed } from "@gryt/theme";
|
|
6
7
|
import { durations, springy } from "../../motion/index.js";
|
|
7
8
|
import { useOpenState } from "../../overlay/useOpenState.js";
|
|
8
9
|
import { useTheme } from "../../theme/index.js";
|
|
@@ -39,7 +40,26 @@ function Popup({ children, side = "left", size = 0.8, dismissible = true, style
|
|
|
39
40
|
const vertical = side === "bottom";
|
|
40
41
|
const extent = vertical ? screen.height * size : screen.width * size;
|
|
41
42
|
const progress = useSharedValue(0);
|
|
42
|
-
|
|
43
|
+
/**
|
|
44
|
+
* The panel is built `grytDrawerBleed` larger than it needs to be and hangs
|
|
45
|
+
* that much off-screen, with matching padding so the content sits where it
|
|
46
|
+
* would have.
|
|
47
|
+
*
|
|
48
|
+
* The spring overshoots — that is what makes it a spring — and it settles
|
|
49
|
+
* onto its target from both directions. A panel sized exactly to its resting
|
|
50
|
+
* place therefore shows a seam of backdrop down its edge on the undershoot,
|
|
51
|
+
* for a frame or two, every time it opens. The web has had this since the
|
|
52
|
+
* Drawer was written, as `--gryt-drawer-bleed`; this side did not.
|
|
53
|
+
*
|
|
54
|
+
* `grytDrawerBleed` rather than a local 64, and `bleedTokens.test.ts` in
|
|
55
|
+
* @gryt/ui keeps it equal to what theme.css says. Two overhangs that must
|
|
56
|
+
* match is how they stop matching.
|
|
57
|
+
*/
|
|
58
|
+
const bleed = grytDrawerBleed;
|
|
59
|
+
const panelExtent = extent + bleed;
|
|
60
|
+
// Measured against the bleed panel, so the panel's *visible* edge lands
|
|
61
|
+
// exactly off-screen rather than the overhang doing it.
|
|
62
|
+
const hidden = side === "right" ? panelExtent : side === "left" ? -panelExtent : panelExtent;
|
|
43
63
|
// 700ms, the web's `--gryt-dur-spring-soft`, on the overshooting curve —
|
|
44
64
|
// which is what the web uses here despite the panel travelling its own
|
|
45
65
|
// width. Matching it rather than substituting the tight curve, because the
|
|
@@ -68,8 +88,13 @@ function Popup({ children, side = "left", size = 0.8, dismissible = true, style
|
|
|
68
88
|
bottom: 0,
|
|
69
89
|
left: side === "right" ? undefined : 0,
|
|
70
90
|
right: side === "left" ? undefined : 0,
|
|
71
|
-
width: vertical ? "100%" :
|
|
72
|
-
height: vertical ?
|
|
91
|
+
width: vertical ? "100%" : panelExtent,
|
|
92
|
+
height: vertical ? panelExtent : "100%",
|
|
93
|
+
// The overhang, and the padding that puts the content back where
|
|
94
|
+
// it would have been without it.
|
|
95
|
+
paddingLeft: side === "right" ? bleed : 0,
|
|
96
|
+
paddingRight: side === "left" ? bleed : 0,
|
|
97
|
+
paddingBottom: vertical ? bleed : 0,
|
|
73
98
|
backgroundColor: theme.color.surface,
|
|
74
99
|
borderColor: theme.color.border,
|
|
75
100
|
borderRightWidth: side === "left" ? 1 : 0,
|
|
@@ -21,8 +21,16 @@ export interface RadioProps {
|
|
|
21
21
|
value: string | number;
|
|
22
22
|
disabled?: boolean;
|
|
23
23
|
tone?: RadioTone;
|
|
24
|
+
/**
|
|
25
|
+
* Tapping this selects the radio, which is what a `<label>` does on the web.
|
|
26
|
+
*
|
|
27
|
+
* Radios need it more than anything else here: they come in stacks, so a
|
|
28
|
+
* 20pt target missed by a few points does not do nothing — it selects the
|
|
29
|
+
* neighbour, which is worse than no response at all.
|
|
30
|
+
*/
|
|
31
|
+
label?: ReactNode;
|
|
24
32
|
style?: StyleProp<ViewStyle>;
|
|
25
33
|
accessibilityLabel?: string;
|
|
26
34
|
}
|
|
27
|
-
export declare function Radio({ value, disabled, tone, style, accessibilityLabel, }: RadioProps): import("react").JSX.Element;
|
|
35
|
+
export declare function Radio({ value, disabled, tone, label, style, accessibilityLabel, }: RadioProps): import("react").JSX.Element;
|
|
28
36
|
//# sourceMappingURL=Radio.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Radio.d.ts","sourceRoot":"","sources":["../../../src/components/Radio/Radio.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"Radio.d.ts","sourceRoot":"","sources":["../../../src/components/Radio/Radio.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvF,OAAO,EAAQ,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AASpE,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAUvE,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EAAE,UAAU,EACjB,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,KAAK,GACN,EAAE,eAAe,+BAsBjB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAID,wBAAgB,KAAK,CAAC,EACpB,KAAK,EACL,QAAQ,EACR,IAAgB,EAChB,KAAK,EACL,KAAK,EACL,kBAAkB,GACnB,EAAE,UAAU,+BAoEZ"}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useState } from "react";
|
|
3
|
-
import {
|
|
2
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
3
|
+
import { View } from "react-native";
|
|
4
|
+
import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
5
|
+
import { grytScaleSteps } from "@gryt/theme";
|
|
6
|
+
import { useReducedMotion } from "../../hooks/useReducedMotion.js";
|
|
7
|
+
import { springy } from "../../motion/index.js";
|
|
4
8
|
import { toneRamp, useTheme } from "../../theme/index.js";
|
|
9
|
+
import { ControlRow } from "../internal/ControlRow.js";
|
|
5
10
|
const RadioGroupContext = createContext(null);
|
|
6
11
|
/**
|
|
7
12
|
* The group holds the value, which is the part a radio cannot do alone.
|
|
@@ -24,30 +29,49 @@ export function RadioGroup({ value: controlled, defaultValue, onValueChange, dis
|
|
|
24
29
|
}, children: _jsx(View, { accessibilityRole: "radiogroup", style: style, children: children }) }));
|
|
25
30
|
}
|
|
26
31
|
const SIZE = 20;
|
|
27
|
-
export function Radio({ value, disabled, tone = "primary", style, accessibilityLabel, }) {
|
|
32
|
+
export function Radio({ value, disabled, tone = "primary", label, style, accessibilityLabel, }) {
|
|
28
33
|
const theme = useTheme();
|
|
34
|
+
const reducedMotion = useReducedMotion();
|
|
29
35
|
const group = useContext(RadioGroupContext);
|
|
30
36
|
if (!group)
|
|
31
37
|
throw new Error("Radio must be rendered inside a RadioGroup.");
|
|
32
38
|
const selected = group.value === value;
|
|
33
39
|
const isDisabled = disabled || group.disabled;
|
|
34
40
|
const ramp = toneRamp(theme, tone);
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
// Scales from 0 for the same reason the checkbox tick does — see the comment
|
|
42
|
+
// there. The dot used to be mounted and unmounted, which is that transition
|
|
43
|
+
// with the duration set to nothing.
|
|
44
|
+
const dot = useSharedValue(selected ? 1 : 0);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const to = selected ? 1 : 0;
|
|
47
|
+
// eslint-disable-next-line react-hooks/immutability
|
|
48
|
+
dot.value = reducedMotion ? to : springy(to);
|
|
49
|
+
}, [selected, dot, reducedMotion]);
|
|
50
|
+
const dotStyle = useAnimatedStyle(() => ({
|
|
51
|
+
transform: [{ scale: dot.value }],
|
|
52
|
+
opacity: dot.value,
|
|
53
|
+
}));
|
|
54
|
+
return (_jsx(ControlRow, { label: label, onPress: () => group.setValue(value), disabled: isDisabled, pressScale: grytScaleSteps.radio.press, accessibilityRole: "radio", accessibilityState: { checked: selected, disabled: !!isDisabled }, accessibilityLabel: accessibilityLabel, style: style, children: _jsx(View, { style: {
|
|
37
55
|
width: SIZE,
|
|
38
56
|
height: SIZE,
|
|
39
57
|
borderRadius: SIZE / 2,
|
|
40
58
|
alignItems: "center",
|
|
41
59
|
justifyContent: "center",
|
|
42
|
-
|
|
60
|
+
// 1, matching the web. It was 1.5, which reads heavier than the
|
|
61
|
+
// checkbox sitting next to it in the same form.
|
|
62
|
+
borderWidth: 1,
|
|
43
63
|
borderColor: selected ? ramp[8] : theme.color.border,
|
|
64
|
+
// The web fills the circle and outlines it; only the dot is new when
|
|
65
|
+
// it is selected.
|
|
66
|
+
backgroundColor: theme.color.surfaceRaised,
|
|
44
67
|
opacity: isDisabled ? 0.5 : 1,
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
68
|
+
}, children: _jsx(Animated.View, { style: [
|
|
69
|
+
{
|
|
70
|
+
width: SIZE / 2,
|
|
71
|
+
height: SIZE / 2,
|
|
72
|
+
borderRadius: SIZE / 4,
|
|
73
|
+
backgroundColor: ramp[8],
|
|
74
|
+
},
|
|
75
|
+
dotStyle,
|
|
76
|
+
] }) }) }));
|
|
53
77
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
2
|
import { type ScrollViewProps, type StyleProp, type ViewStyle } from "react-native";
|
|
3
3
|
export interface ScrollAreaProps extends Omit<ScrollViewProps, "style"> {
|
|
4
4
|
children?: ReactNode;
|
|
@@ -15,8 +15,10 @@ export interface ScrollAreaProps extends Omit<ScrollViewProps, "style"> {
|
|
|
15
15
|
* device. So the component stays for call-site parity and does almost nothing,
|
|
16
16
|
* which is the honest version.
|
|
17
17
|
*
|
|
18
|
-
* Its one job
|
|
19
|
-
* native scrollbar too.
|
|
18
|
+
* Its one job was defaulting the indicator off, since the web version hides the
|
|
19
|
+
* native scrollbar too. It has a second now: it publishes a drag lock, so a
|
|
20
|
+
* control inside it can hold it still while it is being dragged. See
|
|
21
|
+
* internal/dragLock — a Slider is unusable in a scrolling screen without it.
|
|
20
22
|
*/
|
|
21
|
-
export declare function ScrollArea({ children, horizontal, style, contentStyle, showsVerticalScrollIndicator, showsHorizontalScrollIndicator, ...rest }: ScrollAreaProps): import("react").JSX.Element;
|
|
23
|
+
export declare function ScrollArea({ children, horizontal, style, contentStyle, showsVerticalScrollIndicator, showsHorizontalScrollIndicator, scrollEnabled, ...rest }: ScrollAreaProps): import("react").JSX.Element;
|
|
22
24
|
//# sourceMappingURL=ScrollArea.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScrollArea.d.ts","sourceRoot":"","sources":["../../../src/components/ScrollArea/ScrollArea.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"ScrollArea.d.ts","sourceRoot":"","sources":["../../../src/components/ScrollArea/ScrollArea.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvE,OAAO,EAAc,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAIhG,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC;IACrE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,YAAY,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CACrC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,UAAkB,EAClB,KAAK,EACL,YAAY,EACZ,4BAAoC,EACpC,8BAAsC,EACtC,aAAa,EACb,GAAG,IAAI,EACR,EAAE,eAAe,+BAgCjB"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2
3
|
import { ScrollView } from "react-native";
|
|
4
|
+
import { DragLockContext } from "../internal/dragLock.js";
|
|
3
5
|
/**
|
|
4
6
|
* A thin pass over ScrollView.
|
|
5
7
|
*
|
|
@@ -9,9 +11,22 @@ import { ScrollView } from "react-native";
|
|
|
9
11
|
* device. So the component stays for call-site parity and does almost nothing,
|
|
10
12
|
* which is the honest version.
|
|
11
13
|
*
|
|
12
|
-
* Its one job
|
|
13
|
-
* native scrollbar too.
|
|
14
|
+
* Its one job was defaulting the indicator off, since the web version hides the
|
|
15
|
+
* native scrollbar too. It has a second now: it publishes a drag lock, so a
|
|
16
|
+
* control inside it can hold it still while it is being dragged. See
|
|
17
|
+
* internal/dragLock — a Slider is unusable in a scrolling screen without it.
|
|
14
18
|
*/
|
|
15
|
-
export function ScrollArea({ children, horizontal = false, style, contentStyle, showsVerticalScrollIndicator = false, showsHorizontalScrollIndicator = false, ...rest }) {
|
|
16
|
-
|
|
19
|
+
export function ScrollArea({ children, horizontal = false, style, contentStyle, showsVerticalScrollIndicator = false, showsHorizontalScrollIndicator = false, scrollEnabled, ...rest }) {
|
|
20
|
+
const [locks, setLocks] = useState(0);
|
|
21
|
+
// Counted rather than a boolean. Two controls can overlap in principle — a
|
|
22
|
+
// drag interrupted by a second one starting — and the first to finish would
|
|
23
|
+
// otherwise unlock the list while the second is still going.
|
|
24
|
+
const lock = useCallback(() => setLocks((n) => n + 1), []);
|
|
25
|
+
const unlock = useCallback(() => setLocks((n) => Math.max(0, n - 1)), []);
|
|
26
|
+
const dragLock = useMemo(() => ({ locked: locks > 0, lock, unlock }), [locks, lock, unlock]);
|
|
27
|
+
return (_jsx(DragLockContext.Provider, { value: dragLock, children: _jsx(ScrollView, { horizontal: horizontal, style: style, contentContainerStyle: contentStyle, showsVerticalScrollIndicator: showsVerticalScrollIndicator, showsHorizontalScrollIndicator: showsHorizontalScrollIndicator,
|
|
28
|
+
// Only overridden while something is actually dragging, so a caller
|
|
29
|
+
// that passes `scrollEnabled={false}` still gets a list that never
|
|
30
|
+
// scrolls.
|
|
31
|
+
scrollEnabled: scrollEnabled === false ? false : locks === 0, ...rest, children: children }) }));
|
|
17
32
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
+
/**
|
|
4
|
+
* A bottom sheet, which is the phone's modal.
|
|
5
|
+
*
|
|
6
|
+
* `@gryt/ui` has no counterpart, and that is deliberate rather than an
|
|
7
|
+
* oversight to be corrected later. A sheet is what a phone does where the web
|
|
8
|
+
* opens a dialog or slides a drawer, and the two are not the same interaction:
|
|
9
|
+
* a sheet is dragged, it settles at heights the user chooses, and dismissing it
|
|
10
|
+
* is a flick rather than a click on an X. Shipping the web's Dialog on a phone
|
|
11
|
+
* would be 1:1 and wrong; inventing a sheet on the web would be worse. So this
|
|
12
|
+
* is an addition, and it is in the parity exceptions table as one.
|
|
13
|
+
*
|
|
14
|
+
* Built on `@gorhom/bottom-sheet` rather than by hand, which is the opposite of
|
|
15
|
+
* the call made for Slider and Tabs, and the reason is the shape of the
|
|
16
|
+
* problem. Those were shallow and Gryt-specific — a press scale is four lines
|
|
17
|
+
* and no library will give you the right one. A sheet is deep and generic:
|
|
18
|
+
* velocity-based dismiss, snap points, keyboard avoidance, a backdrop whose
|
|
19
|
+
* opacity tracks the drag, and the handoff between dragging the sheet and
|
|
20
|
+
* scrolling the content inside it. None of that is about Gryt and all of it is
|
|
21
|
+
* months of edge cases.
|
|
22
|
+
*
|
|
23
|
+
* The library supplies behaviour only. Every colour, radius and dimension below
|
|
24
|
+
* comes from the theme.
|
|
25
|
+
*
|
|
26
|
+
* **`SheetProvider` has to be mounted above anything that renders a Sheet**,
|
|
27
|
+
* next to `ToastProvider` and `TooltipProvider`. That is not ceremony: this is
|
|
28
|
+
* `BottomSheetModal`, not `BottomSheet`, and the difference is where the sheet
|
|
29
|
+
* is anchored.
|
|
30
|
+
*
|
|
31
|
+
* `BottomSheet` positions itself inside whatever container it is rendered in.
|
|
32
|
+
* Written inline in a screen, as this was first, it anchors to its slot in the
|
|
33
|
+
* layout — so it appeared partway down the page with the content behind it
|
|
34
|
+
* showing through, and read as entering from the top. `BottomSheetModal`
|
|
35
|
+
* portals to the provider instead, which sits at the app root, so it is always
|
|
36
|
+
* anchored to the bottom of the screen no matter where the call site is.
|
|
37
|
+
*/
|
|
38
|
+
export interface SheetProviderProps {
|
|
39
|
+
children?: ReactNode;
|
|
40
|
+
}
|
|
41
|
+
/** Mount once, at the root. See the note above for why it is required. */
|
|
42
|
+
export declare function SheetProvider({ children }: SheetProviderProps): import("react").JSX.Element;
|
|
43
|
+
export interface SheetProps {
|
|
44
|
+
/**
|
|
45
|
+
* Heights the sheet settles at, as percentages or points.
|
|
46
|
+
*
|
|
47
|
+
* The web has no equivalent concept — a dialog is one size — which is part of
|
|
48
|
+
* why this is its own component rather than a Drawer with a prop.
|
|
49
|
+
*/
|
|
50
|
+
snapPoints?: (string | number)[];
|
|
51
|
+
/** Starts open. Uncontrolled; use the Trigger for the usual case. */
|
|
52
|
+
defaultOpen?: boolean;
|
|
53
|
+
onOpenChange?: (open: boolean) => void;
|
|
54
|
+
children?: ReactNode;
|
|
55
|
+
}
|
|
56
|
+
declare function Root({ snapPoints, defaultOpen, onOpenChange, children, }: SheetProps): import("react").JSX.Element;
|
|
57
|
+
export interface SheetTriggerProps {
|
|
58
|
+
children?: ReactNode;
|
|
59
|
+
style?: StyleProp<ViewStyle>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A Pressable, like every other trigger in this package — which means its
|
|
63
|
+
* children have to be plain visual content. Nesting a Button inside one gives
|
|
64
|
+
* the inner pressable the touch and the sheet never opens, silently. Same
|
|
65
|
+
* footgun the overlay triggers already carry.
|
|
66
|
+
*/
|
|
67
|
+
declare function Trigger({ children, style }: SheetTriggerProps): import("react").JSX.Element;
|
|
68
|
+
export interface SheetContentProps {
|
|
69
|
+
children?: ReactNode;
|
|
70
|
+
style?: StyleProp<ViewStyle>;
|
|
71
|
+
}
|
|
72
|
+
declare function Content({ children, style }: SheetContentProps): import("react").JSX.Element;
|
|
73
|
+
export interface SheetCloseProps {
|
|
74
|
+
children?: ReactNode;
|
|
75
|
+
style?: StyleProp<ViewStyle>;
|
|
76
|
+
}
|
|
77
|
+
declare function Close({ children, style }: SheetCloseProps): import("react").JSX.Element;
|
|
78
|
+
export interface SheetTitleProps {
|
|
79
|
+
children?: ReactNode;
|
|
80
|
+
style?: StyleProp<ViewStyle>;
|
|
81
|
+
}
|
|
82
|
+
declare function Title({ children, style }: SheetTitleProps): import("react").JSX.Element;
|
|
83
|
+
export declare const Sheet: typeof Root & {
|
|
84
|
+
Trigger: typeof Trigger;
|
|
85
|
+
Content: typeof Content;
|
|
86
|
+
Close: typeof Close;
|
|
87
|
+
Title: typeof Title;
|
|
88
|
+
};
|
|
89
|
+
export {};
|
|
90
|
+
//# sourceMappingURL=Sheet.d.ts.map
|
|
@@ -0,0 +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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,UAAU;IACzB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IACjC,qEAAqE;IACrE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,iBAAS,IAAI,CAAC,EACZ,UAAoB,EACpB,WAAmB,EACnB,YAAY,EACZ,QAAQ,GACT,EAAE,UAAU,+BAyBZ;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,+BA+HtD;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"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, } from "react";
|
|
3
|
+
import { Pressable, Text, View } from "react-native";
|
|
4
|
+
import { BottomSheetBackdrop, BottomSheetModal, BottomSheetModalProvider, BottomSheetView, useBottomSheetTimingConfigs, } from "@gorhom/bottom-sheet";
|
|
5
|
+
import { grytDrawerBleed } from "@gryt/theme";
|
|
6
|
+
import { durations, easeSpring } from "../../motion/index.js";
|
|
7
|
+
import { useTheme } from "../../theme/index.js";
|
|
8
|
+
/** Mount once, at the root. See the note above for why it is required. */
|
|
9
|
+
export function SheetProvider({ children }) {
|
|
10
|
+
return _jsx(BottomSheetModalProvider, { children: children });
|
|
11
|
+
}
|
|
12
|
+
const SheetContext = createContext(null);
|
|
13
|
+
function useSheet(part) {
|
|
14
|
+
const value = useContext(SheetContext);
|
|
15
|
+
if (!value)
|
|
16
|
+
throw new Error(`Sheet.${part} must be rendered inside Sheet.`);
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
function Root({ snapPoints = ["50%"], defaultOpen = false, onOpenChange, children, }) {
|
|
20
|
+
const ref = useRef(null);
|
|
21
|
+
// `present` and `dismiss` rather than mounting and unmounting by hand: the
|
|
22
|
+
// modal is always rendered and the provider decides whether it is on screen,
|
|
23
|
+
// which is what removes the mount-then-snap-on-the-next-frame dance the
|
|
24
|
+
// inline version needed.
|
|
25
|
+
const open = useCallback(() => {
|
|
26
|
+
ref.current?.present();
|
|
27
|
+
onOpenChange?.(true);
|
|
28
|
+
}, [onOpenChange]);
|
|
29
|
+
const close = useCallback(() => {
|
|
30
|
+
ref.current?.dismiss();
|
|
31
|
+
}, []);
|
|
32
|
+
const context = useMemo(() => ({ open, close }), [open, close]);
|
|
33
|
+
return (_jsx(SheetContext.Provider, { value: context, children: _jsx(SheetRefContext.Provider, { value: { ref, defaultOpen, snapPoints, onOpenChange }, children: children }) }));
|
|
34
|
+
}
|
|
35
|
+
const SheetRefContext = createContext(null);
|
|
36
|
+
/**
|
|
37
|
+
* A Pressable, like every other trigger in this package — which means its
|
|
38
|
+
* children have to be plain visual content. Nesting a Button inside one gives
|
|
39
|
+
* the inner pressable the touch and the sheet never opens, silently. Same
|
|
40
|
+
* footgun the overlay triggers already carry.
|
|
41
|
+
*/
|
|
42
|
+
function Trigger({ children, style }) {
|
|
43
|
+
const sheet = useSheet("Trigger");
|
|
44
|
+
return (_jsx(Pressable, { onPress: sheet.open, style: style, children: children }));
|
|
45
|
+
}
|
|
46
|
+
function Content({ children, style }) {
|
|
47
|
+
const theme = useTheme();
|
|
48
|
+
const state = useContext(SheetRefContext);
|
|
49
|
+
// Read here, in the normal tree, and handed back down below — see the note
|
|
50
|
+
// on the provider inside the modal.
|
|
51
|
+
const sheet = useContext(SheetContext);
|
|
52
|
+
if (!state || !sheet) {
|
|
53
|
+
throw new Error("Sheet.Content must be rendered inside Sheet.");
|
|
54
|
+
}
|
|
55
|
+
const { ref, defaultOpen, snapPoints, onOpenChange } = state;
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (defaultOpen)
|
|
58
|
+
ref.current?.present();
|
|
59
|
+
}, [defaultOpen, ref]);
|
|
60
|
+
// The modal renders nothing until it is presented, so there is no invisible
|
|
61
|
+
// backdrop sitting over the screen eating taps while it is closed. That was
|
|
62
|
+
// a real hazard with the inline version and had to be handled by unmounting.
|
|
63
|
+
/**
|
|
64
|
+
* The sheet's own background, drawn here rather than left to `backgroundStyle`.
|
|
65
|
+
*
|
|
66
|
+
* Two things needed fixing and one prop could not do either.
|
|
67
|
+
*
|
|
68
|
+
* `backgroundStyle` paints a view sized exactly to the sheet, and the
|
|
69
|
+
* container `style` sits outside the rounded corners — so a border on the
|
|
70
|
+
* container drew a straight line across the full width above the rounded top,
|
|
71
|
+
* which is the stray line that was reported.
|
|
72
|
+
*
|
|
73
|
+
* And the spring overshoots. A sheet sized exactly to its snap point travels
|
|
74
|
+
* past it and leaves a band of backdrop along the bottom of the screen for a
|
|
75
|
+
* frame or two. So this hangs `grytDrawerBleed` below the bottom edge, which
|
|
76
|
+
* is the same trick and the same distance the web Drawer uses — its comment
|
|
77
|
+
* calls the artefact "a seam of backdrop down the edge".
|
|
78
|
+
*/
|
|
79
|
+
const renderBackground = useCallback(({ style: bgStyle }) => (_jsx(View, { pointerEvents: "none", style: [
|
|
80
|
+
bgStyle,
|
|
81
|
+
{
|
|
82
|
+
backgroundColor: theme.color.surface,
|
|
83
|
+
borderTopLeftRadius: theme.radius.xl,
|
|
84
|
+
borderTopRightRadius: theme.radius.xl,
|
|
85
|
+
// Follows the rounded corners, unlike a border on the container.
|
|
86
|
+
borderTopWidth: 1,
|
|
87
|
+
borderColor: theme.color.border,
|
|
88
|
+
// The overhang. Negative bottom rather than extra height, so the
|
|
89
|
+
// rounded top stays where the sheet actually is.
|
|
90
|
+
bottom: -grytDrawerBleed,
|
|
91
|
+
},
|
|
92
|
+
] })), [theme]);
|
|
93
|
+
/**
|
|
94
|
+
* Gryt's spring, rather than gorhom's default.
|
|
95
|
+
*
|
|
96
|
+
* `withTiming` over the sampled curve, for the same reason every other
|
|
97
|
+
* animation in this package does it that way: the curve in @gryt/theme is a
|
|
98
|
+
* damped spring solved analytically, and approximating it with a physics
|
|
99
|
+
* engine would be approximating an exact curve with the thing it was chosen
|
|
100
|
+
* over.
|
|
101
|
+
*
|
|
102
|
+
* `springSoft` — 700ms — because a sheet travels its own height, which is the
|
|
103
|
+
* case that duration was shaped for. It is what Drawer uses and for the same
|
|
104
|
+
* reason.
|
|
105
|
+
*/
|
|
106
|
+
const animationConfigs = useBottomSheetTimingConfigs({
|
|
107
|
+
duration: durations.springSoft,
|
|
108
|
+
easing: easeSpring,
|
|
109
|
+
});
|
|
110
|
+
const renderBackdrop = useCallback((props) => (_jsx(BottomSheetBackdrop, { ...props, appearsOnIndex: 0, disappearsOnIndex: -1,
|
|
111
|
+
// Tracks the drag, so dragging the sheet down lightens the scrim
|
|
112
|
+
// rather than holding full opacity until it snaps shut.
|
|
113
|
+
opacity: 0.6, pressBehavior: "close" })), []);
|
|
114
|
+
return (_jsx(BottomSheetModal, { ref: ref, snapPoints: snapPoints, enablePanDownToClose: true, onDismiss: () => onOpenChange?.(false), animationConfigs: animationConfigs, backdropComponent: renderBackdrop, backgroundComponent: renderBackground, handleIndicatorStyle: {
|
|
115
|
+
backgroundColor: theme.color.border,
|
|
116
|
+
width: 36,
|
|
117
|
+
height: 4,
|
|
118
|
+
}, children: _jsx(BottomSheetView, { style: [{ flex: 1, padding: theme.space(4) }, style], children: _jsx(SheetContext.Provider, { value: sheet, children: children }) }) }));
|
|
119
|
+
}
|
|
120
|
+
function Close({ children, style }) {
|
|
121
|
+
const sheet = useSheet("Close");
|
|
122
|
+
return (_jsx(Pressable, { onPress: sheet.close, style: style, children: children }));
|
|
123
|
+
}
|
|
124
|
+
function Title({ children, style }) {
|
|
125
|
+
const theme = useTheme();
|
|
126
|
+
return (_jsx(View, { style: style, children: _jsx(Text, { accessibilityRole: "header", style: {
|
|
127
|
+
color: theme.color.text,
|
|
128
|
+
fontSize: 18,
|
|
129
|
+
fontWeight: "600",
|
|
130
|
+
marginBottom: theme.space(2),
|
|
131
|
+
}, children: children }) }));
|
|
132
|
+
}
|
|
133
|
+
export const Sheet = Object.assign(Root, { Trigger, Content, Close, Title });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Slider.d.ts","sourceRoot":"","sources":["../../../src/components/Slider/Slider.tsx"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAMtB,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"Slider.d.ts","sourceRoot":"","sources":["../../../src/components/Slider/Slider.tsx"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAMtB,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAIrE,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,aAAa,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAC;AAEhG,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAKD;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,EACrB,KAAK,EAAE,UAAU,EACjB,YAAgB,EAChB,aAAa,EACb,aAAa,EACb,GAAO,EACP,GAAS,EACT,IAAQ,EACR,QAAQ,EACR,IAAgB,EAChB,KAAK,EACL,kBAAkB,GACnB,EAAE,WAAW,+BAuKb"}
|