* 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 * nits * light mode support for onboarding --------- Co-authored-by: Claude <[email protected]>
57 lines
1.7 KiB
TypeScript
57 lines
1.7 KiB
TypeScript
import { useEffect } from 'react';
|
|
import { Check } from 'lucide-react';
|
|
import { OnboardingKeyboard } from './onboarding-keyboard';
|
|
import { OnboardingStep, SUCCESS_DWELL_MS } from './onboarding-step';
|
|
import { useTapGesture } from './use-tap-gesture';
|
|
|
|
export function TapStep({ onAdvance }: { onAdvance: () => void }) {
|
|
const { taps, satisfied } = useTapGesture({
|
|
targetKey: '`',
|
|
targetCode: 'Backquote',
|
|
taps: 2,
|
|
});
|
|
const recording = taps === 1;
|
|
|
|
useEffect(() => {
|
|
if (!satisfied) return;
|
|
const t = setTimeout(onAdvance, SUCCESS_DWELL_MS);
|
|
return () => clearTimeout(t);
|
|
}, [satisfied, onAdvance]);
|
|
|
|
return (
|
|
<OnboardingStep
|
|
title={
|
|
satisfied
|
|
? 'That is tap to toggle'
|
|
: recording
|
|
? 'Recording…'
|
|
: 'Tap to start and stop'
|
|
}
|
|
subtitle={
|
|
satisfied
|
|
? 'A quick tap starts recording, another tap finishes it.'
|
|
: recording
|
|
? 'Now tap ` again to stop.'
|
|
: 'Prefer not to hold? Tap ` once to start recording.'
|
|
}
|
|
status={
|
|
satisfied ? (
|
|
<span className="inline-flex items-center gap-1.5 text-emerald-600 dark:text-emerald-300">
|
|
<Check className="size-4" /> Done.
|
|
</span>
|
|
) : recording ? (
|
|
<span className="inline-flex items-center gap-2 text-muted-foreground">
|
|
<span className="size-2.5 rounded-full bg-red-500 motion-safe:animate-pulse" />
|
|
Recording
|
|
</span>
|
|
) : null
|
|
}
|
|
>
|
|
<OnboardingKeyboard
|
|
highlightKey="`"
|
|
state={satisfied ? 'success' : recording ? 'active' : 'idle'}
|
|
/>
|
|
</OnboardingStep>
|
|
);
|
|
}
|