@hgdc/ui 0.12.0 → 0.12.2

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.
@@ -8,7 +8,8 @@ export declare const PRODUCT_RAIL_OPEN_DELAY = 150;
8
8
  * d'intention) ou au focus clavier, elle s'élargit et écrit le nom de chaque produit. Elle ne
9
9
  * recouvre jamais la page : `AppShell` plie la barre latérale du produit le temps du survol, la
10
10
  * barre prend exactement la place libérée (248 px) ; si la barre latérale est déjà pliée, elle
11
- * pousse la page (220 px). Tout revient dès que la souris sort.
11
+ * pousse la page (220 px). Un menu ouvert depuis la barre (sélecteur d'organisation) la garde
12
+ * élargie ; tout revient quand la souris est sortie et que le menu est fermé.
12
13
  */
13
14
  export declare function ProductRail({ current, organization, accountHref, catalog, onExpandedChange, }: {
14
15
  /** Le mot du produit affiché (« host ») : marqué en teinte. */
@@ -15,17 +15,24 @@ function ProductLink({ entry }) {
15
15
  * d'intention) ou au focus clavier, elle s'élargit et écrit le nom de chaque produit. Elle ne
16
16
  * recouvre jamais la page : `AppShell` plie la barre latérale du produit le temps du survol, la
17
17
  * barre prend exactement la place libérée (248 px) ; si la barre latérale est déjà pliée, elle
18
- * pousse la page (220 px). Tout revient dès que la souris sort.
18
+ * pousse la page (220 px). Un menu ouvert depuis la barre (sélecteur d'organisation) la garde
19
+ * élargie ; tout revient quand la souris est sortie et que le menu est fermé.
19
20
  */
20
21
  export function ProductRail({ current, organization, accountHref, catalog, onExpandedChange, }) {
21
- const [expanded, setExpanded] = useState(false);
22
+ // Trois raisons de rester élargie : le survol (après le délai d'intention), le focus clavier, et
23
+ // un menu ouvert depuis la barre (sélecteur d'organisation ou tout autre) : la souris qui quitte
24
+ // la barre pour aller dans le menu ne doit pas la replier sous elle.
25
+ const [hovered, setHovered] = useState(false);
26
+ const [focused, setFocused] = useState(false);
27
+ const [menuOpen, setMenuOpen] = useState(false);
28
+ const expanded = hovered || focused || menuOpen;
22
29
  useEffect(() => {
23
30
  onExpandedChange?.(expanded);
24
31
  }, [expanded, onExpandedChange]);
25
32
  const ref = useRef(null);
26
33
  const { products, account } = productRailEntries({ current, accountHref, catalog });
27
- // Écouteurs natifs : la barre est un repère `nav`, pas un contrôle ; c'est son survol et le
28
- // focus de ses liens qui l'élargissent.
34
+ // Écouteurs natifs : la barre est un repère `nav`, pas un contrôle ; c'est son survol, le focus
35
+ // de ses liens et les menus qu'elle ouvre qui l'élargissent.
29
36
  useEffect(() => {
30
37
  const rail = ref.current;
31
38
  if (!rail)
@@ -39,27 +46,44 @@ export function ProductRail({ current, organization, accountHref, catalog, onExp
39
46
  if (event.pointerType !== "mouse")
40
47
  return;
41
48
  cancel();
42
- timer = window.setTimeout(() => setExpanded(true), PRODUCT_RAIL_OPEN_DELAY);
49
+ timer = window.setTimeout(() => setHovered(true), PRODUCT_RAIL_OPEN_DELAY);
43
50
  };
44
51
  const onLeave = () => {
45
52
  cancel();
46
- setExpanded(false);
53
+ setHovered(false);
54
+ };
55
+ // Au clavier seulement : un clic de souris ne laisse pas la barre ouverte, ni le focus rendu au
56
+ // déclencheur quand un menu ouvert à la souris se ferme. La dernière interaction fait foi.
57
+ let keyboard = false;
58
+ const onDocKey = () => {
59
+ keyboard = true;
60
+ };
61
+ const onDocPointer = () => {
62
+ keyboard = false;
47
63
  };
48
- // Au clavier seulement : un clic de souris ne laisse pas la barre ouverte.
49
64
  const onFocusIn = (event) => {
50
- if (event.target && focusVisible(event.target))
51
- setExpanded(true);
65
+ if (keyboard && event.target && focusVisible(event.target))
66
+ setFocused(true);
52
67
  };
53
68
  const onFocusOut = (event) => {
54
69
  if (!(event.relatedTarget instanceof Node) || !rail.contains(event.relatedTarget))
55
- setExpanded(false);
70
+ setFocused(false);
56
71
  };
57
72
  const onKey = (event) => {
58
73
  if (event.key !== "Escape")
59
74
  return;
60
75
  cancel();
61
- setExpanded(false);
76
+ setHovered(false);
77
+ setFocused(false);
62
78
  };
79
+ // Un menu ouvert depuis la barre : son déclencheur porte `aria-expanded="true"` (Base UI, Radix,
80
+ // natif). Le menu lui-même vit dans un portail, hors de la barre : on suit donc le déclencheur.
81
+ const syncMenu = () => setMenuOpen(rail.querySelector('[aria-expanded="true"]') !== null);
82
+ const observer = new MutationObserver(syncMenu);
83
+ observer.observe(rail, { subtree: true, attributes: true, attributeFilter: ["aria-expanded"], childList: true });
84
+ syncMenu();
85
+ document.addEventListener("keydown", onDocKey, true);
86
+ document.addEventListener("pointerdown", onDocPointer, true);
63
87
  rail.addEventListener("pointerenter", onEnter);
64
88
  rail.addEventListener("pointerleave", onLeave);
65
89
  rail.addEventListener("focusin", onFocusIn);
@@ -67,6 +91,9 @@ export function ProductRail({ current, organization, accountHref, catalog, onExp
67
91
  rail.addEventListener("keydown", onKey);
68
92
  return () => {
69
93
  cancel();
94
+ observer.disconnect();
95
+ document.removeEventListener("keydown", onDocKey, true);
96
+ document.removeEventListener("pointerdown", onDocPointer, true);
70
97
  rail.removeEventListener("pointerenter", onEnter);
71
98
  rail.removeEventListener("pointerleave", onLeave);
72
99
  rail.removeEventListener("focusin", onFocusIn);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hgdc/ui",
3
- "version": "0.12.0",
3
+ "version": "0.12.2",
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",
package/styles.css CHANGED
@@ -485,17 +485,16 @@
485
485
  opacity: 0;
486
486
  animation: family-loader-in var(--duration-base) var(--ease-out) var(--family-loader-delay, 200ms) forwards;
487
487
  }
488
- .family-loader [data-piece] {
489
- animation: 1.8s infinite both;
490
- }
488
+ /* Le nom dans chaque raccourci : sans lui, lightningcss (minification de Vite) réduit
489
+ `animation: 1.8s infinite both` à `animation: none`. */
491
490
  .family-loader [data-piece="left"] {
492
- animation-name: family-loader-left;
491
+ animation: family-loader-left 1.8s infinite both;
493
492
  }
494
493
  .family-loader [data-piece="bar"] {
495
- animation-name: family-loader-bar;
494
+ animation: family-loader-bar 1.8s infinite both;
496
495
  }
497
496
  .family-loader [data-piece="right"] {
498
- animation-name: family-loader-right;
497
+ animation: family-loader-right 1.8s infinite both;
499
498
  }
500
499
  @keyframes family-loader-in {
501
500
  to {