@mrmeg/expo-ui 0.1.7 → 0.1.9
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/LLM_USAGE.md +5 -0
- package/README.md +12 -4
- package/dist/components/Checkbox.js +12 -2
- package/dist/components/Notification.d.ts +1 -1
- package/dist/components/Notification.js +13 -6
- package/dist/components/StyledText.js +0 -3
- package/dist/hooks/useTheme.js +4 -8
- package/dist/state/themeStore.d.ts +9 -2
- package/dist/state/themeStore.js +58 -2
- package/llms-full.md +146 -0
- package/llms.txt +34 -0
- package/package.json +4 -2
package/LLM_USAGE.md
CHANGED
|
@@ -139,6 +139,11 @@ const { styles } = useStyles(({ theme, spacing, withAlpha }) => ({
|
|
|
139
139
|
}));
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
+
When the saved theme preference is `system`, the package theme store owns the
|
|
143
|
+
OS color-scheme subscription, including web `prefers-color-scheme`. Do not add
|
|
144
|
+
app-local Appearance or `matchMedia` listeners for package components; import
|
|
145
|
+
`useTheme()`, `useStyles()`, and `useThemeStore` from `@mrmeg/expo-ui`.
|
|
146
|
+
|
|
142
147
|
## Component Use-Case Index
|
|
143
148
|
|
|
144
149
|
Use this table before creating a new app-local primitive.
|
package/README.md
CHANGED
|
@@ -11,10 +11,13 @@ permission.
|
|
|
11
11
|
## For LLMs And Coding Agents
|
|
12
12
|
|
|
13
13
|
When this package is installed from npm, read
|
|
14
|
-
`node_modules/@mrmeg/expo-ui/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
`node_modules/@mrmeg/expo-ui/llms.txt` first, then
|
|
15
|
+
`node_modules/@mrmeg/expo-ui/LLM_USAGE.md` or
|
|
16
|
+
`node_modules/@mrmeg/expo-ui/llms-full.md` before creating app-local UI
|
|
17
|
+
primitives. Those files are shipped in the npm package and give the short
|
|
18
|
+
component-selection rules, import paths, setup requirements, component catalog,
|
|
19
|
+
and examples that help agents choose existing package components instead of
|
|
20
|
+
rebuilding them.
|
|
18
21
|
|
|
19
22
|
## Install
|
|
20
23
|
|
|
@@ -97,6 +100,11 @@ const styles = StyleSheet.create({
|
|
|
97
100
|
|
|
98
101
|
`useTheme()` returns the active `theme`, resolved `scheme`, persisted `currentTheme`, `setTheme`, `toggleTheme`, cross-platform shadow helpers, a web focus-ring helper, contrast helpers, and `withAlpha`. `getShadowStyle(type)` supports `base`, `soft`, `sharp`, `subtle`, `elevated`, `glow`, `glass`, `card`, `cardHover`, and `cardSubtle`. Use semantic tokens such as `theme.colors.background`, `foreground`, `card`, `popover`, `border`, `input`, `ring`, `primary`, `secondary`, `accent`, `mutedForeground`, `destructive`, `success`, and `warning`. `primary` is the neutral action color, `secondary` is a neutral secondary surface, `accent` is the teal highlight color, `input` is the default form-control border, and `ring` is the focus outline color.
|
|
99
102
|
|
|
103
|
+
When `currentTheme` is `"system"`, the package tracks the OS color scheme and
|
|
104
|
+
updates all `useTheme()` and `useStyles()` consumers through the package theme
|
|
105
|
+
store. Apps should not add their own Appearance or `matchMedia` listeners just
|
|
106
|
+
to make package components follow system light/dark changes.
|
|
107
|
+
|
|
100
108
|
Use `useStyles()` when a component needs memoized theme-aware styles. The style factory receives `{ theme, spacing, withAlpha }`, and the returned hook value also includes the normal `useTheme()` helpers.
|
|
101
109
|
|
|
102
110
|
```tsx
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect } from "react";
|
|
2
3
|
import { View, StyleSheet, Pressable, Platform } from "react-native";
|
|
3
4
|
import Animated, { useSharedValue, useAnimatedStyle, withTiming, useReducedMotion, } from "react-native-reanimated";
|
|
4
5
|
import { Icon } from "./Icon.js";
|
|
@@ -19,6 +20,15 @@ function Checkbox({ size = "md", label, indeterminate = false, error = false, st
|
|
|
19
20
|
const sizeConfig = SIZE_CONFIGS[size];
|
|
20
21
|
// Simple fast opacity for the checkmark icon
|
|
21
22
|
const checkOpacity = useSharedValue(checked || indeterminate ? 1 : 0);
|
|
23
|
+
const isVisuallyChecked = !!checked || indeterminate;
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (reduceMotion) {
|
|
26
|
+
checkOpacity.value = withTiming(isVisuallyChecked ? 1 : 0, { duration: 0 });
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
checkOpacity.value = withTiming(isVisuallyChecked ? 1 : 0, { duration: 60 });
|
|
30
|
+
}
|
|
31
|
+
}, [checkOpacity, isVisuallyChecked, reduceMotion]);
|
|
22
32
|
const wrappedOnCheckedChange = (next) => {
|
|
23
33
|
if (next)
|
|
24
34
|
hapticLight();
|
|
@@ -36,14 +46,14 @@ function Checkbox({ size = "md", label, indeterminate = false, error = false, st
|
|
|
36
46
|
// Dynamic border color with sufficient contrast against background
|
|
37
47
|
const borderColor = error
|
|
38
48
|
? theme.colors.destructive
|
|
39
|
-
:
|
|
49
|
+
: isVisuallyChecked
|
|
40
50
|
? theme.colors.primary
|
|
41
51
|
: getContrastingColor(theme.colors.background, theme.colors.text, theme.colors.textDim);
|
|
42
52
|
// Flatten style override for web compatibility
|
|
43
53
|
const flattenedStyle = styleOverride ? StyleSheet.flatten(styleOverride) : undefined;
|
|
44
54
|
const checkboxElement = (_jsx(CheckboxPrimitive.Root, { ...props, checked: checked, onCheckedChange: wrappedOnCheckedChange, disabled: disabled, style: {
|
|
45
55
|
borderColor,
|
|
46
|
-
backgroundColor:
|
|
56
|
+
backgroundColor: isVisuallyChecked ? theme.colors.primary : theme.colors.background,
|
|
47
57
|
borderRadius: spacing.radiusSm,
|
|
48
58
|
borderWidth: 1,
|
|
49
59
|
width: sizeConfig.size,
|
|
@@ -134,9 +134,12 @@ export const Notification = () => {
|
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
136
|
};
|
|
137
|
-
const getTitle = () => {
|
|
138
|
-
if (alert?.title)
|
|
137
|
+
const getTitle = (message) => {
|
|
138
|
+
if (alert?.title?.trim())
|
|
139
139
|
return alert.title;
|
|
140
|
+
if (alert?.loading) {
|
|
141
|
+
return translateText("notification.loading", "Loading");
|
|
142
|
+
}
|
|
140
143
|
switch (alert?.type) {
|
|
141
144
|
case "error":
|
|
142
145
|
return translateText("notification.error", "Error");
|
|
@@ -145,14 +148,18 @@ export const Notification = () => {
|
|
|
145
148
|
case "warning":
|
|
146
149
|
return translateText("notification.warning", "Warning");
|
|
147
150
|
case "info":
|
|
148
|
-
return "";
|
|
151
|
+
return message ? "" : translateText("notification.info", "Info");
|
|
149
152
|
default:
|
|
150
153
|
return "";
|
|
151
154
|
}
|
|
152
155
|
};
|
|
153
156
|
const { icon, color: iconColor, bgColor: iconBgColor } = getIconProps();
|
|
154
|
-
const
|
|
155
|
-
const
|
|
157
|
+
const message = alert?.messages?.find((item) => item.trim().length > 0);
|
|
158
|
+
const title = getTitle(message);
|
|
159
|
+
const hasMessage = !!message;
|
|
160
|
+
if (!alert?.show) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
156
163
|
return (_jsx(Animated.View, { accessibilityLiveRegion: "polite", accessibilityRole: "alert", pointerEvents: alert?.show ? "auto" : "none", style: [
|
|
157
164
|
styles.container,
|
|
158
165
|
isBottom
|
|
@@ -164,7 +171,7 @@ export const Notification = () => {
|
|
|
164
171
|
styles.alert,
|
|
165
172
|
isBottom && styles.alertBottom,
|
|
166
173
|
getShadowStyle("base"),
|
|
167
|
-
], children: [_jsx(View, { style: [styles.iconBadge, { backgroundColor: iconBgColor }], children: alert?.loading ? (_jsx(ActivityIndicator, { size: "small", color: iconColor })) : (_jsx(Icon, { name: icon, size: 18, color: iconColor })) }), _jsxs(View, { style: styles.alertContent, children: [!!title && (_jsx(StyledText, { selectable: false, style: [styles.alertTitle, { color: theme.colors.foreground }], numberOfLines: 1, children: title })), hasMessage && (_jsx(StyledText, { selectable: false, style: [styles.alertDescription, { color: theme.colors.mutedForeground }], numberOfLines: 2, children:
|
|
174
|
+
], children: [_jsx(View, { style: [styles.iconBadge, { backgroundColor: iconBgColor }], children: alert?.loading ? (_jsx(ActivityIndicator, { size: "small", color: iconColor })) : (_jsx(Icon, { name: icon, size: 18, color: iconColor })) }), _jsxs(View, { style: styles.alertContent, children: [!!title && (_jsx(StyledText, { selectable: false, style: [styles.alertTitle, { color: theme.colors.foreground }], numberOfLines: 1, children: title })), hasMessage && (_jsx(StyledText, { selectable: false, style: [styles.alertDescription, { color: theme.colors.mutedForeground }], numberOfLines: 2, children: message }))] }), _jsx(Pressable, { style: styles.closeButton, hitSlop: spacing.sm, onPress: animateOut, accessibilityLabel: "Dismiss notification", accessibilityRole: "button", children: _jsx(Icon, { name: "x", size: 16, color: theme.colors.mutedForeground }) })] }) }));
|
|
168
175
|
};
|
|
169
176
|
const createStyles = (theme) => StyleSheet.create({
|
|
170
177
|
container: {
|
|
@@ -133,9 +133,6 @@ export const StyledText = forwardRef((props, ref) => {
|
|
|
133
133
|
},
|
|
134
134
|
resolvedContextTextStyle,
|
|
135
135
|
style,
|
|
136
|
-
// When a parent (Button, ToggleGroupItem) sets TextColorContext,
|
|
137
|
-
// that color must win over any color in the style prop
|
|
138
|
-
contextColor != null && { color: contextColor },
|
|
139
136
|
], selectable: resolvedSelectable, ...otherProps, children: content }));
|
|
140
137
|
});
|
|
141
138
|
StyledText.displayName = "StyledText";
|
package/dist/hooks/useTheme.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo } from "react";
|
|
2
2
|
import { colors } from "../constants/colors.js";
|
|
3
|
-
import {
|
|
4
|
-
import { useThemeStore } from "../state/themeStore.js";
|
|
3
|
+
import { Platform, StyleSheet } from "react-native";
|
|
4
|
+
import { resolveThemePreference, useThemeStore } from "../state/themeStore.js";
|
|
5
5
|
import { spacing as spacingConstants } from "../constants/spacing.js";
|
|
6
6
|
// Module-level cache for contrast calculations to avoid memory leak
|
|
7
7
|
// and share across components
|
|
@@ -45,14 +45,10 @@ function getCachedOrCompute(key, compute) {
|
|
|
45
45
|
*/
|
|
46
46
|
export function useTheme() {
|
|
47
47
|
const userTheme = useThemeStore((s) => s.userTheme);
|
|
48
|
+
const systemTheme = useThemeStore((s) => s.systemTheme);
|
|
48
49
|
const setTheme = useThemeStore((s) => s.setTheme);
|
|
49
|
-
let defaultScheme = useColorSchemeDefault();
|
|
50
|
-
// Ensure a scheme is selected, even if we fail to get one
|
|
51
|
-
if (!defaultScheme) {
|
|
52
|
-
defaultScheme = "light";
|
|
53
|
-
}
|
|
54
50
|
// Determine which theme to use (user preference or system)
|
|
55
|
-
const effectiveScheme = userTheme
|
|
51
|
+
const effectiveScheme = resolveThemePreference(userTheme, systemTheme);
|
|
56
52
|
const theme = colors[effectiveScheme];
|
|
57
53
|
// Sync theme to DOM so CSS in +html.tsx follows the app's runtime theme
|
|
58
54
|
useEffect(() => {
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
export type ThemePreference = "system" | "light" | "dark";
|
|
2
|
+
export type ResolvedTheme = "light" | "dark";
|
|
1
3
|
export type ThemeStore = {
|
|
2
|
-
userTheme:
|
|
3
|
-
|
|
4
|
+
userTheme: ThemePreference;
|
|
5
|
+
systemTheme: ResolvedTheme;
|
|
6
|
+
setTheme: (theme: ThemePreference) => void;
|
|
7
|
+
setSystemTheme: (theme: ResolvedTheme) => void;
|
|
4
8
|
loadTheme: () => void;
|
|
5
9
|
};
|
|
10
|
+
export declare function resolveThemePreference(userTheme: ThemePreference, systemTheme: ResolvedTheme): ResolvedTheme;
|
|
6
11
|
export declare const useThemeStore: import("zustand").UseBoundStore<import("zustand").StoreApi<ThemeStore>>;
|
|
12
|
+
export declare function syncSystemTheme(): void;
|
|
13
|
+
export declare function startSystemThemeListener(): () => void;
|
package/dist/state/themeStore.js
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
|
-
import { Platform } from "react-native";
|
|
1
|
+
import { Appearance, Platform } from "react-native";
|
|
2
2
|
import { create } from "zustand";
|
|
3
3
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
4
4
|
const THEME_KEY = "user-theme-preference";
|
|
5
|
+
export function resolveThemePreference(userTheme, systemTheme) {
|
|
6
|
+
return userTheme === "system" ? systemTheme : userTheme;
|
|
7
|
+
}
|
|
8
|
+
function getSystemTheme() {
|
|
9
|
+
if (Platform.OS === "web" && typeof window !== "undefined" && typeof window.matchMedia === "function") {
|
|
10
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
11
|
+
}
|
|
12
|
+
return Appearance.getColorScheme() === "dark" ? "dark" : "light";
|
|
13
|
+
}
|
|
5
14
|
export const useThemeStore = create((set) => ({
|
|
6
15
|
userTheme: "system",
|
|
16
|
+
systemTheme: getSystemTheme(),
|
|
7
17
|
setTheme: (theme) => {
|
|
8
|
-
set({
|
|
18
|
+
set({
|
|
19
|
+
userTheme: theme,
|
|
20
|
+
...(theme === "system" ? { systemTheme: getSystemTheme() } : {}),
|
|
21
|
+
});
|
|
9
22
|
// Save directly when setting theme
|
|
10
23
|
if (Platform.OS !== "web") {
|
|
11
24
|
AsyncStorage.setItem(THEME_KEY, theme).catch(() => {
|
|
@@ -16,6 +29,9 @@ export const useThemeStore = create((set) => ({
|
|
|
16
29
|
localStorage.setItem(THEME_KEY, theme);
|
|
17
30
|
}
|
|
18
31
|
},
|
|
32
|
+
setSystemTheme: (theme) => {
|
|
33
|
+
set({ systemTheme: theme });
|
|
34
|
+
},
|
|
19
35
|
loadTheme: () => {
|
|
20
36
|
if (Platform.OS !== "web") {
|
|
21
37
|
AsyncStorage.getItem(THEME_KEY).then((saved) => {
|
|
@@ -34,5 +50,45 @@ export const useThemeStore = create((set) => ({
|
|
|
34
50
|
}
|
|
35
51
|
}
|
|
36
52
|
}));
|
|
53
|
+
let stopSystemThemeListener = null;
|
|
54
|
+
export function syncSystemTheme() {
|
|
55
|
+
useThemeStore.getState().setSystemTheme(getSystemTheme());
|
|
56
|
+
}
|
|
57
|
+
export function startSystemThemeListener() {
|
|
58
|
+
if (stopSystemThemeListener) {
|
|
59
|
+
return stopSystemThemeListener;
|
|
60
|
+
}
|
|
61
|
+
syncSystemTheme();
|
|
62
|
+
if (Platform.OS === "web" && typeof window !== "undefined" && typeof window.matchMedia === "function") {
|
|
63
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
64
|
+
const onChange = () => {
|
|
65
|
+
useThemeStore.getState().setSystemTheme(mediaQuery.matches ? "dark" : "light");
|
|
66
|
+
};
|
|
67
|
+
if (typeof mediaQuery.addEventListener === "function") {
|
|
68
|
+
mediaQuery.addEventListener("change", onChange);
|
|
69
|
+
stopSystemThemeListener = () => {
|
|
70
|
+
mediaQuery.removeEventListener("change", onChange);
|
|
71
|
+
stopSystemThemeListener = null;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
mediaQuery.addListener(onChange);
|
|
76
|
+
stopSystemThemeListener = () => {
|
|
77
|
+
mediaQuery.removeListener(onChange);
|
|
78
|
+
stopSystemThemeListener = null;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return stopSystemThemeListener;
|
|
82
|
+
}
|
|
83
|
+
const subscription = Appearance.addChangeListener(({ colorScheme }) => {
|
|
84
|
+
useThemeStore.getState().setSystemTheme(colorScheme === "dark" ? "dark" : "light");
|
|
85
|
+
});
|
|
86
|
+
stopSystemThemeListener = () => {
|
|
87
|
+
subscription.remove();
|
|
88
|
+
stopSystemThemeListener = null;
|
|
89
|
+
};
|
|
90
|
+
return stopSystemThemeListener;
|
|
91
|
+
}
|
|
37
92
|
// Load saved theme on store creation
|
|
38
93
|
useThemeStore.getState().loadTheme();
|
|
94
|
+
startSystemThemeListener();
|
package/llms-full.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# @mrmeg/expo-ui Full Contract
|
|
2
|
+
|
|
3
|
+
`@mrmeg/expo-ui` packages reusable Expo and React Native UI primitives for
|
|
4
|
+
MrMeg apps. It owns the shared design system, tokens, package theme store,
|
|
5
|
+
resource hook, global notification store, overlay shell, and small interaction
|
|
6
|
+
helpers. The consuming app owns routes, screens, feature state, auth, billing,
|
|
7
|
+
media, monitoring, API calls, product copy, and app-specific layouts.
|
|
8
|
+
|
|
9
|
+
## Agent Rules
|
|
10
|
+
|
|
11
|
+
Do not recreate primitives that this package already provides. Import from
|
|
12
|
+
`@mrmeg/expo-ui` and compose package components in the app.
|
|
13
|
+
|
|
14
|
+
Use only exported package paths:
|
|
15
|
+
|
|
16
|
+
- `@mrmeg/expo-ui`
|
|
17
|
+
- `@mrmeg/expo-ui/components`
|
|
18
|
+
- `@mrmeg/expo-ui/components/*`
|
|
19
|
+
- `@mrmeg/expo-ui/constants`
|
|
20
|
+
- `@mrmeg/expo-ui/constants/*`
|
|
21
|
+
- `@mrmeg/expo-ui/hooks`
|
|
22
|
+
- `@mrmeg/expo-ui/hooks/*`
|
|
23
|
+
- `@mrmeg/expo-ui/state`
|
|
24
|
+
- `@mrmeg/expo-ui/lib`
|
|
25
|
+
|
|
26
|
+
Do not import from `@mrmeg/expo-ui/dist/*`, `packages/ui/src/*`, or copied
|
|
27
|
+
app-local component files.
|
|
28
|
+
|
|
29
|
+
## App Setup
|
|
30
|
+
|
|
31
|
+
Call `useResources()` once near the Expo app root. Mount `UIProvider` once near
|
|
32
|
+
the root when the app uses package feedback or overlay components.
|
|
33
|
+
|
|
34
|
+
`UIProvider` owns the package `Notification`, `StatusBar`, and default
|
|
35
|
+
`@rn-primitives` portal host. Mount it before using `Dialog`, `AlertDialog`,
|
|
36
|
+
`BottomSheet`, `Drawer`, `DropdownMenu`, `Popover`, `SelectContent`,
|
|
37
|
+
`Tooltip`, or `globalUIStore` notifications.
|
|
38
|
+
|
|
39
|
+
i18n is optional. Plain children and `text` props work without `i18next` or
|
|
40
|
+
`react-i18next`. Use `configureExpoUiI18n()` only when a consuming app wants
|
|
41
|
+
package `tx` props translated by its app-owned i18n instance.
|
|
42
|
+
|
|
43
|
+
## Theme Rules
|
|
44
|
+
|
|
45
|
+
Use `useTheme()` and `useStyles()` from `@mrmeg/expo-ui/hooks`. Use semantic
|
|
46
|
+
tokens such as `background`, `foreground`, `card`, `popover`, `border`,
|
|
47
|
+
`input`, `ring`, `primary`, `secondary`, `accent`, `mutedForeground`,
|
|
48
|
+
`destructive`, `success`, and `warning`.
|
|
49
|
+
|
|
50
|
+
Use `StyledText` and semantic text aliases instead of raw `Text` for app UI.
|
|
51
|
+
Use package controls instead of hardcoded Pressable/View/Text combinations.
|
|
52
|
+
|
|
53
|
+
When the saved theme preference is `system`, the package theme store owns OS
|
|
54
|
+
color-scheme sync, including web `prefers-color-scheme`. Apps should not add
|
|
55
|
+
their own `Appearance` or `matchMedia` listeners just to make package
|
|
56
|
+
components follow system light/dark changes.
|
|
57
|
+
|
|
58
|
+
## Import Examples
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { Button, StyledText, UIProvider } from "@mrmeg/expo-ui/components";
|
|
62
|
+
import { Button as ButtonDirect } from "@mrmeg/expo-ui/components/Button";
|
|
63
|
+
import { colors, spacing, typography } from "@mrmeg/expo-ui/constants";
|
|
64
|
+
import { useResources, useTheme } from "@mrmeg/expo-ui/hooks";
|
|
65
|
+
import { globalUIStore, useThemeStore } from "@mrmeg/expo-ui/state";
|
|
66
|
+
import { configureExpoUiI18n, hapticLight } from "@mrmeg/expo-ui/lib";
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The root barrel also exports the public surface:
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { Button, UIProvider, colors, useTheme } from "@mrmeg/expo-ui";
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Component Catalog
|
|
76
|
+
|
|
77
|
+
Use this catalog before creating a new app-local primitive.
|
|
78
|
+
|
|
79
|
+
| Component | Import | Use When | Gotchas |
|
|
80
|
+
|-----------|--------|----------|---------|
|
|
81
|
+
| `Accordion` | `@mrmeg/expo-ui/components` | Multi-section disclosure such as FAQ or grouped settings | Use compound parts instead of custom expanders. |
|
|
82
|
+
| `Alert` | `@mrmeg/expo-ui/components` | Cross-platform imperative alerts | Avoid direct `window.alert` and duplicated native/web branching. |
|
|
83
|
+
| `AnimatedView` | `@mrmeg/expo-ui/components` | Entrance and visibility animation | Keep simple reveal effects in the package wrapper. |
|
|
84
|
+
| `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. |
|
|
86
|
+
| `Button` | `@mrmeg/expo-ui/components` | Commands and CTAs | Use `preset`, not `variant`; visible heights are compact. |
|
|
87
|
+
| `Card` | `@mrmeg/expo-ui/components` | Individual framed content groups | Do not wrap whole page sections in cards. |
|
|
88
|
+
| `Checkbox` | `@mrmeg/expo-ui/components` | Boolean selection in forms or lists | Prefer over custom checkmark controls. |
|
|
89
|
+
| `Collapsible` | `@mrmeg/expo-ui/components` | One-off disclosure | Use for advanced settings or helper sections. |
|
|
90
|
+
| `Dialog`, `AlertDialog` | `@mrmeg/expo-ui/components` | Blocking modal content or decisions | Requires root `UIProvider` portal setup. |
|
|
91
|
+
| `DismissKeyboard` | `@mrmeg/expo-ui/components` | Tap-away keyboard dismissal | Prefer over screen-level keyboard wrappers. |
|
|
92
|
+
| `Drawer` | `@mrmeg/expo-ui/components` | Side panels and drawer navigation | `Drawer.Content` owns safe-area top/bottom padding; do not duplicate it in children. |
|
|
93
|
+
| `DropdownMenu` | `@mrmeg/expo-ui/components` | Menus and command lists | Requires root `UIProvider` portal setup. |
|
|
94
|
+
| `EmptyState` | `@mrmeg/expo-ui/components` | No-data or recoverable error regions | Prefer over one-off empty placeholders. |
|
|
95
|
+
| `ErrorBoundary` | `@mrmeg/expo-ui/components` | React render error fallback | Use for route or feature boundaries. |
|
|
96
|
+
| `Icon` | `@mrmeg/expo-ui/components` | Feather or custom icons with theme tokens | Avoid raw vector icons with hardcoded colors. |
|
|
97
|
+
| `InputOTP` | `@mrmeg/expo-ui/components` | Verification code entry | Prefer over manually managed text input groups. |
|
|
98
|
+
| `Label` | `@mrmeg/expo-ui/components` | Accessible form labels | Use with package form controls. |
|
|
99
|
+
| `MaxWidthContainer` | `@mrmeg/expo-ui/components` | Centered responsive width | Use for web and tablet constrained layouts. |
|
|
100
|
+
| `Notification` | `@mrmeg/expo-ui/components` | Global toast surface | Trigger through `globalUIStore` with root `UIProvider`. |
|
|
101
|
+
| `Popover` | `@mrmeg/expo-ui/components` | Anchored contextual content | Requires root `UIProvider` portal setup. |
|
|
102
|
+
| `Progress` | `@mrmeg/expo-ui/components` | Determinate or indeterminate progress | Prefer over layout-shifting spinners for progress regions. |
|
|
103
|
+
| `RadioGroup` | `@mrmeg/expo-ui/components` | Small mutually exclusive choices | Use `Select` for longer option sets. |
|
|
104
|
+
| `Select` | `@mrmeg/expo-ui/components` | Option menus | `SelectContent` requires root `UIProvider` portal setup. |
|
|
105
|
+
| `Separator` | `@mrmeg/expo-ui/components` | Horizontal or vertical dividers | Prefer over border-only spacer views. |
|
|
106
|
+
| `Skeleton` | `@mrmeg/expo-ui/components` | Loading placeholders | Use stable dimensions to avoid layout shift. |
|
|
107
|
+
| `Slider` | `@mrmeg/expo-ui/components` | Numeric value selection | Prefer over custom pan gesture tracks. |
|
|
108
|
+
| `StatusBar` | `@mrmeg/expo-ui/components` | Theme-aware native status bar | Usually mounted through `UIProvider`. |
|
|
109
|
+
| `StyledText` | `@mrmeg/expo-ui/components` | Theme-aware typography | Prefer semantic aliases over raw `Text`. |
|
|
110
|
+
| `Switch` | `@mrmeg/expo-ui/components` | Binary settings | Prefer over custom toggles for on/off state. |
|
|
111
|
+
| `Tabs` | `@mrmeg/expo-ui/components` | In-page tabbed views | Use for profile sections, report views, and settings categories. |
|
|
112
|
+
| `TextInput` | `@mrmeg/expo-ui/components` | Text entry | Use built-in label and error text support. |
|
|
113
|
+
| `Toggle` | `@mrmeg/expo-ui/components` | One pressed/unpressed control | Use `Button` for commands and `Switch` for settings. |
|
|
114
|
+
| `ToggleGroup` | `@mrmeg/expo-ui/components` | Related pressed states | Use for segmented controls, formatting, and filter chips. |
|
|
115
|
+
| `Tooltip` | `@mrmeg/expo-ui/components` | Short hover/focus help | Requires root `UIProvider` portal setup. |
|
|
116
|
+
|
|
117
|
+
## Selection Rules
|
|
118
|
+
|
|
119
|
+
Use `Button` for commands, `Toggle` for one pressed state, `ToggleGroup` for
|
|
120
|
+
related pressed states, and `Switch` for binary settings.
|
|
121
|
+
|
|
122
|
+
Use `RadioGroup` for small mutually exclusive choices and `Select` for longer
|
|
123
|
+
option sets.
|
|
124
|
+
|
|
125
|
+
Use `Dialog` for blocking decisions, `Popover` for contextual controls,
|
|
126
|
+
`Tooltip` for short explanations, and `DropdownMenu` for action lists.
|
|
127
|
+
|
|
128
|
+
Use `Card` for individual repeated or framed items, not as a wrapper around
|
|
129
|
+
full page sections. Use `EmptyState` for no-data or recoverable error regions,
|
|
130
|
+
`Skeleton` for loading content with stable layout, and `Progress` for real
|
|
131
|
+
progress or indeterminate long-running work.
|
|
132
|
+
|
|
133
|
+
## Validation
|
|
134
|
+
|
|
135
|
+
Run the UI package gates when changing package code or shipped docs:
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
bun run ui:typecheck
|
|
139
|
+
bun run ui:test
|
|
140
|
+
bun run ui:build
|
|
141
|
+
bun run ui:pack
|
|
142
|
+
bun run ui:consumer-smoke
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
For documentation-only package surface changes, `bun run ui:pack` is the
|
|
146
|
+
minimum check that proves the new docs are included in the npm tarball.
|
package/llms.txt
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @mrmeg/expo-ui
|
|
2
|
+
|
|
3
|
+
Docs for agents:
|
|
4
|
+
|
|
5
|
+
- README.md: install, setup, imports, theme usage, components, publishing.
|
|
6
|
+
- LLM_USAGE.md: concise component-selection and implementation rules.
|
|
7
|
+
- llms-full.md: expanded package contract and component catalog.
|
|
8
|
+
|
|
9
|
+
Public entrypoints:
|
|
10
|
+
|
|
11
|
+
- @mrmeg/expo-ui
|
|
12
|
+
- @mrmeg/expo-ui/components
|
|
13
|
+
- @mrmeg/expo-ui/components/*
|
|
14
|
+
- @mrmeg/expo-ui/constants
|
|
15
|
+
- @mrmeg/expo-ui/constants/*
|
|
16
|
+
- @mrmeg/expo-ui/hooks
|
|
17
|
+
- @mrmeg/expo-ui/hooks/*
|
|
18
|
+
- @mrmeg/expo-ui/state
|
|
19
|
+
- @mrmeg/expo-ui/lib
|
|
20
|
+
|
|
21
|
+
Do not recreate primitives this package already provides. Import reusable UI
|
|
22
|
+
from the package and compose it in the app.
|
|
23
|
+
|
|
24
|
+
Call useResources() once near the Expo app root. Mount UIProvider once near the
|
|
25
|
+
root before using package notifications or overlay primitives: Dialog,
|
|
26
|
+
AlertDialog, BottomSheet, Drawer, DropdownMenu, Popover, SelectContent, or
|
|
27
|
+
Tooltip.
|
|
28
|
+
|
|
29
|
+
Use useTheme(), useStyles(), semantic tokens, StyledText, and package controls.
|
|
30
|
+
Do not add app-local Appearance or matchMedia listeners for package theme sync.
|
|
31
|
+
Do not import from dist, source checkout paths, or app-local copies.
|
|
32
|
+
|
|
33
|
+
i18n is optional. Plain children and text props work without app i18n. Use
|
|
34
|
+
configureExpoUiI18n() only when the app wants package tx props translated.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrmeg/expo-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Reusable Expo and React Native UI primitives for MrMeg projects.",
|
|
6
6
|
"keywords": [
|
|
@@ -32,7 +32,9 @@
|
|
|
32
32
|
"dist",
|
|
33
33
|
"package.json",
|
|
34
34
|
"README.md",
|
|
35
|
-
"LLM_USAGE.md"
|
|
35
|
+
"LLM_USAGE.md",
|
|
36
|
+
"llms.txt",
|
|
37
|
+
"llms-full.md"
|
|
36
38
|
],
|
|
37
39
|
"exports": {
|
|
38
40
|
".": {
|