@mrmeg/expo-ui 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/README.md +96 -0
- package/dist/components/Accordion.d.ts +54 -0
- package/dist/components/Accordion.js +149 -0
- package/dist/components/Alert.d.ts +30 -0
- package/dist/components/Alert.js +25 -0
- package/dist/components/AnimatedView.d.ts +55 -0
- package/dist/components/AnimatedView.js +39 -0
- package/dist/components/Badge.d.ts +23 -0
- package/dist/components/Badge.js +74 -0
- package/dist/components/BottomSheet.d.ts +74 -0
- package/dist/components/BottomSheet.js +513 -0
- package/dist/components/Button.d.ts +129 -0
- package/dist/components/Button.js +216 -0
- package/dist/components/Card.d.ts +42 -0
- package/dist/components/Card.js +126 -0
- package/dist/components/Checkbox.d.ts +39 -0
- package/dist/components/Checkbox.js +96 -0
- package/dist/components/Collapsible.d.ts +67 -0
- package/dist/components/Collapsible.js +38 -0
- package/dist/components/Dialog.d.ts +140 -0
- package/dist/components/Dialog.js +167 -0
- package/dist/components/DismissKeyboard.d.ts +15 -0
- package/dist/components/DismissKeyboard.js +13 -0
- package/dist/components/Drawer.d.ts +74 -0
- package/dist/components/Drawer.js +423 -0
- package/dist/components/DropdownMenu.d.ts +120 -0
- package/dist/components/DropdownMenu.js +211 -0
- package/dist/components/EmptyState.d.ts +42 -0
- package/dist/components/EmptyState.js +58 -0
- package/dist/components/ErrorBoundary.d.ts +53 -0
- package/dist/components/ErrorBoundary.js +75 -0
- package/dist/components/Icon.d.ts +46 -0
- package/dist/components/Icon.js +40 -0
- package/dist/components/InputOTP.d.ts +72 -0
- package/dist/components/InputOTP.js +155 -0
- package/dist/components/Label.d.ts +61 -0
- package/dist/components/Label.js +72 -0
- package/dist/components/MaxWidthContainer.d.ts +58 -0
- package/dist/components/MaxWidthContainer.js +64 -0
- package/dist/components/Notification.d.ts +26 -0
- package/dist/components/Notification.js +230 -0
- package/dist/components/Popover.d.ts +79 -0
- package/dist/components/Popover.js +91 -0
- package/dist/components/Progress.d.ts +28 -0
- package/dist/components/Progress.js +107 -0
- package/dist/components/RadioGroup.d.ts +65 -0
- package/dist/components/RadioGroup.js +142 -0
- package/dist/components/Select.d.ts +88 -0
- package/dist/components/Select.js +172 -0
- package/dist/components/Separator.d.ts +83 -0
- package/dist/components/Separator.js +85 -0
- package/dist/components/Skeleton.d.ts +68 -0
- package/dist/components/Skeleton.js +99 -0
- package/dist/components/Slider.d.ts +24 -0
- package/dist/components/Slider.js +162 -0
- package/dist/components/StatusBar.d.ts +1 -0
- package/dist/components/StatusBar.js +19 -0
- package/dist/components/StyledText.d.ts +161 -0
- package/dist/components/StyledText.js +193 -0
- package/dist/components/Switch.d.ts +44 -0
- package/dist/components/Switch.js +129 -0
- package/dist/components/Tabs.d.ts +31 -0
- package/dist/components/Tabs.js +127 -0
- package/dist/components/TextInput.d.ts +120 -0
- package/dist/components/TextInput.js +263 -0
- package/dist/components/Toggle.d.ts +106 -0
- package/dist/components/Toggle.js +150 -0
- package/dist/components/ToggleGroup.d.ts +80 -0
- package/dist/components/ToggleGroup.js +189 -0
- package/dist/components/Tooltip.d.ts +121 -0
- package/dist/components/Tooltip.js +132 -0
- package/dist/components/index.d.ts +35 -0
- package/dist/components/index.js +35 -0
- package/dist/constants/colors.d.ts +82 -0
- package/dist/constants/colors.js +116 -0
- package/dist/constants/fonts.d.ts +32 -0
- package/dist/constants/fonts.js +91 -0
- package/dist/constants/index.d.ts +3 -0
- package/dist/constants/index.js +3 -0
- package/dist/constants/spacing.d.ts +40 -0
- package/dist/constants/spacing.js +48 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/useDimensions.d.ts +19 -0
- package/dist/hooks/useDimensions.js +55 -0
- package/dist/hooks/useReduceMotion.d.ts +5 -0
- package/dist/hooks/useReduceMotion.js +64 -0
- package/dist/hooks/useResources.d.ts +12 -0
- package/dist/hooks/useResources.js +56 -0
- package/dist/hooks/useScalePress.d.ts +57 -0
- package/dist/hooks/useScalePress.js +55 -0
- package/dist/hooks/useStaggeredEntrance.d.ts +67 -0
- package/dist/hooks/useStaggeredEntrance.js +74 -0
- package/dist/hooks/useTheme.d.ts +88 -0
- package/dist/hooks/useTheme.js +328 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/lib/animations.d.ts +1 -0
- package/dist/lib/animations.js +3 -0
- package/dist/lib/haptics.d.ts +3 -0
- package/dist/lib/haptics.js +29 -0
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/index.js +3 -0
- package/dist/lib/sentry.d.ts +16 -0
- package/dist/lib/sentry.js +55 -0
- package/dist/state/globalUIStore.d.ts +30 -0
- package/dist/state/globalUIStore.js +8 -0
- package/dist/state/index.d.ts +2 -0
- package/dist/state/index.js +2 -0
- package/dist/state/themeStore.d.ts +6 -0
- package/dist/state/themeStore.js +38 -0
- package/package.json +92 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Pressable, StyleSheet, View, Platform, ActivityIndicator, } from "react-native";
|
|
4
|
+
import Animated from "react-native-reanimated";
|
|
5
|
+
import { spacing } from "../constants/spacing";
|
|
6
|
+
import { StyledText, TextColorContext } from "./StyledText";
|
|
7
|
+
import { fontFamilies } from "../constants/fonts";
|
|
8
|
+
import { palette } from "../constants/colors";
|
|
9
|
+
import { useTheme } from "../hooks/useTheme";
|
|
10
|
+
import { useScalePress } from "../hooks/useScalePress";
|
|
11
|
+
const SIZE_CONFIGS = {
|
|
12
|
+
sm: {
|
|
13
|
+
paddingVertical: spacing.xs,
|
|
14
|
+
paddingHorizontal: 12,
|
|
15
|
+
fontSize: 12,
|
|
16
|
+
height: 32,
|
|
17
|
+
},
|
|
18
|
+
md: {
|
|
19
|
+
paddingVertical: spacing.sm,
|
|
20
|
+
paddingHorizontal: spacing.md,
|
|
21
|
+
fontSize: 14,
|
|
22
|
+
height: 36,
|
|
23
|
+
},
|
|
24
|
+
lg: {
|
|
25
|
+
paddingVertical: 10,
|
|
26
|
+
paddingHorizontal: spacing.lg,
|
|
27
|
+
fontSize: 16,
|
|
28
|
+
height: 44,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Enhanced Button Component
|
|
33
|
+
*
|
|
34
|
+
* Features:
|
|
35
|
+
* - 6 variants (default, outline, ghost, link, destructive, secondary)
|
|
36
|
+
* - 3 sizes (sm, md, lg)
|
|
37
|
+
* - Loading state with spinner
|
|
38
|
+
* - Full width option
|
|
39
|
+
* - Shadow support
|
|
40
|
+
* - Automatic text contrast calculation
|
|
41
|
+
* - Accessory components (left/right)
|
|
42
|
+
*
|
|
43
|
+
* Usage:
|
|
44
|
+
* ```tsx
|
|
45
|
+
* // Default button
|
|
46
|
+
* <Button onPress={handler}>
|
|
47
|
+
* <SansSerifBoldText>Click Me</SansSerifBoldText>
|
|
48
|
+
* </Button>
|
|
49
|
+
*
|
|
50
|
+
* // Different variants
|
|
51
|
+
* <Button preset="outline" onPress={handler}>Outline</Button>
|
|
52
|
+
* <Button preset="ghost" onPress={handler}>Ghost</Button>
|
|
53
|
+
* <Button preset="destructive" onPress={handler}>Delete</Button>
|
|
54
|
+
*
|
|
55
|
+
* // Different sizes
|
|
56
|
+
* <Button size="sm" onPress={handler}>Small</Button>
|
|
57
|
+
* <Button size="lg" onPress={handler}>Large</Button>
|
|
58
|
+
*
|
|
59
|
+
* // Loading state
|
|
60
|
+
* <Button loading onPress={handler}>Processing...</Button>
|
|
61
|
+
*
|
|
62
|
+
* // Full width
|
|
63
|
+
* <Button fullWidth onPress={handler}>Submit</Button>
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export function Button(props) {
|
|
67
|
+
const { tx, text, txOptions, style: styleOverride, pressedStyle: pressedStyleOverride, textStyle: textStyleOverride, pressedTextStyle: pressedTextStyleOverride, disabledTextStyle: disabledTextStyleOverride, children, RightAccessory, LeftAccessory, disabled, disabledStyle: disabledStyleOverride, withShadow = false, preset = "default", size = "md", loading = false, fullWidth = false, ...rest } = props;
|
|
68
|
+
const { theme, getContrastingColor, getShadowStyle } = useTheme();
|
|
69
|
+
const styles = createStyles(theme, size);
|
|
70
|
+
const shadowStyle = getShadowStyle("base");
|
|
71
|
+
const sizeConfig = SIZE_CONFIGS[size];
|
|
72
|
+
// Pre-compute background color for contrast calculation
|
|
73
|
+
// Always flatten to handle both array styles (from Slot) and RegisteredStyle
|
|
74
|
+
const flattenedStyle = styleOverride ? StyleSheet.flatten(styleOverride) : undefined;
|
|
75
|
+
const customBgColor = flattenedStyle?.backgroundColor;
|
|
76
|
+
// Calculate background color for each preset
|
|
77
|
+
let backgroundColor;
|
|
78
|
+
if (customBgColor && typeof customBgColor === "string") {
|
|
79
|
+
backgroundColor = customBgColor;
|
|
80
|
+
}
|
|
81
|
+
else if (preset === "default") {
|
|
82
|
+
backgroundColor = theme.colors.accent;
|
|
83
|
+
}
|
|
84
|
+
else if (preset === "secondary") {
|
|
85
|
+
backgroundColor = theme.colors.primary;
|
|
86
|
+
}
|
|
87
|
+
else if (preset === "destructive") {
|
|
88
|
+
backgroundColor = theme.colors.destructive;
|
|
89
|
+
}
|
|
90
|
+
else if (preset === "outline" || preset === "ghost" || preset === "link") {
|
|
91
|
+
backgroundColor = "transparent";
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
backgroundColor = theme.colors.accent;
|
|
95
|
+
}
|
|
96
|
+
// Determine text color per preset for readable contrast
|
|
97
|
+
const textColor = preset === "outline"
|
|
98
|
+
? theme.colors.foreground
|
|
99
|
+
: preset === "ghost"
|
|
100
|
+
? theme.colors.text
|
|
101
|
+
: preset === "link"
|
|
102
|
+
? theme.colors.accent
|
|
103
|
+
: preset === "default"
|
|
104
|
+
? theme.colors.accentForeground
|
|
105
|
+
: preset === "secondary"
|
|
106
|
+
? theme.colors.primaryForeground
|
|
107
|
+
: getContrastingColor(backgroundColor, palette.white, palette.black);
|
|
108
|
+
const [focused, setFocused] = useState(false);
|
|
109
|
+
const isDisabled = disabled || loading;
|
|
110
|
+
const { animatedStyle: scaleStyle, pressHandlers } = useScalePress({
|
|
111
|
+
disabled: !!isDisabled,
|
|
112
|
+
haptic: false,
|
|
113
|
+
scaleTo: preset === "link" ? 1 : 0.97,
|
|
114
|
+
});
|
|
115
|
+
return (_jsx(TextColorContext.Provider, { value: textColor, children: _jsx(Pressable, { accessibilityRole: "button", accessibilityState: { disabled: !!isDisabled, busy: loading }, ...rest, ...pressHandlers, onFocus: () => setFocused(true), onBlur: () => setFocused(false), style: { alignSelf: fullWidth ? "stretch" : flattenedStyle?.alignSelf ?? "flex-start" }, disabled: isDisabled, children: (state) => (_jsx(Animated.View, { style: scaleStyle, children: _jsxs(View, { style: [
|
|
116
|
+
styles.button,
|
|
117
|
+
preset === "default" && styles.buttonDefault,
|
|
118
|
+
preset === "outline" && styles.buttonOutline,
|
|
119
|
+
preset === "ghost" && styles.buttonGhost,
|
|
120
|
+
preset === "link" && styles.buttonLink,
|
|
121
|
+
preset === "destructive" && styles.buttonDestructive,
|
|
122
|
+
preset === "secondary" && styles.buttonSecondary,
|
|
123
|
+
fullWidth && styles.fullWidth,
|
|
124
|
+
withShadow && !isDisabled && shadowStyle,
|
|
125
|
+
state.pressed && styles.pressed,
|
|
126
|
+
state.pressed && pressedStyleOverride,
|
|
127
|
+
isDisabled && styles.disabled,
|
|
128
|
+
isDisabled && disabledStyleOverride,
|
|
129
|
+
Platform.OS === "web" && focused && !isDisabled && {
|
|
130
|
+
boxShadow: `0 0 0 2px ${theme.colors.background}, 0 0 0 4px ${theme.colors.accent}`,
|
|
131
|
+
},
|
|
132
|
+
// Spread array styles from Slot to prevent nested arrays on web
|
|
133
|
+
...(Array.isArray(styleOverride) ? styleOverride : [styleOverride]),
|
|
134
|
+
], children: [!!LeftAccessory && !loading && (_jsx(LeftAccessory, { style: styles.leftAccessory, pressableState: state, disabled: isDisabled })), loading && (_jsx(ActivityIndicator, { size: "small", color: textColor, style: styles.loader })), (tx || text) ? (_jsx(StyledText, { style: [
|
|
135
|
+
styles.text,
|
|
136
|
+
state.pressed && styles.pressedText,
|
|
137
|
+
state.pressed && pressedTextStyleOverride,
|
|
138
|
+
isDisabled && disabledTextStyleOverride,
|
|
139
|
+
textStyleOverride,
|
|
140
|
+
], children: tx || text })) : !loading && children ? (
|
|
141
|
+
// Wrap string children in StyledText to apply TextColorContext
|
|
142
|
+
typeof children === "string" ? (_jsx(StyledText, { style: [
|
|
143
|
+
styles.text,
|
|
144
|
+
state.pressed && styles.pressedText,
|
|
145
|
+
state.pressed && pressedTextStyleOverride,
|
|
146
|
+
isDisabled && disabledTextStyleOverride,
|
|
147
|
+
textStyleOverride,
|
|
148
|
+
], children: children })) : (children)) : null, !!RightAccessory && !loading && (_jsx(RightAccessory, { style: styles.rightAccessory, pressableState: state, disabled: isDisabled }))] }) })) }) }));
|
|
149
|
+
}
|
|
150
|
+
const createStyles = (theme, size) => {
|
|
151
|
+
const sizeConfig = SIZE_CONFIGS[size];
|
|
152
|
+
return StyleSheet.create({
|
|
153
|
+
button: {
|
|
154
|
+
flexDirection: "row",
|
|
155
|
+
alignItems: "center",
|
|
156
|
+
justifyContent: "center",
|
|
157
|
+
borderRadius: spacing.radiusMd,
|
|
158
|
+
paddingVertical: sizeConfig.paddingVertical,
|
|
159
|
+
paddingHorizontal: sizeConfig.paddingHorizontal,
|
|
160
|
+
minHeight: sizeConfig.height,
|
|
161
|
+
flexShrink: 0,
|
|
162
|
+
...(Platform.OS === "web" && { cursor: "pointer" }),
|
|
163
|
+
},
|
|
164
|
+
buttonDefault: {
|
|
165
|
+
backgroundColor: theme.colors.accent,
|
|
166
|
+
},
|
|
167
|
+
buttonSecondary: {
|
|
168
|
+
backgroundColor: theme.colors.primary,
|
|
169
|
+
},
|
|
170
|
+
buttonDestructive: {
|
|
171
|
+
backgroundColor: theme.colors.destructive,
|
|
172
|
+
},
|
|
173
|
+
buttonOutline: {
|
|
174
|
+
backgroundColor: "transparent",
|
|
175
|
+
borderWidth: 1,
|
|
176
|
+
borderColor: theme.colors.border,
|
|
177
|
+
},
|
|
178
|
+
buttonGhost: {
|
|
179
|
+
backgroundColor: "transparent",
|
|
180
|
+
},
|
|
181
|
+
buttonLink: {
|
|
182
|
+
backgroundColor: "transparent",
|
|
183
|
+
paddingVertical: 0,
|
|
184
|
+
paddingHorizontal: 0,
|
|
185
|
+
},
|
|
186
|
+
fullWidth: {
|
|
187
|
+
width: "100%",
|
|
188
|
+
},
|
|
189
|
+
text: {
|
|
190
|
+
fontFamily: fontFamilies.sansSerif.regular,
|
|
191
|
+
fontSize: sizeConfig.fontSize,
|
|
192
|
+
fontWeight: "500",
|
|
193
|
+
textAlign: "center",
|
|
194
|
+
lineHeight: sizeConfig.fontSize * 1.4,
|
|
195
|
+
flexShrink: 0,
|
|
196
|
+
},
|
|
197
|
+
pressed: {
|
|
198
|
+
opacity: 0.9,
|
|
199
|
+
},
|
|
200
|
+
pressedText: {
|
|
201
|
+
opacity: 0.9,
|
|
202
|
+
},
|
|
203
|
+
disabled: {
|
|
204
|
+
opacity: 0.6,
|
|
205
|
+
},
|
|
206
|
+
leftAccessory: {
|
|
207
|
+
marginRight: spacing.sm,
|
|
208
|
+
},
|
|
209
|
+
rightAccessory: {
|
|
210
|
+
marginLeft: spacing.sm,
|
|
211
|
+
},
|
|
212
|
+
loader: {
|
|
213
|
+
marginRight: spacing.sm,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ViewStyle, TextStyle, StyleProp } from "react-native";
|
|
3
|
+
import { TextProps } from "./StyledText";
|
|
4
|
+
export interface CardProps {
|
|
5
|
+
/** Card contents */
|
|
6
|
+
children?: React.ReactNode;
|
|
7
|
+
/** Custom style override */
|
|
8
|
+
style?: StyleProp<ViewStyle>;
|
|
9
|
+
/** Visual variant */
|
|
10
|
+
variant?: "default" | "outline" | "ghost";
|
|
11
|
+
/** Make card pressable with scale animation */
|
|
12
|
+
onPress?: () => void;
|
|
13
|
+
/** Whether the card is disabled (only relevant when onPress is set) */
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
}
|
|
16
|
+
declare function Card({ children, style: styleOverride, variant, onPress, disabled }: CardProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export interface CardHeaderProps {
|
|
18
|
+
children?: React.ReactNode;
|
|
19
|
+
style?: StyleProp<ViewStyle>;
|
|
20
|
+
}
|
|
21
|
+
declare function CardHeader({ children, style: styleOverride }: CardHeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export interface CardContentProps {
|
|
23
|
+
children?: React.ReactNode;
|
|
24
|
+
style?: StyleProp<ViewStyle>;
|
|
25
|
+
}
|
|
26
|
+
declare function CardContent({ children, style: styleOverride }: CardContentProps): import("react/jsx-runtime").JSX.Element;
|
|
27
|
+
export interface CardFooterProps {
|
|
28
|
+
children?: React.ReactNode;
|
|
29
|
+
style?: StyleProp<ViewStyle>;
|
|
30
|
+
}
|
|
31
|
+
declare function CardFooter({ children, style: styleOverride }: CardFooterProps): import("react/jsx-runtime").JSX.Element;
|
|
32
|
+
export interface CardTitleProps extends Omit<TextProps, "style"> {
|
|
33
|
+
children?: React.ReactNode;
|
|
34
|
+
style?: StyleProp<TextStyle>;
|
|
35
|
+
}
|
|
36
|
+
declare function CardTitle({ children, style: styleOverride, ...props }: CardTitleProps): import("react/jsx-runtime").JSX.Element;
|
|
37
|
+
export interface CardDescriptionProps extends Omit<TextProps, "style"> {
|
|
38
|
+
children?: React.ReactNode;
|
|
39
|
+
style?: StyleProp<TextStyle>;
|
|
40
|
+
}
|
|
41
|
+
declare function CardDescription({ children, style: styleOverride, ...props }: CardDescriptionProps): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
export { Card, CardHeader, CardContent, CardFooter, CardTitle, CardDescription };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
import { View, Pressable, StyleSheet, Platform } from "react-native";
|
|
4
|
+
import Animated from "react-native-reanimated";
|
|
5
|
+
import { StyledText } from "./StyledText";
|
|
6
|
+
import { useTheme } from "../hooks/useTheme";
|
|
7
|
+
import { useScalePress } from "../hooks/useScalePress";
|
|
8
|
+
import { spacing } from "../constants/spacing";
|
|
9
|
+
/**
|
|
10
|
+
* Card Component
|
|
11
|
+
*
|
|
12
|
+
* A themed container component with header, content, and footer sections.
|
|
13
|
+
* Follows shadcn/ui patterns with consistent styling and theme integration.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <Card>
|
|
18
|
+
* <CardHeader>
|
|
19
|
+
* <CardTitle>Card Title</CardTitle>
|
|
20
|
+
* <CardDescription>Card description text</CardDescription>
|
|
21
|
+
* </CardHeader>
|
|
22
|
+
* <CardContent>
|
|
23
|
+
* <Text>Main content goes here</Text>
|
|
24
|
+
* </CardContent>
|
|
25
|
+
* <CardFooter>
|
|
26
|
+
* <Button>Action</Button>
|
|
27
|
+
* </CardFooter>
|
|
28
|
+
* </Card>
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
const CardContext = createContext(null);
|
|
32
|
+
function useCardContext() {
|
|
33
|
+
const ctx = useContext(CardContext);
|
|
34
|
+
if (!ctx) {
|
|
35
|
+
// Fallback for standalone usage without Card parent
|
|
36
|
+
const { theme } = useTheme();
|
|
37
|
+
return { theme, styles: createCardStyles(theme) };
|
|
38
|
+
}
|
|
39
|
+
return ctx;
|
|
40
|
+
}
|
|
41
|
+
function Card({ children, style: styleOverride, variant = "default", onPress, disabled }) {
|
|
42
|
+
const { theme } = useTheme();
|
|
43
|
+
const styles = createCardStyles(theme);
|
|
44
|
+
const ctx = { theme, styles };
|
|
45
|
+
const { animatedStyle: scaleStyle, pressHandlers } = useScalePress({
|
|
46
|
+
disabled: !onPress || !!disabled,
|
|
47
|
+
scaleTo: 0.98,
|
|
48
|
+
haptic: false,
|
|
49
|
+
});
|
|
50
|
+
const cardContent = (_jsx(View, { style: [
|
|
51
|
+
styles.card,
|
|
52
|
+
variant === "default" && styles.cardDefault,
|
|
53
|
+
variant === "outline" && styles.cardOutline,
|
|
54
|
+
variant === "ghost" && styles.cardGhost,
|
|
55
|
+
styleOverride,
|
|
56
|
+
], children: children }));
|
|
57
|
+
if (onPress) {
|
|
58
|
+
return (_jsx(CardContext.Provider, { value: ctx, children: _jsx(Pressable, { onPress: onPress, disabled: disabled, accessibilityRole: "button", accessibilityState: { disabled: !!disabled }, ...pressHandlers, style: Platform.OS === "web" ? { cursor: "pointer" } : undefined, children: _jsx(Animated.View, { style: scaleStyle, children: cardContent }) }) }));
|
|
59
|
+
}
|
|
60
|
+
return (_jsx(CardContext.Provider, { value: ctx, children: cardContent }));
|
|
61
|
+
}
|
|
62
|
+
function CardHeader({ children, style: styleOverride }) {
|
|
63
|
+
const { styles } = useCardContext();
|
|
64
|
+
return (_jsx(View, { style: [styles.header, styleOverride], children: children }));
|
|
65
|
+
}
|
|
66
|
+
function CardContent({ children, style: styleOverride }) {
|
|
67
|
+
const { styles } = useCardContext();
|
|
68
|
+
return (_jsx(View, { style: [styles.content, styleOverride], children: children }));
|
|
69
|
+
}
|
|
70
|
+
function CardFooter({ children, style: styleOverride }) {
|
|
71
|
+
const { styles } = useCardContext();
|
|
72
|
+
return (_jsx(View, { style: [styles.footer, styleOverride], children: children }));
|
|
73
|
+
}
|
|
74
|
+
function CardTitle({ children, style: styleOverride, ...props }) {
|
|
75
|
+
const { theme, styles } = useCardContext();
|
|
76
|
+
return (_jsx(StyledText, { fontWeight: "semibold", ...props, style: [styles.title, { color: theme.colors.text }, styleOverride], children: children }));
|
|
77
|
+
}
|
|
78
|
+
function CardDescription({ children, style: styleOverride, ...props }) {
|
|
79
|
+
const { theme, styles } = useCardContext();
|
|
80
|
+
return (_jsx(StyledText, { ...props, style: [styles.description, { color: theme.colors.textDim }, styleOverride], children: children }));
|
|
81
|
+
}
|
|
82
|
+
const createCardStyles = (theme) => StyleSheet.create({
|
|
83
|
+
card: {
|
|
84
|
+
borderRadius: spacing.radiusLg,
|
|
85
|
+
overflow: "hidden",
|
|
86
|
+
},
|
|
87
|
+
cardDefault: {
|
|
88
|
+
backgroundColor: theme.colors.card,
|
|
89
|
+
borderWidth: 1,
|
|
90
|
+
borderColor: theme.colors.border,
|
|
91
|
+
},
|
|
92
|
+
cardOutline: {
|
|
93
|
+
backgroundColor: "transparent",
|
|
94
|
+
borderWidth: 1,
|
|
95
|
+
borderColor: theme.colors.border,
|
|
96
|
+
},
|
|
97
|
+
cardGhost: {
|
|
98
|
+
backgroundColor: "transparent",
|
|
99
|
+
},
|
|
100
|
+
header: {
|
|
101
|
+
padding: spacing.lg,
|
|
102
|
+
paddingBottom: spacing.xs,
|
|
103
|
+
gap: spacing.xs,
|
|
104
|
+
},
|
|
105
|
+
content: {
|
|
106
|
+
paddingHorizontal: spacing.lg,
|
|
107
|
+
paddingBottom: spacing.lg,
|
|
108
|
+
},
|
|
109
|
+
footer: {
|
|
110
|
+
flexDirection: "row",
|
|
111
|
+
alignItems: "center",
|
|
112
|
+
paddingHorizontal: spacing.lg,
|
|
113
|
+
paddingBottom: spacing.lg,
|
|
114
|
+
paddingTop: 0,
|
|
115
|
+
},
|
|
116
|
+
title: {
|
|
117
|
+
fontSize: 18,
|
|
118
|
+
lineHeight: 24,
|
|
119
|
+
letterSpacing: -0.3,
|
|
120
|
+
},
|
|
121
|
+
description: {
|
|
122
|
+
fontSize: 14,
|
|
123
|
+
lineHeight: 20,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
export { Card, CardHeader, CardContent, CardFooter, CardTitle, CardDescription };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { StyleProp, ViewStyle } from "react-native";
|
|
2
|
+
import * as CheckboxPrimitive from "@rn-primitives/checkbox";
|
|
3
|
+
/**
|
|
4
|
+
* Size variants for Checkbox
|
|
5
|
+
*/
|
|
6
|
+
export type CheckboxSize = "sm" | "md" | "lg";
|
|
7
|
+
export interface CheckboxProps extends Omit<CheckboxPrimitive.RootProps, "style"> {
|
|
8
|
+
/**
|
|
9
|
+
* Size variant
|
|
10
|
+
* @default "md"
|
|
11
|
+
*/
|
|
12
|
+
size?: CheckboxSize;
|
|
13
|
+
/**
|
|
14
|
+
* Optional label text displayed next to the checkbox
|
|
15
|
+
*/
|
|
16
|
+
label?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Whether the checkbox is in an indeterminate state (dash icon)
|
|
19
|
+
*/
|
|
20
|
+
indeterminate?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Whether the checkbox is in an error state
|
|
23
|
+
*/
|
|
24
|
+
error?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Custom style override (uses StyleSheet.flatten for web compatibility)
|
|
27
|
+
*/
|
|
28
|
+
style?: StyleProp<ViewStyle>;
|
|
29
|
+
/**
|
|
30
|
+
* Style for the label text
|
|
31
|
+
*/
|
|
32
|
+
labelStyle?: StyleProp<ViewStyle>;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the field is required (shows asterisk in label)
|
|
35
|
+
*/
|
|
36
|
+
required?: boolean;
|
|
37
|
+
}
|
|
38
|
+
declare function Checkbox({ size, label, indeterminate, error, style: styleOverride, labelStyle, required, checked, onCheckedChange, disabled, ...props }: CheckboxProps): import("react/jsx-runtime").JSX.Element;
|
|
39
|
+
export { Checkbox };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { View, StyleSheet, Pressable } from "react-native";
|
|
3
|
+
import Animated, { useSharedValue, useAnimatedStyle, withTiming, useReducedMotion, } from "react-native-reanimated";
|
|
4
|
+
import { Icon } from "./Icon";
|
|
5
|
+
import { StyledText } from "./StyledText";
|
|
6
|
+
import { useTheme } from "../hooks/useTheme";
|
|
7
|
+
import { spacing } from "../constants/spacing";
|
|
8
|
+
import { hapticLight } from "../lib/haptics";
|
|
9
|
+
import * as CheckboxPrimitive from "@rn-primitives/checkbox";
|
|
10
|
+
const DEFAULT_HIT_SLOP = 8;
|
|
11
|
+
const SIZE_CONFIGS = {
|
|
12
|
+
sm: { size: 16, iconSize: 12, strokeWidth: 2.5 },
|
|
13
|
+
md: { size: 20, iconSize: 16, strokeWidth: 3 },
|
|
14
|
+
lg: { size: 24, iconSize: 20, strokeWidth: 3.5 },
|
|
15
|
+
};
|
|
16
|
+
function Checkbox({ size = "md", label, indeterminate = false, error = false, style: styleOverride, labelStyle, required = false, checked, onCheckedChange, disabled, ...props }) {
|
|
17
|
+
const { theme, getContrastingColor } = useTheme();
|
|
18
|
+
const reduceMotion = useReducedMotion();
|
|
19
|
+
const sizeConfig = SIZE_CONFIGS[size];
|
|
20
|
+
// Simple fast opacity for the checkmark icon
|
|
21
|
+
const checkOpacity = useSharedValue(checked || indeterminate ? 1 : 0);
|
|
22
|
+
const wrappedOnCheckedChange = (next) => {
|
|
23
|
+
if (next)
|
|
24
|
+
hapticLight();
|
|
25
|
+
if (reduceMotion) {
|
|
26
|
+
checkOpacity.value = withTiming(next ? 1 : 0, { duration: 0 });
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
checkOpacity.value = withTiming(next ? 1 : 0, { duration: 60 });
|
|
30
|
+
}
|
|
31
|
+
onCheckedChange?.(next);
|
|
32
|
+
};
|
|
33
|
+
const checkAnimatedStyle = useAnimatedStyle(() => ({
|
|
34
|
+
opacity: checkOpacity.value,
|
|
35
|
+
}));
|
|
36
|
+
// Dynamic border color with sufficient contrast against background
|
|
37
|
+
const borderColor = error
|
|
38
|
+
? theme.colors.destructive
|
|
39
|
+
: checked || indeterminate
|
|
40
|
+
? theme.colors.primary
|
|
41
|
+
: getContrastingColor(theme.colors.background, theme.colors.text, theme.colors.textDim);
|
|
42
|
+
// Flatten style override for web compatibility
|
|
43
|
+
const flattenedStyle = styleOverride ? StyleSheet.flatten(styleOverride) : undefined;
|
|
44
|
+
const checkboxElement = (_jsx(CheckboxPrimitive.Root, { ...props, checked: checked, onCheckedChange: wrappedOnCheckedChange, disabled: disabled, style: {
|
|
45
|
+
borderColor,
|
|
46
|
+
backgroundColor: checked || indeterminate ? theme.colors.primary : "transparent",
|
|
47
|
+
borderRadius: spacing.radiusSm,
|
|
48
|
+
borderWidth: 1,
|
|
49
|
+
width: sizeConfig.size,
|
|
50
|
+
height: sizeConfig.size,
|
|
51
|
+
justifyContent: "center",
|
|
52
|
+
alignItems: "center",
|
|
53
|
+
opacity: disabled ? 0.5 : 1,
|
|
54
|
+
...(flattenedStyle || {}),
|
|
55
|
+
}, hitSlop: DEFAULT_HIT_SLOP, accessibilityRole: "checkbox", accessibilityState: {
|
|
56
|
+
checked: indeterminate ? "mixed" : checked,
|
|
57
|
+
disabled: !!disabled,
|
|
58
|
+
}, accessibilityLabel: label, children: _jsx(CheckboxPrimitive.Indicator, { style: {
|
|
59
|
+
justifyContent: "center",
|
|
60
|
+
alignItems: "center",
|
|
61
|
+
}, children: _jsx(Animated.View, { style: checkAnimatedStyle, children: indeterminate ? (_jsx(Icon, { name: "minus", size: sizeConfig.iconSize, color: theme.colors.primaryForeground })) : (_jsx(Icon, { name: "check", size: sizeConfig.iconSize, color: theme.colors.primaryForeground })) }) }) }));
|
|
62
|
+
// If no label, return just the checkbox
|
|
63
|
+
if (!label) {
|
|
64
|
+
return checkboxElement;
|
|
65
|
+
}
|
|
66
|
+
// With label, wrap in a container
|
|
67
|
+
return (_jsxs(Pressable, { onPress: () => !disabled && wrappedOnCheckedChange(!checked), style: [styles.container, labelStyle], disabled: disabled, accessibilityRole: "checkbox", accessibilityState: {
|
|
68
|
+
checked: indeterminate ? "mixed" : checked,
|
|
69
|
+
disabled: !!disabled,
|
|
70
|
+
}, accessibilityLabel: label, children: [checkboxElement, _jsx(View, { style: styles.labelContainer, children: _jsxs(StyledText, { style: [
|
|
71
|
+
styles.label,
|
|
72
|
+
{ color: theme.colors.text },
|
|
73
|
+
disabled && styles.disabledLabel,
|
|
74
|
+
error && { color: theme.colors.destructive },
|
|
75
|
+
], children: [label, required && (_jsx(StyledText, { style: [styles.required, { color: theme.colors.destructive }], children: " *" }))] }) })] }));
|
|
76
|
+
}
|
|
77
|
+
const styles = StyleSheet.create({
|
|
78
|
+
container: {
|
|
79
|
+
flexDirection: "row",
|
|
80
|
+
alignItems: "center",
|
|
81
|
+
gap: spacing.sm,
|
|
82
|
+
},
|
|
83
|
+
labelContainer: {
|
|
84
|
+
flex: 1,
|
|
85
|
+
},
|
|
86
|
+
label: {
|
|
87
|
+
fontSize: 14,
|
|
88
|
+
},
|
|
89
|
+
disabledLabel: {
|
|
90
|
+
opacity: 0.5,
|
|
91
|
+
},
|
|
92
|
+
required: {
|
|
93
|
+
fontWeight: "bold",
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
export { Checkbox };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as CollapsiblePrimitive from "@rn-primitives/collapsible";
|
|
2
|
+
/**
|
|
3
|
+
* Collapsible Component (Root)
|
|
4
|
+
* A wrapper component that provides expand/collapse functionality with smooth animations
|
|
5
|
+
* Using @rn-primitives/collapsible with DaisyUI theme integration
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```tsx
|
|
9
|
+
* const [open, setOpen] = useState(false);
|
|
10
|
+
* <Collapsible open={open} onOpenChange={setOpen}>
|
|
11
|
+
* <CollapsibleTrigger>
|
|
12
|
+
* <SansSerifText>Toggle</SansSerifText>
|
|
13
|
+
* </CollapsibleTrigger>
|
|
14
|
+
* <CollapsibleContent>
|
|
15
|
+
* <SansSerifText>Content that expands and collapses</SansSerifText>
|
|
16
|
+
* </CollapsibleContent>
|
|
17
|
+
* </Collapsible>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
type CollapsibleProps = CollapsiblePrimitive.RootProps;
|
|
21
|
+
declare function Collapsible({ children, ...props }: CollapsibleProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
/**
|
|
23
|
+
* CollapsibleTrigger Component
|
|
24
|
+
* The trigger button/element that toggles the collapsible state
|
|
25
|
+
* Supports asChild pattern for custom trigger components
|
|
26
|
+
*
|
|
27
|
+
* Usage:
|
|
28
|
+
* ```tsx
|
|
29
|
+
* // Simple trigger
|
|
30
|
+
* <CollapsibleTrigger>
|
|
31
|
+
* <SansSerifText>Click to expand</SansSerifText>
|
|
32
|
+
* </CollapsibleTrigger>
|
|
33
|
+
*
|
|
34
|
+
* // With asChild (using Button)
|
|
35
|
+
* <CollapsibleTrigger asChild>
|
|
36
|
+
* <Button preset="outline">
|
|
37
|
+
* <SansSerifText>Toggle</SansSerifText>
|
|
38
|
+
* </Button>
|
|
39
|
+
* </CollapsibleTrigger>
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
type CollapsibleTriggerProps = CollapsiblePrimitive.TriggerProps;
|
|
43
|
+
declare function CollapsibleTrigger({ style: styleOverride, ...props }: CollapsibleTriggerProps): import("react/jsx-runtime").JSX.Element;
|
|
44
|
+
/**
|
|
45
|
+
* CollapsibleContent Component
|
|
46
|
+
* The content that expands and collapses
|
|
47
|
+
* Includes exit animation on native platforms
|
|
48
|
+
*
|
|
49
|
+
* Usage:
|
|
50
|
+
* ```tsx
|
|
51
|
+
* <CollapsibleContent>
|
|
52
|
+
* <View>
|
|
53
|
+
* <SansSerifText>This content will animate in and out</SansSerifText>
|
|
54
|
+
* </View>
|
|
55
|
+
* </CollapsibleContent>
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
type CollapsibleContentProps = CollapsiblePrimitive.ContentProps & {
|
|
59
|
+
/**
|
|
60
|
+
* Whether to force remove the content from the tree when closed
|
|
61
|
+
* Default: false (content remains in tree but hidden)
|
|
62
|
+
*/
|
|
63
|
+
forceMount?: boolean;
|
|
64
|
+
};
|
|
65
|
+
declare function CollapsibleContent({ forceMount, style: styleOverride, children, ...props }: CollapsibleContentProps): import("react/jsx-runtime").JSX.Element;
|
|
66
|
+
export { Collapsible, CollapsibleContent, CollapsibleTrigger };
|
|
67
|
+
export type { CollapsibleContentProps, CollapsibleProps, CollapsibleTriggerProps };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { Animated, Platform, StyleSheet, View } from "react-native";
|
|
4
|
+
import { TextClassContext } from "./StyledText";
|
|
5
|
+
import { spacing } from "../constants/spacing";
|
|
6
|
+
import { useTheme } from "../hooks/useTheme";
|
|
7
|
+
import * as CollapsiblePrimitive from "@rn-primitives/collapsible";
|
|
8
|
+
function Collapsible({ children, ...props }) {
|
|
9
|
+
return (_jsx(CollapsiblePrimitive.Root, { ...props, asChild: Platform.OS !== "web", children: _jsx(View, { children: children }) }));
|
|
10
|
+
}
|
|
11
|
+
function CollapsibleTrigger({ style: styleOverride, ...props }) {
|
|
12
|
+
const { theme } = useTheme();
|
|
13
|
+
return (_jsx(TextClassContext.Provider, { value: "", children: _jsx(CollapsiblePrimitive.Trigger, { ...props, style: {
|
|
14
|
+
flexDirection: "row",
|
|
15
|
+
alignItems: "center",
|
|
16
|
+
justifyContent: "space-between",
|
|
17
|
+
paddingVertical: spacing.sm,
|
|
18
|
+
...(Platform.OS === "web" && {
|
|
19
|
+
cursor: "pointer",
|
|
20
|
+
outlineStyle: "none",
|
|
21
|
+
}),
|
|
22
|
+
...(styleOverride && typeof styleOverride !== "function"
|
|
23
|
+
? StyleSheet.flatten(styleOverride)
|
|
24
|
+
: {}),
|
|
25
|
+
} }) }));
|
|
26
|
+
}
|
|
27
|
+
function CollapsibleContent({ forceMount, style: styleOverride, children, ...props }) {
|
|
28
|
+
const { theme } = useTheme();
|
|
29
|
+
const fadeAnim = React.useRef(new Animated.Value(1)).current;
|
|
30
|
+
return (_jsx(TextClassContext.Provider, { value: "", children: _jsx(CollapsiblePrimitive.Content, { ...props, forceMount: forceMount, children: _jsx(Animated.View, { style: {
|
|
31
|
+
overflow: "hidden",
|
|
32
|
+
opacity: fadeAnim,
|
|
33
|
+
...(styleOverride && typeof styleOverride !== "function"
|
|
34
|
+
? StyleSheet.flatten(styleOverride)
|
|
35
|
+
: {}),
|
|
36
|
+
}, children: children }) }) }));
|
|
37
|
+
}
|
|
38
|
+
export { Collapsible, CollapsibleContent, CollapsibleTrigger };
|