Files
llink/js/desktop/src/features/onboarding/tap-step.tsx
T
fec7f5d6d8 Add onboarding wizard for teaching the record key (#301)
* 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]>
2026-06-21 11:45:14 -07:00

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>
);
}