@mrmeg/expo-ui 0.11.0 → 0.12.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/CHANGELOG.md +19 -0
- package/LLM_USAGE.md +1 -0
- package/README.md +2 -1
- package/dist/components/DismissKeyboard.js +11 -4
- package/dist/components/KeyboardAvoidingView.d.ts +20 -0
- package/dist/components/KeyboardAvoidingView.js +20 -0
- package/dist/components/UIProvider.d.ts +12 -1
- package/dist/components/UIProvider.js +6 -2
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/llms-full.md +9 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
All notable changes to `@mrmeg/expo-ui` are documented here. This project
|
|
4
4
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
5
|
|
|
6
|
+
## [0.12.0]
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **`KeyboardAvoidingView` is now a public package component.** Native uses
|
|
11
|
+
`react-native-keyboard-controller` with `automaticOffset` enabled by default,
|
|
12
|
+
while web renders a plain `View`.
|
|
13
|
+
- **`UIProvider` now owns app-wide native keyboard avoidance by default.** Apps
|
|
14
|
+
that mount `KeyboardProvider` above `UIProvider` get root-level keyboard
|
|
15
|
+
avoiding behavior without adding per-screen `KeyboardAvoidingView` wrappers.
|
|
16
|
+
Pass `keyboardAvoiding={false}` to opt out, or `keyboardAvoidingProps` to tune
|
|
17
|
+
the root wrapper. Web skips the root keyboard wrapper unless explicitly
|
|
18
|
+
enabled.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **`DismissKeyboard` no longer nests keyboard-avoiding wrappers when the root
|
|
23
|
+
provider already owns keyboard avoidance.**
|
|
24
|
+
|
|
6
25
|
## [0.11.0]
|
|
7
26
|
|
|
8
27
|
### Added
|
package/LLM_USAGE.md
CHANGED
|
@@ -177,6 +177,7 @@ Use this table before creating a new app-local primitive.
|
|
|
177
177
|
| `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent` | One-off disclosure | Local animated height wrappers | Advanced settings, hidden helper text |
|
|
178
178
|
| `Dialog`, `AlertDialog` | Modal decisions and custom modal content | Custom modal overlays | Confirm delete, edit profile, invite user |
|
|
179
179
|
| `DismissKeyboard` | Tap-away keyboard dismissal | Screen-level keyboard handling | Forms, search screens, sign-in screens |
|
|
180
|
+
| `KeyboardAvoidingView` | Native keyboard-aware layout root | Repeated app-local keyboard wrappers | Screen roots, composer footers, form-heavy subtrees |
|
|
180
181
|
| `Drawer` | Side panels and drawer navigation | Custom sliding panels | Filter drawer, app navigation drawer, inspector panel |
|
|
181
182
|
| `DropdownMenu` | Menus and command lists | Homemade popover menus | Row actions, account menu, sort menu |
|
|
182
183
|
| `EmptyState` | No-data or recoverable error regions | One-off empty placeholders | Empty inbox, no search results, failed list load |
|
package/README.md
CHANGED
|
@@ -222,6 +222,7 @@ All components are exported from `@mrmeg/expo-ui/components`; direct imports suc
|
|
|
222
222
|
| `ErrorBoundary` | React render error fallback | Unhandled screen crashes | Route-level fallback, feature boundary, recoverable widget crashes |
|
|
223
223
|
| `Icon` | Feather or custom icons with theme tokens | Raw vector icons with hardcoded colors | Button accessories, empty-state icons, menu icons, status glyphs |
|
|
224
224
|
| `InputOTP` | Verification code entry | Multiple manually managed text inputs | Email codes, SMS codes, MFA, invite codes |
|
|
225
|
+
| `KeyboardAvoidingView` | Native keyboard-aware layout root | Repeated app-local keyboard wrappers | Screen roots, composer footers, form-heavy subtrees |
|
|
225
226
|
| `Label` | Accessible form labels | Plain styled text labels | Required labels, disabled labels, field group labels |
|
|
226
227
|
| `MaxWidthContainer` | Centered responsive width | Per-screen max-width wrappers | Web pages, tablet layouts, settings forms, auth panels |
|
|
227
228
|
| `Notification` | Global toast surface | Screen-local toast state | Saved/error/sync notifications, action toasts, loading toast, bottom-position alerts |
|
|
@@ -272,7 +273,7 @@ Use `Button.preset`, not `variant`. `default` is the neutral primary action, `se
|
|
|
272
273
|
|
|
273
274
|
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.
|
|
274
275
|
|
|
275
|
-
Mount `UIProvider` once near the root before using `Dialog`, `AlertDialog`, `BottomSheet`, `Drawer`, `DropdownMenu`, `Popover`, `SelectContent`, `Tooltip`, or package notifications. On native, `BottomSheet.Content` listens to React Native keyboard events when `avoidKeyboard` is enabled; it defaults to `true` and can be disabled per sheet. Trigger transient feedback with `notify`.
|
|
276
|
+
Mount `UIProvider` once near the root before using `Dialog`, `AlertDialog`, `BottomSheet`, `Drawer`, `DropdownMenu`, `Popover`, `SelectContent`, `Tooltip`, or package notifications. On native, `UIProvider` also wraps app content in the package keyboard-avoiding root by default, so ordinary screens and fixed footers stay above the soft keyboard without repeated app-local wrappers; pass `keyboardAvoiding={false}` to opt out, or use `KeyboardAvoidingView` directly for a subtree with custom behavior. Web skips the root keyboard wrapper unless `keyboardAvoiding` is explicitly enabled. `BottomSheet.Content` listens to React Native keyboard events when `avoidKeyboard` is enabled; it defaults to `true` and can be disabled per sheet. Trigger transient feedback with `notify`.
|
|
276
277
|
|
|
277
278
|
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.
|
|
278
279
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Pressable, StyleSheet, Platform,
|
|
2
|
+
import { Pressable, StyleSheet, Platform, ScrollView, View, } from "react-native";
|
|
3
3
|
import { KeyboardController, useKeyboardState } from "react-native-keyboard-controller";
|
|
4
|
+
import { KeyboardAvoidingView, useKeyboardAvoidance } from "./KeyboardAvoidingView.js";
|
|
4
5
|
/**
|
|
5
6
|
* Full-screen tap-catcher that dismisses the keyboard, mounted ONLY while the
|
|
6
7
|
* keyboard is visible.
|
|
@@ -21,18 +22,24 @@ function KeyboardDismissOverlay() {
|
|
|
21
22
|
const isVisible = useKeyboardState((state) => state.isVisible);
|
|
22
23
|
if (!isVisible)
|
|
23
24
|
return null;
|
|
24
|
-
return (_jsx(Pressable, { style: StyleSheet.absoluteFill,
|
|
25
|
+
return (_jsx(Pressable, { style: [StyleSheet.absoluteFill, styles.overlay], onPressIn: () => KeyboardController.dismiss(), accessibilityLabel: "Dismiss keyboard", accessibilityRole: "button" }));
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
27
28
|
* @returns Wrapper for a view that dismisses the keyboard when tapped outside of a text input
|
|
28
29
|
*/
|
|
29
30
|
export const DismissKeyboard = ({ children, style, avoidKeyboard = true, scrollable = true }) => {
|
|
31
|
+
const hasKeyboardAvoidance = useKeyboardAvoidance();
|
|
30
32
|
const content = scrollable ? (_jsx(ScrollView, { style: { flex: 1 }, contentContainerStyle: { flexGrow: 1, justifyContent: "center" }, keyboardShouldPersistTaps: "handled", showsVerticalScrollIndicator: false, children: children })) : (children);
|
|
31
33
|
// Web has no software keyboard, so the tap-catcher is native-only. The overlay
|
|
32
34
|
// is never created on web, keeping `useKeyboardState` off that platform.
|
|
33
35
|
const overlay = Platform.OS !== "web" ? _jsx(KeyboardDismissOverlay, {}) : null;
|
|
34
|
-
if (!avoidKeyboard) {
|
|
36
|
+
if (!avoidKeyboard || hasKeyboardAvoidance) {
|
|
35
37
|
return (_jsxs(View, { style: { flex: 1 }, children: [content, overlay] }));
|
|
36
38
|
}
|
|
37
|
-
return (_jsxs(KeyboardAvoidingView, { style: [{ flex: 1, width: "100%" }, style],
|
|
39
|
+
return (_jsxs(KeyboardAvoidingView, { style: [{ flex: 1, width: "100%" }, style], keyboardVerticalOffset: Platform.OS === "ios" ? 0 : 0, children: [content, overlay] }));
|
|
38
40
|
};
|
|
41
|
+
const styles = StyleSheet.create({
|
|
42
|
+
overlay: {
|
|
43
|
+
zIndex: 999,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type StyleProp, type ViewProps, type ViewStyle } from "react-native";
|
|
3
|
+
type KeyboardAvoidingBehavior = "height" | "padding" | "position" | "translate-with-padding";
|
|
4
|
+
export interface KeyboardAvoidingViewProps extends ViewProps {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
style?: StyleProp<ViewStyle>;
|
|
7
|
+
behavior?: KeyboardAvoidingBehavior;
|
|
8
|
+
contentContainerStyle?: ViewProps["style"];
|
|
9
|
+
keyboardVerticalOffset?: number;
|
|
10
|
+
automaticOffset?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function useKeyboardAvoidance(): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Package-level keyboard avoiding wrapper.
|
|
15
|
+
*
|
|
16
|
+
* Native uses `react-native-keyboard-controller` so screens can avoid the soft
|
|
17
|
+
* keyboard with `automaticOffset`; web renders a plain `View`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function KeyboardAvoidingView({ children, style, behavior, automaticOffset, contentContainerStyle, keyboardVerticalOffset, ...props }: KeyboardAvoidingViewProps): React.JSX.Element;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
import { Platform, View, } from "react-native";
|
|
4
|
+
import { KeyboardAvoidingView as NativeKeyboardAvoidingView, } from "react-native-keyboard-controller";
|
|
5
|
+
const KeyboardAvoidanceContext = createContext(false);
|
|
6
|
+
export function useKeyboardAvoidance() {
|
|
7
|
+
return useContext(KeyboardAvoidanceContext);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Package-level keyboard avoiding wrapper.
|
|
11
|
+
*
|
|
12
|
+
* Native uses `react-native-keyboard-controller` so screens can avoid the soft
|
|
13
|
+
* keyboard with `automaticOffset`; web renders a plain `View`.
|
|
14
|
+
*/
|
|
15
|
+
export function KeyboardAvoidingView({ children, style, behavior = Platform.OS === "ios" ? "padding" : "height", automaticOffset = true, contentContainerStyle, keyboardVerticalOffset, ...props }) {
|
|
16
|
+
if (Platform.OS === "web") {
|
|
17
|
+
return (_jsx(KeyboardAvoidanceContext.Provider, { value: true, children: _jsx(View, { style: style, ...props, children: children }) }));
|
|
18
|
+
}
|
|
19
|
+
return (_jsx(KeyboardAvoidanceContext.Provider, { value: true, children: _jsx(NativeKeyboardAvoidingView, { style: style, behavior: behavior, automaticOffset: automaticOffset, contentContainerStyle: contentContainerStyle, keyboardVerticalOffset: keyboardVerticalOffset, ...props, children: children }) }));
|
|
20
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { type KeyboardAvoidingViewProps } from "./KeyboardAvoidingView";
|
|
2
3
|
export interface UIProviderProps {
|
|
3
4
|
children: React.ReactNode;
|
|
4
5
|
/**
|
|
@@ -19,5 +20,15 @@ export interface UIProviderProps {
|
|
|
19
20
|
* @default true
|
|
20
21
|
*/
|
|
21
22
|
statusBar?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Wrap app content in the package keyboard-avoiding root.
|
|
25
|
+
*
|
|
26
|
+
* @default true on native, false on web
|
|
27
|
+
*/
|
|
28
|
+
keyboardAvoiding?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Props forwarded to the keyboard-avoiding root when enabled.
|
|
31
|
+
*/
|
|
32
|
+
keyboardAvoidingProps?: Omit<KeyboardAvoidingViewProps, "children">;
|
|
22
33
|
}
|
|
23
|
-
export declare function UIProvider({ children, notification, portalHost, statusBar, }: UIProviderProps): React.JSX.Element;
|
|
34
|
+
export declare function UIProvider({ children, notification, portalHost, statusBar, keyboardAvoiding, keyboardAvoidingProps, }: UIProviderProps): React.JSX.Element;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Platform } from "react-native";
|
|
2
3
|
import { PortalHost } from "@rn-primitives/portal";
|
|
3
4
|
import { Notification } from "./Notification.js";
|
|
4
5
|
import { StatusBar } from "./StatusBar.js";
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
import { KeyboardAvoidingView, } from "./KeyboardAvoidingView.js";
|
|
7
|
+
export function UIProvider({ children, notification = true, portalHost = true, statusBar = true, keyboardAvoiding = Platform.OS !== "web", keyboardAvoidingProps, }) {
|
|
8
|
+
const { style: keyboardAvoidingStyle, ...restKeyboardAvoidingProps } = keyboardAvoidingProps ?? {};
|
|
9
|
+
const content = keyboardAvoiding ? (_jsx(KeyboardAvoidingView, { style: [{ flex: 1 }, keyboardAvoidingStyle], ...restKeyboardAvoidingProps, children: children })) : (children);
|
|
10
|
+
return (_jsxs(_Fragment, { children: [content, notification ? _jsx(Notification, {}) : null, portalHost ? _jsx(PortalHost, {}) : null, statusBar ? _jsx(StatusBar, {}) : null] }));
|
|
7
11
|
}
|
|
@@ -15,6 +15,7 @@ export * from "./EmptyState";
|
|
|
15
15
|
export * from "./ErrorBoundary";
|
|
16
16
|
export * from "./Icon";
|
|
17
17
|
export * from "./InputOTP";
|
|
18
|
+
export * from "./KeyboardAvoidingView";
|
|
18
19
|
export * from "./Label";
|
|
19
20
|
export * from "./MaxWidthContainer";
|
|
20
21
|
export * from "./Notification";
|
package/dist/components/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export * from "./EmptyState.js";
|
|
|
15
15
|
export * from "./ErrorBoundary.js";
|
|
16
16
|
export * from "./Icon.js";
|
|
17
17
|
export * from "./InputOTP.js";
|
|
18
|
+
export * from "./KeyboardAvoidingView.js";
|
|
18
19
|
export * from "./Label.js";
|
|
19
20
|
export * from "./MaxWidthContainer.js";
|
|
20
21
|
export * from "./Notification.js";
|
package/llms-full.md
CHANGED
|
@@ -31,10 +31,14 @@ app-local component files.
|
|
|
31
31
|
Call `useResources()` once near the Expo app root. Mount `UIProvider` once near
|
|
32
32
|
the root when the app uses package feedback or overlay components.
|
|
33
33
|
|
|
34
|
-
`UIProvider` owns the package `Notification`, `StatusBar`,
|
|
35
|
-
`@rn-primitives` portal host
|
|
36
|
-
`
|
|
37
|
-
`Tooltip`, or `notify` / `globalUIStore`
|
|
34
|
+
`UIProvider` owns the package `Notification`, `StatusBar`, default
|
|
35
|
+
`@rn-primitives` portal host, and native keyboard-avoiding root. Mount it
|
|
36
|
+
before using `Dialog`, `AlertDialog`, `BottomSheet`, `Drawer`, `DropdownMenu`,
|
|
37
|
+
`Popover`, `SelectContent`, `Tooltip`, or `notify` / `globalUIStore`
|
|
38
|
+
notifications. Pass `keyboardAvoiding={false}` to opt out of the native root
|
|
39
|
+
keyboard avoidance, or use `KeyboardAvoidingView` directly for a subtree with
|
|
40
|
+
custom behavior. Web skips the root keyboard wrapper unless `keyboardAvoiding`
|
|
41
|
+
is explicitly enabled.
|
|
38
42
|
|
|
39
43
|
On native, `BottomSheet.Content` composes its sheet transform with React Native
|
|
40
44
|
keyboard event values. Pass `avoidKeyboard={false}` for sheets that should not
|
|
@@ -99,6 +103,7 @@ Use this catalog before creating a new app-local primitive.
|
|
|
99
103
|
| `ErrorBoundary` | `@mrmeg/expo-ui/components` | React render error fallback | Use for route or feature boundaries. |
|
|
100
104
|
| `Icon` | `@mrmeg/expo-ui/components` | Feather or custom icons with theme tokens | Avoid raw vector icons with hardcoded colors. |
|
|
101
105
|
| `InputOTP` | `@mrmeg/expo-ui/components` | Verification code entry | Prefer over manually managed text input groups. |
|
|
106
|
+
| `KeyboardAvoidingView` | `@mrmeg/expo-ui/components` | Native keyboard-aware layout roots, composer footers, and form-heavy subtrees | `UIProvider` already mounts one root by default; use this directly only for custom subtrees. |
|
|
102
107
|
| `Label` | `@mrmeg/expo-ui/components` | Accessible form labels | Use with package form controls. |
|
|
103
108
|
| `MaxWidthContainer` | `@mrmeg/expo-ui/components` | Centered responsive width | Use for web and tablet constrained layouts. |
|
|
104
109
|
| `Notification` | `@mrmeg/expo-ui/components` | Global toast surface | Trigger through `notify` (or `globalUIStore` for subscriptions/tests) with root `UIProvider`; optional actions dismiss after press. |
|