@oxyhq/bloom 0.1.14 → 0.1.16
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/accordion/Accordion.js +230 -0
- package/lib/commonjs/accordion/Accordion.js.map +1 -0
- package/lib/commonjs/accordion/index.js +31 -0
- package/lib/commonjs/accordion/index.js.map +1 -0
- package/lib/commonjs/accordion/types.js +6 -0
- package/lib/commonjs/accordion/types.js.map +1 -0
- package/lib/commonjs/avatar/Avatar.js +19 -7
- package/lib/commonjs/avatar/Avatar.js.map +1 -1
- package/lib/commonjs/badge/Badge.js +173 -0
- package/lib/commonjs/badge/Badge.js.map +1 -0
- package/lib/commonjs/badge/index.js +13 -0
- package/lib/commonjs/badge/index.js.map +1 -0
- package/lib/commonjs/badge/types.js +6 -0
- package/lib/commonjs/badge/types.js.map +1 -0
- package/lib/commonjs/bottom-sheet/index.js +32 -14
- package/lib/commonjs/bottom-sheet/index.js.map +1 -1
- package/lib/commonjs/card/Card.js +165 -0
- package/lib/commonjs/card/Card.js.map +1 -0
- package/lib/commonjs/card/index.js +43 -0
- package/lib/commonjs/card/index.js.map +1 -0
- package/lib/commonjs/card/types.js +6 -0
- package/lib/commonjs/card/types.js.map +1 -0
- package/lib/commonjs/checkbox/Checkbox.js +177 -0
- package/lib/commonjs/checkbox/Checkbox.js.map +1 -0
- package/lib/commonjs/checkbox/index.js +13 -0
- package/lib/commonjs/checkbox/index.js.map +1 -0
- package/lib/commonjs/checkbox/types.js +6 -0
- package/lib/commonjs/checkbox/types.js.map +1 -0
- package/lib/commonjs/chip/Chip.js +180 -0
- package/lib/commonjs/chip/Chip.js.map +1 -0
- package/lib/commonjs/chip/index.js +13 -0
- package/lib/commonjs/chip/index.js.map +1 -0
- package/lib/commonjs/chip/types.js +6 -0
- package/lib/commonjs/chip/types.js.map +1 -0
- package/lib/commonjs/index.js +56 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/tabs/Tabs.js +202 -0
- package/lib/commonjs/tabs/Tabs.js.map +1 -0
- package/lib/commonjs/tabs/index.js +25 -0
- package/lib/commonjs/tabs/index.js.map +1 -0
- package/lib/commonjs/tabs/types.js +6 -0
- package/lib/commonjs/tabs/types.js.map +1 -0
- package/lib/module/accordion/Accordion.js +225 -0
- package/lib/module/accordion/Accordion.js.map +1 -0
- package/lib/module/accordion/index.js +4 -0
- package/lib/module/accordion/index.js.map +1 -0
- package/lib/module/accordion/types.js +4 -0
- package/lib/module/accordion/types.js.map +1 -0
- package/lib/module/avatar/Avatar.js +19 -7
- package/lib/module/avatar/Avatar.js.map +1 -1
- package/lib/module/badge/Badge.js +168 -0
- package/lib/module/badge/Badge.js.map +1 -0
- package/lib/module/badge/index.js +4 -0
- package/lib/module/badge/index.js.map +1 -0
- package/lib/module/badge/types.js +4 -0
- package/lib/module/badge/types.js.map +1 -0
- package/lib/module/bottom-sheet/index.js +32 -14
- package/lib/module/bottom-sheet/index.js.map +1 -1
- package/lib/module/card/Card.js +160 -0
- package/lib/module/card/Card.js.map +1 -0
- package/lib/module/card/index.js +4 -0
- package/lib/module/card/index.js.map +1 -0
- package/lib/module/card/types.js +4 -0
- package/lib/module/card/types.js.map +1 -0
- package/lib/module/checkbox/Checkbox.js +172 -0
- package/lib/module/checkbox/Checkbox.js.map +1 -0
- package/lib/module/checkbox/index.js +4 -0
- package/lib/module/checkbox/index.js.map +1 -0
- package/lib/module/checkbox/types.js +4 -0
- package/lib/module/checkbox/types.js.map +1 -0
- package/lib/module/chip/Chip.js +175 -0
- package/lib/module/chip/Chip.js.map +1 -0
- package/lib/module/chip/index.js +4 -0
- package/lib/module/chip/index.js.map +1 -0
- package/lib/module/chip/types.js +4 -0
- package/lib/module/chip/types.js.map +1 -0
- package/lib/module/index.js +8 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/tabs/Tabs.js +197 -0
- package/lib/module/tabs/Tabs.js.map +1 -0
- package/lib/module/tabs/index.js +4 -0
- package/lib/module/tabs/index.js.map +1 -0
- package/lib/module/tabs/types.js +4 -0
- package/lib/module/tabs/types.js.map +1 -0
- package/lib/typescript/commonjs/__tests__/BloomThemeProvider.test.d.ts +2 -0
- package/lib/typescript/commonjs/__tests__/BloomThemeProvider.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/__tests__/BottomSheet.test.d.ts +2 -0
- package/lib/typescript/commonjs/__tests__/BottomSheet.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/__tests__/Button.test.d.ts +2 -0
- package/lib/typescript/commonjs/__tests__/Button.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/__tests__/theme.test.d.ts +2 -0
- package/lib/typescript/commonjs/__tests__/theme.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/accordion/Accordion.d.ts +7 -0
- package/lib/typescript/commonjs/accordion/Accordion.d.ts.map +1 -0
- package/lib/typescript/commonjs/accordion/index.d.ts +3 -0
- package/lib/typescript/commonjs/accordion/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/accordion/types.d.ts +38 -0
- package/lib/typescript/commonjs/accordion/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/avatar/Avatar.d.ts.map +1 -1
- package/lib/typescript/commonjs/avatar/types.d.ts +4 -0
- package/lib/typescript/commonjs/avatar/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/badge/Badge.d.ts +4 -0
- package/lib/typescript/commonjs/badge/Badge.d.ts.map +1 -0
- package/lib/typescript/commonjs/badge/index.d.ts +3 -0
- package/lib/typescript/commonjs/badge/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/badge/types.d.ts +29 -0
- package/lib/typescript/commonjs/badge/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/card/Card.d.ts +9 -0
- package/lib/typescript/commonjs/card/Card.d.ts.map +1 -0
- package/lib/typescript/commonjs/card/index.d.ts +3 -0
- package/lib/typescript/commonjs/card/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/card/types.d.ts +34 -0
- package/lib/typescript/commonjs/card/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkbox/Checkbox.d.ts +4 -0
- package/lib/typescript/commonjs/checkbox/Checkbox.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkbox/index.d.ts +3 -0
- package/lib/typescript/commonjs/checkbox/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkbox/types.d.ts +25 -0
- package/lib/typescript/commonjs/checkbox/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/chip/Chip.d.ts +4 -0
- package/lib/typescript/commonjs/chip/Chip.d.ts.map +1 -0
- package/lib/typescript/commonjs/chip/index.d.ts +3 -0
- package/lib/typescript/commonjs/chip/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/chip/types.d.ts +31 -0
- package/lib/typescript/commonjs/chip/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +6 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/tabs/Tabs.d.ts +6 -0
- package/lib/typescript/commonjs/tabs/Tabs.d.ts.map +1 -0
- package/lib/typescript/commonjs/tabs/index.d.ts +3 -0
- package/lib/typescript/commonjs/tabs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/tabs/types.d.ts +34 -0
- package/lib/typescript/commonjs/tabs/types.d.ts.map +1 -0
- package/lib/typescript/module/__tests__/BloomThemeProvider.test.d.ts +2 -0
- package/lib/typescript/module/__tests__/BloomThemeProvider.test.d.ts.map +1 -0
- package/lib/typescript/module/__tests__/BottomSheet.test.d.ts +2 -0
- package/lib/typescript/module/__tests__/BottomSheet.test.d.ts.map +1 -0
- package/lib/typescript/module/__tests__/Button.test.d.ts +2 -0
- package/lib/typescript/module/__tests__/Button.test.d.ts.map +1 -0
- package/lib/typescript/module/__tests__/theme.test.d.ts +2 -0
- package/lib/typescript/module/__tests__/theme.test.d.ts.map +1 -0
- package/lib/typescript/module/accordion/Accordion.d.ts +7 -0
- package/lib/typescript/module/accordion/Accordion.d.ts.map +1 -0
- package/lib/typescript/module/accordion/index.d.ts +3 -0
- package/lib/typescript/module/accordion/index.d.ts.map +1 -0
- package/lib/typescript/module/accordion/types.d.ts +38 -0
- package/lib/typescript/module/accordion/types.d.ts.map +1 -0
- package/lib/typescript/module/avatar/Avatar.d.ts.map +1 -1
- package/lib/typescript/module/avatar/types.d.ts +4 -0
- package/lib/typescript/module/avatar/types.d.ts.map +1 -1
- package/lib/typescript/module/badge/Badge.d.ts +4 -0
- package/lib/typescript/module/badge/Badge.d.ts.map +1 -0
- package/lib/typescript/module/badge/index.d.ts +3 -0
- package/lib/typescript/module/badge/index.d.ts.map +1 -0
- package/lib/typescript/module/badge/types.d.ts +29 -0
- package/lib/typescript/module/badge/types.d.ts.map +1 -0
- package/lib/typescript/module/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/module/card/Card.d.ts +9 -0
- package/lib/typescript/module/card/Card.d.ts.map +1 -0
- package/lib/typescript/module/card/index.d.ts +3 -0
- package/lib/typescript/module/card/index.d.ts.map +1 -0
- package/lib/typescript/module/card/types.d.ts +34 -0
- package/lib/typescript/module/card/types.d.ts.map +1 -0
- package/lib/typescript/module/checkbox/Checkbox.d.ts +4 -0
- package/lib/typescript/module/checkbox/Checkbox.d.ts.map +1 -0
- package/lib/typescript/module/checkbox/index.d.ts +3 -0
- package/lib/typescript/module/checkbox/index.d.ts.map +1 -0
- package/lib/typescript/module/checkbox/types.d.ts +25 -0
- package/lib/typescript/module/checkbox/types.d.ts.map +1 -0
- package/lib/typescript/module/chip/Chip.d.ts +4 -0
- package/lib/typescript/module/chip/Chip.d.ts.map +1 -0
- package/lib/typescript/module/chip/index.d.ts +3 -0
- package/lib/typescript/module/chip/index.d.ts.map +1 -0
- package/lib/typescript/module/chip/types.d.ts +31 -0
- package/lib/typescript/module/chip/types.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +6 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/tabs/Tabs.d.ts +6 -0
- package/lib/typescript/module/tabs/Tabs.d.ts.map +1 -0
- package/lib/typescript/module/tabs/index.d.ts +3 -0
- package/lib/typescript/module/tabs/index.d.ts.map +1 -0
- package/lib/typescript/module/tabs/types.d.ts +34 -0
- package/lib/typescript/module/tabs/types.d.ts.map +1 -0
- package/package.json +79 -1
- package/src/__tests__/BloomThemeProvider.test.tsx +160 -0
- package/src/__tests__/BottomSheet.test.tsx +109 -0
- package/src/__tests__/Button.test.tsx +98 -0
- package/src/__tests__/theme.test.ts +148 -0
- package/src/accordion/Accordion.tsx +261 -0
- package/src/accordion/index.ts +8 -0
- package/src/accordion/types.ts +42 -0
- package/src/avatar/Avatar.tsx +16 -6
- package/src/avatar/types.ts +4 -0
- package/src/badge/Badge.tsx +151 -0
- package/src/badge/index.ts +8 -0
- package/src/badge/types.ts +30 -0
- package/src/bottom-sheet/index.tsx +30 -11
- package/src/card/Card.tsx +197 -0
- package/src/card/index.ts +10 -0
- package/src/card/types.ts +40 -0
- package/src/checkbox/Checkbox.tsx +166 -0
- package/src/checkbox/index.ts +2 -0
- package/src/checkbox/types.ts +26 -0
- package/src/chip/Chip.tsx +156 -0
- package/src/chip/index.ts +2 -0
- package/src/chip/types.ts +32 -0
- package/src/index.ts +8 -0
- package/src/tabs/Tabs.tsx +218 -0
- package/src/tabs/index.ts +2 -0
- package/src/tabs/types.ts +37 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { StyleProp, ViewStyle, TextStyle } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export type BadgeVariant = 'solid' | 'subtle' | 'outlined';
|
|
4
|
+
export type BadgeColor = 'default' | 'primary' | 'success' | 'warning' | 'error' | 'info';
|
|
5
|
+
export type BadgeSize = 'small' | 'medium' | 'large';
|
|
6
|
+
export type BadgePlacement = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
|
|
7
|
+
|
|
8
|
+
export interface BadgeProps {
|
|
9
|
+
/** Text or number to display in the badge. */
|
|
10
|
+
content?: string | number;
|
|
11
|
+
/** Visual variant. */
|
|
12
|
+
variant?: BadgeVariant;
|
|
13
|
+
/** Semantic color. */
|
|
14
|
+
color?: BadgeColor;
|
|
15
|
+
/** Size preset. */
|
|
16
|
+
size?: BadgeSize;
|
|
17
|
+
/** If true, renders as a small dot without content. */
|
|
18
|
+
dot?: boolean;
|
|
19
|
+
/** Maximum number to display. Values above this show "{max}+". */
|
|
20
|
+
max?: number;
|
|
21
|
+
/** If true, the badge is hidden. */
|
|
22
|
+
invisible?: boolean;
|
|
23
|
+
/** Where to position the badge relative to its child. */
|
|
24
|
+
placement?: BadgePlacement;
|
|
25
|
+
/** The element the badge is attached to. */
|
|
26
|
+
children?: React.ReactNode;
|
|
27
|
+
style?: StyleProp<ViewStyle>;
|
|
28
|
+
textStyle?: StyleProp<TextStyle>;
|
|
29
|
+
testID?: string;
|
|
30
|
+
}
|
|
@@ -35,7 +35,19 @@ if (Platform.OS !== 'web') {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
/** Hook that returns current screen dimensions and updates on rotation/resize. */
|
|
39
|
+
function useScreenDimensions() {
|
|
40
|
+
const [dimensions, setDimensions] = useState(() => Dimensions.get('window'));
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const subscription = Dimensions.addEventListener('change', ({ window }) => {
|
|
44
|
+
setDimensions(window);
|
|
45
|
+
});
|
|
46
|
+
return () => subscription.remove();
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
return dimensions;
|
|
50
|
+
}
|
|
39
51
|
|
|
40
52
|
const SPRING_CONFIG = {
|
|
41
53
|
damping: 25,
|
|
@@ -78,14 +90,22 @@ const BottomSheet = forwardRef((props: BottomSheetProps, ref: React.ForwardedRef
|
|
|
78
90
|
} = props;
|
|
79
91
|
|
|
80
92
|
const insets = useSafeAreaInsets();
|
|
81
|
-
const
|
|
93
|
+
const theme = useTheme();
|
|
94
|
+
const { colors } = theme;
|
|
95
|
+
const { height: screenHeight } = useScreenDimensions();
|
|
82
96
|
const [visible, setVisible] = useState(false);
|
|
83
97
|
const [rendered, setRendered] = useState(false); // keep mounted for exit animation
|
|
84
98
|
const closeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
85
99
|
const hasClosedRef = useRef(false);
|
|
86
100
|
const scrollViewRef = useRef<Animated.ScrollView>(null);
|
|
87
101
|
|
|
88
|
-
const
|
|
102
|
+
const screenHeightSV = useSharedValue(screenHeight);
|
|
103
|
+
// Keep shared value in sync when screen dimensions change (rotation/resize)
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
screenHeightSV.value = screenHeight;
|
|
106
|
+
}, [screenHeight, screenHeightSV]);
|
|
107
|
+
|
|
108
|
+
const translateY = useSharedValue(screenHeight);
|
|
89
109
|
const opacity = useSharedValue(0);
|
|
90
110
|
const scrollOffsetY = useSharedValue(0);
|
|
91
111
|
const isScrollAtTop = useSharedValue(true);
|
|
@@ -135,7 +155,7 @@ const BottomSheet = forwardRef((props: BottomSheetProps, ref: React.ForwardedRef
|
|
|
135
155
|
runOnJS(finishClose)();
|
|
136
156
|
}
|
|
137
157
|
});
|
|
138
|
-
translateY.value = withSpring(
|
|
158
|
+
translateY.value = withSpring(screenHeight, { ...SPRING_CONFIG, stiffness: 250 });
|
|
139
159
|
|
|
140
160
|
// Fallback timer to ensure close completes (especially on web)
|
|
141
161
|
if (closeTimeoutRef.current) {
|
|
@@ -227,14 +247,14 @@ const BottomSheet = forwardRef((props: BottomSheetProps, ref: React.ForwardedRef
|
|
|
227
247
|
const velocity = event.velocityY;
|
|
228
248
|
const distance = translateY.value;
|
|
229
249
|
// Require a deeper pull to close (more like native bottom sheets)
|
|
230
|
-
const closeThreshold = Math.max(140,
|
|
250
|
+
const closeThreshold = Math.max(140, screenHeightSV.value * 0.25);
|
|
231
251
|
const fastSwipeThreshold = 900;
|
|
232
252
|
const shouldClose =
|
|
233
253
|
velocity > fastSwipeThreshold ||
|
|
234
254
|
(distance > closeThreshold && velocity > -300);
|
|
235
255
|
|
|
236
256
|
if (shouldClose) {
|
|
237
|
-
translateY.value = withSpring(
|
|
257
|
+
translateY.value = withSpring(screenHeightSV.value, {
|
|
238
258
|
...SPRING_CONFIG,
|
|
239
259
|
velocity: velocity,
|
|
240
260
|
});
|
|
@@ -262,7 +282,7 @@ const BottomSheet = forwardRef((props: BottomSheetProps, ref: React.ForwardedRef
|
|
|
262
282
|
}));
|
|
263
283
|
|
|
264
284
|
const sheetStyle = useAnimatedStyle(() => {
|
|
265
|
-
const scale = interpolate(translateY.value, [0,
|
|
285
|
+
const scale = interpolate(translateY.value, [0, screenHeightSV.value], [1, 0.95]);
|
|
266
286
|
return {
|
|
267
287
|
transform: [
|
|
268
288
|
{ translateY: translateY.value - keyboardHeight.value },
|
|
@@ -272,7 +292,7 @@ const BottomSheet = forwardRef((props: BottomSheetProps, ref: React.ForwardedRef
|
|
|
272
292
|
});
|
|
273
293
|
|
|
274
294
|
const sheetHeightStyle = useAnimatedStyle(() => ({
|
|
275
|
-
maxHeight:
|
|
295
|
+
maxHeight: screenHeightSV.value - keyboardHeight.value - insets.top - (detached ? insets.bottom + 16 : 0),
|
|
276
296
|
}), [insets.top, insets.bottom, detached]);
|
|
277
297
|
|
|
278
298
|
const sheetMarginStyle = useAnimatedStyle(() => {
|
|
@@ -303,11 +323,10 @@ const BottomSheet = forwardRef((props: BottomSheetProps, ref: React.ForwardedRef
|
|
|
303
323
|
});
|
|
304
324
|
|
|
305
325
|
const dynamicStyles = useMemo(() => {
|
|
306
|
-
const isDark = colors.background === '#000000';
|
|
307
326
|
return StyleSheet.create({
|
|
308
327
|
handle: {
|
|
309
328
|
...styles.handle,
|
|
310
|
-
backgroundColor: isDark ? '#444' : '#C7C7CC',
|
|
329
|
+
backgroundColor: theme.isDark ? '#444' : '#C7C7CC',
|
|
311
330
|
},
|
|
312
331
|
sheet: {
|
|
313
332
|
...styles.sheet,
|
|
@@ -320,7 +339,7 @@ const BottomSheet = forwardRef((props: BottomSheetProps, ref: React.ForwardedRef
|
|
|
320
339
|
// The sheet extends behind safe area, and screens add padding as needed
|
|
321
340
|
},
|
|
322
341
|
});
|
|
323
|
-
}, [colors.background, detached]);
|
|
342
|
+
}, [colors.background, theme.isDark, detached]);
|
|
324
343
|
|
|
325
344
|
if (!rendered) return null;
|
|
326
345
|
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import React, { memo, useMemo } from 'react';
|
|
2
|
+
import { View, Text, Pressable, Platform, type ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useTheme } from '../theme/use-theme';
|
|
5
|
+
import { borderRadius, space } from '../styles/tokens';
|
|
6
|
+
import type {
|
|
7
|
+
CardProps,
|
|
8
|
+
CardHeaderProps,
|
|
9
|
+
CardBodyProps,
|
|
10
|
+
CardFooterProps,
|
|
11
|
+
CardTitleProps,
|
|
12
|
+
CardDescriptionProps,
|
|
13
|
+
} from './types';
|
|
14
|
+
|
|
15
|
+
const CardRootComponent: React.FC<CardProps> = ({
|
|
16
|
+
children,
|
|
17
|
+
variant = 'elevated',
|
|
18
|
+
style,
|
|
19
|
+
onPress,
|
|
20
|
+
disabled = false,
|
|
21
|
+
accessibilityLabel,
|
|
22
|
+
testID,
|
|
23
|
+
}) => {
|
|
24
|
+
const theme = useTheme();
|
|
25
|
+
|
|
26
|
+
const containerStyle = useMemo((): ViewStyle => {
|
|
27
|
+
const base: ViewStyle = {
|
|
28
|
+
borderRadius: borderRadius.md,
|
|
29
|
+
overflow: 'hidden',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
switch (variant) {
|
|
33
|
+
case 'elevated':
|
|
34
|
+
base.backgroundColor = theme.colors.card;
|
|
35
|
+
if (Platform.OS === 'web') {
|
|
36
|
+
base.boxShadow = `0px 1px 3px ${theme.colors.shadow}`;
|
|
37
|
+
} else {
|
|
38
|
+
base.shadowColor = theme.colors.shadow;
|
|
39
|
+
base.shadowOffset = { width: 0, height: 1 };
|
|
40
|
+
base.shadowOpacity = 0.2;
|
|
41
|
+
base.shadowRadius = 3;
|
|
42
|
+
base.elevation = 2;
|
|
43
|
+
}
|
|
44
|
+
break;
|
|
45
|
+
case 'outlined':
|
|
46
|
+
base.backgroundColor = theme.colors.card;
|
|
47
|
+
base.borderWidth = 1;
|
|
48
|
+
base.borderColor = theme.colors.border;
|
|
49
|
+
break;
|
|
50
|
+
case 'filled':
|
|
51
|
+
base.backgroundColor = theme.colors.backgroundSecondary;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return base;
|
|
56
|
+
}, [variant, theme]);
|
|
57
|
+
|
|
58
|
+
if (onPress) {
|
|
59
|
+
return (
|
|
60
|
+
<Pressable
|
|
61
|
+
style={({ pressed }) => [
|
|
62
|
+
containerStyle,
|
|
63
|
+
pressed && { opacity: 0.85 },
|
|
64
|
+
disabled && { opacity: 0.5 },
|
|
65
|
+
style,
|
|
66
|
+
]}
|
|
67
|
+
onPress={onPress}
|
|
68
|
+
disabled={disabled}
|
|
69
|
+
accessibilityLabel={accessibilityLabel}
|
|
70
|
+
accessibilityRole="button"
|
|
71
|
+
accessibilityState={{ disabled }}
|
|
72
|
+
testID={testID}
|
|
73
|
+
>
|
|
74
|
+
{children}
|
|
75
|
+
</Pressable>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<View
|
|
81
|
+
style={[containerStyle, disabled && { opacity: 0.5 }, style]}
|
|
82
|
+
accessibilityLabel={accessibilityLabel}
|
|
83
|
+
testID={testID}
|
|
84
|
+
>
|
|
85
|
+
{children}
|
|
86
|
+
</View>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const CardHeaderComponent: React.FC<CardHeaderProps> = ({ children, style }) => (
|
|
91
|
+
<View
|
|
92
|
+
style={[
|
|
93
|
+
{
|
|
94
|
+
paddingHorizontal: space.lg,
|
|
95
|
+
paddingTop: space.lg,
|
|
96
|
+
paddingBottom: space.sm,
|
|
97
|
+
},
|
|
98
|
+
style,
|
|
99
|
+
]}
|
|
100
|
+
>
|
|
101
|
+
{children}
|
|
102
|
+
</View>
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const CardBodyComponent: React.FC<CardBodyProps> = ({ children, style }) => (
|
|
106
|
+
<View
|
|
107
|
+
style={[
|
|
108
|
+
{
|
|
109
|
+
paddingHorizontal: space.lg,
|
|
110
|
+
paddingVertical: space.sm,
|
|
111
|
+
},
|
|
112
|
+
style,
|
|
113
|
+
]}
|
|
114
|
+
>
|
|
115
|
+
{children}
|
|
116
|
+
</View>
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const CardFooterComponent: React.FC<CardFooterProps> = ({ children, style }) => (
|
|
120
|
+
<View
|
|
121
|
+
style={[
|
|
122
|
+
{
|
|
123
|
+
paddingHorizontal: space.lg,
|
|
124
|
+
paddingTop: space.sm,
|
|
125
|
+
paddingBottom: space.lg,
|
|
126
|
+
flexDirection: 'row',
|
|
127
|
+
alignItems: 'center',
|
|
128
|
+
justifyContent: 'flex-end',
|
|
129
|
+
gap: space.sm,
|
|
130
|
+
},
|
|
131
|
+
style,
|
|
132
|
+
]}
|
|
133
|
+
>
|
|
134
|
+
{children}
|
|
135
|
+
</View>
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const CardTitleComponent: React.FC<CardTitleProps> = ({ children, style, numberOfLines }) => {
|
|
139
|
+
const theme = useTheme();
|
|
140
|
+
return (
|
|
141
|
+
<Text
|
|
142
|
+
style={[
|
|
143
|
+
{
|
|
144
|
+
fontSize: 17,
|
|
145
|
+
fontWeight: '600',
|
|
146
|
+
color: theme.colors.text,
|
|
147
|
+
lineHeight: 22,
|
|
148
|
+
},
|
|
149
|
+
style,
|
|
150
|
+
]}
|
|
151
|
+
numberOfLines={numberOfLines}
|
|
152
|
+
>
|
|
153
|
+
{children}
|
|
154
|
+
</Text>
|
|
155
|
+
);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const CardDescriptionComponent: React.FC<CardDescriptionProps> = ({
|
|
159
|
+
children,
|
|
160
|
+
style,
|
|
161
|
+
numberOfLines,
|
|
162
|
+
}) => {
|
|
163
|
+
const theme = useTheme();
|
|
164
|
+
return (
|
|
165
|
+
<Text
|
|
166
|
+
style={[
|
|
167
|
+
{
|
|
168
|
+
fontSize: 14,
|
|
169
|
+
color: theme.colors.textSecondary,
|
|
170
|
+
lineHeight: 20,
|
|
171
|
+
},
|
|
172
|
+
style,
|
|
173
|
+
]}
|
|
174
|
+
numberOfLines={numberOfLines}
|
|
175
|
+
>
|
|
176
|
+
{children}
|
|
177
|
+
</Text>
|
|
178
|
+
);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export const Card = memo(CardRootComponent);
|
|
182
|
+
Card.displayName = 'Card';
|
|
183
|
+
|
|
184
|
+
export const CardHeader = memo(CardHeaderComponent);
|
|
185
|
+
CardHeader.displayName = 'CardHeader';
|
|
186
|
+
|
|
187
|
+
export const CardBody = memo(CardBodyComponent);
|
|
188
|
+
CardBody.displayName = 'CardBody';
|
|
189
|
+
|
|
190
|
+
export const CardFooter = memo(CardFooterComponent);
|
|
191
|
+
CardFooter.displayName = 'CardFooter';
|
|
192
|
+
|
|
193
|
+
export const CardTitle = memo(CardTitleComponent);
|
|
194
|
+
CardTitle.displayName = 'CardTitle';
|
|
195
|
+
|
|
196
|
+
export const CardDescription = memo(CardDescriptionComponent);
|
|
197
|
+
CardDescription.displayName = 'CardDescription';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { StyleProp, ViewStyle, TextStyle } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export type CardVariant = 'elevated' | 'outlined' | 'filled';
|
|
4
|
+
|
|
5
|
+
export interface CardProps {
|
|
6
|
+
children?: React.ReactNode;
|
|
7
|
+
variant?: CardVariant;
|
|
8
|
+
style?: StyleProp<ViewStyle>;
|
|
9
|
+
onPress?: () => void;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
accessibilityLabel?: string;
|
|
12
|
+
testID?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CardHeaderProps {
|
|
16
|
+
children?: React.ReactNode;
|
|
17
|
+
style?: StyleProp<ViewStyle>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CardBodyProps {
|
|
21
|
+
children?: React.ReactNode;
|
|
22
|
+
style?: StyleProp<ViewStyle>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface CardFooterProps {
|
|
26
|
+
children?: React.ReactNode;
|
|
27
|
+
style?: StyleProp<ViewStyle>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CardTitleProps {
|
|
31
|
+
children?: React.ReactNode;
|
|
32
|
+
style?: StyleProp<TextStyle>;
|
|
33
|
+
numberOfLines?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CardDescriptionProps {
|
|
37
|
+
children?: React.ReactNode;
|
|
38
|
+
style?: StyleProp<TextStyle>;
|
|
39
|
+
numberOfLines?: number;
|
|
40
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { View, Text, Pressable, Animated, type ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useTheme } from '../theme/use-theme';
|
|
5
|
+
import { animation, borderRadius, space } from '../styles/tokens';
|
|
6
|
+
import type { CheckboxProps } from './types';
|
|
7
|
+
|
|
8
|
+
const SIZE_CONFIG = {
|
|
9
|
+
small: { box: 18, checkmark: 10, fontSize: 14, lineHeight: 20, descFontSize: 12 },
|
|
10
|
+
medium: { box: 22, checkmark: 12, fontSize: 15, lineHeight: 22, descFontSize: 13 },
|
|
11
|
+
large: { box: 26, checkmark: 14, fontSize: 16, lineHeight: 24, descFontSize: 14 },
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
const CheckboxComponent: React.FC<CheckboxProps> = ({
|
|
15
|
+
checked,
|
|
16
|
+
onCheckedChange,
|
|
17
|
+
label,
|
|
18
|
+
description,
|
|
19
|
+
size = 'medium',
|
|
20
|
+
disabled = false,
|
|
21
|
+
indeterminate = false,
|
|
22
|
+
color,
|
|
23
|
+
style,
|
|
24
|
+
labelStyle,
|
|
25
|
+
accessibilityLabel,
|
|
26
|
+
testID,
|
|
27
|
+
}) => {
|
|
28
|
+
const theme = useTheme();
|
|
29
|
+
const scaleAnim = useRef(new Animated.Value(checked ? 1 : 0)).current;
|
|
30
|
+
const pressAnim = useRef(new Animated.Value(1)).current;
|
|
31
|
+
const sizeConfig = SIZE_CONFIG[size];
|
|
32
|
+
const checkColor = color ?? theme.colors.primary;
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
Animated.spring(scaleAnim, {
|
|
36
|
+
toValue: checked || indeterminate ? 1 : 0,
|
|
37
|
+
useNativeDriver: true,
|
|
38
|
+
...animation.spring.snappy,
|
|
39
|
+
}).start();
|
|
40
|
+
}, [checked, indeterminate, scaleAnim]);
|
|
41
|
+
|
|
42
|
+
const handlePress = useCallback(() => {
|
|
43
|
+
if (!disabled) {
|
|
44
|
+
onCheckedChange(!checked);
|
|
45
|
+
}
|
|
46
|
+
}, [checked, disabled, onCheckedChange]);
|
|
47
|
+
|
|
48
|
+
const onPressIn = useCallback(() => {
|
|
49
|
+
Animated.spring(pressAnim, {
|
|
50
|
+
toValue: 0.9,
|
|
51
|
+
useNativeDriver: true,
|
|
52
|
+
...animation.spring.snappy,
|
|
53
|
+
}).start();
|
|
54
|
+
}, [pressAnim]);
|
|
55
|
+
|
|
56
|
+
const onPressOut = useCallback(() => {
|
|
57
|
+
Animated.spring(pressAnim, {
|
|
58
|
+
toValue: 1,
|
|
59
|
+
useNativeDriver: true,
|
|
60
|
+
...animation.spring.gentle,
|
|
61
|
+
}).start();
|
|
62
|
+
}, [pressAnim]);
|
|
63
|
+
|
|
64
|
+
const boxStyle = useMemo((): ViewStyle => {
|
|
65
|
+
const base: ViewStyle = {
|
|
66
|
+
width: sizeConfig.box,
|
|
67
|
+
height: sizeConfig.box,
|
|
68
|
+
borderRadius: borderRadius._2xs + 2,
|
|
69
|
+
borderWidth: 2,
|
|
70
|
+
alignItems: 'center',
|
|
71
|
+
justifyContent: 'center',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (checked || indeterminate) {
|
|
75
|
+
base.backgroundColor = checkColor;
|
|
76
|
+
base.borderColor = checkColor;
|
|
77
|
+
} else {
|
|
78
|
+
base.backgroundColor = 'transparent';
|
|
79
|
+
base.borderColor = theme.colors.border;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return base;
|
|
83
|
+
}, [sizeConfig, checked, indeterminate, checkColor, theme]);
|
|
84
|
+
|
|
85
|
+
// Checkmark using unicode characters for zero-dependency rendering
|
|
86
|
+
const checkmarkContent = indeterminate ? '\u2014' : '\u2713';
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<Pressable
|
|
90
|
+
style={[
|
|
91
|
+
{
|
|
92
|
+
flexDirection: 'row',
|
|
93
|
+
alignItems: 'flex-start',
|
|
94
|
+
gap: space.sm,
|
|
95
|
+
opacity: disabled ? 0.4 : 1,
|
|
96
|
+
},
|
|
97
|
+
style,
|
|
98
|
+
]}
|
|
99
|
+
onPress={handlePress}
|
|
100
|
+
onPressIn={onPressIn}
|
|
101
|
+
onPressOut={onPressOut}
|
|
102
|
+
disabled={disabled}
|
|
103
|
+
accessibilityRole="checkbox"
|
|
104
|
+
accessibilityState={{ checked: indeterminate ? 'mixed' : checked, disabled }}
|
|
105
|
+
accessibilityLabel={accessibilityLabel ?? label}
|
|
106
|
+
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
107
|
+
testID={testID}
|
|
108
|
+
>
|
|
109
|
+
<Animated.View style={[boxStyle, { transform: [{ scale: pressAnim }] }]}>
|
|
110
|
+
<Animated.View
|
|
111
|
+
style={{
|
|
112
|
+
opacity: scaleAnim,
|
|
113
|
+
transform: [{ scale: scaleAnim }],
|
|
114
|
+
}}
|
|
115
|
+
>
|
|
116
|
+
<Text
|
|
117
|
+
style={{
|
|
118
|
+
fontSize: sizeConfig.checkmark,
|
|
119
|
+
color: '#fff',
|
|
120
|
+
fontWeight: '700',
|
|
121
|
+
lineHeight: sizeConfig.checkmark + 2,
|
|
122
|
+
textAlign: 'center',
|
|
123
|
+
}}
|
|
124
|
+
>
|
|
125
|
+
{checkmarkContent}
|
|
126
|
+
</Text>
|
|
127
|
+
</Animated.View>
|
|
128
|
+
</Animated.View>
|
|
129
|
+
|
|
130
|
+
{(label || description) && (
|
|
131
|
+
<View style={{ flex: 1, paddingTop: 1 }}>
|
|
132
|
+
{label && (
|
|
133
|
+
<Text
|
|
134
|
+
style={[
|
|
135
|
+
{
|
|
136
|
+
fontSize: sizeConfig.fontSize,
|
|
137
|
+
lineHeight: sizeConfig.lineHeight,
|
|
138
|
+
color: theme.colors.text,
|
|
139
|
+
fontWeight: '500',
|
|
140
|
+
},
|
|
141
|
+
labelStyle,
|
|
142
|
+
]}
|
|
143
|
+
>
|
|
144
|
+
{label}
|
|
145
|
+
</Text>
|
|
146
|
+
)}
|
|
147
|
+
{description && (
|
|
148
|
+
<Text
|
|
149
|
+
style={{
|
|
150
|
+
fontSize: sizeConfig.descFontSize,
|
|
151
|
+
color: theme.colors.textSecondary,
|
|
152
|
+
lineHeight: sizeConfig.descFontSize + 6,
|
|
153
|
+
marginTop: 2,
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
{description}
|
|
157
|
+
</Text>
|
|
158
|
+
)}
|
|
159
|
+
</View>
|
|
160
|
+
)}
|
|
161
|
+
</Pressable>
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export const Checkbox = memo(CheckboxComponent);
|
|
166
|
+
Checkbox.displayName = 'Checkbox';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { StyleProp, ViewStyle, TextStyle } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export type CheckboxSize = 'small' | 'medium' | 'large';
|
|
4
|
+
|
|
5
|
+
export interface CheckboxProps {
|
|
6
|
+
/** Whether the checkbox is checked. */
|
|
7
|
+
checked: boolean;
|
|
8
|
+
/** Called when the checked state changes. */
|
|
9
|
+
onCheckedChange: (checked: boolean) => void;
|
|
10
|
+
/** Optional label text. */
|
|
11
|
+
label?: string;
|
|
12
|
+
/** Optional description shown below the label. */
|
|
13
|
+
description?: string;
|
|
14
|
+
/** Size preset. */
|
|
15
|
+
size?: CheckboxSize;
|
|
16
|
+
/** Whether the checkbox is disabled. */
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
/** Whether the checkbox is in an indeterminate state. */
|
|
19
|
+
indeterminate?: boolean;
|
|
20
|
+
/** Semantic color when checked. Uses theme primary by default. */
|
|
21
|
+
color?: string;
|
|
22
|
+
style?: StyleProp<ViewStyle>;
|
|
23
|
+
labelStyle?: StyleProp<TextStyle>;
|
|
24
|
+
accessibilityLabel?: string;
|
|
25
|
+
testID?: string;
|
|
26
|
+
}
|