feat(mobile): Wave 4 — RegisterPage responsive + TransferIndicator on settings + MessageInput sheets

- RegisterPage: scroll envelope, iOS-zoom-safe inputs, ≥44px tap targets, mobile-friendly invite chip + swatch row
- TransferIndicator: mounted via MobileScreenHeader.rightActions across every settings/instance screen and inline in MobileChatScreen; touch-close listener; viewport-safe panel width
- MessageInput popovers (emoji/GIF/mention) now route through new InputPopover wrapper — desktop popover, mobile bottom-sheet
- EmojiPicker mobile branch: dynamicWidth + scoped CSS (.emoji-picker-wrapper--mobile) so the <em-emoji-picker> custom element fills the sheet; bigger touch targets (perLine 8, button 40, emoji 28); desktop unchanged
- MessageInput trigger row: mobile-first sizing with md: desktop overrides (40×40 buttons + gap on mobile, 34×34 unchanged on desktop)
- Spec updates: docs/systems/auth.md (RegisterPage responsive contract), docs/systems/uploads.md (TransferIndicator mobile surfaces)
This commit is contained in:
Jannis Braun
2026-05-07 22:58:57 +02:00
parent 8544f83225
commit fb9fe326c4
13 changed files with 439 additions and 135 deletions
+18 -5
View File
@@ -4,9 +4,14 @@ import type { GifResult } from '@backspace/shared';
interface GifPickerProps {
onGifSelect: (url: string) => void;
/**
* Mobile rendering: drop the desktop fixed dimensions and let the picker
* fill its parent (a bottom sheet that controls width + max-height).
*/
mobile?: boolean;
}
export function GifPicker({ onGifSelect }: GifPickerProps) {
export function GifPicker({ onGifSelect, mobile = false }: GifPickerProps) {
const [query, setQuery] = useState('');
const [debouncedQuery, setDebouncedQuery] = useState('');
const [results, setResults] = useState<GifResult[]>([]);
@@ -78,17 +83,25 @@ export function GifPicker({ onGifSelect }: GifPickerProps) {
e.stopPropagation();
};
// Mobile: fill parent (sheet sets width + max-height). Desktop: fixed dims
// matching the legacy popover footprint.
const rootClass = mobile
? 'flex flex-col flex-1 min-h-0 w-full'
: 'flex flex-col h-[390px] w-[390px]';
return (
<div className="flex flex-col h-[390px] w-[390px]" onKeyDown={handleKeyDown}>
<div className={rootClass} onKeyDown={handleKeyDown}>
{/* Search */}
<div className="px-3 pt-2 pb-1.5">
<div className="px-3 pt-2 pb-1.5 shrink-0">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search GIFs"
className="input-search w-full"
autoFocus
// Auto-focus only on desktop. On mobile this would force the OS
// keyboard up the moment the sheet opens, hiding most of the grid.
autoFocus={!mobile}
/>
</div>
@@ -141,7 +154,7 @@ export function GifPicker({ onGifSelect }: GifPickerProps) {
</div>
{/* Attribution */}
<div className="px-3 py-1 text-[10px] text-txt-tertiary text-right">
<div className="px-3 py-1 text-[10px] text-txt-tertiary text-right shrink-0">
Powered by Klipy
</div>
</div>