Files
multica/apps/mobile/components.json
Naiyuan Qing 420230c62a feat(mobile): adopt react-native-reusables + class-mode dark mode
First wave of the RNR migration documented in apps/mobile/docs/
rnr-migration.md. The hand-written components/ui/ shell was producing a
steady stream of dark-mode and sheet-handling bugs; this commit
establishes the foundation that lets every subsequent screen pick up
RNR-shipped components and a real theme system instead.

Foundation (Phase 1):
- global.css + tailwind.config.js switch to shadcn neutral CSS variables
  (light + dark) under :root and .dark:root, with Multica custom tokens
  appended. tailwind utilities resolve to hsl(var(--...)).
- New lib/theme.ts mirrors the variables in TypeScript and exports
  NAV_THEME for React Navigation chrome.
- New lib/use-color-scheme.ts wraps NativeWind's useColorScheme with
  expo-secure-store persistence (preference key: theme-preference,
  values: light/dark/system).
- components.json registers shadcn CLI paths so `npx @rnr/cli add` writes
  to the expected aliases. metro.config.js gains inlineRem: 16.
- app/_layout.tsx wraps the tree in ThemeProvider(NAV_THEME[scheme]) and
  mounts <PortalHost /> for RNR dialogs.
- Settings → Appearance picker (three rows: Light / Dark / System,
  persisted) — the only product addition in this commit.

Component canary (Phase 2):
- button.tsx + text.tsx replaced by RNR's defaults via the CLI (uses
  TextClassContext to flow text variants from Button into nested Text).
- 11 button call sites updated to wrap children in <Text> (the RNR
  convention). The old `brand` variant had zero call sites and was
  dropped without follow-up.

Bottom navigation:
- (tabs)/_layout.tsx tried NativeTabs first but rolled back to JS Tabs:
  NativeTabs hard-codes canPreventDefault: false on tabPress events, so
  the "More tap opens a sheet without navigating" pattern was
  unreachable. The rolled-back layout uses useColorScheme + THEME to
  derive active/inactive tint, fixing the dark-mode "dim selected tab"
  bug.
- More tab intercepts tabPress and pushes /[workspace]/menu — a stack
  route registered with presentation: "formSheet" +
  sheetAllowedDetents: "fitToContents" so iOS sizes the sheet to the
  menu's intrinsic height (UIKit handles drag handle, swipe dismiss,
  blur backdrop).
- The formSheet route is named `menu.tsx` rather than `more.tsx` to
  avoid the URL collision with (tabs)/more.tsx — both files would
  otherwise resolve to /[workspace]/more because (tabs) is a transparent
  route group.
- components/nav/global-nav-menu.tsx refactored from a self-managed
  Modal into a plain ScrollView (no flex-1, so fitToContents can
  measure). Closes via router.dismiss() instead of an onClose prop.

Docs / rules:
- apps/mobile/CLAUDE.md adds two hard rules: "defaults first" and "iOS
  native > RNR > discuss" (the three-tier waterfall).
- apps/mobile/docs/rnr-migration.md captures the alternatives evaluated,
  the three-tier component classification, the phased rollout, and the
  pitfalls hit during this commit.

Out of scope for this wave (planned but not started):
- Tier A remaining primitives (input / card / text-field / textarea)
- Tier B sheets (the 18 hand-rolled Modal sheets — to be replaced one
  PR at a time with ActionSheetIOS / native pickers / RNR Dialog)
- Tier C domain UI internal-token upgrades

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 11:09:00 +08:00

20 lines
396 B
JSON

{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "global.css",
"baseColor": "neutral",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}