@mrmeg/expo-ui 0.27.1 → 0.29.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 +666 -0
- package/LLM_USAGE.md +241 -37
- package/README.md +591 -136
- package/bin/expo-ui-icons.mjs +509 -0
- package/dist/components/Accordion.js +12 -6
- package/dist/components/AnimatedView.d.ts +1 -1
- package/dist/components/AnimatedView.js +5 -2
- package/dist/components/Avatar.d.ts +1 -1
- package/dist/components/Avatar.js +2 -2
- package/dist/components/Badge.js +5 -1
- package/dist/components/BottomSheet.d.ts +29 -1
- package/dist/components/BottomSheet.js +107 -30
- package/dist/components/Button.d.ts +20 -4
- package/dist/components/Button.js +60 -43
- package/dist/components/Card.d.ts +1 -1
- package/dist/components/Card.js +24 -4
- package/dist/components/Carousel.js +2 -2
- package/dist/components/Checkbox.js +51 -44
- package/dist/components/Collapsible.js +7 -3
- package/dist/components/Dialog.js +87 -16
- package/dist/components/Drawer.d.ts +1 -1
- package/dist/components/Drawer.js +65 -55
- package/dist/components/DropdownMenu.js +11 -7
- package/dist/components/EmptyState.d.ts +2 -2
- package/dist/components/EmptyState.js +5 -2
- package/dist/components/Hydrated.d.ts +31 -0
- package/dist/components/Hydrated.js +30 -0
- package/dist/components/Icon.d.ts +71 -28
- package/dist/components/Icon.js +70 -28
- package/dist/components/IconGlyph.d.ts +15 -0
- package/dist/components/IconGlyph.js +32 -0
- package/dist/components/IconGlyph.native.d.ts +10 -0
- package/dist/components/IconGlyph.native.js +30 -0
- package/dist/components/InputOTP.js +5 -2
- package/dist/components/Item.d.ts +103 -4
- package/dist/components/Item.js +168 -15
- package/dist/components/KeyboardAvoidingView.d.ts +1 -1
- package/dist/components/KeyboardAvoidingView.js +4 -1
- package/dist/components/Label.js +3 -2
- package/dist/components/MaxWidthContainer.js +1 -1
- package/dist/components/Notification.js +45 -27
- package/dist/components/Popover.d.ts +18 -5
- package/dist/components/Popover.js +72 -8
- package/dist/components/Progress.js +8 -6
- package/dist/components/RadioGroup.js +52 -49
- package/dist/components/Screen.d.ts +61 -0
- package/dist/components/Screen.js +62 -0
- package/dist/components/SectionHeader.d.ts +1 -1
- package/dist/components/SegmentedControl.d.ts +12 -4
- package/dist/components/SegmentedControl.js +94 -4
- package/dist/components/Select.js +21 -26
- package/dist/components/Skeleton.js +24 -10
- package/dist/components/Slider.d.ts +11 -3
- package/dist/components/Slider.js +73 -6
- package/dist/components/StatCard.d.ts +4 -2
- package/dist/components/StatCard.js +3 -1
- package/dist/components/StyledText.d.ts +7 -1
- package/dist/components/StyledText.js +5 -3
- package/dist/components/Switch.d.ts +1 -1
- package/dist/components/Switch.js +20 -30
- package/dist/components/Tabs.d.ts +2 -2
- package/dist/components/Tabs.js +13 -24
- package/dist/components/TextInput.d.ts +1 -1
- package/dist/components/TextInput.js +71 -26
- package/dist/components/ThemeSelector.d.ts +52 -0
- package/dist/components/ThemeSelector.js +90 -0
- package/dist/components/Toggle.d.ts +5 -49
- package/dist/components/Toggle.js +53 -29
- package/dist/components/ToggleGroup.d.ts +4 -8
- package/dist/components/ToggleGroup.js +52 -34
- package/dist/components/Tooltip.d.ts +1 -1
- package/dist/components/Tooltip.js +9 -3
- package/dist/components/UIProvider.d.ts +22 -2
- package/dist/components/UIProvider.js +15 -3
- package/dist/components/bottomSheetDismiss.d.ts +19 -0
- package/dist/components/bottomSheetDismiss.js +44 -0
- package/dist/components/iconBuiltins.generated.d.ts +143 -0
- package/dist/components/iconBuiltins.generated.js +25 -0
- package/dist/components/iconTypes.d.ts +37 -0
- package/dist/components/iconTypes.js +1 -0
- package/dist/components/index.d.ts +47 -44
- package/dist/components/index.js +3 -0
- package/dist/components/keyboardDismiss.js +12 -4
- package/dist/components/nativeTextField.android.js +13 -9
- package/dist/constants/colors.d.ts +40 -5
- package/dist/constants/colors.js +26 -8
- package/dist/constants/fonts.d.ts +41 -2
- package/dist/constants/fonts.js +46 -7
- package/dist/constants/index.d.ts +5 -4
- package/dist/constants/index.js +1 -0
- package/dist/constants/interaction.d.ts +18 -0
- package/dist/constants/interaction.js +18 -0
- package/dist/constants/spacing.d.ts +2 -1
- package/dist/constants/spacing.js +2 -1
- package/dist/design-system.json +429 -132
- package/dist/hooks/index.d.ts +12 -7
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/useDimensions.d.ts +13 -8
- package/dist/hooks/useDimensions.js +123 -48
- package/dist/hooks/useFocusVisible.d.ts +30 -0
- package/dist/hooks/useFocusVisible.js +45 -0
- package/dist/hooks/useFontStyle.d.ts +2 -2
- package/dist/hooks/useFontStyle.js +3 -2
- package/dist/hooks/useHydrated.d.ts +13 -0
- package/dist/hooks/useHydrated.js +19 -0
- package/dist/hooks/useNotificationOffset.d.ts +20 -0
- package/dist/hooks/useNotificationOffset.js +34 -0
- package/dist/hooks/useResources.d.ts +21 -20
- package/dist/hooks/useResources.js +72 -68
- package/dist/hooks/useScalePress.d.ts +8 -5
- package/dist/hooks/useScalePress.js +16 -9
- package/dist/hooks/useShape.d.ts +24 -0
- package/dist/hooks/useShape.js +24 -0
- package/dist/hooks/useStaggeredEntrance.js +9 -7
- package/dist/hooks/useTheme.d.ts +3 -3
- package/dist/hooks/useTheme.js +177 -66
- package/dist/hooks/useWindowInsets.d.ts +13 -0
- package/dist/hooks/useWindowInsets.js +23 -0
- package/dist/icons/LICENSE.lucide +43 -0
- package/dist/icons/lucide.json +2144 -0
- package/dist/index.d.ts +5 -5
- package/dist/lib/composeRefs.d.ts +8 -0
- package/dist/lib/composeRefs.js +20 -0
- package/dist/lib/haptics.d.ts +12 -0
- package/dist/lib/haptics.js +21 -0
- package/dist/lib/index.d.ts +6 -5
- package/dist/lib/index.js +1 -0
- package/dist/lib/interFonts.d.ts +15 -0
- package/dist/lib/interFonts.js +1 -0
- package/dist/lib/interFonts.native.d.ts +3 -0
- package/dist/lib/interFonts.native.js +20 -0
- package/dist/lib/popoverPlacement.d.ts +36 -0
- package/dist/lib/popoverPlacement.js +29 -0
- package/dist/lib/themedStyles.d.ts +1 -1
- package/dist/lib/useAnimatedValue.d.ts +15 -0
- package/dist/lib/useAnimatedValue.js +19 -0
- package/dist/lib/webHitTarget.d.ts +40 -0
- package/dist/lib/webHitTarget.js +37 -0
- package/dist/state/feedbackStore.d.ts +24 -0
- package/dist/state/feedbackStore.js +16 -0
- package/dist/state/globalUIStore.d.ts +27 -0
- package/dist/state/globalUIStore.js +23 -0
- package/dist/state/index.d.ts +7 -5
- package/dist/state/index.js +2 -0
- package/dist/state/initialScheme.d.ts +27 -0
- package/dist/state/initialScheme.js +29 -0
- package/dist/state/notify.d.ts +1 -1
- package/dist/state/themeColorScope.d.ts +1 -1
- package/dist/state/themeStore.d.ts +69 -16
- package/dist/state/themeStore.js +65 -38
- package/llms-full.md +59 -10
- package/llms.txt +10 -1
- package/package.json +79 -4
- package/dist/components/iconRegistry.generated.d.ts +0 -148
- package/dist/components/iconRegistry.generated.js +0 -296
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ThemeColors } from "../constants/colors";
|
|
2
|
-
import type { FontOverrides } from "../constants/fonts";
|
|
1
|
+
import type { ThemeColors } from "../constants/colors.js";
|
|
2
|
+
import type { FontOverrides, SerifPreset } from "../constants/fonts.js";
|
|
3
3
|
export type ThemePreference = "system" | "light" | "dark";
|
|
4
4
|
export type ResolvedTheme = "light" | "dark";
|
|
5
5
|
/**
|
|
@@ -27,19 +27,28 @@ export type ColorOverrides = {
|
|
|
27
27
|
light?: Partial<ThemeColors>;
|
|
28
28
|
dark?: Partial<ThemeColors>;
|
|
29
29
|
};
|
|
30
|
+
/** A radius override for one shape slot. `0` is square corners; `9999` is a pill. */
|
|
31
|
+
export type ShapeSlotOverride = {
|
|
32
|
+
/** Border radius for every component in the slot. A caller `style` still wins. */
|
|
33
|
+
borderRadius?: number;
|
|
34
|
+
};
|
|
30
35
|
/**
|
|
31
|
-
* Shape overrides a host app can inject, grouped per
|
|
32
|
-
*
|
|
33
|
-
*
|
|
36
|
+
* Shape overrides a host app can inject, grouped per slot. Every field is
|
|
37
|
+
* optional; omitted fields keep the package default. Components read their
|
|
38
|
+
* slot through `useShape(slot)` and layer it after the static radius, before
|
|
39
|
+
* the caller's `style`.
|
|
40
|
+
*
|
|
41
|
+
* | Slot | Components | Package default |
|
|
42
|
+
* |---|---|---|
|
|
43
|
+
* | `button` | Button (every preset) | `spacing.radiusMd` (10) |
|
|
44
|
+
* | `input` | TextInput (not `underlined`), Select trigger, InputOTP cells | `spacing.radiusMd` (10) |
|
|
45
|
+
* | `card` | Card, StatCard, EmptyState (`bordered`), SkeletonCard | `spacing.radiusLg` (14) |
|
|
46
|
+
* | `sheet` | BottomSheet top corners (where the platform lets the sheet draw them: web, Android) | platform |
|
|
47
|
+
* | `badge` | Badge | `spacing.radiusFull` |
|
|
48
|
+
* | `dialog` | Dialog and AlertDialog content | `spacing.radiusLg` (14) |
|
|
34
49
|
*/
|
|
35
50
|
export type ShapeOverrides = {
|
|
36
|
-
button?: {
|
|
37
|
-
/**
|
|
38
|
-
* Border radius applied to every Button preset. Package default:
|
|
39
|
-
* `spacing.radiusMd` (10). Use 9999 for pill buttons. A caller `style`
|
|
40
|
-
* still wins over this, as it always has.
|
|
41
|
-
*/
|
|
42
|
-
borderRadius?: number;
|
|
51
|
+
button?: ShapeSlotOverride & {
|
|
43
52
|
/**
|
|
44
53
|
* Whether the `default` preset renders its shadow. Package default: true.
|
|
45
54
|
* Other presets stay flat regardless; the per-instance `withShadow` prop
|
|
@@ -47,7 +56,13 @@ export type ShapeOverrides = {
|
|
|
47
56
|
*/
|
|
48
57
|
withShadow?: boolean;
|
|
49
58
|
};
|
|
59
|
+
input?: ShapeSlotOverride;
|
|
60
|
+
card?: ShapeSlotOverride;
|
|
61
|
+
sheet?: ShapeSlotOverride;
|
|
62
|
+
badge?: ShapeSlotOverride;
|
|
63
|
+
dialog?: ShapeSlotOverride;
|
|
50
64
|
};
|
|
65
|
+
export type ShapeSlot = keyof ShapeOverrides;
|
|
51
66
|
export type ThemeStore = {
|
|
52
67
|
userTheme: ThemePreference;
|
|
53
68
|
systemTheme: ResolvedTheme;
|
|
@@ -72,11 +87,19 @@ export type ThemeStore = {
|
|
|
72
87
|
*/
|
|
73
88
|
fontOverrides: FontOverrides;
|
|
74
89
|
/**
|
|
75
|
-
* App-injected shape overrides (
|
|
76
|
-
* Same contract as the other override slots: empty by default,
|
|
77
|
-
* backward compatible when unset.
|
|
90
|
+
* App-injected shape overrides (radii per slot, the default Button preset's
|
|
91
|
+
* shadow). Same contract as the other override slots: empty by default,
|
|
92
|
+
* fully backward compatible when unset.
|
|
78
93
|
*/
|
|
79
94
|
shapeOverrides: ShapeOverrides;
|
|
95
|
+
/**
|
|
96
|
+
* Which serif the `serif` variant resolves to when no `setFonts` serif
|
|
97
|
+
* override exists: `"georgia"` (default, the system face) or
|
|
98
|
+
* `"newsreader"`, set by `useResources({ serif: "newsreader" })` once its
|
|
99
|
+
* files or stylesheet are in place. Read by `resolveFontStyle` through
|
|
100
|
+
* `StyledText` and `useFontStyle`.
|
|
101
|
+
*/
|
|
102
|
+
serifPreset: SerifPreset;
|
|
80
103
|
setTheme: (theme: ThemePreference) => void;
|
|
81
104
|
setSystemTheme: (theme: ResolvedTheme) => void;
|
|
82
105
|
/**
|
|
@@ -96,13 +119,43 @@ export type ThemeStore = {
|
|
|
96
119
|
setFonts: (overrides: FontOverrides) => void;
|
|
97
120
|
/**
|
|
98
121
|
* Replace the active shape overrides. Pass `{}` to clear them and fall back
|
|
99
|
-
* to the package defaults (
|
|
122
|
+
* to the package defaults (see the slot table on `ShapeOverrides`: button
|
|
123
|
+
* and input radius 10 = `spacing.radiusMd`, card and dialog 14 =
|
|
124
|
+
* `spacing.radiusLg`, badge pill, default-preset shadow on).
|
|
100
125
|
*/
|
|
101
126
|
setShape: (overrides: ShapeOverrides) => void;
|
|
127
|
+
/** Switch the serif preset. `useResources` calls this; apps rarely need to. */
|
|
128
|
+
setSerifPreset: (preset: SerifPreset) => void;
|
|
102
129
|
loadTheme: () => void;
|
|
103
130
|
};
|
|
104
131
|
export declare function resolveThemePreference(userTheme: ThemePreference, systemTheme: ResolvedTheme): ResolvedTheme;
|
|
105
132
|
export declare const useThemeStore: import("zustand").UseBoundStore<import("zustand").StoreApi<ThemeStore>>;
|
|
106
133
|
export declare function syncSystemTheme(): void;
|
|
134
|
+
/**
|
|
135
|
+
* Keep `systemTheme` following the OS color scheme (`prefers-color-scheme` on
|
|
136
|
+
* web, `Appearance` on native). The first holder reads the current scheme and
|
|
137
|
+
* attaches the one listener; later calls share it.
|
|
138
|
+
*
|
|
139
|
+
* Returns this caller's release: once every holder has released, the
|
|
140
|
+
* listener is removed. Calling a release again is a no-op, so an effect
|
|
141
|
+
* cleanup that runs twice can't drop another caller's hold. On native the
|
|
142
|
+
* package holds the listener for the app's lifetime (see the module-load
|
|
143
|
+
* init below), so an app's release never stops OS tracking there.
|
|
144
|
+
*/
|
|
107
145
|
export declare function startSystemThemeListener(): () => void;
|
|
146
|
+
/**
|
|
147
|
+
* Single entry point for host apps to populate the store from the
|
|
148
|
+
* environment: the OS color-scheme listener plus the persisted preference.
|
|
149
|
+
*
|
|
150
|
+
* Web apps call it once, from a top-level `useEffect` (never during render or
|
|
151
|
+
* at module scope — see below), and return its result as the cleanup:
|
|
152
|
+
* `useEffect(() => syncThemeFromEnvironment(), [])`. Safe to call more than
|
|
153
|
+
* once (StrictMode's double effects, two roots): every call shares the one
|
|
154
|
+
* OS listener, re-reads the persisted preference, and returns its own
|
|
155
|
+
* cleanup, which releases only that call's hold on the listener.
|
|
156
|
+
*
|
|
157
|
+
* The listener starts BEFORE `loadTheme()` on purpose: reading the real OS
|
|
158
|
+
* scheme first means a `system` user resolves straight from the boot-default
|
|
159
|
+
* "light" to their actual scheme in one commit.
|
|
160
|
+
*/
|
|
108
161
|
export declare function syncThemeFromEnvironment(): () => void;
|
package/dist/state/themeStore.js
CHANGED
|
@@ -38,6 +38,10 @@ export const useThemeStore = create((set) => ({
|
|
|
38
38
|
fontOverrides: {},
|
|
39
39
|
// And again for shape: package geometry until a host app calls `setShape`.
|
|
40
40
|
shapeOverrides: {},
|
|
41
|
+
serifPreset: "georgia",
|
|
42
|
+
setSerifPreset: (preset) => {
|
|
43
|
+
set({ serifPreset: preset ?? "georgia" });
|
|
44
|
+
},
|
|
41
45
|
setColors: (overrides) => {
|
|
42
46
|
set({ colorOverrides: overrides ?? {} });
|
|
43
47
|
},
|
|
@@ -89,15 +93,15 @@ export const useThemeStore = create((set) => ({
|
|
|
89
93
|
set({ hasLoadedTheme: true });
|
|
90
94
|
}
|
|
91
95
|
}));
|
|
92
|
-
|
|
96
|
+
// The OS color-scheme listener is shared: one subscription however many
|
|
97
|
+
// callers hold it, removed when the last holder releases. Each
|
|
98
|
+
// `startSystemThemeListener()` call takes a hold and gets its own release.
|
|
99
|
+
let systemThemeSubscription = null;
|
|
100
|
+
let systemThemeHolds = 0;
|
|
93
101
|
export function syncSystemTheme() {
|
|
94
102
|
useThemeStore.getState().setSystemTheme(getSystemTheme());
|
|
95
103
|
}
|
|
96
|
-
|
|
97
|
-
if (stopSystemThemeListener) {
|
|
98
|
-
return stopSystemThemeListener;
|
|
99
|
-
}
|
|
100
|
-
syncSystemTheme();
|
|
104
|
+
function subscribeToSystemTheme() {
|
|
101
105
|
if (Platform.OS === "web" && typeof window !== "undefined" && typeof window.matchMedia === "function") {
|
|
102
106
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
103
107
|
const onChange = () => {
|
|
@@ -105,48 +109,71 @@ export function startSystemThemeListener() {
|
|
|
105
109
|
};
|
|
106
110
|
if (typeof mediaQuery.addEventListener === "function") {
|
|
107
111
|
mediaQuery.addEventListener("change", onChange);
|
|
108
|
-
|
|
109
|
-
mediaQuery.removeEventListener("change", onChange);
|
|
110
|
-
stopSystemThemeListener = null;
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
mediaQuery.addListener(onChange);
|
|
115
|
-
stopSystemThemeListener = () => {
|
|
116
|
-
mediaQuery.removeListener(onChange);
|
|
117
|
-
stopSystemThemeListener = null;
|
|
118
|
-
};
|
|
112
|
+
return { remove: () => mediaQuery.removeEventListener("change", onChange) };
|
|
119
113
|
}
|
|
120
|
-
|
|
114
|
+
mediaQuery.addListener(onChange);
|
|
115
|
+
return { remove: () => mediaQuery.removeListener(onChange) };
|
|
121
116
|
}
|
|
122
|
-
|
|
117
|
+
return Appearance.addChangeListener(({ colorScheme }) => {
|
|
123
118
|
useThemeStore.getState().setSystemTheme(colorScheme === "dark" ? "dark" : "light");
|
|
124
119
|
});
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Keep `systemTheme` following the OS color scheme (`prefers-color-scheme` on
|
|
123
|
+
* web, `Appearance` on native). The first holder reads the current scheme and
|
|
124
|
+
* attaches the one listener; later calls share it.
|
|
125
|
+
*
|
|
126
|
+
* Returns this caller's release: once every holder has released, the
|
|
127
|
+
* listener is removed. Calling a release again is a no-op, so an effect
|
|
128
|
+
* cleanup that runs twice can't drop another caller's hold. On native the
|
|
129
|
+
* package holds the listener for the app's lifetime (see the module-load
|
|
130
|
+
* init below), so an app's release never stops OS tracking there.
|
|
131
|
+
*/
|
|
132
|
+
export function startSystemThemeListener() {
|
|
133
|
+
systemThemeHolds += 1;
|
|
134
|
+
if (systemThemeHolds === 1) {
|
|
135
|
+
syncSystemTheme();
|
|
136
|
+
systemThemeSubscription = subscribeToSystemTheme();
|
|
137
|
+
}
|
|
138
|
+
let released = false;
|
|
139
|
+
return () => {
|
|
140
|
+
if (released)
|
|
141
|
+
return;
|
|
142
|
+
released = true;
|
|
143
|
+
systemThemeHolds -= 1;
|
|
144
|
+
if (systemThemeHolds === 0) {
|
|
145
|
+
systemThemeSubscription?.remove();
|
|
146
|
+
systemThemeSubscription = null;
|
|
147
|
+
}
|
|
128
148
|
};
|
|
129
|
-
return stopSystemThemeListener;
|
|
130
149
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Single entry point for host apps to populate the store from the
|
|
152
|
+
* environment: the OS color-scheme listener plus the persisted preference.
|
|
153
|
+
*
|
|
154
|
+
* Web apps call it once, from a top-level `useEffect` (never during render or
|
|
155
|
+
* at module scope — see below), and return its result as the cleanup:
|
|
156
|
+
* `useEffect(() => syncThemeFromEnvironment(), [])`. Safe to call more than
|
|
157
|
+
* once (StrictMode's double effects, two roots): every call shares the one
|
|
158
|
+
* OS listener, re-reads the persisted preference, and returns its own
|
|
159
|
+
* cleanup, which releases only that call's hold on the listener.
|
|
160
|
+
*
|
|
161
|
+
* The listener starts BEFORE `loadTheme()` on purpose: reading the real OS
|
|
162
|
+
* scheme first means a `system` user resolves straight from the boot-default
|
|
163
|
+
* "light" to their actual scheme in one commit.
|
|
164
|
+
*/
|
|
139
165
|
export function syncThemeFromEnvironment() {
|
|
140
|
-
const
|
|
166
|
+
const release = startSystemThemeListener();
|
|
141
167
|
useThemeStore.getState().loadTheme();
|
|
142
|
-
return
|
|
168
|
+
return release;
|
|
143
169
|
}
|
|
144
170
|
// Native can read persistence at module load, so keep the historical
|
|
145
|
-
// auto-init behavior there
|
|
146
|
-
// `syncThemeFromEnvironment()` from a
|
|
147
|
-
// also evaluated in Node when
|
|
148
|
-
// `
|
|
149
|
-
//
|
|
171
|
+
// auto-init behavior there, with a hold on the OS listener that is never
|
|
172
|
+
// released. On web the host app must call `syncThemeFromEnvironment()` from a
|
|
173
|
+
// top-level `useEffect`: web bundles are also evaluated in Node when
|
|
174
|
+
// `expo export` renders the HTML shell, where `window`/`localStorage` don't
|
|
175
|
+
// exist, and reading them during the browser's first render would disagree
|
|
176
|
+
// with the markup being hydrated.
|
|
150
177
|
if (Platform.OS !== "web") {
|
|
151
178
|
useThemeStore.getState().loadTheme();
|
|
152
179
|
startSystemThemeListener();
|
package/llms-full.md
CHANGED
|
@@ -37,7 +37,8 @@ import { Button, colors, UIProvider, useTheme } from "@mrmeg/expo-ui";
|
|
|
37
37
|
|
|
38
38
|
Call `useResources()` once near the Expo app root; it resolves `{ loaded, error }`
|
|
39
39
|
and loads the four static Inter weights (web gets a single Google Fonts Inter
|
|
40
|
-
stylesheet). Icons are
|
|
40
|
+
stylesheet). Icons are SVG drawn from glyph data, so there is no icon font to load;
|
|
41
|
+
call `registerIcons(appIcons)` once at module scope in the root layout (see `Icon`).
|
|
41
42
|
|
|
42
43
|
Mount `UIProvider` once near the root. It owns the package `Notification`,
|
|
43
44
|
`StatusBar`, default `@rn-primitives` portal host, and native keyboard-avoiding
|
|
@@ -90,6 +91,52 @@ When the saved theme preference is `system`, the package theme store owns OS
|
|
|
90
91
|
color-scheme sync, including web `prefers-color-scheme`. Apps must not add their
|
|
91
92
|
own `Appearance` or `matchMedia` listeners for package components.
|
|
92
93
|
|
|
94
|
+
## Screen Layout
|
|
95
|
+
|
|
96
|
+
Screens are flat. On a 390 pt phone each nested inset comes out of the content:
|
|
97
|
+
rows boxed in a bordered group inside a padded screen start 33 pt from each edge
|
|
98
|
+
(16 + 1 + 16) with 324 pt left for them; the same rows in an `ItemGroup` start
|
|
99
|
+
16 pt in with 358 pt.
|
|
100
|
+
|
|
101
|
+
- One horizontal inset per screen: `spacing.screenPadding` (16). Either a
|
|
102
|
+
container pads or its children do, never both. `Item` rows and `ItemGroup`
|
|
103
|
+
titles and footers carry the 16 themselves, so a scroll view of groups gets
|
|
104
|
+
no horizontal padding; text, forms, and buttons between them pad their own
|
|
105
|
+
wrapper.
|
|
106
|
+
- No boxes as layout: no bordered, shadowed, or tinted rounded panel (or `Card`)
|
|
107
|
+
around a section, a form, or a group of rows. Separate sections with a header,
|
|
108
|
+
`spacing.sectionSpacing` (24), or a hairline (`Separator`).
|
|
109
|
+
- Lists and settings are `ItemGroup` + `Item`: full-width rows and touch
|
|
110
|
+
targets, hairlines inset under the title, no card per row, no border around
|
|
111
|
+
the group. Rows are direct children; the group separates them.
|
|
112
|
+
- Forms: fields span the column, grouped under section headers, not cards.
|
|
113
|
+
- Primary content (photos, video, maps, QR codes, charts) sizes to the column
|
|
114
|
+
width, never a fixed size floating in whitespace.
|
|
115
|
+
- Wide screens cap and centre the column (`width: "100%", maxWidth: 640,
|
|
116
|
+
alignSelf: "center"` on the scroll content; 960 for dense grids) instead of
|
|
117
|
+
boxing it.
|
|
118
|
+
- `Card` is for one item in a collection (feed entry, grid tile, carousel slide)
|
|
119
|
+
or a single tappable object; `StatCard` likewise (a scrolling metrics rail, a
|
|
120
|
+
tappable metric). Never nest them.
|
|
121
|
+
- Dialog, sheet, and popover chrome keep their surfaces.
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
<ScrollView contentContainerStyle={{ width: "100%", maxWidth: 640, alignSelf: "center", paddingVertical: spacing.md, gap: spacing.sectionSpacing }}>
|
|
125
|
+
<ItemGroup title="Account" footer="Signed in as jane@example.com">
|
|
126
|
+
<Item onPress={openProfile}>
|
|
127
|
+
<ItemMedia icon="user" />
|
|
128
|
+
<ItemContent><ItemTitle>Edit profile</ItemTitle></ItemContent>
|
|
129
|
+
<ItemActions><Icon name="chevron-right" size={18} color="mutedForeground" /></ItemActions>
|
|
130
|
+
</Item>
|
|
131
|
+
<Item>
|
|
132
|
+
<ItemMedia icon="bell" />
|
|
133
|
+
<ItemContent><ItemTitle>Notifications</ItemTitle></ItemContent>
|
|
134
|
+
<ItemActions><Switch checked={enabled} onCheckedChange={setEnabled} /></ItemActions>
|
|
135
|
+
</Item>
|
|
136
|
+
</ItemGroup>
|
|
137
|
+
</ScrollView>
|
|
138
|
+
```
|
|
139
|
+
|
|
93
140
|
## Component Catalog
|
|
94
141
|
|
|
95
142
|
Every component is exported from `@mrmeg/expo-ui/components`, and from
|
|
@@ -105,7 +152,7 @@ before creating a new app-local primitive.
|
|
|
105
152
|
| `Badge` | Short status labels | Prefer over custom pill views. |
|
|
106
153
|
| `BottomSheet` | Mobile-first modal sheets | Requires root `UIProvider`. Native sheet via `@expo/ui`; `swipeEnabled`, `avoidKeyboard`, `dismissKeyboardOnDrag` are accepted but ignored (the iOS sheet presentation and Android Material3 keep content above the keyboard; never nest a `KeyboardAvoidingView` in a sheet). |
|
|
107
154
|
| `Button` | Commands and CTAs | Use `preset`, not `variant`; heights are 28/32/40. |
|
|
108
|
-
| `Card` |
|
|
155
|
+
| `Card` | One item in a collection (feed entry, grid tile, carousel slide) or a single tappable object | Not a layout box: never around a section, form, or row group; never nested. Parts pad `spacing.cardPadding`. |
|
|
109
156
|
| `Carousel` | Small known set of horizontally snapping slides | Renders every child (no virtualization); fractional `itemWidth` measures the viewport until first layout. |
|
|
110
157
|
| `Checkbox` | Boolean selection in forms or lists | Prefer over custom checkmark controls. |
|
|
111
158
|
| `Collapsible` | One-off disclosure | Use for advanced settings or helper sections. |
|
|
@@ -115,9 +162,10 @@ before creating a new app-local primitive.
|
|
|
115
162
|
| `DropdownMenu` | Menus and command lists | Requires root `UIProvider` portal host. |
|
|
116
163
|
| `EmptyState` | No-data or recoverable error regions | Props: `icon`, `title`, `description`, `actionLabel`, `onAction`, `actionPreset`. |
|
|
117
164
|
| `ErrorBoundary` | React render error fallback | Use for route or feature boundaries. |
|
|
118
|
-
| `Icon` | Lucide or custom
|
|
165
|
+
| `Icon` | Registered Lucide glyphs or custom art with theme tokens | `name` is a builtin or a name in `client/icons.json` (`bunx expo-ui-icons add <name>`); `color` takes a theme color name; an unlabeled icon is hidden from a11y. |
|
|
119
166
|
| `InputOTP` | Verification code entry | Prefer over manually managed text input groups. |
|
|
120
167
|
| `Item` | List / settings rows | Applies the row density tokens and a 44px native hit area (40px on web). |
|
|
168
|
+
| `ItemGroup` | A titled group of full-width `Item` rows | Props: `title`, `description`, `footer`, `style`, `testID`. Draws inset hairlines between rows; no border, fill, or horizontal padding, so the parent must not pad either. |
|
|
121
169
|
| `KeyboardAvoidingView` | Native keyboard-aware roots, composer footers, form-heavy subtrees | `UIProvider` already mounts one root; use directly only for custom subtrees. |
|
|
122
170
|
| `Label` | Accessible form labels | Two distinct ids: `nativeID` is the label's, `htmlFor` is the input's `nativeID`. Never reuse one id for both. |
|
|
123
171
|
| `MaxWidthContainer` | Centered responsive width | Use for web and tablet constrained layouts. |
|
|
@@ -131,7 +179,7 @@ before creating a new app-local primitive.
|
|
|
131
179
|
| `Separator` | Horizontal or vertical dividers | Prefer over border-only spacer views. |
|
|
132
180
|
| `Skeleton` | Loading placeholders | Use stable dimensions to avoid layout shift. |
|
|
133
181
|
| `Slider` | Numeric value selection | Backed by `@expo/ui`; prefer over custom pan gesture tracks. |
|
|
134
|
-
| `StatCard` | Metric tile | Props: `label`, `value`, `unit`, `change` (`{ value, direction }`), `icon`, `onPress`. |
|
|
182
|
+
| `StatCard` | Metric tile in a collection, or one tappable metric | Props: `label`, `value`, `unit`, `change` (`{ value, direction }`), `icon`, `onPress`. A Card, so the same rule applies. |
|
|
135
183
|
| `StatusBar` | Theme-aware native status bar | Usually mounted through `UIProvider`. |
|
|
136
184
|
| `StyledText` | Theme-aware typography | Prefer semantic aliases over raw `Text`. |
|
|
137
185
|
| `Switch` | Binary settings | Prefer over custom toggles for on/off state. |
|
|
@@ -156,9 +204,10 @@ option sets.
|
|
|
156
204
|
`Dialog` blocking decisions · `Popover` contextual controls · `Tooltip` short
|
|
157
205
|
explanations · `DropdownMenu` action lists.
|
|
158
206
|
|
|
159
|
-
`
|
|
160
|
-
|
|
161
|
-
|
|
207
|
+
`ItemGroup` + `Item` lists and settings · `Card` one item in a collection or one
|
|
208
|
+
tappable object, never a wrapper around a section, form, or row group, never
|
|
209
|
+
nested · `EmptyState` no-data or recoverable errors · `Skeleton` loading content
|
|
210
|
+
with stable layout · `Progress` real or indeterminate progress.
|
|
162
211
|
|
|
163
212
|
## Notifications
|
|
164
213
|
|
|
@@ -196,6 +245,6 @@ selectors and tests. Use `notify` for imperative triggers in app code.
|
|
|
196
245
|
## Validation
|
|
197
246
|
|
|
198
247
|
Changing package code or shipped docs? Run the gates from the monorepo root:
|
|
199
|
-
`
|
|
200
|
-
|
|
201
|
-
|
|
248
|
+
`typecheck`, `test`, `build`, `pack`, `consumer-smoke` (`bun run pkg ui <gate>`).
|
|
249
|
+
Docs-only changes need at least `bun run pkg ui pack`, which proves the new docs
|
|
250
|
+
land in the npm tarball.
|
package/llms.txt
CHANGED
|
@@ -30,7 +30,16 @@ gestures and keyboard avoidance; swipeEnabled, avoidKeyboard, and
|
|
|
30
30
|
dismissKeyboardOnDrag are accepted but ignored. On iOS the sheet presentation
|
|
31
31
|
lifts or shrinks the hosted content so Footer and Body end at the keyboard's
|
|
32
32
|
top (device-verified); Android avoidance is Material3's. Do not nest a
|
|
33
|
-
KeyboardAvoidingView inside a sheet.
|
|
33
|
+
KeyboardAvoidingView inside a sheet. Open the next modal from the sheet's
|
|
34
|
+
onDismissed callback (fires once the sheet is fully gone), not from
|
|
35
|
+
onOpenChange(false).
|
|
36
|
+
|
|
37
|
+
Screens are flat: one 16 pt horizontal inset (spacing.screenPadding), either
|
|
38
|
+
on the container or on its children, never both. Lists and settings are
|
|
39
|
+
ItemGroup + Item (full-width rows, inset hairlines), never rows inside a
|
|
40
|
+
bordered, shadowed, or tinted panel. Card is for one item in a collection or one
|
|
41
|
+
tappable object; never nest Cards. Media and charts fill the column width; wide
|
|
42
|
+
screens cap and centre the column. LLM_USAGE.md "Screen Layout" has the rules.
|
|
34
43
|
|
|
35
44
|
Use useTheme(), useStyles(), semantic tokens, StyledText, and package controls.
|
|
36
45
|
Do not add app-local Appearance or matchMedia listeners for theme sync. On web
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrmeg/expo-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Reusable Expo and React Native UI primitives for MrMeg projects.",
|
|
6
6
|
"keywords": [
|
|
@@ -29,7 +29,15 @@
|
|
|
29
29
|
},
|
|
30
30
|
"main": "./dist/index.js",
|
|
31
31
|
"types": "./dist/index.d.ts",
|
|
32
|
+
"bin": {
|
|
33
|
+
"expo-ui-icons": "./bin/expo-ui-icons.mjs"
|
|
34
|
+
},
|
|
35
|
+
"sideEffects": [
|
|
36
|
+
"./src/state/themeStore.ts",
|
|
37
|
+
"./dist/state/themeStore.js"
|
|
38
|
+
],
|
|
32
39
|
"files": [
|
|
40
|
+
"bin",
|
|
33
41
|
"dist",
|
|
34
42
|
"package.json",
|
|
35
43
|
"README.md",
|
|
@@ -40,42 +48,110 @@
|
|
|
40
48
|
],
|
|
41
49
|
"exports": {
|
|
42
50
|
".": {
|
|
51
|
+
"@mrmeg/source": "./src/index.ts",
|
|
43
52
|
"types": "./dist/index.d.ts",
|
|
44
53
|
"default": "./dist/index.js"
|
|
45
54
|
},
|
|
46
55
|
"./components": {
|
|
56
|
+
"@mrmeg/source": "./src/components/index.ts",
|
|
47
57
|
"types": "./dist/components/index.d.ts",
|
|
48
58
|
"default": "./dist/components/index.js"
|
|
49
59
|
},
|
|
60
|
+
"./components/bottomSheetDismiss": {
|
|
61
|
+
"@mrmeg/source": "./src/components/bottomSheetDismiss.ts",
|
|
62
|
+
"types": "./dist/components/bottomSheetDismiss.d.ts",
|
|
63
|
+
"default": "./dist/components/bottomSheetDismiss.js"
|
|
64
|
+
},
|
|
65
|
+
"./components/bottomSheetHostSupport": {
|
|
66
|
+
"@mrmeg/source": "./src/components/bottomSheetHostSupport.ts",
|
|
67
|
+
"types": "./dist/components/bottomSheetHostSupport.d.ts",
|
|
68
|
+
"default": "./dist/components/bottomSheetHostSupport.js"
|
|
69
|
+
},
|
|
70
|
+
"./components/iconBuiltins.generated": {
|
|
71
|
+
"@mrmeg/source": "./src/components/iconBuiltins.generated.ts",
|
|
72
|
+
"types": "./dist/components/iconBuiltins.generated.d.ts",
|
|
73
|
+
"default": "./dist/components/iconBuiltins.generated.js"
|
|
74
|
+
},
|
|
75
|
+
"./components/iconTypes": {
|
|
76
|
+
"@mrmeg/source": "./src/components/iconTypes.ts",
|
|
77
|
+
"types": "./dist/components/iconTypes.d.ts",
|
|
78
|
+
"default": "./dist/components/iconTypes.js"
|
|
79
|
+
},
|
|
80
|
+
"./components/index": {
|
|
81
|
+
"@mrmeg/source": "./src/components/index.ts",
|
|
82
|
+
"types": "./dist/components/index.d.ts",
|
|
83
|
+
"default": "./dist/components/index.js"
|
|
84
|
+
},
|
|
85
|
+
"./components/keyboardController": {
|
|
86
|
+
"@mrmeg/source": {
|
|
87
|
+
"react-native": "./src/components/keyboardController.native.ts",
|
|
88
|
+
"default": "./src/components/keyboardController.ts"
|
|
89
|
+
},
|
|
90
|
+
"types": "./dist/components/keyboardController.d.ts",
|
|
91
|
+
"default": "./dist/components/keyboardController.js"
|
|
92
|
+
},
|
|
93
|
+
"./components/keyboardDismiss": {
|
|
94
|
+
"@mrmeg/source": "./src/components/keyboardDismiss.ts",
|
|
95
|
+
"types": "./dist/components/keyboardDismiss.d.ts",
|
|
96
|
+
"default": "./dist/components/keyboardDismiss.js"
|
|
97
|
+
},
|
|
98
|
+
"./components/keyboardFocusRegistry": {
|
|
99
|
+
"@mrmeg/source": "./src/components/keyboardFocusRegistry.ts",
|
|
100
|
+
"types": "./dist/components/keyboardFocusRegistry.d.ts",
|
|
101
|
+
"default": "./dist/components/keyboardFocusRegistry.js"
|
|
102
|
+
},
|
|
103
|
+
"./components/StyledText.context": {
|
|
104
|
+
"@mrmeg/source": "./src/components/StyledText.context.ts",
|
|
105
|
+
"types": "./dist/components/StyledText.context.d.ts",
|
|
106
|
+
"default": "./dist/components/StyledText.context.js"
|
|
107
|
+
},
|
|
50
108
|
"./components/*": {
|
|
109
|
+
"@mrmeg/source": "./src/components/*.tsx",
|
|
51
110
|
"types": "./dist/components/*.d.ts",
|
|
52
111
|
"default": "./dist/components/*.js"
|
|
53
112
|
},
|
|
54
113
|
"./constants": {
|
|
114
|
+
"@mrmeg/source": "./src/constants/index.ts",
|
|
55
115
|
"types": "./dist/constants/index.d.ts",
|
|
56
116
|
"default": "./dist/constants/index.js"
|
|
57
117
|
},
|
|
58
118
|
"./constants/*": {
|
|
119
|
+
"@mrmeg/source": "./src/constants/*.ts",
|
|
59
120
|
"types": "./dist/constants/*.d.ts",
|
|
60
121
|
"default": "./dist/constants/*.js"
|
|
61
122
|
},
|
|
62
123
|
"./hooks": {
|
|
124
|
+
"@mrmeg/source": "./src/hooks/index.ts",
|
|
63
125
|
"types": "./dist/hooks/index.d.ts",
|
|
64
126
|
"default": "./dist/hooks/index.js"
|
|
65
127
|
},
|
|
128
|
+
"./hooks/useReduceMotion": {
|
|
129
|
+
"@mrmeg/source": "./src/hooks/useReduceMotion.tsx",
|
|
130
|
+
"types": "./dist/hooks/useReduceMotion.d.ts",
|
|
131
|
+
"default": "./dist/hooks/useReduceMotion.js"
|
|
132
|
+
},
|
|
66
133
|
"./hooks/*": {
|
|
134
|
+
"@mrmeg/source": "./src/hooks/*.ts",
|
|
67
135
|
"types": "./dist/hooks/*.d.ts",
|
|
68
136
|
"default": "./dist/hooks/*.js"
|
|
69
137
|
},
|
|
70
138
|
"./state": {
|
|
139
|
+
"@mrmeg/source": "./src/state/index.ts",
|
|
71
140
|
"types": "./dist/state/index.d.ts",
|
|
72
141
|
"default": "./dist/state/index.js"
|
|
73
142
|
},
|
|
143
|
+
"./state/themeColorScope": {
|
|
144
|
+
"@mrmeg/source": "./src/state/themeColorScope.tsx",
|
|
145
|
+
"types": "./dist/state/themeColorScope.d.ts",
|
|
146
|
+
"default": "./dist/state/themeColorScope.js"
|
|
147
|
+
},
|
|
74
148
|
"./state/*": {
|
|
149
|
+
"@mrmeg/source": "./src/state/*.ts",
|
|
75
150
|
"types": "./dist/state/*.d.ts",
|
|
76
151
|
"default": "./dist/state/*.js"
|
|
77
152
|
},
|
|
78
153
|
"./lib": {
|
|
154
|
+
"@mrmeg/source": "./src/lib/index.ts",
|
|
79
155
|
"types": "./dist/lib/index.d.ts",
|
|
80
156
|
"default": "./dist/lib/index.js"
|
|
81
157
|
},
|
|
@@ -86,7 +162,7 @@
|
|
|
86
162
|
"scripts": {
|
|
87
163
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
88
164
|
"test": "jest --config ../../jest.config.js packages/ui/src --runInBand --watchman=false && bun run check:forbidden-imports",
|
|
89
|
-
"build": "node ../../scripts/build-icon-registry.mjs && rm -rf dist && tsc -p tsconfig.build.json && node ../../scripts/fix-package-esm.mjs ui && node ../../scripts/build-design-system-manifest.mjs ui && bun run check:forbidden-imports",
|
|
165
|
+
"build": "node ../../scripts/build-icon-registry.mjs && rm -rf dist && tsc -p tsconfig.build.json && mkdir -p dist/icons && cp src/icons/lucide.json src/icons/LICENSE.lucide dist/icons/ && node ../../scripts/fix-package-esm.mjs ui && node ../../scripts/build-design-system-manifest.mjs ui && bun run check:forbidden-imports",
|
|
90
166
|
"check:forbidden-imports": "node ../../scripts/check-ui-forbidden-imports.mjs",
|
|
91
167
|
"publish:dry-run": "bun pm pack --dry-run"
|
|
92
168
|
},
|
|
@@ -117,7 +193,6 @@
|
|
|
117
193
|
"expo": ">=56.0.0 <59.0.0 || >=58.0.0-0 <59.0.0",
|
|
118
194
|
"expo-font": ">=56.0.0 <59.0.0 || >=58.0.0-0 <59.0.0",
|
|
119
195
|
"expo-haptics": ">=56.0.0 <59.0.0 || >=58.0.0-0 <59.0.0",
|
|
120
|
-
"lucide-react-native": ">=1.46.0 <2.0.0",
|
|
121
196
|
"react": ">=19.2.0 <20.0.0",
|
|
122
197
|
"react-native": ">=0.85.0 <0.89.0 || >=0.88.0-0 <0.89.0",
|
|
123
198
|
"react-native-gesture-handler": ">=2.30.0 <4.0.0",
|
|
@@ -129,7 +204,7 @@
|
|
|
129
204
|
"zustand": ">=5.0.0 <6.0.0"
|
|
130
205
|
},
|
|
131
206
|
"devDependencies": {
|
|
132
|
-
"@types/react": "~19.
|
|
207
|
+
"@types/react": "~19.3.0",
|
|
133
208
|
"typescript": "~6.0.3"
|
|
134
209
|
}
|
|
135
210
|
}
|