526 lines
18 KiB
TypeScript
526 lines
18 KiB
TypeScript
'use client';
|
||
|
||
import { useState } from 'react';
|
||
import {
|
||
Avatar,
|
||
Badge,
|
||
Button,
|
||
Card,
|
||
CardContent,
|
||
CardDescription,
|
||
CardFooter,
|
||
CardHeader,
|
||
CardTitle,
|
||
CollaboratorDisplay,
|
||
DateInput,
|
||
Disclosure,
|
||
DropdownMenu,
|
||
EditableGifMoodTitle,
|
||
EditableMotivatorTitle,
|
||
EditableSessionTitle,
|
||
EditableTitle,
|
||
EditableWeatherTitle,
|
||
EditableWeeklyCheckInTitle,
|
||
EditableYearReviewTitle,
|
||
InlineFormActions,
|
||
Input,
|
||
IconCheck,
|
||
IconClose,
|
||
IconDuplicate,
|
||
IconEdit,
|
||
IconButton,
|
||
IconPlus,
|
||
IconTrash,
|
||
Modal,
|
||
ModalFooter,
|
||
PageHeader,
|
||
ParticipantInput,
|
||
RocketIcon,
|
||
Select,
|
||
SegmentedControl,
|
||
SessionPageHeader,
|
||
Textarea,
|
||
ToggleGroup,
|
||
FormField,
|
||
NumberInput,
|
||
} from '@/components/ui';
|
||
|
||
const BUTTON_VARIANTS = [
|
||
'primary',
|
||
'secondary',
|
||
'outline',
|
||
'ghost',
|
||
'destructive',
|
||
'brand',
|
||
] as const;
|
||
const BUTTON_SIZES = ['sm', 'md', 'lg'] as const;
|
||
|
||
const BADGE_VARIANTS = [
|
||
'default',
|
||
'primary',
|
||
'strength',
|
||
'weakness',
|
||
'opportunity',
|
||
'threat',
|
||
'success',
|
||
'warning',
|
||
'destructive',
|
||
'accent',
|
||
] as const;
|
||
|
||
const SELECT_OPTIONS = [
|
||
{ value: 'editor', label: 'Editeur' },
|
||
{ value: 'viewer', label: 'Lecteur' },
|
||
{ value: 'admin', label: 'Admin' },
|
||
];
|
||
|
||
const SECTION_LINKS = [
|
||
{ id: 'buttons', label: 'Buttons' },
|
||
{ id: 'badges', label: 'Badges' },
|
||
{ id: 'icon-button', label: 'IconButton' },
|
||
{ id: 'form-inputs', label: 'Form Inputs' },
|
||
{ id: 'select-toggle', label: 'Select & Toggle' },
|
||
{ id: 'form-field', label: 'FormField / Date / Number' },
|
||
{ id: 'cards', label: 'Cards' },
|
||
{ id: 'avatars', label: 'Avatar & Collaborators' },
|
||
{ id: 'disclosure-dropdown', label: 'Disclosure & Dropdown' },
|
||
{ id: 'menu', label: 'Menu' },
|
||
{ id: 'editable-titles', label: 'Editable Titles' },
|
||
{ id: 'session-header', label: 'Session Header' },
|
||
{ id: 'participant-input', label: 'ParticipantInput' },
|
||
{ id: 'icons', label: 'Icons' },
|
||
{ id: 'modal', label: 'Modal' },
|
||
] as const;
|
||
|
||
export default function DesignSystemPage() {
|
||
const [modalOpen, setModalOpen] = useState(false);
|
||
const [toggleValue, setToggleValue] = useState<'cards' | 'table' | 'list'>('cards');
|
||
const [selectMd, setSelectMd] = useState('editor');
|
||
const [selectSm, setSelectSm] = useState('viewer');
|
||
const [selectXs, setSelectXs] = useState('admin');
|
||
const [selectLg, setSelectLg] = useState('editor');
|
||
const [menuCount, setMenuCount] = useState(0);
|
||
|
||
return (
|
||
<main className="mx-auto max-w-7xl px-4 py-8">
|
||
<PageHeader
|
||
emoji="🎨"
|
||
title="Design System"
|
||
subtitle="Guide visuel des composants UI et de leurs variantes"
|
||
actions={
|
||
<Button variant="brand" size="sm">
|
||
Action principale
|
||
</Button>
|
||
}
|
||
/>
|
||
|
||
<div className="grid items-start gap-8" style={{ gridTemplateColumns: '240px minmax(0, 1fr)' }}>
|
||
<aside>
|
||
<Card className="sticky top-20 p-4">
|
||
<p className="mb-3 text-sm font-medium text-foreground">Menu de la page</p>
|
||
<nav className="flex flex-col gap-1.5">
|
||
{SECTION_LINKS.map((section) => (
|
||
<a
|
||
key={section.id}
|
||
href={`#${section.id}`}
|
||
className="rounded-md px-2.5 py-1.5 text-sm text-muted transition-colors hover:bg-card-hover hover:text-foreground"
|
||
>
|
||
{section.label}
|
||
</a>
|
||
))}
|
||
</nav>
|
||
</Card>
|
||
</aside>
|
||
|
||
<div className="space-y-8">
|
||
<Card id="buttons" className="p-6">
|
||
<h2 className="mb-4 text-xl font-semibold text-foreground">Buttons</h2>
|
||
<div className="space-y-4">
|
||
{BUTTON_SIZES.map((size) => (
|
||
<div key={size} className="flex flex-wrap items-center gap-3">
|
||
<span className="w-12 text-xs uppercase tracking-wide text-muted">{size}</span>
|
||
{BUTTON_VARIANTS.map((variant) => (
|
||
<Button key={`${size}-${variant}`} variant={variant} size={size}>
|
||
{variant}
|
||
</Button>
|
||
))}
|
||
</div>
|
||
))}
|
||
<div className="flex flex-wrap items-center gap-3 border-t border-border pt-4">
|
||
<Button loading>Chargement</Button>
|
||
<Button disabled>Desactive</Button>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
<Card id="badges" className="p-6">
|
||
<h2 className="mb-4 text-xl font-semibold text-foreground">Badges</h2>
|
||
<div className="flex flex-wrap gap-2">
|
||
{BADGE_VARIANTS.map((variant) => (
|
||
<Badge key={variant} variant={variant}>
|
||
{variant}
|
||
</Badge>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
|
||
<Card id="icon-button" className="p-6">
|
||
<h2 className="mb-4 text-xl font-semibold text-foreground">IconButton</h2>
|
||
<div className="flex flex-wrap items-center gap-2">
|
||
<IconButton icon={<IconEdit />} label="Edit" />
|
||
<IconButton icon={<IconDuplicate />} label="Duplicate" variant="primary" />
|
||
<IconButton icon={<IconTrash />} label="Delete" variant="destructive" />
|
||
</div>
|
||
</Card>
|
||
|
||
<Card id="form-inputs" className="p-6">
|
||
<h2 className="mb-4 text-xl font-semibold text-foreground">Form Inputs</h2>
|
||
<div className="grid gap-4 md:grid-cols-2">
|
||
<Input label="Input standard" placeholder="Votre texte" />
|
||
<Input label="Input avec erreur" defaultValue="Valeur invalide" error="Champ invalide" />
|
||
<Textarea label="Textarea standard" placeholder="Votre description" rows={3} />
|
||
<Textarea
|
||
label="Textarea avec erreur"
|
||
defaultValue="Texte"
|
||
rows={3}
|
||
error="Description trop courte"
|
||
/>
|
||
</div>
|
||
</Card>
|
||
|
||
<Card id="select-toggle" className="p-6">
|
||
<h2 className="mb-4 text-xl font-semibold text-foreground">Select & ToggleGroup</h2>
|
||
<div className="grid gap-4 md:grid-cols-2">
|
||
<div className="space-y-3">
|
||
<Select
|
||
label="Select XS"
|
||
size="xs"
|
||
value={selectXs}
|
||
onChange={(e) => setSelectXs(e.target.value)}
|
||
options={SELECT_OPTIONS}
|
||
/>
|
||
<Select
|
||
label="Select SM"
|
||
size="sm"
|
||
value={selectSm}
|
||
onChange={(e) => setSelectSm(e.target.value)}
|
||
options={SELECT_OPTIONS}
|
||
/>
|
||
<Select
|
||
label="Select MD"
|
||
size="md"
|
||
value={selectMd}
|
||
onChange={(e) => setSelectMd(e.target.value)}
|
||
options={SELECT_OPTIONS}
|
||
/>
|
||
<Select
|
||
label="Select LG"
|
||
size="lg"
|
||
value={selectLg}
|
||
onChange={(e) => setSelectLg(e.target.value)}
|
||
options={SELECT_OPTIONS}
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-3">
|
||
<p className="text-sm font-medium text-foreground">Toggle group</p>
|
||
<ToggleGroup
|
||
value={toggleValue}
|
||
onChange={setToggleValue}
|
||
options={[
|
||
{ value: 'cards', label: 'Cards' },
|
||
{ value: 'table', label: 'Table' },
|
||
{ value: 'list', label: 'List' },
|
||
]}
|
||
/>
|
||
<p className="text-sm text-muted">Valeur active: {toggleValue}</p>
|
||
<p className="pt-2 text-sm font-medium text-foreground">Segmented control</p>
|
||
<SegmentedControl
|
||
value={toggleValue}
|
||
onChange={setToggleValue}
|
||
options={[
|
||
{ value: 'cards', label: 'Cards' },
|
||
{ value: 'table', label: 'Table' },
|
||
{ value: 'list', label: 'List' },
|
||
]}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
<Card id="form-field" className="p-6">
|
||
<h2 className="mb-4 text-xl font-semibold text-foreground">FormField / Date / Number</h2>
|
||
<div className="grid gap-4 md:grid-cols-2">
|
||
<FormField label="FormField">
|
||
<Input placeholder="Control custom" />
|
||
</FormField>
|
||
<DateInput label="DateInput" defaultValue="2026-03-03" />
|
||
<NumberInput label="NumberInput" defaultValue={42} />
|
||
</div>
|
||
</Card>
|
||
|
||
<Card id="cards" className="p-6">
|
||
<h2 className="mb-4 text-xl font-semibold text-foreground">Cards & Header blocks</h2>
|
||
<div className="grid gap-4 md:grid-cols-2">
|
||
<Card hover>
|
||
<CardHeader>
|
||
<CardTitle>Card title</CardTitle>
|
||
<CardDescription>Description secondaire</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<p className="text-sm text-muted">Contenu principal de la card.</p>
|
||
</CardContent>
|
||
<CardFooter className="justify-end">
|
||
<Button size="sm" variant="outline">
|
||
Annuler
|
||
</Button>
|
||
<Button size="sm">Valider</Button>
|
||
</CardFooter>
|
||
</Card>
|
||
|
||
<Card className="p-4">
|
||
<h3 className="mb-3 font-medium text-foreground">Inline actions</h3>
|
||
<Input placeholder="Exemple inline" className="mb-2" />
|
||
<InlineFormActions
|
||
onCancel={() => {}}
|
||
onSubmit={() => {}}
|
||
isPending={false}
|
||
submitLabel="Ajouter"
|
||
/>
|
||
</Card>
|
||
</div>
|
||
</Card>
|
||
|
||
<Card id="avatars" className="p-6">
|
||
<h2 className="mb-4 text-xl font-semibold text-foreground">Avatar & Collaborators</h2>
|
||
<div className="grid gap-4 md:grid-cols-2">
|
||
<div className="flex items-center gap-3">
|
||
<Avatar email="jane.doe@example.com" name="Jane Doe" size={40} />
|
||
<Avatar email="john.smith@example.com" name="John Smith" size={32} />
|
||
<Avatar email="team@example.com" size={24} />
|
||
</div>
|
||
|
||
<div className="space-y-3">
|
||
<CollaboratorDisplay
|
||
collaborator={{
|
||
raw: 'Jane Doe',
|
||
matchedUser: {
|
||
id: '1',
|
||
email: 'jane.doe@example.com',
|
||
name: 'Jane Doe',
|
||
},
|
||
}}
|
||
showEmail
|
||
/>
|
||
<CollaboratorDisplay
|
||
collaborator={{
|
||
raw: 'Intervenant externe',
|
||
matchedUser: null,
|
||
}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
<Card id="disclosure-dropdown" className="p-6">
|
||
<h2 className="mb-4 text-xl font-semibold text-foreground">Disclosure & Dropdown</h2>
|
||
<div className="space-y-4">
|
||
<Disclosure icon="ℹ️" title="Panneau pliable" subtitle="Composant Disclosure">
|
||
<p className="text-sm text-muted">Contenu du panneau.</p>
|
||
</Disclosure>
|
||
<DropdownMenu
|
||
panelClassName="mt-2 w-56 rounded-lg border border-border bg-card p-2 shadow-lg"
|
||
trigger={({ open, toggle }) => (
|
||
<Button type="button" variant="outline" onClick={toggle}>
|
||
Menu demo {open ? '▲' : '▼'}
|
||
</Button>
|
||
)}
|
||
>
|
||
{({ close }) => (
|
||
<Button
|
||
size="sm"
|
||
variant="secondary"
|
||
onClick={() => {
|
||
setMenuCount((prev) => prev + 1);
|
||
close();
|
||
}}
|
||
>
|
||
Incrementer ({menuCount})
|
||
</Button>
|
||
)}
|
||
</DropdownMenu>
|
||
</div>
|
||
</Card>
|
||
|
||
<Card id="menu" className="p-6">
|
||
<h2 className="mb-4 text-xl font-semibold text-foreground">Menu</h2>
|
||
<DropdownMenu
|
||
panelClassName="mt-2 w-64 overflow-hidden rounded-lg border border-border bg-card py-1 shadow-lg"
|
||
trigger={({ open, toggle }) => (
|
||
<Button type="button" variant="outline" onClick={toggle}>
|
||
Ouvrir le menu {open ? '▲' : '▼'}
|
||
</Button>
|
||
)}
|
||
>
|
||
{({ close }) => (
|
||
<>
|
||
<div className="border-b border-border px-4 py-2">
|
||
<p className="text-xs text-muted">MENU DE DEMO</p>
|
||
<p className="text-sm font-medium text-foreground">Navigation rapide</p>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={close}
|
||
className="block w-full px-4 py-2 text-left text-sm text-foreground hover:bg-card-hover"
|
||
>
|
||
👤 Mon profil
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={close}
|
||
className="block w-full px-4 py-2 text-left text-sm text-foreground hover:bg-card-hover"
|
||
>
|
||
👥 Equipes
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={close}
|
||
className="block w-full px-4 py-2 text-left text-sm text-destructive hover:bg-card-hover"
|
||
>
|
||
Se deconnecter
|
||
</button>
|
||
</>
|
||
)}
|
||
</DropdownMenu>
|
||
</Card>
|
||
|
||
<Card id="editable-titles" className="p-6">
|
||
<h2 className="mb-4 text-xl font-semibold text-foreground">Editable Titles</h2>
|
||
<div className="space-y-6">
|
||
<div>
|
||
<p className="mb-2 text-sm font-medium text-foreground">EditableTitle (base)</p>
|
||
<EditableTitle
|
||
sessionId="demo-editable-title"
|
||
initialTitle="Titre modifiable (cliquez pour tester)"
|
||
canEdit
|
||
onUpdate={async () => ({ success: true })}
|
||
/>
|
||
</div>
|
||
<div className="grid gap-4 md:grid-cols-2">
|
||
<EditableSessionTitle
|
||
sessionId="demo-session-title"
|
||
initialTitle="Session title wrapper"
|
||
canEdit={false}
|
||
/>
|
||
<EditableMotivatorTitle
|
||
sessionId="demo-motivator-title"
|
||
initialTitle="Motivator title wrapper"
|
||
canEdit={false}
|
||
/>
|
||
<EditableYearReviewTitle
|
||
sessionId="demo-year-review-title"
|
||
initialTitle="Year review title wrapper"
|
||
canEdit={false}
|
||
/>
|
||
<EditableWeatherTitle
|
||
sessionId="demo-weather-title"
|
||
initialTitle="Weather title wrapper"
|
||
canEdit={false}
|
||
/>
|
||
<EditableWeeklyCheckInTitle
|
||
sessionId="demo-weekly-checkin-title"
|
||
initialTitle="Weekly check-in title wrapper"
|
||
canEdit={false}
|
||
/>
|
||
<EditableGifMoodTitle
|
||
sessionId="demo-gif-mood-title"
|
||
initialTitle="Gif mood title wrapper"
|
||
canEdit={false}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
<Card id="session-header" className="p-6">
|
||
<h2 className="mb-4 text-xl font-semibold text-foreground">Session Header</h2>
|
||
<SessionPageHeader
|
||
workshopType="swot"
|
||
sessionId="demo-session"
|
||
sessionTitle="Atelier de demonstration"
|
||
isOwner={true}
|
||
canEdit={false}
|
||
ownerUser={{ name: 'Jane Doe', email: 'jane.doe@example.com' }}
|
||
date={new Date()}
|
||
collaborator={{
|
||
raw: 'Jane Doe',
|
||
matchedUser: {
|
||
id: '1',
|
||
email: 'jane.doe@example.com',
|
||
name: 'Jane Doe',
|
||
},
|
||
}}
|
||
badges={<Badge variant="primary">DEMO</Badge>}
|
||
/>
|
||
</Card>
|
||
|
||
<Card id="participant-input" className="p-6">
|
||
<h2 className="mb-4 text-xl font-semibold text-foreground">ParticipantInput</h2>
|
||
<ParticipantInput name="participant" />
|
||
</Card>
|
||
|
||
<Card id="icons" className="p-6">
|
||
<h2 className="mb-4 text-xl font-semibold text-foreground">Icons</h2>
|
||
<div className="flex flex-wrap items-center gap-4 text-foreground">
|
||
<div className="flex items-center gap-2">
|
||
<IconEdit />
|
||
<span className="text-sm text-muted">Edit</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<IconTrash />
|
||
<span className="text-sm text-muted">Trash</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<IconDuplicate />
|
||
<span className="text-sm text-muted">Duplicate</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<IconPlus />
|
||
<span className="text-sm text-muted">Plus</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<IconCheck />
|
||
<span className="text-sm text-muted">Check</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<IconClose />
|
||
<span className="text-sm text-muted">Close</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<RocketIcon className="h-5 w-5" />
|
||
<span className="text-sm text-muted">Rocket</span>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
<Card id="modal" className="p-6">
|
||
<h2 className="mb-4 text-xl font-semibold text-foreground">Modal</h2>
|
||
<Button onClick={() => setModalOpen(true)}>Ouvrir la popup</Button>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
|
||
<Modal isOpen={modalOpen} onClose={() => setModalOpen(false)} title="Exemple de popup" size="md">
|
||
<p className="text-sm text-muted">
|
||
Ceci est un exemple de modal avec ses actions standardisees.
|
||
</p>
|
||
<ModalFooter>
|
||
<Button variant="outline" onClick={() => setModalOpen(false)}>
|
||
Annuler
|
||
</Button>
|
||
<Button onClick={() => setModalOpen(false)}>Confirmer</Button>
|
||
</ModalFooter>
|
||
</Modal>
|
||
</main>
|
||
);
|
||
}
|