@hgdc/ui 0.10.2 → 0.11.0
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 +14 -1
- package/dist/account-menu.js +2 -9
- package/dist/app-shell.d.ts +16 -3
- package/dist/app-shell.js +3 -2
- package/dist/checkbox.js +3 -1
- package/dist/copy-button.js +3 -1
- package/dist/empty.d.ts +9 -1
- package/dist/empty.js +8 -2
- package/dist/initials.d.ts +8 -0
- package/dist/initials.js +23 -0
- package/dist/member-item.d.ts +28 -0
- package/dist/member-item.js +22 -0
- package/dist/org-switcher.d.ts +11 -2
- package/dist/org-switcher.js +11 -2
- package/dist/page-header.d.ts +20 -0
- package/dist/page-header.js +13 -0
- package/dist/radio-group.js +1 -1
- package/dist/resource-list.js +1 -1
- package/package.json +1 -1
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 |
|
package/dist/account-menu.js
CHANGED
|
@@ -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);
|
package/dist/app-shell.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
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",
|
|
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
|
}
|
package/dist/copy-button.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
5
|
-
|
|
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;
|
package/dist/initials.js
ADDED
|
@@ -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
|
+
}
|
package/dist/org-switcher.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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;
|
package/dist/org-switcher.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
package/dist/page-header.d.ts
CHANGED
|
@@ -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;
|
package/dist/page-header.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/radio-group.js
CHANGED
|
@@ -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
|
}
|
package/dist/resource-list.js
CHANGED
|
@@ -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: "
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hgdc/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
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",
|