Mobile Quick Reference - nself-org/nchat GitHub Wiki
Quick reference guide for mobile UI components and utilities.
// Mobile Components
import {
VirtualMessageList,
LongPressMenu,
PinchZoom,
PullToRefresh,
SkeletonLoader,
TouchButton,
TouchIconButton,
TouchListItem,
} from '@/components/mobile'
// Dark Mode
import { useDarkMode } from '@/hooks/use-dark-mode'
import { DarkModeToggle } from '@/components/ui/dark-mode-toggle'
// Performance
import { LazyMessageList, preloadChatComponents } from '@/lib/performance/lazy-components'
import { debounce, useDebounce, memoize } from '@/lib/performance/optimization'
// Accessibility
import {
useAnnounce,
useFocusTrap,
useKeyboardShortcut,
useReducedMotion,
meetsWCAGAA,
} from '@/lib/accessibility/a11y-utils'<PullToRefresh onRefresh={fetchNewMessages}>
<VirtualMessageList
messages={messages}
isLoading={isLoading}
hasMore={hasMore}
onLoadMore={loadOlderMessages}
renderMessage={(msg, index) => (
<LongPressMenu
items={[
{ id: 'reply', label: 'Reply', icon: <Reply />, onClick: () => handleReply(msg) },
{ id: 'delete', label: 'Delete', destructive: true, onClick: () => handleDelete(msg.id) },
]}
>
<MessageItem message={msg} />
</LongPressMenu>
)}
renderDateSeparator={(date) => <DateSeparator date={date} />}
/>
</PullToRefresh>{
isLoading ? (
<SkeletonLoader type="message" count={8} />
) : (
<VirtualMessageList messages={messages} />
)
}<TouchButton
variant="default"
size="default"
hapticFeedback
onClick={handleClick}
>
Send Message
</TouchButton>
<TouchIconButton
icon={<Send />}
label="Send"
hapticFeedback
onClick={handleSend}
/><PinchZoom minScale={1} maxScale={4} enableRotation enableDownload downloadUrl={imageUrl}>
<img src={imageUrl} alt="Message attachment" />
</PinchZoom>// In header
<DarkModeToggle variant="dropdown" />
// Mobile FAB
<CompactDarkModeToggle />
// Hook usage
const { isDark, toggle, colorScheme, setColorScheme } = useDarkMode()// Announce to screen readers
const announce = useAnnounce()
announce('Message sent successfully', 'polite')
// Keyboard shortcuts
useKeyboardShortcut([
{ key: 'k', ctrl: true, callback: openSearch },
{ key: 'Escape', callback: closeModal },
])
// Respect user preferences
const shouldReduceMotion = useReducedMotion()
const isHighContrast = useHighContrast()// Component
const MessageList = LazyMessageList.Component
// Preload on route change
useEffect(() => {
preloadChatComponents()
}, [])
// Smart preload (checks network)
smartPreload(() => LazyModal.preload())// Debounce search
const [search, setSearch] = useState('')
const debouncedSearch = useDebounce(search, 300)
// Memoize expensive function
const result = useMemo(() => memoize(expensiveCalc)(data), [data])
// Process in chunks
const results = await processInChunks(items, processItem, 10)When building mobile UI:
- Use
VirtualMessageListfor lists >50 items - Add
SkeletonLoaderfor all async content - Use
TouchButton/TouchListItemfor interactive elements - Implement
LongPressMenufor context actions - Add
PullToRefreshfor list views - Use
PinchZoomfor images - Respect
useReducedMotion()for animations - Test with
useAnnounce()for screen readers - Ensure all touch targets ≥44pt/48dp
- Add proper
aria-labelto icon buttons
- Lazy load heavy components
- Preload on route change
- Use virtual scrolling for long lists
- Debounce search inputs
- Memoize expensive calculations
- Optimize images (lazy load, srcset)
- Check bundle size (<200KB main)
- Test on low-end devices
- Measure with Lighthouse mobile
- All text has 4.5:1 contrast ratio
- All interactive elements have focus indicators
- Keyboard navigation works
- Screen reader announces important changes
- Forms have proper labels and error messages
- Touch targets ≥44pt/48dp
- Supports high contrast mode
- Respects reduced motion preference
- Color is not the only indicator
- Images have alt text
// Constants
import {
IOS_MIN_TAP_TARGET, // 44
ANDROID_MIN_TAP_TARGET, // 48
MIN_TAP_TARGET, // 48 (max of iOS/Android)
} from '@/components/mobile'
// Usage
<button style={{ minHeight: MIN_TAP_TARGET, minWidth: MIN_TAP_TARGET }}>
Icon
</button>
// Or use TouchArea wrapper
<TouchArea>
<SmallIcon />
</TouchArea>// Long press
const { handlers } = useLongPress(handleLongPress, { duration: 500 })
<div {...handlers}>Long press me</div>
// Pull to refresh
const { refreshState, pullDistance, handlers } = usePullToRefresh({
onRefresh: async () => await fetchData(),
})
// Already built-in to components:
// - Swipe actions (SwipeActions)
// - Pinch zoom (PinchZoom)
// - Pull to refresh (PullToRefresh)// Built-in to components
<TouchButton hapticFeedback onClick={handleClick} />
<LongPressMenu hapticFeedback items={items} />
// Manual trigger
if ('vibrate' in navigator) {
navigator.vibrate(10) // 10ms vibration
}import { shouldPreload, getNetworkInfo } from '@/lib/performance/optimization'
// Check before preloading
if (shouldPreload()) {
preloadHeavyComponents()
}
// Get network info
const network = getNetworkInfo()
if (network.saveData) {
// Use low-quality images
}# Lighthouse mobile audit
pnpm lighthouse
# Bundle size analysis
pnpm build:analyze
# Type check
pnpm type-check
# E2E tests
pnpm test:e2e
# All checks
pnpm check-all- Reduce
overscanprop - Check message render performance
- Ensure
estimateSizeis accurate
- Use
next-themesThemeProvider - Add
suppressHydrationWarningto html tag - Ensure localStorage loads before render
- Use
TouchButtoninstead ofButton - Wrap small elements in
TouchArea - Check with browser inspector (show rulers)
- Adjust skeleton heights
- Use multiple skeleton types
- Match skeleton structure to content
- Check bundle size with
pnpm build:analyze - Lazy load heavy components
- Use virtual scrolling
- Debounce expensive operations
- Full docs:
/docs/Mobile-UI-v0.8.0.md - Component demos:
/src/app/mobile-demo - Storybook:
pnpm storybook(if configured)