Files
workshop-manager/src/app/design-system/page.tsx

526 lines
18 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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>
);
}