Skip to content

feat: replace native-ui components with the React Native Reusables set - #144

Open
adelrodriguez wants to merge 4 commits into
mainfrom
native-ui-react-native-reusables
Open

feat: replace native-ui components with the React Native Reusables set#144
adelrodriguez wants to merge 4 commits into
mainfrom
native-ui-react-native-reusables

Conversation

@adelrodriguez

Copy link
Copy Markdown
Collaborator

@init/native-ui only had four hand-rolled components (button, text, icon, activity-indicator), so building mobile screens meant constantly hitting primitives that exist in packages/ui but not natively. This replaces the package's components with the full React Native Reusables Uniwind registry — 31 components, from accordion to tooltip — so the native package now mirrors the web one in breadth and workflow.

Upstream's Uniwind variant already matches this repo's stack (Uniwind + Tailwind v4, cva, TextClassContext, cn), so the sources are vendored copy-owned (pinned at founded-labs/react-native-reusables@119d0b1, MIT) and adapted to repo conventions rather than ported. A components.json + shadcn CLI setup mirroring packages/ui (bun run components:add @rnr/<name>) makes future adds and upstream diffs repeatable. This realizes the intent of docs/plans/20, ahead of the Plan 19 catalog gate.

Notable decisions:

  • Existing API preservedapps/mobile call sites (Button, Text variants, LargeTitleHeader) needed no changes. large-title-header stays (no upstream equivalent); unused activity-indicator is deleted.
  • Real fixes during adaptation — dropped placeholderClassName from input/textarea (NativeWind API that doesn't exist in Uniwind; native placeholder: classes preserve the styling), typed menubar's controlled/uncontrolled props honestly, moved skeleton's animation into useAnimatedStyle for React Compiler compliance.
  • Inline disables, not package overrides — oxlint's import/namespace can't resolve the export * re-exports in @rn-primitives dist bundles (still broken in oxlint 1.78 after updating Adamantite to 0.35.0 to check), and jsx-no-constructed-context-values false-positives on string-valued contexts (string equality prevents consumer re-renders). Both are suppressed per-file with reasons; the package stays fully linted and type-checked, unlike packages/ui which is excluded wholesale.
  • Deps — 24 @rn-primitives/* packages pinned at 1.5.2, lucide-react-native + react-native-svg added; missing card-foreground/popover/popover-foreground theme tokens added to globals.css. Package docs live in docs/packages.md; the README stays sparse.

Not verified here: on-device behavior. Worth smoke-testing overlay components (dialog/select/dropdown render through the existing PortalHost) and group-active: pressed-state text colors on iOS/Android.

Verified with bun run format, bun run check (0 errors), bun run analyze, bun run check:monorepo, and bun test (22 pass).

Changes made by Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Aug 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
init Skipped Skipped Aug 15, 2026 4:14pm
init-docs Skipped Skipped Aug 15, 2026 4:14pm

Request Review

@init/native-ui had four hand-rolled components while packages/ui has the
full shadcn set, so mobile screens kept hitting missing primitives. The
package now vendors all 31 components from the React Native Reusables
Uniwind registry (pinned at founded-labs/react-native-reusables@119d0b1,
MIT), adapted to repo conventions, with a components.json + shadcn CLI
workflow mirroring packages/ui for future adds and diffs.

Also updates Adamantite to 0.35.0 (oxlint 1.78, oxfmt 0.63, knip 6.32),
documents the package in docs/packages.md, and keeps the README sparse.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Metro treats an `imports` mapping that targets a bare package name
("#utils" -> "@init/utils/ui") as an invalid package configuration and
falls back to file-based resolution for the whole package, which broke
every #components/* import during expo export. Nothing imports #utils
anymore, so drop the mapping and point the shadcn CLI aliases directly
at @init/utils/ui.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel
vercel Bot temporarily deployed to Preview – init August 15, 2026 15:45 Inactive
@vercel
vercel Bot temporarily deployed to Preview – init-docs August 15, 2026 15:45 Inactive

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

react-native-svg now resolves to two different versions inside the mobile bundle. Every other shared native dependency dedupes; this one does not, because it is declared only as a "*" peer. Details inline on packages/native-ui/package.json.

Reviewed changes — the full 44-file diff at f2f06c9, with each vendored component diffed against the upstream React Native Reusables Uniwind registry at the pinned commit.

  • 31 vendored componentspackages/native-ui/src/components/ gains 29 new files and rewrites button, text, and icon to the upstream shape (Slot generic instead of Slot.Text, TextClassContext-aware Icon, useMemo dropped from Button). large-title-header is untouched; activity-indicator is deleted.
  • Dependency surface — 24 @rn-primitives/* at 1.5.2, lucide-react-native, shadcn as a devDependency, a react-native-svg peer, and a #utils subpath-import alias. apps/mobile bumps @rn-primitives/portal to 1.5.2 and adds react-native-svg@15.15.4.
  • Theme tokensglobals.css gains --color-card-foreground, --color-popover, and --color-popover-foreground in both light and dark, which the new overlay components need.
  • Registry workflowcomponents.json + components:add / components:diff scripts mirroring packages/ui, documented in a new docs/packages.md#native-ui section and a new package README.
  • Toolchain bumps — adamantite 0.35.0, knip, oxfmt, oxlint 1.78.0; the scripts/template/add.ts change is a pure oxfmt reformat that falls out of the bump.

I verified locally that the vendoring is faithful: all 32 components match the pinned upstream apart from the four deviations the description declares, plus the two minor ones in Nitpicks below. bunx oxlint packages/native-ui reports 0 errors (the per-file eslint-disable headers are honored) and tsc --noEmit in the package exits clean. #components/*.tsx resolution, uniwind's @source coverage of the package, and the mounted PortalHost all check out. The Reanimated hooks are sound — the omitted dependency arrays are inert because the Worklets Babel plugin is active, and react/rules-of-hooks and react/react-compiler are both enforced on this package.

⚠️ 31 components merge with no runtime verification and nothing in the repo that can provide it

The description is candid that on-device behavior is unverified, and Plan 19 — the catalog workspace that was the stated gate for this work — is still Planned. That leaves 3210 lines of UI with no tests, no stories, and no way for a reviewer or a future contributor to confirm that the portal-rendered overlays or the group-active: pressed-state text colors actually work before shipping a screen that uses them. This is a scope call only you can make: whether @init/native-ui ships broad-but-unproven now and Plan 19 validates it later, or whether the vendoring lands in slices behind the catalog.

Technical details
# No verification path for the vendored component set

## Affected sites
- `packages/native-ui/src/components/` — 31 components, no tests and no catalog entry.
- `docs/plans/20-expand-native-ui-with-react-native-reusables.md:3` — status now records that Plan 19's gate was bypassed, but Plan 19 itself is unchanged and still `Planned`.
- `apps/mobile/src/app/` — only `Button`, `Text`, and `LargeTitleHeader` are exercised by real screens; the other 28 components have zero call sites.

## Required outcome
- A decision, recorded in the plan docs, on whether the unverified components are acceptable to merge ahead of Plan 19, and what the verification trigger is.

## Open questions for the human
- Should the overlay family (`alert-dialog`, `context-menu`, `dialog`, `dropdown-menu`, `hover-card`, `menubar`, `popover`, `select`, `tooltip`) be smoke-tested on iOS and Android before merge, given they all depend on the single `PortalHost` and `FullWindowOverlay` path?
- Does Plan 19's scope need updating now that the component set it was meant to gate has already landed?

ℹ️ Nitpicks

  • packages/native-ui/package.json:9 declares #utils → @init/utils/ui and components.json points its lib/utils aliases at it, but no component uses #utils — they all import @init/utils/ui directly, and docs/packages.md:83 then instructs rewriting the CLI's #utils import by hand after every add. packages/ui does use #utils in its sources. Keeping the alias and dropping the manual rewrite step would make the two packages consistent and remove a per-add chore.
  • docs/plans/19-cross-platform-component-catalog.md:60 still says "render @init/native-ui Button, Text, and ActivityIndicator in an iOS simulator" — ActivityIndicator is deleted by this PR.
  • button.tsx:101 (accessibilityRole="button") and card.tsx:37 (accessibilityRole="header") use the legacy prop where upstream and the rest of this package use role (alert.tsx:31, text.tsx:82). React Native gives role precedence and treats accessibilityRole as the older API, so role="button" / role="heading" would be both upstream-faithful and internally consistent.
  • menubar.tsx:154 drops upstream's overlayClassName / overlayStyle props from MenubarContent. Upstream never uses them either, so this is harmless today, but it is an undeclared local edit that components:diff will surface on every future upstream sync.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread packages/native-ui/package.json
Comment thread docs/packages.md
Comment thread packages/native-ui/src/components/text.tsx Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ The new commit is a correct, well-scoped fix. Nothing new to flag — the three findings from the previous review are untouched and still open.

Reviewed changes — only a895629, the single commit pushed since the f2f06c9 review.

  • #utils subpath import removedpackages/native-ui/package.json drops "#utils": "@init/utils/ui" from imports, keeping just "#*": "./src/*", and components.json repoints its lib / utils aliases straight at @init/utils/ui.

The commit message explains the failure mode precisely: Metro treats an imports mapping whose target is a bare package name as an invalid package configuration and falls back to file-based resolution for the entire package, which broke every #components/* import during expo export. That is a strictly better resolution than the one I suggested — the alias was never used by any component, so pointing the shadcn CLI at @init/utils/ui matches what the sources already do and removes the manual rewrite step from the add workflow at the same time.

I re-verified on a895629: Bun.resolveSync("#components/text.tsx") from packages/native-ui resolves correctly, bunx oxlint packages/native-ui reports 0 errors, and tsc --noEmit exits clean. packages/ui still carries the same "#utils": "@init/utils/ui" mapping, but it is not reachable from a Metro bundle — apps/mobile does not depend on @init/ui, and Plan 19 routes @init/ui through the Vite renderer rather than Metro — so there is nothing to change there.

⚠️ The react-native-svg version split from the previous review is still present

Restating only because it is the one blocking item and this commit did not touch it: packages/native-ui declares react-native-svg solely as a "*" peer, so lucide-react-native resolves 15.15.5 while apps/mobile pins and autolinks 15.15.4. I re-confirmed the split after bun install --frozen-lockfile on a895629. The two docs/packages.md and text.tsx comments are likewise still open.

Pullfrog  | Fix it ➔View workflow run | Using Claude Opus𝕏

…ockquote border

- Pin react-native-svg 15.15.4 in native-ui dependencies and add a root
  override so lucide-react-native's peer resolves to the same install the
  app autolinks, instead of a second 15.15.5 copy.
- Add lucide-react-native to apps/mobile so the documented icon pattern
  (importing icon components directly) resolves from app code.
- Restore border-border on the blockquote text variant, which the
  vendored upstream source had silently dropped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel
vercel Bot temporarily deployed to Preview – init-docs August 15, 2026 16:14 Inactive
@vercel
vercel Bot temporarily deployed to Preview – init August 15, 2026 16:14 Inactive

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

ab90907 commits an unformatted regeneration of apps/mobile/src/uniwind-types.d.ts, which makes the CI format job fail. One bun run format clears it.

Reviewed changes — only ab90907, the single commit pushed since the a895629 review.

  • react-native-svg deduped — pinned at 15.15.4 in packages/native-ui dependencies alongside the existing "*" peer, plus a new root overrides entry.
  • lucide-react-native added to apps/mobile — so the icon pattern documented in docs/packages.md:65 resolves from app code; the doc now states that requirement explicitly.
  • blockquote border restoredborder-border is back on the blockquote text variant in text.tsx:20.
  • uniwind-types.d.ts regenerated — the tracked generated file was rewritten with single quotes and 4-space indent.

All three inline threads from the f2f06c9 review are genuinely fixed and already resolved. I re-verified the dependency work on a clean bun install --frozen-lockfile: apps/mobile, packages/native-ui, and lucide-react-native's peer all resolve the same react-native-svg@15.15.4 install, bun.lock carries no 15.15.5 entry, and lucide-react-native now resolves from apps/mobile. bun run check:monorepo reports no issues.

The f2f06c9 scope question — 31 components merging with no runtime verification while Plan 19 is still Planned — is untouched by this commit and remains yours to call.

ℹ️ Nitpicks

  • apps/mobile/package.json:55lucide-react-native has no imports anywhere in apps/mobile/src, so bunx knip --workspace apps/mobile reports it under "Unused dependencies". It is non-blocking (knip.config.ts sets dependencies: "warn", and the run exits 0 once codegen has produced the i18n files), but it stays in bun run analyze output until a mobile screen actually renders an icon.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread apps/mobile/src/uniwind-types.d.ts Outdated
Comment thread package.json
"react": "19.2.3",
"react-dom": "19.2.3"
"react-dom": "19.2.3",
"react-native-svg": "15.15.4"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The packages/native-ui dependencies pin alone is enough — this global override is redundant. I removed it, deleted bun.lock, and ran a clean bun install: the regenerated lockfile has exactly one react-native-svg entry at 15.15.4 and lucide-react-native's peer symlink points at it. The contrary observation in the commit message is what a sticky lockfile looks like: bun install against the existing lock reports no changes and keeps the stale 15.15.5 entry regardless of the new pin.

Technical details
# Redundant root `overrides` entry for `react-native-svg`

## Affected sites
- `package.json:64``"react-native-svg": "15.15.4"` under `overrides`.
- `packages/native-ui/package.json:51` — already pins `"react-native-svg": "15.15.4"` under `dependencies`.
- `apps/mobile/package.json:64` — pins the same version.

## Evidence
With the override removed and `bun.lock` regenerated from scratch:

```
grep 'react-native-svg@' bun.lock
  "react-native-svg": ["react-native-svg@15.15.4", ...]   # single entry

readlink node_modules/.bun/lucide-react-native@*/node_modules/react-native-svg
  ../../react-native-svg@15.15.4+.../node_modules/react-native-svg
```

## Required outcome
- The three declarations of this version collapse to the two that carry real meaning, so a future Expo SDK bump of `apps/mobile`'s pin is not silently reverted by a root override nobody remembers to update.

## Suggested approach
- Drop the `overrides` line, delete `bun.lock`, re-run `bun install`, and confirm a single `react-native-svg@15.15.4` lock entry. Keeping it is also fine if you prefer belt-and-braces, but then it needs to move in lockstep with the app pin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant