@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 +1 -1
- package/dist/destructive-dialog.js +7 -1
- package/dist/products.js +11 -2
- package/dist/settings-section.js +11 -3
- package/package.json +1 -1
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: "
|
|
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
|
|
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,
|
|
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) };
|
package/dist/settings-section.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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",
|