agent-ecosystem/src/renderer/components/team/editor/EditorToolbar.tsx

173 lines
5.3 KiB
TypeScript

/**
* Toolbar with Save, Undo, Redo buttons.
*/
import React from 'react';
import { redo, undo } from '@codemirror/commands';
import { useAppTranslation } from '@features/localization/renderer';
import { Button } from '@renderer/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { useStore } from '@renderer/store';
import { editorBridge } from '@renderer/utils/editorBridge';
import { shortcutLabel } from '@renderer/utils/platformKeys';
import { Columns2, Eye, Redo2, Save, Undo2, WrapText } from 'lucide-react';
import { useShallow } from 'zustand/react/shallow';
// =============================================================================
// Types
// =============================================================================
export type MdPreviewMode = 'off' | 'split' | 'preview';
// =============================================================================
// Component
// =============================================================================
interface EditorToolbarProps {
isMarkdown?: boolean;
mdPreviewMode?: MdPreviewMode;
onToggleSplit?: () => void;
onToggleFullPreview?: () => void;
}
export const EditorToolbar = ({
isMarkdown = false,
mdPreviewMode = 'off',
onToggleSplit,
onToggleFullPreview,
}: EditorToolbarProps): React.ReactElement | null => {
const { t } = useAppTranslation('team');
const { activeTabId, modifiedFiles, saving, lineWrap } = useStore(
useShallow((s) => ({
activeTabId: s.editorActiveTabId,
modifiedFiles: s.editorModifiedFiles,
saving: s.editorSaving,
lineWrap: s.editorLineWrap,
}))
);
const saveFile = useStore((s) => s.saveFile);
const toggleLineWrap = useStore((s) => s.toggleLineWrap);
if (!activeTabId) return null;
const isDirty = !!modifiedFiles[activeTabId];
const isSaving = !!saving[activeTabId];
const handleSave = () => {
void saveFile(activeTabId);
};
const handleUndo = () => {
const view = editorBridge.getView();
if (view) undo(view);
};
const handleRedo = () => {
const view = editorBridge.getView();
if (view) redo(view);
};
return (
<div className="flex h-8 shrink-0 items-center gap-1 border-b border-border bg-surface px-2">
<ToolbarButton
icon={<Save className="size-3.5" />}
label={t('editor.shortcuts.actions.save')}
shortcut={shortcutLabel('⌘ S', 'Ctrl+S')}
onClick={handleSave}
disabled={!isDirty || isSaving}
/>
<ToolbarButton
icon={<Undo2 className="size-3.5" />}
label={t('editor.shortcuts.actions.undo')}
shortcut={shortcutLabel('⌘ Z', 'Ctrl+Z')}
onClick={handleUndo}
/>
<ToolbarButton
icon={<Redo2 className="size-3.5" />}
label={t('editor.shortcuts.actions.redo')}
shortcut={shortcutLabel('⌘ ⇧ Z', 'Ctrl+Y')}
onClick={handleRedo}
/>
<div className="mx-1 h-4 w-px bg-border" />
<ToolbarButton
icon={<WrapText className="size-3.5" />}
label={lineWrap ? t('editor.toolbar.disableWordWrap') : t('editor.toolbar.enableWordWrap')}
shortcut={shortcutLabel('⌘ ⇧ W', 'Ctrl+Shift+W')}
onClick={toggleLineWrap}
active={lineWrap}
/>
{isMarkdown && (
<>
<div className="mx-1 h-4 w-px bg-border" />
<ToolbarButton
icon={<Columns2 className="size-3.5" />}
label={
mdPreviewMode === 'split'
? t('editor.toolbar.closeSplitPreview')
: t('editor.shortcuts.actions.splitPreview')
}
shortcut={shortcutLabel('⌘ ⇧ M', 'Ctrl+Shift+M')}
onClick={onToggleSplit ?? (() => {})}
active={mdPreviewMode === 'split'}
/>
<ToolbarButton
icon={<Eye className="size-3.5" />}
label={
mdPreviewMode === 'preview'
? t('editor.toolbar.closePreview')
: t('editor.shortcuts.actions.fullPreview')
}
shortcut={shortcutLabel('⌘ ⇧ V', 'Ctrl+Shift+V')}
onClick={onToggleFullPreview ?? (() => {})}
active={mdPreviewMode === 'preview'}
/>
</>
)}
</div>
);
};
// =============================================================================
// Toolbar button
// =============================================================================
interface ToolbarButtonProps {
icon: React.ReactNode;
label: string;
shortcut: string;
onClick: () => void;
disabled?: boolean;
active?: boolean;
}
const ToolbarButton = React.memo(function ToolbarButton({
icon,
label,
shortcut,
onClick,
disabled = false,
active = false,
}: ToolbarButtonProps): React.ReactElement {
return (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
onClick={onClick}
disabled={disabled}
className={`h-auto gap-1 px-1.5 py-0.5 text-xs ${
active ? 'bg-surface-raised text-text' : 'text-text-muted'
}`}
aria-label={`${label} (${shortcut})`}
aria-pressed={active}
>
{icon}
</Button>
</TooltipTrigger>
<TooltipContent side="bottom">
{label} ({shortcut})
</TooltipContent>
</Tooltip>
);
});