feat: enhance sidebar and toolbar components with shortcut key display, improving user interaction and accessibility

This commit is contained in:
Anish Sarkar 2026-03-07 02:50:01 +05:30
parent b98dbf8952
commit 7a1e24fc52
4 changed files with 70 additions and 24 deletions

View file

@ -3,6 +3,7 @@
import { PanelLeft, PanelLeftClose } from "lucide-react";
import { useTranslations } from "next-intl";
import { Button } from "@/components/ui/button";
import { ShortcutKbd } from "@/components/ui/shortcut-kbd";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { usePlatformShortcut } from "@/hooks/use-platform-shortcut";
@ -18,7 +19,7 @@ export function SidebarCollapseButton({
disableTooltip = false,
}: SidebarCollapseButtonProps) {
const t = useTranslations("sidebar");
const { shortcut } = usePlatformShortcut();
const { shortcutKeys } = usePlatformShortcut();
const button = (
<Button variant="ghost" size="icon" onClick={onToggle} className="h-8 w-8 shrink-0">
@ -35,9 +36,10 @@ export function SidebarCollapseButton({
<Tooltip>
<TooltipTrigger asChild>{button}</TooltipTrigger>
<TooltipContent side={isCollapsed ? "right" : "bottom"}>
{isCollapsed
? `${t("expand_sidebar")} ${shortcut("Mod", "\\")}`
: `${t("collapse_sidebar")} ${shortcut("Mod", "\\")}`}
<span className="flex items-center">
{isCollapsed ? t("expand_sidebar") : t("collapse_sidebar")}
<ShortcutKbd keys={shortcutKeys("Mod", "\\")} />
</span>
</TooltipContent>
</Tooltip>
);

View file

@ -13,9 +13,9 @@ import {
} from "lucide-react";
import { KEYS } from "platejs";
import { useEditorReadOnly, useEditorRef } from "platejs/react";
import * as React from "react";
import { useEditorSave } from "@/components/editor/editor-save-context";
import { ShortcutKbd } from "@/components/ui/shortcut-kbd";
import { Spinner } from "@/components/ui/spinner";
import { usePlatformShortcut } from "@/hooks/use-platform-shortcut";
@ -26,11 +26,20 @@ import { ModeToolbarButton } from "./mode-toolbar-button";
import { ToolbarButton, ToolbarGroup } from "./toolbar";
import { TurnIntoToolbarButton } from "./turn-into-toolbar-button";
function TooltipWithShortcut({ label, keys }: { label: string; keys: string[] }) {
return (
<span className="flex items-center">
{label}
<ShortcutKbd keys={keys} />
</span>
);
}
export function FixedToolbarButtons() {
const readOnly = useEditorReadOnly();
const editor = useEditorRef();
const { onSave, hasUnsavedChanges, isSaving, canToggleMode } = useEditorSave();
const { shortcut } = usePlatformShortcut();
const { shortcutKeys } = usePlatformShortcut();
return (
<div className="flex w-full items-center">
@ -40,7 +49,7 @@ export function FixedToolbarButtons() {
<>
<ToolbarGroup>
<ToolbarButton
tooltip={`Undo ${shortcut("Mod", "Z")}`}
tooltip={<TooltipWithShortcut label="Undo" keys={shortcutKeys("Mod", "Z")} />}
onClick={() => {
editor.undo();
editor.tf.focus();
@ -50,7 +59,7 @@ export function FixedToolbarButtons() {
</ToolbarButton>
<ToolbarButton
tooltip={`Redo ${shortcut("Mod", "Shift", "Z")}`}
tooltip={<TooltipWithShortcut label="Redo" keys={shortcutKeys("Mod", "Shift", "Z")} />}
onClick={() => {
editor.redo();
editor.tf.focus();
@ -66,35 +75,35 @@ export function FixedToolbarButtons() {
</ToolbarGroup>
<ToolbarGroup>
<MarkToolbarButton nodeType={KEYS.bold} tooltip={`Bold ${shortcut("Mod", "B")}`}>
<MarkToolbarButton nodeType={KEYS.bold} tooltip={<TooltipWithShortcut label="Bold" keys={shortcutKeys("Mod", "B")} />}>
<BoldIcon />
</MarkToolbarButton>
<MarkToolbarButton nodeType={KEYS.italic} tooltip={`Italic ${shortcut("Mod", "I")}`}>
<MarkToolbarButton nodeType={KEYS.italic} tooltip={<TooltipWithShortcut label="Italic" keys={shortcutKeys("Mod", "I")} />}>
<ItalicIcon />
</MarkToolbarButton>
<MarkToolbarButton
nodeType={KEYS.underline}
tooltip={`Underline ${shortcut("Mod", "U")}`}
tooltip={<TooltipWithShortcut label="Underline" keys={shortcutKeys("Mod", "U")} />}
>
<UnderlineIcon />
</MarkToolbarButton>
<MarkToolbarButton
nodeType={KEYS.strikethrough}
tooltip={`Strikethrough ${shortcut("Mod", "Shift", "X")}`}
tooltip={<TooltipWithShortcut label="Strikethrough" keys={shortcutKeys("Mod", "Shift", "X")} />}
>
<StrikethroughIcon />
</MarkToolbarButton>
<MarkToolbarButton nodeType={KEYS.code} tooltip={`Code ${shortcut("Mod", "E")}`}>
<MarkToolbarButton nodeType={KEYS.code} tooltip={<TooltipWithShortcut label="Code" keys={shortcutKeys("Mod", "E")} />}>
<Code2Icon />
</MarkToolbarButton>
<MarkToolbarButton
nodeType={KEYS.highlight}
tooltip={`Highlight ${shortcut("Mod", "Shift", "H")}`}
tooltip={<TooltipWithShortcut label="Highlight" keys={shortcutKeys("Mod", "Shift", "H")} />}
>
<HighlighterIcon />
</MarkToolbarButton>
@ -113,7 +122,7 @@ export function FixedToolbarButtons() {
{!readOnly && onSave && hasUnsavedChanges && (
<ToolbarGroup>
<ToolbarButton
tooltip={isSaving ? "Saving..." : `Save ${shortcut("Mod", "S")}`}
tooltip={isSaving ? "Saving..." : <TooltipWithShortcut label="Save" keys={shortcutKeys("Mod", "S")} />}
onClick={onSave}
disabled={isSaving}
className="bg-primary text-primary-foreground hover:bg-primary/90"

View file

@ -0,0 +1,23 @@
import { cn } from "@/lib/utils";
interface ShortcutKbdProps {
keys: string[];
className?: string;
}
export function ShortcutKbd({ keys, className }: ShortcutKbdProps) {
if (keys.length === 0) return null;
return (
<span className={cn("ml-2 inline-flex items-center gap-0.5 text-white/50", className)}>
{keys.map((key) => (
<kbd
key={key}
className="inline-flex size-[16px] items-center justify-center rounded-[3px] bg-white/[0.08] font-sans text-[10px] leading-none"
>
{key}
</kbd>
))}
</span>
);
}

View file

@ -33,25 +33,37 @@ export function usePlatformShortcut() {
setReady(true);
}, []);
const shortcut = useCallback(
(...keys: string[]) => {
if (!ready) return "";
const resolveKeys = useCallback(
(keys: string[]) => {
const mod = isMac ? "⌘" : "Ctrl";
const shift = isMac ? "⇧" : "Shift";
const alt = isMac ? "⌥" : "Alt";
const mapped = keys.map((k) => {
return keys.map((k) => {
if (k === "Mod") return mod;
if (k === "Shift") return shift;
if (k === "Alt") return alt;
return k;
});
return `(${mapped.join("+")})`;
},
[ready, isMac]
[isMac]
);
return { shortcut, isMac, ready };
const shortcut = useCallback(
(...keys: string[]) => {
if (!ready) return "";
return `(${resolveKeys(keys).join("+")})`;
},
[ready, resolveKeys]
);
const shortcutKeys = useCallback(
(...keys: string[]) => {
if (!ready) return [];
return resolveKeys(keys);
},
[ready, resolveKeys]
);
return { shortcut, shortcutKeys, isMac, ready };
}