@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,55 @@
1
+ import { useState, useEffect } from "react";
2
+ import { Dimensions, Platform } from "react-native";
3
+ export const SCREEN_SIZES = {
4
+ SMALL: 768,
5
+ MEDIUM: 1000,
6
+ LARGE: 1200,
7
+ };
8
+ /**
9
+ * Provides a consistent way to access window dimensions and screen size information across mobile and web.
10
+ *
11
+ */
12
+ export const useDimensions = () => {
13
+ const isWeb = Platform.OS === "web";
14
+ const [dimensions, setDimensions] = useState({
15
+ width: 0,
16
+ height: 0,
17
+ orientation: "portrait",
18
+ isSmallScreen: true,
19
+ isMediumScreen: false,
20
+ isLargeScreen: false,
21
+ });
22
+ useEffect(() => {
23
+ const initialDimensions = isWeb
24
+ ? { width: window.innerWidth, height: window.innerHeight }
25
+ : Dimensions.get("window");
26
+ const updateDimensions = (width, height) => {
27
+ const orientation = width > height ? "landscape" : "portrait";
28
+ setDimensions({
29
+ width,
30
+ height,
31
+ orientation,
32
+ isSmallScreen: width <= SCREEN_SIZES.SMALL,
33
+ isMediumScreen: width > SCREEN_SIZES.SMALL,
34
+ isLargeScreen: width > SCREEN_SIZES.MEDIUM,
35
+ });
36
+ };
37
+ updateDimensions(initialDimensions.width, initialDimensions.height);
38
+ if (isWeb) {
39
+ const handleResize = () => {
40
+ updateDimensions(window.innerWidth, window.innerHeight);
41
+ };
42
+ window.addEventListener("resize", handleResize);
43
+ return () => {
44
+ window.removeEventListener("resize", handleResize);
45
+ };
46
+ }
47
+ else {
48
+ const onChange = ({ window }) => {
49
+ updateDimensions(window.width, window.height);
50
+ };
51
+ Dimensions.addEventListener("change", onChange);
52
+ }
53
+ }, [isWeb]);
54
+ return dimensions;
55
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Hook that returns whether the user prefers reduced motion.
3
+ * Uses a shared singleton listener so multiple consumers don't create duplicate subscriptions.
4
+ */
5
+ export declare function useReducedMotion(): boolean;
@@ -0,0 +1,64 @@
1
+ import { useEffect, useState } from "react";
2
+ import { AccessibilityInfo, Platform } from "react-native";
3
+ let sharedValue = false;
4
+ let listenerCount = 0;
5
+ let subscription = null;
6
+ function startListening() {
7
+ if (Platform.OS === "web") {
8
+ // On web, use the media query
9
+ if (typeof window !== "undefined" && window.matchMedia) {
10
+ const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
11
+ sharedValue = mq.matches;
12
+ const handler = (e) => { sharedValue = e.matches; };
13
+ mq.addEventListener("change", handler);
14
+ subscription = { remove: () => mq.removeEventListener("change", handler) };
15
+ }
16
+ return;
17
+ }
18
+ AccessibilityInfo.isReduceMotionEnabled().then((enabled) => {
19
+ sharedValue = enabled;
20
+ });
21
+ const sub = AccessibilityInfo.addEventListener("reduceMotionChanged", (enabled) => {
22
+ sharedValue = enabled;
23
+ });
24
+ subscription = sub;
25
+ }
26
+ function stopListening() {
27
+ subscription?.remove();
28
+ subscription = null;
29
+ }
30
+ /**
31
+ * Hook that returns whether the user prefers reduced motion.
32
+ * Uses a shared singleton listener so multiple consumers don't create duplicate subscriptions.
33
+ */
34
+ export function useReducedMotion() {
35
+ const [reduceMotion, setReduceMotion] = useState(sharedValue);
36
+ useEffect(() => {
37
+ listenerCount++;
38
+ if (listenerCount === 1) {
39
+ startListening();
40
+ }
41
+ // Re-read current value on mount (may have changed since last render)
42
+ if (Platform.OS === "web") {
43
+ if (typeof window !== "undefined" && window.matchMedia) {
44
+ const current = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
45
+ setReduceMotion(current);
46
+ }
47
+ }
48
+ else {
49
+ AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion);
50
+ }
51
+ // Poll the shared value to pick up changes (lightweight — only boolean comparison)
52
+ const interval = setInterval(() => {
53
+ setReduceMotion((prev) => (prev !== sharedValue ? sharedValue : prev));
54
+ }, 500);
55
+ return () => {
56
+ clearInterval(interval);
57
+ listenerCount--;
58
+ if (listenerCount === 0) {
59
+ stopListening();
60
+ }
61
+ };
62
+ }, []);
63
+ return reduceMotion;
64
+ }
@@ -0,0 +1,12 @@
1
+ interface LoadResourcesResult {
2
+ loaded: boolean;
3
+ error: Error | null;
4
+ }
5
+ /**
6
+ * Loads essential app resources on startup.
7
+ *
8
+ * The UI package does not bundle font files. Web loads Lato from Google Fonts;
9
+ * native platforms use their system sans-serif fallback.
10
+ */
11
+ export declare const useResources: () => LoadResourcesResult;
12
+ export {};
@@ -0,0 +1,56 @@
1
+ import { useEffect, useState } from "react";
2
+ import * as Font from "expo-font";
3
+ import Feather from "@expo/vector-icons/Feather";
4
+ import { Platform } from "react-native";
5
+ const LATO_STYLESHEET_ID = "mrmeg-expo-ui-lato";
6
+ const LATO_STYLESHEET_URL = "https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap";
7
+ function ensureWebFontStylesheet() {
8
+ if (Platform.OS !== "web" || typeof document === "undefined") {
9
+ return Promise.resolve();
10
+ }
11
+ if (document.getElementById(LATO_STYLESHEET_ID)) {
12
+ return Promise.resolve();
13
+ }
14
+ return new Promise((resolve, reject) => {
15
+ const link = document.createElement("link");
16
+ link.id = LATO_STYLESHEET_ID;
17
+ link.rel = "stylesheet";
18
+ link.href = LATO_STYLESHEET_URL;
19
+ link.onload = () => resolve();
20
+ link.onerror = () => reject(new Error("Lato stylesheet failed to load"));
21
+ document.head.appendChild(link);
22
+ });
23
+ }
24
+ /**
25
+ * Loads essential app resources on startup.
26
+ *
27
+ * The UI package does not bundle font files. Web loads Lato from Google Fonts;
28
+ * native platforms use their system sans-serif fallback.
29
+ */
30
+ export const useResources = () => {
31
+ const [loaded, setLoaded] = useState(false);
32
+ const [error, setError] = useState(null);
33
+ useEffect(() => {
34
+ async function loadResourcesAndDataAsync() {
35
+ try {
36
+ const fontPromise = Promise.all([
37
+ Font.loadAsync(Feather.font),
38
+ ensureWebFontStylesheet(),
39
+ ]);
40
+ // Timeout after 5 seconds — proceed with system fallback fonts
41
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Font loading timed out after 5s")), 5000));
42
+ await Promise.race([fontPromise, timeoutPromise]);
43
+ }
44
+ catch (e) {
45
+ const error = e instanceof Error ? e : new Error(String(e));
46
+ console.warn("Font loading issue (proceeding with fallback):", error.message);
47
+ setError(error);
48
+ }
49
+ finally {
50
+ setLoaded(true);
51
+ }
52
+ }
53
+ loadResourcesAndDataAsync();
54
+ }, []);
55
+ return { loaded, error };
56
+ };
@@ -0,0 +1,57 @@
1
+ interface ScalePressOptions {
2
+ /**
3
+ * Scale value when pressed (1 = no change, 0.97 = subtle, 0.93 = more pronounced)
4
+ * @default 0.97
5
+ */
6
+ scaleTo?: number;
7
+ /**
8
+ * Whether to fire haptic feedback on press
9
+ * @default true
10
+ */
11
+ haptic?: boolean;
12
+ /**
13
+ * Spring damping for bounce-back
14
+ * @default 20
15
+ */
16
+ damping?: number;
17
+ /**
18
+ * Spring stiffness
19
+ * @default 300
20
+ */
21
+ stiffness?: number;
22
+ /**
23
+ * Whether the component is disabled (skips animation)
24
+ * @default false
25
+ */
26
+ disabled?: boolean;
27
+ }
28
+ /**
29
+ * Hook for press-feedback scale animation using Reanimated.
30
+ *
31
+ * Returns an animated style and onPressIn/onPressOut handlers to spread onto a Pressable.
32
+ * Respects reduced motion preferences.
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * const { animatedStyle, pressHandlers } = useScalePress();
37
+ *
38
+ * <Animated.View style={animatedStyle}>
39
+ * <Pressable {...pressHandlers} onPress={handlePress}>
40
+ * <Text>Press me</Text>
41
+ * </Pressable>
42
+ * </Animated.View>
43
+ * ```
44
+ */
45
+ export declare function useScalePress(options?: ScalePressOptions): {
46
+ animatedStyle: {
47
+ transform: {
48
+ scale: number;
49
+ }[];
50
+ };
51
+ pressHandlers: {
52
+ onPressIn: () => void;
53
+ onPressOut: () => void;
54
+ };
55
+ scale: import("react-native-reanimated").SharedValue<number>;
56
+ };
57
+ export {};
@@ -0,0 +1,55 @@
1
+ import { useCallback } from "react";
2
+ import { useSharedValue, useAnimatedStyle, withSpring, withTiming, useReducedMotion, } from "react-native-reanimated";
3
+ import { hapticLight } from "../lib/haptics";
4
+ /**
5
+ * Hook for press-feedback scale animation using Reanimated.
6
+ *
7
+ * Returns an animated style and onPressIn/onPressOut handlers to spread onto a Pressable.
8
+ * Respects reduced motion preferences.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * const { animatedStyle, pressHandlers } = useScalePress();
13
+ *
14
+ * <Animated.View style={animatedStyle}>
15
+ * <Pressable {...pressHandlers} onPress={handlePress}>
16
+ * <Text>Press me</Text>
17
+ * </Pressable>
18
+ * </Animated.View>
19
+ * ```
20
+ */
21
+ export function useScalePress(options = {}) {
22
+ const { scaleTo = 0.97, haptic = true, damping = 20, stiffness = 300, disabled = false, } = options;
23
+ const reduceMotion = useReducedMotion();
24
+ const scale = useSharedValue(1);
25
+ const onPressIn = useCallback(() => {
26
+ if (disabled)
27
+ return;
28
+ if (haptic)
29
+ hapticLight();
30
+ if (reduceMotion) {
31
+ scale.value = withTiming(scaleTo, { duration: 0 });
32
+ }
33
+ else {
34
+ scale.value = withSpring(scaleTo, { damping, stiffness });
35
+ }
36
+ }, [disabled, haptic, reduceMotion, scale, scaleTo, damping, stiffness]);
37
+ const onPressOut = useCallback(() => {
38
+ if (disabled)
39
+ return;
40
+ if (reduceMotion) {
41
+ scale.value = withTiming(1, { duration: 0 });
42
+ }
43
+ else {
44
+ scale.value = withSpring(1, { damping, stiffness });
45
+ }
46
+ }, [disabled, reduceMotion, scale, damping, stiffness]);
47
+ const animatedStyle = useAnimatedStyle(() => ({
48
+ transform: [{ scale: scale.value }],
49
+ }));
50
+ return {
51
+ animatedStyle,
52
+ pressHandlers: { onPressIn, onPressOut },
53
+ scale,
54
+ };
55
+ }
@@ -0,0 +1,67 @@
1
+ type EntranceType = "fade" | "fadeSlideUp" | "fadeSlideDown" | "scale";
2
+ interface StaggeredEntranceOptions {
3
+ /**
4
+ * Type of entrance animation
5
+ * @default "fadeSlideUp"
6
+ */
7
+ type?: EntranceType;
8
+ /**
9
+ * Delay before this item starts animating (ms).
10
+ * Use index * staggerMs for staggered lists.
11
+ * @default 0
12
+ */
13
+ delay?: number;
14
+ /**
15
+ * Duration of the entrance animation (ms)
16
+ * @default 200
17
+ */
18
+ duration?: number;
19
+ /**
20
+ * Slide distance in pixels (for fadeSlideUp/fadeSlideDown)
21
+ * @default 8
22
+ */
23
+ slideDistance?: number;
24
+ /**
25
+ * Initial scale (for scale type)
26
+ * @default 0.95
27
+ */
28
+ initialScale?: number;
29
+ }
30
+ /**
31
+ * Hook for entrance animations with stagger support using Reanimated.
32
+ *
33
+ * Returns an animated style to apply to an Animated.View.
34
+ * Respects reduced motion preferences.
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * // Single entrance
39
+ * const entranceStyle = useStaggeredEntrance({ type: "fadeSlideUp" });
40
+ * <Animated.View style={entranceStyle}>...</Animated.View>
41
+ *
42
+ * // Staggered list
43
+ * {items.map((item, i) => {
44
+ * const style = useStaggeredEntrance({ delay: i * 50 });
45
+ * return <Animated.View key={item.id} style={style}>...</Animated.View>;
46
+ * })}
47
+ * ```
48
+ */
49
+ export declare function useStaggeredEntrance(options?: StaggeredEntranceOptions): {
50
+ opacity: number;
51
+ transform?: undefined;
52
+ } | {
53
+ opacity: number;
54
+ transform: {
55
+ translateY: number;
56
+ }[];
57
+ } | {
58
+ opacity: number;
59
+ transform: {
60
+ scale: number;
61
+ }[];
62
+ };
63
+ /**
64
+ * Convenience constant: default stagger delay between items (ms)
65
+ */
66
+ export declare const STAGGER_DELAY = 30;
67
+ export {};
@@ -0,0 +1,74 @@
1
+ import { useEffect } from "react";
2
+ import { useSharedValue, useAnimatedStyle, withTiming, withSpring, withDelay, useReducedMotion, Easing, } from "react-native-reanimated";
3
+ /**
4
+ * Hook for entrance animations with stagger support using Reanimated.
5
+ *
6
+ * Returns an animated style to apply to an Animated.View.
7
+ * Respects reduced motion preferences.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * // Single entrance
12
+ * const entranceStyle = useStaggeredEntrance({ type: "fadeSlideUp" });
13
+ * <Animated.View style={entranceStyle}>...</Animated.View>
14
+ *
15
+ * // Staggered list
16
+ * {items.map((item, i) => {
17
+ * const style = useStaggeredEntrance({ delay: i * 50 });
18
+ * return <Animated.View key={item.id} style={style}>...</Animated.View>;
19
+ * })}
20
+ * ```
21
+ */
22
+ export function useStaggeredEntrance(options = {}) {
23
+ const { type = "fadeSlideUp", delay = 0, duration = 200, slideDistance = 8, initialScale = 0.95, } = options;
24
+ const reduceMotion = useReducedMotion();
25
+ const opacity = useSharedValue(reduceMotion ? 1 : 0);
26
+ const translateY = useSharedValue(reduceMotion
27
+ ? 0
28
+ : type === "fadeSlideUp"
29
+ ? slideDistance
30
+ : type === "fadeSlideDown"
31
+ ? -slideDistance
32
+ : 0);
33
+ const scale = useSharedValue(reduceMotion ? 1 : type === "scale" ? initialScale : 1);
34
+ useEffect(() => {
35
+ if (reduceMotion) {
36
+ opacity.value = 1;
37
+ translateY.value = 0;
38
+ scale.value = 1;
39
+ return;
40
+ }
41
+ const timingConfig = {
42
+ duration,
43
+ easing: Easing.out(Easing.cubic),
44
+ };
45
+ opacity.value = withDelay(delay, withTiming(1, timingConfig));
46
+ if (type === "fadeSlideUp" || type === "fadeSlideDown") {
47
+ translateY.value = withDelay(delay, withTiming(0, timingConfig));
48
+ }
49
+ if (type === "scale") {
50
+ scale.value = withDelay(delay, withSpring(1, { damping: 14, stiffness: 250 }));
51
+ }
52
+ }, [reduceMotion]);
53
+ const animatedStyle = useAnimatedStyle(() => {
54
+ if (type === "fade") {
55
+ return { opacity: opacity.value };
56
+ }
57
+ if (type === "fadeSlideUp" || type === "fadeSlideDown") {
58
+ return {
59
+ opacity: opacity.value,
60
+ transform: [{ translateY: translateY.value }],
61
+ };
62
+ }
63
+ // scale
64
+ return {
65
+ opacity: opacity.value,
66
+ transform: [{ scale: scale.value }],
67
+ };
68
+ });
69
+ return animatedStyle;
70
+ }
71
+ /**
72
+ * Convenience constant: default stagger delay between items (ms)
73
+ */
74
+ export const STAGGER_DELAY = 30;
@@ -0,0 +1,88 @@
1
+ import { Colors } from "../constants/colors";
2
+ import { ViewStyle, StyleSheet } from "react-native";
3
+ import { spacing as spacingConstants } from "../constants/spacing";
4
+ type ShadowType = "base" | "soft" | "sharp" | "subtle";
5
+ interface ExtendedColorScheme {
6
+ theme: Colors["light" | "dark"];
7
+ scheme: "light" | "dark";
8
+ getShadowStyle: (type: ShadowType) => ViewStyle;
9
+ getContrastingColor: (backgroundColor: string, color1?: string, color2?: string) => string;
10
+ getTextColorForBackground: (backgroundColor: string) => "light" | "dark";
11
+ withAlpha: (color: string, alpha: number) => string;
12
+ getContrastRatio: (color1: string, color2: string) => number;
13
+ }
14
+ /**
15
+ * useTheme
16
+ *
17
+ * Provides access to app colors, theme styles, and utilities for color contrast.
18
+ * Includes helpers to determine readable text color for any background.
19
+ *
20
+ * Returns:
21
+ * - theme: active theme colors (light or dark)
22
+ * - scheme: "light" | "dark"
23
+ * - getShadowStyle(type): returns cross-platform shadow style object
24
+ * - getContrastingColor(bg, color1?, color2?): pick best contrast of two options
25
+ * - getTextColorForBackground(bg): returns "light" or "dark"
26
+ * - withAlpha(color, alpha): adds transparency
27
+ * - getContrastRatio(color1, color2): returns numeric WCAG contrast ratio
28
+ *
29
+ * Examples:
30
+ * - getTextColorForBackground("#000") → "light"
31
+ * - getContrastingColor("#f4f4f4", "#222", "#fff") → "#222"
32
+ * - withAlpha("#336699", 0.6) → "rgba(51,102,153,0.6)"
33
+ * - getShadowStyle('base') → { shadowColor, shadowOffset, ... }
34
+ */
35
+ export declare function useTheme(): ExtendedColorScheme & {
36
+ toggleTheme: () => void;
37
+ setTheme: (theme: "system" | "light" | "dark") => void;
38
+ currentTheme: "system" | "light" | "dark";
39
+ };
40
+ /**
41
+ * Style factory context passed to the createStyles callback
42
+ */
43
+ interface StyleContext {
44
+ theme: Colors["light" | "dark"];
45
+ spacing: typeof spacingConstants;
46
+ }
47
+ /**
48
+ * Return type for useStyles hook
49
+ */
50
+ type UseStylesReturn<T extends StyleSheet.NamedStyles<T>> = {
51
+ styles: T;
52
+ theme: Colors["light" | "dark"];
53
+ spacing: typeof spacingConstants;
54
+ } & Omit<ReturnType<typeof useTheme>, "theme">;
55
+ /**
56
+ * useStyles
57
+ *
58
+ * A hook that combines useTheme with StyleSheet.create for theme-aware styling.
59
+ * Provides access to theme colors and spacing constants within the style factory.
60
+ *
61
+ * @param factory - A function that receives { theme, spacing } and returns style definitions
62
+ * @returns { styles, theme, spacing, ...themeUtilities }
63
+ *
64
+ * @example
65
+ * ```tsx
66
+ * function MyComponent() {
67
+ * const { styles, theme } = useStyles(({ theme, spacing }) => ({
68
+ * container: {
69
+ * backgroundColor: theme.colors.background,
70
+ * padding: spacing.md,
71
+ * borderRadius: spacing.radiusMd,
72
+ * },
73
+ * text: {
74
+ * color: theme.colors.textPrimary,
75
+ * fontSize: 16,
76
+ * },
77
+ * }));
78
+ *
79
+ * return (
80
+ * <View style={styles.container}>
81
+ * <Text style={styles.text}>Hello</Text>
82
+ * </View>
83
+ * );
84
+ * }
85
+ * ```
86
+ */
87
+ export declare function useStyles<T extends StyleSheet.NamedStyles<T>>(factory: (context: StyleContext) => T): UseStylesReturn<T>;
88
+ export {};