diff --git a/docs/UI_DESIGN_SYSTEM.md b/docs/UI_DESIGN_SYSTEM.md
index 9ba1848..16988ed 100644
--- a/docs/UI_DESIGN_SYSTEM.md
+++ b/docs/UI_DESIGN_SYSTEM.md
@@ -38,25 +38,107 @@ when-to-read: prima di creare o modificare qualsiasi componente visivo
- **Hover inattivo:** `hover:border-editorial-accent/40 hover:text-editorial-accent`
- **Disabled:** `disabled:opacity-40 disabled:cursor-not-allowed`
- **Focus:** `focus:outline-none focus-visible:ring-2 focus-visible:ring-editorial-accent`
-- **Tooltip obbligatorio:** ogni pulsante icon-only deve avere `title` + `aria-label`
+- **Tooltip obbligatorio:** ogni pulsante icon-only usa `` — il tooltip è incluso automaticamente.
+- **Nessuna variante locale:** i componenti feature non devono reintrodurre button/dot/label custom. Usa sempre le primitive condivise.
---
-## Componenti
+## Primitive condivise (`src/components/ui/`)
-### Label di sezione
+### IconButton — pulsanti icon-only (OBBLIGATORIO)
-Ogni sezione ha intestazione icona + etichetta uppercase:
+Ogni controllo icon-only **deve** usare ``. Non usare `
);
});
const diffButtons = diffPairs.map((pair) => {
@@ -470,34 +447,36 @@ export function DocumentView({ onRetranslateChunk }: DocumentViewProps) {
const fromStage = enabledStages.find((s) => s.id === pair.fromId);
const DiffIcon = fromStage?.role === 'refine' ? Wand2 : fromStage?.role === 'format' ? FileText : Languages;
return (
- setDiffPairKey(pair.key)}
title={`${pair.fromName} → ${pair.toName}`}
disabled={!showDiffMode}
- active={isActive}
ariaPressed={isActive}
>
-
+
);
});
const stageActions = isEditorialMode ? (
{stageButtons}
- {
if (paneFocus !== 'translation') return;
setShowDiffMode(!showDiffMode);
}}
title={showDiffMode ? t('document.diffModeDisable') : t('document.diffModeEnable')}
- active={showDiffMode}
ariaPressed={showDiffMode}
disabled={paneFocus !== 'translation'}
>
-
+
{diffButtons}
) : null;
@@ -748,89 +727,3 @@ function InlineStatusBadge({
);
}
-function ChunkIconButton({
- onClick,
- children,
- title,
- disabled = false,
- active = false,
- activeClassName,
- ariaPressed,
-}: {
- onClick: () => void;
- children: React.ReactNode;
- title: string;
- disabled?: boolean;
- active?: boolean;
- activeClassName?: string;
- ariaPressed?: boolean;
-}) {
- return (
-
-
- {children}
-
-
- );
-}
-
-const COMPACT_STATUS_TONE = {
- completed:
- 'border-editorial-success/40 bg-editorial-success/12 text-editorial-success',
- processing:
- 'border-editorial-running/45 bg-editorial-running/12 text-editorial-running animate-pulse',
- error: 'border-editorial-accent/40 bg-editorial-accent/10 text-editorial-accent',
- retrying: 'border-editorial-running/45 bg-editorial-running/12 text-editorial-running animate-pulse',
- idle: 'border-editorial-border bg-editorial-bg text-editorial-muted',
-} as const;
-
-function CompactStatusIndicator({
- status,
- label,
- icon: Icon,
- size = 'md',
-}: {
- status: string;
- label?: string;
- icon?: LucideIcon;
- size?: 'sm' | 'md';
-}) {
- const tone =
- status === 'completed' || status === 'processing' || status === 'error' || status === 'retrying'
- ? status
- : 'idle';
- const sizeClass = size === 'sm' ? 'h-7 w-7' : 'h-9 w-9';
- const iconSize = size === 'sm' ? 13 : 16;
-
- return (
-
- {Icon ? (
-
- ) : (
-
- {label}
-
- )}
-
- );
-}
-
-function truncateChunk(text: string) {
- const normalized = text.replace(/\s+/g, ' ').trim();
- if (normalized.length <= 58) return normalized;
- return `${normalized.slice(0, 55)}...`;
-}
diff --git a/src/components/document/ImportPreviewDialog.tsx b/src/components/document/ImportPreviewDialog.tsx
index be2ecaa..4e0b750 100644
--- a/src/components/document/ImportPreviewDialog.tsx
+++ b/src/components/document/ImportPreviewDialog.tsx
@@ -31,6 +31,7 @@ import { LANGUAGES } from '../../constants';
import { getSelectableModelIds, MODEL_PROVIDER_ORDER } from '../../models/catalog';
import { useUiStore } from '../../stores/uiStore';
import type { ModelProvider } from '../../types';
+import { IconButton } from '../ui';
// ─── Types ───────────────────────────────────────────────────────────────────
@@ -800,49 +801,40 @@ export function ImportPreviewDialog({
{/* Auto-segment toggle — icon button */}
-
onUseChunkingChange(!useChunking)}
title={t('pipeline.autoSegment')}
- className={`rounded-full border p-2 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-editorial-accent ${
- useChunking
- ? 'border-editorial-accent bg-editorial-accent text-white'
- : 'border-editorial-border text-editorial-muted hover:border-editorial-accent/40 hover:text-editorial-accent'
- }`}
+ ariaPressed={useChunking}
>
-
+
{/* Heading-aware toggle — icon button (markdown only) */}
{markdownAware && (
-
useChunking && onHeadingAwareChange(!headingAware)}
title={t('pipeline.headingAware')}
disabled={!useChunking}
- className={`rounded-full border p-2 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-editorial-accent disabled:cursor-not-allowed disabled:opacity-30 ${
- headingAware && useChunking
- ? 'border-editorial-accent bg-editorial-accent text-white'
- : 'border-editorial-border text-editorial-muted hover:border-editorial-accent/40 hover:text-editorial-accent'
- }`}
+ ariaPressed={headingAware && useChunking}
>
-
+
)}
-
useChunking && onCarryTrailingShortBlocksChange(!carryTrailingShortBlocks)}
title={t('pipeline.trailingShortBlocks')}
disabled={!useChunking}
- className={`rounded-full border p-2 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-editorial-accent disabled:cursor-not-allowed disabled:opacity-30 ${
- carryTrailingShortBlocks && useChunking
- ? 'border-editorial-accent bg-editorial-accent text-white'
- : 'border-editorial-border text-editorial-muted hover:border-editorial-accent/40 hover:text-editorial-accent'
- }`}
+ ariaPressed={carryTrailingShortBlocks && useChunking}
>
-
+
{/* Separator — solo in stato normale */}
{useChunking && !hasManualEdits && (
@@ -851,19 +843,16 @@ export function ImportPreviewDialog({
{/* Preset inline — solo in stato normale */}
{useChunking && !hasManualEdits && CHUNK_PRESETS.map(({ words, titleKey, Icon }) => (
-
handleWordsPerChunkChange(words)}
title={t(titleKey)}
- className={`rounded-full border p-2 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-editorial-accent ${
- activePresetWords === words
- ? 'border-editorial-accent bg-editorial-accent text-white'
- : 'border-editorial-border text-editorial-muted hover:border-editorial-accent/40 hover:text-editorial-accent'
- }`}
+ ariaPressed={activePresetWords === words}
>
-
+
))}
{/* Con modifiche manuali: preset + ricalcola raggruppati a destra in warning */}
diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx
index ee56946..146ae70 100644
--- a/src/components/layout/Header.tsx
+++ b/src/components/layout/Header.tsx
@@ -341,7 +341,7 @@ export function Header({ onRunPipeline, onCancelPipeline }: HeaderProps = {}) {
title={sandboxLabel}
ariaLabel={sandboxLabel}
ariaPressed={viewMode === 'sandbox'}
- active={viewMode === 'sandbox'}
+ tone={viewMode === 'sandbox' ? 'accent' : 'default'}
>
diff --git a/src/components/layout/PipelineSidebar.tsx b/src/components/layout/PipelineSidebar.tsx
index 17110a3..40a7356 100644
--- a/src/components/layout/PipelineSidebar.tsx
+++ b/src/components/layout/PipelineSidebar.tsx
@@ -27,7 +27,7 @@ import { useProjectStore } from '../../stores/projectStore';
import { useUiStore } from '../../stores/uiStore';
import { estimatePipelineCost } from '../../utils/costEstimate';
import { CostBreakdownPanel } from '../pipeline/CostBadge';
-import { Tooltip } from '../ui';
+import { IconButton, Tooltip } from '../ui';
interface PipelineSidebarProps {
onRunPipeline: () => void;
@@ -385,54 +385,54 @@ export function PipelineSidebar({
{t('document.panelsTitle')}
-
setDocumentPaneFocus('both')}
title={t('document.focusBoth')}
- active={documentPaneFocus === 'both'}
ariaPressed={documentPaneFocus === 'both'}
>
-
-
+ setDocumentPaneFocus('source')}
title={t('document.focusSource')}
- active={documentPaneFocus === 'source'}
ariaPressed={documentPaneFocus === 'source'}
>
-
-
+ setDocumentPaneFocus('translation')}
title={t('document.focusTranslation')}
- active={documentPaneFocus === 'translation'}
ariaPressed={documentPaneFocus === 'translation'}
>
-
+
- setSyncScrollEnabled(!syncScrollEnabled)}
title={syncScrollEnabled ? t('document.scrollSyncDisable') : t('document.scrollSyncEnable')}
- active={syncScrollEnabled && !syncScrollDisabled}
disabled={syncScrollDisabled}
ariaPressed={syncScrollEnabled && !syncScrollDisabled}
>
{syncScrollEnabled && !syncScrollDisabled ? : }
-
-
+ setHighlightsEnabled(!highlightsEnabled)}
title={t('document.highlightsToggle')}
- active={highlightsEnabled}
ariaPressed={highlightsEnabled}
>
-
+
@@ -440,44 +440,6 @@ export function PipelineSidebar({
);
}
-function SidebarIconButton({
- children,
- active = false,
- disabled = false,
- compact = false,
- title,
- onClick,
- ariaPressed,
-}: {
- children: ReactNode;
- active?: boolean;
- disabled?: boolean;
- compact?: boolean;
- title: string;
- onClick: () => void;
- ariaPressed?: boolean;
-}) {
- return (
-
-
- {children}
-
-
- );
-}
function SectionLabel({
children,
diff --git a/src/components/library/PromptTemplatesTab.tsx b/src/components/library/PromptTemplatesTab.tsx
index feb56a5..9a6510d 100644
--- a/src/components/library/PromptTemplatesTab.tsx
+++ b/src/components/library/PromptTemplatesTab.tsx
@@ -62,7 +62,7 @@ export function PromptTemplatesTab() {
const contextBadgeClass = (context: 'stage' | 'audit' | 'persona') => {
if (context === 'audit') return 'bg-editorial-warning/20 text-editorial-warning';
- if (context === 'persona') return 'bg-purple-500/15 text-purple-400';
+ if (context === 'persona') return 'bg-editorial-textbox/60 text-editorial-muted';
return 'bg-editorial-accent/20 text-editorial-accent';
};
diff --git a/src/components/pipeline/PipelineConfig.tsx b/src/components/pipeline/PipelineConfig.tsx
index b76c2be..1774caa 100644
--- a/src/components/pipeline/PipelineConfig.tsx
+++ b/src/components/pipeline/PipelineConfig.tsx
@@ -20,6 +20,7 @@ import { useOperationLogStore } from '../../stores/operationLogStore';
import { ReasoningPicker } from '../models/ReasoningPicker';
import { PromptPreviewTab } from './PromptPreviewTab';
import { canRefineWithProvider, formatProviderModelLabel, useProviderKeyStatus } from '../../hooks/useProviderKeyStatus';
+import { IconButton } from '../ui';
export type ConfigSection = 'settings' | 'translation' | 'audit' | 'glossary' | 'preview';
@@ -814,12 +815,6 @@ export function PipelineConfig({
preview: t('pipeline.tabPreview'),
};
- const tabBtnCls = (active: boolean) =>
- `rounded-full border p-2.5 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-editorial-accent ${
- active
- ? 'border-editorial-accent bg-editorial-accent text-white'
- : 'border-editorial-border text-editorial-muted hover:border-editorial-accent/40 hover:text-editorial-accent'
- }`;
return (
@@ -846,8 +841,9 @@ export function PipelineConfig({
))}
-
setConfig((prev) => ({
...prev,
@@ -857,11 +853,9 @@ export function PipelineConfig({
}
disabled={!!config.persona}
title={t('pipeline.swapLanguages')}
- aria-label={t('pipeline.swapLanguages')}
- className="shrink-0 rounded-full border border-editorial-border p-2 text-editorial-muted transition-colors hover:border-editorial-accent/40 hover:text-editorial-accent focus:outline-none focus-visible:ring-2 focus-visible:ring-editorial-accent disabled:opacity-40"
>
-
+