"use client"; import { useState, type ReactNode } from "react"; import type { LucideIcon } from "lucide-react"; import { cn } from "@/lib/utils"; export interface TabItem { value: V; label: ReactNode; /** Compteur affiché à droite du libellé. */ count?: number; /** Pastille d'alerte (rouge) au lieu d'un compteur neutre. */ alert?: boolean; icon?: LucideIcon; } interface TabsProps { items: readonly TabItem[]; value: V; onChange: (value: V) => void; disabled?: boolean; className?: string; } /** * Onglets soulignés, identiques sur toutes les pages. L'appelant décide de la * synchronisation avec l'URL (cf. `useUrlTab`) : un onglet doit pouvoir être * partagé par lien et survivre au rechargement. */ export function Tabs({ items, value, onChange, disabled, className }: TabsProps) { return (
{items.map((t) => { const actif = t.value === value; const Icone = t.icon; return ( ); })}
); } /** * Segment de choix court (2 à 4 options), pour les bascules du type * « Fournisseur / Nomenclature » ou « Tous / Frouard / Houdemont ». */ export function Segmented({ items, value, onChange, disabled, className }: TabsProps) { return (
{items.map((t) => { const actif = t.value === value; return ( ); })}
); } /** * Onglet actif reflété dans l'URL (`?param=valeur`) sans navigation serveur : * le lien se partage et survit au rechargement, et changer d'onglet ne relance * aucune requête. `initial` vient de la page serveur (searchParams). */ export function useUrlTab(param: string, initial: V): [V, (value: V) => void] { const [value, setValue] = useState(initial); const change = (next: V) => { setValue(next); const url = new URL(window.location.href); url.searchParams.set(param, next); window.history.replaceState(window.history.state, "", url.toString()); }; return [value, change]; }