Files
llink/js/desktop/src/features/onboarding/onboarding-keyboard.tsx
T
ClaudeandArjun Patel af06a250fe Add first-run onboarding wizard for recording mechanics
Introduce a minimal, skippable first-run wizard that teaches the record
key. On first authenticated launch it renders as a full-screen takeover
(gated above PusherProvider) so the stream/compose keyboard handlers are
not mounted to compete for the same keys.

Steps: welcome, hold ` to record, tap V to switch audio/video, tap ` to
toggle recording, mic/camera permission priming, and a recap. The lessons
detect the real gestures and never block — Esc or the Skip control
dismisses at any point.

Reuses existing pieces: VideoAudioToggle, useMediaDevices().requestLabels
for permission priming, KeyHint, and isTypingTarget. Extracts the shared
HOLD_THRESHOLD_MS into lib/constants and reuses it in the compose overlay.

Resolves #259

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01A9AHySdLb1zfZAEm6SuaQP
2026-06-21 11:35:01 -07:00

78 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { cn } from '@/lib/utils';
type KeyState = 'idle' | 'active' | 'success';
interface OnboardingKeyboardProps {
/** The key to emphasise. Defaults to the record key. */
highlightKey?: string;
state: KeyState;
/** Hold progress (01), fills the emphasised key from the bottom. */
progress?: number;
}
interface KeyCapProps {
label: string;
hero?: boolean;
state?: KeyState;
progress?: number;
className?: string;
}
function KeyCap({
label,
hero,
state = 'idle',
progress = 0,
className,
}: KeyCapProps) {
return (
<div
className={cn(
'relative flex select-none items-center justify-center overflow-hidden rounded-lg border text-sm font-medium',
hero ? 'h-14 w-14 text-xl' : 'h-11',
!hero && 'border-white/10 bg-white/5 text-white/30',
hero &&
state === 'idle' &&
'border-white/25 bg-white/10 text-white motion-safe:animate-pulse',
hero && state === 'active' && 'border-white/50 bg-white/20 text-white',
hero &&
state === 'success' &&
'border-emerald-400/50 bg-emerald-500/20 text-emerald-200',
className,
)}
>
{hero && state !== 'success' && progress > 0 && (
<span
className="pointer-events-none absolute inset-x-0 bottom-0 bg-white/25"
style={{ height: `${progress * 100}%` }}
/>
)}
<span className="relative z-10">{label}</span>
</div>
);
}
/**
* Draws the record key in its physical context: Esc above, 1 to its right, and
* Tab / Q on the row below — so the key is recognisable on a real keyboard.
*/
export function OnboardingKeyboard({
highlightKey = '`',
state,
progress = 0,
}: OnboardingKeyboardProps) {
return (
<div className="flex flex-col items-start gap-1.5">
<KeyCap label="esc" className="w-12 text-xs" />
<div className="flex gap-1.5">
<KeyCap label={highlightKey} hero state={state} progress={progress} />
<KeyCap label="1" className="w-11" />
</div>
<div className="flex gap-1.5">
<KeyCap label="tab" className="w-16 text-xs" />
<KeyCap label="Q" className="w-12" />
</div>
</div>
);
}