@hgdc/ui 0.10.2 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,7 +41,7 @@ Primitives déjà là : `avatar` `badge` `button` `dialog` `drawer` `dropdown-me
41
41
 
42
42
  | Import | Exemple |
43
43
  | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
44
- | `app-shell` | `<AppShell product="mail" organization={<OrgSwitcher …/>} nav={<SidebarNav label="Projet">…</SidebarNav>} sidebarFooter={<AccountMenu …/>}>` |
44
+ | `app-shell` | `<AppShell product="mail" organization={<OrgSwitcher …/>} nav={<SidebarNav label="Projet">…</SidebarNav>} sidebarFooter={<AccountMenu …/>} onSearch={() => setOpen(true)}>` |
45
45
  | | `<SidebarNavItem icon={Rocket01Icon} label="Déploiements" render={<Link to="/deployments" />} />` |
46
46
  | `org-switcher` | `<OrgSwitcher organizations={orgs} activeId={org.id} onSelect={go} createHref="/organisations/nouvelle" />` |
47
47
  | `account-menu` | `<AccountMenu user={user} onProfile={…} onSecurity={…} onSignOut={signOut} product="Mail" />` : Account et les produits ouverts du catalogue, Mail coché |
@@ -49,9 +49,22 @@ Primitives déjà là : `avatar` `badge` `button` `dialog` `drawer` `dropdown-me
49
49
  | `products` | `HGDC_PRODUCTS` (mot, nom, sous-domaine, phrase, pastille `badge`, icône, état, `entryHref`), `HGDC_ACCOUNT`, `productMenuEntries({ current: "Mail" })` : chaque produit du menu pointe vers son entrée `/auth/hgdc` (ADR 0010) |
50
50
  | `kbd` | `<KbdGroup><Kbd>Ctrl</Kbd><Kbd>K</Kbd></KbdGroup>` |
51
51
  | `page-header` | `<PageHeader title="Domaines" breadcrumbs={[{ label: "Acme", render: <Link to="/o/acme" /> }, { label: "Domaines" }]} actions={<Button>Ajouter</Button>} />` |
52
+ | `page-header` | `<Page title="Réglages">…</Page>` (colonne de lecture de 768 px), `<Page title="Projets" wide actions={…}>…</Page>` (tableaux, listes, tableaux de bord) |
53
+ | `member-item` | `<ResourceList><MemberItem name={m.name} email={m.email} orgRole={m.role} isMe={m.isMe} /></ResourceList>` : avatar à initiales, « (toi) », `RoleBadge` |
54
+ | `initials` | `initials("Test vérif")` donne « TV » : la même règle pour une personne et une organisation (`OrgMark`, `AccountMenu`, `MemberItem`) |
52
55
 
53
56
  Le menu « Produits » et la palette (`badge` d'une commande) montrent la pastille de chaque produit.
54
57
 
58
+ ### Mise en page (ADR 0012)
59
+
60
+ Une seule règle dans les sept produits, posée par la coque :
61
+
62
+ - **Barre du haut** : sur téléphone, menu puis marque du produit (pastille et mot), toujours. Puis le contexte du produit (`header`, à gauche : sélecteur de projet). La recherche passe par `onSearch` : toujours à droite, 384 px sur ordinateur, la loupe seule sous `lg`. Ne pas poser `CommandMenuButton` dans `header`.
63
+ - **Contenu** : la même boîte partout (`max-w-6xl`, marges `px-4`, `sm:px-6`, `lg:px-10`), donc le titre au même endroit. Pas de `mainClassName` (déprécié).
64
+ - **Largeur d'une page** : `Page`, alignée à gauche. Par défaut une colonne de lecture de 768 px (formulaires, réglages, fiches, membres) ; `wide` pour les tableaux, listes à colonnes, tableaux de bord et grilles de cartes.
65
+ - **États vides** : `<Empty variant="card">` pour une page vide, icône monochrome (`EmptyMedia variant="icon"`), sans teinte de couleur.
66
+ - **Rôles et initiales** : `RoleBadge` (neutre), `MemberItem`, `initials`. Jamais une fonction d'initiales ou un badge de rôle dans un produit.
67
+
55
68
  ## Identité H family
56
69
 
57
70
  | Import | Rôle |
@@ -12,19 +12,12 @@ import { HugeiconsIcon } from "@hugeicons/react";
12
12
  import { Menu as MenuPrimitive } from "@base-ui/react/menu";
13
13
  import { Avatar, AvatarFallback, AvatarImage } from "./avatar.js";
14
14
  import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "./dropdown-menu.js";
15
+ import { initials } from "./initials.js";
15
16
  import { MENU_LINK_ITEM } from "./org-switcher.js";
16
17
  import { ProductBadge } from "./product-badge.js";
17
18
  import { productMenuEntries } from "./products.js";
18
19
  import { useTheme } from "./theme.js";
19
20
  const THEMES = ["light", "dark", "system"];
20
- function initials(name) {
21
- return name
22
- .split(/\s+/)
23
- .filter(Boolean)
24
- .slice(0, 2)
25
- .map((part) => part[0])
26
- .join("");
27
- }
28
21
  /**
29
22
  * Le menu du compte, en bas de la barre latérale : profil, sécurité, thème, raccourcis, autres
30
23
  * produits HGDC, déconnexion. `row` : avatar, nom et adresse (l'avatar seul en rail) ; `avatar` :
@@ -38,7 +31,7 @@ function initials(name) {
38
31
  export function AccountMenu({ user, onProfile, onSecurity, onShortcuts, onSignOut, products: productsProp, product: currentProduct, accountHref, children, variant = "row", }) {
39
32
  const { theme, setTheme } = useTheme();
40
33
  const products = productsProp ?? productMenuEntries({ current: currentProduct, accountHref });
41
- return (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { render: variant === "row" ? (_jsx("button", { type: "button", "aria-label": `Compte de ${user.name}`, className: "flex w-full min-w-0 items-center gap-2.5 rounded-xl p-1.5 text-left outline-none transition-colors duration-(--duration-fast) ease-out hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring/50 aria-expanded:bg-muted lg:hover:bg-shell-hover lg:aria-expanded:bg-shell-hover lg:rail:justify-center" })) : (_jsx("button", { type: "button", "aria-label": `Compte de ${user.name}`, className: "touch-target grid size-9 place-items-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring/50" })), children: [_jsxs(Avatar, { children: [user.image ? _jsx(AvatarImage, { src: user.image, alt: "" }) : null, _jsx(AvatarFallback, { className: "text-xs font-semibold uppercase", children: initials(user.name) })] }), variant === "row" ? (_jsxs("span", { className: "min-w-0 flex-1 lg:rail:hidden", children: [_jsx("span", { className: "block truncate text-sm font-medium", children: user.name }), _jsx("span", { className: "block truncate text-xs text-muted-foreground lg:text-shell-muted", children: user.email })] })) : null] }), _jsxs(DropdownMenuContent, { align: variant === "row" ? "start" : "end", side: variant === "row" ? "top" : "bottom", className: "w-64", children: [_jsx(DropdownMenuGroup, { children: _jsxs(DropdownMenuLabel, { children: [_jsx("span", { className: "block truncate text-foreground", children: user.name }), _jsx("span", { className: "block truncate", children: user.email })] }) }), _jsx(DropdownMenuSeparator, {}), onProfile ? (_jsxs(DropdownMenuItem, { onClick: onProfile, children: [_jsx(HugeiconsIcon, { icon: UserIcon, "aria-hidden": true }), "Profil"] })) : null, onSecurity ? (_jsxs(DropdownMenuItem, { onClick: onSecurity, children: [_jsx(HugeiconsIcon, { icon: SecurityLockIcon, "aria-hidden": true }), "S\u00E9curit\u00E9"] })) : null, children, _jsxs(DropdownMenuSub, { children: [_jsxs(DropdownMenuSubTrigger, { children: [_jsx(HugeiconsIcon, { icon: theme === "dark" ? Moon02Icon : theme === "light" ? Sun03Icon : ComputerIcon, "aria-hidden": true }), "Th\u00E8me"] }), _jsx(DropdownMenuSubContent, { children: _jsxs(DropdownMenuRadioGroup, { value: theme, onValueChange: (value) => {
34
+ return (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { render: variant === "row" ? (_jsx("button", { type: "button", "aria-label": `Compte de ${user.name}`, className: "flex w-full min-w-0 items-center gap-2.5 rounded-xl p-1.5 text-left outline-none transition-colors duration-(--duration-fast) ease-out hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring/50 aria-expanded:bg-muted lg:hover:bg-shell-hover lg:aria-expanded:bg-shell-hover lg:rail:justify-center" })) : (_jsx("button", { type: "button", "aria-label": `Compte de ${user.name}`, className: "touch-target grid size-9 place-items-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring/50" })), children: [_jsxs(Avatar, { children: [user.image ? _jsx(AvatarImage, { src: user.image, alt: "" }) : null, _jsx(AvatarFallback, { className: "text-xs font-semibold uppercase", children: initials(user.name, user.email) })] }), variant === "row" ? (_jsxs("span", { className: "min-w-0 flex-1 lg:rail:hidden", children: [_jsx("span", { className: "block truncate text-sm font-medium", children: user.name }), _jsx("span", { className: "block truncate text-xs text-muted-foreground lg:text-shell-muted", children: user.email })] })) : null] }), _jsxs(DropdownMenuContent, { align: variant === "row" ? "start" : "end", side: variant === "row" ? "top" : "bottom", className: "w-64", children: [_jsx(DropdownMenuGroup, { children: _jsxs(DropdownMenuLabel, { children: [_jsx("span", { className: "block truncate text-foreground", children: user.name }), _jsx("span", { className: "block truncate", children: user.email })] }) }), _jsx(DropdownMenuSeparator, {}), onProfile ? (_jsxs(DropdownMenuItem, { onClick: onProfile, children: [_jsx(HugeiconsIcon, { icon: UserIcon, "aria-hidden": true }), "Profil"] })) : null, onSecurity ? (_jsxs(DropdownMenuItem, { onClick: onSecurity, children: [_jsx(HugeiconsIcon, { icon: SecurityLockIcon, "aria-hidden": true }), "S\u00E9curit\u00E9"] })) : null, children, _jsxs(DropdownMenuSub, { children: [_jsxs(DropdownMenuSubTrigger, { children: [_jsx(HugeiconsIcon, { icon: theme === "dark" ? Moon02Icon : theme === "light" ? Sun03Icon : ComputerIcon, "aria-hidden": true }), "Th\u00E8me"] }), _jsx(DropdownMenuSubContent, { children: _jsxs(DropdownMenuRadioGroup, { value: theme, onValueChange: (value) => {
42
35
  const next = THEMES.find((t) => t === value);
43
36
  if (next)
44
37
  setTheme(next);
@@ -44,7 +44,7 @@ export declare function SidebarNavItem({ icon, label, active, href, render, }: {
44
44
  * Le kit ne connaît ni le routeur ni l'authentification : le produit passe ses éléments
45
45
  * (`OrgSwitcher`, `SidebarNav`, `AccountMenu`) et ses liens.
46
46
  */
47
- export declare function AppShell({ product, organization, productRail, accountHref, nav, sidebarFooter, header, children, mainClassName, }: {
47
+ export declare function AppShell({ product, organization, productRail, accountHref, nav, sidebarFooter, header, onSearch, searchLabel, children, mainClassName, }: {
48
48
  /** Le produit affiché (`"host"`) : sa marque en haut de la barre, « vous êtes ici » dans la barre des produits. */
49
49
  product: FamilyProduct;
50
50
  /** `OrgSwitcher` : en haut de la barre des produits, en haut du tiroir sur téléphone. */
@@ -60,9 +60,22 @@ export declare function AppShell({ product, organization, productRail, accountHr
60
60
  nav: ReactNode;
61
61
  /** Bas de la barre : liens secondaires (Docs, Paramètres) et `AccountMenu`. */
62
62
  sidebarFooter?: ReactNode;
63
- /** Barre du haut de la page : sélecteur de projet, recherche (`CommandMenuButton`). */
63
+ /**
64
+ * Barre du haut, à gauche, après la marque sur téléphone : le contexte de la page (sélecteur de
65
+ * projet, rappel). Jamais la recherche : elle passe par `onSearch`.
66
+ */
64
67
  header?: ReactNode;
68
+ /**
69
+ * Ouvre la palette (`CommandMenu`). La coque pose la recherche toujours au même endroit : à
70
+ * droite de la barre du haut, 384 px sur ordinateur, la loupe seule sur téléphone.
71
+ */
72
+ onSearch?: () => void;
73
+ /** Texte du champ de recherche : « Rechercher » par défaut. */
74
+ searchLabel?: string;
65
75
  children: ReactNode;
66
- /** Largeur et marges du contenu : `max-w-6xl` par défaut. */
76
+ /**
77
+ * @deprecated La largeur du contenu est la même dans toute la famille (`max-w-6xl`, ADR 0012) :
78
+ * une page étroite passe par `Page` (`@hgdc/ui/page-header`), pas par la coque.
79
+ */
67
80
  mainClassName?: string;
68
81
  }): React.JSX.Element;
package/dist/app-shell.js CHANGED
@@ -5,6 +5,7 @@ import PanelLeftOpenIcon from "@hugeicons/core-free-icons/PanelLeftOpenIcon";
5
5
  import { HugeiconsIcon } from "@hugeicons/react";
6
6
  import { useRender } from "@base-ui/react/use-render";
7
7
  import { createContext, useContext, useEffect, useState, useSyncExternalStore } from "react";
8
+ import { CommandMenuButton } from "./command-menu.js";
8
9
  import { Drawer, DrawerContent, DrawerTitle } from "./drawer.js";
9
10
  import { isApplePlatform, Kbd, KbdGroup } from "./kbd.js";
10
11
  import { ProductBadge } from "./product-badge.js";
@@ -128,8 +129,8 @@ function SidebarHead({ product }) {
128
129
  * Le kit ne connaît ni le routeur ni l'authentification : le produit passe ses éléments
129
130
  * (`OrgSwitcher`, `SidebarNav`, `AccountMenu`) et ses liens.
130
131
  */
131
- export function AppShell({ product, organization, productRail = true, accountHref, nav, sidebarFooter, header, children, mainClassName, }) {
132
+ export function AppShell({ product, organization, productRail = true, accountHref, nav, sidebarFooter, header, onSearch, searchLabel, children, mainClassName, }) {
132
133
  const [menuOpen, setMenuOpen] = useState(false);
133
134
  useToggleShortcut();
134
- return (_jsx(ShellContext.Provider, { value: { closeMobileNav: () => setMenuOpen(false) }, children: _jsxs("div", { "data-slot": "app-shell", className: cn("min-h-dvh lg:grid lg:h-dvh lg:bg-shell", productRail ? "lg:grid-cols-[auto_auto_minmax(0,1fr)]" : "lg:grid-cols-[auto_minmax(0,1fr)]"), children: [_jsx("a", { href: "#contenu", className: "sr-only z-50 rounded-xl bg-background px-4 py-2 text-sm font-medium focus:not-sr-only focus:fixed focus:top-3 focus:left-3 focus:ring-2 focus:ring-ring", children: "Aller au contenu" }), productRail ? _jsx(ProductRail, { current: product, organization: organization, accountHref: accountHref }) : null, _jsxs("aside", { "aria-label": "Barre lat\u00E9rale", className: "group/sidebar hidden overflow-hidden text-shell-foreground transition-[width] duration-(--duration-drawer) ease-ios motion-reduce:transition-none lg:flex lg:w-64 lg:flex-col lg:rail:w-16", children: [_jsx(SidebarHead, { product: product }), !productRail && organization ? _jsx("div", { className: "shrink-0 px-3 pb-1", children: organization }) : null, nav, sidebarFooter ? _jsx("div", { className: "grid gap-0.5 px-3 pt-1 pb-3", children: sidebarFooter }) : null] }), _jsx("div", { className: "flex min-h-dvh flex-col lg:min-h-0 lg:p-2 lg:pl-0", children: _jsxs("div", { className: "flex flex-1 flex-col lg:min-h-0 lg:overflow-y-auto lg:rounded-xl lg:bg-background", children: [_jsxs("header", { className: "sticky top-[env(safe-area-inset-top,0px)] z-30 flex h-14 shrink-0 items-center gap-1 border-b border-foreground/5 bg-background px-2 sm:px-4 lg:top-0", children: [_jsx("button", { type: "button", onClick: () => setMenuOpen(true), "aria-label": "Ouvrir le menu", className: "touch-target grid size-10 shrink-0 place-items-center rounded-xl text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50 lg:hidden", children: _jsx(HugeiconsIcon, { icon: Menu01Icon, "aria-hidden": true, className: "size-5" }) }), _jsx("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: header })] }), _jsx("main", { id: "contenu", tabIndex: -1, className: cn("mx-auto w-full max-w-6xl flex-1 px-4 pt-6 pb-16 outline-none sm:px-6 lg:px-10 lg:pt-8", mainClassName), children: children })] }) }), _jsx(Drawer, { side: "bottom", open: menuOpen, onOpenChange: setMenuOpen, children: _jsxs(DrawerContent, { className: "lg:hidden", children: [_jsx(DrawerTitle, { className: "sr-only", children: "Menu" }), organization ? _jsx("div", { className: "px-3 pt-1", children: organization }) : null, nav, sidebarFooter ? (_jsx("div", { className: "grid gap-0.5 border-t border-foreground/5 px-3 pt-2 pb-[max(env(safe-area-inset-bottom),0.75rem)]", children: sidebarFooter })) : null] }) })] }) }));
135
+ return (_jsx(ShellContext.Provider, { value: { closeMobileNav: () => setMenuOpen(false) }, children: _jsxs("div", { "data-slot": "app-shell", className: cn("min-h-dvh lg:grid lg:h-dvh lg:bg-shell", productRail ? "lg:grid-cols-[auto_auto_minmax(0,1fr)]" : "lg:grid-cols-[auto_minmax(0,1fr)]"), children: [_jsx("a", { href: "#contenu", className: "sr-only z-50 rounded-xl bg-background px-4 py-2 text-sm font-medium focus:not-sr-only focus:fixed focus:top-3 focus:left-3 focus:ring-2 focus:ring-ring", children: "Aller au contenu" }), productRail ? _jsx(ProductRail, { current: product, organization: organization, accountHref: accountHref }) : null, _jsxs("aside", { "aria-label": "Barre lat\u00E9rale", className: "group/sidebar hidden overflow-hidden text-shell-foreground transition-[width] duration-(--duration-drawer) ease-ios motion-reduce:transition-none lg:flex lg:w-64 lg:flex-col lg:rail:w-16", children: [_jsx(SidebarHead, { product: product }), !productRail && organization ? _jsx("div", { className: "shrink-0 px-3 pb-1", children: organization }) : null, nav, sidebarFooter ? _jsx("div", { className: "grid gap-0.5 px-3 pt-1 pb-3", children: sidebarFooter }) : null] }), _jsx("div", { className: "flex min-h-dvh flex-col lg:min-h-0 lg:p-2 lg:pl-0", children: _jsxs("div", { className: "flex flex-1 flex-col lg:min-h-0 lg:overflow-y-auto lg:rounded-xl lg:bg-background", children: [_jsxs("header", { className: "sticky top-[env(safe-area-inset-top,0px)] z-30 flex h-14 shrink-0 items-center gap-1 border-b border-foreground/5 bg-background px-2 sm:px-4 lg:top-0", children: [_jsx("button", { type: "button", onClick: () => setMenuOpen(true), "aria-label": "Ouvrir le menu", className: "touch-target grid size-10 shrink-0 place-items-center rounded-xl text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50 lg:hidden", children: _jsx(HugeiconsIcon, { icon: Menu01Icon, "aria-hidden": true, className: "size-5" }) }), _jsxs("div", { "data-slot": "app-shell-brand", className: "flex shrink-0 items-center gap-2 pr-1 lg:hidden", children: [_jsx(ProductBadge, { product: product, className: "size-6" }), _jsx("span", { className: "font-heading text-[0.9375rem] font-semibold", children: productName(product) })] }), _jsx("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: header }), onSearch ? (_jsx(CommandMenuButton, { onClick: onSearch, compact: "mobile", className: "shrink-0 lg:w-96 lg:max-w-none", ...(searchLabel !== undefined ? { label: searchLabel } : {}) })) : null] }), _jsx("main", { id: "contenu", tabIndex: -1, className: cn("mx-auto w-full max-w-6xl flex-1 px-4 pt-6 pb-16 outline-none sm:px-6 lg:px-10 lg:pt-8", mainClassName), children: children })] }) }), _jsx(Drawer, { side: "bottom", open: menuOpen, onOpenChange: setMenuOpen, children: _jsxs(DrawerContent, { className: "lg:hidden", children: [_jsx(DrawerTitle, { className: "sr-only", children: "Menu" }), organization ? _jsx("div", { className: "px-3 pt-1", children: organization }) : null, nav, sidebarFooter ? (_jsx("div", { className: "grid gap-0.5 border-t border-foreground/5 px-3 pt-2 pb-[max(env(safe-area-inset-bottom),0.75rem)]", children: sidebarFooter })) : null] }) })] }) }));
135
136
  }
package/dist/checkbox.js CHANGED
@@ -15,5 +15,7 @@ import { cn } from "./cn.js";
15
15
  * ```
16
16
  */
17
17
  export function Checkbox({ className, indeterminate, ...props }) {
18
- return (_jsx(CheckboxPrimitive.Root, { "data-slot": "checkbox", indeterminate: indeterminate, className: cn("touch-target peer grid size-5 shrink-0 place-items-center rounded-[0.4rem] border border-foreground/25 bg-background text-primary-foreground", "transition-[background-color,border-color,box-shadow] duration-(--duration-fast) ease-out outline-none", "hover:border-foreground/40 focus-visible:ring-3 focus-visible:ring-ring/50", "data-checked:border-primary data-checked:bg-primary data-indeterminate:border-primary data-indeterminate:bg-primary", "data-disabled:cursor-not-allowed data-disabled:opacity-50", "aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-invalid:border-destructive", "dark:bg-white/[0.04]", className), ...props, children: _jsx(CheckboxPrimitive.Indicator, { className: "grid place-items-center data-unchecked:hidden", children: _jsx(HugeiconsIcon, { icon: indeterminate ? MinusSignIcon : Tick02Icon, strokeWidth: 2.5, className: "size-3.5" }) }) }));
18
+ return (_jsx(CheckboxPrimitive.Root, { "data-slot": "checkbox", indeterminate: indeterminate, className: cn("touch-target peer grid size-5 shrink-0 place-items-center rounded-[0.4rem] border border-foreground/25 bg-background text-primary-foreground", "transition-[background-color,border-color,box-shadow] duration-(--duration-fast) ease-out outline-none", "hover:border-foreground/40 focus-visible:ring-3 focus-visible:ring-ring/50", "data-checked:border-primary data-checked:bg-primary data-indeterminate:border-primary data-indeterminate:bg-primary", "data-disabled:cursor-not-allowed data-disabled:opacity-50", "aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-invalid:border-destructive",
19
+ // Le fond sombre de la case vide ne doit jamais recouvrir le fond coché (même spécificité).
20
+ "dark:bg-white/[0.04] dark:data-checked:bg-primary dark:data-indeterminate:bg-primary", className), ...props, children: _jsx(CheckboxPrimitive.Indicator, { className: "grid place-items-center data-unchecked:hidden", children: _jsx(HugeiconsIcon, { icon: indeterminate ? MinusSignIcon : Tick02Icon, strokeWidth: 2.5, className: "size-3.5" }) }) }));
19
21
  }
@@ -45,5 +45,7 @@ export function CopyButton({ value, label = "Copier", showLabel = false, classNa
45
45
  * qu'il soit copié.
46
46
  */
47
47
  export function Snippet({ value, prompt = false, label, className, }) {
48
- return (_jsxs("div", { "data-slot": "snippet", className: cn("flex min-w-0 items-center gap-2 rounded-2xl bg-foreground/[0.05] py-1.5 pr-1.5 pl-4", className), children: [_jsxs("pre", { className: "min-w-0 flex-1 overflow-x-auto py-1 font-mono text-[13px] [scrollbar-width:thin]", children: [prompt ? (_jsx("span", { "aria-hidden": true, className: "mr-2 text-muted-foreground select-none", children: "$" })) : null, _jsx("code", { children: value })] }), _jsx(CopyButton, { value: value, ...(label !== undefined ? { label } : {}) })] }));
48
+ return (_jsxs("div", { "data-slot": "snippet", className: cn(
49
+ // `max-w-full` : dans une colonne centrée (état vide), la commande défile au lieu de déborder.
50
+ "flex max-w-full min-w-0 items-center gap-2 rounded-2xl bg-foreground/[0.05] py-1.5 pr-1.5 pl-4", className), children: [_jsxs("pre", { className: "min-w-0 flex-1 overflow-x-auto py-1 font-mono text-[13px] [scrollbar-width:thin]", children: [prompt ? (_jsx("span", { "aria-hidden": true, className: "mr-2 text-muted-foreground select-none", children: "$" })) : null, _jsx("code", { children: value })] }), _jsx(CopyButton, { value: value, ...(label !== undefined ? { label } : {}) })] }));
49
51
  }
package/dist/empty.d.ts CHANGED
@@ -1,6 +1,14 @@
1
1
  import { type VariantProps } from "class-variance-authority";
2
2
  import { type Variants } from "./cn.ts";
3
- declare function Empty({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
3
+ /**
4
+ * État vide. `variant="card"` : la carte commune d'une page sans contenu (liste, tableau, tableau
5
+ * de bord vides), la même dans tous les produits. `plain` (défaut) : sans fond, dans une carte
6
+ * existante ou un panneau. L'icône (`EmptyMedia variant="icon"`) reste monochrome : l'accent
7
+ * violet est réservé au focus, aux liens et à l'élément actif.
8
+ */
9
+ declare function Empty({ className, variant, ...props }: React.ComponentProps<"div"> & {
10
+ variant?: "plain" | "card";
11
+ }): React.JSX.Element;
4
12
  declare function EmptyHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
5
13
  declare const emptyMediaVariants: Variants<{
6
14
  variant: "default" | "icon";
package/dist/empty.js CHANGED
@@ -1,8 +1,14 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { cva } from "class-variance-authority";
3
3
  import { cn } from "./cn.js";
4
- function Empty({ className, ...props }) {
5
- return (_jsx("div", { "data-slot": "empty", className: cn("flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-2xl p-12 text-center text-balance", className), ...props }));
4
+ /**
5
+ * État vide. `variant="card"` : la carte commune d'une page sans contenu (liste, tableau, tableau
6
+ * de bord vides), la même dans tous les produits. `plain` (défaut) : sans fond, dans une carte
7
+ * existante ou un panneau. L'icône (`EmptyMedia variant="icon"`) reste monochrome : l'accent
8
+ * violet est réservé au focus, aux liens et à l'élément actif.
9
+ */
10
+ function Empty({ className, variant = "plain", ...props }) {
11
+ return (_jsx("div", { "data-slot": "empty", "data-variant": variant, className: cn("flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-2xl p-12 text-center text-balance", variant === "card" && "surface rounded-3xl px-6 py-14", className), ...props }));
6
12
  }
7
13
  function EmptyHeader({ className, ...props }) {
8
14
  return (_jsx("div", { "data-slot": "empty-header", className: cn("flex max-w-sm flex-col items-center gap-2", className), ...props }));
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Les initiales d'un nom, pour un avatar ou une marque sans image : la première lettre des deux
3
+ * premiers mots (« Ada Lovelace » donne « AL », « Test vérif » donne « TV »), la première lettre
4
+ * seule pour un mot unique (« Acme » donne « A »). La même règle pour une personne et une
5
+ * organisation, dans tous les produits. Sans nom, la partie locale de `fallback` (une adresse
6
+ * e-mail : « jean.dupont@… » donne « JD »), puis « ? ».
7
+ */
8
+ export declare function initials(name: string | null | undefined, fallback?: string | null): string;
@@ -0,0 +1,23 @@
1
+ function firstLetters(value, separator) {
2
+ return value
3
+ .trim()
4
+ .split(separator)
5
+ .filter(Boolean)
6
+ .slice(0, 2)
7
+ .map((word) => Array.from(word)[0] ?? "")
8
+ .join("")
9
+ .toLocaleUpperCase("fr-CH");
10
+ }
11
+ /**
12
+ * Les initiales d'un nom, pour un avatar ou une marque sans image : la première lettre des deux
13
+ * premiers mots (« Ada Lovelace » donne « AL », « Test vérif » donne « TV »), la première lettre
14
+ * seule pour un mot unique (« Acme » donne « A »). La même règle pour une personne et une
15
+ * organisation, dans tous les produits. Sans nom, la partie locale de `fallback` (une adresse
16
+ * e-mail : « jean.dupont@… » donne « JD »), puis « ? ».
17
+ */
18
+ export function initials(name, fallback) {
19
+ const fromName = firstLetters(name ?? "", /\s+/);
20
+ if (fromName)
21
+ return fromName;
22
+ return firstLetters((fallback ?? "").split("@")[0] ?? "", /[\s._+-]+/) || "?";
23
+ }
@@ -0,0 +1,28 @@
1
+ import type { ReactNode } from "react";
2
+ import { type OrgRole } from "./org-switcher.tsx";
3
+ /**
4
+ * Une personne d'une organisation, dans une `ResourceList` : avatar (photo ou initiales),
5
+ * nom, « (toi) », adresse e-mail et rôle. La même ligne dans tous les produits ; le rôle reste
6
+ * visible sur téléphone.
7
+ *
8
+ * ```tsx
9
+ * <ResourceList>
10
+ * {members.map((m) => (
11
+ * <MemberItem key={m.id} name={m.name} email={m.email} orgRole={m.role} isMe={m.isMe} />
12
+ * ))}
13
+ * </ResourceList>
14
+ * ```
15
+ */
16
+ export declare function MemberItem({ name, email, orgRole: role, image, isMe, roleHint, actions, className, }: {
17
+ name: string;
18
+ email?: string;
19
+ /** Rôle dans l'organisation (`role` est réservé à ARIA). */
20
+ orgRole?: OrgRole | null;
21
+ image?: string | null;
22
+ isMe?: boolean;
23
+ /** Ce que le rôle permet, au survol du badge. */
24
+ roleHint?: string;
25
+ /** Menu « … » ou boutons-icônes. */
26
+ actions?: ReactNode;
27
+ className?: string;
28
+ }): React.JSX.Element;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Avatar, AvatarFallback, AvatarImage } from "./avatar.js";
3
+ import { initials } from "./initials.js";
4
+ import { RoleBadge } from "./org-switcher.js";
5
+ import { ResourceItem } from "./resource-list.js";
6
+ /**
7
+ * Une personne d'une organisation, dans une `ResourceList` : avatar (photo ou initiales),
8
+ * nom, « (toi) », adresse e-mail et rôle. La même ligne dans tous les produits ; le rôle reste
9
+ * visible sur téléphone.
10
+ *
11
+ * ```tsx
12
+ * <ResourceList>
13
+ * {members.map((m) => (
14
+ * <MemberItem key={m.id} name={m.name} email={m.email} orgRole={m.role} isMe={m.isMe} />
15
+ * ))}
16
+ * </ResourceList>
17
+ * ```
18
+ */
19
+ export function MemberItem({ name, email, orgRole: role, image, isMe = false, roleHint, actions, className, }) {
20
+ const shown = name.trim() || email || "Sans nom";
21
+ return (_jsx(ResourceItem, { className: className, icon: _jsxs(Avatar, { className: "size-10", children: [image ? _jsx(AvatarImage, { src: image, alt: "" }) : null, _jsx(AvatarFallback, { className: "text-xs font-semibold", children: initials(name, email) })] }), title: _jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [_jsx("span", { className: "truncate", children: shown }), isMe ? _jsx("span", { className: "shrink-0 font-normal text-muted-foreground", children: "(toi)" }) : null] }), meta: email && email !== shown ? email : undefined, badge: role ? _jsx(RoleBadge, { orgRole: role, ...(roleHint !== undefined ? { title: roleHint } : {}) }) : undefined, actions: actions }));
22
+ }
@@ -1,16 +1,25 @@
1
1
  /** Les trois rôles d'une organisation de la famille. */
2
2
  export type OrgRole = "owner" | "admin" | "member";
3
3
  export declare const ORG_ROLE_LABEL: Record<OrgRole, string>;
4
+ /**
5
+ * Le rôle d'une personne dans l'organisation, le même badge dans tous les produits : neutre,
6
+ * monochrome (l'accent violet ne marque pas un rôle). `title` : ce que le rôle permet.
7
+ */
8
+ export declare function RoleBadge({ orgRole: role, title, className, }: {
9
+ orgRole: OrgRole;
10
+ title?: string;
11
+ className?: string;
12
+ }): React.JSX.Element;
4
13
  export type Organization = {
5
14
  id: string;
6
15
  name: string;
7
16
  role?: OrgRole;
8
- /** URL d'un logo carré ; sans logo, les deux premières lettres du nom. */
17
+ /** URL d'un logo carré ; sans logo, les initiales du nom (`initials`). */
9
18
  logo?: string;
10
19
  };
11
20
  /** Un lien dans un menu, à l'allure d'un `DropdownMenuItem`. */
12
21
  export declare const MENU_LINK_ITEM: string;
13
- /** Marque d'une organisation : son logo, ou ses deux premières lettres sur un carré arrondi. */
22
+ /** Marque d'une organisation : son logo, ou ses initiales (`initials`) sur un carré arrondi. */
14
23
  export declare function OrgMark({ org, className, }: {
15
24
  org: Pick<Organization, "name" | "logo">;
16
25
  className?: string;
@@ -4,7 +4,9 @@ import Tick02Icon from "@hugeicons/core-free-icons/Tick02Icon";
4
4
  import UnfoldMoreIcon from "@hugeicons/core-free-icons/UnfoldMoreIcon";
5
5
  import { HugeiconsIcon } from "@hugeicons/react";
6
6
  import { Menu as MenuPrimitive } from "@base-ui/react/menu";
7
+ import { Badge } from "./badge.js";
7
8
  import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "./dropdown-menu.js";
9
+ import { initials } from "./initials.js";
8
10
  import { useShellPlacement } from "./shell-placement.js";
9
11
  import { cn } from "./cn.js";
10
12
  export const ORG_ROLE_LABEL = {
@@ -12,13 +14,20 @@ export const ORG_ROLE_LABEL = {
12
14
  admin: "Admin",
13
15
  member: "Membre",
14
16
  };
17
+ /**
18
+ * Le rôle d'une personne dans l'organisation, le même badge dans tous les produits : neutre,
19
+ * monochrome (l'accent violet ne marque pas un rôle). `title` : ce que le rôle permet.
20
+ */
21
+ export function RoleBadge({ orgRole: role, title, className, }) {
22
+ return (_jsx(Badge, { tone: "neutral", "data-role": role, title: title, className: className, children: ORG_ROLE_LABEL[role] }));
23
+ }
15
24
  /** Un lien dans un menu, à l'allure d'un `DropdownMenuItem`. */
16
25
  export const MENU_LINK_ITEM = "relative flex cursor-default items-center gap-2.5 rounded-xl px-3 py-2 text-sm font-medium outline-hidden select-none focus:bg-muted focus:text-foreground data-highlighted:bg-muted max-lg:min-h-11 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4";
17
- /** Marque d'une organisation : son logo, ou ses deux premières lettres sur un carré arrondi. */
26
+ /** Marque d'une organisation : son logo, ou ses initiales (`initials`) sur un carré arrondi. */
18
27
  export function OrgMark({ org, className, }) {
19
28
  if (org.logo)
20
29
  return _jsx("img", { src: org.logo, alt: "", className: cn("size-8 shrink-0 rounded-lg object-cover", className) });
21
- return (_jsx("span", { "aria-hidden": true, className: cn("grid size-8 shrink-0 place-items-center rounded-lg bg-foreground text-xs font-semibold text-background uppercase", className), children: org.name.slice(0, 2) }));
30
+ return (_jsx("span", { "aria-hidden": true, className: cn("grid size-8 shrink-0 place-items-center rounded-lg bg-foreground text-xs font-semibold text-background uppercase", className), children: initials(org.name) }));
22
31
  }
23
32
  /**
24
33
  * L'organisation active, et le menu pour en changer ou en créer une. En haut de la barre des
@@ -25,3 +25,23 @@ export declare function PageHeader({ title, description, actions, breadcrumbs, c
25
25
  breadcrumbs?: Array<Crumb>;
26
26
  className?: string;
27
27
  }): React.JSX.Element;
28
+ /**
29
+ * Une page de console, dans la coque (`AppShell`) : `PageHeader`, puis les sections empilées.
30
+ * La règle de largeur de la famille (ADR 0012) : la coque pose la même boîte partout, le titre
31
+ * commence donc au même endroit dans tous les produits ; la page choisit sa colonne.
32
+ *
33
+ * - par défaut, une colonne de lecture de 768 px (`max-w-3xl`), alignée à gauche : formulaires,
34
+ * réglages, fiches, membres ;
35
+ * - `wide` : toute la largeur (1152 px au plus) pour les tableaux, les listes à colonnes, les
36
+ * tableaux de bord et les grilles de cartes.
37
+ */
38
+ export declare function Page({ title, description, actions, breadcrumbs, wide, className, children, }: {
39
+ title: ReactNode;
40
+ description?: ReactNode;
41
+ actions?: ReactNode;
42
+ breadcrumbs?: Array<Crumb>;
43
+ /** Tableaux, listes, tableaux de bord : toute la largeur plutôt que la colonne de lecture. */
44
+ wide?: boolean;
45
+ className?: string;
46
+ children?: ReactNode;
47
+ }): React.JSX.Element;
@@ -29,3 +29,16 @@ export function Breadcrumbs({ items, className }) {
29
29
  export function PageHeader({ title, description, actions, breadcrumbs, className, }) {
30
30
  return (_jsxs("header", { "data-slot": "page-header", className: cn("mb-8 grid gap-3", className), children: [breadcrumbs?.length ? _jsx(Breadcrumbs, { items: breadcrumbs }) : null, _jsxs("div", { className: "flex flex-wrap items-end justify-between gap-4", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "text-2xl font-semibold tracking-tight sm:text-[28px]", children: title }), description ? _jsx("p", { className: "mt-1.5 max-w-prose text-sm text-muted-foreground", children: description }) : null] }), actions ? _jsx("div", { className: "flex flex-wrap items-center gap-2", children: actions }) : null] })] }));
31
31
  }
32
+ /**
33
+ * Une page de console, dans la coque (`AppShell`) : `PageHeader`, puis les sections empilées.
34
+ * La règle de largeur de la famille (ADR 0012) : la coque pose la même boîte partout, le titre
35
+ * commence donc au même endroit dans tous les produits ; la page choisit sa colonne.
36
+ *
37
+ * - par défaut, une colonne de lecture de 768 px (`max-w-3xl`), alignée à gauche : formulaires,
38
+ * réglages, fiches, membres ;
39
+ * - `wide` : toute la largeur (1152 px au plus) pour les tableaux, les listes à colonnes, les
40
+ * tableaux de bord et les grilles de cartes.
41
+ */
42
+ export function Page({ title, description, actions, breadcrumbs, wide = false, className, children, }) {
43
+ return (_jsxs("div", { "data-slot": "page", "data-width": wide ? "wide" : "default", className: cn("w-full", !wide && "max-w-3xl", className), children: [_jsx(PageHeader, { title: title, ...(description !== undefined ? { description } : {}), ...(actions !== undefined ? { actions } : {}), ...(breadcrumbs !== undefined ? { breadcrumbs } : {}) }), children !== undefined ? _jsx("div", { className: "grid gap-6", children: children }) : null] }));
44
+ }
@@ -18,5 +18,5 @@ export function RadioGroup({ className, ...props }) {
18
18
  return _jsx(RadioGroupPrimitive, { "data-slot": "radio-group", className: cn("grid gap-3", className), ...props });
19
19
  }
20
20
  export function RadioGroupItem({ className, ...props }) {
21
- return (_jsx(RadioPrimitive.Root, { "data-slot": "radio-group-item", className: cn("touch-target grid size-5 shrink-0 place-items-center rounded-full border border-foreground/25 bg-background", "transition-[border-color,box-shadow] duration-(--duration-fast) ease-out outline-none", "hover:border-foreground/40 focus-visible:ring-3 focus-visible:ring-ring/50", "data-checked:border-primary data-checked:bg-primary", "data-disabled:cursor-not-allowed data-disabled:opacity-50", "aria-invalid:border-destructive data-invalid:border-destructive", "dark:bg-white/[0.04]", className), ...props, children: _jsx(RadioPrimitive.Indicator, { className: "size-2 rounded-full bg-primary-foreground data-unchecked:hidden" }) }));
21
+ return (_jsx(RadioPrimitive.Root, { "data-slot": "radio-group-item", className: cn("touch-target grid size-5 shrink-0 place-items-center rounded-full border border-foreground/25 bg-background", "transition-[border-color,box-shadow] duration-(--duration-fast) ease-out outline-none", "hover:border-foreground/40 focus-visible:ring-3 focus-visible:ring-ring/50", "data-checked:border-primary data-checked:bg-primary", "data-disabled:cursor-not-allowed data-disabled:opacity-50", "aria-invalid:border-destructive data-invalid:border-destructive", "dark:bg-white/[0.04] dark:data-checked:bg-primary", className), ...props, children: _jsx(RadioPrimitive.Indicator, { className: "size-2 rounded-full bg-primary-foreground data-unchecked:hidden" }) }));
22
22
  }
@@ -17,7 +17,7 @@ export function ResourceList({ className, surface = true, ...props }) {
17
17
  */
18
18
  export function ResourceItem({ icon, title, meta, badge, actions, href, render, className, }) {
19
19
  const linked = href !== undefined || render !== undefined;
20
- return (_jsxs("li", { "data-slot": "resource-item", className: cn("relative flex min-h-16 items-center gap-3 px-4 py-3 transition-colors duration-(--duration-fast) ease-out not-first:border-t not-first:border-foreground/5 sm:gap-4 sm:px-5", linked && "hover:bg-foreground/[0.03] has-focus-visible:bg-foreground/[0.03]", className), children: [icon ? (_jsx("span", { "aria-hidden": true, className: "grid size-10 shrink-0 place-items-center overflow-hidden rounded-xl bg-background text-muted-foreground dark:bg-muted [&_svg:not([class*='size-'])]:size-5", children: icon })) : null, _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate text-sm font-medium", children: linked ? (_jsx(ResourceLink, { href: href, render: render, children: title })) : (title) }), meta ? _jsx("p", { className: "mt-0.5 truncate text-xs text-muted-foreground", children: meta }) : null] }), badge ? _jsx("div", { className: "hidden shrink-0 sm:block", children: badge }) : null, actions ? _jsx("div", { className: "relative z-1 flex shrink-0 items-center gap-1", children: actions }) : null] }));
20
+ return (_jsxs("li", { "data-slot": "resource-item", className: cn("relative flex min-h-16 items-center gap-3 px-4 py-3 transition-colors duration-(--duration-fast) ease-out not-first:border-t not-first:border-foreground/5 sm:gap-4 sm:px-5", linked && "hover:bg-foreground/[0.03] has-focus-visible:bg-foreground/[0.03]", className), children: [icon ? (_jsx("span", { "aria-hidden": true, className: "grid size-10 shrink-0 place-items-center overflow-hidden rounded-xl bg-background text-muted-foreground dark:bg-muted [&_svg:not([class*='size-'])]:size-5", children: icon })) : null, _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate text-sm font-medium", children: linked ? (_jsx(ResourceLink, { href: href, render: render, children: title })) : (title) }), meta ? _jsx("p", { className: "mt-0.5 truncate text-xs text-muted-foreground", children: meta }) : null] }), badge ? _jsx("div", { className: "shrink-0", children: badge }) : null, actions ? _jsx("div", { className: "relative z-1 flex shrink-0 items-center gap-1", children: actions }) : null] }));
21
21
  }
22
22
  function ResourceLink({ href, render, children, }) {
23
23
  return useRender({
package/dist/table.js CHANGED
@@ -36,7 +36,7 @@ function TableRow({ className, ...props }) {
36
36
  function TableHead({ className, children, sort, onSort, numeric = false, ...props }) {
37
37
  const sortable = onSort !== undefined;
38
38
  const direction = sort ?? "none";
39
- return (_jsx("th", { "data-slot": "table-head", scope: "col", "aria-sort": sortable ? direction : undefined, className: cn("h-11 px-4 text-left align-middle text-xs font-medium whitespace-nowrap text-muted-foreground first:pl-5 last:pr-5", numeric && "text-right", className), ...props, children: sortable ? (_jsxs("button", { type: "button", onClick: onSort, className: cn("-mx-1.5 inline-flex items-center gap-1 rounded-md px-1.5 py-1 outline-none transition-colors duration-(--duration-fast) ease-out hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50", direction !== "none" && "text-foreground", numeric && "flex-row-reverse"), children: [children, _jsx(HugeiconsIcon, { icon: direction === "ascending" ? ArrowUp01Icon : direction === "descending" ? ArrowDown01Icon : ArrowUpDownIcon, strokeWidth: 2, "aria-hidden": true, className: cn("size-3.5 shrink-0", direction === "none" && "opacity-50") })] })) : (children) }));
39
+ return (_jsx("th", { "data-slot": "table-head", scope: "col", "aria-sort": sortable ? direction : undefined, className: cn("h-11 px-4 text-left align-middle text-xs font-medium whitespace-nowrap text-muted-foreground first:pl-5 last:pr-5", numeric && "text-right", className), ...props, children: sortable ? (_jsxs("button", { type: "button", onClick: onSort, className: cn("-mx-1.5 inline-flex items-center gap-1 rounded-md px-1.5 py-1 outline-none transition-colors duration-(--duration-fast) ease-out hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50", direction !== "none" && "text-foreground"), children: [children, _jsx(HugeiconsIcon, { icon: direction === "ascending" ? ArrowUp01Icon : direction === "descending" ? ArrowDown01Icon : ArrowUpDownIcon, strokeWidth: 2, "aria-hidden": true, className: cn("size-3.5 shrink-0", direction === "none" && "opacity-50") })] })) : (children) }));
40
40
  }
41
41
  function TableCell({ className, numeric = false, ...props }) {
42
42
  return (_jsx("td", { "data-slot": "table-cell", className: cn("px-4 py-3 align-middle first:pl-5 last:pr-5", numeric && "text-right tabular-nums",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hgdc/ui",
3
- "version": "0.10.2",
3
+ "version": "0.11.1",
4
4
  "description": "Interface de la H family : jetons de la DA, thèmes, polices et composants.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://github.com/hugogebel-boop/hgdc-kit/tree/main/packages/ui#readme",