Files
planflow/src/components/ui/Button.tsx
T
MichaelandClaude Opus 5 9356c6e3f4 Refondre le thème autour du registre et du chiffre
L'ancien thème était crème et vert forêt, corps à 14 px, libellés secondaires à
11 px. Deux défauts s'y cumulaient : ça se lisait mal huit heures par jour, et
le fond chaud décalait la perception des aplats — un jaune de poste virait au
vert, un rouge d'alerte s'éteignait.

Le nouveau part de ce que l'écran est vraiment : un registre qu'on lit en
chiffres. Papier légèrement bleuté, encre bleu-ardoise plus dense, et un seul
bleu d'acte réservé à trois emplois — le filet actif, les liens, le bouton qui
engage. Tout le reste est en gris et en état, ce qui rend les alertes visibles
parce qu'elles sont les seules choses colorées de la page.

Lisibilité d'abord. Le corps monte à 15 px, rien ne descend sous 12, et `ink-3`
passe de 0,545 à 0,50 de clarté : à l'ancienne valeur, les libellés secondaires
tombaient sous 4,5:1 sur ce papier.

Typographie : IBM Plex Sans, et Plex Mono pour les codes. Le choix vient du
contenu — cet écran affiche des identifiants, AB-300, HS-HS25, un matricule, un
département 2A. Plex a été dessiné pour la documentation technique : le 1 porte
un empattement, le l est courbé, le 0 se distingue du O, et ces trois détails
décident si un gestionnaire recopie le bon code. Le zéro barré est activé
partout. Les fichiers sont téléchargés à la compilation et servis par
l'application : `font-src 'self'` tient, et aucune requête ne part vers un tiers.

La structure est portée par un **filet de marge** de 2 px plutôt que par des
bordures et des aplats gris — la marge du cahier, appliquée à une interface. Il
marque l'onglet ouvert, l'en-tête de carte, la section active. Il ne décore
jamais : un filet dit une appartenance.

Le dimanche a le sien, doublé, et une colonne teintée. C'est la seule colonne du
planning qui porte une conséquence de droit — L3132-27 impose une rémunération
au moins double **et** un repos compensateur équivalent. La grille le montre
avant que la paie ne le découvre. Le marquage ne remplace aucune alerte : le
moteur de règles reste seul juge.

La barre d'application devient collante, et les boutons passent de 26 à 32 px de
haut : sur une grille de quatre-vingts lignes, remonter changer de semaine est
le geste le plus répété, et 26 px était sous le seuil confortable au pointeur.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 15:37:11 +02:00

56 lines
1.8 KiB
TypeScript

import type { ButtonHTMLAttributes, ReactNode } from 'react';
import { cx } from '@/lib/cx';
export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';
export type ButtonSize = 'sm' | 'md';
const VARIANTS: Record<ButtonVariant, string> = {
// Le primaire porte une ombre courte : c'est le seul élément de l'interface
// qui se soulève, et il n'y en a qu'un par écran.
primary:
'border-accent bg-accent text-accent-ink shadow-e1 hover:bg-accent-hover active:bg-accent-press active:shadow-none',
secondary:
'border-line-2 bg-surface text-ink-1 hover:border-line-3 hover:bg-surface-2 active:bg-surface-3',
ghost:
'border-transparent bg-transparent text-ink-2 hover:bg-surface-2 hover:text-ink-1',
danger:
'border-danger bg-danger text-danger-ink shadow-e1 hover:bg-danger-hover active:bg-danger-hover active:shadow-none',
};
/* Cibles plus hautes : 26 px était sous le seuil confortable au pointeur, et
nettement sous les 44 px recommandés au doigt sur la version installable. */
const SIZES: Record<ButtonSize, string> = {
sm: 'h-8 px-3 text-xs',
md: 'h-9 px-4 text-sm',
};
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant;
size?: ButtonSize;
children: ReactNode;
}
export function Button({
variant = 'secondary',
size = 'md',
className,
type = 'button',
...rest
}: ButtonProps) {
return (
<button
type={type}
className={cx(
'inline-flex shrink-0 items-center justify-center gap-1.5 rounded-2 border font-medium',
'cursor-pointer transition-colors duration-[var(--d-1)] ease-organic',
'disabled:cursor-not-allowed disabled:opacity-50',
VARIANTS[variant],
SIZES[size],
className,
)}
{...rest}
/>
);
}