ui-native
The React Native component kit — deep imports, no barrel, and the three seams it deliberately leaves to the app.
@statshub/ui-native is the React Native component kit that
expo renders with. It was lifted out of the app, where it had
lived as an ordinary directory of components, and the app now reaches it by name
across roughly 550 call sites.
Importing
Deep paths, always.
import { Button } from "@statshub/ui-native/button/button";
import { useSurfaceStyle } from "@statshub/ui-native/sheet/surface-style";There is no barrel and no main, deliberately. A root index.ts re-exporting
140 modules is one import away from pulling the whole kit into a bundle that
wanted one button, and Metro does not tree-shake its way back out.
Why the files sit at the package root
Metro's package-exports wildcards must name an exact extension. A
"./*": "./src/*" map does not resolve, and an array fallback resolves for
tsc but not for Metro. This kit mixes .ts and .tsx, so it ships no
exports field at all and lets Metro's ordinary file lookup work — which is
also why the component directories are at the package root rather than under
src/.
Note the absence of src/ and of an index.ts. Both are consequences of the
box above, not oversights.
What it must not know
The kit renders. It does not decide where a failure is reported or how the app talks to disk. Three seams exist because of that, and each has an app-side half:
| Seam | The kit owns | The app owns |
|---|---|---|
| Errors | Nothing | RootErrorBoundary around UIProvider, AppErrorBoundary as its children |
| Surface style | The store (sheet/surface-style) | Reading it at launch, writing each change |
| Route presentation | The RoutePresentation vocabulary | Which route gets which presentation |
A kit file that needs @/lib/... is the smell: either the thing it needs
belongs in the kit too, or the component belongs in the app. Both answers are
fine; a package importing from an app is not.
Theme
_core/_theme holds the uniwind CSS entry and the generated .d.ts.
apps/statshub-expo/metro.config.js addresses both by path from the workspace root, and
the app's tsconfig.json names the .d.ts explicitly — without that, every
className in the app stops typechecking.
See also platform differences and add a UI component.