@mrmeg/expo-ui 0.25.1 → 0.27.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "package": "@mrmeg/expo-ui",
4
- "version": "0.25.1",
4
+ "version": "0.27.0",
5
5
  "tokens": {
6
6
  "spacing": {
7
7
  "entries": [
@@ -2232,5 +2232,154 @@
2232
2232
  "variantValues": null,
2233
2233
  "sizeValues": null
2234
2234
  }
2235
- ]
2235
+ ],
2236
+ "icons": {
2237
+ "names": [
2238
+ "archive",
2239
+ "arrow-down",
2240
+ "arrow-left",
2241
+ "arrow-right",
2242
+ "arrow-up",
2243
+ "award",
2244
+ "beer",
2245
+ "bell",
2246
+ "bell-off",
2247
+ "bold",
2248
+ "bookmark",
2249
+ "box",
2250
+ "briefcase",
2251
+ "calendar",
2252
+ "calendar-check",
2253
+ "camera",
2254
+ "camera-off",
2255
+ "chart-no-axes-column",
2256
+ "check",
2257
+ "chevron-down",
2258
+ "chevron-left",
2259
+ "chevron-right",
2260
+ "chevron-up",
2261
+ "circle-alert",
2262
+ "circle-check-big",
2263
+ "circle-plus",
2264
+ "circle-question-mark",
2265
+ "circle-stop",
2266
+ "circle-x",
2267
+ "clock",
2268
+ "cloud",
2269
+ "cloud-off",
2270
+ "code",
2271
+ "coffee",
2272
+ "compass",
2273
+ "copy",
2274
+ "credit-card",
2275
+ "crosshair",
2276
+ "database",
2277
+ "dollar-sign",
2278
+ "download",
2279
+ "droplet",
2280
+ "ellipsis-vertical",
2281
+ "external-link",
2282
+ "eye",
2283
+ "eye-off",
2284
+ "face-slightly-smiling",
2285
+ "file",
2286
+ "file-text",
2287
+ "folder",
2288
+ "gift",
2289
+ "git-branch",
2290
+ "git-pull-request",
2291
+ "globe",
2292
+ "grid-3x3",
2293
+ "hash",
2294
+ "heart",
2295
+ "hexagon",
2296
+ "house",
2297
+ "image",
2298
+ "inbox",
2299
+ "info",
2300
+ "italic",
2301
+ "key",
2302
+ "layers",
2303
+ "layout-grid",
2304
+ "link",
2305
+ "link-2",
2306
+ "list",
2307
+ "loader",
2308
+ "lock",
2309
+ "lock-open",
2310
+ "log-in",
2311
+ "log-out",
2312
+ "mail",
2313
+ "map",
2314
+ "map-pin",
2315
+ "maximize",
2316
+ "maximize-2",
2317
+ "menu",
2318
+ "message-circle",
2319
+ "message-square",
2320
+ "mic",
2321
+ "mic-off",
2322
+ "minus",
2323
+ "monitor",
2324
+ "moon",
2325
+ "music",
2326
+ "navigation",
2327
+ "package",
2328
+ "panel-left",
2329
+ "panels-top-left",
2330
+ "party-popper",
2331
+ "pen",
2332
+ "pen-line",
2333
+ "pencil",
2334
+ "percent",
2335
+ "phone",
2336
+ "play",
2337
+ "plus",
2338
+ "printer",
2339
+ "radio",
2340
+ "refresh-cw",
2341
+ "repeat",
2342
+ "rotate-cw",
2343
+ "search",
2344
+ "send",
2345
+ "server",
2346
+ "settings",
2347
+ "share",
2348
+ "share-2",
2349
+ "shield",
2350
+ "shield-off",
2351
+ "shopping-bag",
2352
+ "shopping-cart",
2353
+ "shuffle",
2354
+ "slash",
2355
+ "sliders-horizontal",
2356
+ "sliders-vertical",
2357
+ "smartphone",
2358
+ "square",
2359
+ "square-check-big",
2360
+ "square-pen",
2361
+ "star",
2362
+ "sun",
2363
+ "tag",
2364
+ "terminal",
2365
+ "thumbs-up",
2366
+ "trash",
2367
+ "trending-down",
2368
+ "trending-up",
2369
+ "triangle-alert",
2370
+ "truck",
2371
+ "underline",
2372
+ "upload",
2373
+ "user",
2374
+ "user-check",
2375
+ "user-minus",
2376
+ "user-plus",
2377
+ "users",
2378
+ "video",
2379
+ "wifi-off",
2380
+ "wrench",
2381
+ "x",
2382
+ "zap"
2383
+ ]
2384
+ }
2236
2385
  }
@@ -1,20 +1,3 @@
1
- /**
2
- * Registers the Feather icon font in expo-font's server store during
3
- * export-time rendering (`expo export` renders every route's HTML shell in
4
- * Node, where the module-scope client load above is guarded off).
5
- *
6
- * Must be called from a **render body** (see `useResources` below): expo-font
7
- * scopes its server store per render context, so a module-scope registration
8
- * lands in at most one route's shell. Registering during render means every
9
- * exported shell emits the `@font-face` rule and renders `<Icon>` glyphs —
10
- * without it the shell contains an EMPTY icon text node while the client's
11
- * first render (font pre-loaded above) contains the glyph, and React throws
12
- * hydration error #418 on every cold load and regenerates the tree.
13
- *
14
- * No-op on native and on client web (both already covered: native by the
15
- * effect below, client web by the module-scope call above).
16
- */
17
- export declare function ensureIconFontRegistered(): void;
18
1
  interface LoadResourcesResult {
19
2
  loaded: boolean;
20
3
  error: Error | null;
@@ -27,12 +10,16 @@ interface LoadResourcesResult {
27
10
  * font files. Web loads Inter from Google Fonts as a single CSS family;
28
11
  * weight differentiation there comes from a numeric fontWeight instead.
29
12
  *
13
+ * Icons need nothing here: `Icon` renders `lucide-react-native` SVGs, which
14
+ * have no font face to register on any platform, so server-rendered HTML
15
+ * carries the glyph markup and hydrates without a font round trip.
16
+ *
30
17
  * A host app that overrides the sans-serif families via `setFonts` owns
31
18
  * loading its own faces (typically through `expo-font`), so the Inter fetch
32
19
  * is skipped entirely — nothing would reference those files. For the skip to
33
20
  * apply, call `setFonts` before this hook mounts (module scope or ahead of
34
21
  * rendering the root); a later call still re-skins text, it just doesn't
35
- * un-download Inter. The Feather icon font always loads.
22
+ * un-download Inter.
36
23
  */
37
24
  export declare const useResources: () => LoadResourcesResult;
38
25
  export {};
@@ -1,46 +1,8 @@
1
1
  import { useEffect, useState } from "react";
2
2
  import * as Font from "expo-font";
3
- import Feather from "@expo/vector-icons/Feather";
4
3
  import { Inter_400Regular, Inter_500Medium, Inter_600SemiBold, Inter_700Bold, } from "@expo-google-fonts/inter";
5
4
  import { Platform } from "react-native";
6
5
  import { useThemeStore } from "../state/themeStore.js";
7
- // Eager, module-scope load that primes the Feather font before first render:
8
- // on web it synchronously injects the @font-face rule, so icons have their
9
- // glyphs on the first paint; on native it just starts the fetch ahead of the
10
- // effect below (which then no-ops for the already-loaded font).
11
- //
12
- // Guarded on `window` because web bundles are also evaluated in Node when
13
- // `expo export` renders the HTML shell, where there is no document to inject
14
- // into.
15
- if (typeof window !== "undefined") {
16
- void Font.loadAsync(Feather.font);
17
- }
18
- /**
19
- * Registers the Feather icon font in expo-font's server store during
20
- * export-time rendering (`expo export` renders every route's HTML shell in
21
- * Node, where the module-scope client load above is guarded off).
22
- *
23
- * Must be called from a **render body** (see `useResources` below): expo-font
24
- * scopes its server store per render context, so a module-scope registration
25
- * lands in at most one route's shell. Registering during render means every
26
- * exported shell emits the `@font-face` rule and renders `<Icon>` glyphs —
27
- * without it the shell contains an EMPTY icon text node while the client's
28
- * first render (font pre-loaded above) contains the glyph, and React throws
29
- * hydration error #418 on every cold load and regenerates the tree.
30
- *
31
- * No-op on native and on client web (both already covered: native by the
32
- * effect below, client web by the module-scope call above).
33
- */
34
- export function ensureIconFontRegistered() {
35
- if (Platform.OS !== "web" || typeof window !== "undefined")
36
- return;
37
- if (Font.isLoaded("feather"))
38
- return;
39
- // Synchronous on server-web (expo-font routes to registerStaticFont). No
40
- // `void`/try-catch: a throw here means we're outside a render scope, which
41
- // must be loud.
42
- Font.loadAsync(Feather.font);
43
- }
44
6
  // The four static Inter weights StyledText's native family keys point at
45
7
  // (see constants/fonts.ts). Native-only: web never renders these family names
46
8
  // ("Inter_400Regular" etc.) — fontFamilies.sansSerif resolves every weight to
@@ -86,17 +48,18 @@ function ensureWebFontStylesheet() {
86
48
  * font files. Web loads Inter from Google Fonts as a single CSS family;
87
49
  * weight differentiation there comes from a numeric fontWeight instead.
88
50
  *
51
+ * Icons need nothing here: `Icon` renders `lucide-react-native` SVGs, which
52
+ * have no font face to register on any platform, so server-rendered HTML
53
+ * carries the glyph markup and hydrates without a font round trip.
54
+ *
89
55
  * A host app that overrides the sans-serif families via `setFonts` owns
90
56
  * loading its own faces (typically through `expo-font`), so the Inter fetch
91
57
  * is skipped entirely — nothing would reference those files. For the skip to
92
58
  * apply, call `setFonts` before this hook mounts (module scope or ahead of
93
59
  * rendering the root); a later call still re-skins text, it just doesn't
94
- * un-download Inter. The Feather icon font always loads.
60
+ * un-download Inter.
95
61
  */
96
62
  export const useResources = () => {
97
- // Export-time renders need the icon font registered per render scope; the
98
- // module-scope call only reaches the client. See ensureIconFontRegistered.
99
- ensureIconFontRegistered();
100
63
  const [loaded, setLoaded] = useState(false);
101
64
  const [error, setError] = useState(null);
102
65
  useEffect(() => {
@@ -107,7 +70,6 @@ export const useResources = () => {
107
70
  async function loadResourcesAndDataAsync() {
108
71
  try {
109
72
  const fontPromise = Promise.all([
110
- Font.loadAsync(Feather.font),
111
73
  sansSerifOverridden ? Promise.resolve() : loadNativeInterFonts(),
112
74
  sansSerifOverridden ? Promise.resolve() : ensureWebFontStylesheet(),
113
75
  ]);
package/llms-full.md CHANGED
@@ -36,8 +36,8 @@ import { Button, colors, UIProvider, useTheme } from "@mrmeg/expo-ui";
36
36
  ## App Setup
37
37
 
38
38
  Call `useResources()` once near the Expo app root; it resolves `{ loaded, error }`
39
- and loads the Feather icon font plus the four static Inter weights (web gets a
40
- single Google Fonts Inter stylesheet).
39
+ and loads the four static Inter weights (web gets a single Google Fonts Inter
40
+ stylesheet). Icons are `lucide-react-native` SVGs, so there is no icon font to load.
41
41
 
42
42
  Mount `UIProvider` once near the root. It owns the package `Notification`,
43
43
  `StatusBar`, default `@rn-primitives` portal host, and native keyboard-avoiding
@@ -103,7 +103,7 @@ before creating a new app-local primitive.
103
103
  | `AnimatedView` | Entrance and visibility animation | Keep simple reveal effects in this wrapper. |
104
104
  | `Avatar`, `AvatarGroup` | Profile images with initials/icon fallback | Pass both `source` and `name`; set `size`/`shape` on the group, not per child. |
105
105
  | `Badge` | Short status labels | Prefer over custom pill views. |
106
- | `BottomSheet` | Mobile-first modal sheets | Requires root `UIProvider`. Native sheet via `@expo/ui`; `swipeEnabled`, `avoidKeyboard`, `dismissKeyboardOnDrag` are accepted but ignored. |
106
+ | `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
107
  | `Button` | Commands and CTAs | Use `preset`, not `variant`; heights are 28/32/40. |
108
108
  | `Card` | Individual framed content groups | Do not wrap whole page sections in cards. |
109
109
  | `Carousel` | Small known set of horizontally snapping slides | Renders every child (no virtualization); fractional `itemWidth` measures the viewport until first layout. |
@@ -115,7 +115,7 @@ before creating a new app-local primitive.
115
115
  | `DropdownMenu` | Menus and command lists | Requires root `UIProvider` portal host. |
116
116
  | `EmptyState` | No-data or recoverable error regions | Props: `icon`, `title`, `description`, `actionLabel`, `onAction`, `actionPreset`. |
117
117
  | `ErrorBoundary` | React render error fallback | Use for route or feature boundaries. |
118
- | `Icon` | Feather or custom icons with theme tokens | `color` takes a theme color name; pass `decorative` to hide from a11y. |
118
+ | `Icon` | Lucide or custom icons with theme tokens | `color` takes a theme color name; pass `decorative` to hide from a11y. |
119
119
  | `InputOTP` | Verification code entry | Prefer over manually managed text input groups. |
120
120
  | `Item` | List / settings rows | Applies the row density tokens and a 44px native hit area (40px on web). |
121
121
  | `KeyboardAvoidingView` | Native keyboard-aware roots, composer footers, form-heavy subtrees | `UIProvider` already mounts one root; use directly only for custom subtrees. |
package/llms.txt CHANGED
@@ -27,7 +27,10 @@ mount react-native-keyboard-controller's KeyboardProvider above UIProvider.
27
27
 
28
28
  BottomSheet renders the platform's native sheet via @expo/ui, which owns
29
29
  gestures and keyboard avoidance; swipeEnabled, avoidKeyboard, and
30
- dismissKeyboardOnDrag are accepted but ignored.
30
+ dismissKeyboardOnDrag are accepted but ignored. On iOS the sheet presentation
31
+ lifts or shrinks the hosted content so Footer and Body end at the keyboard's
32
+ top (device-verified); Android avoidance is Material3's. Do not nest a
33
+ KeyboardAvoidingView inside a sheet.
31
34
 
32
35
  Use useTheme(), useStyles(), semantic tokens, StyledText, and package controls.
33
36
  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.25.1",
3
+ "version": "0.27.0",
4
4
  "private": false,
5
5
  "description": "Reusable Expo and React Native UI primitives for MrMeg projects.",
6
6
  "keywords": [
@@ -86,13 +86,12 @@
86
86
  "scripts": {
87
87
  "typecheck": "tsc --noEmit -p tsconfig.json",
88
88
  "test": "jest --config ../../jest.config.js packages/ui/src --runInBand --watchman=false && bun run check:forbidden-imports",
89
- "build": "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",
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",
90
90
  "check:forbidden-imports": "node ../../scripts/check-ui-forbidden-imports.mjs",
91
91
  "publish:dry-run": "bun pm pack --dry-run"
92
92
  },
93
93
  "dependencies": {
94
94
  "@expo-google-fonts/inter": "^0.4.2",
95
- "@expo/vector-icons": ">=15.0.0 <16.0.0",
96
95
  "@rn-primitives/accordion": "~1.5.2",
97
96
  "@rn-primitives/alert-dialog": "~1.5.2",
98
97
  "@rn-primitives/checkbox": "~1.5.2",
@@ -118,12 +117,14 @@
118
117
  "expo": ">=56.0.0 <59.0.0 || >=58.0.0-0 <59.0.0",
119
118
  "expo-font": ">=56.0.0 <59.0.0 || >=58.0.0-0 <59.0.0",
120
119
  "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
121
  "react": ">=19.2.0 <20.0.0",
122
122
  "react-native": ">=0.85.0 <0.89.0 || >=0.88.0-0 <0.89.0",
123
123
  "react-native-gesture-handler": ">=2.30.0 <4.0.0",
124
124
  "react-native-keyboard-controller": ">=1.21.0 <2.0.0",
125
125
  "react-native-safe-area-context": ">=5.6.0 <6.0.0",
126
126
  "react-native-screens": ">=4.23.0 <5.0.0",
127
+ "react-native-svg": ">=15.0.0 <16.0.0",
127
128
  "react-native-web": ">=0.21.0 <0.22.0",
128
129
  "zustand": ">=5.0.0 <6.0.0"
129
130
  },