@oxyhq/bloom 0.27.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/animated-check/AnimatedCheck.js +162 -0
- package/lib/commonjs/animated-check/AnimatedCheck.js.map +1 -0
- package/lib/commonjs/animated-check/index.js +13 -0
- package/lib/commonjs/animated-check/index.js.map +1 -0
- package/lib/commonjs/dialog/placement.js +9 -7
- package/lib/commonjs/dialog/placement.js.map +1 -1
- package/lib/commonjs/fill/index.js.map +1 -1
- package/lib/commonjs/hooks/index.js +20 -0
- package/lib/commonjs/hooks/index.js.map +1 -1
- package/lib/commonjs/hooks/useGutters.js +106 -0
- package/lib/commonjs/hooks/useGutters.js.map +1 -0
- package/lib/commonjs/hooks/useHaptics.js +69 -0
- package/lib/commonjs/hooks/useHaptics.js.map +1 -0
- package/lib/commonjs/hooks/useInteractionState.js +8 -2
- package/lib/commonjs/hooks/useInteractionState.js.map +1 -1
- package/lib/commonjs/index.js +155 -63
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +155 -63
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/media-inset-border/MediaInsetBorder.js +50 -0
- package/lib/commonjs/media-inset-border/MediaInsetBorder.js.map +1 -0
- package/lib/commonjs/media-inset-border/index.js +13 -0
- package/lib/commonjs/media-inset-border/index.js.map +1 -0
- package/lib/commonjs/motion/ScreenTransition.js +46 -0
- package/lib/commonjs/motion/ScreenTransition.js.map +1 -0
- package/lib/commonjs/motion/index.js +32 -0
- package/lib/commonjs/motion/index.js.map +1 -0
- package/lib/commonjs/motion/motion.js +93 -0
- package/lib/commonjs/motion/motion.js.map +1 -0
- package/lib/commonjs/pressable-scale/PressableScale.js +75 -0
- package/lib/commonjs/pressable-scale/PressableScale.js.map +1 -0
- package/lib/commonjs/pressable-scale/index.js +13 -0
- package/lib/commonjs/pressable-scale/index.js.map +1 -0
- package/lib/commonjs/pressable-with-hover/PressableWithHover.js +36 -0
- package/lib/commonjs/pressable-with-hover/PressableWithHover.js.map +1 -0
- package/lib/commonjs/pressable-with-hover/index.js +13 -0
- package/lib/commonjs/pressable-with-hover/index.js.map +1 -0
- package/lib/commonjs/styles/breakpoints.js +24 -0
- package/lib/commonjs/styles/breakpoints.js.map +1 -0
- package/lib/commonjs/styles/index.js +7 -0
- package/lib/commonjs/styles/index.js.map +1 -1
- package/lib/commonjs/subtle-hover/SubtleHover.js +69 -0
- package/lib/commonjs/subtle-hover/SubtleHover.js.map +1 -0
- package/lib/commonjs/subtle-hover/index.js +13 -0
- package/lib/commonjs/subtle-hover/index.js.map +1 -0
- package/lib/commonjs/theme/build-theme.js +2 -0
- package/lib/commonjs/theme/build-theme.js.map +1 -1
- package/lib/commonjs/theme/gradients.js +39 -0
- package/lib/commonjs/theme/gradients.js.map +1 -0
- package/lib/commonjs/theme/index.js +7 -0
- package/lib/commonjs/theme/index.js.map +1 -1
- package/lib/module/animated-check/AnimatedCheck.js +157 -0
- package/lib/module/animated-check/AnimatedCheck.js.map +1 -0
- package/lib/module/animated-check/index.js +4 -0
- package/lib/module/animated-check/index.js.map +1 -0
- package/lib/module/dialog/placement.js +9 -7
- package/lib/module/dialog/placement.js.map +1 -1
- package/lib/module/fill/index.js.map +1 -1
- package/lib/module/hooks/index.js +2 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/useGutters.js +103 -0
- package/lib/module/hooks/useGutters.js.map +1 -0
- package/lib/module/hooks/useHaptics.js +65 -0
- package/lib/module/hooks/useHaptics.js.map +1 -0
- package/lib/module/hooks/useInteractionState.js +10 -3
- package/lib/module/hooks/useInteractionState.js.map +1 -1
- package/lib/module/index.js +10 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +10 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/media-inset-border/MediaInsetBorder.js +47 -0
- package/lib/module/media-inset-border/MediaInsetBorder.js.map +1 -0
- package/lib/module/media-inset-border/index.js +4 -0
- package/lib/module/media-inset-border/index.js.map +1 -0
- package/lib/module/motion/ScreenTransition.js +40 -0
- package/lib/module/motion/ScreenTransition.js.map +1 -0
- package/lib/module/motion/index.js +5 -0
- package/lib/module/motion/index.js.map +1 -0
- package/lib/module/motion/motion.js +87 -0
- package/lib/module/motion/motion.js.map +1 -0
- package/lib/module/pressable-scale/PressableScale.js +72 -0
- package/lib/module/pressable-scale/PressableScale.js.map +1 -0
- package/lib/module/pressable-scale/index.js +4 -0
- package/lib/module/pressable-scale/index.js.map +1 -0
- package/lib/module/pressable-with-hover/PressableWithHover.js +31 -0
- package/lib/module/pressable-with-hover/PressableWithHover.js.map +1 -0
- package/lib/module/pressable-with-hover/index.js +4 -0
- package/lib/module/pressable-with-hover/index.js.map +1 -0
- package/lib/module/styles/breakpoints.js +20 -0
- package/lib/module/styles/breakpoints.js.map +1 -0
- package/lib/module/styles/index.js +1 -0
- package/lib/module/styles/index.js.map +1 -1
- package/lib/module/subtle-hover/SubtleHover.js +66 -0
- package/lib/module/subtle-hover/SubtleHover.js.map +1 -0
- package/lib/module/subtle-hover/index.js +4 -0
- package/lib/module/subtle-hover/index.js.map +1 -0
- package/lib/module/theme/build-theme.js +2 -0
- package/lib/module/theme/build-theme.js.map +1 -1
- package/lib/module/theme/gradients.js +35 -0
- package/lib/module/theme/gradients.js.map +1 -0
- package/lib/module/theme/index.js +1 -0
- package/lib/module/theme/index.js.map +1 -1
- package/lib/typescript/commonjs/animated-check/AnimatedCheck.d.ts +18 -0
- package/lib/typescript/commonjs/animated-check/AnimatedCheck.d.ts.map +1 -0
- package/lib/typescript/commonjs/animated-check/index.d.ts +3 -0
- package/lib/typescript/commonjs/animated-check/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/dialog/placement.d.ts +8 -7
- package/lib/typescript/commonjs/dialog/placement.d.ts.map +1 -1
- package/lib/typescript/commonjs/fill/index.d.ts +2 -2
- package/lib/typescript/commonjs/fill/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/index.d.ts +4 -0
- package/lib/typescript/commonjs/hooks/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/useGutters.d.ts +32 -0
- package/lib/typescript/commonjs/hooks/useGutters.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/useHaptics.d.ts +28 -0
- package/lib/typescript/commonjs/hooks/useHaptics.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/useInteractionState.d.ts +6 -0
- package/lib/typescript/commonjs/hooks/useInteractionState.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +16 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +16 -0
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/media-inset-border/MediaInsetBorder.d.ts +26 -0
- package/lib/typescript/commonjs/media-inset-border/MediaInsetBorder.d.ts.map +1 -0
- package/lib/typescript/commonjs/media-inset-border/index.d.ts +3 -0
- package/lib/typescript/commonjs/media-inset-border/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/motion/ScreenTransition.d.ts +33 -0
- package/lib/typescript/commonjs/motion/ScreenTransition.d.ts.map +1 -0
- package/lib/typescript/commonjs/motion/index.d.ts +4 -0
- package/lib/typescript/commonjs/motion/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/motion/motion.d.ts +24 -0
- package/lib/typescript/commonjs/motion/motion.d.ts.map +1 -0
- package/lib/typescript/commonjs/pressable-scale/PressableScale.d.ts +21 -0
- package/lib/typescript/commonjs/pressable-scale/PressableScale.d.ts.map +1 -0
- package/lib/typescript/commonjs/pressable-scale/index.d.ts +3 -0
- package/lib/typescript/commonjs/pressable-scale/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/pressable-with-hover/PressableWithHover.d.ts +18 -0
- package/lib/typescript/commonjs/pressable-with-hover/PressableWithHover.d.ts.map +1 -0
- package/lib/typescript/commonjs/pressable-with-hover/index.d.ts +3 -0
- package/lib/typescript/commonjs/pressable-with-hover/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/styles/breakpoints.d.ts +18 -0
- package/lib/typescript/commonjs/styles/breakpoints.d.ts.map +1 -0
- package/lib/typescript/commonjs/styles/index.d.ts +2 -0
- package/lib/typescript/commonjs/styles/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/subtle-hover/SubtleHover.d.ts +32 -0
- package/lib/typescript/commonjs/subtle-hover/SubtleHover.d.ts.map +1 -0
- package/lib/typescript/commonjs/subtle-hover/index.d.ts +3 -0
- package/lib/typescript/commonjs/subtle-hover/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/build-theme.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/gradients.d.ts +12 -0
- package/lib/typescript/commonjs/theme/gradients.d.ts.map +1 -0
- package/lib/typescript/commonjs/theme/index.d.ts +2 -1
- package/lib/typescript/commonjs/theme/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/types.d.ts +11 -0
- package/lib/typescript/commonjs/theme/types.d.ts.map +1 -1
- package/lib/typescript/module/animated-check/AnimatedCheck.d.ts +18 -0
- package/lib/typescript/module/animated-check/AnimatedCheck.d.ts.map +1 -0
- package/lib/typescript/module/animated-check/index.d.ts +3 -0
- package/lib/typescript/module/animated-check/index.d.ts.map +1 -0
- package/lib/typescript/module/dialog/placement.d.ts +8 -7
- package/lib/typescript/module/dialog/placement.d.ts.map +1 -1
- package/lib/typescript/module/fill/index.d.ts +2 -2
- package/lib/typescript/module/fill/index.d.ts.map +1 -1
- package/lib/typescript/module/hooks/index.d.ts +4 -0
- package/lib/typescript/module/hooks/index.d.ts.map +1 -1
- package/lib/typescript/module/hooks/useGutters.d.ts +32 -0
- package/lib/typescript/module/hooks/useGutters.d.ts.map +1 -0
- package/lib/typescript/module/hooks/useHaptics.d.ts +28 -0
- package/lib/typescript/module/hooks/useHaptics.d.ts.map +1 -0
- package/lib/typescript/module/hooks/useInteractionState.d.ts +6 -0
- package/lib/typescript/module/hooks/useInteractionState.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +16 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +16 -0
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/media-inset-border/MediaInsetBorder.d.ts +26 -0
- package/lib/typescript/module/media-inset-border/MediaInsetBorder.d.ts.map +1 -0
- package/lib/typescript/module/media-inset-border/index.d.ts +3 -0
- package/lib/typescript/module/media-inset-border/index.d.ts.map +1 -0
- package/lib/typescript/module/motion/ScreenTransition.d.ts +33 -0
- package/lib/typescript/module/motion/ScreenTransition.d.ts.map +1 -0
- package/lib/typescript/module/motion/index.d.ts +4 -0
- package/lib/typescript/module/motion/index.d.ts.map +1 -0
- package/lib/typescript/module/motion/motion.d.ts +24 -0
- package/lib/typescript/module/motion/motion.d.ts.map +1 -0
- package/lib/typescript/module/pressable-scale/PressableScale.d.ts +21 -0
- package/lib/typescript/module/pressable-scale/PressableScale.d.ts.map +1 -0
- package/lib/typescript/module/pressable-scale/index.d.ts +3 -0
- package/lib/typescript/module/pressable-scale/index.d.ts.map +1 -0
- package/lib/typescript/module/pressable-with-hover/PressableWithHover.d.ts +18 -0
- package/lib/typescript/module/pressable-with-hover/PressableWithHover.d.ts.map +1 -0
- package/lib/typescript/module/pressable-with-hover/index.d.ts +3 -0
- package/lib/typescript/module/pressable-with-hover/index.d.ts.map +1 -0
- package/lib/typescript/module/styles/breakpoints.d.ts +18 -0
- package/lib/typescript/module/styles/breakpoints.d.ts.map +1 -0
- package/lib/typescript/module/styles/index.d.ts +2 -0
- package/lib/typescript/module/styles/index.d.ts.map +1 -1
- package/lib/typescript/module/subtle-hover/SubtleHover.d.ts +32 -0
- package/lib/typescript/module/subtle-hover/SubtleHover.d.ts.map +1 -0
- package/lib/typescript/module/subtle-hover/index.d.ts +3 -0
- package/lib/typescript/module/subtle-hover/index.d.ts.map +1 -0
- package/lib/typescript/module/theme/build-theme.d.ts.map +1 -1
- package/lib/typescript/module/theme/gradients.d.ts +12 -0
- package/lib/typescript/module/theme/gradients.d.ts.map +1 -0
- package/lib/typescript/module/theme/index.d.ts +2 -1
- package/lib/typescript/module/theme/index.d.ts.map +1 -1
- package/lib/typescript/module/theme/types.d.ts +11 -0
- package/lib/typescript/module/theme/types.d.ts.map +1 -1
- package/package.json +72 -1
- package/src/__tests__/AnimatedCheck.test.tsx +31 -0
- package/src/__tests__/MediaInsetBorder.test.tsx +36 -0
- package/src/__tests__/Motion.test.tsx +58 -0
- package/src/__tests__/PressableScale.test.tsx +29 -0
- package/src/__tests__/PressableWithHover.test.tsx +48 -0
- package/src/__tests__/SubtleHover.test.tsx +42 -0
- package/src/__tests__/theme-gradients.test.ts +31 -0
- package/src/__tests__/theme.test.ts +3 -0
- package/src/__tests__/useGutters.test.ts +58 -0
- package/src/__tests__/useHaptics.test.tsx +58 -0
- package/src/__tests__/useInteractionState.test.ts +27 -0
- package/src/animated-check/AnimatedCheck.stories.tsx +60 -0
- package/src/animated-check/AnimatedCheck.tsx +173 -0
- package/src/animated-check/index.ts +2 -0
- package/src/dialog/placement.ts +10 -7
- package/src/fill/index.tsx +2 -2
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useGutters.ts +99 -0
- package/src/hooks/useHaptics.tsx +69 -0
- package/src/hooks/useInteractionState.ts +8 -2
- package/src/index.ts +20 -0
- package/src/index.web.ts +20 -0
- package/src/media-inset-border/MediaInsetBorder.stories.tsx +60 -0
- package/src/media-inset-border/MediaInsetBorder.tsx +72 -0
- package/src/media-inset-border/index.ts +2 -0
- package/src/motion/Motion.stories.tsx +99 -0
- package/src/motion/ScreenTransition.tsx +71 -0
- package/src/motion/index.ts +3 -0
- package/src/motion/motion.ts +75 -0
- package/src/pressable-scale/PressableScale.stories.tsx +71 -0
- package/src/pressable-scale/PressableScale.tsx +103 -0
- package/src/pressable-scale/index.ts +2 -0
- package/src/pressable-with-hover/PressableWithHover.stories.tsx +43 -0
- package/src/pressable-with-hover/PressableWithHover.tsx +43 -0
- package/src/pressable-with-hover/index.ts +2 -0
- package/src/styles/breakpoints.ts +18 -0
- package/src/styles/index.ts +2 -0
- package/src/subtle-hover/SubtleHover.stories.tsx +72 -0
- package/src/subtle-hover/SubtleHover.tsx +90 -0
- package/src/subtle-hover/index.ts +2 -0
- package/src/theme/build-theme.ts +2 -0
- package/src/theme/gradients.ts +61 -0
- package/src/theme/index.ts +2 -1
- package/src/theme/types.ts +13 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Pressable, Text, View } from 'react-native';
|
|
3
|
+
import Animated from 'react-native-reanimated';
|
|
4
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
5
|
+
|
|
6
|
+
import { ScaleAndFadeIn, ScaleAndFadeOut, ScreenTransition, ShrinkAndPop } from './index';
|
|
7
|
+
|
|
8
|
+
const meta: Meta = {
|
|
9
|
+
title: 'Motion/Presets',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
|
|
14
|
+
type Story = StoryObj;
|
|
15
|
+
|
|
16
|
+
function Toggle({ children }: { children: React.ReactNode }) {
|
|
17
|
+
const [shown, setShown] = useState(true);
|
|
18
|
+
return (
|
|
19
|
+
<View style={{ gap: 16, alignItems: 'flex-start' }}>
|
|
20
|
+
<Pressable
|
|
21
|
+
accessibilityLabel="Toggle"
|
|
22
|
+
onPress={() => setShown((v) => !v)}
|
|
23
|
+
style={{
|
|
24
|
+
paddingVertical: 8,
|
|
25
|
+
paddingHorizontal: 16,
|
|
26
|
+
borderRadius: 8,
|
|
27
|
+
backgroundColor: '#111827',
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
<Text style={{ color: 'white', fontWeight: '600' }}>{shown ? 'Hide' : 'Show'}</Text>
|
|
31
|
+
</Pressable>
|
|
32
|
+
<View style={{ height: 96, justifyContent: 'center' }}>{shown ? children : null}</View>
|
|
33
|
+
</View>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const card = {
|
|
38
|
+
width: 160,
|
|
39
|
+
height: 72,
|
|
40
|
+
borderRadius: 16,
|
|
41
|
+
backgroundColor: '#6366f1',
|
|
42
|
+
alignItems: 'center',
|
|
43
|
+
justifyContent: 'center',
|
|
44
|
+
} as const;
|
|
45
|
+
|
|
46
|
+
export const ScaleAndFade: Story = {
|
|
47
|
+
render: () => (
|
|
48
|
+
<Toggle>
|
|
49
|
+
<Animated.View entering={ScaleAndFadeIn} exiting={ScaleAndFadeOut} style={card}>
|
|
50
|
+
<Text style={{ color: 'white', fontWeight: '700' }}>Scale + Fade</Text>
|
|
51
|
+
</Animated.View>
|
|
52
|
+
</Toggle>
|
|
53
|
+
),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const Pop: Story = {
|
|
57
|
+
render: () => (
|
|
58
|
+
<Toggle>
|
|
59
|
+
<Animated.View entering={ScaleAndFadeIn} exiting={ShrinkAndPop} style={card}>
|
|
60
|
+
<Text style={{ color: 'white', fontWeight: '700' }}>Shrink + Pop</Text>
|
|
61
|
+
</Animated.View>
|
|
62
|
+
</Toggle>
|
|
63
|
+
),
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const Transition: Story = {
|
|
67
|
+
render: () => {
|
|
68
|
+
function Demo() {
|
|
69
|
+
const [forward, setForward] = useState(true);
|
|
70
|
+
return (
|
|
71
|
+
<View style={{ gap: 16, alignItems: 'flex-start' }}>
|
|
72
|
+
<Pressable
|
|
73
|
+
accessibilityLabel="Swap direction"
|
|
74
|
+
onPress={() => setForward((v) => !v)}
|
|
75
|
+
style={{
|
|
76
|
+
paddingVertical: 8,
|
|
77
|
+
paddingHorizontal: 16,
|
|
78
|
+
borderRadius: 8,
|
|
79
|
+
backgroundColor: '#111827',
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<Text style={{ color: 'white', fontWeight: '600' }}>
|
|
83
|
+
Direction: {forward ? 'forward' : 'backward'}
|
|
84
|
+
</Text>
|
|
85
|
+
</Pressable>
|
|
86
|
+
<ScreenTransition
|
|
87
|
+
key={forward ? 'forward' : 'backward'}
|
|
88
|
+
direction={forward ? 'forward' : 'backward'}
|
|
89
|
+
enabledWeb
|
|
90
|
+
style={card}
|
|
91
|
+
>
|
|
92
|
+
<Text style={{ color: 'white', fontWeight: '700' }}>Screen</Text>
|
|
93
|
+
</ScreenTransition>
|
|
94
|
+
</View>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return <Demo />;
|
|
98
|
+
},
|
|
99
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Platform, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
import Animated, {
|
|
4
|
+
Easing,
|
|
5
|
+
FadeIn,
|
|
6
|
+
FadeOut,
|
|
7
|
+
SlideInLeft,
|
|
8
|
+
SlideInRight,
|
|
9
|
+
} from 'react-native-reanimated';
|
|
10
|
+
|
|
11
|
+
/** Direction the incoming screen travels. */
|
|
12
|
+
export type ScreenTransitionDirection = 'forward' | 'backward';
|
|
13
|
+
|
|
14
|
+
export interface ScreenTransitionProps {
|
|
15
|
+
/**
|
|
16
|
+
* `'forward'` slides the new screen in from the right, `'backward'` from the
|
|
17
|
+
* left — matching a push / pop navigation gesture.
|
|
18
|
+
*/
|
|
19
|
+
direction: ScreenTransitionDirection;
|
|
20
|
+
/**
|
|
21
|
+
* Enable the transition on web. Off by default: Reanimated layout animations
|
|
22
|
+
* are fragile across web bundlers, so web opts in explicitly and gets a
|
|
23
|
+
* lightweight cross-fade rather than a slide.
|
|
24
|
+
*/
|
|
25
|
+
enabledWeb?: boolean;
|
|
26
|
+
style?: StyleProp<ViewStyle>;
|
|
27
|
+
children?: React.ReactNode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** CSS-smooth deceleration for the native slide. */
|
|
31
|
+
const SLIDE_EASING = Easing.out(Easing.exp);
|
|
32
|
+
/** Web cross-fade duration (ms) — "totally vibes based", per the Bluesky original. */
|
|
33
|
+
const WEB_FADE_DURATION = 90;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Wraps `children` in a Reanimated layout-animated view that slides in
|
|
37
|
+
* directionally on native and (optionally) cross-fades on web. Ported from
|
|
38
|
+
* Bluesky's `ScreenTransition`.
|
|
39
|
+
*
|
|
40
|
+
* On web the slide is intentionally dropped: layout animations there are
|
|
41
|
+
* unreliable, so `enabledWeb` gates a simple fade and the default is no
|
|
42
|
+
* transition at all.
|
|
43
|
+
*/
|
|
44
|
+
export function ScreenTransition({
|
|
45
|
+
direction,
|
|
46
|
+
enabledWeb,
|
|
47
|
+
style,
|
|
48
|
+
children,
|
|
49
|
+
}: ScreenTransitionProps) {
|
|
50
|
+
const isWeb = Platform.OS === 'web';
|
|
51
|
+
|
|
52
|
+
const entering = isWeb
|
|
53
|
+
? enabledWeb
|
|
54
|
+
? FadeIn.duration(WEB_FADE_DURATION)
|
|
55
|
+
: undefined
|
|
56
|
+
: (direction === 'forward' ? SlideInRight : SlideInLeft).easing(SLIDE_EASING);
|
|
57
|
+
|
|
58
|
+
const exiting = isWeb
|
|
59
|
+
? enabledWeb
|
|
60
|
+
? FadeOut.duration(WEB_FADE_DURATION)
|
|
61
|
+
: undefined
|
|
62
|
+
: FadeOut.duration(WEB_FADE_DURATION);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Animated.View entering={entering} exiting={exiting} style={style}>
|
|
66
|
+
{children}
|
|
67
|
+
</Animated.View>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
ScreenTransition.displayName = 'ScreenTransition';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
withDelay,
|
|
3
|
+
withSequence,
|
|
4
|
+
withTiming,
|
|
5
|
+
type LayoutAnimation,
|
|
6
|
+
} from 'react-native-reanimated';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Reanimated custom layout-animation presets. Each is a worklet builder
|
|
10
|
+
* compatible with an `Animated.View`'s `entering` / `exiting` prop:
|
|
11
|
+
*
|
|
12
|
+
* ```tsx
|
|
13
|
+
* <Animated.View entering={ScaleAndFadeIn} exiting={ScaleAndFadeOut} />
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Ported from Bluesky's custom-animation set. The `'worklet'` directive is
|
|
17
|
+
* preserved through Bloom's build; the consuming app's Metro + reanimated Babel
|
|
18
|
+
* plugin transforms it so the animation runs on the UI thread (same path the
|
|
19
|
+
* existing `bottom-sheet` gesture worklets rely on).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** Entering: fade in while growing from 70% to full size. */
|
|
23
|
+
export const ScaleAndFadeIn: () => LayoutAnimation = () => {
|
|
24
|
+
'worklet';
|
|
25
|
+
return {
|
|
26
|
+
animations: {
|
|
27
|
+
opacity: withTiming(1),
|
|
28
|
+
transform: [{ scale: withTiming(1) }],
|
|
29
|
+
},
|
|
30
|
+
initialValues: {
|
|
31
|
+
opacity: 0,
|
|
32
|
+
transform: [{ scale: 0.7 }],
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Exiting counterpart to {@link ScaleAndFadeIn}: fade out while shrinking to 70%. */
|
|
38
|
+
export const ScaleAndFadeOut: () => LayoutAnimation = () => {
|
|
39
|
+
'worklet';
|
|
40
|
+
return {
|
|
41
|
+
animations: {
|
|
42
|
+
opacity: withTiming(0),
|
|
43
|
+
transform: [{ scale: withTiming(0.7) }],
|
|
44
|
+
},
|
|
45
|
+
initialValues: {
|
|
46
|
+
opacity: 1,
|
|
47
|
+
transform: [{ scale: 1 }],
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Exiting with a brief "pop": the scale dips to 70%, overshoots to 110%, and the
|
|
54
|
+
* element fades out. Good for confirming a tapped or removed item.
|
|
55
|
+
*/
|
|
56
|
+
export const ShrinkAndPop: () => LayoutAnimation = () => {
|
|
57
|
+
'worklet';
|
|
58
|
+
return {
|
|
59
|
+
animations: {
|
|
60
|
+
opacity: withDelay(125, withTiming(0, { duration: 125 })),
|
|
61
|
+
transform: [
|
|
62
|
+
{
|
|
63
|
+
scale: withSequence(
|
|
64
|
+
withTiming(0.7, { duration: 75 }),
|
|
65
|
+
withTiming(1.1, { duration: 150 }),
|
|
66
|
+
),
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
initialValues: {
|
|
71
|
+
opacity: 1,
|
|
72
|
+
transform: [{ scale: 1 }],
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text, View } from 'react-native';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
4
|
+
|
|
5
|
+
import { PressableScale } from './index';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof PressableScale> = {
|
|
8
|
+
title: 'Components/PressableScale',
|
|
9
|
+
component: PressableScale,
|
|
10
|
+
argTypes: {
|
|
11
|
+
targetScale: { control: { type: 'number', min: 0.8, max: 1, step: 0.01 } },
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
|
|
17
|
+
type Story = StoryObj<typeof PressableScale>;
|
|
18
|
+
|
|
19
|
+
export const Basic: Story = {
|
|
20
|
+
render: (args) => (
|
|
21
|
+
<PressableScale
|
|
22
|
+
{...args}
|
|
23
|
+
accessibilityLabel="Press me"
|
|
24
|
+
contentContainerStyle={{
|
|
25
|
+
paddingVertical: 12,
|
|
26
|
+
paddingHorizontal: 20,
|
|
27
|
+
borderRadius: 12,
|
|
28
|
+
backgroundColor: '#111827',
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
<Text style={{ color: '#fff', fontWeight: '600' }}>Press me</Text>
|
|
32
|
+
</PressableScale>
|
|
33
|
+
),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const StrongScale: Story = {
|
|
37
|
+
args: { targetScale: 0.9 },
|
|
38
|
+
render: (args) => (
|
|
39
|
+
<PressableScale
|
|
40
|
+
{...args}
|
|
41
|
+
accessibilityLabel="Press me harder"
|
|
42
|
+
contentContainerStyle={{
|
|
43
|
+
paddingVertical: 12,
|
|
44
|
+
paddingHorizontal: 20,
|
|
45
|
+
borderRadius: 12,
|
|
46
|
+
backgroundColor: '#4f46e5',
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
<Text style={{ color: '#fff', fontWeight: '600' }}>Strong scale (0.9)</Text>
|
|
50
|
+
</PressableScale>
|
|
51
|
+
),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const Card: Story = {
|
|
55
|
+
render: (args) => (
|
|
56
|
+
<PressableScale {...args} accessibilityLabel="Open card">
|
|
57
|
+
<View
|
|
58
|
+
style={{
|
|
59
|
+
width: 220,
|
|
60
|
+
padding: 16,
|
|
61
|
+
borderRadius: 16,
|
|
62
|
+
backgroundColor: '#f3f4f6',
|
|
63
|
+
gap: 6,
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
<Text style={{ fontWeight: '700' }}>Tappable card</Text>
|
|
67
|
+
<Text style={{ color: '#6b7280' }}>The whole card springs on press.</Text>
|
|
68
|
+
</View>
|
|
69
|
+
</PressableScale>
|
|
70
|
+
),
|
|
71
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Platform,
|
|
4
|
+
Pressable,
|
|
5
|
+
type PressableProps,
|
|
6
|
+
type StyleProp,
|
|
7
|
+
type View,
|
|
8
|
+
type ViewStyle,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import Animated, {
|
|
11
|
+
cancelAnimation,
|
|
12
|
+
useAnimatedStyle,
|
|
13
|
+
useReducedMotion,
|
|
14
|
+
useSharedValue,
|
|
15
|
+
withTiming,
|
|
16
|
+
} from 'react-native-reanimated';
|
|
17
|
+
|
|
18
|
+
/** Press-in / press-out timing, in ms. */
|
|
19
|
+
const DURATION = 100;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* True on touch-capable web browsers (coarse pointer). The press-scale
|
|
23
|
+
* affordance only makes sense where a finger obscures the element — with a
|
|
24
|
+
* mouse it reads as jitter — so it is disabled on non-touch web, matching the
|
|
25
|
+
* native / web-touch behaviour.
|
|
26
|
+
*/
|
|
27
|
+
const IS_WEB_TOUCH_DEVICE =
|
|
28
|
+
Platform.OS === 'web' &&
|
|
29
|
+
typeof window !== 'undefined' &&
|
|
30
|
+
typeof window.matchMedia === 'function' &&
|
|
31
|
+
window.matchMedia('(pointer: coarse)').matches;
|
|
32
|
+
|
|
33
|
+
const SCALE_SUPPORTED = Platform.OS !== 'web' || IS_WEB_TOUCH_DEVICE;
|
|
34
|
+
|
|
35
|
+
export interface PressableScaleProps
|
|
36
|
+
extends Omit<PressableProps, 'children' | 'style'> {
|
|
37
|
+
/** Scale applied while pressed. Defaults to `0.98`. */
|
|
38
|
+
targetScale?: number;
|
|
39
|
+
/** Style for the outer, full-size touch target (stays put while pressing). */
|
|
40
|
+
style?: StyleProp<ViewStyle>;
|
|
41
|
+
/** Style for the inner content wrapper that actually scales. */
|
|
42
|
+
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
43
|
+
children?: React.ReactNode;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A drop-in {@link Pressable} whose content springs to a smaller scale on
|
|
48
|
+
* press-in and back on release. The outer pressable stays full size (so the
|
|
49
|
+
* touch target never shrinks) while an inner wrapper scales.
|
|
50
|
+
*
|
|
51
|
+
* Honours the OS "reduce motion" setting and disables the effect on non-touch
|
|
52
|
+
* web pointers.
|
|
53
|
+
*/
|
|
54
|
+
export const PressableScale = forwardRef<View, PressableScaleProps>(
|
|
55
|
+
function PressableScale(
|
|
56
|
+
{
|
|
57
|
+
targetScale = 0.98,
|
|
58
|
+
style,
|
|
59
|
+
contentContainerStyle,
|
|
60
|
+
children,
|
|
61
|
+
onPressIn,
|
|
62
|
+
onPressOut,
|
|
63
|
+
...rest
|
|
64
|
+
},
|
|
65
|
+
ref,
|
|
66
|
+
) {
|
|
67
|
+
const reducedMotion = useReducedMotion();
|
|
68
|
+
const animate = SCALE_SUPPORTED && !reducedMotion;
|
|
69
|
+
|
|
70
|
+
const scale = useSharedValue(1);
|
|
71
|
+
|
|
72
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
73
|
+
transform: [{ scale: scale.value }],
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<Pressable
|
|
78
|
+
ref={ref}
|
|
79
|
+
accessibilityRole="button"
|
|
80
|
+
onPressIn={(e) => {
|
|
81
|
+
onPressIn?.(e);
|
|
82
|
+
if (!animate) return;
|
|
83
|
+
cancelAnimation(scale);
|
|
84
|
+
scale.value = withTiming(targetScale, { duration: DURATION });
|
|
85
|
+
}}
|
|
86
|
+
onPressOut={(e) => {
|
|
87
|
+
onPressOut?.(e);
|
|
88
|
+
if (!animate) return;
|
|
89
|
+
cancelAnimation(scale);
|
|
90
|
+
scale.value = withTiming(1, { duration: DURATION });
|
|
91
|
+
}}
|
|
92
|
+
style={style}
|
|
93
|
+
{...rest}
|
|
94
|
+
>
|
|
95
|
+
<Animated.View style={[animate ? animatedStyle : null, contentContainerStyle]}>
|
|
96
|
+
{children}
|
|
97
|
+
</Animated.View>
|
|
98
|
+
</Pressable>
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
PressableScale.displayName = 'PressableScale';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text } from 'react-native';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
4
|
+
|
|
5
|
+
import { PressableWithHover } from './index';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof PressableWithHover> = {
|
|
8
|
+
title: 'Components/PressableWithHover',
|
|
9
|
+
component: PressableWithHover,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
|
|
14
|
+
type Story = StoryObj<typeof PressableWithHover>;
|
|
15
|
+
|
|
16
|
+
export const Basic: Story = {
|
|
17
|
+
render: () => (
|
|
18
|
+
<PressableWithHover
|
|
19
|
+
accessibilityLabel="Hover me"
|
|
20
|
+
style={{
|
|
21
|
+
paddingVertical: 12,
|
|
22
|
+
paddingHorizontal: 20,
|
|
23
|
+
borderRadius: 12,
|
|
24
|
+
backgroundColor: '#f3f4f6',
|
|
25
|
+
}}
|
|
26
|
+
hoverStyle={{ backgroundColor: '#e5e7eb' }}
|
|
27
|
+
>
|
|
28
|
+
<Text style={{ fontWeight: '600' }}>Hover me (web)</Text>
|
|
29
|
+
</PressableWithHover>
|
|
30
|
+
),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const ListRow: Story = {
|
|
34
|
+
render: () => (
|
|
35
|
+
<PressableWithHover
|
|
36
|
+
accessibilityLabel="Settings row"
|
|
37
|
+
style={{ paddingVertical: 14, paddingHorizontal: 16, borderRadius: 8, width: 260 }}
|
|
38
|
+
hoverStyle={{ backgroundColor: '#f3f4f6' }}
|
|
39
|
+
>
|
|
40
|
+
<Text>Account settings</Text>
|
|
41
|
+
</PressableWithHover>
|
|
42
|
+
),
|
|
43
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Pressable,
|
|
4
|
+
type PressableProps,
|
|
5
|
+
type StyleProp,
|
|
6
|
+
type View,
|
|
7
|
+
type ViewStyle,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
|
|
10
|
+
import { useInteractionState } from '../hooks/useInteractionState';
|
|
11
|
+
|
|
12
|
+
export interface PressableWithHoverProps extends Omit<PressableProps, 'style'> {
|
|
13
|
+
/** Base style; `hoverStyle` is merged on top while the pointer is over it. */
|
|
14
|
+
style?: StyleProp<ViewStyle>;
|
|
15
|
+
/**
|
|
16
|
+
* Style merged over `style` while hovered. Web-only: react-native-web fires
|
|
17
|
+
* `onHoverIn`/`onHoverOut`, so on touch platforms this is simply never applied.
|
|
18
|
+
*/
|
|
19
|
+
hoverStyle: StyleProp<ViewStyle>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A {@link Pressable} that merges `hoverStyle` on top of `style` while hovered.
|
|
24
|
+
* Hover is a web-only affordance (native platforms have no pointer hover), so
|
|
25
|
+
* this behaves like a plain `Pressable` on touch devices.
|
|
26
|
+
*/
|
|
27
|
+
export const PressableWithHover = forwardRef<View, PressableWithHoverProps>(
|
|
28
|
+
function PressableWithHover({ style, hoverStyle, ...props }, ref) {
|
|
29
|
+
const { state: hovered, onIn: onHoverIn, onOut: onHoverOut } = useInteractionState();
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Pressable
|
|
33
|
+
{...props}
|
|
34
|
+
ref={ref}
|
|
35
|
+
onHoverIn={onHoverIn}
|
|
36
|
+
onHoverOut={onHoverOut}
|
|
37
|
+
style={hovered ? [style, hoverStyle] : style}
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
PressableWithHover.displayName = 'PressableWithHover';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bloom's responsive breakpoint min-widths (px), matching the Tailwind /
|
|
3
|
+
* NativeWind defaults used across the design system.
|
|
4
|
+
*
|
|
5
|
+
* This is the SINGLE SOURCE OF TRUTH for breakpoint values. Anything that
|
|
6
|
+
* branches on viewport width — the `<Dialog>` responsive `placement` resolver,
|
|
7
|
+
* the `useGutters` hook — reads from here rather than hardcoding its own
|
|
8
|
+
* numbers, so a change here propagates everywhere.
|
|
9
|
+
*/
|
|
10
|
+
export const BREAKPOINTS = {
|
|
11
|
+
sm: 640,
|
|
12
|
+
md: 768,
|
|
13
|
+
lg: 1024,
|
|
14
|
+
xl: 1280,
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
/** A named responsive breakpoint tier. */
|
|
18
|
+
export type Breakpoint = keyof typeof BREAKPOINTS;
|
package/src/styles/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { atoms, flatten } from './atoms';
|
|
2
2
|
export type { ViewStyleProp, TextStyleProp } from './atoms';
|
|
3
3
|
export * as tokens from './tokens';
|
|
4
|
+
export { BREAKPOINTS } from './breakpoints';
|
|
5
|
+
export type { Breakpoint } from './breakpoints';
|
|
4
6
|
export { Z_INDEX, zIndex } from './z-index';
|
|
5
7
|
export { web, native, ios, android, platform, select } from './platform';
|
|
6
8
|
export { SUPPORTS_NATIVE_DRIVER } from './native-driver';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Pressable, Text, View } from 'react-native';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
4
|
+
|
|
5
|
+
import { useInteractionState } from '../hooks/useInteractionState';
|
|
6
|
+
import { SubtleHover } from './index';
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof SubtleHover> = {
|
|
9
|
+
title: 'Components/SubtleHover',
|
|
10
|
+
component: SubtleHover,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
|
|
15
|
+
type Story = StoryObj<typeof SubtleHover>;
|
|
16
|
+
|
|
17
|
+
/** A row that washes its background as the pointer enters (web). */
|
|
18
|
+
function HoverRow({ label, native }: { label: string; native?: boolean }) {
|
|
19
|
+
const { state: hovered, onIn, onOut } = useInteractionState();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Pressable
|
|
23
|
+
accessibilityLabel={label}
|
|
24
|
+
onHoverIn={onIn}
|
|
25
|
+
onHoverOut={onOut}
|
|
26
|
+
style={{
|
|
27
|
+
width: 280,
|
|
28
|
+
paddingVertical: 14,
|
|
29
|
+
paddingHorizontal: 16,
|
|
30
|
+
borderRadius: 12,
|
|
31
|
+
overflow: 'hidden',
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
34
|
+
<SubtleHover hover={hovered} native={native} style={{ borderRadius: 12 }} />
|
|
35
|
+
<Text style={{ fontWeight: '600' }}>{label}</Text>
|
|
36
|
+
</Pressable>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const ListRow: Story = {
|
|
41
|
+
render: () => <HoverRow label="Hover me (web)" />,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const StrongerWash: Story = {
|
|
45
|
+
render: () => {
|
|
46
|
+
function Row() {
|
|
47
|
+
const { state: hovered, onIn, onOut } = useInteractionState();
|
|
48
|
+
return (
|
|
49
|
+
<Pressable
|
|
50
|
+
accessibilityLabel="Stronger wash"
|
|
51
|
+
onHoverIn={onIn}
|
|
52
|
+
onHoverOut={onOut}
|
|
53
|
+
style={{ width: 280, padding: 16, borderRadius: 12, overflow: 'hidden' }}
|
|
54
|
+
>
|
|
55
|
+
<SubtleHover hover={hovered} opacity={0.85} style={{ borderRadius: 12 }} />
|
|
56
|
+
<Text style={{ fontWeight: '600' }}>Higher opacity override</Text>
|
|
57
|
+
</Pressable>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return <Row />;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const Stack: Story = {
|
|
65
|
+
render: () => (
|
|
66
|
+
<View style={{ gap: 4 }}>
|
|
67
|
+
<HoverRow label="Inbox" />
|
|
68
|
+
<HoverRow label="Sent" />
|
|
69
|
+
<HoverRow label="Drafts" />
|
|
70
|
+
</View>
|
|
71
|
+
),
|
|
72
|
+
};
|