@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,79 @@
1
+ import * as PopoverPrimitive from "@rn-primitives/popover";
2
+ import * as React from "react";
3
+ import { View, ViewProps } from "react-native";
4
+ /**
5
+ * Popover Trigger Component
6
+ * The element that triggers the popover to open/close
7
+ */
8
+ declare const PopoverTrigger: {
9
+ ({ asChild, onPress: onPressProp, disabled, ref, ...props }: Omit<import("react-native").PressableProps & React.RefAttributes<View>, "ref"> & {
10
+ asChild?: boolean;
11
+ } & {
12
+ onKeyDown?: (ev: React.KeyboardEvent) => void;
13
+ onKeyUp?: (ev: React.KeyboardEvent) => void;
14
+ } & React.RefAttributes<import("@rn-primitives/popover").TriggerRef>): React.JSX.Element;
15
+ displayName: string;
16
+ };
17
+ interface PopoverContentProps extends PopoverPrimitive.ContentProps {
18
+ /**
19
+ * Optional portal host name for custom portal mounting
20
+ */
21
+ portalHost?: string;
22
+ }
23
+ /**
24
+ * Popover Content Component
25
+ * The content that appears in the popover overlay
26
+ * Supports smart positioning, animations, and theme integration
27
+ */
28
+ declare function PopoverContent({ side, align, sideOffset, portalHost, ...props }: PopoverContentProps): import("react/jsx-runtime").JSX.Element;
29
+ /**
30
+ * Popover Header Component
31
+ * Optional header section for the popover content
32
+ */
33
+ interface PopoverHeaderProps extends ViewProps {
34
+ children: React.ReactNode;
35
+ }
36
+ declare function PopoverHeader({ children, style, ...props }: PopoverHeaderProps): import("react/jsx-runtime").JSX.Element;
37
+ /**
38
+ * Popover Body Component
39
+ * Main content area of the popover
40
+ */
41
+ interface PopoverBodyProps extends ViewProps {
42
+ children: React.ReactNode;
43
+ }
44
+ declare function PopoverBody({ children, style, ...props }: PopoverBodyProps): import("react/jsx-runtime").JSX.Element;
45
+ /**
46
+ * Popover Footer Component
47
+ * Optional footer section for the popover content
48
+ */
49
+ interface PopoverFooterProps extends ViewProps {
50
+ children: React.ReactNode;
51
+ }
52
+ declare function PopoverFooter({ children, style, ...props }: PopoverFooterProps): import("react/jsx-runtime").JSX.Element;
53
+ /**
54
+ * Popover Component with Sub-components
55
+ * Properly typed interface for dot notation access (e.g., Popover.Trigger)
56
+ */
57
+ declare const Popover: {
58
+ ({ asChild, onOpenChange: onOpenChangeProp, ref, ...viewProps }: ViewProps & {
59
+ asChild?: boolean;
60
+ } & {
61
+ onOpenChange?: (open: boolean) => void;
62
+ } & React.RefAttributes<View>): React.JSX.Element;
63
+ displayName: string;
64
+ } & {
65
+ Trigger: {
66
+ ({ asChild, onPress: onPressProp, disabled, ref, ...props }: Omit<import("react-native").PressableProps & React.RefAttributes<View>, "ref"> & {
67
+ asChild?: boolean;
68
+ } & {
69
+ onKeyDown?: (ev: React.KeyboardEvent) => void;
70
+ onKeyUp?: (ev: React.KeyboardEvent) => void;
71
+ } & React.RefAttributes<import("@rn-primitives/popover").TriggerRef>): React.JSX.Element;
72
+ displayName: string;
73
+ };
74
+ Content: typeof PopoverContent;
75
+ Header: typeof PopoverHeader;
76
+ Body: typeof PopoverBody;
77
+ Footer: typeof PopoverFooter;
78
+ };
79
+ export { Popover, PopoverTrigger, PopoverContent, PopoverHeader, PopoverBody, PopoverFooter, };
@@ -0,0 +1,91 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { AnimatedView } from "./AnimatedView";
3
+ import { TextClassContext, TextColorContext } from "./StyledText";
4
+ import { useTheme } from "../hooks/useTheme";
5
+ import { spacing } from "../constants/spacing";
6
+ import * as PopoverPrimitive from "@rn-primitives/popover";
7
+ import * as React from "react";
8
+ import { Platform, StyleSheet, View } from "react-native";
9
+ import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens";
10
+ import { palette } from "../constants/colors";
11
+ /**
12
+ * Popover Trigger Component
13
+ * The element that triggers the popover to open/close
14
+ */
15
+ const PopoverTrigger = PopoverPrimitive.Trigger;
16
+ /**
17
+ * FullWindowOverlay wrapper - uses native overlay on iOS for proper z-index handling
18
+ * On Android/Web, uses Fragment to avoid unnecessary nesting
19
+ */
20
+ const FullWindowOverlay = Platform.OS === "ios" ? RNFullWindowOverlay : React.Fragment;
21
+ /**
22
+ * Popover Content Component
23
+ * The content that appears in the popover overlay
24
+ * Supports smart positioning, animations, and theme integration
25
+ */
26
+ function PopoverContent({ side, align = "center", sideOffset = 4, portalHost, ...props }) {
27
+ const { theme, getShadowStyle, getContrastingColor } = useTheme();
28
+ // Calculate text color for popover content based on background
29
+ const textColor = getContrastingColor(theme.colors.background, palette.white, palette.black);
30
+ const contentStyle = StyleSheet.flatten([
31
+ {
32
+ backgroundColor: theme.colors.background,
33
+ borderColor: theme.colors.border,
34
+ borderWidth: 1,
35
+ borderRadius: spacing.radiusMd,
36
+ padding: spacing.xs,
37
+ ...getShadowStyle("soft"),
38
+ },
39
+ ]);
40
+ return (_jsx(PopoverPrimitive.Portal, { hostName: portalHost, children: _jsx(FullWindowOverlay, { children: _jsx(PopoverPrimitive.Overlay, { style: Platform.select({ native: StyleSheet.absoluteFill }), children: _jsx(AnimatedView, { type: "fade", enterDuration: 200, children: _jsx(TextColorContext.Provider, { value: textColor, children: _jsx(TextClassContext.Provider, { value: "", children: _jsx(PopoverPrimitive.Content, { side: side, align: align, sideOffset: sideOffset, style: contentStyle, ...props }) }) }) }) }) }) }));
41
+ }
42
+ function PopoverHeader({ children, style, ...props }) {
43
+ const { theme } = useTheme();
44
+ return (_jsx(View, { style: [
45
+ {
46
+ paddingHorizontal: spacing.xs,
47
+ paddingVertical: spacing.sm,
48
+ borderBottomWidth: 1,
49
+ borderBottomColor: theme.colors.border,
50
+ },
51
+ style,
52
+ ], ...props, children: children }));
53
+ }
54
+ function PopoverBody({ children, style, ...props }) {
55
+ return (_jsx(View, { style: [
56
+ {
57
+ paddingHorizontal: spacing.xs,
58
+ paddingVertical: spacing.sm,
59
+ },
60
+ style,
61
+ ], ...props, children: children }));
62
+ }
63
+ function PopoverFooter({ children, style, ...props }) {
64
+ const { theme } = useTheme();
65
+ return (_jsx(View, { style: [
66
+ {
67
+ paddingHorizontal: spacing.xs,
68
+ paddingVertical: spacing.sm,
69
+ borderTopWidth: 1,
70
+ borderTopColor: theme.colors.border,
71
+ },
72
+ style,
73
+ ], ...props, children: children }));
74
+ }
75
+ /**
76
+ * Popover Root Component
77
+ * Manages popover state and provides context for trigger and content
78
+ */
79
+ const PopoverRoot = PopoverPrimitive.Root;
80
+ /**
81
+ * Popover Component with Sub-components
82
+ * Properly typed interface for dot notation access (e.g., Popover.Trigger)
83
+ */
84
+ const Popover = Object.assign(PopoverRoot, {
85
+ Trigger: PopoverTrigger,
86
+ Content: PopoverContent,
87
+ Header: PopoverHeader,
88
+ Body: PopoverBody,
89
+ Footer: PopoverFooter,
90
+ });
91
+ export { Popover, PopoverTrigger, PopoverContent, PopoverHeader, PopoverBody, PopoverFooter, };
@@ -0,0 +1,28 @@
1
+ import { StyleProp, ViewStyle } from "react-native";
2
+ export type ProgressVariant = "default" | "accent" | "destructive";
3
+ export type ProgressSize = "sm" | "md" | "lg";
4
+ export interface ProgressProps {
5
+ /** Progress value 0-100. Omit for indeterminate mode. */
6
+ value?: number;
7
+ /** Color variant for the fill bar */
8
+ variant?: ProgressVariant;
9
+ /** Height size preset */
10
+ size?: ProgressSize;
11
+ /** Custom style override */
12
+ style?: StyleProp<ViewStyle>;
13
+ }
14
+ /**
15
+ * Progress Component
16
+ *
17
+ * A linear progress bar supporting determinate and indeterminate modes.
18
+ * Determinate mode animates the fill width via Reanimated.
19
+ * Indeterminate mode pulses opacity via RN core Animated.loop.
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * <Progress value={60} />
24
+ * <Progress variant="accent" size="lg" value={30} />
25
+ * <Progress /> // indeterminate
26
+ * ```
27
+ */
28
+ export declare function Progress({ value, variant, size, style, }: ProgressProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,107 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { Animated, StyleSheet, View } from "react-native";
4
+ import Reanimated, { useSharedValue, useAnimatedStyle, withTiming, useReducedMotion, } from "react-native-reanimated";
5
+ import { useTheme } from "../hooks/useTheme";
6
+ import { shouldUseNativeDriver } from "../lib/animations";
7
+ // ============================================================================
8
+ // Constants
9
+ // ============================================================================
10
+ const SIZE_MAP = {
11
+ sm: 4,
12
+ md: 8,
13
+ lg: 12,
14
+ };
15
+ // ============================================================================
16
+ // Progress
17
+ // ============================================================================
18
+ /**
19
+ * Progress Component
20
+ *
21
+ * A linear progress bar supporting determinate and indeterminate modes.
22
+ * Determinate mode animates the fill width via Reanimated.
23
+ * Indeterminate mode pulses opacity via RN core Animated.loop.
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * <Progress value={60} />
28
+ * <Progress variant="accent" size="lg" value={30} />
29
+ * <Progress /> // indeterminate
30
+ * ```
31
+ */
32
+ export function Progress({ value, variant = "default", size = "md", style, }) {
33
+ const { theme } = useTheme();
34
+ const reduceMotion = useReducedMotion();
35
+ const isDeterminate = value !== undefined;
36
+ const height = SIZE_MAP[size];
37
+ const borderRadius = height / 2;
38
+ const fillColor = variant === "accent"
39
+ ? theme.colors.accent
40
+ : variant === "destructive"
41
+ ? theme.colors.destructive
42
+ : theme.colors.primary;
43
+ const flattenedStyle = style ? StyleSheet.flatten(style) : undefined;
44
+ return (_jsx(View, { accessible: true, accessibilityRole: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": isDeterminate ? Math.round(Math.min(100, Math.max(0, value))) : undefined, accessibilityState: { busy: !isDeterminate }, style: StyleSheet.flatten([
45
+ {
46
+ height,
47
+ borderRadius,
48
+ backgroundColor: theme.colors.muted,
49
+ overflow: "hidden",
50
+ },
51
+ flattenedStyle,
52
+ ]), children: isDeterminate ? (_jsx(DeterminateFill, { value: value, fillColor: fillColor, height: height, borderRadius: borderRadius, reduceMotion: reduceMotion })) : (_jsx(IndeterminateFill, { fillColor: fillColor, height: height, borderRadius: borderRadius, reduceMotion: reduceMotion })) }));
53
+ }
54
+ function DeterminateFill({ value, fillColor, height, borderRadius, reduceMotion, }) {
55
+ const [containerWidth, setContainerWidth] = useState(0);
56
+ const clamped = Math.min(100, Math.max(0, value));
57
+ const animatedWidth = useSharedValue(0);
58
+ useEffect(() => {
59
+ if (containerWidth === 0)
60
+ return;
61
+ const target = (clamped / 100) * containerWidth;
62
+ animatedWidth.value = withTiming(target, {
63
+ duration: reduceMotion ? 0 : 300,
64
+ });
65
+ }, [clamped, containerWidth, reduceMotion]);
66
+ const animatedStyle = useAnimatedStyle(() => ({
67
+ width: animatedWidth.value,
68
+ }));
69
+ return (_jsx(View, { style: { flex: 1 }, onLayout: (e) => setContainerWidth(e.nativeEvent.layout.width), children: _jsx(Reanimated.View, { style: [
70
+ {
71
+ height,
72
+ borderRadius,
73
+ backgroundColor: fillColor,
74
+ },
75
+ animatedStyle,
76
+ ] }) }));
77
+ }
78
+ function IndeterminateFill({ fillColor, height, borderRadius, reduceMotion, }) {
79
+ const opacity = useRef(new Animated.Value(reduceMotion ? 0.7 : 0.4)).current;
80
+ useEffect(() => {
81
+ if (reduceMotion) {
82
+ opacity.setValue(0.7);
83
+ return;
84
+ }
85
+ const animation = Animated.loop(Animated.sequence([
86
+ Animated.timing(opacity, {
87
+ toValue: 1.0,
88
+ duration: 800,
89
+ useNativeDriver: shouldUseNativeDriver,
90
+ }),
91
+ Animated.timing(opacity, {
92
+ toValue: 0.4,
93
+ duration: 800,
94
+ useNativeDriver: shouldUseNativeDriver,
95
+ }),
96
+ ]));
97
+ animation.start();
98
+ return () => animation.stop();
99
+ }, [opacity, reduceMotion]);
100
+ return (_jsx(Animated.View, { style: {
101
+ width: "40%",
102
+ height,
103
+ borderRadius,
104
+ backgroundColor: fillColor,
105
+ opacity,
106
+ } }));
107
+ }
@@ -0,0 +1,65 @@
1
+ import { StyleProp, ViewStyle } from "react-native";
2
+ import * as RadioGroupPrimitive from "@rn-primitives/radio-group";
3
+ /**
4
+ * Size variants for RadioGroup items
5
+ */
6
+ export type RadioGroupSize = "sm" | "md" | "lg";
7
+ export interface RadioGroupProps extends Omit<RadioGroupPrimitive.RootProps, "style"> {
8
+ /**
9
+ * Size variant for all items
10
+ * @default "md"
11
+ */
12
+ size?: RadioGroupSize;
13
+ /**
14
+ * Whether the group is in an error state
15
+ */
16
+ error?: boolean;
17
+ /**
18
+ * Custom style override
19
+ */
20
+ style?: StyleProp<ViewStyle>;
21
+ }
22
+ /**
23
+ * RadioGroup Component
24
+ * A group of radio buttons for single selection.
25
+ * Uses @rn-primitives/radio-group with animated indicators.
26
+ *
27
+ * Usage:
28
+ * ```tsx
29
+ * const [value, setValue] = useState("option1");
30
+ * <RadioGroup value={value} onValueChange={setValue}>
31
+ * <RadioGroup.Item value="option1" label="Option 1" />
32
+ * <RadioGroup.Item value="option2" label="Option 2" />
33
+ * <RadioGroup.Item value="option3" label="Option 3" />
34
+ * </RadioGroup>
35
+ * ```
36
+ */
37
+ declare function RadioGroupRoot({ size, error, value, onValueChange, style: styleOverride, children, ...props }: RadioGroupProps): import("react/jsx-runtime").JSX.Element;
38
+ export interface RadioGroupItemProps extends Omit<RadioGroupPrimitive.ItemProps, "style"> {
39
+ /**
40
+ * Optional label text displayed next to the radio button
41
+ */
42
+ label?: string;
43
+ /**
44
+ * Whether the field is required (shows asterisk in label)
45
+ */
46
+ required?: boolean;
47
+ /**
48
+ * Custom style override for the radio circle
49
+ */
50
+ style?: StyleProp<ViewStyle>;
51
+ /**
52
+ * Style for the label container row
53
+ */
54
+ labelStyle?: StyleProp<ViewStyle>;
55
+ }
56
+ /**
57
+ * RadioGroupItem Component
58
+ * Individual radio button with optional label.
59
+ * Must be rendered inside a RadioGroup.
60
+ */
61
+ declare function RadioGroupItem({ label, required, style: styleOverride, labelStyle, value: itemValue, disabled, ...props }: RadioGroupItemProps): import("react/jsx-runtime").JSX.Element;
62
+ declare const RadioGroupComponent: typeof RadioGroupRoot & {
63
+ Item: typeof RadioGroupItem;
64
+ };
65
+ export { RadioGroupComponent as RadioGroup, RadioGroupItem };
@@ -0,0 +1,142 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { createContext, useContext, useEffect } from "react";
3
+ import { View, StyleSheet, Pressable } from "react-native";
4
+ import Animated, { useSharedValue, useAnimatedStyle, withTiming, useReducedMotion, } from "react-native-reanimated";
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 RadioGroupPrimitive from "@rn-primitives/radio-group";
10
+ const DEFAULT_HIT_SLOP = 8;
11
+ const SIZE_CONFIGS = {
12
+ sm: { outer: 16, inner: 8, borderWidth: 1 },
13
+ md: { outer: 20, inner: 10, borderWidth: 1 },
14
+ lg: { outer: 24, inner: 12, borderWidth: 1 },
15
+ };
16
+ const RadioGroupContext = createContext(null);
17
+ function useRadioGroupContext() {
18
+ const context = useContext(RadioGroupContext);
19
+ if (context === null) {
20
+ throw new Error("RadioGroup compound components cannot be rendered outside the RadioGroup component");
21
+ }
22
+ return context;
23
+ }
24
+ /**
25
+ * RadioGroup Component
26
+ * A group of radio buttons for single selection.
27
+ * Uses @rn-primitives/radio-group with animated indicators.
28
+ *
29
+ * Usage:
30
+ * ```tsx
31
+ * const [value, setValue] = useState("option1");
32
+ * <RadioGroup value={value} onValueChange={setValue}>
33
+ * <RadioGroup.Item value="option1" label="Option 1" />
34
+ * <RadioGroup.Item value="option2" label="Option 2" />
35
+ * <RadioGroup.Item value="option3" label="Option 3" />
36
+ * </RadioGroup>
37
+ * ```
38
+ */
39
+ function RadioGroupRoot({ size = "md", error = false, value, onValueChange, style: styleOverride, children, ...props }) {
40
+ const flattenedStyle = styleOverride ? StyleSheet.flatten(styleOverride) : undefined;
41
+ return (_jsx(RadioGroupContext.Provider, { value: { size, error, value, onValueChange }, children: _jsx(RadioGroupPrimitive.Root, { ...props, value: value, onValueChange: onValueChange, style: {
42
+ flexDirection: "column",
43
+ gap: spacing.listItemSpacing,
44
+ ...(flattenedStyle || {}),
45
+ }, children: children }) }));
46
+ }
47
+ /**
48
+ * RadioGroupItem Component
49
+ * Individual radio button with optional label.
50
+ * Must be rendered inside a RadioGroup.
51
+ */
52
+ function RadioGroupItem({ label, required = false, style: styleOverride, labelStyle, value: itemValue, disabled, ...props }) {
53
+ const { theme, getContrastingColor } = useTheme();
54
+ const reduceMotion = useReducedMotion();
55
+ const { size, error, value: groupValue, onValueChange } = useRadioGroupContext();
56
+ const sizeConfig = SIZE_CONFIGS[size];
57
+ const isChecked = groupValue === itemValue;
58
+ // Animated dot scale — follows Checkbox opacity pattern
59
+ const dotScale = useSharedValue(isChecked ? 1 : 0);
60
+ useEffect(() => {
61
+ dotScale.value = reduceMotion
62
+ ? (isChecked ? 1 : 0)
63
+ : withTiming(isChecked ? 1 : 0, { duration: 60 });
64
+ }, [isChecked, reduceMotion, dotScale]);
65
+ const dotStyle = useAnimatedStyle(() => ({
66
+ transform: [{ scale: dotScale.value }],
67
+ }));
68
+ // Wrap onPress to add haptic feedback
69
+ const handlePress = () => {
70
+ hapticLight();
71
+ };
72
+ // Border color follows Checkbox pattern
73
+ const borderColor = error
74
+ ? theme.colors.destructive
75
+ : isChecked
76
+ ? theme.colors.primary
77
+ : getContrastingColor(theme.colors.background, theme.colors.text, theme.colors.textDim);
78
+ const flattenedStyle = styleOverride ? StyleSheet.flatten(styleOverride) : undefined;
79
+ const radioElement = (_jsx(RadioGroupPrimitive.Item, { ...props, value: itemValue, disabled: disabled, onPress: handlePress, style: {
80
+ borderColor,
81
+ backgroundColor: "transparent",
82
+ borderRadius: sizeConfig.outer / 2,
83
+ borderWidth: sizeConfig.borderWidth,
84
+ width: sizeConfig.outer,
85
+ height: sizeConfig.outer,
86
+ justifyContent: "center",
87
+ alignItems: "center",
88
+ opacity: disabled ? 0.5 : 1,
89
+ ...(flattenedStyle || {}),
90
+ }, hitSlop: DEFAULT_HIT_SLOP, accessibilityLabel: label, children: _jsx(Animated.View, { style: [
91
+ dotStyle,
92
+ {
93
+ width: sizeConfig.inner,
94
+ height: sizeConfig.inner,
95
+ borderRadius: sizeConfig.inner / 2,
96
+ backgroundColor: theme.colors.primary,
97
+ },
98
+ ] }) }));
99
+ // If no label, return just the radio button
100
+ if (!label) {
101
+ return radioElement;
102
+ }
103
+ // With label, wrap in a pressable row — tapping the label selects the item
104
+ return (_jsxs(Pressable, { onPress: () => {
105
+ if (!disabled) {
106
+ hapticLight();
107
+ onValueChange(itemValue);
108
+ }
109
+ }, style: [styles.container, labelStyle], disabled: disabled, accessibilityRole: "radio", accessibilityState: {
110
+ checked: isChecked,
111
+ disabled: !!disabled,
112
+ }, accessibilityLabel: label, children: [radioElement, _jsx(View, { style: styles.labelContainer, children: _jsxs(StyledText, { style: [
113
+ styles.label,
114
+ { color: theme.colors.text },
115
+ disabled && styles.disabledLabel,
116
+ error && { color: theme.colors.destructive },
117
+ ], children: [label, required && (_jsx(StyledText, { style: [styles.required, { color: theme.colors.destructive }], children: " *" }))] }) })] }));
118
+ }
119
+ const styles = StyleSheet.create({
120
+ container: {
121
+ flexDirection: "row",
122
+ alignItems: "center",
123
+ gap: spacing.sm,
124
+ },
125
+ labelContainer: {
126
+ flex: 1,
127
+ },
128
+ label: {
129
+ fontSize: 14,
130
+ },
131
+ disabledLabel: {
132
+ opacity: 0.5,
133
+ },
134
+ required: {
135
+ fontWeight: "bold",
136
+ },
137
+ });
138
+ // Compound export
139
+ const RadioGroupComponent = Object.assign(RadioGroupRoot, {
140
+ Item: RadioGroupItem,
141
+ });
142
+ export { RadioGroupComponent as RadioGroup, RadioGroupItem };
@@ -0,0 +1,88 @@
1
+ import * as React from "react";
2
+ import { View } from "react-native";
3
+ import * as SelectPrimitive from "@rn-primitives/select";
4
+ /**
5
+ * Size variants for SelectTrigger
6
+ */
7
+ type SelectSize = "sm" | "md" | "lg";
8
+ /**
9
+ * SelectTrigger Component
10
+ * Button that shows the current value and a chevron-down icon
11
+ * Supports sm/md/lg sizes and error state
12
+ */
13
+ type SelectTriggerProps = SelectPrimitive.TriggerProps & {
14
+ size?: SelectSize;
15
+ error?: boolean;
16
+ };
17
+ declare function SelectTrigger({ size, error, children, style: styleOverride, disabled, ...props }: SelectTriggerProps): import("react/jsx-runtime").JSX.Element;
18
+ /**
19
+ * SelectValue Component
20
+ * Displays the selected value text or placeholder
21
+ */
22
+ type SelectValueProps = SelectPrimitive.ValueProps & {
23
+ size?: SelectSize;
24
+ };
25
+ declare function SelectValue({ size, placeholder, style: styleOverride, ...props }: SelectValueProps): import("react/jsx-runtime").JSX.Element;
26
+ /**
27
+ * SelectContent Component
28
+ * Dropdown overlay following the DropdownMenu.Content pattern
29
+ * Uses portal + FullWindowOverlay on iOS, React.Fragment on other platforms
30
+ */
31
+ type SelectContentProps = SelectPrimitive.ContentProps & {
32
+ portalHost?: string;
33
+ };
34
+ declare function SelectContent({ side, align, sideOffset, portalHost, style: styleOverride, ...props }: SelectContentProps): import("react/jsx-runtime").JSX.Element;
35
+ /**
36
+ * SelectItem Component
37
+ * Individual option in the select dropdown
38
+ * Shows a check icon on the left when selected
39
+ */
40
+ type SelectItemProps = SelectPrimitive.ItemProps;
41
+ declare function SelectItem({ children, style: styleOverride, ...props }: SelectItemProps): import("react/jsx-runtime").JSX.Element;
42
+ /**
43
+ * SelectGroup Component
44
+ * Groups related select items together
45
+ */
46
+ type SelectGroupProps = SelectPrimitive.GroupProps;
47
+ declare function SelectGroup({ style: styleOverride, ...props }: SelectGroupProps): import("react/jsx-runtime").JSX.Element;
48
+ /**
49
+ * SelectLabel Component
50
+ * Label for a group of select items
51
+ */
52
+ type SelectLabelProps = SelectPrimitive.LabelProps;
53
+ declare function SelectLabel({ style: styleOverride, ...props }: SelectLabelProps): import("react/jsx-runtime").JSX.Element;
54
+ /**
55
+ * SelectSeparator Component
56
+ * Visual divider between select item groups
57
+ */
58
+ type SelectSeparatorProps = SelectPrimitive.SeparatorProps;
59
+ declare function SelectSeparator({ style: styleOverride, ...props }: SelectSeparatorProps): import("react/jsx-runtime").JSX.Element;
60
+ /**
61
+ * Select Component with Sub-components
62
+ * Properly typed interface for dot notation access (e.g., Select.Trigger)
63
+ */
64
+ declare const Select: {
65
+ ({ asChild, value: valueProp, defaultValue, onValueChange: onValueChangeProp, onOpenChange: onOpenChangeProp, disabled, ref, ...viewProps }: import("react-native").ViewProps & {
66
+ asChild?: boolean;
67
+ } & {
68
+ value?: import("@rn-primitives/select").Option;
69
+ defaultValue?: import("@rn-primitives/select").Option;
70
+ onValueChange?: (option: import("@rn-primitives/select").Option) => void;
71
+ onOpenChange?: (open: boolean) => void;
72
+ disabled?: boolean;
73
+ dir?: "ltr" | "rtl";
74
+ name?: string;
75
+ required?: boolean;
76
+ } & React.RefAttributes<View>): React.JSX.Element;
77
+ displayName: string;
78
+ } & {
79
+ Trigger: typeof SelectTrigger;
80
+ Value: typeof SelectValue;
81
+ Content: typeof SelectContent;
82
+ Item: typeof SelectItem;
83
+ Group: typeof SelectGroup;
84
+ Label: typeof SelectLabel;
85
+ Separator: typeof SelectSeparator;
86
+ };
87
+ export { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, SelectGroup, SelectLabel, SelectSeparator, };
88
+ export type { SelectSize, SelectTriggerProps, SelectValueProps, SelectContentProps, SelectItemProps, SelectGroupProps, SelectLabelProps, SelectSeparatorProps, };