mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-29 19:06:24 +02:00
feat: update shortcut icons and descriptions for improved clarity
- Replaced icons for "General Assist," "Quick Assist," and "Extreme Assist" shortcuts to better represent their functionalities. - Updated descriptions for each shortcut to enhance user understanding of their actions. - Refactored the layout of the shortcut recorder for a more streamlined user experience.
This commit is contained in:
parent
a4a4deeda0
commit
b74ac8a608
3 changed files with 182 additions and 157 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { AppWindow, Clipboard, Sparkles } from "lucide-react";
|
import { BrainCog, Rocket, Zap } from "lucide-react";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { DEFAULT_SHORTCUTS, ShortcutRecorder } from "@/components/desktop/shortcut-recorder";
|
import { DEFAULT_SHORTCUTS, ShortcutRecorder } from "@/components/desktop/shortcut-recorder";
|
||||||
|
|
@ -103,27 +103,27 @@ export function DesktopContent() {
|
||||||
onReset={() => resetShortcut("generalAssist")}
|
onReset={() => resetShortcut("generalAssist")}
|
||||||
defaultValue={DEFAULT_SHORTCUTS.generalAssist}
|
defaultValue={DEFAULT_SHORTCUTS.generalAssist}
|
||||||
label="General Assist"
|
label="General Assist"
|
||||||
description="Open SurfSense from anywhere"
|
description="Launch SurfSense instantly from any application"
|
||||||
icon={AppWindow}
|
icon={Rocket}
|
||||||
/>
|
/>
|
||||||
<ShortcutRecorder
|
<ShortcutRecorder
|
||||||
value={shortcuts.quickAsk}
|
value={shortcuts.quickAsk}
|
||||||
onChange={(accel) => updateShortcut("quickAsk", accel)}
|
onChange={(accel) => updateShortcut("quickAsk", accel)}
|
||||||
onReset={() => resetShortcut("quickAsk")}
|
onReset={() => resetShortcut("quickAsk")}
|
||||||
defaultValue={DEFAULT_SHORTCUTS.quickAsk}
|
defaultValue={DEFAULT_SHORTCUTS.quickAsk}
|
||||||
label="Quick Assist"
|
label="Quick Assist"
|
||||||
description="Copy selected text and ask AI about it"
|
description="Select text anywhere, then ask AI to explain, rewrite, or act on it"
|
||||||
icon={Clipboard}
|
icon={Zap}
|
||||||
|
/>
|
||||||
|
<ShortcutRecorder
|
||||||
|
value={shortcuts.autocomplete}
|
||||||
|
onChange={(accel) => updateShortcut("autocomplete", accel)}
|
||||||
|
onReset={() => resetShortcut("autocomplete")}
|
||||||
|
defaultValue={DEFAULT_SHORTCUTS.autocomplete}
|
||||||
|
label="Extreme Assist"
|
||||||
|
description="AI drafts text using your screen context and knowledge base"
|
||||||
|
icon={BrainCog}
|
||||||
/>
|
/>
|
||||||
<ShortcutRecorder
|
|
||||||
value={shortcuts.autocomplete}
|
|
||||||
onChange={(accel) => updateShortcut("autocomplete", accel)}
|
|
||||||
onReset={() => resetShortcut("autocomplete")}
|
|
||||||
defaultValue={DEFAULT_SHORTCUTS.autocomplete}
|
|
||||||
label="Extreme Assist"
|
|
||||||
description="AI writing powered by your screen and knowledge base"
|
|
||||||
icon={Sparkles}
|
|
||||||
/>
|
|
||||||
<p className="text-[11px] text-muted-foreground">
|
<p className="text-[11px] text-muted-foreground">
|
||||||
Click a shortcut and press a new key combination to change it.
|
Click a shortcut and press a new key combination to change it.
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { IconBrandGoogleFilled } from "@tabler/icons-react";
|
import { IconBrandGoogleFilled } from "@tabler/icons-react";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { AppWindow, Clipboard, Eye, EyeOff, Keyboard, Sparkles } from "lucide-react";
|
import { BrainCog, Eye, EyeOff, Rocket, Zap } from "lucide-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
@ -10,7 +10,6 @@ import { toast } from "sonner";
|
||||||
import { loginMutationAtom } from "@/atoms/auth/auth-mutation.atoms";
|
import { loginMutationAtom } from "@/atoms/auth/auth-mutation.atoms";
|
||||||
import { DEFAULT_SHORTCUTS, ShortcutRecorder } from "@/components/desktop/shortcut-recorder";
|
import { DEFAULT_SHORTCUTS, ShortcutRecorder } from "@/components/desktop/shortcut-recorder";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
|
@ -100,8 +99,9 @@ export default function DesktopLoginPage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex min-h-screen items-center justify-center bg-background p-4">
|
<div className="relative flex min-h-svh items-center justify-center bg-background p-4 sm:p-6">
|
||||||
<div className="pointer-events-none absolute inset-0 overflow-hidden">
|
{/* Subtle radial glow */}
|
||||||
|
<div className="pointer-events-none fixed inset-0 overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="absolute -top-1/2 left-1/2 size-[800px] -translate-x-1/2 rounded-full opacity-[0.03]"
|
className="absolute -top-1/2 left-1/2 size-[800px] -translate-x-1/2 rounded-full opacity-[0.03]"
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -110,134 +110,157 @@ export default function DesktopLoginPage() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card className="relative w-full max-w-md shadow-lg">
|
<div className="relative flex w-full max-w-md flex-col overflow-hidden rounded-xl border bg-card shadow-lg">
|
||||||
<CardHeader className="items-center text-center pb-4">
|
{/* Header */}
|
||||||
|
<div className="flex flex-col items-center px-6 pt-6 pb-2 text-center">
|
||||||
<Image
|
<Image
|
||||||
src="/icon-128.svg"
|
src="/icon-128.svg"
|
||||||
className="select-none dark:invert size-14 rounded-md mb-2"
|
className="select-none dark:invert size-12 rounded-lg mb-3"
|
||||||
alt="SurfSense"
|
alt="SurfSense"
|
||||||
width={56}
|
width={48}
|
||||||
height={56}
|
height={48}
|
||||||
priority
|
priority
|
||||||
/>
|
/>
|
||||||
<CardTitle className="text-xl">Welcome to SurfSense Desktop App</CardTitle>
|
<h1 className="text-lg font-semibold tracking-tight">
|
||||||
<CardDescription>Configure your shortcuts, then sign in to get started.</CardDescription>
|
Welcome to SurfSense Desktop
|
||||||
</CardHeader>
|
</h1>
|
||||||
|
<p className="mt-1 text-sm text-muted-foreground">
|
||||||
<CardContent className="flex flex-col gap-6">
|
Configure shortcuts, then sign in to get started.
|
||||||
{/* ---- Shortcuts Section (first) ---- */}
|
</p>
|
||||||
{shortcutsLoaded ? (
|
</div>
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
<div className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground mb-1">
|
|
||||||
<Keyboard className="size-3" />
|
|
||||||
Keyboard Shortcuts
|
|
||||||
</div>
|
|
||||||
<ShortcutRecorder
|
|
||||||
value={shortcuts.generalAssist}
|
|
||||||
onChange={(accel) => updateShortcut("generalAssist", accel)}
|
|
||||||
onReset={() => resetShortcut("generalAssist")}
|
|
||||||
defaultValue={DEFAULT_SHORTCUTS.generalAssist}
|
|
||||||
label="General Assist"
|
|
||||||
description="Open SurfSense from anywhere"
|
|
||||||
icon={AppWindow}
|
|
||||||
/>
|
|
||||||
<ShortcutRecorder
|
|
||||||
value={shortcuts.quickAsk}
|
|
||||||
onChange={(accel) => updateShortcut("quickAsk", accel)}
|
|
||||||
onReset={() => resetShortcut("quickAsk")}
|
|
||||||
defaultValue={DEFAULT_SHORTCUTS.quickAsk}
|
|
||||||
label="Quick Assist"
|
|
||||||
description="Copy selected text and ask AI about it"
|
|
||||||
icon={Clipboard}
|
|
||||||
/>
|
|
||||||
<ShortcutRecorder
|
|
||||||
value={shortcuts.autocomplete}
|
|
||||||
onChange={(accel) => updateShortcut("autocomplete", accel)}
|
|
||||||
onReset={() => resetShortcut("autocomplete")}
|
|
||||||
defaultValue={DEFAULT_SHORTCUTS.autocomplete}
|
|
||||||
label="Extreme Assist"
|
|
||||||
description="AI writing powered by your screen and knowledge base"
|
|
||||||
icon={Sparkles}
|
|
||||||
/>
|
|
||||||
<p className="text-[11px] text-muted-foreground text-center">
|
|
||||||
Click a shortcut and press a new key combination to change it.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex justify-center py-4">
|
|
||||||
<Spinner size="sm" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ---- Divider ---- */}
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
{/* ---- Auth Section (second) ---- */}
|
|
||||||
{isGoogleAuth ? (
|
|
||||||
<Button variant="outline" className="w-full gap-2 py-5" onClick={handleGoogleLogin}>
|
|
||||||
<IconBrandGoogleFilled className="size-5" />
|
|
||||||
Continue with Google
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<form onSubmit={handleLocalLogin} className="flex flex-col gap-4">
|
|
||||||
{loginError && (
|
|
||||||
<div className="rounded-md border border-destructive/20 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
|
||||||
{loginError}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
{/* Scrollable content */}
|
||||||
|
<div className="flex-1 overflow-y-auto px-6 py-4">
|
||||||
|
<div className="flex flex-col gap-5">
|
||||||
|
{/* ---- Shortcuts ---- */}
|
||||||
|
{shortcutsLoaded ? (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Label htmlFor="email">Email</Label>
|
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
||||||
<Input
|
Keyboard Shortcuts
|
||||||
id="email"
|
</p>
|
||||||
type="email"
|
<div className="flex flex-col gap-1.5">
|
||||||
placeholder="you@example.com"
|
<ShortcutRecorder
|
||||||
required
|
value={shortcuts.generalAssist}
|
||||||
value={email}
|
onChange={(accel) => updateShortcut("generalAssist", accel)}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onReset={() => resetShortcut("generalAssist")}
|
||||||
disabled={isLoggingIn}
|
defaultValue={DEFAULT_SHORTCUTS.generalAssist}
|
||||||
autoFocus
|
label="General Assist"
|
||||||
/>
|
description="Launch SurfSense instantly from any application"
|
||||||
</div>
|
icon={Rocket}
|
||||||
|
/>
|
||||||
<div className="flex flex-col gap-2">
|
<ShortcutRecorder
|
||||||
<Label htmlFor="password">Password</Label>
|
value={shortcuts.quickAsk}
|
||||||
<div className="relative">
|
onChange={(accel) => updateShortcut("quickAsk", accel)}
|
||||||
<Input
|
onReset={() => resetShortcut("quickAsk")}
|
||||||
id="password"
|
defaultValue={DEFAULT_SHORTCUTS.quickAsk}
|
||||||
type={showPassword ? "text" : "password"}
|
label="Quick Assist"
|
||||||
placeholder="Enter your password"
|
description="Select text anywhere, then ask AI to explain, rewrite, or act on it"
|
||||||
required
|
icon={Zap}
|
||||||
value={password}
|
/>
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
<ShortcutRecorder
|
||||||
disabled={isLoggingIn}
|
value={shortcuts.autocomplete}
|
||||||
className="pr-10"
|
onChange={(accel) => updateShortcut("autocomplete", accel)}
|
||||||
|
onReset={() => resetShortcut("autocomplete")}
|
||||||
|
defaultValue={DEFAULT_SHORTCUTS.autocomplete}
|
||||||
|
label="Extreme Assist"
|
||||||
|
description="AI drafts text using your screen context and knowledge base"
|
||||||
|
icon={BrainCog}
|
||||||
/>
|
/>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowPassword((v) => !v)}
|
|
||||||
className="absolute inset-y-0 right-0 flex items-center pr-3 text-muted-foreground hover:text-foreground"
|
|
||||||
tabIndex={-1}
|
|
||||||
>
|
|
||||||
{showPassword ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-[11px] text-muted-foreground text-center mt-1">
|
||||||
|
Click a shortcut and press a new key combination to change it.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex justify-center py-6">
|
||||||
|
<Spinner size="sm" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button type="submit" disabled={isLoggingIn} className="mt-1">
|
<Separator />
|
||||||
{isLoggingIn ? (
|
|
||||||
<>
|
{/* ---- Auth ---- */}
|
||||||
<Spinner size="sm" className="text-primary-foreground" />
|
<div className="flex flex-col gap-3">
|
||||||
Signing in...
|
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
||||||
</>
|
Sign In
|
||||||
) : (
|
</p>
|
||||||
"Sign in"
|
|
||||||
)}
|
{isGoogleAuth ? (
|
||||||
</Button>
|
<Button variant="outline" className="w-full gap-2 h-10" onClick={handleGoogleLogin}>
|
||||||
</form>
|
<IconBrandGoogleFilled className="size-4" />
|
||||||
)}
|
Continue with Google
|
||||||
</CardContent>
|
</Button>
|
||||||
</Card>
|
) : (
|
||||||
|
<form onSubmit={handleLocalLogin} className="flex flex-col gap-3">
|
||||||
|
{loginError && (
|
||||||
|
<div className="rounded-md border border-destructive/20 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||||
|
{loginError}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
<Label htmlFor="email" className="text-xs">
|
||||||
|
Email
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="you@example.com"
|
||||||
|
required
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
disabled={isLoggingIn}
|
||||||
|
autoFocus
|
||||||
|
className="h-9"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
<Label htmlFor="password" className="text-xs">
|
||||||
|
Password
|
||||||
|
</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
placeholder="Enter your password"
|
||||||
|
required
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
disabled={isLoggingIn}
|
||||||
|
className="h-9 pr-9"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword((v) => !v)}
|
||||||
|
className="absolute inset-y-0 right-0 flex items-center pr-2.5 text-muted-foreground hover:text-foreground"
|
||||||
|
tabIndex={-1}
|
||||||
|
>
|
||||||
|
{showPassword ? (
|
||||||
|
<EyeOff className="size-3.5" />
|
||||||
|
) : (
|
||||||
|
<Eye className="size-3.5" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button type="submit" disabled={isLoggingIn} className="h-9 mt-1">
|
||||||
|
{isLoggingIn ? (
|
||||||
|
<>
|
||||||
|
<Spinner size="sm" className="text-primary-foreground" />
|
||||||
|
Signing in…
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Sign in"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Accelerator ↔ display helpers
|
// Accelerator <-> display helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export function keyEventToAccelerator(e: React.KeyboardEvent): string | null {
|
export function keyEventToAccelerator(e: React.KeyboardEvent): string | null {
|
||||||
|
|
@ -47,13 +47,13 @@ export const DEFAULT_SHORTCUTS = {
|
||||||
|
|
||||||
export function Kbd({ keys, className }: { keys: string[]; className?: string }) {
|
export function Kbd({ keys, className }: { keys: string[]; className?: string }) {
|
||||||
return (
|
return (
|
||||||
<span className={cn("inline-flex items-center gap-1", className)}>
|
<span className={cn("inline-flex items-center gap-0.5", className)}>
|
||||||
{keys.map((key) => (
|
{keys.map((key, i) => (
|
||||||
<kbd
|
<kbd
|
||||||
key={key}
|
key={`${key}-${i}`}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex h-7 min-w-7 items-center justify-center rounded-md border bg-muted px-1.5 font-mono text-xs font-medium text-muted-foreground shadow-sm",
|
"inline-flex h-6 min-w-6 items-center justify-center rounded border bg-muted px-1 font-mono text-[11px] font-medium text-muted-foreground",
|
||||||
key.length > 3 && "px-2"
|
key.length > 3 && "px-1.5"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{key}
|
{key}
|
||||||
|
|
@ -111,27 +111,29 @@ export function ShortcutRecorder({
|
||||||
const isDefault = value === defaultValue;
|
const isDefault = value === defaultValue;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between gap-4 rounded-lg border bg-background p-3">
|
<div className="group flex items-center gap-3 rounded-lg border border-border/60 bg-card px-3 py-2.5 transition-colors hover:border-border">
|
||||||
<div className="flex items-center gap-3 min-w-0">
|
{/* Icon */}
|
||||||
<div className="flex size-9 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
|
<div className="flex size-8 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
|
||||||
<Icon className="size-4" />
|
<Icon className="size-4" />
|
||||||
</div>
|
|
||||||
<div className="min-w-0">
|
|
||||||
<p className="text-sm font-medium leading-none">{label}</p>
|
|
||||||
<p className="mt-1 text-xs text-muted-foreground truncate">{description}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 shrink-0">
|
{/* Label + description */}
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<p className="text-[13px] font-medium leading-none">{label}</p>
|
||||||
|
<p className="mt-1 text-[11px] leading-snug text-muted-foreground">{description}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Actions */}
|
||||||
|
<div className="flex shrink-0 items-center gap-1">
|
||||||
{!isDefault && (
|
{!isDefault && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="size-7"
|
className="size-6 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||||
onClick={onReset}
|
onClick={onReset}
|
||||||
title="Reset to default"
|
title="Reset to default"
|
||||||
>
|
>
|
||||||
<RotateCcw />
|
<RotateCcw className="size-3" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
|
|
@ -141,14 +143,14 @@ export function ShortcutRecorder({
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onBlur={() => setRecording(false)}
|
onBlur={() => setRecording(false)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-9 items-center gap-1 rounded-md border px-3 text-sm transition-all focus:outline-none",
|
"flex h-7 items-center gap-0.5 rounded-md border px-2 transition-all focus:outline-none",
|
||||||
recording
|
recording
|
||||||
? "border-primary bg-primary/5 ring-2 ring-primary/20"
|
? "border-primary bg-primary/5 ring-2 ring-primary/20"
|
||||||
: "border-input bg-muted/50 hover:bg-muted"
|
: "border-input bg-muted/40 hover:bg-muted"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{recording ? (
|
{recording ? (
|
||||||
<span className="text-xs text-primary animate-pulse">Press keys...</span>
|
<span className="text-[11px] text-primary animate-pulse whitespace-nowrap">Press keys…</span>
|
||||||
) : (
|
) : (
|
||||||
<Kbd keys={displayKeys} />
|
<Kbd keys={displayKeys} />
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue