npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tricrobotics/ui-native

v0.1.2

Published

TRIC React Native UI library — the map + liquid-glass panel shell, kit primitives, design tokens, sheet detents and layout hooks shared by the TRIC Expo apps

Readme

@tricrobotics/ui-native

The UI package for building TRIC's domain-specific apps on React Native + react-native-web. Native (SwiftUI via @expo/ui) when possible, a web fallback (RNW/DOM) always, behind one import — the app passes no platform code.

It powers the three "map + liquid-glass panel" Expo apps (client-dashboard-mobile, flex-monitoring-mobile, ops-dashboard-mobile): a full-screen Mapbox background with a draggable glass panel, segmented panel nav, a user menu, and the dashboard primitive kit those panels are built from.

Why it exists

These apps target iPhone, iPad and web from one codebase. We want native chrome where Apple gives it to us — the native BottomSheet on iPhone, SwiftUI glassEffect on iPad — without hand-writing a Platform.OS branch at every call site, and without maintaining a parallel .web.tsx copy of every component inside each app. This package owns that split once, so:

  • iPhone → native @expo/ui BottomSheet.
  • iPad → custom Liquid Glass (glassEffect, iOS 26+) drag panel.
  • web → backdrop-filter glass drag panel.

…all selected by the bundler from a single <MapPanel> import.

The convention (how a platform variant is chosen)

A component that has a native SwiftUI form ships three files; the consumer imports one name:

| File | Role | | --- | --- | | Component.types.ts | the single prop contract — shared by both impls and what tsc checks consumers against | | Component.tsx | base = react-native-web / DOM implementation (web + Android + the type the compiler sees) | | Component.ios.tsx | SwiftUI implementation via @expo/ui/swift-ui (Metro picks it on iOS only) |

Metro resolves .ios.tsx on iOS and .tsx everywhere else. tsc ignores .ios.tsx, so both implementations must satisfy Component.types.ts. @expo/ui is imported only inside .ios.tsx files (it does not resolve on web), which is why it stays an optional peer dependency.

Pure-RN components with no SwiftUI form (Row, Txt, Pill, Button, …) render identically through react-native-web and stay single-file.

graph TD
  imp["import { MapPanel } from '@tricrobotics/ui-native'"]
  metro{Metro platform resolution}
  ios["MapPanel.ios.tsx (SwiftUI): iPhone BottomSheet / iPad glassEffect"]
  web["MapPanel.tsx (RNW base): backdrop-filter drag panel"]
  ts["tsc -> MapPanel.types.ts (one API)"]
  imp --> metro
  metro -->|"platform = ios"| ios
  metro -->|"platform = web / android"| web
  imp -.types.-> ts

Component catalog

Shell — ./panel (and the barrel)

| Component | iOS (SwiftUI) | web (RNW/DOM) | | --- | --- | --- | | MapPanel | iPhone native BottomSheet, iPad Liquid Glass drag panel | backdrop-filter drag panel | | Glass | glassEffect (iOS 26+) / BlurView fallback | backdrop-filter surface | | PanelNav | segmented Picker | RN segmented control | | UserMenu | Menu | RN modal sheet | | LayerControl | floating glass Menu + Toggle | floating dropdown of checkboxes |

MapPanel is generic over the view key and takes the app's PanelViewDefinition[], selectedView/onViewChange, detent/onDetentChange (SheetDetentKey), children (the body — RN or SwiftUI), and userEmail/onLogout/avatarUri. Pass iosBody="swiftui" when the body is composed from this package's SwiftUI primitives (client/flex); leave it as the default "rn" for a react-native body hosted via RNHostView (ops).

Primitive kit — ./primitives (and the barrel)

SwiftUI/web pairs: SectionCard, StatTile, KeyValueRow, SectionLabel, StatusBadge, IconBadge, ViewScaffold, Icon.

Pure-RN (single-file): Row, Spacer, Txt, Card, Divider, Pill, Button, ProgressRing, EmptyState, LoadingState.

StatusBadge is presentational (label / tone / icon). The package ships no status vocabulary — each app maps its own domain states (robot states, review states, …) to those props locally. Same idea for ViewScaffold: it accepts an offlineSlot node instead of importing any app-specific banner.

Other entry points

  • ./sheet — detents (peek/medium/large), detentToHeight, nearestDetentKey, PEEK_PANEL_HEIGHT, …
  • ./hooks — useLayout (phone | tabletPortrait | tabletLandscape | web), computeMapPadding.
  • ./lib — supportsLiquidGlass() (iOS 26+ glass capability check).
  • ./tokens — the "field glass" COLORS palette, CARD, RN SHADOW, TYPOGRAPHY, plus the web-only GLASS / WEB_SHADOW / FONT_FAMILY surfaces used by the base .tsx files, and SPACING/RADIUS re-exported from @tricrobotics/tokens.

Icons

SF Symbols render natively on iOS via @expo/ui. On web, Icon.tsx maps the same systemName to an SVG from @tricrobotics/icons (a regular dependency). If you use a new SF Symbol on web, add it to the SF_SYMBOL_MAP in src/primitives/Icon.tsx (unmapped names fall back to a dashed circle).

Peer dependencies

Provided by the consuming Expo app (not bundled):

  • react, react-native
  • react-native-reanimated, react-native-gesture-handler, react-native-safe-area-context
  • expo-blur
  • @expo/ui — optional, only needed for the SwiftUI (.ios.tsx) variants.

Regular dependencies: @tricrobotics/tokens (value tokens) and @tricrobotics/icons (web SF-Symbol SVGs).

Metro consumption (ships source, not a bundle)

This package intentionally ships TypeScript source (src/) rather than a Vite/webpack bundle, so Metro transforms it with the app's Babel config and Reanimated worklets + platform resolution work correctly. The exports map exposes the react-native condition pointing at source.

Local-link development (no publish required)

Each app installs the package via a file: dependency and watches the source:

// app package.json
"@tricrobotics/icons": "^1.0.7",
"@tricrobotics/tokens": "file:../tric-tokens",
"@tricrobotics/ui-native": "file:../tric-ui-native"
// app metro.config.js — see the apps for the full version
const path = require('path');
const { getDefaultConfig } = require('expo/metro-config');

const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, '..');
const config = getDefaultConfig(projectRoot);

config.watchFolders = [workspaceRoot];
config.resolver.nodeModulesPaths = [
  path.resolve(projectRoot, 'node_modules'),
  path.resolve(workspaceRoot, 'node_modules'),
];
module.exports = config;

After publishing, swap the file: ranges for pinned ^x.y.z versions before shipping to devices.

Validation harness — the gallery

example/ is the ui-native-gallery app: it renders every component so you can compare the SwiftUI (iOS dev build) and RNW (Expo web) renderings side-by-side. After adding or changing a .ios/base pair, add a <XxxDemo/> to example/src/app/index.tsx and run both. SwiftUI primitives must be hosted, so demos wrap them in the example's PrimitiveStage (Host on iOS, a plain View on web). See the ui-native-gallery agent skill.

Type-check both surfaces from the example:

cd example
npm run type-check       # base (.tsx / web) surface
npm run type-check:ios   # the .ios.tsx (SwiftUI) surface
npm run type-check:pkg   # both

@expo/ui SwiftUI is not in Expo Go — the gallery's iOS run needs a dev build (expo run:ios).

Adding a SwiftUI/web component pair

  1. Write Component.types.ts — the one prop contract.
  2. Write Component.tsx — the RNW/DOM base (model it on the existing web bases; use ./primitives/web DOM stacks + Icon + GLASS tokens).
  3. Write Component.ios.tsx — SwiftUI via @expo/ui/swift-ui.
  4. Export all three from the relevant barrel (primitives/index.ts or panel/index.ts).
  5. Add a <ComponentDemo/> to the gallery and verify iOS-vs-web parity.

Golden rule

A change here lands for all three apps at once (shell sizing, detents, drag gestures, glass styling, segmented nav, primitives). Verify on iPhone + iPad + web for each app — use the ui-native-gallery, map-panel-apps, and rn-simulator skills — before finishing.