@mrmeg/expo-ui 0.6.1 → 0.7.1

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 (55) hide show
  1. package/LLM_USAGE.md +9 -6
  2. package/README.md +11 -7
  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 +106 -27
  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/globalUIStore.d.ts +23 -16
  52. package/dist/state/themeColorScope.js +3 -3
  53. package/llms-full.md +5 -6
  54. package/llms.txt +2 -2
  55. package/package.json +8 -6
@@ -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
  /**
@@ -5,26 +5,33 @@
5
5
  * Primarily used to trigger and dismiss the `Notification` component globally.
6
6
  *
7
7
  * Methods:
8
- * - show({ type, title, messages, duration, loading }): displays a notification
8
+ * - show({ type, title, messages, duration, loading, action }): displays a notification
9
9
  * - hide(): hides the current notification
10
10
  *
11
11
  * Recommended: wrap in hooks or utility functions for cleaner usage across components.
12
12
  */
13
- type State = {
14
- alert: {
15
- show: boolean;
16
- type: "error" | "success" | "info" | "warning";
17
- messages?: string[];
18
- title?: string;
19
- duration?: number;
20
- loading?: boolean;
21
- /** Where to display the notification */
22
- position?: "top" | "bottom";
23
- } | null;
13
+ export type GlobalNotificationType = "error" | "success" | "info" | "warning";
14
+ export type GlobalNotificationPosition = "top" | "bottom";
15
+ export type GlobalNotificationAction = {
16
+ label: string;
17
+ onPress: () => void;
24
18
  };
25
- type Actions = {
26
- show: (alert: Omit<NonNullable<State["alert"]>, "show">) => void;
19
+ export type GlobalNotificationAlert = {
20
+ show: boolean;
21
+ type: GlobalNotificationType;
22
+ title?: string;
23
+ messages?: string[];
24
+ duration?: number;
25
+ loading?: boolean;
26
+ /** Where to display the notification */
27
+ position?: GlobalNotificationPosition;
28
+ action?: GlobalNotificationAction;
29
+ };
30
+ export type GlobalUIState = {
31
+ alert: GlobalNotificationAlert | null;
32
+ };
33
+ export type GlobalUIActions = {
34
+ show: (alert: Omit<GlobalNotificationAlert, "show">) => void;
27
35
  hide: () => void;
28
36
  };
29
- export declare const globalUIStore: import("zustand").UseBoundStore<import("zustand").StoreApi<State & Actions>>;
30
- export {};
37
+ export declare const globalUIStore: import("zustand").UseBoundStore<import("zustand").StoreApi<GlobalUIState & GlobalUIActions>>;
@@ -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. |
@@ -102,7 +101,7 @@ Use this catalog before creating a new app-local primitive.
102
101
  | `InputOTP` | `@mrmeg/expo-ui/components` | Verification code entry | Prefer over manually managed text input groups. |
103
102
  | `Label` | `@mrmeg/expo-ui/components` | Accessible form labels | Use with package form controls. |
104
103
  | `MaxWidthContainer` | `@mrmeg/expo-ui/components` | Centered responsive width | Use for web and tablet constrained layouts. |
105
- | `Notification` | `@mrmeg/expo-ui/components` | Global toast surface | Trigger through `globalUIStore` with root `UIProvider`. |
104
+ | `Notification` | `@mrmeg/expo-ui/components` | Global toast surface | Trigger through `globalUIStore` with root `UIProvider`; optional actions dismiss after press. |
106
105
  | `Popover` | `@mrmeg/expo-ui/components` | Anchored contextual content | Requires root `UIProvider` portal setup. |
107
106
  | `Progress` | `@mrmeg/expo-ui/components` | Determinate or indeterminate progress | Prefer over layout-shifting spinners for progress regions. |
108
107
  | `RadioGroup` | `@mrmeg/expo-ui/components` | Small mutually exclusive choices | Use `Select` for longer option sets. |
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.1",
4
4
  "private": false,
5
5
  "description": "Reusable Expo and React Native UI primitives for MrMeg projects.",
6
6
  "keywords": [
@@ -69,6 +69,10 @@
69
69
  "types": "./dist/state/index.d.ts",
70
70
  "default": "./dist/state/index.js"
71
71
  },
72
+ "./state/*": {
73
+ "types": "./dist/state/*.d.ts",
74
+ "default": "./dist/state/*.js"
75
+ },
72
76
  "./lib": {
73
77
  "types": "./dist/lib/index.d.ts",
74
78
  "default": "./dist/lib/index.js"
@@ -76,8 +80,9 @@
76
80
  },
77
81
  "scripts": {
78
82
  "typecheck": "tsc --noEmit -p tsconfig.json",
79
- "test": "jest --config ../../jest.config.js packages/ui/src --runInBand --watchman=false",
80
- "build": "rm -rf dist && tsc -p tsconfig.build.json && node ../../scripts/fix-ui-package-esm.mjs",
83
+ "test": "jest --config ../../jest.config.js packages/ui/src --runInBand --watchman=false && bun run check:forbidden-imports",
84
+ "build": "rm -rf dist && tsc -p tsconfig.build.json && node ../../scripts/fix-ui-package-esm.mjs && bun run check:forbidden-imports",
85
+ "check:forbidden-imports": "node ../../scripts/check-ui-forbidden-imports.mjs",
81
86
  "publish:dry-run": "bun pm pack --dry-run"
82
87
  },
83
88
  "dependencies": {
@@ -109,12 +114,9 @@
109
114
  "react": ">=19.2.0 <20.0.0",
110
115
  "react-native": ">=0.83.0 <0.86.0",
111
116
  "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
117
  "react-native-safe-area-context": ">=5.6.0 <6.0.0",
115
118
  "react-native-screens": ">=4.23.0 <5.0.0",
116
119
  "react-native-web": ">=0.21.0 <0.22.0",
117
- "react-native-worklets": ">=0.7.0 <0.9.0",
118
120
  "zustand": ">=5.0.0 <6.0.0"
119
121
  },
120
122
  "devDependencies": {