diff --git a/packages/mobile/README.md b/packages/mobile/README.md index dcd222344..d6d75b794 100644 --- a/packages/mobile/README.md +++ b/packages/mobile/README.md @@ -25,18 +25,22 @@ npm install @object-ui/mobile ## Quick Start ```tsx -import { MobileProvider, useBreakpoint, useGesture } from '@object-ui/mobile'; +import { MobileProvider, useBreakpoint } from '@object-ui/mobile'; -function App() { - return ( - - - - ); +function MobileNav() { + return ; +} + +function DesktopSidebar() { + return ; +} + +function MainContent() { + return
Main content
; } function ResponsiveApp() { - const { isMobile, isTablet, isDesktop } = useBreakpoint(); + const { isMobile, isDesktop } = useBreakpoint(); return (
@@ -46,84 +50,219 @@ function ResponsiveApp() {
); } + +export function App() { + return ( + + + + ); +} ``` ## API ### MobileProvider -Wraps your application with mobile context: +Wraps your application with mobile context. Both props are optional: `pwa` takes a +`PWAConfig`, `offline` takes a `PWAOfflineConfig`. ```tsx - - - +import { MobileProvider } from '@object-ui/mobile'; + +function App() { + return

Your application

; +} + +export function Root() { + return ( + + + + ); +} ``` ### useBreakpoint -Hook for detecting the current breakpoint: +Hook for detecting the current breakpoint. The current breakpoint name is +`breakpoint` — one of `xs`, `sm`, `md`, `lg`, `xl`, `2xl`: ```tsx -const { isMobile, isTablet, isDesktop, current } = useBreakpoint(); +import { useBreakpoint } from '@object-ui/mobile'; + +export function BreakpointBadge() { + const { isMobile, isTablet, isDesktop, breakpoint, width } = useBreakpoint(); + + return ( + + {breakpoint} at {width}px — mobile {String(isMobile)}, tablet {String(isTablet)}, desktop{' '} + {String(isDesktop)} + + ); +} ``` +`isAbove(bp)` and `isBelow(bp)` are also returned, for comparisons against a named +breakpoint. + ### useResponsive -Hook for responsive values based on screen size: +Hook for responsive values based on screen size. The keys are breakpoint names; a +breakpoint with no entry falls back to the next smaller one that has one: ```tsx -const columns = useResponsive({ mobile: 1, tablet: 2, desktop: 4 }); +import { useResponsive } from '@object-ui/mobile'; + +export function ResponsiveGrid() { + const columns = useResponsive({ xs: 1, md: 2, lg: 4 }); + + return
{columns} column(s)
; +} ``` ### useGesture / useSpecGesture -Hooks for gesture detection on touch devices: +`useGesture` detects one gesture from Object UI's direction-fused vocabulary +(`tap`, `double-tap`, `long-press`, `swipe-left`, `swipe-right`, `swipe-up`, +`swipe-down`, `pinch`, `rotate`, `pan`) per call, and returns a ref to attach: ```tsx -const gestureRef = useGesture({ - onSwipeLeft: () => navigateNext(), - onSwipeRight: () => navigateBack(), - onPinch: (scale) => handleZoom(scale), -}); +import { useGesture } from '@object-ui/mobile'; + +function navigateNext() {} +function navigateBack() {} + +export function SwipeArea() { + const nextRef = useGesture({ + type: 'swipe-left', + onGesture: () => navigateNext(), + }); + const backRef = useGesture({ + type: 'swipe-right', + onGesture: () => navigateBack(), + }); + + return ( +
+
Swipe left for the next record
+
Swipe right to go back
+
+ ); +} +``` + +`useSpecGesture` takes the declarative `SpecGestureConfig` tuning shape instead, and +dispatches to per-gesture callbacks: + +```tsx +import { useSpecGesture } from '@object-ui/mobile'; + +function handleZoom(scale: number) { + return scale; +} + +export function PinchArea() { + const ref = useSpecGesture({ + config: { type: 'pinch', enabled: true, pinch: { minScale: 0.5, maxScale: 3 } }, + onPinch: (scale) => handleZoom(scale), + }); -return
Swipeable content
; + return
Pinchable content
; +} ``` ### usePullToRefresh -Hook for pull-to-refresh behavior: +Hook for pull-to-refresh behavior. Attach the returned `ref` to the scrollable +container: ```tsx -const { isRefreshing } = usePullToRefresh({ - onRefresh: async () => await fetchData(), -}); +import { usePullToRefresh } from '@object-ui/mobile'; + +async function fetchData(): Promise {} + +export function Feed() { + const { ref, isRefreshing, pullDistance } = usePullToRefresh({ + onRefresh: async () => await fetchData(), + }); + + return ( +
+ {isRefreshing ? 'Refreshing…' : 'Pull to refresh'} +
+ ); +} ``` ### useTouchTarget -Hook for ensuring minimum touch target sizes: +Hook for ensuring minimum touch target sizes. It returns the `style` and `className` +to spread onto the element; the defaults follow WCAG 2.5.5 (44×44 CSS pixels): ```tsx -const { targetProps } = useTouchTarget({ minSize: 44 }); -return ; +import { useTouchTarget } from '@object-ui/mobile'; + +export function TapButton() { + const { style, className } = useTouchTarget({ + config: { minWidth: 44, minHeight: 44 }, + }); + + return ( + + ); +} ``` ### ResponsiveContainer -Renders children based on breakpoint: +Renders children based on breakpoint. Pick the range with `minBreakpoint` / +`maxBreakpoint`, or name the breakpoints outright with `showOn` / `hideOn`: ```tsx -} desktop={} /> +import { ResponsiveContainer } from '@object-ui/mobile'; + +function MobileView() { + return

Compact layout

; +} + +function DesktopView() { + return

Full layout

; +} + +export function BreakpointSwitch() { + return ( +
+ + + + + + +
+ ); +} ``` ### PWA Utilities +`PWAConfig` requires `enabled`, `name` and `shortName`. `registerServiceWorker` takes +the script `url` and `scope` plus lifecycle callbacks — the caching strategies live in +the generated worker (`getServiceWorkerSource`), not in this call: + ```tsx import { generatePWAManifest, registerServiceWorker } from '@object-ui/mobile'; -const manifest = generatePWAManifest({ name: 'My App', themeColor: '#000' }); -registerServiceWorker({ cacheStrategy: 'network-first' }); +export const manifest = generatePWAManifest({ + enabled: true, + name: 'My App', + shortName: 'My App', + themeColor: '#000', +}); + +void registerServiceWorker({ url: '/service-worker.js' }); ``` ## Links diff --git a/scripts/check-doc-snippet-types.mjs b/scripts/check-doc-snippet-types.mjs index 5024e6bf9..ca173879e 100644 --- a/scripts/check-doc-snippet-types.mjs +++ b/scripts/check-doc-snippet-types.mjs @@ -723,8 +723,6 @@ const UNGATED_DOCS = { '7 undefined-name diagnostic(s) — blocks continue an earlier block, or use ambient names the page never defines; plus TS2554x2 TS2559x2 — candidate real defects, un-triaged', 'packages/layout/README.md': '3 undefined-name diagnostic(s) — blocks continue an earlier block, or use ambient names the page never defines; 3 unresolved-module diagnostic(s)', - 'packages/mobile/README.md': - '19 undefined-name diagnostic(s) — blocks continue an earlier block, or use ambient names the page never defines; plus TS1108x2 TS2345x1 TS2353x1 — candidate real defects, un-triaged', 'packages/permissions/README.md': '12 undefined-name diagnostic(s) — blocks continue an earlier block, or use ambient names the page never defines; plus TS2322x4 TS2345x1 TS2353x1 — candidate real defects, un-triaged', 'packages/plugin-ai/README.md':