@mrmeg/expo-ui 0.1.10 → 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matt Megenhardt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/LLM_USAGE.md CHANGED
@@ -68,6 +68,11 @@ export function RootLayout() {
68
68
  `AlertDialog`, `BottomSheet`, `Drawer`, `DropdownMenu`, `Popover`,
69
69
  `SelectContent`, or `Tooltip`.
70
70
 
71
+ On native, `BottomSheet.Content` avoids the soft keyboard by default through
72
+ `react-native-keyboard-controller`. Mount that library's `KeyboardProvider`
73
+ near the app root before using bottom sheets with text inputs, or pass
74
+ `avoidKeyboard={false}` to opt out for a specific sheet.
75
+
71
76
  i18n is optional. Do not add app-level i18n setup just to use this package.
72
77
  Plain children and `text` props work without `i18next` or `react-i18next`.
73
78
  `tx` props render fallback text when provided and otherwise render the key
@@ -154,7 +159,7 @@ Use this table before creating a new app-local primitive.
154
159
  | `Alert` | Cross-platform imperative alerts | Direct `window.alert` or duplicated RN/web branching | Confirm destructive actions, native alert dialogs |
155
160
  | `AnimatedView` | Entrance and visibility animation | Hand-rolled Reanimated wrappers | Staggered list rows, revealed panels, animated empty states |
156
161
  | `Badge` | Short status labels | Custom pill `View` + `Text` | Draft/active states, counts, plan labels, role tags |
157
- | `BottomSheet` | Mobile-first modal sheets | Custom absolute-position sheets | Action pickers, mobile filters, quick edit forms |
162
+ | `BottomSheet` | Mobile-first modal sheets | Custom absolute-position sheets | Action pickers, mobile filters, keyboard-aware quick edit forms |
158
163
  | `Button` | Commands and CTAs | Pressable plus custom text styling | Submit, save, cancel, delete, navigation CTAs |
159
164
  | `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter` | Framed content groups | Ad hoc bordered panels | List items, pricing plans, settings sections, summaries |
160
165
  | `Checkbox` | Boolean selection | Custom checkmark controls | Terms consent, checklist items, multi-select filters |
package/README.md CHANGED
@@ -31,8 +31,11 @@ Consumers must also install the native and Expo peer dependencies listed in
31
31
  `package.json`. The tested baseline is Expo SDK 55 with React 19.2, React
32
32
  Native 0.83, React Native Web 0.21, Reanimated 4.2, and Worklets 0.7.
33
33
  `@rn-primitives/*` packages are managed by `@mrmeg/expo-ui` because they are
34
- implementation details of the exported UI components. i18n setup is optional;
35
- plain text and children render without `i18next` or `react-i18next`. Start
34
+ implementation details of the exported UI components. Native bottom sheet
35
+ keyboard avoidance uses `react-native-keyboard-controller`; mount that
36
+ library's `KeyboardProvider` near the app root when sheets contain text
37
+ inputs. i18n setup is optional; plain text and children render without
38
+ `i18next` or `react-i18next`. Start
36
39
  consumer apps from the same Expo SDK family or update the package and peer
37
40
  ranges deliberately. Keep npm auth tokens in developer or CI configuration,
38
41
  not in this repository.
@@ -165,7 +168,7 @@ All components are exported from `@mrmeg/expo-ui/components`; direct imports suc
165
168
  | `Alert` | Cross-platform imperative alerts | Direct `window.alert` or duplicated RN/web branching | Confirm destructive actions, native alert dialogs, simple blocking messages |
166
169
  | `AnimatedView` | Entrance and visibility animation | Hand-rolled Reanimated wrappers | Staggered list rows, revealed panels, animated empty states |
167
170
  | `Badge` | Short status labels | Custom pill `View` + `Text` | Draft/active states, counts, plan labels, role tags |
168
- | `BottomSheet` | Mobile-first modal sheets | Custom absolute-position sheets | Action pickers, mobile filters, quick edit forms, contextual details |
171
+ | `BottomSheet` | Mobile-first modal sheets | Custom absolute-position sheets | Action pickers, mobile filters, keyboard-aware quick edit forms, contextual details |
169
172
  | `Button` | Commands and CTAs | Pressable plus custom text styling | Submit, save, cancel, delete, navigation CTAs, icon-accessory buttons; loading state preserves resting width |
170
173
  | `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter` | Framed content groups | Ad hoc bordered panels | List items, pricing plans, settings sections, summaries, dashboards |
171
174
  | `Checkbox` | Boolean selection | Custom checkmark controls | Terms consent, checklist items, multi-select filters, notification opt-ins |
@@ -228,7 +231,7 @@ Use `Button.preset`, not `variant`. `default` is the neutral primary action, `se
228
231
 
229
232
  Use `StyledText` or its aliases instead of raw `Text` whenever the text is part of app UI. Use `TextInput` for labeled fields because it already owns label, helper text, error text, clear buttons, password visibility, numeric filtering, and left/right elements.
230
233
 
231
- Mount `UIProvider` once near the root before using `Dialog`, `AlertDialog`, `BottomSheet`, `Drawer`, `DropdownMenu`, `Popover`, `SelectContent`, `Tooltip`, or package notifications. Trigger transient feedback from `globalUIStore`.
234
+ Mount `UIProvider` once near the root before using `Dialog`, `AlertDialog`, `BottomSheet`, `Drawer`, `DropdownMenu`, `Popover`, `SelectContent`, `Tooltip`, or package notifications. On native, mount `KeyboardProvider` from `react-native-keyboard-controller` near the root before using `BottomSheet.Content` with text inputs; `avoidKeyboard` defaults to `true` and can be disabled per sheet. Trigger transient feedback from `globalUIStore`.
232
235
 
233
236
  Use `Skeleton` components for loading content with stable dimensions, `EmptyState` for no-data/recoverable errors, `Alert` for blocking confirm/alert dialogs, and `Notification` for transient global feedback.
234
237
 
@@ -32,6 +32,10 @@ interface BottomSheetContentProps extends ViewProps {
32
32
  swipeEnabled?: boolean;
33
33
  /** Velocity threshold for quick swipe to close */
34
34
  velocityThreshold?: number;
35
+ /** Whether to move the sheet with the native keyboard animation. Default: true. */
36
+ avoidKeyboard?: boolean;
37
+ /** Whether to dismiss the keyboard when a native drag starts. Default: true. */
38
+ dismissKeyboardOnDrag?: boolean;
35
39
  style?: StyleProp<ViewStyle>;
36
40
  children: React.ReactNode;
37
41
  }
@@ -55,7 +59,7 @@ interface BottomSheetCloseProps {
55
59
  declare function useBottomSheetContext(): BottomSheetContextValue;
56
60
  declare function BottomSheetRoot({ open: controlledOpen, onOpenChange: controlledOnOpenChange, defaultOpen, snapPoints: rawSnapPoints, closeOnBackdropPress, children, }: BottomSheetProps): import("react/jsx-runtime").JSX.Element;
57
61
  declare function BottomSheetTrigger({ asChild, children, style: styleOverride }: BottomSheetTriggerProps): import("react/jsx-runtime").JSX.Element;
58
- declare function BottomSheetContent({ swipeEnabled, velocityThreshold, style: styleOverride, children, ...props }: BottomSheetContentProps): import("react/jsx-runtime").JSX.Element | null;
62
+ declare function BottomSheetContent({ swipeEnabled, velocityThreshold, avoidKeyboard, dismissKeyboardOnDrag, style: styleOverride, children, ...props }: BottomSheetContentProps): import("react/jsx-runtime").JSX.Element | null;
59
63
  declare function BottomSheetHandle({ style }: BottomSheetHandleProps): import("react/jsx-runtime").JSX.Element;
60
64
  declare function BottomSheetHeader({ children, style, ...props }: BottomSheetHeaderProps): import("react/jsx-runtime").JSX.Element;
61
65
  declare function BottomSheetBody({ children, style, ...props }: BottomSheetBodyProps): import("react/jsx-runtime").JSX.Element;
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { createContext, useCallback, useContext, useEffect, useReducer, useRef, useState } from "react";
2
+ import React, { createContext, useCallback, useContext, useEffect, useMemo, useReducer, useRef, useState } from "react";
3
3
  import { View, Pressable, Animated, StyleSheet, Platform, Dimensions, PanResponder, ScrollView, } from "react-native";
4
4
  import { Portal } from "@rn-primitives/portal";
5
5
  import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens";
6
6
  import { Pressable as SlotPressable } from "@rn-primitives/slot";
7
+ import { KeyboardController, useKeyboardAnimation } from "react-native-keyboard-controller";
7
8
  import { useTheme } from "../hooks/useTheme.js";
8
9
  import { spacing } from "../constants/spacing.js";
9
10
  import { shouldUseNativeDriver } from "../lib/animations.js";
@@ -50,6 +51,20 @@ function useBottomSheetContext() {
50
51
  return context;
51
52
  }
52
53
  const DragContext = createContext(null);
54
+ function BottomSheetPanel({ accessibilityViewIsModal, children, panHandlers, sheetStyle, styleOverride, translateY, ...props }) {
55
+ return (_jsx(Animated.View, { style: [
56
+ sheetStyle,
57
+ { transform: [{ translateY }] },
58
+ styleOverride && typeof styleOverride !== "function"
59
+ ? StyleSheet.flatten(styleOverride)
60
+ : undefined,
61
+ ], accessibilityViewIsModal: accessibilityViewIsModal, ...panHandlers, ...props, children: children }));
62
+ }
63
+ function KeyboardAvoidingBottomSheetPanel(props) {
64
+ const { height: keyboardHeight } = useKeyboardAnimation();
65
+ const composedTranslateY = useMemo(() => Animated.add(props.translateY, keyboardHeight), [keyboardHeight, props.translateY]);
66
+ return _jsx(BottomSheetPanel, { ...props, translateY: composedTranslateY });
67
+ }
53
68
  // ============================================================================
54
69
  // Utility Functions
55
70
  // ============================================================================
@@ -126,7 +141,7 @@ function BottomSheetTrigger({ asChild, children, style: styleOverride }) {
126
141
  // ============================================================================
127
142
  // Content
128
143
  // ============================================================================
129
- function BottomSheetContent({ swipeEnabled = true, velocityThreshold = 500, style: styleOverride, children, ...props }) {
144
+ function BottomSheetContent({ swipeEnabled = true, velocityThreshold = 500, avoidKeyboard = true, dismissKeyboardOnDrag = true, style: styleOverride, children, ...props }) {
130
145
  const sheetContext = useBottomSheetContext();
131
146
  const { open, onOpenChange, snapPoints, closeOnBackdropPress } = sheetContext;
132
147
  const { theme } = useTheme();
@@ -224,6 +239,11 @@ function BottomSheetContent({ swipeEnabled = true, velocityThreshold = 500, styl
224
239
  const progress = 1 - dragFromBase / currentHeightRef.current;
225
240
  backdropOpacity.setValue(Math.max(0, progress));
226
241
  }, [translateY, backdropOpacity, maxHeight]);
242
+ const dismissKeyboardForDrag = useCallback(() => {
243
+ if (Platform.OS !== "web" && dismissKeyboardOnDrag) {
244
+ void KeyboardController.dismiss();
245
+ }
246
+ }, [dismissKeyboardOnDrag]);
227
247
  // ------------------------------------------------------------------
228
248
  // Trigger animation during render if open changed
229
249
  // ------------------------------------------------------------------
@@ -287,7 +307,7 @@ function BottomSheetContent({ swipeEnabled = true, velocityThreshold = 500, styl
287
307
  // ------------------------------------------------------------------
288
308
  // Native: PanResponder for swipe gestures on the whole sheet
289
309
  // ------------------------------------------------------------------
290
- const panResponder = useRef(Platform.OS !== "web" && swipeEnabled
310
+ const panResponder = useMemo(() => Platform.OS !== "web" && swipeEnabled
291
311
  ? PanResponder.create({
292
312
  onStartShouldSetPanResponder: () => false,
293
313
  onMoveShouldSetPanResponder: (_evt, gestureState) => {
@@ -296,6 +316,7 @@ function BottomSheetContent({ swipeEnabled = true, velocityThreshold = 500, styl
296
316
  const isDownward = gestureState.dy > 0;
297
317
  return isVertical && isSignificant && isDownward;
298
318
  },
319
+ onPanResponderGrant: dismissKeyboardForDrag,
299
320
  onPanResponderMove: (_evt, gestureState) => {
300
321
  handleDragMove(gestureState.dy);
301
322
  },
@@ -303,7 +324,7 @@ function BottomSheetContent({ swipeEnabled = true, velocityThreshold = 500, styl
303
324
  handleDragRelease(Math.max(0, gestureState.dy), gestureState.vy);
304
325
  },
305
326
  })
306
- : null).current;
327
+ : null, [dismissKeyboardForDrag, handleDragMove, handleDragRelease, swipeEnabled]);
307
328
  // ------------------------------------------------------------------
308
329
  // Web: drag context provides callbacks for Handle's pointer events
309
330
  // ------------------------------------------------------------------
@@ -339,6 +360,9 @@ function BottomSheetContent({ swipeEnabled = true, velocityThreshold = 500, styl
339
360
  ...(Platform.OS === "web" && { zIndex: 51 }),
340
361
  };
341
362
  const sheetContent = (_jsx(TextColorContext.Provider, { value: textColor, children: _jsx(TextClassContext.Provider, { value: "", children: children }) }));
363
+ const PanelComponent = Platform.OS !== "web" && avoidKeyboard
364
+ ? KeyboardAvoidingBottomSheetPanel
365
+ : BottomSheetPanel;
342
366
  const contentElement = (_jsx(Portal, { name: "bottom-sheet-portal", children: _jsx(FullWindowOverlay, { children: _jsx(BottomSheetContext.Provider, { value: sheetContext, children: _jsxs(View, { style: StyleSheet.absoluteFill, children: [_jsx(Animated.View, { style: [
343
367
  StyleSheet.absoluteFill,
344
368
  {
@@ -346,16 +370,10 @@ function BottomSheetContent({ swipeEnabled = true, velocityThreshold = 500, styl
346
370
  opacity: backdropOpacity,
347
371
  },
348
372
  Platform.OS === "web" && { zIndex: 50 },
349
- ], children: _jsx(Pressable, { style: StyleSheet.absoluteFill, onPress: handleBackdropPress }) }), _jsx(Animated.View, { style: [
350
- sheetStyle,
351
- { transform: [{ translateY }] },
352
- styleOverride && typeof styleOverride !== "function"
353
- ? StyleSheet.flatten(styleOverride)
354
- : undefined,
355
- ], accessibilityViewIsModal: true, ...(Platform.OS === "web" && {
373
+ ], children: _jsx(Pressable, { style: StyleSheet.absoluteFill, onPress: handleBackdropPress }) }), _jsx(PanelComponent, { sheetStyle: sheetStyle, styleOverride: styleOverride, translateY: translateY, accessibilityViewIsModal: true, ...(Platform.OS === "web" && {
356
374
  role: "dialog",
357
375
  "aria-modal": true,
358
- }), ...(panResponder ? panResponder.panHandlers : {}), ...props, children: dragContextValue ? (_jsx(DragContext.Provider, { value: dragContextValue, children: sheetContent })) : (sheetContent) })] }) }) }) }));
376
+ }), panHandlers: panResponder ? panResponder.panHandlers : undefined, ...props, children: dragContextValue ? (_jsx(DragContext.Provider, { value: dragContextValue, children: sheetContent })) : (sheetContent) })] }) }) }) }));
359
377
  return contentElement;
360
378
  }
361
379
  // ============================================================================
@@ -257,7 +257,7 @@ const createStyles = (theme, size) => {
257
257
  marginLeft: spacing.sm,
258
258
  },
259
259
  loaderOverlay: {
260
- ...StyleSheet.absoluteFillObject,
260
+ ...StyleSheet.absoluteFill,
261
261
  alignItems: "center",
262
262
  justifyContent: "center",
263
263
  },
@@ -77,7 +77,7 @@ function Switch({ variant = "default", labelOn, labelOff, size = { width: 44, he
77
77
  disabled: !!props.disabled,
78
78
  busy: loading,
79
79
  }, children: [_jsx(View, { style: {
80
- ...StyleSheet.absoluteFillObject,
80
+ ...StyleSheet.absoluteFill,
81
81
  borderRadius: size.height / 2,
82
82
  backgroundColor: trackBg,
83
83
  borderWidth: 1,
@@ -43,11 +43,11 @@ interface ScalePressOptions {
43
43
  * ```
44
44
  */
45
45
  export declare function useScalePress(options?: ScalePressOptions): {
46
- animatedStyle: {
46
+ animatedStyle: import("react-native-reanimated/lib/typescript/hook/commonTypes").AnimatedStyleHandle<{
47
47
  transform: {
48
48
  scale: number;
49
49
  }[];
50
- };
50
+ }>;
51
51
  pressHandlers: {
52
52
  onPressIn: () => void;
53
53
  onPressOut: () => void;
@@ -46,7 +46,7 @@ interface StaggeredEntranceOptions {
46
46
  * })}
47
47
  * ```
48
48
  */
49
- export declare function useStaggeredEntrance(options?: StaggeredEntranceOptions): {
49
+ export declare function useStaggeredEntrance(options?: StaggeredEntranceOptions): import("react-native-reanimated/lib/typescript/hook/commonTypes").AnimatedStyleHandle<{
50
50
  opacity: number;
51
51
  transform?: undefined;
52
52
  } | {
@@ -59,7 +59,7 @@ export declare function useStaggeredEntrance(options?: StaggeredEntranceOptions)
59
59
  transform: {
60
60
  scale: number;
61
61
  }[];
62
- };
62
+ }>;
63
63
  /**
64
64
  * Convenience constant: default stagger delay between items (ms)
65
65
  */
@@ -11,3 +11,4 @@ export declare function resolveThemePreference(userTheme: ThemePreference, syste
11
11
  export declare const useThemeStore: import("zustand").UseBoundStore<import("zustand").StoreApi<ThemeStore>>;
12
12
  export declare function syncSystemTheme(): void;
13
13
  export declare function startSystemThemeListener(): () => void;
14
+ export declare function syncThemeFromEnvironment(): () => void;
@@ -13,7 +13,9 @@ function getSystemTheme() {
13
13
  }
14
14
  export const useThemeStore = create((set) => ({
15
15
  userTheme: "system",
16
- systemTheme: getSystemTheme(),
16
+ // Always start with "light" so SSR and the first client render agree.
17
+ // Real values are populated by `syncThemeFromEnvironment()` after mount.
18
+ systemTheme: "light",
17
19
  setTheme: (theme) => {
18
20
  set({
19
21
  userTheme: theme,
@@ -89,6 +91,18 @@ export function startSystemThemeListener() {
89
91
  };
90
92
  return stopSystemThemeListener;
91
93
  }
92
- // Load saved theme on store creation
93
- useThemeStore.getState().loadTheme();
94
- startSystemThemeListener();
94
+ // Single entry point for host apps to populate the store from the
95
+ // environment (persisted preference + OS color scheme listener). Safe to
96
+ // call multiple times — `startSystemThemeListener` is idempotent — and
97
+ // returns the unsubscribe so it can be used directly inside `useEffect`.
98
+ export function syncThemeFromEnvironment() {
99
+ useThemeStore.getState().loadTheme();
100
+ return startSystemThemeListener();
101
+ }
102
+ // Native has no SSR mismatch concern, so keep the historical auto-init
103
+ // behavior there. On web the host app must call `syncThemeFromEnvironment()`
104
+ // from a top-level `useEffect` to avoid hydration mismatches.
105
+ if (Platform.OS !== "web") {
106
+ useThemeStore.getState().loadTheme();
107
+ startSystemThemeListener();
108
+ }
package/llms-full.md CHANGED
@@ -36,6 +36,11 @@ 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.
43
+
39
44
  i18n is optional. Plain children and `text` props work without `i18next` or
40
45
  `react-i18next`. Use `configureExpoUiI18n()` only when a consuming app wants
41
46
  package `tx` props translated by its app-owned i18n instance.
@@ -82,7 +87,7 @@ Use this catalog before creating a new app-local primitive.
82
87
  | `Alert` | `@mrmeg/expo-ui/components` | Cross-platform imperative alerts | Avoid direct `window.alert` and duplicated native/web branching. |
83
88
  | `AnimatedView` | `@mrmeg/expo-ui/components` | Entrance and visibility animation | Keep simple reveal effects in the package wrapper. |
84
89
  | `Badge` | `@mrmeg/expo-ui/components` | Short status labels | Prefer over custom pill views. |
85
- | `BottomSheet` | `@mrmeg/expo-ui/components` | Mobile-first modal sheets | Requires root `UIProvider` portal setup. |
90
+ | `BottomSheet` | `@mrmeg/expo-ui/components` | Mobile-first modal sheets | Requires root `UIProvider`; native text-input sheets also require `KeyboardProvider`. |
86
91
  | `Button` | `@mrmeg/expo-ui/components` | Commands and CTAs | Use `preset`, not `variant`; visible heights are compact. |
87
92
  | `Card` | `@mrmeg/expo-ui/components` | Individual framed content groups | Do not wrap whole page sections in cards. |
88
93
  | `Checkbox` | `@mrmeg/expo-ui/components` | Boolean selection in forms or lists | Prefer over custom checkmark controls. |
package/llms.txt CHANGED
@@ -25,6 +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
30
 
29
31
  Use useTheme(), useStyles(), semantic tokens, StyledText, and package controls.
30
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.1.10",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "description": "Reusable Expo and React Native UI primitives for MrMeg projects.",
6
6
  "keywords": [
@@ -12,7 +12,7 @@
12
12
  "universal-ui"
13
13
  ],
14
14
  "author": "Matt Megenhardt",
15
- "license": "UNLICENSED",
15
+ "license": "MIT",
16
16
  "repository": {
17
17
  "type": "git",
18
18
  "url": "git+https://github.com/mrmeg/expo-template.git",
@@ -109,6 +109,7 @@
109
109
  "react": ">=19.2.0 <20.0.0",
110
110
  "react-native": ">=0.83.0 <0.84.0",
111
111
  "react-native-gesture-handler": "~2.30.0",
112
+ "react-native-keyboard-controller": ">=1.20.0 <2.0.0",
112
113
  "react-native-reanimated": "~4.2.0",
113
114
  "react-native-safe-area-context": "~5.6.0",
114
115
  "react-native-screens": "~4.23.0",
@@ -118,6 +119,6 @@
118
119
  },
119
120
  "devDependencies": {
120
121
  "@types/react": "~19.2.14",
121
- "typescript": "~5.9.2"
122
+ "typescript": "~6.0.3"
122
123
  }
123
124
  }