@campxdev/react-native-blueprint 0.1.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/LICENSE +20 -0
- package/README.md +358 -0
- package/lib/module/app/_layout.js +23 -0
- package/lib/module/app/_layout.js.map +1 -0
- package/lib/module/assets/icons/weather_icons/drizzle.png +0 -0
- package/lib/module/assets/icons/weather_icons/foggy.png +0 -0
- package/lib/module/assets/icons/weather_icons/freezing_rain.png +0 -0
- package/lib/module/assets/icons/weather_icons/partly_cloudy.png +0 -0
- package/lib/module/assets/icons/weather_icons/rainy.png +0 -0
- package/lib/module/assets/icons/weather_icons/showers.png +0 -0
- package/lib/module/assets/icons/weather_icons/sunny_weather.png +0 -0
- package/lib/module/assets/icons/weather_icons/thunderstorm.png +0 -0
- package/lib/module/assets/icons/weather_icons/thunderstorm_hail.png +0 -0
- package/lib/module/components/theme-config.js +265 -0
- package/lib/module/components/theme-config.js.map +1 -0
- package/lib/module/components/ui/Accordion.js +228 -0
- package/lib/module/components/ui/Accordion.js.map +1 -0
- package/lib/module/components/ui/Alert-Dialog.js +266 -0
- package/lib/module/components/ui/Alert-Dialog.js.map +1 -0
- package/lib/module/components/ui/Alert.js +107 -0
- package/lib/module/components/ui/Alert.js.map +1 -0
- package/lib/module/components/ui/AppBar.js +403 -0
- package/lib/module/components/ui/AppBar.js.map +1 -0
- package/lib/module/components/ui/Aspect-Ratio.js +27 -0
- package/lib/module/components/ui/Aspect-Ratio.js.map +1 -0
- package/lib/module/components/ui/Avatar.js +97 -0
- package/lib/module/components/ui/Avatar.js.map +1 -0
- package/lib/module/components/ui/Badge.js +127 -0
- package/lib/module/components/ui/Badge.js.map +1 -0
- package/lib/module/components/ui/Bottom-Sheet.js +144 -0
- package/lib/module/components/ui/Bottom-Sheet.js.map +1 -0
- package/lib/module/components/ui/Button.js +88 -0
- package/lib/module/components/ui/Button.js.map +1 -0
- package/lib/module/components/ui/Card.js +176 -0
- package/lib/module/components/ui/Card.js.map +1 -0
- package/lib/module/components/ui/Checkbox.js +65 -0
- package/lib/module/components/ui/Checkbox.js.map +1 -0
- package/lib/module/components/ui/Collapsible.js +42 -0
- package/lib/module/components/ui/Collapsible.js.map +1 -0
- package/lib/module/components/ui/Context-Menu.js +287 -0
- package/lib/module/components/ui/Context-Menu.js.map +1 -0
- package/lib/module/components/ui/Custom-Card.js +202 -0
- package/lib/module/components/ui/Custom-Card.js.map +1 -0
- package/lib/module/components/ui/Dialog.js +202 -0
- package/lib/module/components/ui/Dialog.js.map +1 -0
- package/lib/module/components/ui/Dropdown-Menu.js +421 -0
- package/lib/module/components/ui/Dropdown-Menu.js.map +1 -0
- package/lib/module/components/ui/Floating-Action.js +50 -0
- package/lib/module/components/ui/Floating-Action.js.map +1 -0
- package/lib/module/components/ui/Greeting-Card.js +392 -0
- package/lib/module/components/ui/Greeting-Card.js.map +1 -0
- package/lib/module/components/ui/Hover-Card.js +96 -0
- package/lib/module/components/ui/Hover-Card.js.map +1 -0
- package/lib/module/components/ui/Icon.js +73 -0
- package/lib/module/components/ui/Icon.js.map +1 -0
- package/lib/module/components/ui/Input.js +74 -0
- package/lib/module/components/ui/Input.js.map +1 -0
- package/lib/module/components/ui/Label.js +44 -0
- package/lib/module/components/ui/Label.js.map +1 -0
- package/lib/module/components/ui/Menubar.js +375 -0
- package/lib/module/components/ui/Menubar.js.map +1 -0
- package/lib/module/components/ui/Native-Only-Animated-View.js +41 -0
- package/lib/module/components/ui/Native-Only-Animated-View.js.map +1 -0
- package/lib/module/components/ui/NavBar.js +352 -0
- package/lib/module/components/ui/NavBar.js.map +1 -0
- package/lib/module/components/ui/Popover.js +101 -0
- package/lib/module/components/ui/Popover.js.map +1 -0
- package/lib/module/components/ui/Progress.js +124 -0
- package/lib/module/components/ui/Progress.js.map +1 -0
- package/lib/module/components/ui/Radio-Group.js +75 -0
- package/lib/module/components/ui/Radio-Group.js.map +1 -0
- package/lib/module/components/ui/Select.js +269 -0
- package/lib/module/components/ui/Select.js.map +1 -0
- package/lib/module/components/ui/Separator.js +58 -0
- package/lib/module/components/ui/Separator.js.map +1 -0
- package/lib/module/components/ui/SizedBox.js +101 -0
- package/lib/module/components/ui/SizedBox.js.map +1 -0
- package/lib/module/components/ui/Skeleton.js +57 -0
- package/lib/module/components/ui/Skeleton.js.map +1 -0
- package/lib/module/components/ui/Slider.js +169 -0
- package/lib/module/components/ui/Slider.js.map +1 -0
- package/lib/module/components/ui/Switch.js +55 -0
- package/lib/module/components/ui/Switch.js.map +1 -0
- package/lib/module/components/ui/Table.js +150 -0
- package/lib/module/components/ui/Table.js.map +1 -0
- package/lib/module/components/ui/Tabs.js +106 -0
- package/lib/module/components/ui/Tabs.js.map +1 -0
- package/lib/module/components/ui/Text.js +69 -0
- package/lib/module/components/ui/Text.js.map +1 -0
- package/lib/module/components/ui/Textarea.js +88 -0
- package/lib/module/components/ui/Textarea.js.map +1 -0
- package/lib/module/components/ui/Theme-Toggle.js +156 -0
- package/lib/module/components/ui/Theme-Toggle.js.map +1 -0
- package/lib/module/components/ui/Toast.js +101 -0
- package/lib/module/components/ui/Toast.js.map +1 -0
- package/lib/module/components/ui/Toggle-Group.js +129 -0
- package/lib/module/components/ui/Toggle-Group.js.map +1 -0
- package/lib/module/components/ui/Toggle.js +106 -0
- package/lib/module/components/ui/Toggle.js.map +1 -0
- package/lib/module/components/ui/Tooltip.js +106 -0
- package/lib/module/components/ui/Tooltip.js.map +1 -0
- package/lib/module/components/ui/index.js +45 -0
- package/lib/module/components/ui/index.js.map +1 -0
- package/lib/module/index.js +19 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/lib/ThemeProvider.js +173 -0
- package/lib/module/lib/ThemeProvider.js.map +1 -0
- package/lib/module/lib/cornerRadius.js +164 -0
- package/lib/module/lib/cornerRadius.js.map +1 -0
- package/lib/module/lib/fonts.js +25 -0
- package/lib/module/lib/fonts.js.map +1 -0
- package/lib/module/lib/theme.js +212 -0
- package/lib/module/lib/theme.js.map +1 -0
- package/lib/module/lib/utils.js +137 -0
- package/lib/module/lib/utils.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/package.json +208 -0
- package/src/app/_layout.tsx +25 -0
- package/src/assets/icons/weather_icons/drizzle.png +0 -0
- package/src/assets/icons/weather_icons/foggy.png +0 -0
- package/src/assets/icons/weather_icons/freezing_rain.png +0 -0
- package/src/assets/icons/weather_icons/partly_cloudy.png +0 -0
- package/src/assets/icons/weather_icons/rainy.png +0 -0
- package/src/assets/icons/weather_icons/showers.png +0 -0
- package/src/assets/icons/weather_icons/sunny_weather.png +0 -0
- package/src/assets/icons/weather_icons/thunderstorm.png +0 -0
- package/src/assets/icons/weather_icons/thunderstorm_hail.png +0 -0
- package/src/components/theme-config.ts +331 -0
- package/src/components/ui/Accordion.tsx +253 -0
- package/src/components/ui/Alert-Dialog.tsx +295 -0
- package/src/components/ui/Alert.tsx +137 -0
- package/src/components/ui/AppBar.tsx +551 -0
- package/src/components/ui/Aspect-Ratio.tsx +25 -0
- package/src/components/ui/Avatar.tsx +103 -0
- package/src/components/ui/Badge.tsx +121 -0
- package/src/components/ui/Bottom-Sheet.tsx +224 -0
- package/src/components/ui/Button.tsx +100 -0
- package/src/components/ui/Card.tsx +185 -0
- package/src/components/ui/Checkbox.tsx +81 -0
- package/src/components/ui/Collapsible.tsx +40 -0
- package/src/components/ui/Context-Menu.tsx +407 -0
- package/src/components/ui/Custom-Card.tsx +226 -0
- package/src/components/ui/Dialog.tsx +240 -0
- package/src/components/ui/Dropdown-Menu.tsx +544 -0
- package/src/components/ui/Floating-Action.tsx +54 -0
- package/src/components/ui/Greeting-Card.tsx +471 -0
- package/src/components/ui/Hover-Card.tsx +101 -0
- package/src/components/ui/Icon.tsx +75 -0
- package/src/components/ui/Input.tsx +90 -0
- package/src/components/ui/Label.tsx +48 -0
- package/src/components/ui/Menubar.tsx +509 -0
- package/src/components/ui/Native-Only-Animated-View.tsx +37 -0
- package/src/components/ui/NavBar.tsx +397 -0
- package/src/components/ui/Popover.tsx +110 -0
- package/src/components/ui/Progress.tsx +138 -0
- package/src/components/ui/Radio-Group.tsx +79 -0
- package/src/components/ui/Select.tsx +344 -0
- package/src/components/ui/Separator.tsx +68 -0
- package/src/components/ui/SizedBox.tsx +116 -0
- package/src/components/ui/Skeleton.tsx +55 -0
- package/src/components/ui/Slider.tsx +222 -0
- package/src/components/ui/Switch.tsx +67 -0
- package/src/components/ui/Table.tsx +170 -0
- package/src/components/ui/Tabs.tsx +119 -0
- package/src/components/ui/Text.tsx +73 -0
- package/src/components/ui/Textarea.tsx +93 -0
- package/src/components/ui/Theme-Toggle.tsx +204 -0
- package/src/components/ui/Toast.tsx +127 -0
- package/src/components/ui/Toggle-Group.tsx +160 -0
- package/src/components/ui/Toggle.tsx +122 -0
- package/src/components/ui/Tooltip.tsx +117 -0
- package/src/components/ui/index.ts +42 -0
- package/src/index.tsx +24 -0
- package/src/lib/ThemeProvider.tsx +204 -0
- package/src/lib/cornerRadius.ts +160 -0
- package/src/lib/fonts.ts +28 -0
- package/src/lib/theme.ts +151 -0
- package/src/lib/utils.ts +146 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import * as Slot from '@rn-primitives/slot';
|
|
2
|
+
import type { SlottableViewProps, ViewRef } from '@rn-primitives/types';
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
|
+
import { cssInterop } from 'nativewind';
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import { View } from 'react-native';
|
|
7
|
+
import { cn } from '../../lib/utils';
|
|
8
|
+
import { TextClassContext } from './Text';
|
|
9
|
+
|
|
10
|
+
cssInterop(View, { className: 'style' });
|
|
11
|
+
cssInterop(Slot.View, { className: 'style' });
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Badge container style variants
|
|
15
|
+
*
|
|
16
|
+
* @variant default - Primary badge with solid background
|
|
17
|
+
* @variant secondary - Secondary badge with muted background
|
|
18
|
+
* @variant destructive - Destructive badge with danger/error styling
|
|
19
|
+
* @variant outline - Badge with border and transparent background
|
|
20
|
+
*/
|
|
21
|
+
const badgeVariants = cva(
|
|
22
|
+
'flex-row items-center rounded-full border border-border px-2.5 py-0.5 web:inline-flex',
|
|
23
|
+
{
|
|
24
|
+
variants: {
|
|
25
|
+
variant: {
|
|
26
|
+
default: 'border-transparent bg-primary',
|
|
27
|
+
secondary: 'border-transparent bg-secondary',
|
|
28
|
+
destructive: 'border-transparent bg-destructive',
|
|
29
|
+
outline: 'border-border',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: {
|
|
33
|
+
variant: 'default',
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Badge text style variants for matching text colors with badge variants
|
|
40
|
+
* Applied through TextClassContext to child Text components
|
|
41
|
+
*
|
|
42
|
+
* Variants match badgeVariants to ensure proper contrast
|
|
43
|
+
* @see badgeVariants
|
|
44
|
+
*/
|
|
45
|
+
const badgeTextVariants = cva('text-xs font-semibold', {
|
|
46
|
+
variants: {
|
|
47
|
+
variant: {
|
|
48
|
+
default: 'text-primary-foreground',
|
|
49
|
+
secondary: 'text-secondary-foreground',
|
|
50
|
+
destructive: 'text-destructive-foreground',
|
|
51
|
+
outline: 'text-foreground',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
defaultVariants: {
|
|
55
|
+
variant: 'default',
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Props for Badge component
|
|
61
|
+
*
|
|
62
|
+
* @extends SlottableViewProps - Includes all View props plus asChild for slot composition
|
|
63
|
+
* @extends VariantProps - Includes variant styling options from badgeVariants
|
|
64
|
+
*
|
|
65
|
+
* @property {string} [className] - Additional Tailwind classes for custom styling
|
|
66
|
+
* @property {boolean} [asChild] - When true, merges props into immediate child
|
|
67
|
+
* @property {'default' | 'secondary' | 'destructive' | 'outline'} [variant='default'] - Badge style variant
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```tsx
|
|
71
|
+
* <Badge variant="default">
|
|
72
|
+
* <Text>New</Text>
|
|
73
|
+
* </Badge>
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
type BadgeProps = SlottableViewProps &
|
|
77
|
+
VariantProps<typeof badgeVariants> & { className?: string };
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A compact badge component for displaying status, labels, or counts
|
|
81
|
+
*
|
|
82
|
+
* Small, pill-shaped component for highlighting information. Supports multiple
|
|
83
|
+
* variants for different contexts and automatically styles child Text components.
|
|
84
|
+
*
|
|
85
|
+
* @component
|
|
86
|
+
* @example
|
|
87
|
+
* ```tsx
|
|
88
|
+
* // Status badge
|
|
89
|
+
* <Badge variant="default">
|
|
90
|
+
* <Text>Active</Text>
|
|
91
|
+
* </Badge>
|
|
92
|
+
*
|
|
93
|
+
* // Count badge
|
|
94
|
+
* <Badge variant="destructive">
|
|
95
|
+
* <Text>5</Text>
|
|
96
|
+
* </Badge>
|
|
97
|
+
*
|
|
98
|
+
* // Outline badge
|
|
99
|
+
* <Badge variant="outline">
|
|
100
|
+
* <Text>Draft</Text>
|
|
101
|
+
* </Badge>
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
const Badge = React.forwardRef<ViewRef, BadgeProps>(
|
|
105
|
+
({ className, variant, asChild = false, ...props }, ref) => {
|
|
106
|
+
const Component = asChild ? Slot.View : View;
|
|
107
|
+
return (
|
|
108
|
+
<TextClassContext.Provider value={cn(badgeTextVariants({ variant }))}>
|
|
109
|
+
<Component
|
|
110
|
+
className={cn(badgeVariants({ variant, className }))}
|
|
111
|
+
ref={ref as any}
|
|
112
|
+
{...props}
|
|
113
|
+
/>
|
|
114
|
+
</TextClassContext.Provider>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
Badge.displayName = 'Badge';
|
|
119
|
+
|
|
120
|
+
export { Badge, badgeTextVariants, badgeVariants };
|
|
121
|
+
export type { BadgeProps };
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useCallback,
|
|
4
|
+
useImperativeHandle,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import { StyleSheet, View, ViewStyle } from 'react-native';
|
|
9
|
+
import BottomSheetPrimitive, {
|
|
10
|
+
BottomSheetBackdrop,
|
|
11
|
+
BottomSheetBackdropProps,
|
|
12
|
+
BottomSheetView,
|
|
13
|
+
} from '@gorhom/bottom-sheet';
|
|
14
|
+
import { cn } from '../../lib/utils';
|
|
15
|
+
import { useTheme } from '../../lib/ThemeProvider';
|
|
16
|
+
import { hslToRgb } from '../theme-config';
|
|
17
|
+
|
|
18
|
+
type BottomSheetRef = {
|
|
19
|
+
open: () => void;
|
|
20
|
+
close: () => void;
|
|
21
|
+
expand: () => void;
|
|
22
|
+
collapse: () => void;
|
|
23
|
+
snapToIndex: (index: number) => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
interface BottomSheetProps {
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
snapPoints?: (string | number)[];
|
|
29
|
+
initialSnapIndex?: number;
|
|
30
|
+
enablePanDownToClose?: boolean;
|
|
31
|
+
enableDynamicSizing?: boolean;
|
|
32
|
+
backdropOpacity?: number;
|
|
33
|
+
enableBackdropDismiss?: boolean;
|
|
34
|
+
handleIndicatorStyle?: ViewStyle;
|
|
35
|
+
backgroundStyle?: ViewStyle;
|
|
36
|
+
className?: string;
|
|
37
|
+
containerClassName?: string;
|
|
38
|
+
onChange?: (index: number) => void;
|
|
39
|
+
onClose?: () => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const BottomSheet = forwardRef<BottomSheetRef, BottomSheetProps>(
|
|
43
|
+
(
|
|
44
|
+
{
|
|
45
|
+
children,
|
|
46
|
+
snapPoints: customSnapPoints,
|
|
47
|
+
initialSnapIndex = -1,
|
|
48
|
+
enablePanDownToClose = true,
|
|
49
|
+
enableDynamicSizing = false,
|
|
50
|
+
backdropOpacity = 0.5,
|
|
51
|
+
enableBackdropDismiss = true,
|
|
52
|
+
handleIndicatorStyle,
|
|
53
|
+
backgroundStyle,
|
|
54
|
+
className,
|
|
55
|
+
containerClassName,
|
|
56
|
+
onChange,
|
|
57
|
+
onClose,
|
|
58
|
+
},
|
|
59
|
+
ref
|
|
60
|
+
) => {
|
|
61
|
+
const bottomSheetRef = useRef<BottomSheetPrimitive>(null);
|
|
62
|
+
const { colors } = useTheme();
|
|
63
|
+
|
|
64
|
+
// Default snap points if not provided
|
|
65
|
+
const snapPoints = useMemo(
|
|
66
|
+
() => customSnapPoints ?? ['25%', '50%', '90%'],
|
|
67
|
+
[customSnapPoints]
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// Expose methods via ref (similar to Flutter's showModalBottomSheet)
|
|
71
|
+
useImperativeHandle(ref, () => ({
|
|
72
|
+
open: () => bottomSheetRef.current?.expand(),
|
|
73
|
+
close: () => bottomSheetRef.current?.close(),
|
|
74
|
+
expand: () => bottomSheetRef.current?.expand(),
|
|
75
|
+
collapse: () => bottomSheetRef.current?.collapse(),
|
|
76
|
+
snapToIndex: (index: number) =>
|
|
77
|
+
bottomSheetRef.current?.snapToIndex(index),
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
// Custom backdrop component
|
|
81
|
+
const renderBackdrop = useCallback(
|
|
82
|
+
(props: BottomSheetBackdropProps) => (
|
|
83
|
+
<BottomSheetBackdrop
|
|
84
|
+
{...props}
|
|
85
|
+
opacity={backdropOpacity}
|
|
86
|
+
appearsOnIndex={0}
|
|
87
|
+
disappearsOnIndex={-1}
|
|
88
|
+
pressBehavior={enableBackdropDismiss ? 'close' : 'none'}
|
|
89
|
+
/>
|
|
90
|
+
),
|
|
91
|
+
[backdropOpacity, enableBackdropDismiss]
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Handle sheet changes
|
|
95
|
+
const handleSheetChanges = useCallback(
|
|
96
|
+
(index: number) => {
|
|
97
|
+
onChange?.(index);
|
|
98
|
+
if (index === -1) {
|
|
99
|
+
onClose?.();
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
[onChange, onClose]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// Theme-aware background style - convert HSL to RGB for React Native
|
|
106
|
+
const themedBackgroundStyle = useMemo(
|
|
107
|
+
() => [
|
|
108
|
+
styles.background,
|
|
109
|
+
{ backgroundColor: hslToRgb(colors.card) },
|
|
110
|
+
backgroundStyle,
|
|
111
|
+
],
|
|
112
|
+
[colors.card, backgroundStyle]
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Theme-aware handle indicator style - convert HSL to RGB for React Native
|
|
116
|
+
const themedHandleStyle = useMemo(
|
|
117
|
+
() => [
|
|
118
|
+
styles.handleIndicator,
|
|
119
|
+
{ backgroundColor: hslToRgb(colors.border) },
|
|
120
|
+
handleIndicatorStyle,
|
|
121
|
+
],
|
|
122
|
+
[colors.border, handleIndicatorStyle]
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<BottomSheetPrimitive
|
|
127
|
+
ref={bottomSheetRef}
|
|
128
|
+
index={initialSnapIndex}
|
|
129
|
+
snapPoints={snapPoints}
|
|
130
|
+
enablePanDownToClose={enablePanDownToClose}
|
|
131
|
+
enableDynamicSizing={enableDynamicSizing}
|
|
132
|
+
backdropComponent={renderBackdrop}
|
|
133
|
+
onChange={handleSheetChanges}
|
|
134
|
+
handleIndicatorStyle={themedHandleStyle}
|
|
135
|
+
backgroundStyle={themedBackgroundStyle}
|
|
136
|
+
style={styles.container}
|
|
137
|
+
animateOnMount={false}
|
|
138
|
+
>
|
|
139
|
+
<BottomSheetView
|
|
140
|
+
style={[styles.contentContainer]}
|
|
141
|
+
className={cn('flex-1 bg-card', containerClassName)}
|
|
142
|
+
>
|
|
143
|
+
<View className={cn('flex-1', className)}>{children}</View>
|
|
144
|
+
</BottomSheetView>
|
|
145
|
+
</BottomSheetPrimitive>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
BottomSheet.displayName = 'BottomSheet';
|
|
151
|
+
|
|
152
|
+
const styles = StyleSheet.create({
|
|
153
|
+
container: {
|
|
154
|
+
zIndex: 9999,
|
|
155
|
+
},
|
|
156
|
+
contentContainer: {
|
|
157
|
+
flex: 1,
|
|
158
|
+
},
|
|
159
|
+
background: {
|
|
160
|
+
borderTopLeftRadius: 20,
|
|
161
|
+
borderTopRightRadius: 20,
|
|
162
|
+
},
|
|
163
|
+
handleIndicator: {
|
|
164
|
+
width: 40,
|
|
165
|
+
height: 4,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Helper component for bottom sheet header
|
|
170
|
+
interface BottomSheetHeaderProps {
|
|
171
|
+
children: React.ReactNode;
|
|
172
|
+
className?: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const BottomSheetHeader = ({ children, className }: BottomSheetHeaderProps) => {
|
|
176
|
+
return (
|
|
177
|
+
<View
|
|
178
|
+
className={cn('border-b border-border px-4 py-3 web:px-6', className)}
|
|
179
|
+
>
|
|
180
|
+
{children}
|
|
181
|
+
</View>
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// Helper component for bottom sheet content
|
|
186
|
+
interface BottomSheetContentProps {
|
|
187
|
+
children: React.ReactNode;
|
|
188
|
+
className?: string;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const BottomSheetContent = ({
|
|
192
|
+
children,
|
|
193
|
+
className,
|
|
194
|
+
}: BottomSheetContentProps) => {
|
|
195
|
+
return (
|
|
196
|
+
<View className={cn('flex-1 px-4 py-4 web:px-6', className)}>
|
|
197
|
+
{children}
|
|
198
|
+
</View>
|
|
199
|
+
);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Helper component for bottom sheet footer
|
|
203
|
+
interface BottomSheetFooterProps {
|
|
204
|
+
children: React.ReactNode;
|
|
205
|
+
className?: string;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const BottomSheetFooter = ({ children, className }: BottomSheetFooterProps) => {
|
|
209
|
+
return (
|
|
210
|
+
<View
|
|
211
|
+
className={cn('border-t border-border px-4 py-3 web:px-6', className)}
|
|
212
|
+
>
|
|
213
|
+
{children}
|
|
214
|
+
</View>
|
|
215
|
+
);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export {
|
|
219
|
+
BottomSheet,
|
|
220
|
+
BottomSheetHeader,
|
|
221
|
+
BottomSheetContent,
|
|
222
|
+
BottomSheetFooter,
|
|
223
|
+
};
|
|
224
|
+
export type { BottomSheetRef, BottomSheetProps };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import * as Slot from '@rn-primitives/slot';
|
|
2
|
+
import type {
|
|
3
|
+
PressableRef,
|
|
4
|
+
SlottablePressableProps,
|
|
5
|
+
} from '@rn-primitives/types';
|
|
6
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
7
|
+
import { cssInterop } from 'nativewind';
|
|
8
|
+
import * as React from 'react';
|
|
9
|
+
import { Pressable } from 'react-native';
|
|
10
|
+
import { cn } from '../../lib/utils';
|
|
11
|
+
import { TextClassContext } from './Text';
|
|
12
|
+
|
|
13
|
+
cssInterop(Pressable, { className: 'style' });
|
|
14
|
+
cssInterop(Slot.Pressable, { className: 'style' });
|
|
15
|
+
|
|
16
|
+
const buttonVariants = cva(
|
|
17
|
+
'group flex items-center justify-center rounded-4 web:ring-offset-background web:transition-colors web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2',
|
|
18
|
+
{
|
|
19
|
+
variants: {
|
|
20
|
+
variant: {
|
|
21
|
+
default: 'bg-primary active:opacity-20',
|
|
22
|
+
destructive: 'bg-destructive active:opacity-90',
|
|
23
|
+
outline: 'border border-input bg-background active:bg-accent',
|
|
24
|
+
secondary: 'bg-secondary active:opacity-80',
|
|
25
|
+
ghost: 'active:bg-accent',
|
|
26
|
+
link: 'web:underline-offset-4',
|
|
27
|
+
},
|
|
28
|
+
size: {
|
|
29
|
+
default:
|
|
30
|
+
'h-10 px-button-x py-button-y native:h-12 native:px-button-x native:py-button-y',
|
|
31
|
+
sm: 'h-9 px-3',
|
|
32
|
+
lg: 'h-11 px-8 native:h-14',
|
|
33
|
+
icon: 'h-10 w-10',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
defaultVariants: {
|
|
37
|
+
variant: 'default',
|
|
38
|
+
size: 'default',
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const buttonTextVariants = cva(
|
|
44
|
+
'web:whitespace-nowrap text-sm native:text-base font-medium web:transition-colors',
|
|
45
|
+
{
|
|
46
|
+
variants: {
|
|
47
|
+
variant: {
|
|
48
|
+
default: 'text-primary-foreground',
|
|
49
|
+
destructive: 'text-destructive-foreground',
|
|
50
|
+
outline: 'text-foreground group-active:text-accent-foreground',
|
|
51
|
+
secondary:
|
|
52
|
+
'text-secondary-foreground group-active:text-secondary-foreground',
|
|
53
|
+
ghost: 'text-foreground group-active:text-accent-foreground',
|
|
54
|
+
link: 'text-primary group-active:underline',
|
|
55
|
+
},
|
|
56
|
+
size: {
|
|
57
|
+
default: '',
|
|
58
|
+
sm: '',
|
|
59
|
+
lg: 'native:text-lg',
|
|
60
|
+
icon: '',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
defaultVariants: {
|
|
64
|
+
variant: 'default',
|
|
65
|
+
size: 'default',
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
type ButtonProps = SlottablePressableProps &
|
|
71
|
+
VariantProps<typeof buttonVariants> & {
|
|
72
|
+
className?: string;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const Button = React.forwardRef<PressableRef, ButtonProps>(
|
|
76
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
77
|
+
const Component = asChild ? Slot.Pressable : Pressable;
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<TextClassContext.Provider
|
|
81
|
+
value={cn(buttonTextVariants({ variant, size }))}
|
|
82
|
+
>
|
|
83
|
+
<Component
|
|
84
|
+
ref={ref}
|
|
85
|
+
role="button"
|
|
86
|
+
className={cn(
|
|
87
|
+
buttonVariants({ variant, size, className }),
|
|
88
|
+
props.disabled && 'opacity-50 web:pointer-events-none'
|
|
89
|
+
)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
</TextClassContext.Provider>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
Button.displayName = 'Button';
|
|
98
|
+
|
|
99
|
+
export { Button, buttonTextVariants, buttonVariants };
|
|
100
|
+
export type { ButtonProps };
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import * as Slot from '@rn-primitives/slot';
|
|
2
|
+
import type { SlottableViewProps, ViewRef } from '@rn-primitives/types';
|
|
3
|
+
import { cssInterop } from 'nativewind';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import { View } from 'react-native';
|
|
6
|
+
import { cn } from '../../lib/utils';
|
|
7
|
+
import { TextClassContext } from './Text';
|
|
8
|
+
|
|
9
|
+
cssInterop(View, { className: 'style' });
|
|
10
|
+
cssInterop(Slot.View, { className: 'style' });
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Card container component - Main wrapper for card content
|
|
14
|
+
*
|
|
15
|
+
* Provides elevated container with border, rounded corners, and shadow.
|
|
16
|
+
* Use with CardHeader, CardTitle, CardDescription, CardContent, and CardFooter
|
|
17
|
+
* for a complete card structure.
|
|
18
|
+
*
|
|
19
|
+
* @component
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* <Card>
|
|
23
|
+
* <CardHeader>
|
|
24
|
+
* <CardTitle><Text>Title</Text></CardTitle>
|
|
25
|
+
* <CardDescription><Text>Description</Text></CardDescription>
|
|
26
|
+
* </CardHeader>
|
|
27
|
+
* <CardContent>
|
|
28
|
+
* <Text>Card content goes here</Text>
|
|
29
|
+
* </CardContent>
|
|
30
|
+
* <CardFooter>
|
|
31
|
+
* <Button>Action</Button>
|
|
32
|
+
* </CardFooter>
|
|
33
|
+
* </Card>
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @property {boolean} [asChild] - When true, merges props into immediate child
|
|
37
|
+
* @property {string} [className] - Additional Tailwind classes
|
|
38
|
+
*/
|
|
39
|
+
const Card = React.forwardRef<
|
|
40
|
+
ViewRef,
|
|
41
|
+
SlottableViewProps & { className?: string }
|
|
42
|
+
>(({ className, asChild = false, ...props }, ref) => {
|
|
43
|
+
const Component = asChild ? Slot.View : View;
|
|
44
|
+
return (
|
|
45
|
+
<Component
|
|
46
|
+
className={cn(
|
|
47
|
+
'rounded-lg border border-border dark:border-border bg-card dark:bg-card p-6 shadow-sm shadow-foreground/10 dark:shadow-white/10',
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
50
|
+
ref={ref as any}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
Card.displayName = 'Card';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* CardHeader - Container for card title and description
|
|
59
|
+
*
|
|
60
|
+
* Provides proper spacing for title and description elements at the top of a card.
|
|
61
|
+
*
|
|
62
|
+
* @property {boolean} [asChild] - When true, merges props into immediate child
|
|
63
|
+
* @property {string} [className] - Additional Tailwind classes
|
|
64
|
+
*/
|
|
65
|
+
const CardHeader = React.forwardRef<
|
|
66
|
+
ViewRef,
|
|
67
|
+
SlottableViewProps & { className?: string }
|
|
68
|
+
>(({ className, asChild = false, ...props }, ref) => {
|
|
69
|
+
const Component = asChild ? Slot.View : View;
|
|
70
|
+
return (
|
|
71
|
+
<Component
|
|
72
|
+
className={cn('flex flex-col gap-1.5 p-6', className)}
|
|
73
|
+
ref={ref as any}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
CardHeader.displayName = 'CardHeader';
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* CardTitle - Main title text for the card
|
|
82
|
+
*
|
|
83
|
+
* Automatically styles child Text components with large, semibold typography.
|
|
84
|
+
* Uses TextClassContext to apply consistent styling to nested Text elements.
|
|
85
|
+
*
|
|
86
|
+
* @property {boolean} [asChild] - When true, merges props into immediate child
|
|
87
|
+
* @property {string} [className] - Additional Tailwind classes for text styling
|
|
88
|
+
*/
|
|
89
|
+
const CardTitle = React.forwardRef<
|
|
90
|
+
ViewRef,
|
|
91
|
+
SlottableViewProps & { className?: string }
|
|
92
|
+
>(({ className, asChild = false, ...props }, ref) => {
|
|
93
|
+
const Component = asChild ? Slot.View : View;
|
|
94
|
+
return (
|
|
95
|
+
<TextClassContext.Provider
|
|
96
|
+
value={cn(
|
|
97
|
+
'text-2xl font-semibold leading-none tracking-tight text-card-foreground dark:text-card-foreground',
|
|
98
|
+
className
|
|
99
|
+
)}
|
|
100
|
+
>
|
|
101
|
+
<Component ref={ref as any} {...props} />
|
|
102
|
+
</TextClassContext.Provider>
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
CardTitle.displayName = 'CardTitle';
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* CardDescription - Subtitle or description text for the card
|
|
109
|
+
*
|
|
110
|
+
* Automatically styles child Text components with muted, smaller typography.
|
|
111
|
+
* Uses TextClassContext to apply consistent styling to nested Text elements.
|
|
112
|
+
*
|
|
113
|
+
* @property {boolean} [asChild] - When true, merges props into immediate child
|
|
114
|
+
* @property {string} [className] - Additional Tailwind classes for text styling
|
|
115
|
+
*/
|
|
116
|
+
const CardDescription = React.forwardRef<
|
|
117
|
+
ViewRef,
|
|
118
|
+
SlottableViewProps & { className?: string }
|
|
119
|
+
>(({ className, asChild = false, ...props }, ref) => {
|
|
120
|
+
const Component = asChild ? Slot.View : View;
|
|
121
|
+
return (
|
|
122
|
+
<TextClassContext.Provider
|
|
123
|
+
value={cn(
|
|
124
|
+
'text-sm text-muted-foreground dark:text-muted-foreground',
|
|
125
|
+
className
|
|
126
|
+
)}
|
|
127
|
+
>
|
|
128
|
+
<Component ref={ref as any} {...props} />
|
|
129
|
+
</TextClassContext.Provider>
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
CardDescription.displayName = 'CardDescription';
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* CardContent - Main content area of the card
|
|
136
|
+
*
|
|
137
|
+
* Container for the primary card content with appropriate padding.
|
|
138
|
+
* Top padding is removed to flow naturally after CardHeader.
|
|
139
|
+
*
|
|
140
|
+
* @property {boolean} [asChild] - When true, merges props into immediate child
|
|
141
|
+
* @property {string} [className] - Additional Tailwind classes
|
|
142
|
+
*/
|
|
143
|
+
const CardContent = React.forwardRef<
|
|
144
|
+
ViewRef,
|
|
145
|
+
SlottableViewProps & { className?: string }
|
|
146
|
+
>(({ className, asChild = false, ...props }, ref) => {
|
|
147
|
+
const Component = asChild ? Slot.View : View;
|
|
148
|
+
return (
|
|
149
|
+
<Component className={cn('p-6 pt-0', className)} ref={ref} {...props} />
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
CardContent.displayName = 'CardContent';
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* CardFooter - Footer area for actions or additional info
|
|
156
|
+
*
|
|
157
|
+
* Typically contains buttons or other interactive elements.
|
|
158
|
+
* Arranged as a row with centered items.
|
|
159
|
+
*
|
|
160
|
+
* @property {boolean} [asChild] - When true, merges props into immediate child
|
|
161
|
+
* @property {string} [className] - Additional Tailwind classes
|
|
162
|
+
*/
|
|
163
|
+
const CardFooter = React.forwardRef<
|
|
164
|
+
ViewRef,
|
|
165
|
+
SlottableViewProps & { className?: string }
|
|
166
|
+
>(({ className, asChild = false, ...props }, ref) => {
|
|
167
|
+
const Component = asChild ? Slot.View : View;
|
|
168
|
+
return (
|
|
169
|
+
<Component
|
|
170
|
+
className={cn('flex flex-row items-center p-6 pt-0', className)}
|
|
171
|
+
ref={ref as any}
|
|
172
|
+
{...props}
|
|
173
|
+
/>
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
CardFooter.displayName = 'CardFooter';
|
|
177
|
+
|
|
178
|
+
export {
|
|
179
|
+
Card,
|
|
180
|
+
CardContent,
|
|
181
|
+
CardDescription,
|
|
182
|
+
CardFooter,
|
|
183
|
+
CardHeader,
|
|
184
|
+
CardTitle,
|
|
185
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Icon } from './Icon';
|
|
2
|
+
import { cn } from '../../lib/utils';
|
|
3
|
+
import * as CheckboxPrimitive from '@rn-primitives/checkbox';
|
|
4
|
+
import { Check } from 'lucide-react-native';
|
|
5
|
+
import { Platform } from 'react-native';
|
|
6
|
+
|
|
7
|
+
const DEFAULT_HIT_SLOP = 24;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Interactive checkbox for boolean selections
|
|
11
|
+
*
|
|
12
|
+
* A control that allows users to toggle between checked and unchecked states.
|
|
13
|
+
* Features a checkmark indicator, configurable styling, and accessible touch targets.
|
|
14
|
+
*
|
|
15
|
+
* @component
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* // Basic checkbox
|
|
19
|
+
* <Checkbox checked={isChecked} onCheckedChange={setIsChecked} />
|
|
20
|
+
*
|
|
21
|
+
* // Checkbox with label
|
|
22
|
+
* <View className="flex-row items-center gap-2">
|
|
23
|
+
* <Checkbox id="terms" checked={accepted} onCheckedChange={setAccepted} />
|
|
24
|
+
* <Label htmlFor="terms">Accept terms and conditions</Label>
|
|
25
|
+
* </View>
|
|
26
|
+
*
|
|
27
|
+
* // Disabled checkbox
|
|
28
|
+
* <Checkbox checked={true} disabled />
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @accessibility
|
|
32
|
+
* - Uses 24px hit slop for easier touch interaction
|
|
33
|
+
* - Supports keyboard navigation on web
|
|
34
|
+
* - Proper ARIA attributes for screen readers
|
|
35
|
+
* - Visual focus indicators on web
|
|
36
|
+
*/
|
|
37
|
+
function Checkbox({
|
|
38
|
+
className,
|
|
39
|
+
checkedClassName,
|
|
40
|
+
indicatorClassName,
|
|
41
|
+
iconClassName,
|
|
42
|
+
...props
|
|
43
|
+
}: CheckboxPrimitive.RootProps &
|
|
44
|
+
React.RefAttributes<CheckboxPrimitive.RootRef> & {
|
|
45
|
+
checkedClassName?: string;
|
|
46
|
+
indicatorClassName?: string;
|
|
47
|
+
iconClassName?: string;
|
|
48
|
+
}) {
|
|
49
|
+
return (
|
|
50
|
+
<CheckboxPrimitive.Root
|
|
51
|
+
className={cn(
|
|
52
|
+
'border-input dark:bg-input/30 size-4 shrink-0 rounded-[4px] border shadow-sm shadow-black/5',
|
|
53
|
+
Platform.select({
|
|
54
|
+
web: 'focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive peer cursor-default outline-none transition-shadow focus-visible:ring-[3px] disabled:cursor-not-allowed',
|
|
55
|
+
native: 'overflow-hidden',
|
|
56
|
+
}),
|
|
57
|
+
props.checked && cn('border-primary', checkedClassName),
|
|
58
|
+
props.disabled && 'opacity-50',
|
|
59
|
+
className
|
|
60
|
+
)}
|
|
61
|
+
hitSlop={DEFAULT_HIT_SLOP}
|
|
62
|
+
{...props}
|
|
63
|
+
>
|
|
64
|
+
<CheckboxPrimitive.Indicator
|
|
65
|
+
className={cn(
|
|
66
|
+
'bg-primary h-full w-full items-center justify-center',
|
|
67
|
+
indicatorClassName
|
|
68
|
+
)}
|
|
69
|
+
>
|
|
70
|
+
<Icon
|
|
71
|
+
as={Check}
|
|
72
|
+
size={12}
|
|
73
|
+
strokeWidth={Platform.OS === 'web' ? 2.5 : 3.5}
|
|
74
|
+
className={cn('text-primary-foreground', iconClassName)}
|
|
75
|
+
/>
|
|
76
|
+
</CheckboxPrimitive.Indicator>
|
|
77
|
+
</CheckboxPrimitive.Root>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { Checkbox };
|