@hgdc/ui 0.24.0 → 0.25.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/dist/account-menu.d.ts +1 -1
- package/dist/account-menu.js +4 -13
- package/dist/theme-switch.d.ts +8 -0
- package/dist/theme-switch.js +42 -0
- package/package.json +1 -1
package/dist/account-menu.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export type AccountUser = {
|
|
|
20
20
|
image?: string | null;
|
|
21
21
|
};
|
|
22
22
|
/**
|
|
23
|
-
* Le menu du compte, en bas de la barre latérale : profil, sécurité, thème, raccourcis, autres
|
|
23
|
+
* Le menu du compte, en bas de la barre latérale : profil, sécurité, choix du thème en trois icônes, raccourcis, autres
|
|
24
24
|
* produits HGDC, déconnexion. `row` : avatar, nom et adresse (l'avatar seul en rail) ; `avatar` :
|
|
25
25
|
* l'avatar seul, pour une barre du haut. Le produit passe les actions ; le thème passe par
|
|
26
26
|
* `ThemeProvider`.
|
package/dist/account-menu.js
CHANGED
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import ComputerIcon from "@hugeicons/core-free-icons/ComputerIcon";
|
|
3
2
|
import DashboardSquare01Icon from "@hugeicons/core-free-icons/DashboardSquare01Icon";
|
|
4
3
|
import KeyboardIcon from "@hugeicons/core-free-icons/KeyboardIcon";
|
|
5
4
|
import Logout01Icon from "@hugeicons/core-free-icons/Logout01Icon";
|
|
6
|
-
import Moon02Icon from "@hugeicons/core-free-icons/Moon02Icon";
|
|
7
5
|
import SecurityLockIcon from "@hugeicons/core-free-icons/SecurityLockIcon";
|
|
8
|
-
import Sun03Icon from "@hugeicons/core-free-icons/Sun03Icon";
|
|
9
6
|
import Tick02Icon from "@hugeicons/core-free-icons/Tick02Icon";
|
|
10
7
|
import UserIcon from "@hugeicons/core-free-icons/UserIcon";
|
|
11
8
|
import { HugeiconsIcon } from "@hugeicons/react";
|
|
12
9
|
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
|
|
13
10
|
import { Avatar, AvatarFallback, AvatarImage } from "./avatar.js";
|
|
14
|
-
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel,
|
|
11
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, } from "./dropdown-menu.js";
|
|
15
12
|
import { initials } from "./initials.js";
|
|
16
13
|
import { MENU_LINK_ITEM } from "./org-switcher.js";
|
|
17
14
|
import { ProductBadge } from "./product-badge.js";
|
|
18
15
|
import { productMenuEntries } from "./products.js";
|
|
19
16
|
import { useLeaveOnClick, useProductRailShown } from "./shell-placement.js";
|
|
20
|
-
import {
|
|
21
|
-
const THEMES = ["light", "dark", "system"];
|
|
17
|
+
import { ThemeSwitch } from "./theme-switch.js";
|
|
22
18
|
/**
|
|
23
|
-
* Le menu du compte, en bas de la barre latérale : profil, sécurité, thème, raccourcis, autres
|
|
19
|
+
* Le menu du compte, en bas de la barre latérale : profil, sécurité, choix du thème en trois icônes, raccourcis, autres
|
|
24
20
|
* produits HGDC, déconnexion. `row` : avatar, nom et adresse (l'avatar seul en rail) ; `avatar` :
|
|
25
21
|
* l'avatar seul, pour une barre du haut. Le produit passe les actions ; le thème passe par
|
|
26
22
|
* `ThemeProvider`.
|
|
@@ -31,15 +27,10 @@ const THEMES = ["light", "dark", "system"];
|
|
|
31
27
|
* liste ; `products={[]}` la retire.
|
|
32
28
|
*/
|
|
33
29
|
export function AccountMenu({ user, onProfile, onSecurity, onShortcuts, onSignOut, products: productsProp, product: currentProduct, accountHref, children, variant = "row", }) {
|
|
34
|
-
const { theme, setTheme } = useTheme();
|
|
35
30
|
const products = productsProp ?? productMenuEntries({ current: currentProduct, accountHref });
|
|
36
31
|
const railShown = useProductRailShown();
|
|
37
32
|
const leaveOnClick = useLeaveOnClick();
|
|
38
|
-
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(
|
|
39
|
-
const next = THEMES.find((t) => t === value);
|
|
40
|
-
if (next)
|
|
41
|
-
setTheme(next);
|
|
42
|
-
}, children: [_jsx(DropdownMenuRadioItem, { value: "light", children: "Clair" }), _jsx(DropdownMenuRadioItem, { value: "dark", children: "Sombre" }), _jsx(DropdownMenuRadioItem, { value: "system", children: "Syst\u00E8me" })] }) })] }), onShortcuts ? (_jsxs(DropdownMenuItem, { onClick: onShortcuts, children: [_jsx(HugeiconsIcon, { icon: KeyboardIcon, "aria-hidden": true }), "Raccourcis clavier", _jsx(DropdownMenuShortcut, { children: "?" })] })) : null, products.length ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, { className: railShown ? "lg:hidden" : undefined }), _jsxs(DropdownMenuGroup, { className: railShown ? "lg:hidden" : undefined, children: [_jsx(DropdownMenuLabel, { children: "Produits" }), products.map((product) => product.current ? (_jsxs(DropdownMenuItem, { disabled: true, className: "data-disabled:opacity-100", children: [_jsx(ProductMenuIcon, { product: product }), _jsx("span", { className: "flex-1", children: product.name }), _jsx(HugeiconsIcon, { icon: Tick02Icon, "aria-label": "Produit ouvert" })] }, product.name)) : (_jsxs(MenuPrimitive.LinkItem, { href: product.href, onClick: leaveOnClick, className: MENU_LINK_ITEM, children: [_jsx(ProductMenuIcon, { product: product }), _jsx("span", { className: "flex-1", children: product.name }), product.hint ? (_jsx("span", { className: "text-xs font-normal text-muted-foreground", children: product.hint })) : null] }, product.name)))] })] })) : null, _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { onClick: onSignOut, children: [_jsx(HugeiconsIcon, { icon: Logout01Icon, "aria-hidden": true }), "Se d\u00E9connecter"] })] })] }));
|
|
33
|
+
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, _jsx(ThemeSwitch, {}), onShortcuts ? (_jsxs(DropdownMenuItem, { onClick: onShortcuts, children: [_jsx(HugeiconsIcon, { icon: KeyboardIcon, "aria-hidden": true }), "Raccourcis clavier", _jsx(DropdownMenuShortcut, { children: "?" })] })) : null, products.length ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, { className: railShown ? "lg:hidden" : undefined }), _jsxs(DropdownMenuGroup, { className: railShown ? "lg:hidden" : undefined, children: [_jsx(DropdownMenuLabel, { children: "Produits" }), products.map((product) => product.current ? (_jsxs(DropdownMenuItem, { disabled: true, className: "data-disabled:opacity-100", children: [_jsx(ProductMenuIcon, { product: product }), _jsx("span", { className: "flex-1", children: product.name }), _jsx(HugeiconsIcon, { icon: Tick02Icon, "aria-label": "Produit ouvert" })] }, product.name)) : (_jsxs(MenuPrimitive.LinkItem, { href: product.href, onClick: leaveOnClick, className: MENU_LINK_ITEM, children: [_jsx(ProductMenuIcon, { product: product }), _jsx("span", { className: "flex-1", children: product.name }), product.hint ? (_jsx("span", { className: "text-xs font-normal text-muted-foreground", children: product.hint })) : null] }, product.name)))] })] })) : null, _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { onClick: onSignOut, children: [_jsx(HugeiconsIcon, { icon: Logout01Icon, "aria-hidden": true }), "Se d\u00E9connecter"] })] })] }));
|
|
43
34
|
}
|
|
44
35
|
function ProductMenuIcon({ product }) {
|
|
45
36
|
if (product.badge)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Le choix du thème en trois icônes dans une pilule (Système, Clair, Sombre), à poser dans un
|
|
3
|
+
* menu : chaque choix est une entrée du menu, qui ne se ferme pas. Flèches gauche et droite
|
|
4
|
+
* passent d'un choix à l'autre et l'appliquent ; le thème vient de `ThemeProvider`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function ThemeSwitch({ className }: {
|
|
7
|
+
className?: string;
|
|
8
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import ComputerIcon from "@hugeicons/core-free-icons/ComputerIcon";
|
|
3
|
+
import Moon02Icon from "@hugeicons/core-free-icons/Moon02Icon";
|
|
4
|
+
import Sun03Icon from "@hugeicons/core-free-icons/Sun03Icon";
|
|
5
|
+
import { HugeiconsIcon } from "@hugeicons/react";
|
|
6
|
+
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
|
|
7
|
+
import { useRef } from "react";
|
|
8
|
+
import { cn } from "./cn.js";
|
|
9
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.js";
|
|
10
|
+
import { useTheme } from "./theme.js";
|
|
11
|
+
const CHOICES = [
|
|
12
|
+
{ value: "system", label: "Système", icon: ComputerIcon },
|
|
13
|
+
{ value: "light", label: "Clair", icon: Sun03Icon },
|
|
14
|
+
{ value: "dark", label: "Sombre", icon: Moon02Icon },
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Le choix du thème en trois icônes dans une pilule (Système, Clair, Sombre), à poser dans un
|
|
18
|
+
* menu : chaque choix est une entrée du menu, qui ne se ferme pas. Flèches gauche et droite
|
|
19
|
+
* passent d'un choix à l'autre et l'appliquent ; le thème vient de `ThemeProvider`.
|
|
20
|
+
*/
|
|
21
|
+
export function ThemeSwitch({ className }) {
|
|
22
|
+
const { theme, setTheme } = useTheme();
|
|
23
|
+
const group = useRef(null);
|
|
24
|
+
function onKeyDown(event) {
|
|
25
|
+
const step = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
|
|
26
|
+
if (step === 0)
|
|
27
|
+
return;
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
event.stopPropagation();
|
|
30
|
+
const index = CHOICES.findIndex((choice) => choice.value === theme);
|
|
31
|
+
const next = CHOICES[(index + step + CHOICES.length) % CHOICES.length];
|
|
32
|
+
if (!next)
|
|
33
|
+
return;
|
|
34
|
+
setTheme(next.value);
|
|
35
|
+
group.current?.querySelector(`[data-theme-choice="${next.value}"]`)?.focus();
|
|
36
|
+
}
|
|
37
|
+
const current = CHOICES.find((choice) => choice.value === theme);
|
|
38
|
+
return (_jsxs("div", { className: cn("flex items-center gap-2.5 px-3 py-[3px] text-sm font-medium max-lg:min-h-11 coarse:min-h-11", className), children: [_jsx(HugeiconsIcon, { icon: current?.icon ?? ComputerIcon, "aria-hidden": true, className: "size-4 shrink-0" }), _jsx("span", { className: "flex-1", children: "Th\u00E8me" }), _jsx("div", { ref: group, role: "radiogroup", "aria-label": "Th\u00E8me", tabIndex: -1, onKeyDown: onKeyDown, className: "flex w-fit items-center gap-0.5 rounded-full border border-border p-0.5", children: CHOICES.map(({ value, label, icon }) => {
|
|
39
|
+
const active = theme === value;
|
|
40
|
+
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx(MenuPrimitive.Item, { closeOnClick: false, role: "radio", "aria-checked": active, "aria-label": label, "data-theme-choice": value, onClick: () => setTheme(value), className: cn("touch-target grid size-6 cursor-default place-items-center rounded-full outline-none", "transition-colors duration-(--duration-fast) ease-out focus-visible:ring-2 focus-visible:ring-ring/50", active ? "bg-muted text-foreground" : "text-muted-foreground hover:text-foreground") }), children: _jsx(HugeiconsIcon, { icon: icon, "aria-hidden": true, className: "size-3.5" }) }), _jsx(TooltipContent, { children: label })] }, value));
|
|
41
|
+
}) })] }));
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hgdc/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.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",
|