@mrmeg/expo-ui 0.28.0 → 0.29.1
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 +80 -0
- package/LLM_USAGE.md +10 -4
- package/README.md +104 -50
- package/bin/expo-ui-icons.mjs +509 -0
- package/dist/components/Icon.d.ts +57 -26
- package/dist/components/Icon.js +40 -19
- 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/ThemeSelector.d.ts +52 -0
- package/dist/components/ThemeSelector.js +90 -0
- package/dist/components/Toggle.d.ts +4 -8
- package/dist/components/Toggle.js +15 -2
- package/dist/components/ToggleGroup.d.ts +4 -8
- package/dist/components/ToggleGroup.js +15 -2
- 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 +1 -0
- package/dist/components/index.js +1 -0
- package/dist/design-system.json +276 -131
- package/dist/icons/LICENSE.lucide +43 -0
- package/dist/icons/lucide.json +2157 -0
- package/llms-full.md +3 -2
- package/package.json +15 -7
- package/dist/components/iconRegistry.generated.d.ts +0 -148
- package/dist/components/iconRegistry.generated.js +0 -296
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,86 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.29.1]
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- The shipped Lucide snapshot (`dist/icons/lucide.json`) moves from Lucide
|
|
13
|
+
1.55.0 to 1.57.0: 13 new glyphs (among them `cpu-ai`, `folder-user`,
|
|
14
|
+
`message-circle-text`, `server-nas`, `users-collaboration`), no removals or
|
|
15
|
+
alias changes. After upgrading, run `bunx expo-ui-icons` and
|
|
16
|
+
`bunx expo-ui-icons tabs`.
|
|
17
|
+
|
|
18
|
+
## [0.29.0]
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **`ThemeSelector`** (`@mrmeg/expo-ui/components`): a compact System / Light /
|
|
23
|
+
Dark switch bound to `useThemeStore`, so a settings screen needs one row for
|
|
24
|
+
appearance instead of three radio rows. Icon-only by default (about 116 pt,
|
|
25
|
+
sized for `ItemActions`); `showLabels` stretches it with text. Translated
|
|
26
|
+
`labels` name each segment for screen readers. Drawn in RN on every platform,
|
|
27
|
+
with a 44 pt vertical hit area and a selection haptic; `value` /
|
|
28
|
+
`onValueChange` make it controlled.
|
|
29
|
+
- **`registerIcons(icons)`** and **`isIconName(value)`**
|
|
30
|
+
(`@mrmeg/expo-ui/components/Icon`): register an app's glyphs once at module
|
|
31
|
+
scope in the root layout, and narrow a runtime string to `IconName`. The
|
|
32
|
+
`IconRegistry` interface they back is what an app's generated module
|
|
33
|
+
augments. Also exported: the `IconGlyph`, `IconTag`, `IconComponent`,
|
|
34
|
+
`IconAccessibilityProps`, and `NamedIconProps` types, and a `testID` prop on
|
|
35
|
+
`Icon` (a named icon defaults to `icon-<name>` in development).
|
|
36
|
+
- **`expo-ui-icons` bin**: compiles `client/icons.json` against the shipped
|
|
37
|
+
Lucide snapshot (`dist/icons/lucide.json`, Lucide 1.55.0, ISC license in
|
|
38
|
+
`dist/icons/LICENSE.lucide`) into `client/icons.generated.ts` (`appIcons` plus
|
|
39
|
+
the `IconRegistry` augmentation). `init` seeds the manifest from the app's
|
|
40
|
+
`name="…"` / `icon="…"` literals, `add` inserts names (an alias becomes its
|
|
41
|
+
canonical name), `--check` fails on a stale module, and `tabs` rasterizes the
|
|
42
|
+
`tabIcons` subset to 1x/2x/3x template PNGs for a native tab bar, with a
|
|
43
|
+
hash manifest so `tabs --check` needs no rasterizer.
|
|
44
|
+
- `design-system.json` `icons` gains `aliases` (Lucide's renamed names →
|
|
45
|
+
canonical) and `lucide` (the snapshot version).
|
|
46
|
+
- Exact `./components/iconTypes` (the glyph types) and
|
|
47
|
+
`./components/iconBuiltins.generated` (`BUILTIN_ICONS`) subpaths. `Icon`
|
|
48
|
+
re-exports everything an app needs; these exist so every `.ts` module the
|
|
49
|
+
`./components/*` pattern exposes also resolves through the repo's source
|
|
50
|
+
condition.
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- **`Icon` renders package-owned glyph data instead of `lucide-react-native`.**
|
|
55
|
+
Glyphs are plain `[tag, attrs]` data drawn by the package: an inline `<svg>`
|
|
56
|
+
on web and in server HTML (stroked `currentColor`, so theme changes recolor
|
|
57
|
+
through CSS), `react-native-svg` on native with Lucide's own geometry and
|
|
58
|
+
stroke handling. The web bundle no longer carries `react-native-svg` or the
|
|
59
|
+
Lucide runtime, and Jest needs no icon mocks.
|
|
60
|
+
- **`IconName` is the 19 builtins plus app-registered names.** The package
|
|
61
|
+
registers only what its components draw (`check`, `x`, the chevrons, the
|
|
62
|
+
status and theme-mode glyphs, `eye`/`eye-off`, `user`, …); every other name
|
|
63
|
+
comes from the app's `client/icons.json`. `<Icon name>` call sites do not
|
|
64
|
+
change once the names are registered.
|
|
65
|
+
- `ToggleIcon` and `ToggleGroupIcon` take `IconProps` (`name` or `component`,
|
|
66
|
+
plus `size`, `style`, `accessibilityLabel`, …) like `Button.Icon`: color from
|
|
67
|
+
the control's text color and decorative by default.
|
|
68
|
+
- `Icon`'s `component` prop is typed `IconComponent` (`size`, `color`, `style`,
|
|
69
|
+
accessibility props); any component taking `size` and `color` still fits.
|
|
70
|
+
|
|
71
|
+
Upgrading from 0.28: run `bunx expo-ui-icons init` once, typecheck, and
|
|
72
|
+
`bunx expo-ui-icons add` each name it reports (names built at runtime are not
|
|
73
|
+
found by the scan); add `registerIcons(appIcons)` at module scope in the root
|
|
74
|
+
layout (`import { appIcons } from "@/client/icons.generated"`); register
|
|
75
|
+
`appIcons` in the Jest setup too; delete the Lucide Jest module mapping or
|
|
76
|
+
`transformIgnorePatterns` entry and any `iconRegistry.generated` mock; and
|
|
77
|
+
remove `lucide-react-native` from `package.json` once nothing imports it.
|
|
78
|
+
Optionally list `tabIcons` and run `bunx expo-ui-icons tabs` to replace
|
|
79
|
+
`@expo/vector-icons` tab glyphs with template PNGs. Add
|
|
80
|
+
`bunx expo-ui-icons --check` to CI.
|
|
81
|
+
|
|
82
|
+
### Removed
|
|
83
|
+
|
|
84
|
+
- The `lucide-react-native` peer dependency.
|
|
85
|
+
- The `./components/iconRegistry.generated` export (`ICONS`); glyphs are
|
|
86
|
+
registered data now, and `IconName` comes from `@mrmeg/expo-ui/components/Icon`.
|
|
87
|
+
|
|
8
88
|
## [0.28.0]
|
|
9
89
|
|
|
10
90
|
### Added
|
package/LLM_USAGE.md
CHANGED
|
@@ -191,7 +191,7 @@ configureExpoUiI18n((key, options) => i18n.t(key, options));
|
|
|
191
191
|
- Web SSR paints the visitor's scheme: wrap the root layout in `<InitialSchemeProvider scheme>` (`@mrmeg/expo-ui/state`) with the `"light"`/`"dark"` value read from a cookie on both server and client (template: `shared/ssrColorScheme.ts`, written by `+html.tsx` and `client/features/app/colorSchemeCookie.ts`); `useTheme()` uses it until `hasLoadedTheme`. Never read `localStorage` for the first render.
|
|
192
192
|
- A Radix-backed component (`Tabs`, `Accordion`, `Collapsible`, `Select`, `DropdownMenu`, `Popover`) on a server-rendered web route logs a `useId` hydration mismatch (Expo Router hydrates `#root`, the server rendered `+html.tsx`). Wrap that subtree — not the page — in `<Hydrated fallback={…}>` (`@mrmeg/expo-ui/components`) with a fallback that holds its height; never branch on `typeof window`. Content that must be in the server HTML stays outside the gate.
|
|
193
193
|
- Web console hygiene: animate with `useNativeDriver: shouldUseNativeDriver` (`@mrmeg/expo-ui/lib`), never `true`; set `pointerEvents` in `style`, never as a prop (react-native-web deprecates the prop, and its lazy warning module has overflowed the first SSR render of deep routes). `AnimatedView` folds a `pointerEvents` prop into style for you.
|
|
194
|
-
- Haptics are one setting: `<UIProvider haptics="off" | "selection" | "all">` (default `"selection"`: Switch/Checkbox/Toggle/ToggleGroup/SegmentedControl tap on change; `"all"` adds a light tap on press for Button, pressable Card and Item). Do not call `expo-haptics` per control; force one Button with `haptic`. Pressed/disabled looks come from `interaction.pressedOpacity` / `interaction.disabledOpacity` in `@mrmeg/expo-ui/constants`.
|
|
194
|
+
- Haptics are one setting: `<UIProvider haptics="off" | "selection" | "all">` (default `"selection"`: Switch/Checkbox/Toggle/ToggleGroup/SegmentedControl/ThemeSelector tap on change; `"all"` adds a light tap on press for Button, pressable Card and Item). Do not call `expo-haptics` per control; force one Button with `haptic`. Pressed/disabled looks come from `interaction.pressedOpacity` / `interaction.disabledOpacity` in `@mrmeg/expo-ui/constants`.
|
|
195
195
|
- Bottom toasts must clear the tab bar: the layout that owns a bar calls `useNotificationOffset({ bottom })` (`@mrmeg/expo-ui/hooks`) with the bar's distance from the window edge, inset included — `useBottomTabBarHeight()` for a JS `<Tabs>` layout, platform bar height + `useWindowInsets().bottom` for `NativeTabs` — and `null` while the bar is hidden. Never position a toast yourself.
|
|
196
196
|
- Use `notify` plus a root `UIProvider` for transient global feedback. (`globalUIStore` stays available for reactive subscriptions and tests. In a component, read it with zustand's `useStore(globalUIStore, selector)`, never `globalUIStore()`: the React Compiler only treats `use*` calls as hooks, caches the bare call, and the next render crashes with React error #311.)
|
|
197
197
|
- Keep app monitoring, auth, API, and domain behavior outside this package.
|
|
@@ -261,6 +261,11 @@ tokens and keeps a 44px hit area on native while rendering 40px on web.
|
|
|
261
261
|
`spacing.cardPadding` (16) is the inner padding of `Card` and `StatCard` tiles,
|
|
262
262
|
not a layout inset. See Screen Layout below.
|
|
263
263
|
|
|
264
|
+
A settings screen picks the theme with one row: `ThemeSelector` in the row's
|
|
265
|
+
`ItemActions`, with translated `labels`. It reads and writes `useThemeStore`, so
|
|
266
|
+
it needs no state of its own. Describe the resolved scheme under the title only
|
|
267
|
+
while the preference is `system`.
|
|
268
|
+
|
|
264
269
|
When the saved theme preference is `system`, the package theme store owns the OS
|
|
265
270
|
color-scheme subscription, including web `prefers-color-scheme`. Do not add
|
|
266
271
|
app-local Appearance or `matchMedia` listeners. `THEME_STORAGE_KEY` (from
|
|
@@ -393,7 +398,7 @@ Check this before creating a new app-local primitive. All components come from
|
|
|
393
398
|
| `DropdownMenu` | Menus and command lists | Homemade popover menus |
|
|
394
399
|
| `EmptyState` | No-data or recoverable error regions | One-off empty placeholders |
|
|
395
400
|
| `ErrorBoundary` | React render error fallback | Unhandled screen crashes |
|
|
396
|
-
| `Icon` | Lucide or custom
|
|
401
|
+
| `Icon` | Registered Lucide glyphs or custom art with theme tokens | Raw vector icons with hardcoded colors |
|
|
397
402
|
| `InputOTP` | Verification code entry | Several manually managed text inputs |
|
|
398
403
|
| `Item`, `ItemMedia`, `ItemContent`, `ItemTitle`, `ItemDescription`, `ItemActions` | List / settings rows with density tokens | Hand-rolled row `View`s |
|
|
399
404
|
| `ItemGroup` | A titled group of full-width `Item` rows with inset hairlines and an optional footer | Bordered or shadowed boxes around rows, a `Card` per row, hand-drawn dividers |
|
|
@@ -417,6 +422,7 @@ Check this before creating a new app-local primitive. All components come from
|
|
|
417
422
|
| `Switch` | Binary settings | Custom toggle switches |
|
|
418
423
|
| `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` | In-page tabbed views | Custom segmented/tab controls |
|
|
419
424
|
| `TextInput` | Text entry with label, helper/error text, clear, password reveal | Raw `TextInput` plus repeated label/error code |
|
|
425
|
+
| `ThemeSelector` | System / Light / Dark switch wired to the theme store; icon-only fits an `ItemActions` slot, `showLabels` stretches | Three radio rows or a hand-rolled theme picker |
|
|
420
426
|
| `Toggle`, `ToggleIcon` | Pressed/unpressed control; an `iconOnly` toggle needs `accessibilityLabel` (dev warns once) | Button with local selected styling |
|
|
421
427
|
| `ToggleGroup`, `ToggleGroupItem`, `ToggleGroupIcon` | Single or multi toggle groups | Custom segmented controls |
|
|
422
428
|
| `Tooltip` | Short hover/focus help | Persistent helper text or custom hover cards |
|
|
@@ -446,7 +452,7 @@ already scope label selectability; ordinary iOS/web text stays selectable.
|
|
|
446
452
|
- A plain `View` whose `opacity`/`pointerEvents` follows state must be `collapsable={false}` on Android (Fabric re-parents its children on the flip; a flip racing a navigation pop crashes). Package surfaces are pinned; spread `stateSurfaceProps()` from `@mrmeg/expo-ui/lib` on app-owned ones. `Pressable` already pins itself.
|
|
447
453
|
- `TextInput` `onBlur` fires only after a real focus on Android (the native field's first-composition blur is dropped in the package); validate on blur directly and do not add a touched-fields guard in app code.
|
|
448
454
|
- `Drawer.Header` takes `icon`, `title`, and `action` slots for a compact app-brand row; put `Drawer.ToggleCollapse` in `action` for a trailing rail control. `Drawer.Content` owns safe-area top/bottom padding — do not duplicate it in children.
|
|
449
|
-
- `Icon` and `Button.Icon` take `name` (a kebab-case name
|
|
455
|
+
- `Icon` and `Button.Icon` take `name` (a kebab-case Lucide name typed `IconName`: the package builtins plus the names the app lists in `client/icons.json`, compiled with `bunx expo-ui-icons` and registered once with `registerIcons(appIcons)` at module scope in the root layout) or `component` (an `IconComponent` taking `size` and `color`, for art that is not a Lucide icon). Both are sized, colored with theme tokens, and hidden from assistive tech unless `accessibilityLabel` names a standalone icon (`decorative` only silences a labelled one); `Button.Icon` inherits the button's label color and is decorative by default. A missing name is a type error: run `bunx expo-ui-icons add <name>` (it maps an alias such as `home` to `house`) rather than importing an icon library. Check a name that arrives at runtime with `isIconName`.
|
|
450
456
|
|
|
451
457
|
## Minimal Examples
|
|
452
458
|
|
|
@@ -557,7 +563,7 @@ import { Drawer, Icon } from "@mrmeg/expo-ui/components";
|
|
|
557
563
|
title="Acme"
|
|
558
564
|
action={
|
|
559
565
|
<Drawer.ToggleCollapse>
|
|
560
|
-
<Icon name="
|
|
566
|
+
<Icon name="panel-left" decorative />
|
|
561
567
|
</Drawer.ToggleCollapse>
|
|
562
568
|
}
|
|
563
569
|
/>
|
package/README.md
CHANGED
|
@@ -32,9 +32,10 @@ Web 0.21. Install these peers at the versions your Expo SDK recommends: `expo`,
|
|
|
32
32
|
`react-native-web`, `react-native-gesture-handler`,
|
|
33
33
|
`react-native-keyboard-controller`, `react-native-safe-area-context`,
|
|
34
34
|
`react-native-screens`, `@react-native-async-storage/async-storage`, `zustand`.
|
|
35
|
-
`Icon`
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
`Icon` draws SVG, so also install `react-native-svg` (a native module, pinned
|
|
36
|
+
by your SDK): `npx expo install react-native-svg`. The icons themselves are
|
|
37
|
+
[Lucide](https://lucide.dev/icons) glyph data the package ships and renders;
|
|
38
|
+
no icon library is a peer (see [Icons](#icons)).
|
|
38
39
|
|
|
39
40
|
`@rn-primitives/*` and `@expo-google-fonts/inter` are package dependencies, not
|
|
40
41
|
peers. Animations use React Native `Animated`. `BottomSheet`, `Slider`, and
|
|
@@ -481,7 +482,7 @@ setHaptics("all");
|
|
|
481
482
|
| `haptics` | Fires on |
|
|
482
483
|
|---|---|
|
|
483
484
|
| `"off"` | nothing |
|
|
484
|
-
| `"selection"` (default) | state changes: Switch, Checkbox, Toggle, ToggleGroup, SegmentedControl |
|
|
485
|
+
| `"selection"` (default) | state changes: Switch, Checkbox, Toggle, ToggleGroup, SegmentedControl, ThemeSelector |
|
|
485
486
|
| `"all"` | selection plus a light tap on press-in for Button, pressable Card and Item, and `useScalePress` consumers |
|
|
486
487
|
|
|
487
488
|
Two ways to disable a `Button`. `disabled` swallows presses, skips the scale
|
|
@@ -567,7 +568,7 @@ monospace.
|
|
|
567
568
|
weight is imported from its own `@expo-google-fonts/inter/<weight>` subpath, so
|
|
568
569
|
a native bundle carries those four files and no other Inter face, and a web
|
|
569
570
|
bundle carries no Inter file at all. Icons
|
|
570
|
-
are SVG
|
|
571
|
+
are SVG drawn from glyph data, so there is no icon font to load on any
|
|
571
572
|
platform. On web
|
|
572
573
|
it injects one Google Fonts Inter stylesheet (all four weights) after hydration
|
|
573
574
|
unless the app already added it; web weight differentiation then comes from a
|
|
@@ -727,7 +728,7 @@ building a new primitive.
|
|
|
727
728
|
| `DropdownMenu` | Menus and command lists | Row actions, account menu, sort menu, checkbox/radio groups |
|
|
728
729
|
| `EmptyState` | No-data or recoverable error regions | Empty inbox, no results, failed list load |
|
|
729
730
|
| `ErrorBoundary` | React render error fallback | Route-level and feature-level fallbacks |
|
|
730
|
-
| `Icon` | Lucide or custom
|
|
731
|
+
| `Icon` | Registered Lucide glyphs or custom art on theme tokens | Button accessories, menu icons, status glyphs |
|
|
731
732
|
| `InputOTP` | Verification code entry | Email/SMS codes, MFA, invite codes |
|
|
732
733
|
| `Item` | List and settings rows on the density tokens | Settings lists, inbox rows, pickers, detail rows |
|
|
733
734
|
| `ItemGroup` | Flat grouped list: title, full-width `Item` rows with inset hairlines, footer | Settings sections, profile details, activity feeds |
|
|
@@ -751,61 +752,113 @@ building a new primitive.
|
|
|
751
752
|
| `Switch` | Binary settings | Notification, privacy, and feature toggles |
|
|
752
753
|
| `Tabs` | In-page tabbed views | Profile sections, report views, settings categories |
|
|
753
754
|
| `TextInput` | Text entry with label, helper/error text, clear button, password reveal, numeric filtering, left/right elements | Email/password, search, multiline notes |
|
|
755
|
+
| `ThemeSelector` | Compact System / Light / Dark switch bound to the theme store (icon-only, or `showLabels`) | The appearance row of a settings screen |
|
|
754
756
|
| `Toggle` | Pressed/unpressed control | Favorite, mute, bold/italic, view mode |
|
|
755
757
|
| `ToggleGroup` | Single or multi toggle groups | Alignment, formatting toolbar, filter chips |
|
|
756
758
|
| `Tooltip` | Short hover/focus help | Icon button labels, field hints, disabled-action reasons |
|
|
757
759
|
|
|
758
760
|
### Icons
|
|
759
761
|
|
|
760
|
-
`Icon`
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
(`
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
`
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
762
|
+
`Icon` draws Lucide glyphs from plain data: each icon is a list of
|
|
763
|
+
`[tag, attrs]` SVG nodes on a 24×24 grid, stroked 2 px with round caps and
|
|
764
|
+
joins. The package owns both renderers: an inline `<svg>` on web and in
|
|
765
|
+
server-rendered HTML (stroked `currentColor` with the color as a style, so a
|
|
766
|
+
theme change recolors it through CSS and server and client markup match), and
|
|
767
|
+
`react-native-svg` on iOS and Android, drawn the way Lucide's own native
|
|
768
|
+
component draws it. No icon runtime or font ships, and each registered glyph
|
|
769
|
+
adds about 40 bytes (brotli) to the bundle.
|
|
770
|
+
|
|
771
|
+
`name` is typed by `IconName`: the 19 names the package's own components draw
|
|
772
|
+
(`src/components/icon-names.json`: the chevrons, `check`, `x`, the status and
|
|
773
|
+
theme-mode glyphs) plus every name the app registers. An app lists the Lucide
|
|
774
|
+
names it uses in `client/icons.json` and compiles them with the package's
|
|
775
|
+
`expo-ui-icons` CLI:
|
|
776
|
+
|
|
777
|
+
```json
|
|
778
|
+
{ "icons": ["archive", "bell", "compass"], "tabIcons": ["compass"] }
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
```sh
|
|
782
|
+
bunx expo-ui-icons init # seed client/icons.json from name="…" / icon="…" literals in app/, client/, shared/
|
|
783
|
+
bunx expo-ui-icons add calendar # add names; an alias is replaced by its canonical name
|
|
784
|
+
bunx expo-ui-icons # write client/icons.generated.ts (--check: exit 1 when stale)
|
|
785
|
+
bunx expo-ui-icons tabs # rasterize tabIcons to 1x/2x/3x template PNGs (--check never rasterizes)
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
The generated module exports the glyphs as `appIcons` and augments the
|
|
789
|
+
`IconRegistry` interface, so the new names type-check everywhere. Register them
|
|
790
|
+
once at module scope in the root layout, so they are in place before any route
|
|
791
|
+
renders, on the server as well as the client:
|
|
792
|
+
|
|
793
|
+
```tsx
|
|
794
|
+
import { registerIcons } from "@mrmeg/expo-ui/components/Icon";
|
|
795
|
+
import { appIcons } from "@/client/icons.generated";
|
|
796
|
+
|
|
797
|
+
registerIcons(appIcons);
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
An unregistered literal is a type error. A name that arrives at runtime (a
|
|
801
|
+
database field, a server payload) renders nothing and warns once in
|
|
802
|
+
development; check it with `isIconName(value)`, which narrows to `IconName`.
|
|
803
|
+
The CLI rejects a name that is not in the snapshot, and an alias with its
|
|
804
|
+
canonical name (`home → house`); the alias map also ships in
|
|
805
|
+
`design-system.json` as `icons.aliases`. Brand glyphs (`github`, `chrome`,
|
|
806
|
+
`facebook`, `instagram`, `linkedin`, `twitter`) have no Lucide equivalent.
|
|
807
|
+
|
|
808
|
+
`tabs` writes `assets/icons/tabs/<name>.png` (plus `@2x` and `@3x`) and
|
|
809
|
+
`client/tabIcons.generated.ts` (`TAB_ICONS`, `TabIconName`) for a native tab
|
|
810
|
+
bar: `<NativeTabs.Trigger.Icon src={TAB_ICONS.compass} renderingMode="template" />`.
|
|
811
|
+
The PNGs are black on transparent and the platform tints them. Freshness is a
|
|
812
|
+
hash of each glyph's SVG in `assets/icons/tabs/manifest.json`, so `tabs --check`
|
|
813
|
+
and an up-to-date `tabs` need no rasterizer; only a changed tab icon needs
|
|
814
|
+
`rsvg-convert` (`brew install librsvg`, or point `RSVG_CONVERT` at it).
|
|
815
|
+
|
|
816
|
+
`color` takes a theme color name or a literal. `accessibilityLabel` names an
|
|
817
|
+
icon that stands alone (`role="img"` + `aria-label` on web, `accessible` +
|
|
818
|
+
`accessibilityLabel` on native); an icon without one is hidden from assistive
|
|
819
|
+
tech — the glyph beside text that already says it would otherwise be announced
|
|
820
|
+
as an unnamed image — and `decorative` only remains to silence a labelled icon.
|
|
821
|
+
On web the SVG carries ARIA only (`aria-hidden` or `role="img"`), never the
|
|
822
|
+
RN-only accessibility props. A named icon's SVG root takes `testID`, which
|
|
823
|
+
defaults to `icon-<name>` in development. Instead of `name`, `Icon` and
|
|
824
|
+
`Button.Icon` take `component` (an `IconComponent`) for art that is not a
|
|
825
|
+
Lucide icon, such as a brand mark: any component that accepts `size` and
|
|
826
|
+
`color`, sized, colored, themed, and hidden from assistive tech exactly like a
|
|
827
|
+
named icon. `Button.Icon`, `ToggleIcon`, and `ToggleGroupIcon` take the same
|
|
828
|
+
props, default their color to the control's label color, and are decorative.
|
|
777
829
|
|
|
778
830
|
```tsx
|
|
779
831
|
import { Button, Icon } from "@mrmeg/expo-ui/components";
|
|
780
|
-
import
|
|
781
|
-
import Rocket from "lucide-react-native/icons/rocket";
|
|
832
|
+
import { GitHubMark } from "@/client/components/GitHubMark";
|
|
782
833
|
|
|
783
834
|
<Icon name="circle-check-big" color="success" size={16} />
|
|
784
|
-
<Icon component={
|
|
835
|
+
<Icon component={GitHubMark} color="accent" />
|
|
785
836
|
<Button onPress={goHome}>
|
|
786
|
-
<Button.Icon
|
|
837
|
+
<Button.Icon name="house" />
|
|
787
838
|
<Button.Text>Home</Button.Text>
|
|
788
839
|
</Button>
|
|
789
840
|
```
|
|
790
841
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
`
|
|
807
|
-
|
|
808
|
-
|
|
842
|
+
The Lucide snapshot ships as `dist/icons/lucide.json` (Lucide 1.57.0: every
|
|
843
|
+
icon plus the alias map) and the CLI compiles against it, so adding a name needs
|
|
844
|
+
no package release. Lucide renames about one name in six minor releases and
|
|
845
|
+
sometimes redraws a glyph under a stable name, so the snapshot is pinned
|
|
846
|
+
exactly and moves only in a package release: after upgrading, run
|
|
847
|
+
`bunx expo-ui-icons` and `bunx expo-ui-icons tabs`, review the glyph diff, and
|
|
848
|
+
replace any name the CLI now rejects. Lucide is ISC-licensed (its
|
|
849
|
+
Feather-derived icons MIT); the license ships as `dist/icons/LICENSE.lucide`.
|
|
850
|
+
|
|
851
|
+
Tests need no icon mock and no Lucide module mapping: glyphs are plain objects
|
|
852
|
+
drawn with `react-native-svg`. Register `appIcons` in the Jest setup (lazily, in
|
|
853
|
+
a `beforeAll`, so test-file mocks still apply to `Icon`) and query a named icon
|
|
854
|
+
by its `icon-<name>` test id.
|
|
855
|
+
|
|
856
|
+
In this repository, `bun run ui:icons` regenerates `iconBuiltins.generated.ts`,
|
|
857
|
+
`src/icons/lucide.json`, and `src/icons/LICENSE.lucide` from the exactly pinned
|
|
858
|
+
`lucide-static` devDependency and `icon-names.json`. A Lucide bump changes that
|
|
859
|
+
pin, then `bun install`, `bun run ui:icons`, `bun run icons`, and
|
|
860
|
+
`bun run icons:tabs`. The template app's own list is `client/icons.json`
|
|
861
|
+
(`bun run icons`, `bun run icons:tabs`).
|
|
809
862
|
|
|
810
863
|
### Compound Parts
|
|
811
864
|
|
|
@@ -841,7 +894,7 @@ are named exports only.
|
|
|
841
894
|
|
|
842
895
|
| Hook | Returns |
|
|
843
896
|
|------|---------|
|
|
844
|
-
| `useResources()` | `{ loaded, error }` — loads the Inter faces (icons are SVG, nothing to load) |
|
|
897
|
+
| `useResources()` | `{ loaded, error }` — loads the Inter faces (icons are SVG drawn from glyph data, nothing to load) |
|
|
845
898
|
| `useTheme()` | Active theme, scheme helpers, shadow/contrast/alpha helpers |
|
|
846
899
|
| `useStyles(factory)` | Memoized theme-aware styles plus the `useTheme()` helpers |
|
|
847
900
|
| `useDimensions()` | `{ width, height, orientation, isSmallScreen, isMediumScreen, isLargeScreen }` against `SCREEN_SIZES` (768 / 1000 / 1200) |
|
|
@@ -1033,7 +1086,7 @@ import { Drawer, Icon } from "@mrmeg/expo-ui/components";
|
|
|
1033
1086
|
title="Acme"
|
|
1034
1087
|
action={
|
|
1035
1088
|
<Drawer.ToggleCollapse>
|
|
1036
|
-
<Icon name="
|
|
1089
|
+
<Icon name="panel-left" decorative />
|
|
1037
1090
|
</Drawer.ToggleCollapse>
|
|
1038
1091
|
}
|
|
1039
1092
|
/>
|
|
@@ -1218,9 +1271,10 @@ copies of `@radix-ui/react-slot` crashes the web `AlertDialog` with
|
|
|
1218
1271
|
`pkg ui build` also writes `dist/design-system.json`
|
|
1219
1272
|
([`scripts/build-design-system-manifest.mjs`](../../scripts/build-design-system-manifest.mjs)):
|
|
1220
1273
|
the spacing, radius and icon scales, the palette, the light and dark themes, the
|
|
1221
|
-
font variants, every component's variant and size values, and `icons
|
|
1222
|
-
`IconName`
|
|
1223
|
-
|
|
1274
|
+
font variants, every component's variant and size values, and `icons` (the
|
|
1275
|
+
builtin `IconName` list as `names`, Lucide's renamed-name `aliases`, and the
|
|
1276
|
+
snapshot's `lucide` version), serialized out of `src` by the same extractor the
|
|
1277
|
+
lint rules use. It is exported as
|
|
1224
1278
|
`@mrmeg/expo-ui/design-system.json`, and `@mrmeg/eslint-plugin-expo-ui` reads it
|
|
1225
1279
|
in projects that install this package instead of checking out its sources — which
|
|
1226
1280
|
is what lets the design-system rules quote this release's presets and tokens.
|