nits
This commit is contained in:
@@ -29,7 +29,7 @@ function KeyCap({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative flex select-none items-center justify-center overflow-hidden rounded-lg border text-sm font-medium',
|
'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 ? 'h-14 w-14 text-xl' : 'h-14',
|
||||||
!hero && 'border-white/10 bg-white/5 text-white/30',
|
!hero && 'border-white/10 bg-white/5 text-white/30',
|
||||||
hero &&
|
hero &&
|
||||||
state === 'idle' &&
|
state === 'idle' &&
|
||||||
@@ -63,13 +63,13 @@ export function OnboardingKeyboard({
|
|||||||
}: OnboardingKeyboardProps) {
|
}: OnboardingKeyboardProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-start gap-1.5">
|
<div className="flex flex-col items-start gap-1.5">
|
||||||
<KeyCap label="esc" className="w-12 text-xs" />
|
<KeyCap label="esc" className="w-14 text-xs" />
|
||||||
<div className="flex gap-1.5">
|
<div className="flex gap-1.5">
|
||||||
<KeyCap label={highlightKey} hero state={state} progress={progress} />
|
<KeyCap label={highlightKey} hero state={state} progress={progress} />
|
||||||
<KeyCap label="1" className="w-11" />
|
<KeyCap label="1" className="w-14" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1.5">
|
<div className="flex gap-1.5">
|
||||||
<KeyCap label="tab" className="w-16 text-xs" />
|
<KeyCap label="tab" className="w-20 text-xs" />
|
||||||
<KeyCap label="Q" className="w-12" />
|
<KeyCap label="Q" className="w-12" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { WindowControls } from '@/components/window-controls';
|
import { WindowControls } from '@/components/window-controls';
|
||||||
import { KeyHint } from '@/components/key-hint';
|
import { KeyHint } from '@/components/key-hint';
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { OnboardingKeyboard } from './onboarding-keyboard';
|
import { OnboardingKeyboard } from './onboarding-keyboard';
|
||||||
import { OnboardingStep } from './onboarding-step';
|
import { OnboardingStep } from './onboarding-step';
|
||||||
@@ -10,14 +9,7 @@ import { ToggleStep } from './toggle-step';
|
|||||||
import { TapStep } from './tap-step';
|
import { TapStep } from './tap-step';
|
||||||
import { PermissionStep } from './permission-step';
|
import { PermissionStep } from './permission-step';
|
||||||
|
|
||||||
const STEPS = [
|
const STEPS = ['welcome', 'hold', 'tap', 'toggle', 'permission'] as const;
|
||||||
'welcome',
|
|
||||||
'hold',
|
|
||||||
'toggle',
|
|
||||||
'tap',
|
|
||||||
'permission',
|
|
||||||
'done',
|
|
||||||
] as const;
|
|
||||||
type Step = (typeof STEPS)[number];
|
type Step = (typeof STEPS)[number];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,6 +26,10 @@ export function OnboardingOverlay({ onComplete }: { onComplete: () => void }) {
|
|||||||
setStep((cur) => STEPS[Math.min(STEPS.indexOf(cur) + 1, STEPS.length - 1)]);
|
setStep((cur) => STEPS[Math.min(STEPS.indexOf(cur) + 1, STEPS.length - 1)]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const goBack = useCallback(() => {
|
||||||
|
setStep((cur) => STEPS[Math.max(0, STEPS.indexOf(cur) - 1)]);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Focus the panel on the gesture steps so window-level key handling has a
|
// Focus the panel on the gesture steps so window-level key handling has a
|
||||||
// home; the static steps autofocus their primary button instead, which lets
|
// home; the static steps autofocus their primary button instead, which lets
|
||||||
// Enter activate it natively (no duplicate window handler).
|
// Enter activate it natively (no duplicate window handler).
|
||||||
@@ -43,17 +39,27 @@ export function OnboardingOverlay({ onComplete }: { onComplete: () => void }) {
|
|||||||
}
|
}
|
||||||
}, [step]);
|
}, [step]);
|
||||||
|
|
||||||
// Esc skips the wizard from anywhere.
|
// Esc skips the wizard from anywhere; ← (and Backspace) steps back.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKey = (e: KeyboardEvent) => {
|
const onKey = (e: KeyboardEvent) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onComplete();
|
onComplete();
|
||||||
|
} else if (e.key === 'Enter' && step === 'welcome') {
|
||||||
|
e.preventDefault();
|
||||||
|
goNext();
|
||||||
|
} else if (
|
||||||
|
e.key === 'ArrowLeft' ||
|
||||||
|
e.key === 'Backspace' ||
|
||||||
|
e.key === 'Delete'
|
||||||
|
) {
|
||||||
|
e.preventDefault();
|
||||||
|
goBack();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.addEventListener('keydown', onKey);
|
window.addEventListener('keydown', onKey);
|
||||||
return () => window.removeEventListener('keydown', onKey);
|
return () => window.removeEventListener('keydown', onKey);
|
||||||
}, [onComplete]);
|
}, [onComplete, goNext, step, goBack]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen flex-col bg-background text-white">
|
<div className="flex h-screen flex-col bg-background text-white">
|
||||||
@@ -80,17 +86,7 @@ export function OnboardingOverlay({ onComplete }: { onComplete: () => void }) {
|
|||||||
{step === 'welcome' && (
|
{step === 'welcome' && (
|
||||||
<OnboardingStep
|
<OnboardingStep
|
||||||
title="Welcome to llink"
|
title="Welcome to llink"
|
||||||
subtitle="llink is built for your keyboard. Let's learn the one key that matters, it takes 20 seconds."
|
subtitle="To use llink, you must learn the keyboard shortcuts. Don't worry, once you learn them, you'll feel like your flowing."
|
||||||
footer={
|
|
||||||
<>
|
|
||||||
<Button onClick={goNext} autoFocus>
|
|
||||||
Get started
|
|
||||||
</Button>
|
|
||||||
<KeyHint keys="Enter" className="text-xs text-white/40">
|
|
||||||
to begin
|
|
||||||
</KeyHint>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<OnboardingKeyboard highlightKey="`" state="idle" />
|
<OnboardingKeyboard highlightKey="`" state="idle" />
|
||||||
</OnboardingStep>
|
</OnboardingStep>
|
||||||
@@ -98,33 +94,7 @@ export function OnboardingOverlay({ onComplete }: { onComplete: () => void }) {
|
|||||||
{step === 'hold' && <HoldStep onAdvance={goNext} />}
|
{step === 'hold' && <HoldStep onAdvance={goNext} />}
|
||||||
{step === 'toggle' && <ToggleStep onAdvance={goNext} />}
|
{step === 'toggle' && <ToggleStep onAdvance={goNext} />}
|
||||||
{step === 'tap' && <TapStep onAdvance={goNext} />}
|
{step === 'tap' && <TapStep onAdvance={goNext} />}
|
||||||
{step === 'permission' && <PermissionStep onAdvance={goNext} />}
|
{step === 'permission' && <PermissionStep onAdvance={onComplete} />}
|
||||||
{step === 'done' && (
|
|
||||||
<OnboardingStep
|
|
||||||
title="You're all set"
|
|
||||||
subtitle="That's everything you need to start sharing."
|
|
||||||
footer={
|
|
||||||
<>
|
|
||||||
<Button onClick={onComplete} autoFocus>
|
|
||||||
Start using llink
|
|
||||||
</Button>
|
|
||||||
<KeyHint keys="?" className="text-xs text-white/40">
|
|
||||||
for all shortcuts
|
|
||||||
</KeyHint>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="flex flex-col items-center gap-2 text-sm text-white/70">
|
|
||||||
<KeyHint keys="`" prefix="Hold">
|
|
||||||
to talk
|
|
||||||
</KeyHint>
|
|
||||||
<KeyHint keys="V">switch audio / video</KeyHint>
|
|
||||||
<KeyHint keys="`" prefix="Tap">
|
|
||||||
to start and stop
|
|
||||||
</KeyHint>
|
|
||||||
</div>
|
|
||||||
</OnboardingStep>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex items-center justify-center gap-1.5">
|
<div className="flex items-center justify-center gap-1.5">
|
||||||
{STEPS.map((s, i) => (
|
{STEPS.map((s, i) => (
|
||||||
@@ -141,6 +111,28 @@ export function OnboardingOverlay({ onComplete }: { onComplete: () => void }) {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex min-h-5 items-center justify-center gap-4 text-xs text-white/40">
|
||||||
|
{index > 0 && (
|
||||||
|
<KeyHint
|
||||||
|
keys="←"
|
||||||
|
onClick={goBack}
|
||||||
|
title="Back (or press ←)"
|
||||||
|
aria-label="Previous step"
|
||||||
|
>
|
||||||
|
back
|
||||||
|
</KeyHint>
|
||||||
|
)}
|
||||||
|
{step === 'welcome' && (
|
||||||
|
<KeyHint
|
||||||
|
keys="Enter"
|
||||||
|
onClick={goNext}
|
||||||
|
title="Continue (or press Enter)"
|
||||||
|
>
|
||||||
|
continue
|
||||||
|
</KeyHint>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
/** How long a success state lingers before the wizard auto-advances, in ms. */
|
/** How long a success state lingers before the wizard auto-advances, in ms. */
|
||||||
export const SUCCESS_DWELL_MS = 800;
|
export const SUCCESS_DWELL_MS = 2000;
|
||||||
|
|
||||||
interface OnboardingStepProps {
|
interface OnboardingStepProps {
|
||||||
title: ReactNode;
|
title: ReactNode;
|
||||||
|
|||||||
Reference in New Issue
Block a user