@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.
Files changed (112) hide show
  1. package/README.md +96 -0
  2. package/dist/components/Accordion.d.ts +54 -0
  3. package/dist/components/Accordion.js +149 -0
  4. package/dist/components/Alert.d.ts +30 -0
  5. package/dist/components/Alert.js +25 -0
  6. package/dist/components/AnimatedView.d.ts +55 -0
  7. package/dist/components/AnimatedView.js +39 -0
  8. package/dist/components/Badge.d.ts +23 -0
  9. package/dist/components/Badge.js +74 -0
  10. package/dist/components/BottomSheet.d.ts +74 -0
  11. package/dist/components/BottomSheet.js +513 -0
  12. package/dist/components/Button.d.ts +129 -0
  13. package/dist/components/Button.js +216 -0
  14. package/dist/components/Card.d.ts +42 -0
  15. package/dist/components/Card.js +126 -0
  16. package/dist/components/Checkbox.d.ts +39 -0
  17. package/dist/components/Checkbox.js +96 -0
  18. package/dist/components/Collapsible.d.ts +67 -0
  19. package/dist/components/Collapsible.js +38 -0
  20. package/dist/components/Dialog.d.ts +140 -0
  21. package/dist/components/Dialog.js +167 -0
  22. package/dist/components/DismissKeyboard.d.ts +15 -0
  23. package/dist/components/DismissKeyboard.js +13 -0
  24. package/dist/components/Drawer.d.ts +74 -0
  25. package/dist/components/Drawer.js +423 -0
  26. package/dist/components/DropdownMenu.d.ts +120 -0
  27. package/dist/components/DropdownMenu.js +211 -0
  28. package/dist/components/EmptyState.d.ts +42 -0
  29. package/dist/components/EmptyState.js +58 -0
  30. package/dist/components/ErrorBoundary.d.ts +53 -0
  31. package/dist/components/ErrorBoundary.js +75 -0
  32. package/dist/components/Icon.d.ts +46 -0
  33. package/dist/components/Icon.js +40 -0
  34. package/dist/components/InputOTP.d.ts +72 -0
  35. package/dist/components/InputOTP.js +155 -0
  36. package/dist/components/Label.d.ts +61 -0
  37. package/dist/components/Label.js +72 -0
  38. package/dist/components/MaxWidthContainer.d.ts +58 -0
  39. package/dist/components/MaxWidthContainer.js +64 -0
  40. package/dist/components/Notification.d.ts +26 -0
  41. package/dist/components/Notification.js +230 -0
  42. package/dist/components/Popover.d.ts +79 -0
  43. package/dist/components/Popover.js +91 -0
  44. package/dist/components/Progress.d.ts +28 -0
  45. package/dist/components/Progress.js +107 -0
  46. package/dist/components/RadioGroup.d.ts +65 -0
  47. package/dist/components/RadioGroup.js +142 -0
  48. package/dist/components/Select.d.ts +88 -0
  49. package/dist/components/Select.js +172 -0
  50. package/dist/components/Separator.d.ts +83 -0
  51. package/dist/components/Separator.js +85 -0
  52. package/dist/components/Skeleton.d.ts +68 -0
  53. package/dist/components/Skeleton.js +99 -0
  54. package/dist/components/Slider.d.ts +24 -0
  55. package/dist/components/Slider.js +162 -0
  56. package/dist/components/StatusBar.d.ts +1 -0
  57. package/dist/components/StatusBar.js +19 -0
  58. package/dist/components/StyledText.d.ts +161 -0
  59. package/dist/components/StyledText.js +193 -0
  60. package/dist/components/Switch.d.ts +44 -0
  61. package/dist/components/Switch.js +129 -0
  62. package/dist/components/Tabs.d.ts +31 -0
  63. package/dist/components/Tabs.js +127 -0
  64. package/dist/components/TextInput.d.ts +120 -0
  65. package/dist/components/TextInput.js +263 -0
  66. package/dist/components/Toggle.d.ts +106 -0
  67. package/dist/components/Toggle.js +150 -0
  68. package/dist/components/ToggleGroup.d.ts +80 -0
  69. package/dist/components/ToggleGroup.js +189 -0
  70. package/dist/components/Tooltip.d.ts +121 -0
  71. package/dist/components/Tooltip.js +132 -0
  72. package/dist/components/index.d.ts +35 -0
  73. package/dist/components/index.js +35 -0
  74. package/dist/constants/colors.d.ts +82 -0
  75. package/dist/constants/colors.js +116 -0
  76. package/dist/constants/fonts.d.ts +32 -0
  77. package/dist/constants/fonts.js +91 -0
  78. package/dist/constants/index.d.ts +3 -0
  79. package/dist/constants/index.js +3 -0
  80. package/dist/constants/spacing.d.ts +40 -0
  81. package/dist/constants/spacing.js +48 -0
  82. package/dist/hooks/index.d.ts +6 -0
  83. package/dist/hooks/index.js +6 -0
  84. package/dist/hooks/useDimensions.d.ts +19 -0
  85. package/dist/hooks/useDimensions.js +55 -0
  86. package/dist/hooks/useReduceMotion.d.ts +5 -0
  87. package/dist/hooks/useReduceMotion.js +64 -0
  88. package/dist/hooks/useResources.d.ts +12 -0
  89. package/dist/hooks/useResources.js +56 -0
  90. package/dist/hooks/useScalePress.d.ts +57 -0
  91. package/dist/hooks/useScalePress.js +55 -0
  92. package/dist/hooks/useStaggeredEntrance.d.ts +67 -0
  93. package/dist/hooks/useStaggeredEntrance.js +74 -0
  94. package/dist/hooks/useTheme.d.ts +88 -0
  95. package/dist/hooks/useTheme.js +328 -0
  96. package/dist/index.d.ts +5 -0
  97. package/dist/index.js +5 -0
  98. package/dist/lib/animations.d.ts +1 -0
  99. package/dist/lib/animations.js +3 -0
  100. package/dist/lib/haptics.d.ts +3 -0
  101. package/dist/lib/haptics.js +29 -0
  102. package/dist/lib/index.d.ts +3 -0
  103. package/dist/lib/index.js +3 -0
  104. package/dist/lib/sentry.d.ts +16 -0
  105. package/dist/lib/sentry.js +55 -0
  106. package/dist/state/globalUIStore.d.ts +30 -0
  107. package/dist/state/globalUIStore.js +8 -0
  108. package/dist/state/index.d.ts +2 -0
  109. package/dist/state/index.js +2 -0
  110. package/dist/state/themeStore.d.ts +6 -0
  111. package/dist/state/themeStore.js +38 -0
  112. 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 };