refactor: extract Icons and InlineFormActions UI components
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m28s

- Add Icons.tsx: IconEdit, IconTrash, IconDuplicate, IconPlus, IconCheck, IconClose
- Add InlineFormActions.tsx: unified Annuler/Ajouter-Enregistrer button pair
- Replace inline SVGs in SwotCard, YearReviewCard, WeeklyCheckInCard, SwotQuadrant,
  YearReviewSection, WeeklyCheckInSection, EditableTitle, Modal, GifMoodCard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 14:25:35 +01:00
parent 09a849279b
commit 9a43980412
12 changed files with 150 additions and 245 deletions

View File

@@ -1,6 +1,7 @@
'use client';
import { useState, useTransition, useRef, useEffect, useMemo } from 'react';
import { IconEdit } from './Icons';
interface EditableTitleProps {
sessionId: string;
@@ -92,19 +93,7 @@ export function EditableTitle({ sessionId, initialTitle, canEdit, onUpdate }: Ed
title="Cliquez pour modifier"
>
<h1 className="text-3xl font-bold text-foreground">{title}</h1>
<svg
className="h-5 w-5 text-muted opacity-0 transition-opacity group-hover:opacity-100"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"
/>
</svg>
<IconEdit className="h-5 w-5 text-muted opacity-0 transition-opacity group-hover:opacity-100" />
</button>
);
}

View File

@@ -0,0 +1,54 @@
interface IconProps {
className?: string;
}
const base = { fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor' } as const;
const path = { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2 } as const;
export function IconEdit({ className = 'h-3.5 w-3.5' }: IconProps) {
return (
<svg className={className} {...base}>
<path {...path} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
</svg>
);
}
export function IconTrash({ className = 'h-3.5 w-3.5' }: IconProps) {
return (
<svg className={className} {...base}>
<path {...path} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
);
}
export function IconDuplicate({ className = 'h-3.5 w-3.5' }: IconProps) {
return (
<svg className={className} {...base}>
<path {...path} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
);
}
export function IconPlus({ className = 'h-5 w-5' }: IconProps) {
return (
<svg className={className} {...base}>
<path {...path} d="M12 4v16m8-8H4" />
</svg>
);
}
export function IconCheck({ className = 'h-4 w-4' }: IconProps) {
return (
<svg className={className} fill="currentColor" viewBox="0 0 24 24">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
</svg>
);
}
export function IconClose({ className = 'h-5 w-5' }: IconProps) {
return (
<svg className={className} {...base}>
<path {...path} d="M6 18L18 6M6 6l12 12" />
</svg>
);
}

View File

@@ -0,0 +1,39 @@
interface InlineFormActionsProps {
onCancel: () => void;
onSubmit: () => void;
isPending: boolean;
disabled?: boolean;
submitLabel?: string;
submitColorClass?: string;
className?: string;
}
export function InlineFormActions({
onCancel,
onSubmit,
isPending,
disabled = false,
submitLabel = 'Ajouter',
submitColorClass = 'text-primary hover:bg-primary/10',
className = '',
}: InlineFormActionsProps) {
return (
<div className={`flex justify-end gap-1 ${className}`}>
<button
onClick={onCancel}
className="rounded px-2 py-1 text-xs text-muted hover:bg-card-hover"
disabled={isPending}
>
Annuler
</button>
<button
onMouseDown={(e) => e.preventDefault()}
onClick={onSubmit}
disabled={isPending || disabled}
className={`rounded px-2 py-1 text-xs font-medium disabled:opacity-50 ${submitColorClass}`}
>
{isPending ? '...' : submitLabel}
</button>
</div>
);
}

View File

@@ -1,6 +1,7 @@
'use client';
import { Fragment, ReactNode, useEffect, useSyncExternalStore } from 'react';
import { IconClose } from './Icons';
import { createPortal } from 'react-dom';
interface ModalProps {
@@ -84,14 +85,7 @@ export function Modal({ isOpen, onClose, title, children, size = 'md' }: ModalPr
className="rounded-lg p-1 text-muted hover:bg-card-hover hover:text-foreground transition-colors"
aria-label="Fermer"
>
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
<IconClose />
</button>
</div>
)}

View File

@@ -12,6 +12,8 @@ export {
EditableWeatherTitle,
EditableGifMoodTitle,
} from './EditableTitles';
export { IconEdit, IconTrash, IconDuplicate, IconPlus, IconCheck, IconClose } from './Icons';
export { InlineFormActions } from './InlineFormActions';
export { PageHeader } from './PageHeader';
export { SessionPageHeader } from './SessionPageHeader';
export { Input } from './Input';