@hzblj/kara-components 0.11.0 → 0.12.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/CHANGELOG.md +28 -0
- package/lib/module/atoms/box/box.js +3 -0
- package/lib/module/atoms/box/box.js.map +1 -1
- package/lib/module/atoms/box/index.js +0 -1
- package/lib/module/atoms/box/index.js.map +1 -1
- package/lib/module/atoms/index.js +1 -0
- package/lib/module/atoms/index.js.map +1 -1
- package/lib/module/atoms/pressable/pressable.js +62 -15
- package/lib/module/atoms/pressable/pressable.js.map +1 -1
- package/lib/module/atoms/rn-text/rn-text.js +66 -26
- package/lib/module/atoms/rn-text/rn-text.js.map +1 -1
- package/lib/module/atoms/skeleton/skeleton.js +3 -3
- package/lib/module/atoms/skeleton/skeleton.js.map +1 -1
- package/lib/module/atoms/stagger/index.js +5 -0
- package/lib/module/atoms/stagger/index.js.map +1 -0
- package/lib/module/atoms/stagger/stagger.js +102 -0
- package/lib/module/atoms/stagger/stagger.js.map +1 -0
- package/lib/module/atoms/stagger/stagger.types.js +4 -0
- package/lib/module/atoms/stagger/stagger.types.js.map +1 -0
- package/lib/module/molecules/scroll-edge-container/scroll-edge-container.android.js +3 -3
- package/lib/module/molecules/scroll-edge-container/scroll-edge-container.android.js.map +1 -1
- package/lib/module/organisms/snackbar/snackbar.js +32 -20
- package/lib/module/organisms/snackbar/snackbar.js.map +1 -1
- package/lib/typescript/atoms/box/box.d.ts +3 -0
- package/lib/typescript/atoms/box/index.d.ts +0 -1
- package/lib/typescript/atoms/index.d.ts +1 -0
- package/lib/typescript/atoms/pressable/pressable.types.d.ts +4 -3
- package/lib/typescript/atoms/rn-text/rn-text.types.d.ts +7 -4
- package/lib/typescript/atoms/stagger/index.d.ts +2 -0
- package/lib/typescript/atoms/stagger/stagger.d.ts +2 -0
- package/lib/typescript/atoms/stagger/stagger.types.d.ts +32 -0
- package/lib/typescript/organisms/snackbar/snackbar.types.d.ts +6 -8
- package/package.json +4 -4
- package/src/atoms/box/box.tsx +3 -0
- package/src/atoms/box/index.ts +0 -1
- package/src/atoms/index.ts +1 -0
- package/src/atoms/pressable/pressable.tsx +162 -106
- package/src/atoms/pressable/pressable.types.ts +4 -2
- package/src/atoms/rn-text/rn-text.tsx +101 -32
- package/src/atoms/rn-text/rn-text.types.ts +7 -3
- package/src/atoms/skeleton/skeleton.tsx +3 -3
- package/src/atoms/stagger/index.ts +2 -0
- package/src/atoms/stagger/stagger.tsx +98 -0
- package/src/atoms/stagger/stagger.types.ts +35 -0
- package/src/molecules/scroll-edge-container/scroll-edge-container.android.tsx +3 -3
- package/src/organisms/snackbar/snackbar.tsx +20 -40
- package/src/organisms/snackbar/snackbar.types.ts +6 -8
- package/lib/module/atoms/box/a-box.js +0 -6
- package/lib/module/atoms/box/a-box.js.map +0 -1
- package/lib/module/motion.js +0 -19
- package/lib/module/motion.js.map +0 -1
- package/lib/typescript/atoms/box/a-box.d.ts +0 -49
- package/lib/typescript/motion.d.ts +0 -8
- package/src/atoms/box/a-box.ts +0 -5
- package/src/motion.ts +0 -13
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { timing } from '@hzblj/kara-sx';
|
|
4
|
+
import { spacing, useReducedMotion } from '@hzblj/kara-theme';
|
|
4
5
|
import { useEffect, useRef } from 'react';
|
|
5
6
|
import { AccessibilityInfo, Platform } from 'react-native';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
7
|
+
import { withTiming } from 'react-native-reanimated';
|
|
8
|
+
import { Box } from "../../atoms/box/index.js";
|
|
8
9
|
import { Pressable } from "../../atoms/pressable/index.js";
|
|
9
10
|
import { RnText } from "../../atoms/rn-text/index.js";
|
|
10
11
|
import { RnGlyph } from "../../glyph/index.js";
|
|
11
|
-
import { timing } from "../../motion.js";
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
13
|
/** Material's `SnackbarDuration.Short` and `Long`. */
|
|
14
14
|
const DURATIONS = {
|
|
@@ -19,10 +19,9 @@ const PLATFORM_ANIMATION = Platform.OS === 'android' ? 'scale' : 'slide';
|
|
|
19
19
|
|
|
20
20
|
/** The close glyph at the label's height: an SF Symbol fills its box, Material's icon sits inside a padded one. */
|
|
21
21
|
const CLOSE_SIZE = Platform.OS === 'ios' ? 's12' : 's16';
|
|
22
|
-
const SMOOTH_OUT = Easing.bezier(...easing.smoothOut);
|
|
23
22
|
const GROW = timing('quick');
|
|
24
23
|
|
|
25
|
-
/** Material's snackbar enter: it fades in while it grows from 80 %, in 150 ms. */
|
|
24
|
+
/** Material's snackbar enter: it fades in while it grows from 80 %, in 150 ms. Further than kara's `scale` preset. */
|
|
26
25
|
const scaleIn = () => {
|
|
27
26
|
'worklet';
|
|
28
27
|
|
|
@@ -41,29 +40,42 @@ const scaleIn = () => {
|
|
|
41
40
|
}
|
|
42
41
|
};
|
|
43
42
|
};
|
|
44
|
-
|
|
45
43
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
44
|
+
* kara's motion presets, which arrive in `fast`, leave in `quick` and fade with motion reduced by themselves. A slide
|
|
45
|
+
* travels further than a preset's default: in from the edge it sits on, and back out toward it.
|
|
48
46
|
*/
|
|
49
47
|
const TRANSITIONS = {
|
|
50
48
|
fade: {
|
|
51
|
-
entering:
|
|
52
|
-
exiting:
|
|
49
|
+
entering: 'fade',
|
|
50
|
+
exiting: 'fade'
|
|
53
51
|
},
|
|
54
52
|
// Material's exit is a 75 ms fade: `micro`.
|
|
55
53
|
scale: {
|
|
56
54
|
entering: scaleIn,
|
|
57
|
-
exiting:
|
|
55
|
+
exiting: {
|
|
56
|
+
duration: 'micro',
|
|
57
|
+
preset: 'fade'
|
|
58
|
+
}
|
|
58
59
|
},
|
|
59
|
-
// Slide: in from the edge it sits on, and back out toward it.
|
|
60
60
|
slideBottom: {
|
|
61
|
-
entering:
|
|
62
|
-
|
|
61
|
+
entering: {
|
|
62
|
+
distance: 's24',
|
|
63
|
+
preset: 'fadeUp'
|
|
64
|
+
},
|
|
65
|
+
exiting: {
|
|
66
|
+
distance: 's24',
|
|
67
|
+
preset: 'fadeDown'
|
|
68
|
+
}
|
|
63
69
|
},
|
|
64
70
|
slideTop: {
|
|
65
|
-
entering:
|
|
66
|
-
|
|
71
|
+
entering: {
|
|
72
|
+
distance: 's24',
|
|
73
|
+
preset: 'fadeDown'
|
|
74
|
+
},
|
|
75
|
+
exiting: {
|
|
76
|
+
distance: 's24',
|
|
77
|
+
preset: 'fadeUp'
|
|
78
|
+
}
|
|
67
79
|
}
|
|
68
80
|
};
|
|
69
81
|
const transitionsFor = (animation, position) => {
|
|
@@ -118,9 +130,9 @@ export const Snackbar = ({
|
|
|
118
130
|
bottom: spacing[offset]
|
|
119
131
|
};
|
|
120
132
|
const hasTrailing = Boolean(actionLabel) || dismissible;
|
|
121
|
-
//
|
|
122
|
-
const preset = transitionsFor(reduced ? 'fade' : animation, position);
|
|
123
|
-
return /*#__PURE__*/_jsxs(
|
|
133
|
+
// The presets fade with motion reduced by themselves; Material's scale is a worklet of its own, so it is picked out.
|
|
134
|
+
const preset = transitionsFor(reduced && animation === 'scale' ? 'fade' : animation, position);
|
|
135
|
+
return /*#__PURE__*/_jsxs(Box, {
|
|
124
136
|
align: actionOnNewLine ? 'stretch' : 'center',
|
|
125
137
|
bg: "surface.high.contrast",
|
|
126
138
|
borderRadius: "medium",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["
|
|
1
|
+
{"version":3,"names":["timing","spacing","useReducedMotion","useEffect","useRef","AccessibilityInfo","Platform","withTiming","Box","Pressable","RnText","RnGlyph","jsx","_jsx","jsxs","_jsxs","DURATIONS","long","short","PLATFORM_ANIMATION","OS","CLOSE_SIZE","GROW","scaleIn","animations","opacity","transform","scale","initialValues","TRANSITIONS","fade","entering","exiting","duration","preset","slideBottom","distance","slideTop","transitionsFor","animation","position","millisecondsOf","undefined","Snackbar","actionLabel","actionOnNewLine","dismissible","message","offset","onAction","onDismiss","visible","reduced","dismiss","current","milliseconds","timer","setTimeout","clearTimeout","announceForAccessibility","edge","top","bottom","hasTrailing","Boolean","align","bg","borderRadius","flexDirection","gap","left","s16","px","py","right","shadow","children","color","flex","variant","alignSelf","onPress","pressEffect","ripple","accessibilityLabel","name","size"],"sourceRoot":"../../../../src","sources":["organisms/snackbar/snackbar.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -2,6 +2,9 @@ import { View } from 'react-native';
|
|
|
2
2
|
/**
|
|
3
3
|
* A React Native `View` that takes the design system's `sx` props. The `sx()` HOC is the whole
|
|
4
4
|
* implementation — Box is just its first caller, and any other style-taking component gets the same.
|
|
5
|
+
*
|
|
6
|
+
* That includes motion: `entering`, `exiting`, `layout`, `animate` or an animated `style` make it an `Animated.View`
|
|
7
|
+
* from its first frame, and without them it stays a plain `View`.
|
|
5
8
|
*/
|
|
6
9
|
export declare const Box: import("react").MemoExoticComponent<{
|
|
7
10
|
({ style, ...props }: import("@hzblj/kara-sx").SxProps<import("react-native").ViewProps & import("react").RefAttributes<View>>): import("react").JSX.Element;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AccessibilityProps, ResponsiveViewSx } from '@hzblj/kara-sx';
|
|
1
|
+
import type { AccessibilityProps, MotionProps, ResponsiveViewSx } from '@hzblj/kara-sx';
|
|
2
2
|
import type { OpacitySize } from '@hzblj/kara-theme';
|
|
3
3
|
import type { ReactNode } from 'react';
|
|
4
4
|
import type { Haptic } from '../../haptics';
|
|
@@ -9,9 +9,10 @@ export type PressEffect = 'none' | 'opacity' | 'scale';
|
|
|
9
9
|
* presses its own way by default: iOS eases the content down to 96%, Android draws Material's ripple over it,
|
|
10
10
|
* clipped to the pressable's shape. `pressEffect` picks the same answer on both, and Android keeps its ripple on top
|
|
11
11
|
* unless `ripple` is off. Give it the `borderRadius` of what it wraps (`full` around a `GlassView`). Screen readers read
|
|
12
|
-
* it as a button unless `accessibilityRole` names another role.
|
|
12
|
+
* it as a button unless `accessibilityRole` names another role. It takes the motion props a `Box` does: `animate` a
|
|
13
|
+
* selected state's `bg`, or let it arrive with `entering`. The React Native tier: it needs no host.
|
|
13
14
|
*/
|
|
14
|
-
export type PressableProps = ResponsiveViewSx & AccessibilityProps & {
|
|
15
|
+
export type PressableProps = ResponsiveViewSx & Omit<MotionProps, 'animated'> & AccessibilityProps & {
|
|
15
16
|
/** The pressable content. */
|
|
16
17
|
children?: ReactNode;
|
|
17
18
|
/** Ignores presses: no `onPress`, no haptic, no press effect or ripple. */
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import type { ResponsiveViewSx } from '@hzblj/kara-sx';
|
|
1
|
+
import type { MotionProps, ResponsiveViewSx } from '@hzblj/kara-sx';
|
|
2
2
|
import type { ColorToken, TextVariant } from '@hzblj/kara-theme';
|
|
3
3
|
import type { ReactNode } from 'react';
|
|
4
|
-
import type { TextProps as RNTextProps } from 'react-native';
|
|
4
|
+
import type { TextProps as RNTextProps, StyleProp, TextStyle } from 'react-native';
|
|
5
|
+
import type { AnimatedStyle } from 'react-native-reanimated';
|
|
5
6
|
/**
|
|
6
7
|
* React Native's `Text` in a theme typography variant: the React Native tier, for text outside a native host. It takes
|
|
7
8
|
* `Box`'s `sx` surface for its own box (margins, flex, padding, a background), all but `align`, which here aligns the
|
|
8
|
-
* lines.
|
|
9
|
+
* lines, and the motion props a `Box` takes: `entering="fadeUp"` makes it an `Animated.Text` by itself.
|
|
9
10
|
*/
|
|
10
|
-
export type RnTextProps = Omit<RNTextProps, 'style'> & Omit<ResponsiveViewSx, 'align'> & {
|
|
11
|
+
export type RnTextProps = Omit<RNTextProps, 'style'> & MotionProps & Omit<ResponsiveViewSx, 'align'> & {
|
|
11
12
|
/** Horizontal alignment, as React Native's `textAlign`. */
|
|
12
13
|
align?: 'center' | 'left' | 'right';
|
|
13
14
|
/** The text's colour. `content.primary` by default. */
|
|
14
15
|
color?: ColorToken;
|
|
16
|
+
/** Goes last, over the variant and the `sx`: an animated style from `useAnimatedStyle`, which it renders through Reanimated for. */
|
|
17
|
+
style?: StyleProp<AnimatedStyle<TextStyle>>;
|
|
15
18
|
/** The typography variant. `body` by default. */
|
|
16
19
|
variant?: TextVariant;
|
|
17
20
|
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { MotionPreset, MotionPresetConfig, ResponsiveViewSx } from '@hzblj/kara-sx';
|
|
2
|
+
import type { DurationSize, SpringName } from '@hzblj/kara-theme';
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
/** A preset by name, or one adjusted: `{preset: 'fadeUp', duration: 'slow'}`. Its `delay` is where the first one starts. */
|
|
5
|
+
export type StaggerMotion = MotionPreset | MotionPresetConfig;
|
|
6
|
+
/**
|
|
7
|
+
* A `Box` whose children arrive one after another: each takes `entering` a step later than the one before. Children
|
|
8
|
+
* that arrive together are staggered among themselves, the first ones as much as a later batch (the next page of a
|
|
9
|
+
* list), while a single child added later arrives at once. A `Box`, `RnText` or `Pressable` child takes the motion
|
|
10
|
+
* itself; any other child is wrapped in a `Box` that does. With motion reduced every child fades, still one after
|
|
11
|
+
* another. The React Native tier: it needs no host.
|
|
12
|
+
*/
|
|
13
|
+
export type StaggerProps = ResponsiveViewSx & {
|
|
14
|
+
/** What arrives. A child with an `entering` preset of its own keeps it and only takes the delay. */
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
/** How each child arrives. `fadeUp` by default. */
|
|
17
|
+
entering?: StaggerMotion;
|
|
18
|
+
/** How a child leaves when it is removed. They leave together, not one after another; none by default. */
|
|
19
|
+
exiting?: StaggerMotion;
|
|
20
|
+
/** Which end arrives first. `start` by default. */
|
|
21
|
+
from?: 'end' | 'start';
|
|
22
|
+
/** The step between one child and the next, a duration token. `stagger` (40 ms) by default, `micro` for a slower one. */
|
|
23
|
+
interval?: DurationSize;
|
|
24
|
+
/**
|
|
25
|
+
* Moves the other children into place when one arrives or leaves, and the stagger itself to its new size: `true` on
|
|
26
|
+
* the `smooth` spring, or a spring token. Off by default. Give what sits after it `layout` too, so it follows.
|
|
27
|
+
*/
|
|
28
|
+
layout?: boolean | SpringName;
|
|
29
|
+
/** Test identifier, forwarded to the view. */
|
|
30
|
+
testID?: string;
|
|
31
|
+
};
|
|
32
|
+
export type StaggerComponent = (props: StaggerProps) => ReactNode;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
import type { SxEntryExit } from '@hzblj/kara-sx';
|
|
1
2
|
import type { SpaceSize } from '@hzblj/kara-theme';
|
|
2
|
-
import type {
|
|
3
|
-
import type Animated from 'react-native-reanimated';
|
|
4
|
-
type LayoutAnimation = ComponentProps<typeof Animated.View>['entering'];
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
5
4
|
/** How it comes and goes. */
|
|
6
5
|
export type SnackbarAnimation = 'fade' | 'scale' | 'slide';
|
|
7
6
|
/** How long it stays: Material's short (4 s) and long (10 s), never, or a number of milliseconds. */
|
|
@@ -28,10 +27,10 @@ export type SnackbarProps = {
|
|
|
28
27
|
dismissible?: boolean;
|
|
29
28
|
/** How long it stays up before `onDismiss` is called. `indefinite` by default: it never hides itself. */
|
|
30
29
|
duration?: SnackbarDuration;
|
|
31
|
-
/**
|
|
32
|
-
entering?:
|
|
33
|
-
/**
|
|
34
|
-
exiting?:
|
|
30
|
+
/** How it arrives instead: a motion preset (`'scale'`), or Reanimated's own, e.g. `FadeInDown.springify()`. It wins over `animation`. */
|
|
31
|
+
entering?: SxEntryExit;
|
|
32
|
+
/** How it leaves instead: a motion preset, or Reanimated's own. It wins over `animation`. */
|
|
33
|
+
exiting?: SxEntryExit;
|
|
35
34
|
/** The message. */
|
|
36
35
|
message: string;
|
|
37
36
|
/** Between the snackbar and its edge, a space token. `s24` by default; raise it above a tab bar. */
|
|
@@ -46,4 +45,3 @@ export type SnackbarProps = {
|
|
|
46
45
|
visible: boolean;
|
|
47
46
|
};
|
|
48
47
|
export type SnackbarComponent = (props: SnackbarProps) => ReactNode;
|
|
49
|
-
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hzblj/kara-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "The atoms, molecules, organisms and templates of @hzblj/kara-ui — real SwiftUI and Jetpack Compose views through @expo/ui.",
|
|
5
5
|
"author": "Jan Blazej",
|
|
6
6
|
"license": "MIT",
|
|
@@ -59,9 +59,9 @@
|
|
|
59
59
|
]
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@hzblj/kara-native": "^0.
|
|
63
|
-
"@hzblj/kara-sx": "^0.
|
|
64
|
-
"@hzblj/kara-theme": "^0.
|
|
62
|
+
"@hzblj/kara-native": "^0.12.0",
|
|
63
|
+
"@hzblj/kara-sx": "^0.12.0",
|
|
64
|
+
"@hzblj/kara-theme": "^0.12.0",
|
|
65
65
|
"sf-symbols-typescript": "^2.2.0"
|
|
66
66
|
},
|
|
67
67
|
"peerDependencies": {
|
package/src/atoms/box/box.tsx
CHANGED
|
@@ -4,5 +4,8 @@ import {View} from 'react-native'
|
|
|
4
4
|
/**
|
|
5
5
|
* A React Native `View` that takes the design system's `sx` props. The `sx()` HOC is the whole
|
|
6
6
|
* implementation — Box is just its first caller, and any other style-taking component gets the same.
|
|
7
|
+
*
|
|
8
|
+
* That includes motion: `entering`, `exiting`, `layout`, `animate` or an animated `style` make it an `Animated.View`
|
|
9
|
+
* from its first frame, and without them it stays a plain `View`.
|
|
7
10
|
*/
|
|
8
11
|
export const Box = sx(View)
|
package/src/atoms/box/index.ts
CHANGED
package/src/atoms/index.ts
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
type AnimatableKey,
|
|
3
|
+
accessibilityProps,
|
|
4
|
+
markMotionComponent,
|
|
5
|
+
type SxAnimate,
|
|
6
|
+
splitAccessibility,
|
|
7
|
+
spring,
|
|
8
|
+
timing,
|
|
9
|
+
useSplitSxMotion,
|
|
10
|
+
} from '@hzblj/kara-sx'
|
|
11
|
+
import {opacity, type SpringName, springs, useAppTheme, useReducedMotion, withOpacity} from '@hzblj/kara-theme'
|
|
3
12
|
import {useCallback, useMemo} from 'react'
|
|
4
13
|
import {Platform, Pressable as RNPressable, type ViewStyle} from 'react-native'
|
|
5
|
-
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'
|
|
14
|
+
import Animated, {useAnimatedStyle, useSharedValue, withSpring, withTiming} from 'react-native-reanimated'
|
|
6
15
|
|
|
7
16
|
import {haptics} from '../../haptics'
|
|
8
|
-
import {timing} from '../../motion'
|
|
9
17
|
import {splitForRipple} from '../../ripple-clip'
|
|
10
18
|
import type {PressableComponent, PressableProps} from './pressable.types'
|
|
11
19
|
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
/**
|
|
21
|
+
* The scale follows the finger on springs, as UIKit's own presses do: down on `interactive`, back on `smooth`, neither
|
|
22
|
+
* with a bounce (a press is not a toy). A spring keeps the speed it has, so a tap let go halfway down eases back instead
|
|
23
|
+
* of turning on the spot, which a timed curve restarted from rest cannot do.
|
|
24
|
+
*/
|
|
25
|
+
const SCALE_IN = spring('interactive')
|
|
26
|
+
const SCALE_OUT = spring('smooth')
|
|
15
27
|
|
|
16
28
|
/** A dim comes in at once and leaves a little slower, as UIKit's own highlight does. */
|
|
17
29
|
const DIM_IN = timing('micro', 'out')
|
|
@@ -21,111 +33,155 @@ const PLATFORM_EFFECT = Platform.OS === 'ios' ? 'scale' : 'none'
|
|
|
21
33
|
|
|
22
34
|
const AnimatedPressable = Animated.createAnimatedComponent(RNPressable)
|
|
23
35
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
disabled = false,
|
|
27
|
-
haptic = false,
|
|
28
|
-
onPress,
|
|
29
|
-
pressEffect = PLATFORM_EFFECT,
|
|
30
|
-
pressedOpacity = 'o60',
|
|
31
|
-
pressedScale = 0.96,
|
|
32
|
-
ripple: withRipple = true,
|
|
33
|
-
testID,
|
|
34
|
-
...props
|
|
35
|
-
}: PressableProps) => {
|
|
36
|
-
const [accessibility, ownProps] = splitAccessibility(props)
|
|
37
|
-
const [resolved, rest] = useSplitSx(ownProps)
|
|
38
|
-
const reduced = useReducedMotion()
|
|
39
|
-
// With motion reduced the press still shows, and nothing moves: a scale dims instead.
|
|
40
|
-
const effect = reduced && pressEffect === 'scale' ? 'opacity' : pressEffect
|
|
41
|
-
// 0 at rest, 1 held: one value drives whichever effect is picked, so a change of effect needs no second animation.
|
|
42
|
-
const pressed = useSharedValue(0)
|
|
43
|
-
const feedback = haptic === true ? 'light' : haptic || undefined
|
|
44
|
-
const a11y = accessibilityProps(accessibility, 'button')
|
|
45
|
-
const dimmed = opacity[pressedOpacity]
|
|
46
|
-
|
|
47
|
-
const theme = useAppTheme()
|
|
48
|
-
const isAndroid = Platform.OS === 'android'
|
|
49
|
-
|
|
50
|
-
const rStyle = useAnimatedStyle(() => {
|
|
51
|
-
if (effect === 'scale') {
|
|
52
|
-
return {transform: [{scale: 1 - (1 - pressedScale) * pressed.value}]}
|
|
53
|
-
}
|
|
36
|
+
/** What `animate` covers by default, less the transform. */
|
|
37
|
+
const VISUAL_KEYS: readonly AnimatableKey[] = ['bg', 'borderColor', 'opacity']
|
|
54
38
|
|
|
55
|
-
|
|
56
|
-
}, [dimmed, effect, pressedScale])
|
|
57
|
-
|
|
58
|
-
// Android's press is Material's: a ripple over the content (the pressed state layer, `onSurface` at 10 %) clipped to
|
|
59
|
-
// the shape by an outer view (`splitForRipple`). The effect, when there is one, moves that outer view, ripple and all.
|
|
60
|
-
const ripple = useMemo(
|
|
61
|
-
() =>
|
|
62
|
-
isAndroid && withRipple
|
|
63
|
-
? {color: withOpacity(String(theme.color('content.primary')), 'o10'), foreground: true}
|
|
64
|
-
: undefined,
|
|
65
|
-
[isAndroid, theme, withRipple]
|
|
66
|
-
)
|
|
67
|
-
const androidStyle = useMemo(
|
|
68
|
-
() => (isAndroid ? splitForRipple(resolved as ViewStyle) : undefined),
|
|
69
|
-
[isAndroid, resolved]
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
// The finger is down: the Taptic Engine has until the release to wake, so the haptic lands with the press.
|
|
73
|
-
const onPressIn = useCallback(() => {
|
|
74
|
-
if (effect !== 'none') {
|
|
75
|
-
pressed.value = withTiming(1, effect === 'scale' ? SCALE_IN : DIM_IN)
|
|
76
|
-
}
|
|
39
|
+
const TRANSFORM_KEYS = new Set<AnimatableKey>(['offsetX', 'offsetY', 'rotate', 'scale'])
|
|
77
40
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
41
|
+
/**
|
|
42
|
+
* The press scale owns the transform, so `animate` leaves it alone: a transition on the same transform would take it
|
|
43
|
+
* back from the press at every re-render. A duration or spring token keeps its timing.
|
|
44
|
+
*/
|
|
45
|
+
const withoutTransform = (animate: SxAnimate | undefined): SxAnimate | undefined => {
|
|
46
|
+
if (animate === undefined || animate === false) {
|
|
47
|
+
return animate
|
|
48
|
+
}
|
|
82
49
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
50
|
+
if (animate === true) {
|
|
51
|
+
return {keys: VISUAL_KEYS}
|
|
52
|
+
}
|
|
86
53
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
54
|
+
if (typeof animate === 'string') {
|
|
55
|
+
return animate in springs
|
|
56
|
+
? {keys: VISUAL_KEYS, spring: animate as SpringName}
|
|
57
|
+
: {duration: animate as Exclude<typeof animate, SpringName>, keys: VISUAL_KEYS}
|
|
58
|
+
}
|
|
91
59
|
|
|
92
|
-
|
|
93
|
-
|
|
60
|
+
return {...animate, keys: (animate.keys ?? VISUAL_KEYS).filter(key => !TRANSFORM_KEYS.has(key))}
|
|
61
|
+
}
|
|
94
62
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
63
|
+
export const Pressable: PressableComponent = markMotionComponent(
|
|
64
|
+
({
|
|
65
|
+
children,
|
|
66
|
+
disabled = false,
|
|
67
|
+
haptic = false,
|
|
68
|
+
onPress,
|
|
69
|
+
pressEffect = PLATFORM_EFFECT,
|
|
70
|
+
pressedOpacity = 'o60',
|
|
71
|
+
pressedScale = 0.96,
|
|
72
|
+
ripple: withRipple = true,
|
|
73
|
+
testID,
|
|
74
|
+
...props
|
|
75
|
+
}: PressableProps) => {
|
|
76
|
+
const [accessibility, ownProps] = splitAccessibility(props)
|
|
77
|
+
const reduced = useReducedMotion()
|
|
78
|
+
// With motion reduced the press still shows, and nothing moves: a scale dims instead.
|
|
79
|
+
const effect = reduced && pressEffect === 'scale' ? 'opacity' : pressEffect
|
|
80
|
+
// Always a Reanimated view (the press effect is one), so the motion props need no latch here.
|
|
81
|
+
const [resolved, motion, rest] = useSplitSxMotion(
|
|
82
|
+
effect === 'scale' && ownProps.animate !== undefined
|
|
83
|
+
? {...ownProps, animate: withoutTransform(ownProps.animate as SxAnimate)}
|
|
84
|
+
: ownProps
|
|
85
|
+
)
|
|
86
|
+
// 0 at rest, 1 held: one value drives whichever effect is picked, so a change of effect needs no second animation.
|
|
87
|
+
const pressed = useSharedValue(0)
|
|
88
|
+
const feedback = haptic === true ? 'light' : haptic || undefined
|
|
89
|
+
const a11y = accessibilityProps(accessibility, 'button')
|
|
90
|
+
const dimmed = opacity[pressedOpacity]
|
|
91
|
+
|
|
92
|
+
const theme = useAppTheme()
|
|
93
|
+
const isAndroid = Platform.OS === 'android'
|
|
94
|
+
|
|
95
|
+
const rStyle = useAnimatedStyle(() => {
|
|
96
|
+
if (effect === 'scale') {
|
|
97
|
+
return {transform: [{scale: 1 - (1 - pressedScale) * pressed.value}]}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return effect === 'opacity' ? {opacity: 1 - (1 - dimmed) * pressed.value} : {}
|
|
101
|
+
}, [dimmed, effect, pressedScale])
|
|
102
|
+
|
|
103
|
+
// Android's press is Material's: a ripple over the content (the pressed state layer, `onSurface` at 10 %) clipped to
|
|
104
|
+
// the shape by an outer view (`splitForRipple`). The effect, when there is one, moves that outer view, ripple and all.
|
|
105
|
+
const ripple = useMemo(
|
|
106
|
+
() =>
|
|
107
|
+
isAndroid && withRipple
|
|
108
|
+
? {color: withOpacity(String(theme.color('content.primary')), 'o10'), foreground: true}
|
|
109
|
+
: undefined,
|
|
110
|
+
[isAndroid, theme, withRipple]
|
|
111
|
+
)
|
|
112
|
+
const androidStyle = useMemo(
|
|
113
|
+
() => (isAndroid ? splitForRipple(resolved as ViewStyle) : undefined),
|
|
114
|
+
[isAndroid, resolved]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
// The finger is down: the Taptic Engine has until the release to wake, so the haptic lands with the press.
|
|
118
|
+
const onPressIn = useCallback(() => {
|
|
119
|
+
if (effect === 'scale') {
|
|
120
|
+
pressed.value = withSpring(1, SCALE_IN)
|
|
121
|
+
} else if (effect === 'opacity') {
|
|
122
|
+
pressed.value = withTiming(1, DIM_IN)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (feedback) {
|
|
126
|
+
haptics.prepare(feedback)
|
|
127
|
+
}
|
|
128
|
+
}, [effect, feedback, pressed])
|
|
129
|
+
|
|
130
|
+
const onPressOut = useCallback(() => {
|
|
131
|
+
pressed.value = effect === 'scale' ? withSpring(0, SCALE_OUT) : withTiming(0, DIM_OUT)
|
|
132
|
+
}, [effect, pressed])
|
|
133
|
+
|
|
134
|
+
const onPressWithHaptic = useCallback(() => {
|
|
135
|
+
if (feedback) {
|
|
136
|
+
haptics.play(feedback)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
onPress?.()
|
|
140
|
+
}, [feedback, onPress])
|
|
141
|
+
|
|
142
|
+
if (androidStyle) {
|
|
143
|
+
return (
|
|
144
|
+
<Animated.View
|
|
145
|
+
entering={motion.entering}
|
|
146
|
+
exiting={motion.exiting}
|
|
147
|
+
layout={motion.layout}
|
|
148
|
+
style={[androidStyle.outer, motion.style, rStyle]}
|
|
109
149
|
>
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
150
|
+
<RNPressable
|
|
151
|
+
{...a11y}
|
|
152
|
+
android_ripple={ripple}
|
|
153
|
+
disabled={disabled}
|
|
154
|
+
hitSlop={8}
|
|
155
|
+
onPress={onPressWithHaptic}
|
|
156
|
+
onPressIn={onPressIn}
|
|
157
|
+
onPressOut={onPressOut}
|
|
158
|
+
style={androidStyle.inner}
|
|
159
|
+
testID={testID}
|
|
160
|
+
{...rest}
|
|
161
|
+
>
|
|
162
|
+
{children}
|
|
163
|
+
</RNPressable>
|
|
164
|
+
</Animated.View>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<AnimatedPressable
|
|
170
|
+
{...a11y}
|
|
171
|
+
disabled={disabled}
|
|
172
|
+
entering={motion.entering}
|
|
173
|
+
exiting={motion.exiting}
|
|
174
|
+
hitSlop={8}
|
|
175
|
+
layout={motion.layout}
|
|
176
|
+
onPress={onPressWithHaptic}
|
|
177
|
+
onPressIn={onPressIn}
|
|
178
|
+
onPressOut={onPressOut}
|
|
179
|
+
style={[resolved, motion.style, rStyle]}
|
|
180
|
+
testID={testID}
|
|
181
|
+
{...rest}
|
|
182
|
+
>
|
|
183
|
+
{children}
|
|
184
|
+
</AnimatedPressable>
|
|
113
185
|
)
|
|
114
186
|
}
|
|
115
|
-
|
|
116
|
-
return (
|
|
117
|
-
<AnimatedPressable
|
|
118
|
-
{...a11y}
|
|
119
|
-
disabled={disabled}
|
|
120
|
-
hitSlop={8}
|
|
121
|
-
onPress={onPressWithHaptic}
|
|
122
|
-
onPressIn={onPressIn}
|
|
123
|
-
onPressOut={onPressOut}
|
|
124
|
-
style={[resolved, rStyle]}
|
|
125
|
-
testID={testID}
|
|
126
|
-
{...rest}
|
|
127
|
-
>
|
|
128
|
-
{children}
|
|
129
|
-
</AnimatedPressable>
|
|
130
|
-
)
|
|
131
|
-
}
|
|
187
|
+
)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {AccessibilityProps, ResponsiveViewSx} from '@hzblj/kara-sx'
|
|
1
|
+
import type {AccessibilityProps, MotionProps, ResponsiveViewSx} from '@hzblj/kara-sx'
|
|
2
2
|
import type {OpacitySize} from '@hzblj/kara-theme'
|
|
3
3
|
import type {ReactNode} from 'react'
|
|
4
4
|
|
|
@@ -12,9 +12,11 @@ export type PressEffect = 'none' | 'opacity' | 'scale'
|
|
|
12
12
|
* presses its own way by default: iOS eases the content down to 96%, Android draws Material's ripple over it,
|
|
13
13
|
* clipped to the pressable's shape. `pressEffect` picks the same answer on both, and Android keeps its ripple on top
|
|
14
14
|
* unless `ripple` is off. Give it the `borderRadius` of what it wraps (`full` around a `GlassView`). Screen readers read
|
|
15
|
-
* it as a button unless `accessibilityRole` names another role.
|
|
15
|
+
* it as a button unless `accessibilityRole` names another role. It takes the motion props a `Box` does: `animate` a
|
|
16
|
+
* selected state's `bg`, or let it arrive with `entering`. The React Native tier: it needs no host.
|
|
16
17
|
*/
|
|
17
18
|
export type PressableProps = ResponsiveViewSx &
|
|
19
|
+
Omit<MotionProps, 'animated'> &
|
|
18
20
|
AccessibilityProps & {
|
|
19
21
|
/** The pressable content. */
|
|
20
22
|
children?: ReactNode
|