import { useEffect, useRef, useState, type CSSProperties } from 'react' import './ios.css' // The iOS 27 keyboard, at true points inside a Phone: the Liquid Glass sheet // with its predictive bar, softly rounded keys with wide gaps, special keys // the same color as the letters, a ↵ return key, and the emoji and dictation // buttons below the keys. Measured from iOS 27 screenshots. const ROWS = ['qwertyuiop', 'asdfghjkl', 'zxcvbnm'] export interface KeyboardProps { /** * The text typed so far. Each new character pops its key, and shift follows * iOS's auto-capitalisation (on at the start and after a sentence ends). */ typed?: string /** Force shift on or off instead of following the text. */ shift?: boolean /** The three words in the predictive bar. */ suggestions?: [string, string, string] /** A key held down: a letter, or 'shift', 'delete', 'return', 'space', '123'. */ pressed?: string | null dark?: boolean className?: string style?: CSSProperties } function Glyph({ name }: { name: 'shift' | 'shift-on' | 'delete' | 'return' | 'emoji' | 'mic' }) { const common = { fill: 'none', stroke: 'currentColor', strokeWidth: 1.6, strokeLinejoin: 'round' as const, strokeLinecap: 'round' as const } switch (name) { case 'shift': case 'shift-on': return ( ) case 'delete': return ( ) case 'return': return ( ) case 'emoji': return ( ) case 'mic': return ( ) } } /** * The iOS keyboard. Put it at the bottom of a Phone's screen, under the * composer; pass `typed` and it pops each key as the text grows. */ export function Keyboard({ typed = '', shift, suggestions = ['I', 'The', 'I’m'], pressed, dark, className, style }: KeyboardProps) { // The key that just landed, shown in its callout for a moment const [pop, setPop] = useState<{ ch: string; n: number } | null>(null) const last = useRef(typed) useEffect(() => { const was = last.current last.current = typed if (typed.length <= was.length || !typed.startsWith(was)) return const ch = typed[typed.length - 1].toLowerCase() setPop((p) => ({ ch, n: (p?.n ?? 0) + 1 })) const id = setTimeout(() => setPop(null), 140) return () => clearTimeout(id) }, [typed]) const auto = typed === '' || /[.!?]\s$/.test(typed) const upper = shift ?? auto const down = (k: string) => pressed === k || (k.length === 1 && pop?.ch === k) return (