@hgdc/ui 0.54.0 → 0.55.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/avatar.js +2 -12
- package/dist/initials.d.ts +7 -0
- package/dist/initials.js +14 -0
- package/dist/org-switcher.d.ts +8 -2
- package/dist/org-switcher.js +13 -9
- package/dist/shell-memory.d.ts +5 -0
- package/dist/shell-memory.js +18 -2
- package/package.json +1 -1
- package/styles.css +2 -2
package/dist/avatar.js
CHANGED
|
@@ -2,23 +2,13 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cn } from "./cn.js";
|
|
5
|
-
import { avatarTone } from "./initials.js";
|
|
5
|
+
import { AVATAR_TONE_CLASSES, avatarTone } from "./initials.js";
|
|
6
6
|
function Avatar({ className, size = "default", ...props }) {
|
|
7
7
|
return (_jsx(AvatarPrimitive.Root, { "data-slot": "avatar", "data-size": size, className: cn("group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten", className), ...props }));
|
|
8
8
|
}
|
|
9
9
|
function AvatarImage({ className, ...props }) {
|
|
10
10
|
return (_jsx(AvatarPrimitive.Image, { "data-slot": "avatar-image", className: cn("aspect-square size-full rounded-full object-cover", className), ...props }));
|
|
11
11
|
}
|
|
12
|
-
/** Lavis et encre de chaque teinte (`--avatar-<n>`, `styles.css`), à 4,5:1 en clair et en sombre. */
|
|
13
|
-
const TONE_CLASSES = {
|
|
14
|
-
1: "bg-avatar-1 text-avatar-1-ink",
|
|
15
|
-
2: "bg-avatar-2 text-avatar-2-ink",
|
|
16
|
-
3: "bg-avatar-3 text-avatar-3-ink",
|
|
17
|
-
4: "bg-avatar-4 text-avatar-4-ink",
|
|
18
|
-
5: "bg-avatar-5 text-avatar-5-ink",
|
|
19
|
-
6: "bg-avatar-6 text-avatar-6-ink",
|
|
20
|
-
7: "bg-avatar-7 text-avatar-7-ink",
|
|
21
|
-
};
|
|
22
12
|
/**
|
|
23
13
|
* Ce qui s'affiche sans photo (la photo, `AvatarImage`, reste prioritaire). Des initiales en
|
|
24
14
|
* texte prennent une teinte de la marque, stable : celle de `colorKey` (identifiant ou adresse
|
|
@@ -28,7 +18,7 @@ const TONE_CLASSES = {
|
|
|
28
18
|
function AvatarFallback({ className, colorKey, children, ...props }) {
|
|
29
19
|
const key = colorKey === undefined ? (typeof children === "string" ? children : "") : (colorKey ?? "");
|
|
30
20
|
const tone = key.trim() ? avatarTone(key) : null;
|
|
31
|
-
return (_jsx(AvatarPrimitive.Fallback, { "data-slot": "avatar-fallback", "data-tone": tone ?? undefined, className: cn("flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs", tone ? cn("font-semibold",
|
|
21
|
+
return (_jsx(AvatarPrimitive.Fallback, { "data-slot": "avatar-fallback", "data-tone": tone ?? undefined, className: cn("flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs", tone ? cn("font-semibold", AVATAR_TONE_CLASSES[tone]) : "bg-muted text-muted-foreground", className), ...props, children: children }));
|
|
32
22
|
}
|
|
33
23
|
function AvatarBadge({ className, ...props }) {
|
|
34
24
|
return (_jsx("span", { "data-slot": "avatar-badge", className: cn("absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none", "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden", "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2", "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2", className), ...props }));
|
package/dist/initials.d.ts
CHANGED
|
@@ -15,3 +15,10 @@ export type AvatarTone = 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
|
|
15
15
|
* toujours la même teinte, dans tous les produits. FNV-1a 32 bits, puis modulo 7.
|
|
16
16
|
*/
|
|
17
17
|
export declare function avatarTone(key: string): AvatarTone;
|
|
18
|
+
/** Lavis et encre de chaque teinte (`--avatar-<n>`, `styles.css`), à 4,5:1 en clair et en sombre. */
|
|
19
|
+
export declare const AVATAR_TONE_CLASSES: Readonly<Record<AvatarTone, string>>;
|
|
20
|
+
/** Teinte de la marque d'une organisation (`OrgMark`, mémoire de la coque) : celle de son identifiant, sinon de son nom. */
|
|
21
|
+
export declare function orgTone(org: {
|
|
22
|
+
id?: string | undefined;
|
|
23
|
+
name: string;
|
|
24
|
+
}): AvatarTone;
|
package/dist/initials.js
CHANGED
|
@@ -36,3 +36,17 @@ export function avatarTone(key) {
|
|
|
36
36
|
}
|
|
37
37
|
return ((hash % AVATAR_TONES) + 1);
|
|
38
38
|
}
|
|
39
|
+
/** Lavis et encre de chaque teinte (`--avatar-<n>`, `styles.css`), à 4,5:1 en clair et en sombre. */
|
|
40
|
+
export const AVATAR_TONE_CLASSES = {
|
|
41
|
+
1: "bg-avatar-1 text-avatar-1-ink",
|
|
42
|
+
2: "bg-avatar-2 text-avatar-2-ink",
|
|
43
|
+
3: "bg-avatar-3 text-avatar-3-ink",
|
|
44
|
+
4: "bg-avatar-4 text-avatar-4-ink",
|
|
45
|
+
5: "bg-avatar-5 text-avatar-5-ink",
|
|
46
|
+
6: "bg-avatar-6 text-avatar-6-ink",
|
|
47
|
+
7: "bg-avatar-7 text-avatar-7-ink",
|
|
48
|
+
};
|
|
49
|
+
/** Teinte de la marque d'une organisation (`OrgMark`, mémoire de la coque) : celle de son identifiant, sinon de son nom. */
|
|
50
|
+
export function orgTone(org) {
|
|
51
|
+
return avatarTone(org.id?.trim() ? org.id : org.name);
|
|
52
|
+
}
|
package/dist/org-switcher.d.ts
CHANGED
|
@@ -19,9 +19,15 @@ export type Organization = {
|
|
|
19
19
|
};
|
|
20
20
|
/** Un lien dans un menu, à l'allure d'un `DropdownMenuItem`. */
|
|
21
21
|
export declare const MENU_LINK_ITEM: string;
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Marque d'une organisation : son logo, ou ses initiales (`initials`) sur un carré arrondi, dans la teinte de
|
|
24
|
+
* son identifiant (`avatarTone`) : deux organisations aux mêmes initiales ne se confondent pas. Sans `id`, la
|
|
25
|
+
* teinte du nom.
|
|
26
|
+
*/
|
|
23
27
|
export declare function OrgMark({ org, className, }: {
|
|
24
|
-
org: Pick<Organization, "name" | "logo"
|
|
28
|
+
org: Pick<Organization, "name" | "logo"> & {
|
|
29
|
+
id?: string;
|
|
30
|
+
};
|
|
25
31
|
className?: string;
|
|
26
32
|
}): React.JSX.Element;
|
|
27
33
|
/**
|
package/dist/org-switcher.js
CHANGED
|
@@ -7,7 +7,7 @@ import { Menu as MenuPrimitive } from "@base-ui/react/menu";
|
|
|
7
7
|
import { useEffect } from "react";
|
|
8
8
|
import { Badge } from "./badge.js";
|
|
9
9
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "./dropdown-menu.js";
|
|
10
|
-
import { initials } from "./initials.js";
|
|
10
|
+
import { AVATAR_TONE_CLASSES, initials, orgTone } from "./initials.js";
|
|
11
11
|
import { rememberShellOrganization } from "./shell-memory.js";
|
|
12
12
|
import { useShellPlacement } from "./shell-placement.js";
|
|
13
13
|
import { cn } from "./cn.js";
|
|
@@ -25,11 +25,15 @@ export function RoleBadge({ orgRole: role, title, className, }) {
|
|
|
25
25
|
}
|
|
26
26
|
/** Un lien dans un menu, à l'allure d'un `DropdownMenuItem`. */
|
|
27
27
|
export const MENU_LINK_ITEM = "relative flex cursor-default items-center gap-2.5 rounded-xl px-3 py-2 text-sm font-medium outline-hidden select-none focus:bg-muted focus:text-foreground data-highlighted:bg-muted max-lg:min-h-11 coarse:min-h-11 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4";
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Marque d'une organisation : son logo, ou ses initiales (`initials`) sur un carré arrondi, dans la teinte de
|
|
30
|
+
* son identifiant (`avatarTone`) : deux organisations aux mêmes initiales ne se confondent pas. Sans `id`, la
|
|
31
|
+
* teinte du nom.
|
|
32
|
+
*/
|
|
29
33
|
export function OrgMark({ org, className, }) {
|
|
30
34
|
if (org.logo)
|
|
31
35
|
return _jsx("img", { src: org.logo, alt: "", className: cn("size-8 shrink-0 rounded-lg object-cover", className) });
|
|
32
|
-
return (_jsx("span", { "aria-hidden": true, className: cn("grid size-8 shrink-0 place-items-center rounded-lg
|
|
36
|
+
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) }));
|
|
33
37
|
}
|
|
34
38
|
/**
|
|
35
39
|
* L'organisation active, et le menu pour en changer ou en créer une. En haut de la barre des
|
|
@@ -46,17 +50,17 @@ export function OrgSwitcher({ organizations, activeId, onSelect, createHref, onC
|
|
|
46
50
|
const activeLogo = active?.logo;
|
|
47
51
|
useEffect(() => {
|
|
48
52
|
if (activeName !== undefined)
|
|
49
|
-
rememberShellOrganization({ name: activeName, logo: activeLogo });
|
|
50
|
-
}, [activeName, activeLogo]);
|
|
53
|
+
rememberShellOrganization({ id: activeId, name: activeName, logo: activeLogo });
|
|
54
|
+
}, [activeId, activeName, activeLogo]);
|
|
51
55
|
if (!active)
|
|
52
56
|
return null;
|
|
53
57
|
// En haut de la barre des produits : la marque seule dans 40 px, le nom quand la barre s'élargit.
|
|
54
58
|
const inRail = placement === "product-rail";
|
|
55
|
-
return (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { render: _jsx("button", { type: "button", "aria-label": `Organisation ${active.name}, changer d'organisation`, className: cn("flex h-10 w-full min-w-0 items-center gap-2.5 rounded-xl px-1.5 text-left coarse: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
|
|
56
|
-
? "shrink-0 gap-3 px-1 hover:bg-shell-hover aria-expanded:bg-shell-hover"
|
|
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"
|
|
57
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
|
|
58
62
|
? "opacity-0 transition-opacity duration-(--duration-fast) ease-out group-data-expanded/rail:opacity-100 motion-reduce:transition-none"
|
|
59
|
-
: "lg:rail:hidden"), children: [_jsx("span", { className: "block
|
|
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
|
|
60
64
|
? "opacity-0 transition-opacity duration-(--duration-fast) group-data-expanded/rail:opacity-100"
|
|
61
|
-
: "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
|
|
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] })] }));
|
|
62
66
|
}
|
package/dist/shell-memory.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare const SHELL_SWITCH_MAX_AGE_MS = 10000;
|
|
|
9
9
|
export type ShellOrganization = {
|
|
10
10
|
initials: string;
|
|
11
11
|
logo?: string;
|
|
12
|
+
tone?: number;
|
|
12
13
|
};
|
|
13
14
|
export type ShellBootConfig = {
|
|
14
15
|
/** Clé du rail de la barre latérale (`sidebar`), valeur `rail`. */
|
|
@@ -21,6 +22,8 @@ export type ShellBootConfig = {
|
|
|
21
22
|
origins: ReadonlyArray<readonly [string, string, string]>;
|
|
22
23
|
/** Domaine des produits sur leur sous-domaine (`hgdc.ch` : `mail.hgdc.ch` est Mail). */
|
|
23
24
|
domain: string;
|
|
25
|
+
/** Nombre de teintes d'avatar (`AVATAR_TONES`) : la teinte retenue de l'organisation est un entier de 1 à `tones`. */
|
|
26
|
+
tones: number;
|
|
24
27
|
};
|
|
25
28
|
type BootWindow = {
|
|
26
29
|
document: {
|
|
@@ -46,11 +49,13 @@ export declare const SHELL_BOOT_CONFIG: ShellBootConfig;
|
|
|
46
49
|
export declare function shellBootScript(config?: ShellBootConfig): string;
|
|
47
50
|
/** Ce que la coque retient d'une organisation : ses initiales et son logo (`https:` seulement). */
|
|
48
51
|
export declare function shellOrganization(org: {
|
|
52
|
+
id?: string | undefined;
|
|
49
53
|
name: string;
|
|
50
54
|
logo?: string | undefined;
|
|
51
55
|
}): ShellOrganization;
|
|
52
56
|
/** Retient l'organisation affichée (`OrgSwitcher` le fait) et l'applique à la page. */
|
|
53
57
|
export declare function rememberShellOrganization(org: {
|
|
58
|
+
id?: string | undefined;
|
|
54
59
|
name: string;
|
|
55
60
|
logo?: string | undefined;
|
|
56
61
|
}): void;
|
package/dist/shell-memory.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { initials } from "./initials.js";
|
|
1
|
+
import { AVATAR_TONES, initials, orgTone } from "./initials.js";
|
|
2
2
|
import { FAMILY_PRODUCTS } from "./family-svg.js";
|
|
3
3
|
import { PRODUCT_ORIGINS } from "./products.js";
|
|
4
4
|
/*
|
|
@@ -77,11 +77,26 @@ export function shellBoot(config, win) {
|
|
|
77
77
|
root.style.setProperty("--hgdc-org-logo", `url("${logo}")`);
|
|
78
78
|
else
|
|
79
79
|
root.style.removeProperty("--hgdc-org-logo");
|
|
80
|
+
// Teinte d'OrgMark (`--avatar-<n>`), un entier de 1 à 7 seulement.
|
|
81
|
+
const tone = org && Number.isInteger(org.tone) && Number(org.tone) >= 1 && Number(org.tone) <= config.tones
|
|
82
|
+
? Number(org.tone)
|
|
83
|
+
: 0;
|
|
84
|
+
if (tone) {
|
|
85
|
+
const token = `--avatar-${tone}`;
|
|
86
|
+
root.style.setProperty("--hgdc-org-bg", `var(${token})`);
|
|
87
|
+
root.style.setProperty("--hgdc-org-ink", `var(${token}-ink)`);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
root.style.removeProperty("--hgdc-org-bg");
|
|
91
|
+
root.style.removeProperty("--hgdc-org-ink");
|
|
92
|
+
}
|
|
80
93
|
}
|
|
81
94
|
else {
|
|
82
95
|
delete root.dataset.hgdcOrg;
|
|
83
96
|
root.style.removeProperty("--hgdc-org-initials");
|
|
84
97
|
root.style.removeProperty("--hgdc-org-logo");
|
|
98
|
+
root.style.removeProperty("--hgdc-org-bg");
|
|
99
|
+
root.style.removeProperty("--hgdc-org-ink");
|
|
85
100
|
}
|
|
86
101
|
let height = 0;
|
|
87
102
|
try {
|
|
@@ -101,6 +116,7 @@ export const SHELL_BOOT_CONFIG = {
|
|
|
101
116
|
products: FAMILY_PRODUCTS,
|
|
102
117
|
origins: Object.entries(PRODUCT_ORIGINS).map(([word, { origin, basePath }]) => [word, origin, basePath]),
|
|
103
118
|
domain: "hgdc.ch",
|
|
119
|
+
tones: AVATAR_TONES,
|
|
104
120
|
};
|
|
105
121
|
/** Le script en ligne du `<head>` : `shellBoot` et sa configuration, sans dépendance. */
|
|
106
122
|
export function shellBootScript(config = SHELL_BOOT_CONFIG) {
|
|
@@ -122,7 +138,7 @@ function storageSet(storage, key, value) {
|
|
|
122
138
|
}
|
|
123
139
|
/** Ce que la coque retient d'une organisation : ses initiales et son logo (`https:` seulement). */
|
|
124
140
|
export function shellOrganization(org) {
|
|
125
|
-
return { initials: initials(org.name), ...(org.logo ? { logo: org.logo } : {}) };
|
|
141
|
+
return { initials: initials(org.name), ...(org.logo ? { logo: org.logo } : { tone: orgTone(org) }) };
|
|
126
142
|
}
|
|
127
143
|
/** Retient l'organisation affichée (`OrgSwitcher` le fait) et l'applique à la page. */
|
|
128
144
|
export function rememberShellOrganization(org) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hgdc/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.55.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",
|
package/styles.css
CHANGED
|
@@ -896,8 +896,8 @@
|
|
|
896
896
|
background: var(--shell-hover);
|
|
897
897
|
}
|
|
898
898
|
:root[data-hgdc-org] [data-slot="org-placeholder-mark"] {
|
|
899
|
-
background: var(--hgdc-org-logo, var(--foreground)) center / cover no-repeat;
|
|
900
|
-
color: var(--background);
|
|
899
|
+
background: var(--hgdc-org-logo, var(--hgdc-org-bg, var(--foreground))) center / cover no-repeat;
|
|
900
|
+
color: var(--hgdc-org-ink, var(--background));
|
|
901
901
|
}
|
|
902
902
|
:root[data-hgdc-org] [data-slot="org-placeholder-mark"]::before {
|
|
903
903
|
content: var(--hgdc-org-initials, "");
|