Files
wehub-resource-sync 2114b14ee0
Sync main into demo / sync (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:26 +08:00

4.4 KiB

App UI Shell

This page collects app-facing shell rules: status bar space, navigation bar foreground, keyboard resize, pointer events, and URL-driven UI state. The complete app contract remains in module-contract.md.

Status and Navigation Bars

Every page's outermost element must reserve status bar space:

<main className="pt-10 ..." data-status-bar-foreground="dark">
  ...
</main>

Use:

Attribute Values Purpose
data-status-bar-foreground dark / light Sets status bar icon/text color for that page.
data-navigation-bar-foreground dark / light Sets bottom gesture bar color when it differs from the status bar.

The OS no longer relies on DOM color auto-detection fallback. Declare the foreground when a page differs from the manifest default.

Foreground fallback chain

The system chrome resolves its foreground color from declarative signals only — it never samples the page's background pixels.

Status bar:

  1. The current page's data-status-bar-foreground (if set).
  2. The app manifest's theme.colors.statusBarForeground (if set).
  3. Default dark.

Bottom gesture (navigation) bar:

  1. The current page's data-navigation-bar-foreground (if set).
  2. The app manifest's theme.colors.navigationBarForeground (if set).
  3. The current page's data-status-bar-foreground (status-bar fallback).
  4. The app manifest's theme.colors.statusBarForeground.
  5. Default dark.

Place declarative attributes on the outermost page container. Don't render a status-bar background of your own — the OS draws transparent chrome over your page, and pt-10 is the only spacing convention.

Keyboard Resize

The OS implements Android-like adjustResize: when the keyboard is visible, the active Activity container shrinks by keyboard height. App pages should use flex layouts so content reflows naturally.

Rules:

Do Avoid
Use flex layout and flex-shrink-0 for chat/input bars. position: fixed; bottom: keyboardHeight.
Add data-keep-keyboard="true" on keyboard-attached controls that should not blur input. Manual keyboard-height positioning.
Add data-hide-on-keyboard for bottom tabs that should disappear while typing. Letting tab bars get pushed above the keyboard.

CSS zoom from designViewportWidth changes coordinate spaces; fixed bottom offsets are especially prone to drift.

Pointer Events

Continuous interactions — drag, swipe, slider, bottom sheet, drawer, drag-to-reorder, page-turning — must use a single Pointer Event source:

onPointerDown
onPointerMove
onPointerUp
onPointerCancel

Use setPointerCapture when dragging should continue after the pointer leaves the element.

Hard rules:

  • Do not maintain parallel touch* + mouse* handlers on the same element. They fire from different event paths and create double-trigger bugs.
  • Do not rely on touchmove for the drag body with a click "fallback" for mouse. Pointer events already unify both.
  • The bench's bindTap / bindLongPress / bindDoubleTap / bindBack hooks do not replace a custom drag implementation — they're for discrete taps. Slider thumbs, reader pagination handles, and draggable overlays must implement their own pointer handlers.
  • The mousedown handlers used to preventDefault and stop focus theft on inputs are acceptable; they don't drive a drag state machine.

DevTools "touch device emulation" simulates touch then synthesizes mouse/click events, producing ghost double-fires that can mask Pointer-event correctness bugs. Use it for layout/scale checks only; validate continuous interactions against a real mouse and a non-emulated touch surface.

URL-Driven UI State

Discrete UI state belongs in the route/history stack:

UI state Preferred modeling
Main tabs Separate pathname routes such as /, /contacts, /me; switch with replace.
Dialogs / drawers Push search params or dialog routes; close with back.
Finite substates uiStates in navigation.declaration.ts.

Avoid useState for modal visibility when Back should close the modal. App Back only understands URL/history state.