@mrmeg/expo-ui 0.6.1 → 0.7.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 (54) hide show
  1. package/LLM_USAGE.md +4 -5
  2. package/README.md +6 -6
  3. package/dist/components/Accordion.js +21 -16
  4. package/dist/components/AnimatedView.d.ts +1 -1
  5. package/dist/components/AnimatedView.js +2 -2
  6. package/dist/components/Badge.d.ts +3 -2
  7. package/dist/components/Badge.js +4 -3
  8. package/dist/components/BottomSheet.js +31 -29
  9. package/dist/components/BottomSheetKeyboard.d.ts +7 -0
  10. package/dist/components/BottomSheetKeyboard.js +35 -0
  11. package/dist/components/Button.d.ts +55 -13
  12. package/dist/components/Button.js +72 -28
  13. package/dist/components/Card.js +8 -10
  14. package/dist/components/Checkbox.js +22 -25
  15. package/dist/components/Collapsible.js +3 -7
  16. package/dist/components/Dialog.js +1 -1
  17. package/dist/components/DismissKeyboard.js +3 -3
  18. package/dist/components/Drawer.js +21 -10
  19. package/dist/components/DropdownMenu.d.ts +3 -2
  20. package/dist/components/DropdownMenu.js +29 -29
  21. package/dist/components/EmptyState.js +1 -1
  22. package/dist/components/InputOTP.js +16 -40
  23. package/dist/components/Notification.js +50 -25
  24. package/dist/components/Popover.js +1 -1
  25. package/dist/components/Progress.d.ts +2 -2
  26. package/dist/components/Progress.js +36 -34
  27. package/dist/components/RadioGroup.js +22 -20
  28. package/dist/components/Select.js +30 -20
  29. package/dist/components/Skeleton.js +6 -6
  30. package/dist/components/Slider.js +90 -97
  31. package/dist/components/StyledText.context.d.ts +6 -0
  32. package/dist/components/StyledText.context.js +5 -0
  33. package/dist/components/StyledText.d.ts +7 -58
  34. package/dist/components/StyledText.js +8 -28
  35. package/dist/components/Switch.js +30 -26
  36. package/dist/components/Tabs.d.ts +23 -3
  37. package/dist/components/Tabs.js +39 -17
  38. package/dist/components/TextInput.d.ts +6 -2
  39. package/dist/components/TextInput.js +6 -7
  40. package/dist/components/Toggle.js +12 -7
  41. package/dist/components/ToggleGroup.js +17 -11
  42. package/dist/components/Tooltip.js +1 -1
  43. package/dist/hooks/useDimensions.js +25 -26
  44. package/dist/hooks/useReduceMotion.d.ts +5 -1
  45. package/dist/hooks/useReduceMotion.js +46 -41
  46. package/dist/hooks/useResources.js +6 -1
  47. package/dist/hooks/useScalePress.d.ts +6 -5
  48. package/dist/hooks/useScalePress.js +25 -21
  49. package/dist/hooks/useStaggeredEntrance.d.ts +9 -8
  50. package/dist/hooks/useStaggeredEntrance.js +48 -21
  51. package/dist/state/themeColorScope.js +3 -3
  52. package/llms-full.md +4 -5
  53. package/llms.txt +2 -2
  54. package/package.json +1 -4
@@ -1,5 +1,5 @@
1
- import { useContext, useEffect, useState } from "react";
2
- import { Dimensions, Platform } from "react-native";
1
+ import { use, useEffect, useState } from "react";
2
+ import { Platform, useWindowDimensions } from "react-native";
3
3
  import { SsrViewportContext, SSR_VIEWPORT_DEFAULT_HEIGHT, } from "../state/SsrViewportContext.js";
4
4
  export const SCREEN_SIZES = {
5
5
  SMALL: 768,
@@ -50,35 +50,34 @@ function writeViewportCookie(width) {
50
50
  */
51
51
  export const useDimensions = () => {
52
52
  const isWeb = Platform.OS === "web";
53
- const ssrWidth = useContext(SsrViewportContext);
53
+ const ssrWidth = use(SsrViewportContext);
54
+ // Native reads come from useWindowDimensions, which subscribes to rotation /
55
+ // split-screen / resize and tears the listener down for us — no manual
56
+ // Dimensions.addEventListener to leak. On web we ignore it and drive layout
57
+ // from the SSR context + the resize listener below so hydration stays exact.
58
+ const native = useWindowDimensions();
54
59
  // Lazy initializer: both server and client first render compute identical
55
60
  // flags from the context value, so hydration matches.
56
61
  const [dimensions, setDimensions] = useState(() => calculateDimensionFlags(ssrWidth, SSR_VIEWPORT_DEFAULT_HEIGHT));
62
+ // Web: read the real viewport after mount and follow resize events. Keeping
63
+ // this in an effect (not render) preserves the SSR-matched first paint.
57
64
  useEffect(() => {
58
- const initialDimensions = isWeb
59
- ? { width: window.innerWidth, height: window.innerHeight }
60
- : Dimensions.get("window");
61
- const updateDimensions = (width, height) => {
62
- setDimensions(calculateDimensionFlags(width, height));
65
+ if (!isWeb)
66
+ return;
67
+ const syncFromWindow = () => {
68
+ setDimensions(calculateDimensionFlags(window.innerWidth, window.innerHeight));
69
+ writeViewportCookie(window.innerWidth);
70
+ };
71
+ syncFromWindow();
72
+ window.addEventListener("resize", syncFromWindow);
73
+ return () => {
74
+ window.removeEventListener("resize", syncFromWindow);
63
75
  };
64
- updateDimensions(initialDimensions.width, initialDimensions.height);
65
- if (isWeb) {
66
- writeViewportCookie(initialDimensions.width);
67
- const handleResize = () => {
68
- updateDimensions(window.innerWidth, window.innerHeight);
69
- writeViewportCookie(window.innerWidth);
70
- };
71
- window.addEventListener("resize", handleResize);
72
- return () => {
73
- window.removeEventListener("resize", handleResize);
74
- };
75
- }
76
- else {
77
- const onChange = ({ window }) => {
78
- updateDimensions(window.width, window.height);
79
- };
80
- Dimensions.addEventListener("change", onChange);
81
- }
82
76
  }, [isWeb]);
77
+ // Native: useWindowDimensions already reacts to changes; mirror it into our
78
+ // enriched flags. (On web `native` is unused — the effect above wins.)
79
+ if (!isWeb) {
80
+ return calculateDimensionFlags(native.width, native.height);
81
+ }
83
82
  return dimensions;
84
83
  };
@@ -1,5 +1,9 @@
1
1
  /**
2
2
  * Hook that returns whether the user prefers reduced motion.
3
- * Uses a shared singleton listener so multiple consumers don't create duplicate subscriptions.
3
+ *
4
+ * Backed by useSyncExternalStore so the value is read straight from a shared
5
+ * OS subscription — no mount-time setState, no polling, and a clean SSR
6
+ * snapshot that hydrates without a mismatch. A single singleton listener is
7
+ * shared across all consumers.
4
8
  */
5
9
  export declare function useReducedMotion(): boolean;
@@ -1,64 +1,69 @@
1
- import { useEffect, useState } from "react";
1
+ import { useSyncExternalStore } from "react";
2
2
  import { AccessibilityInfo, Platform } from "react-native";
3
3
  let sharedValue = false;
4
- let listenerCount = 0;
4
+ const listeners = new Set();
5
5
  let subscription = null;
6
+ function notify() {
7
+ for (const listener of listeners)
8
+ listener();
9
+ }
10
+ function setSharedValue(next) {
11
+ if (next === sharedValue)
12
+ return;
13
+ sharedValue = next;
14
+ notify();
15
+ }
6
16
  function startListening() {
7
17
  if (Platform.OS === "web") {
8
18
  // On web, use the media query
9
19
  if (typeof window !== "undefined" && window.matchMedia) {
10
20
  const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
11
- sharedValue = mq.matches;
12
- const handler = (e) => { sharedValue = e.matches; };
21
+ setSharedValue(mq.matches);
22
+ const handler = (e) => setSharedValue(e.matches);
13
23
  mq.addEventListener("change", handler);
14
24
  subscription = { remove: () => mq.removeEventListener("change", handler) };
15
25
  }
16
26
  return;
17
27
  }
18
- AccessibilityInfo.isReduceMotionEnabled().then((enabled) => {
19
- sharedValue = enabled;
20
- });
21
- const sub = AccessibilityInfo.addEventListener("reduceMotionChanged", (enabled) => {
22
- sharedValue = enabled;
23
- });
24
- subscription = sub;
28
+ AccessibilityInfo.isReduceMotionEnabled().then(setSharedValue);
29
+ subscription = AccessibilityInfo.addEventListener("reduceMotionChanged", setSharedValue);
25
30
  }
26
31
  function stopListening() {
27
32
  subscription?.remove();
28
33
  subscription = null;
29
34
  }
35
+ // useSyncExternalStore contract: register the consumer, lazily starting the
36
+ // shared OS subscription on the first listener and tearing it down after the
37
+ // last unmounts.
38
+ function subscribe(listener) {
39
+ listeners.add(listener);
40
+ if (listeners.size === 1) {
41
+ startListening();
42
+ }
43
+ return () => {
44
+ listeners.delete(listener);
45
+ if (listeners.size === 0) {
46
+ stopListening();
47
+ }
48
+ };
49
+ }
50
+ function getSnapshot() {
51
+ return sharedValue;
52
+ }
53
+ // The server can't know the user's accessibility preference; default to
54
+ // "motion allowed" so SSR and the first client render agree (the real value
55
+ // arrives via subscribe() after hydration).
56
+ function getServerSnapshot() {
57
+ return false;
58
+ }
30
59
  /**
31
60
  * Hook that returns whether the user prefers reduced motion.
32
- * Uses a shared singleton listener so multiple consumers don't create duplicate subscriptions.
61
+ *
62
+ * Backed by useSyncExternalStore so the value is read straight from a shared
63
+ * OS subscription — no mount-time setState, no polling, and a clean SSR
64
+ * snapshot that hydrates without a mismatch. A single singleton listener is
65
+ * shared across all consumers.
33
66
  */
34
67
  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;
68
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
64
69
  }
@@ -40,6 +40,7 @@ export const useResources = () => {
40
40
  const [loaded, setLoaded] = useState(false);
41
41
  const [error, setError] = useState(null);
42
42
  useEffect(() => {
43
+ let timeoutId;
43
44
  async function loadResourcesAndDataAsync() {
44
45
  try {
45
46
  const fontPromise = Promise.all([
@@ -47,7 +48,9 @@ export const useResources = () => {
47
48
  ensureWebFontStylesheet(),
48
49
  ]);
49
50
  // Timeout after 5 seconds — proceed with system fallback fonts
50
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Font loading timed out after 5s")), 5000));
51
+ const timeoutPromise = new Promise((_, reject) => {
52
+ timeoutId = setTimeout(() => reject(new Error("Font loading timed out after 5s")), 5000);
53
+ });
51
54
  await Promise.race([fontPromise, timeoutPromise]);
52
55
  }
53
56
  catch (e) {
@@ -56,10 +59,12 @@ export const useResources = () => {
56
59
  setError(error);
57
60
  }
58
61
  finally {
62
+ clearTimeout(timeoutId);
59
63
  setLoaded(true);
60
64
  }
61
65
  }
62
66
  loadResourcesAndDataAsync();
67
+ return () => clearTimeout(timeoutId);
63
68
  }, []);
64
69
  return { loaded, error };
65
70
  };
@@ -1,3 +1,4 @@
1
+ import { Animated } from "react-native";
1
2
  interface ScalePressOptions {
2
3
  /**
3
4
  * Scale value when pressed (1 = no change, 0.97 = subtle, 0.93 = more pronounced)
@@ -26,7 +27,7 @@ interface ScalePressOptions {
26
27
  disabled?: boolean;
27
28
  }
28
29
  /**
29
- * Hook for press-feedback scale animation using Reanimated.
30
+ * Hook for press-feedback scale animation using React Native Animated.
30
31
  *
31
32
  * Returns an animated style and onPressIn/onPressOut handlers to spread onto a Pressable.
32
33
  * Respects reduced motion preferences.
@@ -43,15 +44,15 @@ interface ScalePressOptions {
43
44
  * ```
44
45
  */
45
46
  export declare function useScalePress(options?: ScalePressOptions): {
46
- animatedStyle: import("react-native-reanimated/lib/typescript/hook/commonTypes").AnimatedStyleHandle<{
47
+ animatedStyle: {
47
48
  transform: {
48
- scale: number;
49
+ scale: Animated.Value;
49
50
  }[];
50
- }>;
51
+ };
51
52
  pressHandlers: {
52
53
  onPressIn: () => void;
53
54
  onPressOut: () => void;
54
55
  };
55
- scale: import("react-native-reanimated").SharedValue<number>;
56
+ scale: Animated.Value;
56
57
  };
57
58
  export {};
@@ -1,8 +1,9 @@
1
- import { useCallback } from "react";
2
- import { useSharedValue, useAnimatedStyle, withSpring, withTiming, useReducedMotion, } from "react-native-reanimated";
1
+ import { useCallback, useMemo, useRef } from "react";
2
+ import { Animated } from "react-native";
3
3
  import { hapticLight } from "../lib/haptics.js";
4
+ import { useReducedMotion } from "./useReduceMotion.js";
4
5
  /**
5
- * Hook for press-feedback scale animation using Reanimated.
6
+ * Hook for press-feedback scale animation using React Native Animated.
6
7
  *
7
8
  * Returns an animated style and onPressIn/onPressOut handlers to spread onto a Pressable.
8
9
  * Respects reduced motion preferences.
@@ -21,32 +22,35 @@ import { hapticLight } from "../lib/haptics.js";
21
22
  export function useScalePress(options = {}) {
22
23
  const { scaleTo = 0.97, haptic = true, damping = 20, stiffness = 300, disabled = false, } = options;
23
24
  const reduceMotion = useReducedMotion();
24
- const scale = useSharedValue(1);
25
+ const scale = useRef(new Animated.Value(1)).current;
26
+ const animateTo = useCallback((toValue) => {
27
+ scale.stopAnimation();
28
+ if (reduceMotion) {
29
+ scale.setValue(toValue);
30
+ return;
31
+ }
32
+ Animated.spring(scale, {
33
+ toValue,
34
+ damping,
35
+ stiffness,
36
+ useNativeDriver: true,
37
+ }).start();
38
+ }, [damping, reduceMotion, scale, stiffness]);
25
39
  const onPressIn = useCallback(() => {
26
40
  if (disabled)
27
41
  return;
28
42
  if (haptic)
29
43
  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]);
44
+ animateTo(scaleTo);
45
+ }, [animateTo, disabled, haptic, scaleTo]);
37
46
  const onPressOut = useCallback(() => {
38
47
  if (disabled)
39
48
  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
- }));
49
+ animateTo(1);
50
+ }, [animateTo, disabled]);
51
+ const animatedStyle = useMemo(() => ({
52
+ transform: [{ scale }],
53
+ }), [scale]);
50
54
  return {
51
55
  animatedStyle,
52
56
  pressHandlers: { onPressIn, onPressOut },
@@ -1,3 +1,4 @@
1
+ import { Animated } from "react-native";
1
2
  type EntranceType = "fade" | "fadeSlideUp" | "fadeSlideDown" | "scale";
2
3
  interface StaggeredEntranceOptions {
3
4
  /**
@@ -28,7 +29,7 @@ interface StaggeredEntranceOptions {
28
29
  initialScale?: number;
29
30
  }
30
31
  /**
31
- * Hook for entrance animations with stagger support using Reanimated.
32
+ * Hook for entrance animations with stagger support using React Native Animated.
32
33
  *
33
34
  * Returns an animated style to apply to an Animated.View.
34
35
  * Respects reduced motion preferences.
@@ -46,20 +47,20 @@ interface StaggeredEntranceOptions {
46
47
  * })}
47
48
  * ```
48
49
  */
49
- export declare function useStaggeredEntrance(options?: StaggeredEntranceOptions): import("react-native-reanimated/lib/typescript/hook/commonTypes").AnimatedStyleHandle<{
50
- opacity: number;
50
+ export declare function useStaggeredEntrance(options?: StaggeredEntranceOptions): {
51
+ opacity: Animated.Value;
51
52
  transform?: undefined;
52
53
  } | {
53
- opacity: number;
54
+ opacity: Animated.Value;
54
55
  transform: {
55
- translateY: number;
56
+ translateY: Animated.Value;
56
57
  }[];
57
58
  } | {
58
- opacity: number;
59
+ opacity: Animated.Value;
59
60
  transform: {
60
- scale: number;
61
+ scale: Animated.Value;
61
62
  }[];
62
- }>;
63
+ };
63
64
  /**
64
65
  * Convenience constant: default stagger delay between items (ms)
65
66
  */
@@ -1,7 +1,8 @@
1
- import { useEffect } from "react";
2
- import { useSharedValue, useAnimatedStyle, withTiming, withSpring, withDelay, useReducedMotion, Easing, } from "react-native-reanimated";
1
+ import { useEffect, useMemo, useRef } from "react";
2
+ import { Animated, Easing } from "react-native";
3
+ import { useReducedMotion } from "./useReduceMotion.js";
3
4
  /**
4
- * Hook for entrance animations with stagger support using Reanimated.
5
+ * Hook for entrance animations with stagger support using React Native Animated.
5
6
  *
6
7
  * Returns an animated style to apply to an Animated.View.
7
8
  * Respects reduced motion preferences.
@@ -22,50 +23,76 @@ import { useSharedValue, useAnimatedStyle, withTiming, withSpring, withDelay, us
22
23
  export function useStaggeredEntrance(options = {}) {
23
24
  const { type = "fadeSlideUp", delay = 0, duration = 200, slideDistance = 8, initialScale = 0.95, } = options;
24
25
  const reduceMotion = useReducedMotion();
25
- const opacity = useSharedValue(reduceMotion ? 1 : 0);
26
- const translateY = useSharedValue(reduceMotion
26
+ const opacity = useRef(new Animated.Value(reduceMotion ? 1 : 0)).current;
27
+ const translateY = useRef(new Animated.Value(reduceMotion
27
28
  ? 0
28
29
  : type === "fadeSlideUp"
29
30
  ? slideDistance
30
31
  : type === "fadeSlideDown"
31
32
  ? -slideDistance
32
- : 0);
33
- const scale = useSharedValue(reduceMotion ? 1 : type === "scale" ? initialScale : 1);
33
+ : 0)).current;
34
+ const scale = useRef(new Animated.Value(reduceMotion ? 1 : type === "scale" ? initialScale : 1)).current;
34
35
  useEffect(() => {
35
36
  if (reduceMotion) {
36
- opacity.value = 1;
37
- translateY.value = 0;
38
- scale.value = 1;
37
+ opacity.setValue(1);
38
+ translateY.setValue(0);
39
+ scale.setValue(1);
39
40
  return;
40
41
  }
42
+ opacity.setValue(0);
43
+ translateY.setValue(type === "fadeSlideUp"
44
+ ? slideDistance
45
+ : type === "fadeSlideDown"
46
+ ? -slideDistance
47
+ : 0);
48
+ scale.setValue(type === "scale" ? initialScale : 1);
41
49
  const timingConfig = {
42
50
  duration,
43
51
  easing: Easing.out(Easing.cubic),
52
+ useNativeDriver: true,
44
53
  };
45
- opacity.value = withDelay(delay, withTiming(1, timingConfig));
54
+ const animations = [
55
+ Animated.timing(opacity, {
56
+ toValue: 1,
57
+ ...timingConfig,
58
+ }),
59
+ ];
46
60
  if (type === "fadeSlideUp" || type === "fadeSlideDown") {
47
- translateY.value = withDelay(delay, withTiming(0, timingConfig));
61
+ animations.push(Animated.timing(translateY, {
62
+ toValue: 0,
63
+ ...timingConfig,
64
+ }));
48
65
  }
49
66
  if (type === "scale") {
50
- scale.value = withDelay(delay, withSpring(1, { damping: 14, stiffness: 250 }));
67
+ animations.push(Animated.spring(scale, {
68
+ toValue: 1,
69
+ damping: 14,
70
+ stiffness: 250,
71
+ useNativeDriver: true,
72
+ }));
51
73
  }
52
- }, [reduceMotion]);
53
- const animatedStyle = useAnimatedStyle(() => {
74
+ const animation = delay > 0
75
+ ? Animated.sequence([Animated.delay(delay), Animated.parallel(animations)])
76
+ : Animated.parallel(animations);
77
+ animation.start();
78
+ return () => animation.stop();
79
+ }, [delay, duration, initialScale, opacity, reduceMotion, scale, slideDistance, translateY, type]);
80
+ const animatedStyle = useMemo(() => {
54
81
  if (type === "fade") {
55
- return { opacity: opacity.value };
82
+ return { opacity };
56
83
  }
57
84
  if (type === "fadeSlideUp" || type === "fadeSlideDown") {
58
85
  return {
59
- opacity: opacity.value,
60
- transform: [{ translateY: translateY.value }],
86
+ opacity,
87
+ transform: [{ translateY }],
61
88
  };
62
89
  }
63
90
  // scale
64
91
  return {
65
- opacity: opacity.value,
66
- transform: [{ scale: scale.value }],
92
+ opacity,
93
+ transform: [{ scale }],
67
94
  };
68
- });
95
+ }, [opacity, scale, translateY, type]);
69
96
  return animatedStyle;
70
97
  }
71
98
  /**
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useContext, useMemo } from "react";
2
+ import { createContext, use, useMemo } from "react";
3
3
  /**
4
4
  * Per-subtree color overrides, layered on top of the global theme by `useTheme`.
5
5
  *
@@ -13,7 +13,7 @@ import { createContext, useContext, useMemo } from "react";
13
13
  const ThemeColorScopeContext = createContext(null);
14
14
  /** Read the active scoped override (null when not inside a scope). */
15
15
  export function useThemeColorScope() {
16
- return useContext(ThemeColorScopeContext);
16
+ return use(ThemeColorScopeContext);
17
17
  }
18
18
  // Nested scopes layer: the inner scope's keys win, the outer scope's fill in.
19
19
  function mergeScopes(parent, next) {
@@ -25,7 +25,7 @@ function mergeScopes(parent, next) {
25
25
  };
26
26
  }
27
27
  export function ThemeColorScope({ colors, children, }) {
28
- const parent = useContext(ThemeColorScopeContext);
28
+ const parent = use(ThemeColorScopeContext);
29
29
  const value = useMemo(() => mergeScopes(parent, colors), [parent, colors]);
30
30
  return (_jsx(ThemeColorScopeContext.Provider, { value: value, children: children }));
31
31
  }
package/llms-full.md CHANGED
@@ -36,10 +36,9 @@ the root when the app uses package feedback or overlay components.
36
36
  `BottomSheet`, `Drawer`, `DropdownMenu`, `Popover`, `SelectContent`,
37
37
  `Tooltip`, or `globalUIStore` notifications.
38
38
 
39
- On native, `BottomSheet.Content` composes its sheet transform with
40
- `react-native-keyboard-controller` keyboard animation values. Mount that
41
- library's `KeyboardProvider` near the app root before using bottom sheets with
42
- text inputs, or pass `avoidKeyboard={false}` for sheets that should not move.
39
+ On native, `BottomSheet.Content` composes its sheet transform with React Native
40
+ keyboard event values. Pass `avoidKeyboard={false}` for sheets that should not
41
+ move.
43
42
 
44
43
  i18n is optional. Plain children and `text` props work without `i18next` or
45
44
  `react-i18next`. Use `configureExpoUiI18n()` only when a consuming app wants
@@ -87,7 +86,7 @@ Use this catalog before creating a new app-local primitive.
87
86
  | `Alert` | `@mrmeg/expo-ui/components` | Cross-platform imperative alerts | Avoid direct `window.alert` and duplicated native/web branching. |
88
87
  | `AnimatedView` | `@mrmeg/expo-ui/components` | Entrance and visibility animation | Keep simple reveal effects in the package wrapper. |
89
88
  | `Badge` | `@mrmeg/expo-ui/components` | Short status labels | Prefer over custom pill views. |
90
- | `BottomSheet` | `@mrmeg/expo-ui/components` | Mobile-first modal sheets | Requires root `UIProvider`; native text-input sheets also require `KeyboardProvider`. |
89
+ | `BottomSheet` | `@mrmeg/expo-ui/components` | Mobile-first modal sheets | Requires root `UIProvider`; text-input sheets can disable `avoidKeyboard`. |
91
90
  | `Button` | `@mrmeg/expo-ui/components` | Commands and CTAs | Use `preset`, not `variant`; visible heights are compact. |
92
91
  | `Card` | `@mrmeg/expo-ui/components` | Individual framed content groups | Do not wrap whole page sections in cards. |
93
92
  | `Checkbox` | `@mrmeg/expo-ui/components` | Boolean selection in forms or lists | Prefer over custom checkmark controls. |
package/llms.txt CHANGED
@@ -25,8 +25,8 @@ Call useResources() once near the Expo app root. Mount UIProvider once near the
25
25
  root before using package notifications or overlay primitives: Dialog,
26
26
  AlertDialog, BottomSheet, Drawer, DropdownMenu, Popover, SelectContent, or
27
27
  Tooltip.
28
- On native, mount react-native-keyboard-controller's KeyboardProvider before
29
- using BottomSheet.Content with text inputs; avoidKeyboard defaults to true.
28
+ BottomSheet.Content uses React Native keyboard events for text inputs;
29
+ avoidKeyboard defaults to true.
30
30
 
31
31
  Use useTheme(), useStyles(), semantic tokens, StyledText, and package controls.
32
32
  Do not add app-local Appearance or matchMedia listeners for package theme sync.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrmeg/expo-ui",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "description": "Reusable Expo and React Native UI primitives for MrMeg projects.",
6
6
  "keywords": [
@@ -109,12 +109,9 @@
109
109
  "react": ">=19.2.0 <20.0.0",
110
110
  "react-native": ">=0.83.0 <0.86.0",
111
111
  "react-native-gesture-handler": ">=2.30.0 <2.32.0",
112
- "react-native-keyboard-controller": ">=1.20.0 <2.0.0",
113
- "react-native-reanimated": ">=4.2.0 <5.0.0",
114
112
  "react-native-safe-area-context": ">=5.6.0 <6.0.0",
115
113
  "react-native-screens": ">=4.23.0 <5.0.0",
116
114
  "react-native-web": ">=0.21.0 <0.22.0",
117
- "react-native-worklets": ">=0.7.0 <0.9.0",
118
115
  "zustand": ">=5.0.0 <6.0.0"
119
116
  },
120
117
  "devDependencies": {