@hgdc/ui 0.63.1 → 0.64.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.
@@ -31,10 +31,8 @@ export declare function OrgMark({ org, className, }: {
31
31
  className?: string;
32
32
  }): React.JSX.Element;
33
33
  /**
34
- * L'organisation active, et le menu pour en changer ou en créer une. En haut de la barre des
35
- * produits d'`AppShell` (la marque seule, le nom quand la barre s'élargit), dans le tiroir sur
36
- * téléphone, ou en haut de la barre latérale quand le produit coupe la barre des produits.
37
- * Présentationnel : le produit passe la liste, l'active et ce qu'il faut faire au choix (changer
34
+ * L'organisation active, et le menu pour en changer ou en créer une. En haut de la barre latérale
35
+ * d'`AppShell` (la marque seule en rail) et du tiroir sur téléphone. Présentationnel : le produit passe la liste, l'active et ce qu'il faut faire au choix (changer
38
36
  * l'URL `/o/<slug>/…`), le kit n'appelle rien.
39
37
  */
40
38
  export declare function OrgSwitcher({ organizations, activeId, onSelect, createHref, onCreate, className, }: {
@@ -9,7 +9,6 @@ import { Badge } from "./badge.js";
9
9
  import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "./dropdown-menu.js";
10
10
  import { AVATAR_TONE_CLASSES, initials, orgTone } from "./initials.js";
11
11
  import { rememberShellOrganization } from "./shell-memory.js";
12
- import { useShellPlacement } from "./shell-placement.js";
13
12
  import { cn } from "./cn.js";
14
13
  export const ORG_ROLE_LABEL = {
15
14
  owner: "Propriétaire",
@@ -36,14 +35,11 @@ export function OrgMark({ org, className, }) {
36
35
  return (_jsx("span", { "aria-hidden": true, className: cn("grid size-8 shrink-0 place-items-center rounded-lg text-xs font-semibold uppercase", AVATAR_TONE_CLASSES[orgTone(org)], className), children: initials(org.name) }));
37
36
  }
38
37
  /**
39
- * L'organisation active, et le menu pour en changer ou en créer une. En haut de la barre des
40
- * produits d'`AppShell` (la marque seule, le nom quand la barre s'élargit), dans le tiroir sur
41
- * téléphone, ou en haut de la barre latérale quand le produit coupe la barre des produits.
42
- * Présentationnel : le produit passe la liste, l'active et ce qu'il faut faire au choix (changer
38
+ * L'organisation active, et le menu pour en changer ou en créer une. En haut de la barre latérale
39
+ * d'`AppShell` (la marque seule en rail) et du tiroir sur téléphone. Présentationnel : le produit passe la liste, l'active et ce qu'il faut faire au choix (changer
43
40
  * l'URL `/o/<slug>/…`), le kit n'appelle rien.
44
41
  */
45
42
  export function OrgSwitcher({ organizations, activeId, onSelect, createHref, onCreate, className, }) {
46
- const placement = useShellPlacement();
47
43
  const active = organizations.find((org) => org.id === activeId);
48
44
  // Retenue pour la première image du prochain produit ouvert sous la même origine (ADR 0020).
49
45
  const activeName = active?.name;
@@ -54,13 +50,5 @@ export function OrgSwitcher({ organizations, activeId, onSelect, createHref, onC
54
50
  }, [activeId, activeName, activeLogo]);
55
51
  if (!active)
56
52
  return null;
57
- // En haut de la barre des produits : la marque seule dans 40 px, le nom quand la barre s'élargit.
58
- const inRail = placement === "product-rail";
59
- return (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { render: _jsx("button", { type: "button", "aria-label": `Organisation ${active.name}, changer d'organisation`, title: active.name, className: cn("flex min-h-10 w-full min-w-0 items-center gap-2.5 rounded-xl px-1.5 py-1 text-left coarse:min-h-11 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", inRail
60
- ? "h-10 shrink-0 gap-3 px-1 py-0 hover:bg-shell-hover aria-expanded:bg-shell-hover coarse:h-11"
61
- : "lg:hover:bg-shell-hover lg:aria-expanded:bg-shell-hover lg:rail:w-auto lg:rail:justify-center lg:rail:px-1", className) }), children: [_jsx(OrgMark, { org: active }), _jsxs("span", { className: cn("min-w-0 flex-1", inRail
62
- ? "opacity-0 transition-opacity duration-(--duration-fast) ease-out group-data-expanded/rail:opacity-100 motion-reduce:transition-none"
63
- : "lg:rail:hidden"), children: [_jsx("span", { className: cn("block text-sm font-medium", inRail ? "truncate" : "line-clamp-2 break-words"), children: active.name }), active.role ? (_jsx("span", { className: "block truncate text-xs text-muted-foreground lg:text-shell-muted", children: ORG_ROLE_LABEL[active.role] })) : null] }), _jsx(HugeiconsIcon, { icon: UnfoldMoreIcon, "aria-hidden": true, className: cn("size-4 shrink-0 text-muted-foreground lg:text-shell-muted", inRail
64
- ? "opacity-0 transition-opacity duration-(--duration-fast) group-data-expanded/rail:opacity-100"
65
- : "lg:rail:hidden") })] }), _jsxs(DropdownMenuContent, { align: "start", className: "w-64", children: [_jsxs(DropdownMenuGroup, { children: [_jsx(DropdownMenuLabel, { children: "Organisations" }), organizations.map((org) => (_jsxs(DropdownMenuItem, { onClick: () => onSelect(org.id), children: [_jsx(OrgMark, { org: org, className: "size-6 rounded-md text-[10px]" }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: org.name }), org.id === activeId ? (_jsx(HugeiconsIcon, { icon: Tick02Icon, "aria-label": "Organisation active", className: "size-4" })) : org.role ? (_jsx("span", { className: "text-xs font-normal text-muted-foreground", children: ORG_ROLE_LABEL[org.role] })) : null] }, org.id)))] }), createHref !== undefined ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsxs(MenuPrimitive.LinkItem, { href: createHref, className: MENU_LINK_ITEM, children: [_jsx(HugeiconsIcon, { icon: PlusSignIcon, "aria-hidden": true }), "Cr\u00E9er une organisation"] })] })) : onCreate !== undefined ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { onClick: onCreate, children: [_jsx(HugeiconsIcon, { icon: PlusSignIcon, "aria-hidden": true }), "Cr\u00E9er une organisation"] })] })) : null] })] }));
53
+ return (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { render: _jsx("button", { type: "button", "aria-label": `Organisation ${active.name}, changer d'organisation`, title: active.name, className: cn("flex min-h-10 w-full min-w-0 items-center gap-2.5 rounded-xl px-1.5 py-1 text-left coarse:min-h-11 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:w-auto lg:rail:justify-center lg:rail:px-1", className) }), children: [_jsx(OrgMark, { org: active }), _jsxs("span", { className: "min-w-0 flex-1 lg:rail:hidden", children: [_jsx("span", { className: "line-clamp-2 block text-sm font-medium break-words", children: active.name }), active.role ? (_jsx("span", { className: "block truncate text-xs text-muted-foreground lg:text-shell-muted", children: ORG_ROLE_LABEL[active.role] })) : null] }), _jsx(HugeiconsIcon, { icon: UnfoldMoreIcon, "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground lg:text-shell-muted lg:rail:hidden" })] }), _jsxs(DropdownMenuContent, { align: "start", className: "w-64", children: [_jsxs(DropdownMenuGroup, { children: [_jsx(DropdownMenuLabel, { children: "Organisations" }), organizations.map((org) => (_jsxs(DropdownMenuItem, { onClick: () => onSelect(org.id), children: [_jsx(OrgMark, { org: org, className: "size-6 rounded-md text-[10px]" }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: org.name }), org.id === activeId ? (_jsx(HugeiconsIcon, { icon: Tick02Icon, "aria-label": "Organisation active", className: "size-4" })) : org.role ? (_jsx("span", { className: "text-xs font-normal text-muted-foreground", children: ORG_ROLE_LABEL[org.role] })) : null] }, org.id)))] }), createHref !== undefined ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsxs(MenuPrimitive.LinkItem, { href: createHref, className: MENU_LINK_ITEM, children: [_jsx(HugeiconsIcon, { icon: PlusSignIcon, "aria-hidden": true }), "Cr\u00E9er une organisation"] })] })) : onCreate !== undefined ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { onClick: onCreate, children: [_jsx(HugeiconsIcon, { icon: PlusSignIcon, "aria-hidden": true }), "Cr\u00E9er une organisation"] })] })) : null] })] }));
66
54
  }
@@ -0,0 +1,3 @@
1
+ import type { FamilyProduct } from "./family-svg.ts";
2
+ /** Nom affiché d'un produit de la famille (« E-mails » pour `mail`), Account compris. */
3
+ export declare function familyProductName(product: FamilyProduct): string;
@@ -0,0 +1,7 @@
1
+ import { HGDC_ACCOUNT, HGDC_PRODUCTS } from "./products.js";
2
+ /** Nom affiché d'un produit de la famille (« E-mails » pour `mail`), Account compris. */
3
+ export function familyProductName(product) {
4
+ if (product === HGDC_ACCOUNT.badge)
5
+ return HGDC_ACCOUNT.name;
6
+ return HGDC_PRODUCTS.find((item) => item.badge === product)?.name ?? product;
7
+ }
@@ -9,7 +9,7 @@ export declare const PRODUCT_STAGE_LABEL: Readonly<Record<ProductStage, string>>
9
9
  export type HgdcCatalogProduct = {
10
10
  /** Le mot du produit, qui sert de sous-domaine : « Host ». */
11
11
  word: string;
12
- /** Nom affiché, le mot seul (« Mail », « Basalte » pour `host`) : la famille ne se répète pas devant chaque produit. */
12
+ /** Nom affiché : celui du module de Basalte (« Déploiements » pour `host`), sinon le mot seul. */
13
13
  name: string;
14
14
  /** « host.hgdc.ch ». */
15
15
  subdomain: string;
@@ -108,7 +108,7 @@ export declare function productMenuEntries(options?: {
108
108
  */
109
109
  export declare function sameOriginPath(href: string, pageOrigin: string | undefined): string | null;
110
110
  /** Une entrée de la barre des produits d'`AppShell`. */
111
- export type ProductRailEntry = {
111
+ export type ShellProductEntry = {
112
112
  name: string;
113
113
  href: string;
114
114
  badge: FamilyProduct;
@@ -132,14 +132,14 @@ export type ProductRailEntry = {
132
132
  * (`PRODUCT_ORIGINS`, `app.hgdc.ch/<produit>`) reçoit un lien relatif et `sameOrigin` (ADR 0020),
133
133
  * et le produit courant sa base (`/mail/`) plutôt que `/`.
134
134
  */
135
- export declare function productRailEntries(options?: {
135
+ export declare function shellProductEntries(options?: {
136
136
  current?: string | undefined;
137
137
  accountHref?: string | undefined;
138
138
  catalog?: ReadonlyArray<HgdcCatalogProduct> | undefined;
139
139
  pageOrigin?: string | undefined;
140
140
  }): {
141
- products: Array<ProductRailEntry>;
142
- account: ProductRailEntry;
141
+ products: Array<ShellProductEntry>;
142
+ account: ShellProductEntry;
143
143
  };
144
144
  /**
145
145
  * Lien vers l'autre vue du même produit (ADR 0014) : depuis `app.hgdc.ch/<mot>`, « Ouvrir seul » vers
package/dist/products.js CHANGED
@@ -48,7 +48,7 @@ export function productEntryHref(word, origins = PRODUCT_ORIGINS) {
48
48
  */
49
49
  export const PRODUCT_ENTRY_REFRESH_QUERY = "refresh=background&next=/";
50
50
  function product(word, pitch, icon, stage,
51
- /** Nom affiché quand il diffère du mot de l'adresse (Basalte, servi sous `host`). */
51
+ /** Nom affiché quand il diffère du mot de l'adresse : le nom du module (« Déploiements » pour `host`). */
52
52
  name = word) {
53
53
  const badge = word.toLowerCase();
54
54
  if (!isFamilyProduct(badge))
@@ -76,21 +76,21 @@ export const HGDC_ACCOUNT = { ...account, entryHref: account.href };
76
76
  export const HGDC_MANAGER = product("Manager", "Tes organisations, leurs membres et leurs rôles.", UserGroupIcon, "live");
77
77
  /** Les produits de la famille, dans leur ordre d'affichage. Account n'en fait pas partie (`HGDC_ACCOUNT`). */
78
78
  export const HGDC_PRODUCTS = [
79
- // Basalte est le nom affiché de Host depuis le 2026-11-03 ; adresses, pastille et clés restent en `host`.
80
- product("Host", "Ton site en ligne, déployé à chaque modification.", Rocket01Icon, "live", "Basalte"),
81
- product("Mail", "Tes e-mails, newsletters et formulaires, envoyés depuis ton adresse.", Mail01Icon, "live"),
79
+ // Basalte est le produit ; chacun de ceux-ci y est un module, sous son nom de fonction. Adresses, pastille et clés gardent le mot.
80
+ product("Host", "Ton site en ligne, déployé à chaque modification.", Rocket01Icon, "live", "Déploiements"),
81
+ product("Mail", "Tes e-mails, newsletters et formulaires, envoyés depuis ton adresse.", Mail01Icon, "live", "E-mails"),
82
82
  // Masqué temporairement le 2026-11-03 ; la console reste en ligne.
83
83
  product("Portal", "Un espace partagé où ton client suit son projet, signe et paie.", Briefcase01Icon, "soon"),
84
- product("Status", "La surveillance de tes sites et ta page de statut.", Activity01Icon, "live"),
85
- product("Analytics", "Tes visiteurs mesurés, sans cookies ni bannière.", Analytics01Icon, "live"),
86
- product("Docs", "Ta documentation en ligne, avec un assistant qui y répond.", BookOpen01Icon, "live"),
87
- product("IAssistant", "Une bulle de chat IA sur ton site, qui répond à tes visiteurs.", BubbleChatIcon, "preview"),
84
+ product("Status", "La surveillance de tes sites et ta page de statut.", Activity01Icon, "live", "Surveillance"),
85
+ product("Analytics", "Tes visiteurs mesurés, sans cookies ni bannière.", Analytics01Icon, "live", "Statistiques"),
86
+ product("Docs", "Ta documentation en ligne, avec un assistant qui y répond.", BookOpen01Icon, "live", "Documentation"),
87
+ product("IAssistant", "Une bulle de chat IA sur ton site, qui répond à tes visiteurs.", BubbleChatIcon, "preview", "Assistant IA"),
88
88
  id(),
89
89
  HGDC_MANAGER,
90
90
  ];
91
91
  /** ID, le compte de connexion ouvert aux applications tierces : pas d'entrée `/auth/hgdc`, sa racine. */
92
92
  function id() {
93
- const item = product("ID", "Ton compte pour te connecter à tes applications.", UserCircleIcon, "live");
93
+ const item = product("ID", "Ton compte pour te connecter à tes applications.", UserCircleIcon, "live", "Connexion");
94
94
  return { ...item, entryHref: item.href };
95
95
  }
96
96
  /** Vrai si le produit répond déjà (en service ou en préversion). */
@@ -141,7 +141,7 @@ export function sameOriginPath(href, pageOrigin) {
141
141
  * (`PRODUCT_ORIGINS`, `app.hgdc.ch/<produit>`) reçoit un lien relatif et `sameOrigin` (ADR 0020),
142
142
  * et le produit courant sa base (`/mail/`) plutôt que `/`.
143
143
  */
144
- export function productRailEntries(options = {}) {
144
+ export function shellProductEntries(options = {}) {
145
145
  const current = options.current?.toLowerCase();
146
146
  const { pageOrigin } = options;
147
147
  const entry = (item, href, product) => {
@@ -1,6 +1,4 @@
1
- import { type FamilyProduct } from "./family-svg.ts";
2
- /** Nom affiché d'un produit de la famille (« Mail »), Account compris. */
3
- export declare function familyProductName(product: FamilyProduct): string;
1
+ export { familyProductName } from "./product-name.ts";
4
2
  /** Haut de la barre latérale (marque du produit) : le même dans la coque et dans le cadre. */
5
3
  export declare const SIDEBAR_HEAD = "mt-2 flex h-14 shrink-0 items-center gap-1 px-3";
6
4
  export declare const SIDEBAR_BRAND = "flex min-w-0 flex-1 items-center gap-2.5 pl-2.25";
@@ -9,13 +7,12 @@ export declare const SIDEBAR_BRAND_NAME = "truncate font-heading text-[0.9375rem
9
7
  export declare const SIDEBAR_RAIL_BOX = "grid h-9 w-10 shrink-0 place-items-center rounded-xl";
10
8
  /**
11
9
  * Le cadre de la coque, dessiné par le chargeur du document (`FamilyLoaderRoot`) quand une page
12
- * attend hors de la coque : barre des produits complète (organisation retenue, pastilles, produit
13
- * courant), barre latérale du produit (sa marque, la navigation en squelette à sa hauteur
14
- * retenue), carte du contenu et sa barre du haut. Mêmes boîtes que la coque, au pixel : la
15
- * transition de vue d'un produit à l'autre n'a rien à faire bouger, et la coque qui monte ensuite
16
- * prend la place sans saut.
10
+ * attend hors de la coque : organisation retenue, Manager, retour aux modules, nom du module,
11
+ * navigation en squelette à sa hauteur retenue, carte du contenu et sa barre du haut. Mêmes boîtes
12
+ * que la coque, au pixel : la transition de vue d'un module à l'autre n'a rien à faire bouger, et
13
+ * la coque qui monte ensuite prend la place sans saut.
17
14
  *
18
15
  * Le produit vient de l'adresse (`<html data-hgdc-product>`, `sidebarInitScript`) : le rendu
19
- * serveur dessine toutes les marques, le CSS montre la bonne.
16
+ * serveur dessine tous les noms, le CSS montre le bon.
20
17
  */
21
18
  export declare function ShellFrame(): React.JSX.Element;
@@ -1,14 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { FAMILY_PRODUCTS } from "./family-svg.js";
3
+ import { OrgPlaceholder } from "./org-placeholder.js";
3
4
  import { ProductBadge } from "./product-badge.js";
4
- import { ProductRailFrame } from "./product-rail.js";
5
- import { HGDC_ACCOUNT, HGDC_PRODUCTS } from "./products.js";
6
- /** Nom affiché d'un produit de la famille (« Mail »), Account compris. */
7
- export function familyProductName(product) {
8
- if (product === HGDC_ACCOUNT.badge)
9
- return HGDC_ACCOUNT.name;
10
- return HGDC_PRODUCTS.find((item) => item.badge === product)?.name ?? `HGDC ${product}`;
11
- }
5
+ import { familyProductName } from "./product-name.js";
6
+ export { familyProductName } from "./product-name.js";
12
7
  /** Haut de la barre latérale (marque du produit) : le même dans la coque et dans le cadre. */
13
8
  export const SIDEBAR_HEAD = "mt-2 flex h-14 shrink-0 items-center gap-1 px-3";
14
9
  export const SIDEBAR_BRAND = "flex min-w-0 flex-1 items-center gap-2.5 pl-2.25";
@@ -17,17 +12,18 @@ export const SIDEBAR_BRAND_NAME = "truncate font-heading text-[0.9375rem] font-s
17
12
  export const SIDEBAR_RAIL_BOX = "grid h-9 w-10 shrink-0 place-items-center rounded-xl";
18
13
  /** Lignes de squelette de la navigation, coupées à la hauteur retenue (`--hgdc-nav-height`). */
19
14
  const SKELETON_ROWS = 12;
15
+ /** Une ligne de la barre sans lien : même boîte qu'une entrée. */
16
+ const FRAME_ROW = "flex h-9 shrink-0 items-center gap-2.5 px-3 text-sm font-medium text-shell-muted";
20
17
  /**
21
18
  * Le cadre de la coque, dessiné par le chargeur du document (`FamilyLoaderRoot`) quand une page
22
- * attend hors de la coque : barre des produits complète (organisation retenue, pastilles, produit
23
- * courant), barre latérale du produit (sa marque, la navigation en squelette à sa hauteur
24
- * retenue), carte du contenu et sa barre du haut. Mêmes boîtes que la coque, au pixel : la
25
- * transition de vue d'un produit à l'autre n'a rien à faire bouger, et la coque qui monte ensuite
26
- * prend la place sans saut.
19
+ * attend hors de la coque : organisation retenue, Manager, retour aux modules, nom du module,
20
+ * navigation en squelette à sa hauteur retenue, carte du contenu et sa barre du haut. Mêmes boîtes
21
+ * que la coque, au pixel : la transition de vue d'un module à l'autre n'a rien à faire bouger, et
22
+ * la coque qui monte ensuite prend la place sans saut.
27
23
  *
28
24
  * Le produit vient de l'adresse (`<html data-hgdc-product>`, `sidebarInitScript`) : le rendu
29
- * serveur dessine toutes les marques, le CSS montre la bonne.
25
+ * serveur dessine tous les noms, le CSS montre le bon.
30
26
  */
31
27
  export function ShellFrame() {
32
- return (_jsxs("div", { "data-slot": "shell-frame", "aria-hidden": true, className: "hidden h-full lg:flex", children: [_jsx(ProductRailFrame, {}), _jsxs("div", { "data-slot": "shell-frame-sidebar", className: "flex w-64 shrink-0 flex-col overflow-hidden rail:w-16", children: [_jsx("div", { className: SIDEBAR_HEAD, children: FAMILY_PRODUCTS.map((product) => (_jsxs("div", { "data-product": product, className: "contents", children: [_jsxs("div", { className: `${SIDEBAR_BRAND} rail:hidden`, children: [_jsx(ProductBadge, { product: product, className: "size-6" }), _jsx("span", { className: SIDEBAR_BRAND_NAME, children: familyProductName(product) })] }), _jsx("div", { className: `${SIDEBAR_RAIL_BOX} hidden rail:grid`, children: _jsx(ProductBadge, { product: product, className: "size-6" }) })] }, product))) }), _jsx("div", { className: "px-3 py-2", children: _jsx("div", { "data-slot": "shell-frame-nav", className: "grid gap-0.5 overflow-hidden", children: Array.from({ length: SKELETON_ROWS }, (_, index) => (_jsxs("div", { className: "flex h-9 shrink-0 items-center gap-2.5 px-3 rail:justify-center rail:px-0", children: [_jsx("span", { className: "size-[1.125rem] shrink-0 rounded-md bg-shell-hover" }), _jsx("span", { className: "h-2.5 w-24 rounded-full bg-shell-hover rail:hidden" })] }, index))) }) })] }), _jsx("div", { className: "min-w-0 flex-1 py-2 pr-2", children: _jsx("div", { className: "h-full overflow-hidden rounded-xl bg-background", children: _jsx("div", { "data-slot": "shell-frame-header", className: "min-h-14 border-b border-foreground/5 bg-background" }) }) })] }));
28
+ return (_jsxs("div", { "data-slot": "shell-frame", "aria-hidden": true, className: "hidden h-full lg:flex", children: [_jsxs("div", { "data-slot": "shell-frame-sidebar", className: "flex w-64 shrink-0 flex-col overflow-hidden rail:w-16", children: [_jsx("div", { className: SIDEBAR_HEAD, children: _jsx(OrgPlaceholder, {}) }), _jsxs("div", { className: "px-3 pt-1", children: [_jsxs("div", { className: FRAME_ROW, children: [_jsx("span", { className: "size-[1.125rem] shrink-0 rounded-md bg-shell-hover" }), _jsx("span", { className: "rail:hidden", children: "Manager" })] }), _jsx("div", { className: "mx-2 mt-2 h-px bg-foreground/8" })] }), _jsxs("div", { className: "px-3 pt-2", children: [_jsxs("div", { className: `${FRAME_ROW} mb-2 rounded-xl bg-shell-hover text-shell-foreground`, children: [_jsx("span", { className: "size-4 shrink-0" }), _jsx("span", { className: "rail:hidden", children: "Retour aux modules" })] }), FAMILY_PRODUCTS.map((product) => (_jsx("div", { "data-product": product, className: "contents", children: _jsxs("div", { className: `${FRAME_ROW} font-semibold text-shell-foreground rail:hidden`, children: [_jsx(ProductBadge, { product: product, className: "size-[1.125rem] shrink-0" }), _jsx("span", { className: "truncate font-heading", children: familyProductName(product) })] }) }, product)))] }), _jsx("div", { className: "px-3 py-2", children: _jsx("div", { "data-slot": "shell-frame-nav", className: "grid gap-0.5 overflow-hidden", children: Array.from({ length: SKELETON_ROWS }, (_, index) => (_jsxs("div", { className: "flex h-9 shrink-0 items-center gap-2.5 px-3 rail:justify-center rail:px-0", children: [_jsx("span", { className: "size-[1.125rem] shrink-0 rounded-md bg-shell-hover" }), _jsx("span", { className: "h-2.5 w-24 rounded-full bg-shell-hover rail:hidden" })] }, index))) }) })] }), _jsx("div", { className: "min-w-0 flex-1 py-2 pr-2", children: _jsx("div", { className: "h-full overflow-hidden rounded-xl bg-background", children: _jsx("div", { "data-slot": "shell-frame-header", className: "min-h-14 border-b border-foreground/5 bg-background" }) }) })] }));
33
29
  }
@@ -1,6 +1,7 @@
1
1
  import type { ReactNode } from "react";
2
2
  import { type ProductView } from "./addresses.ts";
3
3
  import type { FamilyProduct } from "./family-svg.ts";
4
+ import type { ShellMenuLinks } from "./shell-placement.ts";
4
5
  /**
5
6
  * Un accès commun de la barre : une adresse, ou un lien du routeur (`{ render: <Link to="/settings" /> }`,
6
7
  * dont le `data-status="active"` marque la page ouverte), ou une adresse avec `active`.
@@ -10,7 +11,7 @@ export type ShellLink = string | {
10
11
  render?: React.ReactElement;
11
12
  active?: boolean;
12
13
  };
13
- /** Les trois accès que la coque pose juste au-dessus du compte, toujours dans cet ordre. */
14
+ /** Les trois accès communs : au-dessus du compte ou dans son menu, selon la coque (`AppShell`). */
14
15
  export type AppShellLinks = {
15
16
  /** Documentation du produit. */
16
17
  documentation?: ShellLink;
@@ -25,6 +26,8 @@ export declare function ShellCommonLinks({ links, statusPageUrl, otherView, }: {
25
26
  /** Lien vers l'autre vue du produit (ADR 0014), en dernier ; il ne suffit pas à montrer la liste. */
26
27
  otherView?: ReactNode;
27
28
  }): React.JSX.Element | null;
29
+ /** L'autre vue, pour le menu du compte. `undefined` pour un produit qui n'en a pas. */
30
+ export declare function otherViewMenuLink(product: FamilyProduct, view: ProductView): ShellMenuLinks["otherView"];
28
31
  /**
29
32
  * « Ouvrir seul » (vers `<mot>.hgdc.ch`) ou « Ouvrir dans app.hgdc.ch », discret, sous les accès communs.
30
33
  * Le chemin courant n'est lu qu'au clic : le lien rendu par le serveur mène à l'accueil.
@@ -30,6 +30,17 @@ export function ShellCommonLinks({ links, statusPageUrl, otherView, }) {
30
30
  return null;
31
31
  return (_jsx("nav", { "aria-label": "Acc\u00E8s communs", children: _jsxs("ul", { "data-slot": "app-shell-links", className: "grid gap-0.5", children: [items, otherView] }) }));
32
32
  }
33
+ function otherViewClick(product, view, fallback) {
34
+ return (event) => {
35
+ const path = soloHref(`/${product}`, `${window.location.pathname}${window.location.search}`);
36
+ event.currentTarget.href = otherViewHref(product, view, path) ?? fallback;
37
+ };
38
+ }
39
+ /** L'autre vue, pour le menu du compte. `undefined` pour un produit qui n'en a pas. */
40
+ export function otherViewMenuLink(product, view) {
41
+ const href = otherViewHref(product, view);
42
+ return href ? { label: otherViewLabel(view), href, onClick: otherViewClick(product, view, href) } : undefined;
43
+ }
33
44
  /**
34
45
  * « Ouvrir seul » (vers `<mot>.hgdc.ch`) ou « Ouvrir dans app.hgdc.ch », discret, sous les accès communs.
35
46
  * Le chemin courant n'est lu qu'au clic : le lien rendu par le serveur mène à l'accueil.
@@ -38,9 +49,5 @@ export function OtherViewLink({ product, view, }) {
38
49
  const href = otherViewHref(product, view);
39
50
  if (!href)
40
51
  return null;
41
- const onClick = (event) => {
42
- const path = soloHref(`/${product}`, `${window.location.pathname}${window.location.search}`);
43
- event.currentTarget.href = otherViewHref(product, view, path) ?? href;
44
- };
45
- return (_jsx(SidebarNavItem, { icon: LinkSquare02Icon, label: otherViewLabel(view), render: _jsx("a", { href: href, onClick: onClick }) }));
52
+ return (_jsx(SidebarNavItem, { icon: LinkSquare02Icon, label: otherViewLabel(view), render: _jsx("a", { href: href, onClick: otherViewClick(product, view, href) }) }));
46
53
  }
@@ -4,10 +4,6 @@ export declare const SHELL_ORG_KEY = "hgdc-shell:org";
4
4
  export declare const SHELL_NAV_KEY_PREFIX = "hgdc-shell:nav:";
5
5
  /** Dernier produit ouvert (son mot), en `localStorage` : l'accueil d'`app.hgdc.ch` y renvoie. */
6
6
  export declare const SHELL_LAST_PRODUCT_KEY = "hgdc-shell:last";
7
- /** Passage d'un produit à l'autre (horodatage), en `sessionStorage`. */
8
- export declare const SHELL_SWITCH_KEY = "hgdc-shell:switch";
9
- /** Au-delà, un passage noté n'est plus celui qu'on vit (onglet laissé, retour bien plus tard). */
10
- export declare const SHELL_SWITCH_MAX_AGE_MS = 10000;
11
7
  export type ShellOrganization = {
12
8
  initials: string;
13
9
  logo?: string;
@@ -67,8 +63,4 @@ export declare function forgetShellOrganization(): void;
67
63
  export declare function rememberShellNavHeight(product: string, height: number): void;
68
64
  /** Retient le produit affiché (`AppShell` le fait), pour que l'accueil d'`app.hgdc.ch` y renvoie. */
69
65
  export declare function rememberLastProduct(product: string): void;
70
- /** Note le départ vers un autre produit (clic dans la barre des produits). */
71
- export declare function markProductSwitch(now?: number): void;
72
- /** Lit et efface la note : vrai si la page vient d'un passage depuis la barre, il y a peu. */
73
- export declare function takeProductSwitch(now?: number): boolean;
74
66
  export {};
@@ -3,13 +3,11 @@ import { FAMILY_PRODUCTS } from "./family-svg.js";
3
3
  import { PRODUCT_ORIGINS } from "./products.js";
4
4
  /*
5
5
  * Mémoire de la coque, partagée par les produits servis sous la même origine (`app.hgdc.ch/<produit>`,
6
- * ADR 0020) : ce qu'il faut pour que la première image d'un produit ait déjà la barre des produits
7
- * complète et la barre latérale à sa taille, avant que ses données n'arrivent.
6
+ * ADR 0020) : ce qu'il faut pour que la première image d'un produit ait déjà l'organisation
7
+ * et la barre latérale à sa taille, avant que ses données n'arrivent.
8
8
  *
9
9
  * - l'organisation affichée en dernier : ses initiales et son logo (rien d'autre, pas le nom) ;
10
10
  * - la hauteur de la navigation de chaque produit, pour un squelette à la bonne taille ;
11
- * - le passage en cours d'un produit à l'autre (session de l'onglet), pour que la barre d'arrivée
12
- * ne s'élargisse pas sous une souris restée immobile.
13
11
  *
14
12
  * `shellBoot` applique la mémoire sur `<html>` avant la première peinture : il tourne en ligne
15
13
  * dans le `<head>` (`sidebarInitScript`), donc il ne dépend de rien d'autre que de ses arguments.
@@ -20,10 +18,6 @@ export const SHELL_ORG_KEY = "hgdc-shell:org";
20
18
  export const SHELL_NAV_KEY_PREFIX = "hgdc-shell:nav:";
21
19
  /** Dernier produit ouvert (son mot), en `localStorage` : l'accueil d'`app.hgdc.ch` y renvoie. */
22
20
  export const SHELL_LAST_PRODUCT_KEY = "hgdc-shell:last";
23
- /** Passage d'un produit à l'autre (horodatage), en `sessionStorage`. */
24
- export const SHELL_SWITCH_KEY = "hgdc-shell:switch";
25
- /** Au-delà, un passage noté n'est plus celui qu'on vit (onglet laissé, retour bien plus tard). */
26
- export const SHELL_SWITCH_MAX_AGE_MS = 10_000;
27
21
  /**
28
22
  * Applique la mémoire de la coque sur `<html>` : `data-sidebar="rail"`, `data-hgdc-product`
29
23
  * (le produit de l'adresse), `data-hgdc-org` et les variables `--hgdc-org-initials`,
@@ -127,12 +121,12 @@ export function shellBootScript(config = SHELL_BOOT_CONFIG) {
127
121
  function browser() {
128
122
  return typeof window === "undefined" ? null : window;
129
123
  }
130
- function storageSet(storage, key, value) {
124
+ function storageSet(key, value) {
131
125
  try {
132
126
  if (value === null)
133
- window[storage].removeItem(key);
127
+ window.localStorage.removeItem(key);
134
128
  else
135
- window[storage].setItem(key, value);
129
+ window.localStorage.setItem(key, value);
136
130
  }
137
131
  catch {
138
132
  // Stockage bloqué : la première image d'un autre produit montrera un squelette, rien de plus.
@@ -147,7 +141,7 @@ export function rememberShellOrganization(org) {
147
141
  const win = browser();
148
142
  if (!win)
149
143
  return;
150
- storageSet("localStorage", SHELL_ORG_KEY, JSON.stringify(shellOrganization(org)));
144
+ storageSet(SHELL_ORG_KEY, JSON.stringify(shellOrganization(org)));
151
145
  shellBoot(SHELL_BOOT_CONFIG, win);
152
146
  }
153
147
  /** Oublie l'organisation (déconnexion) : la page suivante montre un squelette. */
@@ -155,39 +149,18 @@ export function forgetShellOrganization() {
155
149
  const win = browser();
156
150
  if (!win)
157
151
  return;
158
- storageSet("localStorage", SHELL_ORG_KEY, null);
152
+ storageSet(SHELL_ORG_KEY, null);
159
153
  shellBoot(SHELL_BOOT_CONFIG, win);
160
154
  }
161
155
  /** Retient la hauteur de la navigation d'un produit (`AppShell` le fait après l'affichage). */
162
156
  export function rememberShellNavHeight(product, height) {
163
157
  if (!browser() || !(height > 0))
164
158
  return;
165
- storageSet("localStorage", SHELL_NAV_KEY_PREFIX + product, String(Math.round(height)));
159
+ storageSet(SHELL_NAV_KEY_PREFIX + product, String(Math.round(height)));
166
160
  }
167
161
  /** Retient le produit affiché (`AppShell` le fait), pour que l'accueil d'`app.hgdc.ch` y renvoie. */
168
162
  export function rememberLastProduct(product) {
169
163
  if (!browser())
170
164
  return;
171
- storageSet("localStorage", SHELL_LAST_PRODUCT_KEY, product);
172
- }
173
- /** Note le départ vers un autre produit (clic dans la barre des produits). */
174
- export function markProductSwitch(now = Date.now()) {
175
- if (!browser())
176
- return;
177
- storageSet("sessionStorage", SHELL_SWITCH_KEY, String(now));
178
- }
179
- /** Lit et efface la note : vrai si la page vient d'un passage depuis la barre, il y a peu. */
180
- export function takeProductSwitch(now = Date.now()) {
181
- if (!browser())
182
- return false;
183
- let value = null;
184
- try {
185
- value = window.sessionStorage.getItem(SHELL_SWITCH_KEY);
186
- window.sessionStorage.removeItem(SHELL_SWITCH_KEY);
187
- }
188
- catch {
189
- return false;
190
- }
191
- const at = Number(value);
192
- return value !== null && at > 0 && now - at >= 0 && now - at < SHELL_SWITCH_MAX_AGE_MS;
165
+ storageSet(SHELL_LAST_PRODUCT_KEY, product);
193
166
  }
@@ -1,16 +1,22 @@
1
+ import type { ShellLink } from "./shell-links.tsx";
1
2
  /**
2
- * Où `AppShell` pose un élément du produit (le sélecteur d'organisation) : dans la barre latérale
3
- * ou le tiroir mobile, ou en haut de la barre des produits, où il n'a qu'une colonne de 56 px.
3
+ * Vrai quand la coque montre la barre de Basalte (Manager et modules) : le menu du compte ne répète
4
+ * pas les produits et reprend les accès d'aide (`ShellMenuLinksContext`).
4
5
  */
5
- export type ShellPlacement = "sidebar" | "product-rail";
6
- export declare const ShellPlacementContext: React.Context<ShellPlacement>;
7
- export declare function useShellPlacement(): ShellPlacement;
8
- /**
9
- * Vrai quand la coque affiche la barre des produits (sur ordinateur) : les produits y sont déjà,
10
- * le menu du compte ne les répète pas au-delà de `lg`.
11
- */
12
- export declare const ProductRailShownContext: React.Context<boolean>;
13
- export declare function useProductRailShown(): boolean;
6
+ export declare const ModuleBarShownContext: React.Context<boolean>;
7
+ export declare function useModuleBarShown(): boolean;
8
+ /** Les accès que le menu du compte porte dans la barre de Basalte. */
9
+ export type ShellMenuLinks = {
10
+ documentation?: ShellLink | undefined;
11
+ status?: ShellLink | undefined;
12
+ otherView?: {
13
+ label: string;
14
+ href: string;
15
+ onClick: (event: React.MouseEvent<HTMLAnchorElement>) => void;
16
+ } | undefined;
17
+ };
18
+ export declare const ShellMenuLinksContext: React.Context<ShellMenuLinks>;
19
+ export declare function useShellMenuLinks(): ShellMenuLinks;
14
20
  /**
15
21
  * Prévient la coque qu'un lien vers un autre produit (ou une autre adresse servie en document
16
22
  * complet) vient d'être suivi : elle couvre aussitôt la zone de contenu du H de chargement, le
@@ -1,15 +1,15 @@
1
1
  import { createContext, useContext } from "react";
2
- export const ShellPlacementContext = createContext("sidebar");
3
- export function useShellPlacement() {
4
- return useContext(ShellPlacementContext);
5
- }
6
2
  /**
7
- * Vrai quand la coque affiche la barre des produits (sur ordinateur) : les produits y sont déjà,
8
- * le menu du compte ne les répète pas au-delà de `lg`.
3
+ * Vrai quand la coque montre la barre de Basalte (Manager et modules) : le menu du compte ne répète
4
+ * pas les produits et reprend les accès d'aide (`ShellMenuLinksContext`).
9
5
  */
10
- export const ProductRailShownContext = createContext(false);
11
- export function useProductRailShown() {
12
- return useContext(ProductRailShownContext);
6
+ export const ModuleBarShownContext = createContext(false);
7
+ export function useModuleBarShown() {
8
+ return useContext(ModuleBarShownContext);
9
+ }
10
+ export const ShellMenuLinksContext = createContext({});
11
+ export function useShellMenuLinks() {
12
+ return useContext(ShellMenuLinksContext);
13
13
  }
14
14
  /**
15
15
  * Prévient la coque qu'un lien vers un autre produit (ou une autre adresse servie en document
@@ -1,6 +1,14 @@
1
+ import type { ReactNode } from "react";
1
2
  import type { FamilyProduct } from "./family-svg.ts";
2
3
  /**
3
- * Haut de la barre latérale. Dépliée : la marque du produit, et le bouton « Réduire » à droite.
4
+ * Haut de la barre de Basalte : l'organisation et le bouton « Réduire ». En rail, l'organisation
5
+ * se réduit à sa marque et le bouton « Déplier » passe dessous.
6
+ */
7
+ export declare function ShellOrganizationHead({ organization }: {
8
+ organization: ReactNode;
9
+ }): React.JSX.Element;
10
+ /**
11
+ * Haut de la barre latérale d'une vue seule, sans barre de Basalte. Dépliée : la marque du produit, et le bouton « Réduire » à droite.
4
12
  * En rail : la marque seule, qui devient le bouton « Déplier » au survol de la barre ou au focus,
5
13
  * à la même place : le bouton ne descend jamais sous le logo. Les deux états sont dans la page et
6
14
  * choisis par la variante CSS `rail:`, donc justes dès la première peinture.
@@ -4,6 +4,7 @@ import PanelLeftOpenIcon from "@hugeicons/core-free-icons/PanelLeftOpenIcon";
4
4
  import { HugeiconsIcon } from "@hugeicons/react";
5
5
  import { cn } from "./cn.js";
6
6
  import { isApplePlatform, Kbd, KbdGroup } from "./kbd.js";
7
+ import { OrgPlaceholder } from "./org-placeholder.js";
7
8
  import { ProductBadge } from "./product-badge.js";
8
9
  import { familyProductName, SIDEBAR_BRAND, SIDEBAR_BRAND_NAME, SIDEBAR_HEAD } from "./shell-frame.js";
9
10
  import { toggleSidebar } from "./sidebar-rail.js";
@@ -18,7 +19,14 @@ function ShortcutHint({ label }) {
18
19
  return (_jsxs("span", { className: "flex items-center gap-2", children: [label, _jsxs(KbdGroup, { children: [_jsx(Kbd, { children: isApplePlatform() ? "⌘" : "Ctrl" }), _jsx(Kbd, { children: "B" })] })] }));
19
20
  }
20
21
  /**
21
- * Haut de la barre latérale. Dépliée : la marque du produit, et le bouton « Réduire » à droite.
22
+ * Haut de la barre de Basalte : l'organisation et le bouton « Réduire ». En rail, l'organisation
23
+ * se réduit à sa marque et le bouton « Déplier » passe dessous.
24
+ */
25
+ export function ShellOrganizationHead({ organization }) {
26
+ return (_jsxs("div", { "data-slot": "shell-organization", className: "mt-2 flex min-h-14 shrink-0 items-center gap-1 px-3 lg:rail:flex-col", children: [_jsx("div", { className: "flex min-w-0 flex-1 items-center lg:rail:flex-none", children: organization === undefined ? _jsx(OrgPlaceholder, {}) : organization }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx("button", { type: "button", "data-slot": "sidebar-collapse", onClick: toggleSidebar, "aria-label": "R\u00E9duire la barre lat\u00E9rale", "aria-keyshortcuts": "Control+B Meta+B", className: cn(TOGGLE, "rail:hidden") }), children: _jsx(HugeiconsIcon, { icon: COLLAPSE_ICON, strokeWidth: 1.8, "aria-hidden": true, className: "size-[1.125rem]" }) }), _jsx(TooltipContent, { side: "right", children: _jsx(ShortcutHint, { label: "R\u00E9duire" }) })] }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx("button", { type: "button", "data-slot": "sidebar-expand", onClick: toggleSidebar, "aria-label": "D\u00E9plier la barre lat\u00E9rale", "aria-keyshortcuts": "Control+B Meta+B", className: cn(TOGGLE, "hidden rail:grid") }), children: _jsx(HugeiconsIcon, { icon: EXPAND_ICON, strokeWidth: 1.8, "aria-hidden": true, className: "size-[1.125rem]" }) }), _jsx(TooltipContent, { side: "right", children: _jsx(ShortcutHint, { label: "D\u00E9plier" }) })] })] }));
27
+ }
28
+ /**
29
+ * Haut de la barre latérale d'une vue seule, sans barre de Basalte. Dépliée : la marque du produit, et le bouton « Réduire » à droite.
22
30
  * En rail : la marque seule, qui devient le bouton « Déplier » au survol de la barre ou au focus,
23
31
  * à la même place : le bouton ne descend jamais sous le logo. Les deux états sont dans la page et
24
32
  * choisis par la variante CSS `rail:`, donc justes dès la première peinture.
@@ -1,5 +1,7 @@
1
1
  import { type IconSvgElement } from "@hugeicons/react";
2
2
  import type { ReactNode } from "react";
3
+ /** Une entrée de la barre : la même pour un onglet et pour un module. */
4
+ export declare const SIDEBAR_ITEM: string;
3
5
  /**
4
6
  * La navigation du produit : 4 à 7 entrées, à plat. Une navigation imbriquée (l'espace d'une page,
5
7
  * d'un projet) commence par `SidebarNavBack` et range ses entrées dans des `SidebarNavGroup`.
@@ -29,10 +31,12 @@ export declare function SidebarNavItem({ icon, label, active, href, render, }: {
29
31
  * groupes. Une pastille teintée, flèche à gauche, jamais « vous êtes ici ». En rail, la flèche seule
30
32
  * et le texte en infobulle.
31
33
  */
32
- export declare function SidebarNavBack({ label, href, render, }: {
34
+ export declare function SidebarNavBack({ label, href, render, keepMenuOpen, }: {
33
35
  /** « Retour aux pages » */
34
36
  label: string;
35
37
  href?: string;
36
38
  /** Lien du routeur : `render={<Link to="…" />}`. */
37
39
  render?: React.ReactElement;
40
+ /** Sur téléphone, le tiroir reste ouvert : le retour change la liste sans changer de page. */
41
+ keepMenuOpen?: boolean;
38
42
  }): React.JSX.Element;
@@ -6,6 +6,8 @@ import { cn } from "./cn.js";
6
6
  import { useAppShell } from "./shell-context.js";
7
7
  import { useSidebarRail } from "./sidebar-rail.js";
8
8
  import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.js";
9
+ /** Une entrée de la barre : la même pour un onglet et pour un module. */
10
+ export const SIDEBAR_ITEM = cn("flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-medium whitespace-nowrap text-muted-foreground outline-none transition-colors duration-(--duration-fast) ease-out", "hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50", "aria-[current=page]:bg-muted aria-[current=page]:text-foreground data-[status=active]:bg-muted data-[status=active]:text-foreground", "min-h-11 lg:min-h-9 coarse:min-h-11 lg:gap-2.5 lg:py-2 lg:text-shell-muted lg:hover:bg-shell-hover lg:hover:text-shell-foreground", "lg:aria-[current=page]:bg-shell-active lg:aria-[current=page]:text-shell-foreground lg:data-[status=active]:bg-shell-active lg:data-[status=active]:text-shell-foreground", "lg:rail:justify-center lg:rail:px-0");
9
11
  /**
10
12
  * La navigation du produit : 4 à 7 entrées, à plat. Une navigation imbriquée (l'espace d'une page,
11
13
  * d'un projet) commence par `SidebarNavBack` et range ses entrées dans des `SidebarNavGroup`.
@@ -28,7 +30,7 @@ export function SidebarNavItem({ icon, label, active, href, render, }) {
28
30
  href,
29
31
  "aria-current": active ? "page" : undefined,
30
32
  onClick: closeMobileNav,
31
- className: cn("flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-medium whitespace-nowrap text-muted-foreground outline-none transition-colors duration-(--duration-fast) ease-out", "hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50", "aria-[current=page]:bg-muted aria-[current=page]:text-foreground data-[status=active]:bg-muted data-[status=active]:text-foreground", "min-h-11 lg:min-h-9 coarse:min-h-11 lg:gap-2.5 lg:py-2 lg:text-shell-muted lg:hover:bg-shell-hover lg:hover:text-shell-foreground", "lg:aria-[current=page]:bg-shell-active lg:aria-[current=page]:text-shell-foreground lg:data-[status=active]:bg-shell-active lg:data-[status=active]:text-shell-foreground", "lg:rail:justify-center lg:rail:px-0"),
33
+ className: SIDEBAR_ITEM,
32
34
  children: (_jsxs(_Fragment, { children: [_jsx(HugeiconsIcon, { icon: icon, strokeWidth: 1.8, "aria-hidden": true, className: "size-5 shrink-0 lg:size-[1.125rem]" }), _jsx("span", { className: "truncate lg:rail:sr-only", children: label })] })),
33
35
  },
34
36
  });
@@ -40,7 +42,7 @@ export function SidebarNavItem({ icon, label, active, href, render, }) {
40
42
  * groupes. Une pastille teintée, flèche à gauche, jamais « vous êtes ici ». En rail, la flèche seule
41
43
  * et le texte en infobulle.
42
44
  */
43
- export function SidebarNavBack({ label, href, render, }) {
45
+ export function SidebarNavBack({ label, href, render, keepMenuOpen = false, }) {
44
46
  const rail = useSidebarRail();
45
47
  const { closeMobileNav } = useAppShell();
46
48
  const link = useRender({
@@ -48,7 +50,7 @@ export function SidebarNavBack({ label, href, render, }) {
48
50
  render,
49
51
  props: {
50
52
  href,
51
- onClick: closeMobileNav,
53
+ ...(keepMenuOpen ? {} : { onClick: closeMobileNav }),
52
54
  "data-slot": "sidebar-nav-back",
53
55
  className: cn(
54
56
  // Jamais marqué actif : le lien du routeur pose `data-status="active"` sur un parent ouvert.
@@ -20,7 +20,6 @@ export function useProductLeave() {
20
20
  return;
21
21
  window.clearTimeout(leaveTimer.current);
22
22
  setLeaving(false);
23
- delete document.documentElement.dataset.hgdcSwitching;
24
23
  };
25
24
  window.addEventListener("pageshow", onPageShow);
26
25
  return () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hgdc/ui",
3
- "version": "0.63.1",
3
+ "version": "0.64.0",
4
4
  "description": "Interface de la H Collection : 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",