@gryt/ui-native 0.2.0 → 0.3.1
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/Button/Button.d.ts.map +1 -1
- package/dist/components/Button/Button.js +14 -14
- package/dist/components/Collapsible/Collapsible.d.ts +14 -6
- package/dist/components/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/components/Collapsible/Collapsible.js +31 -10
- package/dist/components/Dialog/Dialog.d.ts.map +1 -1
- package/dist/components/Dialog/Dialog.js +16 -3
- package/dist/components/Drawer/Drawer.d.ts +1 -1
- package/dist/components/Drawer/Drawer.d.ts.map +1 -1
- package/dist/components/Drawer/Drawer.js +46 -41
- package/dist/components/Progress/Progress.d.ts +17 -6
- package/dist/components/Progress/Progress.d.ts.map +1 -1
- package/dist/components/Progress/Progress.js +17 -6
- package/dist/components/Slider/Slider.d.ts.map +1 -1
- package/dist/components/Slider/Slider.js +51 -19
- package/dist/components/Slider/sliderValue.d.ts +15 -0
- package/dist/components/Slider/sliderValue.d.ts.map +1 -0
- package/dist/components/Slider/sliderValue.js +23 -0
- package/dist/components/Switch/Switch.d.ts.map +1 -1
- package/dist/components/Switch/Switch.js +23 -19
- package/dist/components/Toast/Toast.d.ts.map +1 -1
- package/dist/components/Toast/Toast.js +12 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/motion/easing.d.ts +28 -0
- package/dist/motion/easing.d.ts.map +1 -0
- package/dist/motion/easing.js +51 -0
- package/dist/motion/index.d.ts +4 -0
- package/dist/motion/index.d.ts.map +1 -0
- package/dist/motion/index.js +3 -0
- package/dist/motion/motion.d.ts +15 -0
- package/dist/motion/motion.d.ts.map +1 -0
- package/dist/motion/motion.js +32 -0
- package/dist/motion/usePressScale.d.ts +15 -0
- package/dist/motion/usePressScale.d.ts.map +1 -0
- package/dist/motion/usePressScale.js +39 -0
- package/package.json +8 -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;;;;;gBAAk6H,CAAC;aAAkB,CAAC;;;gBAAiS,CAAC;aAAkB,CAAC;;;gBAAkiB,CAAC;aAAkB,CAAC;;CADjoL,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.d.ts","sourceRoot":"","sources":["../../../src/components/Button/Button.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"Button.d.ts","sourceRoot":"","sources":["../../../src/components/Button/Button.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAQtB,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;AAClF,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AASjE,MAAM,WAAW,WAAY,SAAQ,IAAI,CAAC,cAAc,EAAE,OAAO,GAAG,UAAU,CAAC;IAC7E,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAWD,wBAAgB,MAAM,CAAC,EACrB,QAAQ,EACR,IAAgB,EAChB,IAAe,EACf,SAAS,EACT,OAAO,EACP,KAAK,EACL,QAAgB,EAChB,QAAQ,EACR,GAAG,IAAI,EACR,EAAE,WAAW,+BAuEb"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { Pressable, Text, View, } from "react-native";
|
|
3
|
+
import Animated from "react-native-reanimated";
|
|
4
|
+
import { grytScaleSteps } from "@gryt/theme";
|
|
4
5
|
import { useReducedMotion } from "../../hooks/useReducedMotion.js";
|
|
6
|
+
import { usePressScale } from "../../motion/index.js";
|
|
5
7
|
import { useTheme } from "../../theme/index.js";
|
|
6
8
|
const SIZES = {
|
|
7
9
|
xsmall: { minHeight: 32, paddingH: 12, fontSize: 12 },
|
|
@@ -9,17 +11,20 @@ const SIZES = {
|
|
|
9
11
|
medium: { minHeight: 40, paddingH: 20, fontSize: 14 },
|
|
10
12
|
large: { minHeight: 48, paddingH: 24, fontSize: 16 },
|
|
11
13
|
};
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
/**
|
|
15
|
+
* `active:scale-[0.96]` on the web, and the same number here.
|
|
16
|
+
*
|
|
17
|
+
* `hover:scale-[1.03]` has no counterpart: a touch screen has no state
|
|
18
|
+
* between not-touching and touching, so emulating hover would be a
|
|
19
|
+
* difference from the web rather than a match to it.
|
|
20
|
+
*/
|
|
21
|
+
const PRESSED_SCALE = grytScaleSteps.button.press;
|
|
14
22
|
export function Button({ children, tone = "primary", size = "medium", startIcon, endIcon, style, hasPopup = false, disabled, ...rest }) {
|
|
15
23
|
const theme = useTheme();
|
|
16
24
|
const reducedMotion = useReducedMotion();
|
|
17
|
-
// useState with an initialiser rather than useRef(new Animated.Value(1)):
|
|
18
|
-
// reading .current during render is a lint error, and the useRef form also
|
|
19
|
-
// constructs a throwaway Animated.Value on every render.
|
|
20
|
-
const [scale] = useState(() => new Animated.Value(1));
|
|
21
25
|
const metrics = SIZES[size];
|
|
22
26
|
const animate = !reducedMotion && !hasPopup && !disabled;
|
|
27
|
+
const press = usePressScale(PRESSED_SCALE, !animate);
|
|
23
28
|
const background = tone === "primary"
|
|
24
29
|
? theme.color.accent
|
|
25
30
|
: tone === "secondary"
|
|
@@ -38,12 +43,7 @@ export function Button({ children, tone = "primary", size = "medium", startIcon,
|
|
|
38
43
|
: tone === "ghost"
|
|
39
44
|
? theme.color.muted
|
|
40
45
|
: theme.color.text;
|
|
41
|
-
|
|
42
|
-
if (!animate)
|
|
43
|
-
return;
|
|
44
|
-
Animated.spring(scale, { toValue: value, ...SPRING }).start();
|
|
45
|
-
};
|
|
46
|
-
return (_jsx(Animated.View, { style: [{ transform: [{ scale }] }, style], children: _jsxs(Pressable, { accessibilityRole: "button", accessibilityState: { disabled: !!disabled }, disabled: disabled, onPressIn: () => springTo(PRESSED_SCALE), onPressOut: () => springTo(1), style: {
|
|
46
|
+
return (_jsx(Animated.View, { style: [press.style, style], children: _jsxs(Pressable, { accessibilityRole: "button", accessibilityState: { disabled: !!disabled }, disabled: disabled, onPressIn: press.onPressIn, onPressOut: press.onPressOut, style: {
|
|
47
47
|
minHeight: metrics.minHeight,
|
|
48
48
|
paddingHorizontal: metrics.paddingH,
|
|
49
49
|
flexDirection: "row",
|
|
@@ -11,17 +11,25 @@ declare function Trigger({ children, style }: {
|
|
|
11
11
|
style?: StyleProp<ViewStyle>;
|
|
12
12
|
}): import("react").JSX.Element;
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* Animates its height, the same as the web.
|
|
15
15
|
*
|
|
16
|
-
* The web
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* The web transitions height on `ease-spring` at `--gryt-dur-spring`, and this
|
|
17
|
+
* now does the same curve at the same duration.
|
|
18
|
+
*
|
|
19
|
+
* The content has to be measured before its height can be animated to, which
|
|
20
|
+
* is why this was unmounted-when-closed to begin with. It is measured off an
|
|
21
|
+
* absolutely positioned copy that never affects layout, so nothing is drawn at
|
|
22
|
+
* the wrong size on the way in — the earlier objection was to measuring the
|
|
23
|
+
* visible content and paying a frame for it.
|
|
24
|
+
*
|
|
25
|
+
* Children stay mounted while closed, at height zero with `overflow: hidden`,
|
|
26
|
+
* matching the web. That is a behaviour change as well as a visual one: state
|
|
27
|
+
* inside a closed panel now survives, where before it was destroyed.
|
|
20
28
|
*/
|
|
21
29
|
declare function Panel({ children, style }: {
|
|
22
30
|
children?: ReactNode;
|
|
23
31
|
style?: StyleProp<ViewStyle>;
|
|
24
|
-
}): import("react").JSX.Element
|
|
32
|
+
}): import("react").JSX.Element;
|
|
25
33
|
export declare const Collapsible: {
|
|
26
34
|
Root: typeof Root;
|
|
27
35
|
Trigger: typeof Trigger;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Collapsible.d.ts","sourceRoot":"","sources":["../../../src/components/Collapsible/Collapsible.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"Collapsible.d.ts","sourceRoot":"","sources":["../../../src/components/Collapsible/Collapsible.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvF,OAAO,EAAmB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAK/E,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAe/E,MAAM,WAAW,oBAAqB,SAAQ,cAAc;IAC1D,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,iBAAS,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,SAAS,EAAE,EAAE,oBAAoB,+BAOpE;AAED,iBAAS,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CAAE,+BAY3F;AAED;;;;;;;;;;;;;;;GAeG;AACH,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,+BA+BzF;AAED,eAAO,MAAM,WAAW;;;;CAA2B,CAAC"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext } from "react";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
3
3
|
import { Pressable, View } from "react-native";
|
|
4
|
+
import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
5
|
+
import { useReducedMotion } from "../../hooks/useReducedMotion.js";
|
|
6
|
+
import { springy } from "../../motion/index.js";
|
|
4
7
|
import { useOpenState } from "../../overlay/useOpenState.js";
|
|
5
8
|
const CollapsibleContext = createContext(null);
|
|
6
9
|
function useCollapsible(part) {
|
|
@@ -18,17 +21,35 @@ function Trigger({ children, style }) {
|
|
|
18
21
|
return (_jsx(Pressable, { accessibilityRole: "button", accessibilityState: { expanded: open }, onPress: () => setOpen(!open), style: style, children: children }));
|
|
19
22
|
}
|
|
20
23
|
/**
|
|
21
|
-
*
|
|
24
|
+
* Animates its height, the same as the web.
|
|
22
25
|
*
|
|
23
|
-
* The web
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
26
|
+
* The web transitions height on `ease-spring` at `--gryt-dur-spring`, and this
|
|
27
|
+
* now does the same curve at the same duration.
|
|
28
|
+
*
|
|
29
|
+
* The content has to be measured before its height can be animated to, which
|
|
30
|
+
* is why this was unmounted-when-closed to begin with. It is measured off an
|
|
31
|
+
* absolutely positioned copy that never affects layout, so nothing is drawn at
|
|
32
|
+
* the wrong size on the way in — the earlier objection was to measuring the
|
|
33
|
+
* visible content and paying a frame for it.
|
|
34
|
+
*
|
|
35
|
+
* Children stay mounted while closed, at height zero with `overflow: hidden`,
|
|
36
|
+
* matching the web. That is a behaviour change as well as a visual one: state
|
|
37
|
+
* inside a closed panel now survives, where before it was destroyed.
|
|
27
38
|
*/
|
|
28
39
|
function Panel({ children, style }) {
|
|
29
40
|
const { open } = useCollapsible("Panel");
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
const reducedMotion = useReducedMotion();
|
|
42
|
+
const [height, setHeight] = useState(0);
|
|
43
|
+
const progress = useSharedValue(open ? 1 : 0);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const to = open ? 1 : 0;
|
|
46
|
+
// eslint-disable-next-line react-hooks/immutability
|
|
47
|
+
progress.value = reducedMotion ? to : springy(to);
|
|
48
|
+
}, [open, progress, reducedMotion]);
|
|
49
|
+
const animated = useAnimatedStyle(() => ({
|
|
50
|
+
height: height * progress.value,
|
|
51
|
+
opacity: progress.value
|
|
52
|
+
}));
|
|
53
|
+
return (_jsxs(Animated.View, { style: [{ overflow: "hidden" }, animated, style], children: [_jsx(View, { pointerEvents: "none", style: { position: "absolute", left: 0, right: 0, opacity: 0 }, onLayout: (e) => setHeight(e.nativeEvent.layout.height), children: children }), children] }));
|
|
33
54
|
}
|
|
34
55
|
export const Collapsible = { Root, Trigger, Panel };
|
|
@@ -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,+BAwFlB;AAED;;;;;;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"}
|
|
@@ -42,7 +42,10 @@ function Popup({ children, style, dismissible = true, scrollable = true, }) {
|
|
|
42
42
|
{
|
|
43
43
|
width: "100%",
|
|
44
44
|
maxWidth: 480,
|
|
45
|
-
|
|
45
|
+
// Shrinks inside the capped wrapper below rather than carrying its
|
|
46
|
+
// own percentage, so the popup hugs short content and gives way when
|
|
47
|
+
// the content is taller than the cap.
|
|
48
|
+
flexShrink: 1,
|
|
46
49
|
backgroundColor: theme.color.surfaceRaised,
|
|
47
50
|
borderRadius: theme.radius.lg,
|
|
48
51
|
borderWidth: 1,
|
|
@@ -51,7 +54,17 @@ function Popup({ children, style, dismissible = true, scrollable = true, }) {
|
|
|
51
54
|
gap: theme.space(3),
|
|
52
55
|
},
|
|
53
56
|
style,
|
|
54
|
-
], children: scrollable ? (
|
|
57
|
+
], children: scrollable ? (
|
|
58
|
+
// flexGrow 0 so the ScrollView takes the height of its content rather
|
|
59
|
+
// than filling the popup, and flexShrink 1 so it still gives way when
|
|
60
|
+
// maxHeight caps it and the content has to scroll.
|
|
61
|
+
//
|
|
62
|
+
// Without these it clipped every short dialog: a ScrollView has no
|
|
63
|
+
// intrinsic height, the popup sizes to its content, and neither had
|
|
64
|
+
// anything to measure against — so the footer was drawn half off the
|
|
65
|
+
// bottom. The `scrollable={false}` path was unaffected, which is what
|
|
66
|
+
// made it look like a layout choice rather than a bug (GRYT-379).
|
|
67
|
+
_jsx(ScrollView, { style: { flexGrow: 0, flexShrink: 1 }, contentContainerStyle: { gap: theme.space(3) }, children: children })) : (children) }));
|
|
55
68
|
return (_jsx(Modal, { visible: open, transparent: true, animationType: "fade",
|
|
56
69
|
// Android's hardware back button, which is the closest thing a phone has
|
|
57
70
|
// to the Escape key Base UI listens for. AlertDialog turns it off for the
|
|
@@ -68,7 +81,7 @@ function Popup({ children, style, dismissible = true, scrollable = true, }) {
|
|
|
68
81
|
justifyContent: "center",
|
|
69
82
|
padding: theme.space(5),
|
|
70
83
|
backgroundColor: "rgba(0,0,0,0.6)",
|
|
71
|
-
}, children: _jsx(Pressable, { onPress: () => { }, style: { width: "100%", alignItems: "center" }, children: body }) }) }));
|
|
84
|
+
}, children: _jsx(Pressable, { onPress: () => { }, style: { width: "100%", alignItems: "center", maxHeight: "80%" }, children: body }) }) }));
|
|
72
85
|
}
|
|
73
86
|
/**
|
|
74
87
|
* Not rendered.
|
|
@@ -28,7 +28,7 @@ export interface DrawerPopupProps {
|
|
|
28
28
|
* panel, so this one animates even though Collapsible and Skeleton do not.
|
|
29
29
|
* Reduce-motion still turns it off: the drawer arrives in place instead.
|
|
30
30
|
*/
|
|
31
|
-
declare function Popup({ children, side, size, dismissible, style
|
|
31
|
+
declare function Popup({ children, side, size, dismissible, style }: DrawerPopupProps): import("react").JSX.Element;
|
|
32
32
|
declare function Close({ children, style }: {
|
|
33
33
|
children?: ReactNode;
|
|
34
34
|
style?: StyleProp<ViewStyle>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Drawer.d.ts","sourceRoot":"","sources":["../../../src/components/Drawer/Drawer.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
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;AAQtB,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,+BA0ElB;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"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useEffect
|
|
3
|
-
import {
|
|
2
|
+
import { createContext, useContext, useEffect } from "react";
|
|
3
|
+
import { Dimensions, Modal, Pressable } from "react-native";
|
|
4
|
+
import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
4
5
|
import { useReducedMotion } from "../../hooks/useReducedMotion.js";
|
|
6
|
+
import { durations, springy } from "../../motion/index.js";
|
|
5
7
|
import { useOpenState } from "../../overlay/useOpenState.js";
|
|
6
8
|
import { useTheme } from "../../theme/index.js";
|
|
7
9
|
const DrawerContext = createContext(null);
|
|
@@ -13,7 +15,7 @@ function useDrawer(part) {
|
|
|
13
15
|
}
|
|
14
16
|
function Root({ children, ...openProps }) {
|
|
15
17
|
const state = useOpenState(openProps);
|
|
16
|
-
return _jsx(DrawerContext.Provider, { value: state, children: children });
|
|
18
|
+
return (_jsx(DrawerContext.Provider, { value: state, children: children }));
|
|
17
19
|
}
|
|
18
20
|
function Trigger({ children, style }) {
|
|
19
21
|
const { setOpen } = useDrawer("Trigger");
|
|
@@ -29,52 +31,55 @@ function Portal({ children }) {
|
|
|
29
31
|
* panel, so this one animates even though Collapsible and Skeleton do not.
|
|
30
32
|
* Reduce-motion still turns it off: the drawer arrives in place instead.
|
|
31
33
|
*/
|
|
32
|
-
function Popup({ children, side = "left", size = 0.8, dismissible = true, style
|
|
34
|
+
function Popup({ children, side = "left", size = 0.8, dismissible = true, style }) {
|
|
33
35
|
const { open, setOpen } = useDrawer("Popup");
|
|
34
36
|
const theme = useTheme();
|
|
35
37
|
const reducedMotion = useReducedMotion();
|
|
36
38
|
const screen = Dimensions.get("window");
|
|
37
39
|
const vertical = side === "bottom";
|
|
38
40
|
const extent = vertical ? screen.height * size : screen.width * size;
|
|
39
|
-
const
|
|
41
|
+
const progress = useSharedValue(0);
|
|
42
|
+
const hidden = side === "right" ? extent : side === "left" ? -extent : extent;
|
|
43
|
+
// 700ms, the web's `--gryt-dur-spring-soft`, on the overshooting curve —
|
|
44
|
+
// which is what the web uses here despite the panel travelling its own
|
|
45
|
+
// width. Matching it rather than substituting the tight curve, because the
|
|
46
|
+
// brief is 1:1 with the web and not a second opinion about it.
|
|
40
47
|
useEffect(() => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
Animated.spring(progress, {
|
|
50
|
-
toValue: 1,
|
|
51
|
-
useNativeDriver: true,
|
|
52
|
-
speed: 20,
|
|
53
|
-
bounciness: 0,
|
|
54
|
-
}).start();
|
|
48
|
+
// eslint-disable-next-line react-hooks/immutability
|
|
49
|
+
progress.value =
|
|
50
|
+
!open || reducedMotion
|
|
51
|
+
? open
|
|
52
|
+
? 1
|
|
53
|
+
: 0
|
|
54
|
+
: springy(1, { duration: durations.springSoft });
|
|
55
55
|
}, [open, progress, reducedMotion]);
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
56
|
+
const panelStyle = useAnimatedStyle(() => ({
|
|
57
|
+
transform: [
|
|
58
|
+
vertical
|
|
59
|
+
? { translateY: hidden + (0 - hidden) * progress.value }
|
|
60
|
+
: { translateX: hidden + (0 - hidden) * progress.value }
|
|
61
|
+
],
|
|
62
|
+
opacity: progress.value
|
|
63
|
+
}));
|
|
64
|
+
return (_jsx(Modal, { visible: open, transparent: true, animationType: "none", onRequestClose: dismissible ? () => setOpen(false) : undefined, children: _jsx(Pressable, { onPress: dismissible ? () => setOpen(false) : undefined, style: { flex: 1, backgroundColor: "rgba(0,0,0,0.6)" }, children: _jsx(Animated.View, { accessibilityViewIsModal: true, style: [
|
|
65
|
+
{
|
|
66
|
+
position: "absolute",
|
|
67
|
+
top: side === "bottom" ? undefined : 0,
|
|
68
|
+
bottom: 0,
|
|
69
|
+
left: side === "right" ? undefined : 0,
|
|
70
|
+
right: side === "left" ? undefined : 0,
|
|
71
|
+
width: vertical ? "100%" : extent,
|
|
72
|
+
height: vertical ? extent : "100%",
|
|
73
|
+
backgroundColor: theme.color.surface,
|
|
74
|
+
borderColor: theme.color.border,
|
|
75
|
+
borderRightWidth: side === "left" ? 1 : 0,
|
|
76
|
+
borderLeftWidth: side === "right" ? 1 : 0,
|
|
77
|
+
borderTopWidth: vertical ? 1 : 0,
|
|
78
|
+
borderTopLeftRadius: vertical ? theme.radius.lg : 0,
|
|
79
|
+
borderTopRightRadius: vertical ? theme.radius.lg : 0
|
|
80
|
+
},
|
|
81
|
+
panelStyle
|
|
82
|
+
], children: _jsx(Pressable, { onPress: () => { }, style: [{ flex: 1 }, style], children: children }) }) }) }));
|
|
78
83
|
}
|
|
79
84
|
function Close({ children, style }) {
|
|
80
85
|
const { setOpen } = useDrawer("Close");
|
|
@@ -6,13 +6,24 @@ export interface ProgressProps {
|
|
|
6
6
|
style?: StyleProp<ViewStyle>;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
* Determinate only
|
|
9
|
+
* Determinate only — and so is the web, which is not what this used to say.
|
|
10
10
|
*
|
|
11
|
-
* The web's indeterminate state
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* The previous note here claimed the web's indeterminate state was a CSS
|
|
12
|
+
* keyframe sweeping a bar across the track, and listed this as a parity
|
|
13
|
+
* exception. It is not one. `@gryt/ui`'s Progress passes `value={null}` to
|
|
14
|
+
* Base UI and styles nothing for it: there is no `@keyframes` anywhere in the
|
|
15
|
+
* package, no rule for `data-indeterminate`, and the compiled stylesheet has
|
|
16
|
+
* zero rules matching `gryt-progress`. An indeterminate Progress renders an
|
|
17
|
+
* empty track on the web too.
|
|
18
|
+
*
|
|
19
|
+
* So both platforms agree, and both are missing the feature. Implementing it
|
|
20
|
+
* belongs on both at once — GRYT-382 — rather than here, where it would make
|
|
21
|
+
* React Native the one that behaves differently.
|
|
22
|
+
*
|
|
23
|
+
* The travel that *is* implemented follows the web deliberately: 300ms on
|
|
24
|
+
* ease-out rather than the spring. A progress bar is a reading, and the
|
|
25
|
+
* spring's 12% overshoot on a jump to 100% runs past the end of the track and
|
|
26
|
+
* comes back — which reads as the job finishing, unfinishing, then finishing.
|
|
16
27
|
*/
|
|
17
28
|
export declare function Progress({ value, tone, style }: ProgressProps): import("react").JSX.Element;
|
|
18
29
|
//# sourceMappingURL=Progress.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Progress.d.ts","sourceRoot":"","sources":["../../../src/components/Progress/Progress.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAIpE,MAAM,WAAW,aAAa;IAC5B,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;IACnD,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED
|
|
1
|
+
{"version":3,"file":"Progress.d.ts","sourceRoot":"","sources":["../../../src/components/Progress/Progress.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAIpE,MAAM,WAAW,aAAa;IAC5B,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;IACnD,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAe,EAAE,KAAK,EAAE,EAAE,aAAa,+BA4BxE"}
|
|
@@ -2,13 +2,24 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { View } from "react-native";
|
|
3
3
|
import { useTheme } from "../../theme/index.js";
|
|
4
4
|
/**
|
|
5
|
-
* Determinate only
|
|
5
|
+
* Determinate only — and so is the web, which is not what this used to say.
|
|
6
6
|
*
|
|
7
|
-
* The web's indeterminate state
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* The previous note here claimed the web's indeterminate state was a CSS
|
|
8
|
+
* keyframe sweeping a bar across the track, and listed this as a parity
|
|
9
|
+
* exception. It is not one. `@gryt/ui`'s Progress passes `value={null}` to
|
|
10
|
+
* Base UI and styles nothing for it: there is no `@keyframes` anywhere in the
|
|
11
|
+
* package, no rule for `data-indeterminate`, and the compiled stylesheet has
|
|
12
|
+
* zero rules matching `gryt-progress`. An indeterminate Progress renders an
|
|
13
|
+
* empty track on the web too.
|
|
14
|
+
*
|
|
15
|
+
* So both platforms agree, and both are missing the feature. Implementing it
|
|
16
|
+
* belongs on both at once — GRYT-382 — rather than here, where it would make
|
|
17
|
+
* React Native the one that behaves differently.
|
|
18
|
+
*
|
|
19
|
+
* The travel that *is* implemented follows the web deliberately: 300ms on
|
|
20
|
+
* ease-out rather than the spring. A progress bar is a reading, and the
|
|
21
|
+
* spring's 12% overshoot on a jump to 100% runs past the end of the track and
|
|
22
|
+
* comes back — which reads as the job finishing, unfinishing, then finishing.
|
|
12
23
|
*/
|
|
13
24
|
export function Progress({ value, tone = "accent", style }) {
|
|
14
25
|
const theme = useTheme();
|
|
@@ -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;
|
|
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;AAGrE,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,+BAsIb"}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import { PanResponder, View, } 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 { valueAt } from "./sliderValue.js";
|
|
5
9
|
const TRACK_HEIGHT = 4;
|
|
6
10
|
const THUMB = 20;
|
|
7
11
|
/**
|
|
@@ -27,6 +31,16 @@ export function Slider({ value: controlled, defaultValue = 0, onValueChange, onV
|
|
|
27
31
|
// fire from touch events, which is always after an effect has run.
|
|
28
32
|
const state = useRef({ width, value, min, max, step, disabled });
|
|
29
33
|
const emit = useRef({ onValueChange, onValueCommit, controlled });
|
|
34
|
+
/** Where the current gesture started, in track coordinates. */
|
|
35
|
+
const origin = useRef(0);
|
|
36
|
+
/**
|
|
37
|
+
* `active:scale-[0.94]` on the web. `hover:scale-[1.12]` has no touch
|
|
38
|
+
* equivalent, so only the press half ports.
|
|
39
|
+
*/
|
|
40
|
+
const thumbScale = useSharedValue(1);
|
|
41
|
+
const thumbStyle = useAnimatedStyle(() => ({
|
|
42
|
+
transform: [{ scale: thumbScale.value }]
|
|
43
|
+
}));
|
|
30
44
|
useEffect(() => {
|
|
31
45
|
state.current = { width, value, min, max, step, disabled };
|
|
32
46
|
}, [width, value, min, max, step, disabled]);
|
|
@@ -35,12 +49,11 @@ export function Slider({ value: controlled, defaultValue = 0, onValueChange, onV
|
|
|
35
49
|
}, [onValueChange, onValueCommit, controlled]);
|
|
36
50
|
const valueFromX = (x) => {
|
|
37
51
|
const s = state.current;
|
|
52
|
+
// Before layout there is no position to read, so hold what we have rather
|
|
53
|
+
// than snapping to min.
|
|
38
54
|
if (s.width <= 0)
|
|
39
55
|
return s.value;
|
|
40
|
-
|
|
41
|
-
const raw = s.min + ratio * (s.max - s.min);
|
|
42
|
-
const stepped = Math.round(raw / s.step) * s.step;
|
|
43
|
-
return Math.max(s.min, Math.min(s.max, stepped));
|
|
56
|
+
return valueAt(x, s);
|
|
44
57
|
};
|
|
45
58
|
const apply = (next) => {
|
|
46
59
|
if (emit.current.controlled === undefined)
|
|
@@ -62,13 +75,29 @@ export function Slider({ value: controlled, defaultValue = 0, onValueChange, onV
|
|
|
62
75
|
// Claimed on move as well, so a drag that begins as a scroll still lands
|
|
63
76
|
// here once it is clearly horizontal.
|
|
64
77
|
onMoveShouldSetPanResponder: () => !state.current.disabled,
|
|
65
|
-
onPanResponderGrant: (e) =>
|
|
78
|
+
onPanResponderGrant: (e) => {
|
|
79
|
+
// Where the finger landed, kept for the drag to offset from.
|
|
80
|
+
origin.current = e.nativeEvent.locationX;
|
|
81
|
+
thumbScale.value = springy(grytScaleSteps.sliderThumb.press);
|
|
82
|
+
apply(valueFromX(e.nativeEvent.locationX));
|
|
83
|
+
},
|
|
84
|
+
// `gesture.dx` is the distance from where the gesture *started*, not from
|
|
85
|
+
// the previous event. Offsetting the live value by it therefore adds the
|
|
86
|
+
// whole travel again on every move, and the thumb accelerates away from
|
|
87
|
+
// the finger — 200px wide, 0-100, dragging to x=50 then 60 then 70 gave
|
|
88
|
+
// 25, then 55, then 90. Tapping was always fine, because grant uses
|
|
89
|
+
// locationX directly, which is what made it look like a rendering
|
|
90
|
+
// problem rather than an arithmetic one.
|
|
91
|
+
//
|
|
92
|
+
// Anchoring on the grant position is also what makes the drag continue
|
|
93
|
+
// from the finger: grant has already seeked the thumb there.
|
|
66
94
|
onPanResponderMove: (_e, gesture) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
95
|
+
apply(valueFromX(origin.current + gesture.dx));
|
|
96
|
+
},
|
|
97
|
+
onPanResponderRelease: () => {
|
|
98
|
+
thumbScale.value = springy(1);
|
|
99
|
+
emit.current.onValueCommit?.(state.current.value);
|
|
70
100
|
},
|
|
71
|
-
onPanResponderRelease: () => emit.current.onValueCommit?.(state.current.value),
|
|
72
101
|
}));
|
|
73
102
|
/* eslint-enable react-hooks/refs */
|
|
74
103
|
const ratio = (value - min) / (max - min || 1);
|
|
@@ -81,14 +110,17 @@ export function Slider({ value: controlled, defaultValue = 0, onValueChange, onV
|
|
|
81
110
|
height: "100%",
|
|
82
111
|
borderRadius: TRACK_HEIGHT / 2,
|
|
83
112
|
backgroundColor: ramp[8],
|
|
84
|
-
} }) }), _jsx(View, { pointerEvents: "none", style:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
113
|
+
} }) }), _jsx(Animated.View, { pointerEvents: "none", style: [
|
|
114
|
+
{
|
|
115
|
+
position: "absolute",
|
|
116
|
+
left: Math.max(0, ratio * width - THUMB / 2),
|
|
117
|
+
width: THUMB,
|
|
118
|
+
height: THUMB,
|
|
119
|
+
borderRadius: THUMB / 2,
|
|
120
|
+
backgroundColor: ramp[8],
|
|
121
|
+
borderWidth: 2,
|
|
122
|
+
borderColor: theme.color.bg,
|
|
123
|
+
},
|
|
124
|
+
thumbStyle,
|
|
125
|
+
] })] }));
|
|
94
126
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface SliderScale {
|
|
2
|
+
/** Track width in pixels. Zero or less means the layout has not happened. */
|
|
3
|
+
width: number;
|
|
4
|
+
min: number;
|
|
5
|
+
max: number;
|
|
6
|
+
step: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The value at `x` pixels along the track.
|
|
10
|
+
*
|
|
11
|
+
* Clamped at both ends, snapped to `step`, and safe to call before layout —
|
|
12
|
+
* a zero width returns `min`, since there is no position to read yet.
|
|
13
|
+
*/
|
|
14
|
+
export declare function valueAt(x: number, scale: SliderScale): number;
|
|
15
|
+
//# sourceMappingURL=sliderValue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sliderValue.d.ts","sourceRoot":"","sources":["../../../src/components/Slider/sliderValue.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,WAAW;IAC1B,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,CAU7D"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* Track position to value, as a pure function.
|
|
2
|
+
*
|
|
3
|
+
* Split out of Slider.tsx for the same reason placePopup.ts is split out of the
|
|
4
|
+
* overlay: it is arithmetic, it is where the bugs are, and a closure over a ref
|
|
5
|
+
* cannot be tested. GRYT-378 was a one-line error in this maths that shipped,
|
|
6
|
+
* passed every check, and was only caught by a person dragging a real thumb on
|
|
7
|
+
* a real phone.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The value at `x` pixels along the track.
|
|
11
|
+
*
|
|
12
|
+
* Clamped at both ends, snapped to `step`, and safe to call before layout —
|
|
13
|
+
* a zero width returns `min`, since there is no position to read yet.
|
|
14
|
+
*/
|
|
15
|
+
export function valueAt(x, scale) {
|
|
16
|
+
const { width, min, max, step } = scale;
|
|
17
|
+
if (width <= 0)
|
|
18
|
+
return min;
|
|
19
|
+
const ratio = Math.max(0, Math.min(1, x / width));
|
|
20
|
+
const raw = min + ratio * (max - min);
|
|
21
|
+
const stepped = step > 0 ? Math.round(raw / step) * step : raw;
|
|
22
|
+
return Math.max(min, Math.min(max, stepped));
|
|
23
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Switch.d.ts","sourceRoot":"","sources":["../../../src/components/Switch/Switch.tsx"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"Switch.d.ts","sourceRoot":"","sources":["../../../src/components/Switch/Switch.tsx"],"names":[],"mappings":"AACA,OAAO,EAAa,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAOzE,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAExE,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,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;AAQD;;;;;;;;;;;GAWG;AACH,wBAAgB,MAAM,CAAC,EACrB,OAAO,EAAE,UAAU,EACnB,cAAsB,EACtB,eAAe,EACf,QAAQ,EACR,IAAgB,EAChB,KAAK,EACL,kBAAkB,GACnB,EAAE,WAAW,+BA6Db"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { Pressable } from "react-native";
|
|
4
|
+
import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
4
5
|
import { useReducedMotion } from "../../hooks/useReducedMotion.js";
|
|
6
|
+
import { springy } from "../../motion/index.js";
|
|
5
7
|
import { toneRamp, useTheme } from "../../theme/index.js";
|
|
6
8
|
// The web is h-6 w-10 with a thumb translating x-4. Same numbers in points.
|
|
7
9
|
const TRACK_WIDTH = 40;
|
|
@@ -25,19 +27,19 @@ export function Switch({ checked: controlled, defaultChecked = false, onCheckedC
|
|
|
25
27
|
const reducedMotion = useReducedMotion();
|
|
26
28
|
const [uncontrolled, setUncontrolled] = useState(defaultChecked);
|
|
27
29
|
const checked = controlled ?? uncontrolled;
|
|
28
|
-
const
|
|
30
|
+
const offset = useSharedValue(checked ? TRAVEL : 0);
|
|
31
|
+
// The web is `translate-x-4` on `ease-spring` — the overshooting curve, even
|
|
32
|
+
// though the thumb travels, because the travel is 16px inside a 40px track
|
|
33
|
+
// rather than the width of a container. Following the web rather than
|
|
34
|
+
// second-guessing it: 1:1 means the same curve, not a better-argued one.
|
|
29
35
|
useEffect(() => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
Animated.spring(offset, {
|
|
35
|
-
toValue: checked ? TRAVEL : 0,
|
|
36
|
-
useNativeDriver: true,
|
|
37
|
-
speed: 40,
|
|
38
|
-
bounciness: 4,
|
|
39
|
-
}).start();
|
|
36
|
+
const to = checked ? TRAVEL : 0;
|
|
37
|
+
// eslint-disable-next-line react-hooks/immutability
|
|
38
|
+
offset.value = reducedMotion ? to : springy(to);
|
|
40
39
|
}, [checked, offset, reducedMotion]);
|
|
40
|
+
const thumbStyle = useAnimatedStyle(() => ({
|
|
41
|
+
transform: [{ translateX: offset.value }]
|
|
42
|
+
}));
|
|
41
43
|
const ramp = toneRamp(theme, tone);
|
|
42
44
|
return (_jsx(Pressable, { accessibilityRole: "switch", accessibilityState: { checked, disabled: !!disabled }, accessibilityLabel: accessibilityLabel, disabled: disabled, onPress: () => {
|
|
43
45
|
const next = !checked;
|
|
@@ -55,11 +57,13 @@ export function Switch({ checked: controlled, defaultChecked = false, onCheckedC
|
|
|
55
57
|
opacity: disabled ? 0.5 : 1,
|
|
56
58
|
},
|
|
57
59
|
style,
|
|
58
|
-
], children: _jsx(Animated.View, { style:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
], children: _jsx(Animated.View, { style: [
|
|
61
|
+
{
|
|
62
|
+
width: THUMB,
|
|
63
|
+
height: THUMB,
|
|
64
|
+
borderRadius: THUMB / 2,
|
|
65
|
+
backgroundColor: checked ? theme.color.bg : theme.color.text,
|
|
66
|
+
},
|
|
67
|
+
thumbStyle,
|
|
68
|
+
] }) }));
|
|
65
69
|
}
|
|
@@ -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,
|
|
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;AAQrF,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;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,+BAoBnE;AAED,wBAAgB,QAAQ,IAAI,iBAAiB,CAI5C;AAuGD,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B"}
|
|
@@ -1,7 +1,10 @@
|
|
|
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
|
-
import {
|
|
3
|
+
import { Pressable, Text, View } from "react-native";
|
|
4
|
+
import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
5
|
+
import { grytScaleSteps } from "@gryt/theme";
|
|
4
6
|
import { useReducedMotion } from "../../hooks/useReducedMotion.js";
|
|
7
|
+
import { fade, usePressScale } from "../../motion/index.js";
|
|
5
8
|
import { useTheme } from "../../theme/index.js";
|
|
6
9
|
const ToastContext = createContext(null);
|
|
7
10
|
/**
|
|
@@ -57,23 +60,24 @@ function Viewport({ toasts, onDismiss, }) {
|
|
|
57
60
|
function ToastItem({ toast, onDismiss, }) {
|
|
58
61
|
const theme = useTheme();
|
|
59
62
|
const reducedMotion = useReducedMotion();
|
|
60
|
-
const
|
|
63
|
+
const opacity = useSharedValue(reducedMotion ? 1 : 0);
|
|
61
64
|
const ramp = theme.scales[RAMP[toast.severity ?? "info"]];
|
|
62
65
|
const duration = toast.duration === undefined ? 4000 : toast.duration;
|
|
66
|
+
// `active:scale-[0.96]` on the web. The `hover:scale-[1.04]` half has no
|
|
67
|
+
// touch equivalent and is deliberately not emulated.
|
|
68
|
+
const press = usePressScale(grytScaleSteps.toast.press, reducedMotion);
|
|
63
69
|
useEffect(() => {
|
|
64
70
|
if (!reducedMotion) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
duration: 150,
|
|
68
|
-
useNativeDriver: true,
|
|
69
|
-
}).start();
|
|
71
|
+
// eslint-disable-next-line react-hooks/immutability
|
|
72
|
+
opacity.value = fade(1);
|
|
70
73
|
}
|
|
71
74
|
if (duration === null)
|
|
72
75
|
return;
|
|
73
76
|
const timer = setTimeout(() => onDismiss(toast.id), duration);
|
|
74
77
|
return () => clearTimeout(timer);
|
|
75
78
|
}, [duration, onDismiss, opacity, reducedMotion, toast.id]);
|
|
76
|
-
|
|
79
|
+
const enter = useAnimatedStyle(() => ({ opacity: opacity.value }));
|
|
80
|
+
return (_jsx(Animated.View, { style: [enter, press.style], children: _jsxs(Pressable, { onPressIn: press.onPressIn, onPressOut: press.onPressOut, accessibilityRole: "alert", accessibilityLiveRegion: toast.severity === "error" || toast.severity === "warning" ? "assertive" : "polite", onPress: () => onDismiss(toast.id), style: {
|
|
77
81
|
backgroundColor: theme.color.surfaceRaised,
|
|
78
82
|
borderRadius: theme.radius.md,
|
|
79
83
|
borderWidth: 1,
|
package/dist/index.d.ts
CHANGED
|
@@ -36,4 +36,5 @@ export { AnchoredPopup, type AnchoredPopupProps } from "./overlay/AnchoredPopup.
|
|
|
36
36
|
export { placePopup, type Align, type AnchorOptions, type AnchorRect, type PopupPlacement, type Side, } from "./overlay/placePopup.js";
|
|
37
37
|
export { useOpenState, type OpenStateProps } from "./overlay/useOpenState.js";
|
|
38
38
|
export { useReducedMotion } from "./hooks/useReducedMotion.js";
|
|
39
|
+
export { durations, easeSpring, easeSpringTight, fade, springy, travel, usePressScale, type PressScale } from "./motion/index.js";
|
|
39
40
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -36,3 +36,4 @@ export { AnchoredPopup } from "./overlay/AnchoredPopup.js";
|
|
|
36
36
|
export { placePopup, } from "./overlay/placePopup.js";
|
|
37
37
|
export { useOpenState } from "./overlay/useOpenState.js";
|
|
38
38
|
export { useReducedMotion } from "./hooks/useReducedMotion.js";
|
|
39
|
+
export { durations, easeSpring, easeSpringTight, fade, springy, travel, usePressScale } from "./motion/index.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Same shape as Reanimated's `EasingFunction`, without importing it. */
|
|
2
|
+
export type Easing = (t: number) => number;
|
|
3
|
+
/**
|
|
4
|
+
* Turn a sample list into an easing.
|
|
5
|
+
*
|
|
6
|
+
* The body is a worklet so it runs on the UI thread — an easing evaluated on
|
|
7
|
+
* the JS thread would drop frames under exactly the load this exists to
|
|
8
|
+
* survive. `points` is a plain array, which worklets capture by value.
|
|
9
|
+
*
|
|
10
|
+
* The interpolation is written out rather than calling `sampleCurve` from
|
|
11
|
+
* @gryt/theme, and that is not a style choice. A worklet cannot synchronously
|
|
12
|
+
* call a JS-thread function: doing so throws
|
|
13
|
+
*
|
|
14
|
+
* [Worklets] Tried to synchronously call a Remote Function
|
|
15
|
+
*
|
|
16
|
+
* on the first frame of every animation. That shipped in 0.3.0, because the
|
|
17
|
+
* only thing exercising the UI thread in testing used React Native's own
|
|
18
|
+
* easing rather than this one.
|
|
19
|
+
*
|
|
20
|
+
* easing.test.ts asserts this stays identical to `sampleCurve`, so the
|
|
21
|
+
* duplication cannot drift from the definition the web renders from.
|
|
22
|
+
*/
|
|
23
|
+
export declare function easingFromSamples(samples: readonly number[]): Easing;
|
|
24
|
+
/** Overshoots ~12%. For things that scale in place. */
|
|
25
|
+
export declare const easeSpring: Easing;
|
|
26
|
+
/** Critically damped, no overshoot. For things that travel inside bounds. */
|
|
27
|
+
export declare const easeSpringTight: Easing;
|
|
28
|
+
//# sourceMappingURL=easing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"easing.d.ts","sourceRoot":"","sources":["../../src/motion/easing.ts"],"names":[],"mappings":"AASA,yEAAyE;AACzE,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAkBpE;AAED,uDAAuD;AACvD,eAAO,MAAM,UAAU,QAAmC,CAAC;AAE3D,6EAA6E;AAC7E,eAAO,MAAM,eAAe,QAAwC,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* The sampled-spring easings, with no Reanimated import.
|
|
2
|
+
*
|
|
3
|
+
* Split from motion.ts for the same reason sliderValue.ts is split from
|
|
4
|
+
* Slider.tsx: this is arithmetic, it is where a bug hid, and importing
|
|
5
|
+
* react-native-reanimated drags in react-native-worklets, which cannot resolve
|
|
6
|
+
* under vitest. A file that cannot be imported cannot be tested.
|
|
7
|
+
*/
|
|
8
|
+
import { springSamples, springTightSamples } from "@gryt/theme";
|
|
9
|
+
/**
|
|
10
|
+
* Turn a sample list into an easing.
|
|
11
|
+
*
|
|
12
|
+
* The body is a worklet so it runs on the UI thread — an easing evaluated on
|
|
13
|
+
* the JS thread would drop frames under exactly the load this exists to
|
|
14
|
+
* survive. `points` is a plain array, which worklets capture by value.
|
|
15
|
+
*
|
|
16
|
+
* The interpolation is written out rather than calling `sampleCurve` from
|
|
17
|
+
* @gryt/theme, and that is not a style choice. A worklet cannot synchronously
|
|
18
|
+
* call a JS-thread function: doing so throws
|
|
19
|
+
*
|
|
20
|
+
* [Worklets] Tried to synchronously call a Remote Function
|
|
21
|
+
*
|
|
22
|
+
* on the first frame of every animation. That shipped in 0.3.0, because the
|
|
23
|
+
* only thing exercising the UI thread in testing used React Native's own
|
|
24
|
+
* easing rather than this one.
|
|
25
|
+
*
|
|
26
|
+
* easing.test.ts asserts this stays identical to `sampleCurve`, so the
|
|
27
|
+
* duplication cannot drift from the definition the web renders from.
|
|
28
|
+
*/
|
|
29
|
+
export function easingFromSamples(samples) {
|
|
30
|
+
const points = [...samples];
|
|
31
|
+
return (t) => {
|
|
32
|
+
"worklet";
|
|
33
|
+
const count = points.length;
|
|
34
|
+
if (count === 0)
|
|
35
|
+
return t;
|
|
36
|
+
if (t <= 0)
|
|
37
|
+
return points[0];
|
|
38
|
+
if (t >= 1)
|
|
39
|
+
return points[count - 1];
|
|
40
|
+
const span = (count - 1) * t;
|
|
41
|
+
const index = Math.floor(span);
|
|
42
|
+
const rest = span - index;
|
|
43
|
+
const from = points[index];
|
|
44
|
+
const to = points[index + 1] ?? from;
|
|
45
|
+
return from + (to - from) * rest;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Overshoots ~12%. For things that scale in place. */
|
|
49
|
+
export const easeSpring = easingFromSamples(springSamples);
|
|
50
|
+
/** Critically damped, no overshoot. For things that travel inside bounds. */
|
|
51
|
+
export const easeSpringTight = easingFromSamples(springTightSamples);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/motion/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AACvF,OAAO,EAAE,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type WithTimingConfig } from "react-native-reanimated";
|
|
2
|
+
import { easeSpring, easeSpringTight } from "./easing.js";
|
|
3
|
+
export { easeSpring, easeSpringTight };
|
|
4
|
+
export declare const durations: {
|
|
5
|
+
readonly spring: 500;
|
|
6
|
+
readonly springSoft: 700;
|
|
7
|
+
readonly fast: 150;
|
|
8
|
+
};
|
|
9
|
+
/** `withTiming` on the overshooting spring, at the standard duration. */
|
|
10
|
+
export declare function springy(to: number, config?: WithTimingConfig): number;
|
|
11
|
+
/** `withTiming` on the critically damped spring — travel that must stay in bounds. */
|
|
12
|
+
export declare function travel(to: number, config?: WithTimingConfig): number;
|
|
13
|
+
/** A colour or opacity change, which should not feel sprung. */
|
|
14
|
+
export declare function fade(to: number, config?: WithTimingConfig): number;
|
|
15
|
+
//# sourceMappingURL=motion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"motion.d.ts","sourceRoot":"","sources":["../../src/motion/motion.ts"],"names":[],"mappings":"AAYA,OAAO,EAAsB,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEpF,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEvD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC;AAEvC,eAAO,MAAM,SAAS;;;;CAAgB,CAAC;AAEvC,yEAAyE;AACzE,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,gBAAgB,UAG5D;AAED,sFAAsF;AACtF,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,gBAAgB,UAG3D;AAED,gEAAgE;AAChE,wBAAgB,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,gBAAgB,UAGzD"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* The web components' motion, on React Native.
|
|
2
|
+
*
|
|
3
|
+
* Deliberately not `withSpring`. The curve in @gryt/theme is a damped spring
|
|
4
|
+
* solved analytically and sampled — `theme.css` calls it "the real curve of a
|
|
5
|
+
* spring rather than a physics engine approximating one". Reanimated's
|
|
6
|
+
* `withSpring` is a physics engine, so using it would mean approximating an
|
|
7
|
+
* exact curve with the very thing that curve was chosen over, and then tuning
|
|
8
|
+
* damping constants until it looked close.
|
|
9
|
+
*
|
|
10
|
+
* `withTiming` plus an easing that interpolates the same samples at the same
|
|
11
|
+
* duration is both simpler and actually identical.
|
|
12
|
+
*/
|
|
13
|
+
import { Easing, withTiming } from "react-native-reanimated";
|
|
14
|
+
import { grytDurations } from "@gryt/theme";
|
|
15
|
+
import { easeSpring, easeSpringTight } from "./easing.js";
|
|
16
|
+
export { easeSpring, easeSpringTight };
|
|
17
|
+
export const durations = grytDurations;
|
|
18
|
+
/** `withTiming` on the overshooting spring, at the standard duration. */
|
|
19
|
+
export function springy(to, config) {
|
|
20
|
+
"worklet";
|
|
21
|
+
return withTiming(to, { duration: durations.spring, easing: easeSpring, ...config });
|
|
22
|
+
}
|
|
23
|
+
/** `withTiming` on the critically damped spring — travel that must stay in bounds. */
|
|
24
|
+
export function travel(to, config) {
|
|
25
|
+
"worklet";
|
|
26
|
+
return withTiming(to, { duration: durations.spring, easing: easeSpringTight, ...config });
|
|
27
|
+
}
|
|
28
|
+
/** A colour or opacity change, which should not feel sprung. */
|
|
29
|
+
export function fade(to, config) {
|
|
30
|
+
"worklet";
|
|
31
|
+
return withTiming(to, { duration: durations.fast, easing: Easing.out(Easing.quad), ...config });
|
|
32
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useAnimatedStyle } from "react-native-reanimated";
|
|
2
|
+
export interface PressScale {
|
|
3
|
+
onPressIn: () => void;
|
|
4
|
+
onPressOut: () => void;
|
|
5
|
+
style: ReturnType<typeof useAnimatedStyle>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Scale to `pressed` while held, spring back to 1 on release.
|
|
9
|
+
*
|
|
10
|
+
* Returns handlers to spread onto a Pressable and an animated style for the
|
|
11
|
+
* view that should scale. Both directions use the overshooting curve, so the
|
|
12
|
+
* release settles with the same texture the web has.
|
|
13
|
+
*/
|
|
14
|
+
export declare function usePressScale(pressed: number, disabled?: boolean): PressScale;
|
|
15
|
+
//# sourceMappingURL=usePressScale.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePressScale.d.ts","sourceRoot":"","sources":["../../src/motion/usePressScale.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,gBAAgB,EAAkB,MAAM,yBAAyB,CAAC;AAG3E,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,KAAK,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;CAC5C;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,UAAU,CAuB7E"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* The press half of the web's hover/active pair.
|
|
2
|
+
*
|
|
3
|
+
* `hover:scale-[1.03]` has no meaning on a touch screen and is deliberately
|
|
4
|
+
* not emulated — there is no state between "not touching" and "touching", so
|
|
5
|
+
* inventing one would be a difference from the web rather than a match to it.
|
|
6
|
+
* The `active:` half ports exactly.
|
|
7
|
+
*/
|
|
8
|
+
import { useCallback } from "react";
|
|
9
|
+
import { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
10
|
+
import { springy } from "./motion.js";
|
|
11
|
+
/**
|
|
12
|
+
* Scale to `pressed` while held, spring back to 1 on release.
|
|
13
|
+
*
|
|
14
|
+
* Returns handlers to spread onto a Pressable and an animated style for the
|
|
15
|
+
* view that should scale. Both directions use the overshooting curve, so the
|
|
16
|
+
* release settles with the same texture the web has.
|
|
17
|
+
*/
|
|
18
|
+
export function usePressScale(pressed, disabled) {
|
|
19
|
+
const scale = useSharedValue(1);
|
|
20
|
+
// A Reanimated shared value is mutated by design — assigning `.value` is how
|
|
21
|
+
// an animation is started, and the object identity never changes. The
|
|
22
|
+
// immutability rule reads it as a mutated binding, which is what the rule is
|
|
23
|
+
// for everywhere else and a false positive here. Scoped to these two
|
|
24
|
+
// assignments rather than the file.
|
|
25
|
+
/* eslint-disable react-hooks/immutability */
|
|
26
|
+
const onPressIn = useCallback(() => {
|
|
27
|
+
if (disabled)
|
|
28
|
+
return;
|
|
29
|
+
scale.value = springy(pressed);
|
|
30
|
+
}, [disabled, pressed, scale]);
|
|
31
|
+
const onPressOut = useCallback(() => {
|
|
32
|
+
if (disabled)
|
|
33
|
+
return;
|
|
34
|
+
scale.value = springy(1);
|
|
35
|
+
}, [disabled, scale]);
|
|
36
|
+
/* eslint-enable react-hooks/immutability */
|
|
37
|
+
const style = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }] }));
|
|
38
|
+
return { onPressIn, onPressOut, style };
|
|
39
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gryt/ui-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Gryt's design system on React Native. Same tokens as @gryt/ui, a different renderer.",
|
|
@@ -32,15 +32,18 @@
|
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"react": ">=18.3.0 || >=19.0.0",
|
|
35
|
-
"react-native": ">=0.76.0"
|
|
35
|
+
"react-native": ">=0.76.0",
|
|
36
|
+
"react-native-reanimated": ">=4.0.0"
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
|
-
"@gryt/theme": "^0.
|
|
39
|
+
"@gryt/theme": "^0.2.0"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
42
|
"@types/react": "^19.0.0",
|
|
42
43
|
"react": "^19.0.0",
|
|
43
44
|
"react-native": "^0.76.0",
|
|
44
|
-
"vitest": "^3.0.0"
|
|
45
|
-
|
|
45
|
+
"vitest": "^3.0.0",
|
|
46
|
+
"react-native-reanimated": "^4.5.1"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {}
|
|
46
49
|
}
|