mirror of
https://github.com/hanzoai/gui.git
synced 2026-10-25 02:31:41 +00:00
This repo could never sync. There was NO merge-base with tamagui — 372 commits
here against 15,791 there, unrelated histories — because it began as a
copy-paste rebrand with a flattened layout. Every "sync" since was a hand
re-application that drifted, which is why the tree sat on 7.3.1 while upstream
shipped fixes we then rediscovered by hand.
This commit's PARENT IS TAMAGUI. A future sync is `git merge tama/main` then
`node scripts/rebrand.mjs` — a real merge, because the history is now shared.
174 packages: 166 upstream, our 8 restored (chrome, shell, telemetry, recipes,
get, font-geist-{sans,mono}, @hanzo/booker), bento-get gained. 170/170 build.
Our patches to files UPSTREAM ALSO OWNS are carried, which the package-level
delta missed and which was where the value actually was:
- useMedia's server snapshot (`initState ?? getMedia()`). Upstream still
returns undefined there, so every Tamagui Dialog kills a prerender — the
exact crash hanzo.app spent a day on.
- data-slot on web host nodes, absent upstream entirely.
Both applied ONTO upstream's newer files (it rewrote useMedia +150/-63 and
createComponent +160/-55), marked `PATCH (ours, carried across the transplant)`.
Seven traps are encoded in scripts/rebrand.mjs, each found by a gate:
skip itself and lockfiles; special-case the double-branded names in BOTH scoped
and bare form; map path strings, not just paths; drop a level for the flattened
group; check disk rather than a stale index.
Rust build artifacts are NOT committed. An earlier draft of this commit swept
apps/desktop/src-tauri/target/debug in via `git add -A` — 3,859 files including
a 272MB .a, over GitHub's 100MB cap, so the push could never have landed. They
are gitignored now.
Co-Authored-By: Hanzo Dev <[email protected]>
8.6 KiB
8.6 KiB
Keyboard Controller Integration Plan
Overview
Add react-native-keyboard-controller integration to Hanzo GUI following the same pattern as gesture-handler and teleport in @hanzogui/native. This enables smooth keyboard + sheet coordination where:
- Dragging sheet down dismisses keyboard FIRST with smooth handoff
- Keyboard and sheet animate in sync (60/120 FPS frame-by-frame)
- Auto-switches to keyboard-controller on native, no-op on web
Current State
The current moveOnKeyboardChange prop in Sheet (lines 508-539 of SheetImplementationCustom.tsx) uses basic Keyboard.addListener() which:
- Only gets start/end events, not frame-by-frame
- Has timing-based animation (250ms hardcoded)
- No coordination with gesture handler
- Janky visual experience
Target Behavior (like gorhom/bottom-sheet)
- Interactive keyboard tracking: Sheet moves frame-by-frame as keyboard animates
- Gesture handoff: When user drags sheet down, keyboard dismisses first, then sheet drags
- Blur on gesture:
enableBlurKeyboardOnGesture- keyboard dismisses when drag starts - Restore behavior:
keyboardBlurBehavior="restore"- sheet returns to pre-keyboard position
Implementation Pattern
Following the exact pattern of setup-gesture-handler.ts:
1. State Module (keyboardControllerState.ts)
const GLOBAL_KEY = '__gui_native_keyboard_controller_state__'
export interface KeyboardControllerState {
enabled: boolean
KeyboardProvider: any
KeyboardAwareScrollView: any
useKeyboardHandler: any
useReanimatedKeyboardAnimation: any
KeyboardController: any
KeyboardStickyView: any
}
function getGlobalState(): KeyboardControllerState {
/* ... */
}
export function isKeyboardControllerEnabled(): boolean
export function getKeyboardControllerState(): KeyboardControllerState
export function setKeyboardControllerState(
updates: Partial<KeyboardControllerState>
): void
2. Setup Module (setup-keyboard-controller.ts)
import { setKeyboardControllerState } from './keyboardControllerState'
function setup() {
const g = globalThis as any
if (g.__gui_native_keyboard_controller_setup_complete) return
g.__gui_native_keyboard_controller_setup_complete = true
try {
const rnkc = require('react-native-keyboard-controller')
const {
KeyboardProvider,
KeyboardAwareScrollView,
useKeyboardHandler,
useReanimatedKeyboardAnimation,
KeyboardController,
KeyboardStickyView,
} = rnkc
if (useKeyboardHandler && KeyboardProvider) {
setKeyboardControllerState({
enabled: true,
KeyboardProvider,
KeyboardAwareScrollView,
useKeyboardHandler,
useReanimatedKeyboardAnimation,
KeyboardController,
KeyboardStickyView,
})
}
} catch {
// keyboard-controller not available
}
}
setup()
3. Web stub (setup-keyboard-controller.web.ts)
// no-op on web
4. Package.json Updates
Add to @hanzogui/native/package.json:
{
"peerDependencies": {
"react-native-keyboard-controller": ">=1.0.0"
},
"peerDependenciesMeta": {
"react-native-keyboard-controller": {
"optional": true
}
},
"exports": {
"./setup-keyboard-controller": {
"react-native": {
"types": "./types/setup-keyboard-controller.d.ts",
"module": "./dist/esm/setup-keyboard-controller.native.js",
"import": "./dist/esm/setup-keyboard-controller.native.js",
"require": "./dist/cjs/setup-keyboard-controller.native.js"
},
"types": "./types/setup-keyboard-controller.d.ts",
"module": "./dist/esm/setup-keyboard-controller.mjs",
"import": "./dist/esm/setup-keyboard-controller.mjs",
"require": "./dist/cjs/setup-keyboard-controller.cjs"
}
}
}
5. Sheet Integration
New hook: useKeyboardControllerSheet.ts
import { isKeyboardControllerEnabled, getKeyboardControllerState } from '@hanzogui/native'
import { useSharedValue } from 'react-native-reanimated'
export function useKeyboardControllerSheet({
enabled,
onKeyboardShow,
onKeyboardHide,
animatedNumber,
positions,
position,
screenSize,
}: UseKeyboardControllerSheetOptions) {
const keyboardControllerEnabled = isKeyboardControllerEnabled()
const keyboardHeight = useSharedValue(0)
const basePosition = useSharedValue(0)
if (!keyboardControllerEnabled || !enabled) {
return { keyboardHeight: null }
}
const { useKeyboardHandler } = getKeyboardControllerState()
useKeyboardHandler({
onStart: (e) => {
'worklet'
// store position before keyboard
basePosition.value = positions[position]
},
onMove: (e) => {
'worklet'
// frame-by-frame tracking
keyboardHeight.value = e.height
const newY = Math.max(basePosition.value - e.height, 0)
// update sheet position directly via shared value
},
onEnd: (e) => {
'worklet'
// keyboard fully shown/hidden
},
})
return { keyboardHeight }
}
6. Gesture + Keyboard Coordination
The key insight from gorhom/bottom-sheet: when gesture starts, blur keyboard first.
In useGestureHandlerPan.ts, add:
const { KeyboardController } = getKeyboardControllerState()
// in onStart handler:
if (keyboardControllerEnabled && enableBlurKeyboardOnGesture) {
KeyboardController.dismiss()
}
New Props for Sheet
interface SheetProps {
// existing
moveOnKeyboardChange?: boolean
// new with keyboard-controller
keyboardBehavior?: 'interactive' | 'extend' | 'fillParent'
keyboardBlurBehavior?: 'none' | 'restore'
enableBlurKeyboardOnGesture?: boolean
}
Files to Create/Modify
Create
pkgs/core/native/src/keyboardControllerState.tspkgs/core/native/src/setup-keyboard-controller.tspkgs/core/native/src/setup-keyboard-controller.web.tspkgs/ui/sheet/src/useKeyboardControllerSheet.tspkgs/ui/sheet/src/useKeyboardControllerSheet.native.tsapps/kitchen-sink/src/usecases/SheetKeyboardDragCase.tsxapps/kitchen-sink/e2e/SheetKeyboardDrag.test.ts
Modify
pkgs/core/native/package.json- add peer dep + exportspkgs/core/native/src/index.ts- export state functionspkgs/ui/sheet/src/SheetImplementationCustom.tsx- integrate hookpkgs/ui/sheet/src/types.ts- add new propsdocs/using-ios.md- add keyboard test workflow
Detox Test Cases
SheetKeyboardDrag.test.ts
describe('SheetKeyboardDrag - Keyboard Controller Integration', () => {
it('should show keyboard-controller enabled', async () => {
// verify setup worked
})
it('Case 1: open keyboard, sheet moves up smoothly', async () => {
// tap input -> keyboard shows -> sheet animates up
// verify sheet position changed
})
it('Case 2: drag sheet down dismisses keyboard first', async () => {
// open keyboard
// start dragging sheet down
// keyboard should dismiss FIRST
// then sheet can drag down
})
it('Case 3: keyboard dismiss, sheet returns to original position', async () => {
// open keyboard (sheet moves up)
// dismiss keyboard (tap outside)
// sheet should animate back to original snap point
})
it('Case 4: rapid keyboard show/hide is smooth', async () => {
// focus input -> unfocus -> focus -> unfocus
// no janky animation
})
})
Testing Workflow (update using-ios.md)
# 1. Start metro in background
cd apps/kitchen-sink && yarn start > /tmp/metro.log 2>&1 &
# 2. Build app once
detox build -c ios.sim.debug
# 3. Run keyboard tests iteratively
detox test --reuse --retries 0 -t "keyboard" -c ios.sim.debug
# 4. Check screenshots in e2e/artifacts/
# Look for smooth keyboard + sheet coordination
# 5. Filter metro logs for keyboard events
grep -E "keyboard|Keyboard" /tmp/metro.log
Success Criteria
- Smooth 60fps animation: Sheet tracks keyboard frame-by-frame
- Gesture handoff works: Drag sheet down -> keyboard dismisses -> sheet drags
- No jank: No visual glitches during rapid keyboard show/hide
- Graceful fallback: If keyboard-controller not installed, falls back to current behavior
- Web no-op: Import doesn't break web builds
- Detox tests pass: All keyboard test cases green
Reference Implementation
- gorhom/bottom-sheet: https://github.com/gorhom/react-native-bottom-sheet
- react-native-keyboard-controller: https://github.com/kirillzyusko/react-native-keyboard-controller
- actions-sheet: Uses keyboard-controller internally
Phase 2 (Future)
KeyboardAwareScrollViewintegration for Sheet.ScrollViewKeyboardStickyViewfor input toolbars that stick above keyboard- Android-specific optimizations (windowSoftInputMode handling)