@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,172 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { Platform, StyleSheet, View } from "react-native";
4
+ import { Icon } from "./Icon";
5
+ import { AnimatedView } from "./AnimatedView";
6
+ import { TextClassContext, TextColorContext } from "./StyledText";
7
+ import { useTheme } from "../hooks/useTheme";
8
+ import { spacing } from "../constants/spacing";
9
+ import { palette } from "../constants/colors";
10
+ import * as SelectPrimitive from "@rn-primitives/select";
11
+ import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens";
12
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
13
+ // Platform-specific overlay
14
+ const FullWindowOverlay = Platform.OS === "ios" ? RNFullWindowOverlay : React.Fragment;
15
+ /**
16
+ * SelectRoot Component
17
+ * Manages select state and provides context for trigger and content
18
+ */
19
+ const SelectRoot = SelectPrimitive.Root;
20
+ const SIZE_CONFIGS = {
21
+ sm: {
22
+ height: 32,
23
+ fontSize: 12,
24
+ paddingHorizontal: spacing.sm,
25
+ },
26
+ md: {
27
+ height: 36,
28
+ fontSize: 14,
29
+ paddingHorizontal: spacing.inputPadding,
30
+ },
31
+ lg: {
32
+ height: 40,
33
+ fontSize: 14,
34
+ paddingHorizontal: spacing.md,
35
+ },
36
+ };
37
+ function SelectTrigger({ size = "md", error = false, children, style: styleOverride, disabled, ...props }) {
38
+ const { theme } = useTheme();
39
+ const sizeConfig = SIZE_CONFIGS[size];
40
+ return (_jsxs(SelectPrimitive.Trigger, { disabled: disabled, ...props, style: {
41
+ flexDirection: "row",
42
+ justifyContent: "space-between",
43
+ alignItems: "center",
44
+ height: sizeConfig.height,
45
+ paddingHorizontal: sizeConfig.paddingHorizontal,
46
+ borderWidth: 1,
47
+ borderColor: error ? theme.colors.destructive : theme.colors.border,
48
+ borderRadius: spacing.radiusMd,
49
+ backgroundColor: theme.colors.background,
50
+ ...(Platform.OS === "web" && {
51
+ cursor: disabled ? "not-allowed" : "pointer",
52
+ outlineStyle: "none",
53
+ }),
54
+ ...(disabled && { opacity: 0.5 }),
55
+ ...(styleOverride && typeof styleOverride !== "function"
56
+ ? StyleSheet.flatten(styleOverride)
57
+ : {}),
58
+ }, children: [typeof children === "function" ? null : children, _jsx(Icon, { name: "chevron-down", size: 16, color: theme.colors.mutedForeground })] }));
59
+ }
60
+ function SelectValue({ size = "md", placeholder, style: styleOverride, ...props }) {
61
+ const { theme } = useTheme();
62
+ const sizeConfig = SIZE_CONFIGS[size];
63
+ return (_jsx(SelectPrimitive.Value, { placeholder: placeholder, ...props, style: {
64
+ fontSize: sizeConfig.fontSize,
65
+ color: theme.colors.text,
66
+ flex: 1,
67
+ ...(styleOverride && typeof styleOverride !== "function"
68
+ ? StyleSheet.flatten(styleOverride)
69
+ : {}),
70
+ } }));
71
+ }
72
+ function SelectContent({ side, align = "start", sideOffset = 4, portalHost, style: styleOverride, ...props }) {
73
+ const { theme, getShadowStyle, getContrastingColor } = useTheme();
74
+ const shadowStyle = StyleSheet.flatten(getShadowStyle("soft"));
75
+ const insets = useSafeAreaInsets();
76
+ const textColor = getContrastingColor(theme.colors.background, palette.white, palette.black);
77
+ return (_jsx(SelectPrimitive.Portal, { hostName: portalHost, children: _jsx(FullWindowOverlay, { children: _jsx(SelectPrimitive.Overlay, { style: Platform.select({
78
+ native: StyleSheet.absoluteFill,
79
+ default: undefined,
80
+ }), children: _jsx(AnimatedView, { type: "fade", children: _jsx(TextColorContext.Provider, { value: textColor, children: _jsx(TextClassContext.Provider, { value: "", children: _jsx(SelectPrimitive.Content, { side: side, align: align, sideOffset: sideOffset, insets: insets, avoidCollisions: true, ...props, style: {
81
+ backgroundColor: theme.colors.background,
82
+ borderWidth: 1,
83
+ borderColor: theme.colors.border,
84
+ borderRadius: spacing.radiusSm,
85
+ padding: spacing.xs,
86
+ minWidth: 128,
87
+ overflow: "hidden",
88
+ ...shadowStyle,
89
+ ...(Platform.OS === "web" && {
90
+ zIndex: 50,
91
+ cursor: "default",
92
+ }),
93
+ ...(styleOverride && typeof styleOverride !== "function"
94
+ ? StyleSheet.flatten(styleOverride)
95
+ : {}),
96
+ } }) }) }) }) }) }) }));
97
+ }
98
+ function SelectItem({ children, style: styleOverride, ...props }) {
99
+ const { theme } = useTheme();
100
+ return (_jsx(TextClassContext.Provider, { value: "", children: _jsxs(SelectPrimitive.Item, { ...props, style: {
101
+ position: "relative",
102
+ flexDirection: "row",
103
+ alignItems: "center",
104
+ gap: spacing.sm,
105
+ borderRadius: spacing.radiusSm,
106
+ paddingVertical: Platform.select({ web: spacing.xs, default: spacing.sm }),
107
+ paddingLeft: spacing.xl,
108
+ paddingRight: spacing.sm,
109
+ backgroundColor: "transparent",
110
+ ...(Platform.OS === "web" && {
111
+ cursor: props.disabled ? "not-allowed" : "pointer",
112
+ outlineStyle: "none",
113
+ }),
114
+ ...(props.disabled && { opacity: 0.5 }),
115
+ ...(styleOverride && typeof styleOverride !== "function"
116
+ ? StyleSheet.flatten(styleOverride)
117
+ : {}),
118
+ }, children: [_jsx(View, { style: {
119
+ position: "absolute",
120
+ left: spacing.sm,
121
+ height: 14,
122
+ width: 14,
123
+ alignItems: "center",
124
+ justifyContent: "center",
125
+ }, children: _jsx(SelectPrimitive.ItemIndicator, { children: _jsx(Icon, { name: "check", size: 16, color: theme.colors.text, ...(Platform.OS === "web" && { style: { pointerEvents: "none" } }) }) }) }), _jsx(SelectPrimitive.ItemText, {}), typeof children === "function" ? null : children] }) }));
126
+ }
127
+ function SelectGroup({ style: styleOverride, ...props }) {
128
+ return (_jsx(SelectPrimitive.Group, { ...props, style: {
129
+ ...(styleOverride && typeof styleOverride !== "function"
130
+ ? StyleSheet.flatten(styleOverride)
131
+ : {}),
132
+ } }));
133
+ }
134
+ function SelectLabel({ style: styleOverride, ...props }) {
135
+ const { theme } = useTheme();
136
+ return (_jsx(SelectPrimitive.Label, { ...props, style: {
137
+ paddingHorizontal: spacing.sm,
138
+ paddingVertical: Platform.select({ web: spacing.xs, default: spacing.sm }),
139
+ fontSize: 14,
140
+ fontWeight: "500",
141
+ color: theme.colors.text,
142
+ ...(styleOverride && typeof styleOverride !== "function"
143
+ ? StyleSheet.flatten(styleOverride)
144
+ : {}),
145
+ } }));
146
+ }
147
+ function SelectSeparator({ style: styleOverride, ...props }) {
148
+ const { theme } = useTheme();
149
+ return (_jsx(SelectPrimitive.Separator, { ...props, style: {
150
+ backgroundColor: theme.colors.border,
151
+ marginHorizontal: -spacing.xs,
152
+ marginVertical: spacing.xs,
153
+ height: 1,
154
+ ...(styleOverride && typeof styleOverride !== "function"
155
+ ? StyleSheet.flatten(styleOverride)
156
+ : {}),
157
+ } }));
158
+ }
159
+ /**
160
+ * Select Component with Sub-components
161
+ * Properly typed interface for dot notation access (e.g., Select.Trigger)
162
+ */
163
+ const Select = Object.assign(SelectRoot, {
164
+ Trigger: SelectTrigger,
165
+ Value: SelectValue,
166
+ Content: SelectContent,
167
+ Item: SelectItem,
168
+ Group: SelectGroup,
169
+ Label: SelectLabel,
170
+ Separator: SelectSeparator,
171
+ });
172
+ export { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, SelectGroup, SelectLabel, SelectSeparator, };
@@ -0,0 +1,83 @@
1
+ import { StyleProp, ViewStyle } from "react-native";
2
+ import * as SeparatorPrimitive from "@rn-primitives/separator";
3
+ /**
4
+ * Size variants for Separator thickness
5
+ */
6
+ export type SeparatorSize = "sm" | "md" | "lg";
7
+ /**
8
+ * Visual variants for Separator
9
+ */
10
+ export type SeparatorVariant = "default" | "muted" | "primary";
11
+ export interface SeparatorProps extends Omit<SeparatorPrimitive.RootProps, "style"> {
12
+ /**
13
+ * Orientation of the separator
14
+ * @default "horizontal"
15
+ */
16
+ orientation?: "horizontal" | "vertical";
17
+ /**
18
+ * Whether the separator is purely decorative (no semantic meaning)
19
+ * @default true
20
+ */
21
+ decorative?: boolean;
22
+ /**
23
+ * Size variant controlling thickness
24
+ * @default "sm"
25
+ */
26
+ size?: SeparatorSize;
27
+ /**
28
+ * Visual variant controlling color
29
+ * @default "default"
30
+ */
31
+ variant?: SeparatorVariant;
32
+ /**
33
+ * Custom style override (uses StyleSheet.flatten for web compatibility)
34
+ */
35
+ style?: StyleProp<ViewStyle>;
36
+ /**
37
+ * Margin around the separator
38
+ * For horizontal: vertical margin, for vertical: horizontal margin
39
+ * @default spacing.md (16)
40
+ */
41
+ margin?: number;
42
+ }
43
+ /**
44
+ * Enhanced Separator Component
45
+ *
46
+ * A visual or semantic distinction between content sections.
47
+ * Supports both horizontal and vertical orientations with
48
+ * theme-aware styling.
49
+ *
50
+ * Features:
51
+ * - Orientation (horizontal, vertical)
52
+ * - Size variants (sm, md, lg)
53
+ * - Visual variants (default, muted, primary)
54
+ * - Configurable margin
55
+ * - Theme-aware colors
56
+ * - Full accessibility support
57
+ *
58
+ * Usage:
59
+ * ```tsx
60
+ * // Basic horizontal separator
61
+ * <Separator />
62
+ *
63
+ * // Vertical separator
64
+ * <Separator orientation="vertical" />
65
+ *
66
+ * // With size variant
67
+ * <Separator size="lg" />
68
+ *
69
+ * // Primary colored separator
70
+ * <Separator variant="primary" />
71
+ *
72
+ * // Muted separator with custom margin
73
+ * <Separator variant="muted" margin={spacing.lg} />
74
+ *
75
+ * // Non-decorative separator (has semantic meaning)
76
+ * <Separator decorative={false} />
77
+ *
78
+ * // Custom style
79
+ * <Separator style={{ opacity: 0.5 }} />
80
+ * ```
81
+ */
82
+ declare function Separator({ orientation, decorative, size, variant, style: styleOverride, margin, ...props }: SeparatorProps): import("react/jsx-runtime").JSX.Element;
83
+ export { Separator };
@@ -0,0 +1,85 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { StyleSheet } from "react-native";
3
+ import { useTheme } from "../hooks/useTheme";
4
+ import { spacing } from "../constants/spacing";
5
+ import * as SeparatorPrimitive from "@rn-primitives/separator";
6
+ const SIZE_CONFIGS = {
7
+ sm: 1,
8
+ md: 2,
9
+ lg: 4,
10
+ };
11
+ /**
12
+ * Enhanced Separator Component
13
+ *
14
+ * A visual or semantic distinction between content sections.
15
+ * Supports both horizontal and vertical orientations with
16
+ * theme-aware styling.
17
+ *
18
+ * Features:
19
+ * - Orientation (horizontal, vertical)
20
+ * - Size variants (sm, md, lg)
21
+ * - Visual variants (default, muted, primary)
22
+ * - Configurable margin
23
+ * - Theme-aware colors
24
+ * - Full accessibility support
25
+ *
26
+ * Usage:
27
+ * ```tsx
28
+ * // Basic horizontal separator
29
+ * <Separator />
30
+ *
31
+ * // Vertical separator
32
+ * <Separator orientation="vertical" />
33
+ *
34
+ * // With size variant
35
+ * <Separator size="lg" />
36
+ *
37
+ * // Primary colored separator
38
+ * <Separator variant="primary" />
39
+ *
40
+ * // Muted separator with custom margin
41
+ * <Separator variant="muted" margin={spacing.lg} />
42
+ *
43
+ * // Non-decorative separator (has semantic meaning)
44
+ * <Separator decorative={false} />
45
+ *
46
+ * // Custom style
47
+ * <Separator style={{ opacity: 0.5 }} />
48
+ * ```
49
+ */
50
+ function Separator({ orientation = "horizontal", decorative = true, size = "sm", variant = "default", style: styleOverride, margin = spacing.md, ...props }) {
51
+ const { theme } = useTheme();
52
+ // Determine color based on variant
53
+ const getColor = () => {
54
+ switch (variant) {
55
+ case "primary":
56
+ return theme.colors.primary;
57
+ case "muted":
58
+ return theme.colors.muted;
59
+ case "default":
60
+ default:
61
+ return theme.colors.border;
62
+ }
63
+ };
64
+ const thickness = SIZE_CONFIGS[size];
65
+ const color = getColor();
66
+ // Flatten style override for web compatibility
67
+ const flattenedStyle = styleOverride ? StyleSheet.flatten(styleOverride) : undefined;
68
+ const isHorizontal = orientation === "horizontal";
69
+ return (_jsx(SeparatorPrimitive.Root, { ...props, orientation: orientation, decorative: decorative, style: {
70
+ backgroundColor: color,
71
+ ...(isHorizontal
72
+ ? {
73
+ height: thickness,
74
+ width: "100%",
75
+ marginVertical: margin,
76
+ }
77
+ : {
78
+ width: thickness,
79
+ height: "100%",
80
+ marginHorizontal: margin,
81
+ }),
82
+ ...(flattenedStyle || {}),
83
+ } }));
84
+ }
85
+ export { Separator };
@@ -0,0 +1,68 @@
1
+ import { StyleProp, ViewStyle } from "react-native";
2
+ export interface SkeletonProps {
3
+ /** Width of the skeleton element */
4
+ width?: number | `${number}%`;
5
+ /** Height of the skeleton element */
6
+ height?: number;
7
+ /** Border radius (defaults to radiusMd) */
8
+ borderRadius?: number;
9
+ /** Render as a circle (overrides borderRadius) */
10
+ circle?: boolean;
11
+ /** Custom style override */
12
+ style?: StyleProp<ViewStyle>;
13
+ }
14
+ /**
15
+ * Skeleton Component
16
+ *
17
+ * A placeholder loading element with a pulsing shimmer animation.
18
+ * Respects reduced-motion accessibility settings.
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * <Skeleton width={200} height={20} />
23
+ * <Skeleton width={40} height={40} circle />
24
+ * ```
25
+ */
26
+ export declare function Skeleton({ width, height, borderRadius, circle, style, }: SkeletonProps): import("react/jsx-runtime").JSX.Element;
27
+ export interface SkeletonTextProps {
28
+ /** Number of text lines to render */
29
+ lines?: number;
30
+ /** Height of each line */
31
+ lineHeight?: number;
32
+ /** Gap between lines */
33
+ gap?: number;
34
+ /** Custom style override */
35
+ style?: StyleProp<ViewStyle>;
36
+ }
37
+ /**
38
+ * SkeletonText renders N horizontal bars simulating text.
39
+ * The last line is rendered at 60% width for a natural look.
40
+ */
41
+ export declare function SkeletonText({ lines, lineHeight, gap, style, }: SkeletonTextProps): import("react/jsx-runtime").JSX.Element;
42
+ export interface SkeletonAvatarProps {
43
+ /** Diameter of the circular avatar placeholder */
44
+ size?: number;
45
+ /** Custom style override */
46
+ style?: StyleProp<ViewStyle>;
47
+ }
48
+ /**
49
+ * SkeletonAvatar is a circular skeleton shorthand.
50
+ */
51
+ export declare function SkeletonAvatar({ size, style }: SkeletonAvatarProps): import("react/jsx-runtime").JSX.Element;
52
+ export interface SkeletonCardProps {
53
+ /** Whether to show an image placeholder area */
54
+ showImage?: boolean;
55
+ /** Image placeholder height */
56
+ imageHeight?: number;
57
+ /** Whether to show an avatar row */
58
+ showAvatar?: boolean;
59
+ /** Number of text lines */
60
+ textLines?: number;
61
+ /** Custom style override */
62
+ style?: StyleProp<ViewStyle>;
63
+ }
64
+ /**
65
+ * SkeletonCard composes an image placeholder, optional avatar row,
66
+ * and text lines in a Card-styled wrapper.
67
+ */
68
+ export declare function SkeletonCard({ showImage, imageHeight, showAvatar, textLines, style, }: SkeletonCardProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,99 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from "react";
3
+ import { View, Animated, StyleSheet } from "react-native";
4
+ import { useReducedMotion } from "react-native-reanimated";
5
+ import { useTheme } from "../hooks/useTheme";
6
+ import { shouldUseNativeDriver } from "../lib/animations";
7
+ import { spacing } from "../constants/spacing";
8
+ /**
9
+ * Skeleton Component
10
+ *
11
+ * A placeholder loading element with a pulsing shimmer animation.
12
+ * Respects reduced-motion accessibility settings.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * <Skeleton width={200} height={20} />
17
+ * <Skeleton width={40} height={40} circle />
18
+ * ```
19
+ */
20
+ export function Skeleton({ width = "100%", height = 20, borderRadius = spacing.radiusMd, circle = false, style, }) {
21
+ const { theme } = useTheme();
22
+ const reduceMotion = useReducedMotion();
23
+ const opacity = useRef(new Animated.Value(reduceMotion ? 0.6 : 0.3)).current;
24
+ useEffect(() => {
25
+ if (reduceMotion) {
26
+ opacity.setValue(0.6);
27
+ return;
28
+ }
29
+ const animation = Animated.loop(Animated.sequence([
30
+ Animated.timing(opacity, {
31
+ toValue: 1,
32
+ duration: 800,
33
+ useNativeDriver: shouldUseNativeDriver,
34
+ }),
35
+ Animated.timing(opacity, {
36
+ toValue: 0.3,
37
+ duration: 800,
38
+ useNativeDriver: shouldUseNativeDriver,
39
+ }),
40
+ ]));
41
+ animation.start();
42
+ return () => animation.stop();
43
+ }, [opacity, reduceMotion]);
44
+ const resolvedSize = circle ? (typeof height === "number" ? height : 40) : undefined;
45
+ return (_jsx(Animated.View, { style: [
46
+ {
47
+ width: circle ? resolvedSize : width,
48
+ height: circle ? resolvedSize : height,
49
+ borderRadius: circle ? (resolvedSize / 2) : borderRadius,
50
+ backgroundColor: theme.colors.muted,
51
+ opacity,
52
+ },
53
+ style,
54
+ ] }));
55
+ }
56
+ /**
57
+ * SkeletonText renders N horizontal bars simulating text.
58
+ * The last line is rendered at 60% width for a natural look.
59
+ */
60
+ export function SkeletonText({ lines = 3, lineHeight = 14, gap = spacing.sm, style, }) {
61
+ return (_jsx(View, { style: [{ gap }, style], children: Array.from({ length: lines }).map((_, i) => (_jsx(Skeleton, { width: i === lines - 1 ? "60%" : "100%", height: lineHeight }, i))) }));
62
+ }
63
+ /**
64
+ * SkeletonAvatar is a circular skeleton shorthand.
65
+ */
66
+ export function SkeletonAvatar({ size = 40, style }) {
67
+ return _jsx(Skeleton, { width: size, height: size, circle: true, style: style });
68
+ }
69
+ /**
70
+ * SkeletonCard composes an image placeholder, optional avatar row,
71
+ * and text lines in a Card-styled wrapper.
72
+ */
73
+ export function SkeletonCard({ showImage = true, imageHeight = 140, showAvatar = true, textLines = 3, style, }) {
74
+ const { theme } = useTheme();
75
+ const styles = createCardStyles(theme);
76
+ return (_jsxs(View, { style: [styles.card, style], children: [showImage && (_jsx(Skeleton, { width: "100%", height: imageHeight, borderRadius: 0 })), _jsxs(View, { style: styles.body, children: [showAvatar && (_jsxs(View, { style: styles.avatarRow, children: [_jsx(SkeletonAvatar, { size: 36 }), _jsxs(View, { style: styles.avatarText, children: [_jsx(Skeleton, { width: "50%", height: 14 }), _jsx(Skeleton, { width: "30%", height: 12 })] })] })), _jsx(SkeletonText, { lines: textLines })] })] }));
77
+ }
78
+ const createCardStyles = (theme) => StyleSheet.create({
79
+ card: {
80
+ backgroundColor: theme.colors.card,
81
+ borderRadius: spacing.radiusLg,
82
+ borderWidth: 1,
83
+ borderColor: theme.colors.border,
84
+ overflow: "hidden",
85
+ },
86
+ body: {
87
+ padding: spacing.md,
88
+ gap: spacing.md,
89
+ },
90
+ avatarRow: {
91
+ flexDirection: "row",
92
+ alignItems: "center",
93
+ gap: spacing.sm,
94
+ },
95
+ avatarText: {
96
+ flex: 1,
97
+ gap: spacing.xs,
98
+ },
99
+ });
@@ -0,0 +1,24 @@
1
+ import { StyleProp, ViewStyle } from "react-native";
2
+ export type SliderSize = "sm" | "md";
3
+ export interface SliderProps {
4
+ /** Current value */
5
+ value?: number;
6
+ /** Called when the user drags the thumb */
7
+ onValueChange?: (value: number) => void;
8
+ /** Minimum value @default 0 */
9
+ min?: number;
10
+ /** Maximum value @default 100 */
11
+ max?: number;
12
+ /** Step increment @default 1 */
13
+ step?: number;
14
+ /** Size variant @default "md" */
15
+ size?: SliderSize;
16
+ /** Disable interaction @default false */
17
+ disabled?: boolean;
18
+ /** Show value label above thumb @default false */
19
+ showValue?: boolean;
20
+ /** Style override for outer container */
21
+ style?: StyleProp<ViewStyle>;
22
+ }
23
+ declare function Slider({ value, onValueChange, min, max, step, size, disabled, showValue, style: styleOverride, }: SliderProps): import("react/jsx-runtime").JSX.Element;
24
+ export { Slider };