@hgdc/ui 0.66.0 → 0.66.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.
package/dist/app-shell.js CHANGED
@@ -102,7 +102,7 @@ export function AppShell({ product, organization, productRail: productRailOption
102
102
  useAssistantShortcut(assistant);
103
103
  const otherView = viewOption === undefined ? null : _jsx(OtherViewLink, { product: product, view: viewOption });
104
104
  // Dans la barre de Basalte, seul Réglages reste au-dessus du compte : le reste passe dans son menu.
105
- const footer = (_jsxs(_Fragment, { children: [account !== undefined ? sidebarFooter : null, productRail && project ? (_jsx("div", { className: "px-3 pb-1", children: _jsxs("a", { href: project.settingsHref, className: SIDEBAR_ITEM, children: [_jsx(HugeiconsIcon, { icon: Settings02Icon, strokeWidth: 1.8, "aria-hidden": true, className: "size-5 shrink-0 lg:size-[1.125rem]" }), _jsx("span", { className: "truncate lg:rail:sr-only", children: "R\u00E9glages du projet" })] }) })) : null, _jsx(ShellCommonLinks, { links: productRail ? { ...(links?.settings ? { settings: links.settings } : {}), status: false } : (links ?? {}), statusPageUrl: statusPageUrl, otherView: productRail ? null : otherView }), account ?? sidebarFooter] }));
105
+ const footer = (_jsxs(_Fragment, { children: [account !== undefined ? sidebarFooter : null, productRail && project ? (_jsx("div", { className: "pb-1", children: _jsxs("a", { href: project.settingsHref, className: SIDEBAR_ITEM, children: [_jsx(HugeiconsIcon, { icon: Settings02Icon, strokeWidth: 1.8, "aria-hidden": true, className: "size-5 shrink-0 lg:size-[1.125rem]" }), _jsx("span", { className: "truncate lg:rail:sr-only", children: "R\u00E9glages du projet" })] }) })) : null, _jsx(ShellCommonLinks, { links: productRail ? { ...(links?.settings ? { settings: links.settings } : {}), status: false } : (links ?? {}), statusPageUrl: statusPageUrl, otherView: productRail ? null : otherView }), account ?? sidebarFooter] }));
106
106
  const menuLinks = productRail
107
107
  ? {
108
108
  documentation: links?.documentation,
@@ -38,6 +38,7 @@ export function DestructiveDialog({ open, onOpenChange, title, question, consequ
38
38
  const view = shown.current;
39
39
  const [typed, setTyped] = useState("");
40
40
  const [pending, setPending] = useState(false);
41
+ const confirmButton = useRef(null);
41
42
  const ready = !pending && (view.name === undefined || matchesConfirmation(typed, view.name));
42
43
  const [wasOpen, setWasOpen] = useState(open);
43
44
  if (open !== wasOpen) {
@@ -55,6 +56,11 @@ export function DestructiveDialog({ open, onOpenChange, title, question, consequ
55
56
  }
56
57
  catch (error) {
57
58
  toast.error(formatError(error));
59
+ // Le bouton se désactive pendant l'envoi et le focus part sur `body`, hors de la boîte restée ouverte.
60
+ requestAnimationFrame(() => {
61
+ if (document.activeElement === document.body)
62
+ confirmButton.current?.focus();
63
+ });
58
64
  }
59
65
  finally {
60
66
  setPending(false);
@@ -69,5 +75,5 @@ export function DestructiveDialog({ open, onOpenChange, title, question, consequ
69
75
  }, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: view.title }), _jsxs(DialogDescription, { render: _jsx("div", {}), className: "grid gap-1 text-[15px]", children: [_jsx("p", { children: view.question }), view.consequence ? _jsx("p", { children: view.consequence }) : null, _jsx("p", { className: "font-semibold text-destructive", children: "Impossible de revenir en arri\u00E8re." })] })] }), view.name !== undefined ? (_jsxs("form", { className: "grid gap-3", onSubmit: (event) => {
70
76
  event.preventDefault();
71
77
  void confirm();
72
- }, children: [_jsxs("p", { className: "text-[15px] leading-7 text-muted-foreground", children: ["Tape ", _jsx(NameChip, { name: view.name }), " pour confirmer."] }), _jsx(Input, { "aria-label": `Tape ${view.name} pour confirmer`, autoFocus: true, autoComplete: "off", autoCapitalize: "off", spellCheck: false, value: typed, onChange: (event) => setTyped(event.target.value), placeholder: view.name, "aria-invalid": typed.length > 0 && !view.name.startsWith(typed) ? true : undefined })] })) : null, _jsxs(DialogFooter, { className: "sm:justify-start", children: [_jsxs(Button, { variant: "destructive", isLoading: pending, disabled: !ready, onClick: () => void confirm(), children: [view.confirmLabel ?? view.title, _jsx(Kbd, { "aria-hidden": true, className: "ml-1 hidden bg-current/10 text-current lg:inline-flex", children: mod }), _jsx(Kbd, { "aria-hidden": true, className: "hidden bg-current/10 text-current lg:inline-flex", children: "\u21B5" })] }), _jsxs(Button, { variant: "outline", disabled: pending, onClick: () => onOpenChange(false), children: ["Annuler", _jsx(Kbd, { "aria-hidden": true, className: "ml-1 hidden lg:inline-flex", children: "\u00C9chap" })] })] })] }) }));
78
+ }, children: [_jsxs("p", { className: "text-[15px] leading-7 text-muted-foreground", children: ["Tape ", _jsx(NameChip, { name: view.name }), " pour confirmer."] }), _jsx(Input, { "aria-label": `Tape ${view.name} pour confirmer`, autoFocus: true, autoComplete: "off", autoCapitalize: "off", spellCheck: false, value: typed, onChange: (event) => setTyped(event.target.value), placeholder: view.name, "aria-invalid": typed.length > 0 && !view.name.startsWith(typed) ? true : undefined })] })) : null, _jsxs(DialogFooter, { className: "sm:justify-start", children: [_jsxs(Button, { ref: confirmButton, variant: "destructive", isLoading: pending, disabled: !ready, onClick: () => void confirm(), children: [view.confirmLabel ?? view.title, _jsx(Kbd, { "aria-hidden": true, className: "ml-1 hidden bg-current/10 text-current lg:inline-flex", children: mod }), _jsx(Kbd, { "aria-hidden": true, className: "hidden bg-current/10 text-current lg:inline-flex", children: "\u21B5" })] }), _jsxs(Button, { variant: "outline", disabled: pending, onClick: () => onOpenChange(false), children: ["Annuler", _jsx(Kbd, { "aria-hidden": true, className: "ml-1 hidden lg:inline-flex", children: "\u00C9chap" })] })] })] }) }));
73
79
  }
package/dist/products.js CHANGED
@@ -106,7 +106,8 @@ export function isProductOpen(item) {
106
106
  export function productMenuEntries(options = {}) {
107
107
  const current = options.current?.toLowerCase();
108
108
  const { manager, others } = managerFirst(options.catalog ?? HGDC_PRODUCTS);
109
- const head = manager && options.accountHref ? { ...manager, entryHref: options.accountHref } : manager;
109
+ const local = localManagerHref(options.accountHref);
110
+ const head = manager && local ? { ...manager, entryHref: local } : manager;
110
111
  return [...(head ? [head] : []), ...others.filter(isProductOpen)].map((item) => ({
111
112
  name: item.name,
112
113
  href: item.entryHref,
@@ -159,12 +160,20 @@ export function shellProductEntries(options = {}) {
159
160
  };
160
161
  const { manager, others } = managerFirst(options.catalog ?? HGDC_PRODUCTS);
161
162
  const head = manager ?? HGDC_MANAGER;
163
+ const local = localManagerHref(options.accountHref);
162
164
  return {
163
165
  products: others.filter(isProductOpen).map((item) => entry(item, item.entryHref, true)),
164
- account: entry(head, options.accountHref ?? head.entryHref, options.accountHref === undefined),
166
+ account: entry(head, local ?? head.entryHref, local === undefined),
165
167
  };
166
168
  }
167
169
  /** Manager du catalogue, s'il y est, sorti des autres produits : il prend la place d'Account en tête. */
170
+ /**
171
+ * L'adresse reçue, seulement pour un Manager local : les consoles passent aussi l'adresse d'Account en
172
+ * production, et `account.hgdc.ch` redirige vers ID, pas vers Manager.
173
+ */
174
+ function localManagerHref(accountHref) {
175
+ return accountHref?.startsWith("https://account.hgdc.ch") ? undefined : accountHref;
176
+ }
168
177
  function managerFirst(catalog) {
169
178
  const manager = catalog.find((item) => item.badge === "manager" && isProductOpen(item));
170
179
  return { manager, others: catalog.filter((item) => item !== manager) };
@@ -1,5 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useId } from "react";
2
+ import { useId, useRef } from "react";
3
3
  import { Button } from "./button.js";
4
4
  import { cn } from "./cn.js";
5
5
  /**
@@ -26,8 +26,9 @@ import { cn } from "./cn.js";
26
26
  export function SettingsSection({ title, description, children, footer, footerNote, tone = "default", onSave, dirty = true, saving = false, saveLabel = "Enregistrer", className, }) {
27
27
  const titleId = useId();
28
28
  const descriptionId = useId();
29
+ const heading = useRef(null);
29
30
  const hasFooter = Boolean(footer || footerNote || onSave);
30
- const body = (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid gap-4 p-5 sm:p-6", children: [_jsxs("div", { children: [_jsx("h2", { id: titleId, className: "text-base font-semibold text-balance", children: title }), description ? (_jsx("p", { id: descriptionId, className: "mt-1 max-w-prose text-sm text-pretty text-muted-foreground", children: description })) : null] }), children] }), hasFooter ? (_jsxs("div", { "data-slot": "settings-section-footer", className: "flex flex-wrap items-center justify-end gap-3 border-t border-border bg-muted/40 px-5 py-3 sm:px-6", children: [footerNote ? _jsx("p", { className: "mr-auto text-sm text-muted-foreground", children: footerNote }) : null, footer, onSave ? (_jsx(Button, { type: "submit", size: "sm", disabled: !dirty, isLoading: saving, children: saveLabel })) : null] })) : null] }));
31
+ const body = (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid gap-4 p-5 sm:p-6", children: [_jsxs("div", { children: [_jsx("h2", { ref: heading, id: titleId, tabIndex: -1, className: "text-base font-semibold text-balance outline-none", children: title }), description ? (_jsx("p", { id: descriptionId, className: "mt-1 max-w-prose text-sm text-pretty text-muted-foreground", children: description })) : null] }), children] }), hasFooter ? (_jsxs("div", { "data-slot": "settings-section-footer", className: "flex flex-wrap items-center justify-end gap-3 border-t border-border bg-muted/40 px-5 py-3 sm:px-6", children: [footerNote ? _jsx("p", { className: "mr-auto text-sm text-muted-foreground", children: footerNote }) : null, footer, onSave ? (_jsx(Button, { type: "submit", size: "sm", disabled: !dirty, isLoading: saving, children: saveLabel })) : null] })) : null] }));
31
32
  const shared = {
32
33
  "data-slot": "settings-section",
33
34
  "data-tone": tone,
@@ -41,7 +42,14 @@ export function SettingsSection({ title, description, children, footer, footerNo
41
42
  event.preventDefault();
42
43
  if (!dirty || saving)
43
44
  return;
44
- void onSave();
45
+ // Le bouton se désactive pendant l'envoi et le focus part sur `body` : il revient au titre du bloc.
46
+ // Un focus resté dans un champ (Entrée) n'y est pas déplacé.
47
+ void Promise.resolve(onSave()).finally(() => {
48
+ requestAnimationFrame(() => {
49
+ if (document.activeElement === document.body)
50
+ heading.current?.focus();
51
+ });
52
+ });
45
53
  };
46
54
  // Un `<form>` nommé par son titre a le rôle « form » : il apparaît dans les repères du lecteur d'écran.
47
55
  return (_jsx("form", { ...shared, onSubmit: submit, children: body }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hgdc/ui",
3
- "version": "0.66.0",
3
+ "version": "0.66.2",
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",