137 lines
6.3 KiB
TypeScript
137 lines
6.3 KiB
TypeScript
import { CustomButton } from './custom/CustomButton';
|
|
import React from 'react';
|
|
import type { ToolId, ToolSettings } from '../lib/tools';
|
|
import { STAMP_PRESETS } from '../lib/tools';
|
|
import { ColorSwatches, Slider } from './ui';
|
|
import {
|
|
SelectIcon, PanIcon, HighlightIcon, DrawIcon, CommentIcon, TextBoxIcon,
|
|
SignatureIcon, StampIcon, RedactIcon,
|
|
} from './icons';
|
|
|
|
interface ToolbarProps {
|
|
activeTool: ToolId;
|
|
settings: ToolSettings;
|
|
onSettingsChange: (patch: Partial<ToolSettings>) => void;
|
|
onOpenSignature: () => void;
|
|
hasSignature: boolean;
|
|
activeStamp: string | null;
|
|
onSelectStamp: (label: string, color: string) => void;
|
|
}
|
|
|
|
const TOOL_META: Record<ToolId, { label: string; icon: React.ReactNode }> = {
|
|
select: { label: 'Select', icon: <SelectIcon size={17} /> },
|
|
pan: { label: 'Pan', icon: <PanIcon size={17} /> },
|
|
highlight: { label: 'Highlight', icon: <HighlightIcon size={17} /> },
|
|
draw: { label: 'Draw', icon: <DrawIcon size={17} /> },
|
|
comment: { label: 'Comment', icon: <CommentIcon size={17} /> },
|
|
textbox: { label: 'Text box', icon: <TextBoxIcon size={17} /> },
|
|
signature: { label: 'Signature', icon: <SignatureIcon size={17} /> },
|
|
stamp: { label: 'Stamp', icon: <StampIcon size={17} /> },
|
|
redact: { label: 'Redact', icon: <RedactIcon size={17} /> },
|
|
};
|
|
|
|
const Hint: React.FC<{ children: React.ReactNode; tone?: 'normal' | 'warn' }> = ({ children, tone = 'normal' }) => (
|
|
<span className={`text-[12px] ${tone === 'warn' ? 'font-semibold text-[#dc2626]' : 'text-[#98a1ad]'}`}>{children}</span>
|
|
);
|
|
const Label: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
<span className="text-[11px] font-semibold uppercase tracking-wide text-[#98a1ad]">{children}</span>
|
|
);
|
|
const Divider = () => <div className="mx-1 h-5 w-px shrink-0 bg-[#ebedf0]" />;
|
|
|
|
export const Toolbar: React.FC<ToolbarProps> = ({
|
|
activeTool, settings, onSettingsChange, onOpenSignature, hasSignature, activeStamp, onSelectStamp,
|
|
}) => {
|
|
const meta = TOOL_META[activeTool];
|
|
const isRedact = activeTool === 'redact';
|
|
return (
|
|
<div
|
|
className="flex h-[48px] shrink-0 items-center gap-3 border-b border-[#ebedf0] bg-[#ffffff]"
|
|
style={{ paddingLeft: '16px', paddingRight: '16px' }}
|
|
>
|
|
{/* Active tool identity */}
|
|
<div className="flex shrink-0 items-center gap-2">
|
|
<span
|
|
className="flex h-7 w-7 items-center justify-center rounded-[8px]"
|
|
style={{
|
|
background: isRedact ? '#fdecec' : '#eef4ff',
|
|
color: isRedact ? '#dc2626' : '#2563eb',
|
|
}}
|
|
>
|
|
{meta.icon}
|
|
</span>
|
|
<span className="text-[13px] font-bold text-[#18212e]">{meta.label}</span>
|
|
</div>
|
|
<Divider />
|
|
|
|
{/* Per-tool controls (no overflow clip — would cut off the active-swatch ring) */}
|
|
<div className="flex min-w-0 flex-1 items-center gap-3">
|
|
{activeTool === 'select' && <Hint>Drag to select text — release to copy, or press <Kbd>Ctrl/⌘ + C</Kbd></Hint>}
|
|
{activeTool === 'pan' && <Hint>Drag anywhere to move the page.</Hint>}
|
|
|
|
{activeTool === 'highlight' && (
|
|
<>
|
|
<Label>Color</Label>
|
|
<ColorSwatches value={settings.highlightColor} onChange={(c) => onSettingsChange({ highlightColor: c })} />
|
|
<Divider />
|
|
<Slider label="Opacity" min={10} max={100} step={5} value={Math.round(settings.highlightOpacity * 100)} onChange={(v) => onSettingsChange({ highlightOpacity: v / 100 })} suffix="%" />
|
|
</>
|
|
)}
|
|
|
|
{activeTool === 'draw' && (
|
|
<>
|
|
<Label>Color</Label>
|
|
<ColorSwatches value={settings.inkColor} onChange={(c) => onSettingsChange({ inkColor: c })}
|
|
palette={['#2563eb', '#dc2626', '#16a34a', '#d97706', '#7c3aed', '#18212e', '#ec4899', '#0891b2']} />
|
|
<Divider />
|
|
<Slider label="Thickness" min={1} max={12} step={1} value={settings.inkThickness} onChange={(v) => onSettingsChange({ inkThickness: v })} suffix="px" />
|
|
</>
|
|
)}
|
|
|
|
{activeTool === 'comment' && <Hint>Click anywhere on the page to drop a sticky note.</Hint>}
|
|
|
|
{activeTool === 'textbox' && (
|
|
<>
|
|
<Label>Color</Label>
|
|
<ColorSwatches value={settings.textColor} onChange={(c) => onSettingsChange({ textColor: c })}
|
|
palette={['#18212e', '#dc2626', '#2563eb', '#16a34a', '#d97706', '#7c3aed', '#ffffff']} />
|
|
<Divider />
|
|
<Slider label="Size" min={8} max={48} step={1} value={settings.fontSize} onChange={(v) => onSettingsChange({ fontSize: v })} suffix="pt" />
|
|
<Hint>Click to place a text box.</Hint>
|
|
</>
|
|
)}
|
|
|
|
{activeTool === 'signature' && (
|
|
<>
|
|
<CustomButton variant={hasSignature ? 'outline' : 'primary'} size="sm" onClick={onOpenSignature}>
|
|
<SignatureIcon size={16} /> {hasSignature ? 'Change signature' : 'Create signature'}
|
|
</CustomButton>
|
|
{hasSignature ? <Hint>Click on the page to place it.</Hint> : <Hint>Draw, type, or upload a signature.</Hint>}
|
|
<span className="ml-auto shrink-0 rounded-full bg-[#f6f7f9] px-2 py-0.5 text-[10px] font-semibold text-[#98a1ad]">Visual signature — not certified</span>
|
|
</>
|
|
)}
|
|
|
|
{activeTool === 'stamp' && (
|
|
<>
|
|
<div className="flex items-center gap-2">
|
|
{STAMP_PRESETS.map((s) => (
|
|
<CustomButton variant="unstyled" key={s.label} onClick={() => onSelectStamp(s.label, s.color)}
|
|
className={`shrink-0 rounded-[6px] border px-2.5 py-1 text-[10.5px] font-bold tracking-wide transition-transform hover:scale-[1.04] ${activeStamp === s.label ? 'ring-2 ring-offset-1 ring-[#2563eb]' : ''}`}
|
|
style={{ color: s.color, borderColor: s.color, background: `color-mix(in srgb, ${s.color} 8%, white)` }}>
|
|
{s.label}
|
|
</CustomButton>
|
|
))}
|
|
</div>
|
|
<Hint>Pick a stamp, then click to place it.</Hint>
|
|
</>
|
|
)}
|
|
|
|
{activeTool === 'redact' && <Hint tone="warn">⚠ Drag a box to permanently remove content underneath.</Hint>}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const Kbd: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
<kbd className="rounded border border-[#ebedf0] bg-[#f6f7f9] px-1.5 py-0.5 text-[10px] font-semibold text-[#18212e]">{children}</kbd>
|
|
);
|