refactor: extract Icons and InlineFormActions UI components
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m28s
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:
@@ -2,6 +2,7 @@
|
||||
|
||||
import { memo, useState, useTransition } from 'react';
|
||||
import { updateGifMoodItem, deleteGifMoodItem } from '@/actions/gif-mood';
|
||||
import { IconClose } from '@/components/ui';
|
||||
|
||||
interface GifMoodCardProps {
|
||||
sessionId: string;
|
||||
@@ -83,9 +84,7 @@ export const GifMoodCard = memo(function GifMoodCard({
|
||||
className="absolute top-2 right-2 p-1.5 rounded-full bg-black/50 text-white opacity-0 group-hover:opacity-100 hover:bg-black/70 transition-all backdrop-blur-sm"
|
||||
title="Supprimer ce GIF"
|
||||
>
|
||||
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
<IconClose className="w-3 h-3" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { forwardRef, memo, useState, useTransition } from 'react';
|
||||
import type { SwotItem, SwotCategory } from '@prisma/client';
|
||||
import { updateSwotItem, deleteSwotItem, duplicateSwotItem } from '@/actions/swot';
|
||||
import { IconEdit, IconTrash, IconDuplicate, IconCheck } from '@/components/ui';
|
||||
|
||||
interface SwotCardProps {
|
||||
item: SwotItem;
|
||||
@@ -121,63 +122,21 @@ export const SwotCard = memo(
|
||||
className="rounded p-1 text-muted hover:bg-card-hover hover:text-foreground"
|
||||
aria-label="Modifier"
|
||||
>
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
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 />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDuplicate();
|
||||
}}
|
||||
onClick={(e) => { e.stopPropagation(); handleDuplicate(); }}
|
||||
className="rounded p-1 text-muted hover:bg-primary/10 hover:text-primary"
|
||||
aria-label="Dupliquer"
|
||||
>
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
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>
|
||||
<IconDuplicate />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDelete();
|
||||
}}
|
||||
onClick={(e) => { e.stopPropagation(); handleDelete(); }}
|
||||
className="rounded p-1 text-muted hover:bg-destructive/10 hover:text-destructive"
|
||||
aria-label="Supprimer"
|
||||
>
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
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>
|
||||
<IconTrash />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -187,9 +146,7 @@ export const SwotCard = memo(
|
||||
<div
|
||||
className={`absolute -right-1 -top-1 rounded-full bg-card p-0.5 shadow ${styles.text}`}
|
||||
>
|
||||
<svg className="h-4 w-4" 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>
|
||||
<IconCheck />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { forwardRef, useState, useTransition, useRef, ReactNode } from 'react';
|
||||
import type { SwotCategory } from '@prisma/client';
|
||||
import { createSwotItem } from '@/actions/swot';
|
||||
import { QuadrantHelpPanel } from './QuadrantHelp';
|
||||
import { IconPlus, InlineFormActions } from '@/components/ui';
|
||||
|
||||
interface SwotQuadrantProps {
|
||||
category: SwotCategory;
|
||||
@@ -115,14 +116,7 @@ export const SwotQuadrant = forwardRef<HTMLDivElement, SwotQuadrantProps>(
|
||||
`}
|
||||
aria-label={`Ajouter un item ${title}`}
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
<IconPlus />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -152,28 +146,14 @@ export const SwotQuadrant = forwardRef<HTMLDivElement, SwotQuadrantProps>(
|
||||
rows={2}
|
||||
disabled={isPending}
|
||||
/>
|
||||
<div className="mt-1 flex justify-end gap-1">
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsAdding(false);
|
||||
setNewContent('');
|
||||
}}
|
||||
className="rounded px-2 py-1 text-xs text-muted hover:bg-card-hover"
|
||||
disabled={isPending}
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
<button
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault(); // Prevent blur from textarea
|
||||
}}
|
||||
onClick={handleAdd}
|
||||
disabled={isPending || !newContent.trim()}
|
||||
className={`rounded px-2 py-1 text-xs font-medium ${styles.text} hover:bg-white/50 disabled:opacity-50`}
|
||||
>
|
||||
{isPending ? '...' : 'Ajouter'}
|
||||
</button>
|
||||
</div>
|
||||
<InlineFormActions
|
||||
onCancel={() => { setIsAdding(false); setNewContent(''); }}
|
||||
onSubmit={handleAdd}
|
||||
isPending={isPending}
|
||||
disabled={!newContent.trim()}
|
||||
submitColorClass={`${styles.text} hover:bg-white/50`}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
54
src/components/ui/Icons.tsx
Normal file
54
src/components/ui/Icons.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
39
src/components/ui/InlineFormActions.tsx
Normal file
39
src/components/ui/InlineFormActions.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -4,6 +4,7 @@ import { forwardRef, memo, useState, useTransition } from 'react';
|
||||
import type { WeeklyCheckInItem } from '@prisma/client';
|
||||
import { updateWeeklyCheckInItem, deleteWeeklyCheckInItem } from '@/actions/weekly-checkin';
|
||||
import { WEEKLY_CHECK_IN_BY_CATEGORY, EMOTION_BY_TYPE } from '@/lib/types';
|
||||
import { IconEdit, IconTrash, InlineFormActions } from '@/components/ui';
|
||||
import { Select } from '@/components/ui/Select';
|
||||
|
||||
interface WeeklyCheckInCardProps {
|
||||
@@ -113,26 +114,13 @@ export const WeeklyCheckInCard = memo(
|
||||
label: `${em.icon} ${em.label}`,
|
||||
}))}
|
||||
/>
|
||||
<div className="flex justify-end gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setContent(item.content);
|
||||
setEmotion(item.emotion);
|
||||
setIsEditing(false);
|
||||
}}
|
||||
className="rounded px-2 py-1 text-xs text-muted hover:bg-card-hover"
|
||||
disabled={isPending}
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={isPending || !content.trim()}
|
||||
className="rounded px-2 py-1 text-xs font-medium text-primary hover:bg-primary/10 disabled:opacity-50"
|
||||
>
|
||||
{isPending ? '...' : 'Enregistrer'}
|
||||
</button>
|
||||
</div>
|
||||
<InlineFormActions
|
||||
onCancel={() => { setContent(item.content); setEmotion(item.emotion); setIsEditing(false); }}
|
||||
onSubmit={handleSave}
|
||||
isPending={isPending}
|
||||
disabled={!content.trim()}
|
||||
submitLabel="Enregistrer"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
@@ -164,41 +152,14 @@ export const WeeklyCheckInCard = memo(
|
||||
className="rounded p-1 text-muted hover:bg-card-hover hover:text-foreground"
|
||||
aria-label="Modifier"
|
||||
>
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
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 />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDelete();
|
||||
}}
|
||||
onClick={(e) => { e.stopPropagation(); handleDelete(); }}
|
||||
className="rounded p-1 text-muted hover:bg-destructive/10 hover:text-destructive"
|
||||
aria-label="Supprimer"
|
||||
>
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
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>
|
||||
<IconTrash />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { forwardRef, useState, useTransition, useRef, ReactNode } from 'react';
|
||||
import type { WeeklyCheckInCategory } from '@prisma/client';
|
||||
import { createWeeklyCheckInItem } from '@/actions/weekly-checkin';
|
||||
import { WEEKLY_CHECK_IN_BY_CATEGORY, EMOTION_BY_TYPE } from '@/lib/types';
|
||||
import { IconPlus, InlineFormActions } from '@/components/ui';
|
||||
import { Select } from '@/components/ui/Select';
|
||||
|
||||
interface WeeklyCheckInSectionProps {
|
||||
@@ -82,14 +83,7 @@ export const WeeklyCheckInSection = forwardRef<HTMLDivElement, WeeklyCheckInSect
|
||||
className="rounded-lg p-1.5 transition-colors hover:bg-card-hover text-muted hover:text-foreground"
|
||||
aria-label={`Ajouter un item ${config.title}`}
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
<IconPlus />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -138,29 +132,12 @@ export const WeeklyCheckInSection = forwardRef<HTMLDivElement, WeeklyCheckInSect
|
||||
label: `${em.icon} ${em.label}`,
|
||||
}))}
|
||||
/>
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsAdding(false);
|
||||
setNewContent('');
|
||||
setNewEmotion('NONE');
|
||||
}}
|
||||
className="rounded px-2 py-1 text-xs text-muted hover:bg-card-hover"
|
||||
disabled={isPending}
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
<button
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault(); // Prevent blur from textarea
|
||||
}}
|
||||
onClick={handleAdd}
|
||||
disabled={isPending || !newContent.trim()}
|
||||
className="rounded px-2 py-1 text-xs font-medium text-primary hover:bg-primary/10 disabled:opacity-50"
|
||||
>
|
||||
{isPending ? '...' : 'Ajouter'}
|
||||
</button>
|
||||
</div>
|
||||
<InlineFormActions
|
||||
onCancel={() => { setIsAdding(false); setNewContent(''); setNewEmotion('NONE'); }}
|
||||
onSubmit={handleAdd}
|
||||
isPending={isPending}
|
||||
disabled={!newContent.trim()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { forwardRef, memo, useState, useTransition } from 'react';
|
||||
import type { YearReviewItem } from '@prisma/client';
|
||||
import { updateYearReviewItem, deleteYearReviewItem } from '@/actions/year-review';
|
||||
import { YEAR_REVIEW_BY_CATEGORY } from '@/lib/types';
|
||||
import { IconEdit, IconTrash } from '@/components/ui';
|
||||
|
||||
interface YearReviewCardProps {
|
||||
item: YearReviewItem;
|
||||
@@ -95,41 +96,14 @@ export const YearReviewCard = memo(
|
||||
className="rounded p-1 text-muted hover:bg-card-hover hover:text-foreground"
|
||||
aria-label="Modifier"
|
||||
>
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
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 />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDelete();
|
||||
}}
|
||||
onClick={(e) => { e.stopPropagation(); handleDelete(); }}
|
||||
className="rounded p-1 text-muted hover:bg-destructive/10 hover:text-destructive"
|
||||
aria-label="Supprimer"
|
||||
>
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
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>
|
||||
<IconTrash />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { forwardRef, useState, useTransition, useRef, ReactNode } from 'react';
|
||||
import type { YearReviewCategory } from '@prisma/client';
|
||||
import { createYearReviewItem } from '@/actions/year-review';
|
||||
import { YEAR_REVIEW_BY_CATEGORY } from '@/lib/types';
|
||||
import { IconPlus, InlineFormActions } from '@/components/ui';
|
||||
|
||||
interface YearReviewSectionProps {
|
||||
category: YearReviewCategory;
|
||||
@@ -77,14 +78,7 @@ export const YearReviewSection = forwardRef<HTMLDivElement, YearReviewSectionPro
|
||||
className="rounded-lg p-1.5 transition-colors hover:bg-card-hover text-muted hover:text-foreground"
|
||||
aria-label={`Ajouter un item ${config.title}`}
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
<IconPlus />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -111,28 +105,13 @@ export const YearReviewSection = forwardRef<HTMLDivElement, YearReviewSectionPro
|
||||
rows={2}
|
||||
disabled={isPending}
|
||||
/>
|
||||
<div className="mt-1 flex justify-end gap-1">
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsAdding(false);
|
||||
setNewContent('');
|
||||
}}
|
||||
className="rounded px-2 py-1 text-xs text-muted hover:bg-card-hover"
|
||||
disabled={isPending}
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
<button
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault(); // Prevent blur from textarea
|
||||
}}
|
||||
onClick={handleAdd}
|
||||
disabled={isPending || !newContent.trim()}
|
||||
className="rounded px-2 py-1 text-xs font-medium text-primary hover:bg-primary/10 disabled:opacity-50"
|
||||
>
|
||||
{isPending ? '...' : 'Ajouter'}
|
||||
</button>
|
||||
</div>
|
||||
<InlineFormActions
|
||||
onCancel={() => { setIsAdding(false); setNewContent(''); }}
|
||||
onSubmit={handleAdd}
|
||||
isPending={isPending}
|
||||
disabled={!newContent.trim()}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user