@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,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, };
|