@hgdc/ui 0.8.0 → 0.10.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/README.md +40 -27
- package/dist/access-request.d.ts +1 -1
- package/dist/access-request.js +3 -3
- package/dist/account-menu.d.ts +6 -3
- package/dist/account-menu.js +8 -2
- package/dist/app-shell.d.ts +19 -7
- package/dist/app-shell.js +35 -7
- package/dist/command-menu.d.ts +3 -0
- package/dist/command-menu.js +2 -1
- package/dist/email.js +1 -1
- package/dist/family-svg.d.ts +9 -0
- package/dist/family-svg.js +19 -0
- package/dist/org-switcher.d.ts +3 -1
- package/dist/org-switcher.js +14 -2
- package/dist/product-badge.d.ts +24 -0
- package/dist/product-badge.js +32 -0
- package/dist/product-rail.d.ts +19 -0
- package/dist/product-rail.js +85 -0
- package/dist/products.d.ts +33 -3
- package/dist/products.js +30 -3
- package/dist/shell-placement.d.ts +7 -0
- package/dist/shell-placement.js +5 -0
- package/email-icons/account.png +0 -0
- package/email-icons/analytics.png +0 -0
- package/email-icons/docs.png +0 -0
- package/email-icons/host.png +0 -0
- package/email-icons/mail.png +0 -0
- package/email-icons/portal.png +0 -0
- package/email-icons/status.png +0 -0
- package/family/account.svg +1 -0
- package/family/analytics.svg +1 -0
- package/family/docs.svg +1 -0
- package/family/hfamily-dark.svg +1 -0
- package/family/hfamily-icon.svg +1 -0
- package/family/hfamily.svg +1 -0
- package/family/host.svg +1 -0
- package/family/mail.svg +1 -0
- package/family/portal.svg +1 -0
- package/family/status.svg +1 -0
- package/package.json +4 -2
- package/styles.css +29 -0
package/README.md
CHANGED
|
@@ -39,18 +39,31 @@ Primitives déjà là : `avatar` `badge` `button` `dialog` `drawer` `dropdown-me
|
|
|
39
39
|
|
|
40
40
|
## Coque des consoles
|
|
41
41
|
|
|
42
|
-
| Import | Exemple
|
|
43
|
-
| -------------- |
|
|
44
|
-
| `app-shell` | `<AppShell
|
|
45
|
-
| | `<SidebarNavItem icon={Rocket01Icon} label="Déploiements" render={<Link to="/deployments" />} />`
|
|
46
|
-
| `org-switcher` | `<OrgSwitcher organizations={orgs} activeId={org.id} onSelect={go} createHref="/organisations/nouvelle" />`
|
|
47
|
-
| `account-menu` | `<AccountMenu user={user} onProfile={…} onSecurity={…} onSignOut={signOut} product="Mail" />` : Account et les produits ouverts du catalogue, Mail coché
|
|
48
|
-
| `command-menu` | `<CommandMenu groups={[{ label: "Aller à", items }, { label: "Créer", items }, { label: "Changer", items }]} />` (`Ctrl+K`), `<CommandMenuButton compact="mobile" …/>`
|
|
49
|
-
| `products` | `HGDC_PRODUCTS` (mot, nom, sous-domaine, phrase, icône, état, `entryHref`), `HGDC_ACCOUNT`, `productMenuEntries({ current: "Mail" })` : chaque produit du menu pointe vers son entrée `/auth/hgdc` (ADR 0010) |
|
|
50
|
-
| `kbd` | `<KbdGroup><Kbd>Ctrl</Kbd><Kbd>K</Kbd></KbdGroup>`
|
|
51
|
-
| `page-header` | `<PageHeader title="Domaines" breadcrumbs={[{ label: "Acme", render: <Link to="/o/acme" /> }, { label: "Domaines" }]} actions={<Button>Ajouter</Button>} />`
|
|
52
|
-
|
|
53
|
-
|
|
42
|
+
| Import | Exemple |
|
|
43
|
+
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
44
|
+
| `app-shell` | `<AppShell product="mail" organization={<OrgSwitcher …/>} nav={<SidebarNav label="Projet">…</SidebarNav>} sidebarFooter={<AccountMenu …/>}>` |
|
|
45
|
+
| | `<SidebarNavItem icon={Rocket01Icon} label="Déploiements" render={<Link to="/deployments" />} />` |
|
|
46
|
+
| `org-switcher` | `<OrgSwitcher organizations={orgs} activeId={org.id} onSelect={go} createHref="/organisations/nouvelle" />` |
|
|
47
|
+
| `account-menu` | `<AccountMenu user={user} onProfile={…} onSecurity={…} onSignOut={signOut} product="Mail" />` : Account et les produits ouverts du catalogue, Mail coché |
|
|
48
|
+
| `command-menu` | `<CommandMenu groups={[{ label: "Aller à", items }, { label: "Créer", items }, { label: "Changer", items }]} />` (`Ctrl+K`), `<CommandMenuButton compact="mobile" …/>` |
|
|
49
|
+
| `products` | `HGDC_PRODUCTS` (mot, nom, sous-domaine, phrase, pastille `badge`, icône, état, `entryHref`), `HGDC_ACCOUNT`, `productMenuEntries({ current: "Mail" })` : chaque produit du menu pointe vers son entrée `/auth/hgdc` (ADR 0010) |
|
|
50
|
+
| `kbd` | `<KbdGroup><Kbd>Ctrl</Kbd><Kbd>K</Kbd></KbdGroup>` |
|
|
51
|
+
| `page-header` | `<PageHeader title="Domaines" breadcrumbs={[{ label: "Acme", render: <Link to="/o/acme" /> }, { label: "Domaines" }]} actions={<Button>Ajouter</Button>} />` |
|
|
52
|
+
|
|
53
|
+
Le menu « Produits » et la palette (`badge` d'une commande) montrent la pastille de chaque produit.
|
|
54
|
+
|
|
55
|
+
## Identité H family
|
|
56
|
+
|
|
57
|
+
| Import | Rôle |
|
|
58
|
+
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
59
|
+
| `product-badge` | `<ProductBadge product="host" className="size-8" />`, `<FamilyMark />` (le H tricolore), `PRODUCT_GRADIENT` |
|
|
60
|
+
| `@hgdc/ui/family/<mot>.svg` | Fichiers des pastilles (`account`, `host`, `mail`, `portal`, `status`, `analytics`, `docs`), à copier en `favicon.svg` |
|
|
61
|
+
| `@hgdc/ui/family/hfamily*.svg` | `hfamily-icon.svg` (le H seul), `hfamily.svg` et `hfamily-dark.svg` (logo sur fond clair ou sombre, jamais recoloré) |
|
|
62
|
+
| `styles.css` | `--product-<mot>-from` / `--product-<mot>-to` (dégradé de chaque pastille), `--glow-blue`, `--glow-violet`, `--glow-pink`, `--glow-orange` |
|
|
63
|
+
|
|
64
|
+
Couleurs pleines ; pour éclaircir, mélanger au blanc `color-mix(in oklab, …)`, jamais `in oklch`. Une pastille reste seule, jamais dans une tuile de couleur. Les originaux vivent dans le dépôt du site hgdc.ch : les recopier dans `packages/ui/family/`, puis `bun scripts/family.ts && bun run format` (SVG embarqués et PNG des e-mails).
|
|
65
|
+
|
|
66
|
+
Sur ordinateur, la coque ouvre sur la barre des produits (ADR 0011) : organisation en haut, pastilles des produits ouverts, Account en bas, chacun par son entrée `/auth/hgdc?next=/`, le produit courant en teinte. Au survol (150 ms d'intention) ou au focus clavier, elle s'élargit par-dessus la page et écrit les noms. `productRail={false}` la coupe ; `accountHref` pointe vers un Account local. La barre latérale porte la marque du produit et se réduit en rail (`Ctrl+B`, mémorisé) : poser `sidebarInitScript` dans le `<head>`. En rail, la pastille du produit devient le bouton « Déplier » au survol. Le kit ne connaît ni routeur ni authentification : liens par `render={<Link />}`, actions par callbacks. Rôles d'organisation : `owner`, `admin`, `member` (Propriétaire, Admin, Membre).
|
|
54
67
|
|
|
55
68
|
## Contenu et données
|
|
56
69
|
|
|
@@ -99,7 +112,7 @@ Filtres, tri et page vivent dans l'URL du produit : les composants sont contrôl
|
|
|
99
112
|
| `money-field` | `<MoneyField value={centimes} onChange={…} />` : CHF en centimes, « 1’250.50 », accepte « 12.– » ; `formatChf`, `parseMoney` |
|
|
100
113
|
| `address-fields` | `<AddressFields value={address} onChange={…} errors={validatePostalAddress(address)} />` : rue, numéro, NPA, localité, pays (facture QR) |
|
|
101
114
|
|
|
102
|
-
`access-request` : `<AccessRequest endpoint="https://<deploiement>.convex.site/f/<id>" product="
|
|
115
|
+
`access-request` : `<AccessRequest endpoint="https://<deploiement>.convex.site/f/<id>" product="Mail" signInRender={<Link to="/sign-in" />} />`, la page « Demander un accès » d'un produit (les comptes HGDC s'ouvrent sur invitation). Poste en JSON vers un formulaire HGDC Mail Forms : prénom, nom, entreprise, e-mail professionnel, message facultatif, `produit` caché, piège `_gotcha`. États envoi, réussite, erreur, champ refusé ; `submitAccessRequest` s'appelle seul.
|
|
103
116
|
|
|
104
117
|
Les champs de date sont ceux du navigateur, sans calendrier maison (ADR 0006). Les fonctions pures (`parseMoney`, `contrastRatio`, `validatePostalAddress`, `formatDateRange`) s'importent seules, côté serveur aussi.
|
|
105
118
|
|
|
@@ -129,20 +142,20 @@ const { html, text } = await renderEmail(
|
|
|
129
142
|
)
|
|
130
143
|
```
|
|
131
144
|
|
|
132
|
-
| Bloc | Rôle
|
|
133
|
-
| -------------- |
|
|
134
|
-
| `EmailLayout` | Icône et nom du produit, carte, pied «
|
|
135
|
-
| `EmailHeading` | Titre
|
|
136
|
-
| `EmailText` | Paragraphe ; `small` pour une note secondaire
|
|
137
|
-
| `EmailButton` | Lien direct, jamais réécrit ; absolu (`https:`, `mailto:`) ou erreur
|
|
138
|
-
| `EmailLink` | Lien dans le texte
|
|
139
|
-
| `EmailCode` | Code à usage unique, en deux groupes à l'œil, d'un bloc au copier
|
|
140
|
-
| `EmailCallout` | Encadré `neutral`, `info`, `success`, `warning`, `danger`, avec titre
|
|
141
|
-
| `EmailDivider` | Séparateur
|
|
142
|
-
| `EmailDetails` | Liste clé / valeur
|
|
143
|
-
| `renderEmail` | `{ html, text }` : HTML pour clients mail, texte tiré du même rendu
|
|
144
|
-
|
|
145
|
-
L'accent vient du catalogue (`accent` de `@hgdc/ui/products`), les icônes de `https://hgdc.ch/email/<mot>.png` (fichiers dans `@hgdc/ui/email-icons/`,
|
|
145
|
+
| Bloc | Rôle |
|
|
146
|
+
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
147
|
+
| `EmailLayout` | Icône et nom du produit, carte, pied « Envoyé avec Mail de la H family » et mentions légales ; `footer` (désabonnement), `name`, `iconSrc`, `accent` |
|
|
148
|
+
| `EmailHeading` | Titre |
|
|
149
|
+
| `EmailText` | Paragraphe ; `small` pour une note secondaire |
|
|
150
|
+
| `EmailButton` | Lien direct, jamais réécrit ; absolu (`https:`, `mailto:`) ou erreur |
|
|
151
|
+
| `EmailLink` | Lien dans le texte |
|
|
152
|
+
| `EmailCode` | Code à usage unique, en deux groupes à l'œil, d'un bloc au copier |
|
|
153
|
+
| `EmailCallout` | Encadré `neutral`, `info`, `success`, `warning`, `danger`, avec titre |
|
|
154
|
+
| `EmailDivider` | Séparateur |
|
|
155
|
+
| `EmailDetails` | Liste clé / valeur |
|
|
156
|
+
| `renderEmail` | `{ html, text }` : HTML pour clients mail, texte tiré du même rendu |
|
|
157
|
+
|
|
158
|
+
L'accent vient du catalogue (`accent` de `@hgdc/ui/products`), les icônes de `https://hgdc.ch/email/<mot>.png` (fichiers dans `@hgdc/ui/email-icons/`, tirés des pastilles par `bun scripts/family.ts`). Un exemple par type : `scripts/email-showcase.tsx`.
|
|
146
159
|
|
|
147
160
|
## Règles de la DA
|
|
148
161
|
|
package/dist/access-request.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ export declare function submitAccessRequest(endpoint: string, values: AccessRequ
|
|
|
35
35
|
export declare function AccessRequest({ endpoint, product, title, description, messageLabel, signInHref, signInRender, homeHref, homeRender, fetcher, }: {
|
|
36
36
|
/** Adresse d'envoi du formulaire Forms : `https://<deploiement>.convex.site/f/<id>`. */
|
|
37
37
|
endpoint: string;
|
|
38
|
-
/** Nom affiché et valeur du champ caché `produit` : «
|
|
38
|
+
/** Nom affiché et valeur du champ caché `produit` : « Mail ». */
|
|
39
39
|
product: string;
|
|
40
40
|
title?: string;
|
|
41
41
|
description?: ReactNode;
|
package/dist/access-request.js
CHANGED
|
@@ -49,7 +49,7 @@ function LinkSlot({ href, render, className, children, }) {
|
|
|
49
49
|
props: mergeProps({ className, children }, render ? {} : { href }),
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
-
export function AccessRequest({ endpoint, product, title = `Demander un accès à ${product}`, description = "Les comptes
|
|
52
|
+
export function AccessRequest({ endpoint, product, title = `Demander un accès à ${product}`, description = "Les comptes s'ouvrent sur invitation. Dis-nous qui tu es : on te répond rapidement et, si c'est bon, tu reçois une invitation par e-mail.", messageLabel = "Ton projet, en quelques mots (facultatif)", signInHref = "/sign-in", signInRender, homeHref = "/", homeRender, fetcher, }) {
|
|
53
53
|
const [values, setValues] = useState(EMPTY_ACCESS_REQUEST);
|
|
54
54
|
const [trap, setTrap] = useState("");
|
|
55
55
|
const [pending, setPending] = useState(false);
|
|
@@ -75,10 +75,10 @@ export function AccessRequest({ endpoint, product, title = `Demander un accès
|
|
|
75
75
|
setPending(false);
|
|
76
76
|
}
|
|
77
77
|
if (sentTo) {
|
|
78
|
-
return (_jsxs("div", { "data-slot": "access-request", className: "grid rise gap-6", children: [_jsx(HugeiconsIcon, { icon: CheckmarkCircle02Icon, className: "size-10 pop-in text-status-active" }), _jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-semibold text-balance", children: "Demande envoy\u00E9e" }), _jsxs("p", { className: "mt-2 text-muted-foreground", children: ["Merci. Un accus\u00E9 de r\u00E9ception part vers ", sentTo, ". Si ta demande est accept\u00E9e, tu re\u00E7ois une invitation
|
|
78
|
+
return (_jsxs("div", { "data-slot": "access-request", className: "grid rise gap-6", children: [_jsx(HugeiconsIcon, { icon: CheckmarkCircle02Icon, className: "size-10 pop-in text-status-active" }), _jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-semibold text-balance", children: "Demande envoy\u00E9e" }), _jsxs("p", { className: "mt-2 text-muted-foreground", children: ["Merci. Un accus\u00E9 de r\u00E9ception part vers ", sentTo, ". Si ta demande est accept\u00E9e, tu re\u00E7ois une invitation d'Account pour cr\u00E9er ton compte."] })] }), _jsx(LinkSlot, { href: homeHref, render: homeRender, className: buttonVariants({ variant: "outline", size: "lg" }), children: "Retour \u00E0 l'accueil" })] }, "access-done"));
|
|
79
79
|
}
|
|
80
80
|
const invalid = (key) => (fieldErrors[key] ? true : undefined);
|
|
81
81
|
const field = (key, label, input) => (_jsxs(Field, { "data-invalid": invalid(key), children: [_jsx(FieldLabel, { htmlFor: `acces-${key}`, children: label }), input, fieldErrors[key] ? _jsx(FieldError, { children: fieldErrors[key] }) : null] }));
|
|
82
82
|
const text = (key, autoComplete, max, type = "text") => (_jsx(Input, { id: `acces-${key}`, type: type, autoComplete: autoComplete, required: true, maxLength: max, value: values[key], onChange: set(key), "aria-invalid": invalid(key) }));
|
|
83
|
-
return (_jsxs("form", { "data-slot": "access-request", onSubmit: submit, className: "grid rise gap-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-semibold text-balance", children: title }), description ? _jsx("p", { className: "mt-2 text-muted-foreground", children: description }) : null] }), failure ? (_jsx(Alert, { tone: "destructive", role: "alert", children: failure })) : null, _jsxs("div", { className: "grid gap-4", children: [_jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [field("prenom", "Prénom", text("prenom", "given-name", 100)), field("nom", "Nom", text("nom", "family-name", 100))] }), field("entreprise", "Entreprise", text("entreprise", "organization", 120)), field("email", "E-mail professionnel", text("email", "email", 320, "email")), field("message", messageLabel, _jsx(Textarea, { id: "acces-message", rows: 3, maxLength: 2000, value: values.message, onChange: set("message"), "aria-invalid": invalid("message") })), _jsx("input", { type: "text", name: "_gotcha", tabIndex: -1, autoComplete: "off", "aria-hidden": "true", value: trap, onChange: (e) => setTrap(e.target.value), className: "absolute -left-[9999px] size-px opacity-0" })] }), _jsx(Button, { type: "submit", size: "lg", isLoading: pending, children: "Envoyer la demande" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["D\u00E9j\u00E0 un compte
|
|
83
|
+
return (_jsxs("form", { "data-slot": "access-request", onSubmit: submit, className: "grid rise gap-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-semibold text-balance", children: title }), description ? _jsx("p", { className: "mt-2 text-muted-foreground", children: description }) : null] }), failure ? (_jsx(Alert, { tone: "destructive", role: "alert", children: failure })) : null, _jsxs("div", { className: "grid gap-4", children: [_jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [field("prenom", "Prénom", text("prenom", "given-name", 100)), field("nom", "Nom", text("nom", "family-name", 100))] }), field("entreprise", "Entreprise", text("entreprise", "organization", 120)), field("email", "E-mail professionnel", text("email", "email", 320, "email")), field("message", messageLabel, _jsx(Textarea, { id: "acces-message", rows: 3, maxLength: 2000, value: values.message, onChange: set("message"), "aria-invalid": invalid("message") })), _jsx("input", { type: "text", name: "_gotcha", tabIndex: -1, autoComplete: "off", "aria-hidden": "true", value: trap, onChange: (e) => setTrap(e.target.value), className: "absolute -left-[9999px] size-px opacity-0" })] }), _jsx(Button, { type: "submit", size: "lg", isLoading: pending, children: "Envoyer la demande" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["D\u00E9j\u00E0 un compte ?", " ", _jsx(LinkSlot, { href: signInHref, render: signInRender, className: "font-medium text-foreground underline-offset-4 hover:underline", children: "Se connecter" })] })] }, "access"));
|
|
84
84
|
}
|
package/dist/account-menu.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { type IconSvgElement } from "@hugeicons/react";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
|
+
import type { FamilyProduct } from "./family-svg.ts";
|
|
3
4
|
export type HgdcProduct = {
|
|
4
|
-
/** «
|
|
5
|
+
/** « Host ». */
|
|
5
6
|
name: string;
|
|
6
7
|
href: string;
|
|
7
8
|
/** Le produit ouvert : coché, sans lien. */
|
|
8
9
|
current?: boolean;
|
|
9
|
-
/**
|
|
10
|
+
/** Pastille du produit (`host`, `mail`…), prioritaire sur `icon`. */
|
|
11
|
+
badge?: FamilyProduct;
|
|
12
|
+
/** Icône du produit sans pastille ; celle d'un tableau de bord par défaut. */
|
|
10
13
|
icon?: IconSvgElement;
|
|
11
14
|
/** Texte discret à droite : « Préversion ». */
|
|
12
15
|
hint?: string;
|
|
@@ -22,7 +25,7 @@ export type AccountUser = {
|
|
|
22
25
|
* l'avatar seul, pour une barre du haut. Le produit passe les actions ; le thème passe par
|
|
23
26
|
* `ThemeProvider`.
|
|
24
27
|
*
|
|
25
|
-
* « Produits
|
|
28
|
+
* « Produits » liste par défaut Account et les produits ouverts du catalogue
|
|
26
29
|
* (`productMenuEntries`, `@hgdc/ui/products`), avec `product` coché. `products` remplace cette
|
|
27
30
|
* liste ; `products={[]}` la retire.
|
|
28
31
|
*/
|
package/dist/account-menu.js
CHANGED
|
@@ -13,6 +13,7 @@ import { Menu as MenuPrimitive } from "@base-ui/react/menu";
|
|
|
13
13
|
import { Avatar, AvatarFallback, AvatarImage } from "./avatar.js";
|
|
14
14
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "./dropdown-menu.js";
|
|
15
15
|
import { MENU_LINK_ITEM } from "./org-switcher.js";
|
|
16
|
+
import { ProductBadge } from "./product-badge.js";
|
|
16
17
|
import { productMenuEntries } from "./products.js";
|
|
17
18
|
import { useTheme } from "./theme.js";
|
|
18
19
|
const THEMES = ["light", "dark", "system"];
|
|
@@ -30,7 +31,7 @@ function initials(name) {
|
|
|
30
31
|
* l'avatar seul, pour une barre du haut. Le produit passe les actions ; le thème passe par
|
|
31
32
|
* `ThemeProvider`.
|
|
32
33
|
*
|
|
33
|
-
* « Produits
|
|
34
|
+
* « Produits » liste par défaut Account et les produits ouverts du catalogue
|
|
34
35
|
* (`productMenuEntries`, `@hgdc/ui/products`), avec `product` coché. `products` remplace cette
|
|
35
36
|
* liste ; `products={[]}` la retire.
|
|
36
37
|
*/
|
|
@@ -41,5 +42,10 @@ export function AccountMenu({ user, onProfile, onSecurity, onShortcuts, onSignOu
|
|
|
41
42
|
const next = THEMES.find((t) => t === value);
|
|
42
43
|
if (next)
|
|
43
44
|
setTheme(next);
|
|
44
|
-
}, 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, {}), _jsxs(DropdownMenuGroup, { children: [_jsx(DropdownMenuLabel, { children: "Produits
|
|
45
|
+
}, 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, {}), _jsxs(DropdownMenuGroup, { 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, 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"] })] })] }));
|
|
46
|
+
}
|
|
47
|
+
function ProductMenuIcon({ product }) {
|
|
48
|
+
if (product.badge)
|
|
49
|
+
return _jsx(ProductBadge, { product: product.badge });
|
|
50
|
+
return _jsx(HugeiconsIcon, { icon: product.icon ?? DashboardSquare01Icon, "aria-hidden": true });
|
|
45
51
|
}
|
package/dist/app-shell.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type IconSvgElement } from "@hugeicons/react";
|
|
2
2
|
import { type ReactNode } from "react";
|
|
3
|
+
import type { FamilyProduct } from "./family-svg.ts";
|
|
3
4
|
/**
|
|
4
5
|
* À poser dans le `<head>`, avec `themeInitScript` : il remet le rail avant la première
|
|
5
6
|
* peinture, et la variante CSS `rail:` le lit, donc rien ne saute au chargement.
|
|
@@ -33,17 +34,28 @@ export declare function SidebarNavItem({ icon, label, active, href, render, }: {
|
|
|
33
34
|
render?: React.ReactElement;
|
|
34
35
|
}): React.JSX.Element;
|
|
35
36
|
/**
|
|
36
|
-
* La coque commune des consoles HGDC, celle de Host. Sur ordinateur
|
|
37
|
-
* (organisation en haut,
|
|
38
|
-
* la page
|
|
39
|
-
*
|
|
37
|
+
* La coque commune des consoles HGDC, celle de Host. Sur ordinateur, de gauche à droite : la barre
|
|
38
|
+
* des produits de la famille (organisation en haut, pastilles, Account en bas, élargie au survol
|
|
39
|
+
* par-dessus la page), la barre latérale du produit (sa marque, sa navigation, le compte en bas),
|
|
40
|
+
* réductible en rail (`Ctrl+B`, mémorisé), et la page sur une carte. Sur téléphone : une barre en
|
|
41
|
+
* haut, le menu dans un tiroir qui monte du bas et se referme quand on choisit une page ; les
|
|
42
|
+
* produits restent dans le menu de l'avatar.
|
|
40
43
|
*
|
|
41
44
|
* Le kit ne connaît ni le routeur ni l'authentification : le produit passe ses éléments
|
|
42
45
|
* (`OrgSwitcher`, `SidebarNav`, `AccountMenu`) et ses liens.
|
|
43
46
|
*/
|
|
44
|
-
export declare function AppShell({
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
+
export declare function AppShell({ product, organization, productRail, accountHref, nav, sidebarFooter, header, children, mainClassName, }: {
|
|
48
|
+
/** Le produit affiché (`"host"`) : sa marque en haut de la barre, « vous êtes ici » dans la barre des produits. */
|
|
49
|
+
product: FamilyProduct;
|
|
50
|
+
/** `OrgSwitcher` : en haut de la barre des produits, en haut du tiroir sur téléphone. */
|
|
51
|
+
organization?: ReactNode;
|
|
52
|
+
/**
|
|
53
|
+
* `false` : pas de barre des produits (un écran hors de la famille). L'organisation passe alors
|
|
54
|
+
* sous la marque, dans la barre latérale.
|
|
55
|
+
*/
|
|
56
|
+
productRail?: boolean;
|
|
57
|
+
/** Adresse d'Account dans la barre des produits (un Account local en développement). */
|
|
58
|
+
accountHref?: string | undefined;
|
|
47
59
|
/** `SidebarNav` et ses `SidebarNavItem`. */
|
|
48
60
|
nav: ReactNode;
|
|
49
61
|
/** Bas de la barre : liens secondaires (Docs, Paramètres) et `AccountMenu`. */
|
package/dist/app-shell.js
CHANGED
|
@@ -6,6 +6,10 @@ import { HugeiconsIcon } from "@hugeicons/react";
|
|
|
6
6
|
import { useRender } from "@base-ui/react/use-render";
|
|
7
7
|
import { createContext, useContext, useEffect, useState, useSyncExternalStore } from "react";
|
|
8
8
|
import { Drawer, DrawerContent, DrawerTitle } from "./drawer.js";
|
|
9
|
+
import { isApplePlatform, Kbd, KbdGroup } from "./kbd.js";
|
|
10
|
+
import { ProductBadge } from "./product-badge.js";
|
|
11
|
+
import { ProductRail } from "./product-rail.js";
|
|
12
|
+
import { HGDC_ACCOUNT, HGDC_PRODUCTS } from "./products.js";
|
|
9
13
|
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.js";
|
|
10
14
|
import { cn } from "./cn.js";
|
|
11
15
|
// ── Rail : la barre latérale réduite aux icônes, mémorisée par navigateur ──────────────
|
|
@@ -90,18 +94,42 @@ export function SidebarNavItem({ icon, label, active, href, render, }) {
|
|
|
90
94
|
return (_jsx("li", { children: _jsxs(Tooltip, { disabled: !rail, children: [_jsx(TooltipTrigger, { render: link }), _jsx(TooltipContent, { side: "right", children: label })] }) }));
|
|
91
95
|
}
|
|
92
96
|
// ── Coque ─────────────────────────────────────────────────────────────────────────────
|
|
97
|
+
// Les noms de Hugeicons disent l'état du panneau, pas le geste : « Open » dessine la flèche vers la
|
|
98
|
+
// gauche (réduire), « Close » vers la droite (déplier).
|
|
99
|
+
const COLLAPSE_ICON = PanelLeftOpenIcon;
|
|
100
|
+
const EXPAND_ICON = PanelLeftCloseIcon;
|
|
101
|
+
/** Même boîte qu'une entrée de la barre en rail : 40 × 36 px, icône de 18 px centrée. */
|
|
102
|
+
const TOGGLE = "relative grid h-9 w-10 shrink-0 place-items-center rounded-xl text-shell-muted outline-none transition-colors duration-(--duration-fast) ease-out hover:bg-shell-hover hover:text-shell-foreground focus-visible:ring-2 focus-visible:ring-ring/50";
|
|
103
|
+
function ShortcutHint({ label }) {
|
|
104
|
+
return (_jsxs("span", { className: "flex items-center gap-2", children: [label, _jsxs(KbdGroup, { children: [_jsx(Kbd, { children: isApplePlatform() ? "⌘" : "Ctrl" }), _jsx(Kbd, { children: "B" })] })] }));
|
|
105
|
+
}
|
|
106
|
+
function productName(product) {
|
|
107
|
+
if (product === HGDC_ACCOUNT.badge)
|
|
108
|
+
return HGDC_ACCOUNT.name;
|
|
109
|
+
return HGDC_PRODUCTS.find((item) => item.badge === product)?.name ?? `HGDC ${product}`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Haut de la barre latérale. Dépliée : la marque du produit, et le bouton « Réduire » à droite.
|
|
113
|
+
* En rail : la marque seule, qui devient le bouton « Déplier » au survol de la barre ou au focus,
|
|
114
|
+
* à la même place : le bouton ne descend jamais sous le logo. Les deux états sont dans la page et
|
|
115
|
+
* choisis par la variante CSS `rail:`, donc justes dès la première peinture.
|
|
116
|
+
*/
|
|
117
|
+
function SidebarHead({ product }) {
|
|
118
|
+
return (_jsxs("div", { className: "mt-2 flex h-14 shrink-0 items-center gap-1 px-3", children: [_jsxs("div", { "data-slot": "sidebar-brand", className: "flex min-w-0 flex-1 items-center gap-2.5 pl-2.25 rail:hidden", children: [_jsx(ProductBadge, { product: product, className: "size-6" }), _jsx("span", { className: "truncate font-heading text-[0.9375rem] font-semibold", children: productName(product) })] }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx("button", { type: "button", "data-slot": "sidebar-collapse", onClick: toggleSidebar, "aria-label": "R\u00E9duire la barre lat\u00E9rale", "aria-keyshortcuts": "Control+B Meta+B", className: cn(TOGGLE, "rail:hidden") }), children: _jsx(HugeiconsIcon, { icon: COLLAPSE_ICON, strokeWidth: 1.8, "aria-hidden": true, className: "size-[1.125rem]" }) }), _jsx(TooltipContent, { side: "right", children: _jsx(ShortcutHint, { label: "R\u00E9duire" }) })] }), _jsxs(Tooltip, { children: [_jsxs(TooltipTrigger, { render: _jsx("button", { type: "button", "data-slot": "sidebar-expand", onClick: toggleSidebar, "aria-label": "D\u00E9plier la barre lat\u00E9rale", "aria-keyshortcuts": "Control+B Meta+B", className: cn(TOGGLE, "group/toggle hidden rail:grid") }), children: [_jsx(ProductBadge, { product: product, className: "col-start-1 row-start-1 size-6 transition-opacity duration-(--duration-fast) ease-out group-hover/sidebar:opacity-0 group-focus-visible/toggle:opacity-0 motion-reduce:transition-none" }), _jsx(HugeiconsIcon, { icon: EXPAND_ICON, strokeWidth: 1.8, "aria-hidden": true, className: "col-start-1 row-start-1 size-[1.125rem] opacity-0 transition-opacity duration-(--duration-fast) ease-out group-hover/sidebar:opacity-100 group-focus-visible/toggle:opacity-100 motion-reduce:transition-none" })] }), _jsx(TooltipContent, { side: "right", children: _jsx(ShortcutHint, { label: "D\u00E9plier" }) })] })] }));
|
|
119
|
+
}
|
|
93
120
|
/**
|
|
94
|
-
* La coque commune des consoles HGDC, celle de Host. Sur ordinateur
|
|
95
|
-
* (organisation en haut,
|
|
96
|
-
* la page
|
|
97
|
-
*
|
|
121
|
+
* La coque commune des consoles HGDC, celle de Host. Sur ordinateur, de gauche à droite : la barre
|
|
122
|
+
* des produits de la famille (organisation en haut, pastilles, Account en bas, élargie au survol
|
|
123
|
+
* par-dessus la page), la barre latérale du produit (sa marque, sa navigation, le compte en bas),
|
|
124
|
+
* réductible en rail (`Ctrl+B`, mémorisé), et la page sur une carte. Sur téléphone : une barre en
|
|
125
|
+
* haut, le menu dans un tiroir qui monte du bas et se referme quand on choisit une page ; les
|
|
126
|
+
* produits restent dans le menu de l'avatar.
|
|
98
127
|
*
|
|
99
128
|
* Le kit ne connaît ni le routeur ni l'authentification : le produit passe ses éléments
|
|
100
129
|
* (`OrgSwitcher`, `SidebarNav`, `AccountMenu`) et ses liens.
|
|
101
130
|
*/
|
|
102
|
-
export function AppShell({
|
|
103
|
-
const rail = useSidebarRail();
|
|
131
|
+
export function AppShell({ product, organization, productRail = true, accountHref, nav, sidebarFooter, header, children, mainClassName, }) {
|
|
104
132
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
105
133
|
useToggleShortcut();
|
|
106
|
-
return (_jsx(ShellContext.Provider, { value: { closeMobileNav: () => setMenuOpen(false) }, children: _jsxs("div", { "data-slot": "app-shell", className: "min-h-dvh lg:grid lg:h-dvh lg:grid-cols-[
|
|
134
|
+
return (_jsx(ShellContext.Provider, { value: { closeMobileNav: () => setMenuOpen(false) }, children: _jsxs("div", { "data-slot": "app-shell", className: cn("min-h-dvh lg:grid lg:h-dvh lg:bg-shell", productRail ? "lg:grid-cols-[auto_auto_minmax(0,1fr)]" : "lg:grid-cols-[auto_minmax(0,1fr)]"), children: [_jsx("a", { href: "#contenu", className: "sr-only z-50 rounded-xl bg-background px-4 py-2 text-sm font-medium focus:not-sr-only focus:fixed focus:top-3 focus:left-3 focus:ring-2 focus:ring-ring", children: "Aller au contenu" }), productRail ? _jsx(ProductRail, { current: product, organization: organization, accountHref: accountHref }) : null, _jsxs("aside", { "aria-label": "Barre lat\u00E9rale", className: "group/sidebar hidden overflow-hidden text-shell-foreground transition-[width] duration-(--duration-drawer) ease-ios motion-reduce:transition-none lg:flex lg:w-64 lg:flex-col lg:rail:w-16", children: [_jsx(SidebarHead, { product: product }), !productRail && organization ? _jsx("div", { className: "shrink-0 px-3 pb-1", children: organization }) : null, nav, sidebarFooter ? _jsx("div", { className: "grid gap-0.5 px-3 pt-1 pb-3", children: sidebarFooter }) : null] }), _jsx("div", { className: "flex min-h-dvh flex-col lg:min-h-0 lg:p-2 lg:pl-0", children: _jsxs("div", { className: "flex flex-1 flex-col lg:min-h-0 lg:overflow-y-auto lg:rounded-xl lg:bg-background", children: [_jsxs("header", { className: "sticky top-[env(safe-area-inset-top,0px)] z-30 flex h-14 shrink-0 items-center gap-1 border-b border-foreground/5 bg-background px-2 sm:px-4 lg:top-0", children: [_jsx("button", { type: "button", onClick: () => setMenuOpen(true), "aria-label": "Ouvrir le menu", className: "touch-target grid size-10 shrink-0 place-items-center rounded-xl text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50 lg:hidden", children: _jsx(HugeiconsIcon, { icon: Menu01Icon, "aria-hidden": true, className: "size-5" }) }), _jsx("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: header })] }), _jsx("main", { id: "contenu", tabIndex: -1, className: cn("mx-auto w-full max-w-6xl flex-1 px-4 pt-6 pb-16 outline-none sm:px-6 lg:px-10 lg:pt-8", mainClassName), children: children })] }) }), _jsx(Drawer, { side: "bottom", open: menuOpen, onOpenChange: setMenuOpen, children: _jsxs(DrawerContent, { className: "lg:hidden", children: [_jsx(DrawerTitle, { className: "sr-only", children: "Menu" }), organization ? _jsx("div", { className: "px-3 pt-1", children: organization }) : null, nav, sidebarFooter ? (_jsx("div", { className: "grid gap-0.5 border-t border-foreground/5 px-3 pt-2 pb-[max(env(safe-area-inset-bottom),0.75rem)]", children: sidebarFooter })) : null] }) })] }) }));
|
|
107
135
|
}
|
package/dist/command-menu.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { type IconSvgElement } from "@hugeicons/react";
|
|
2
|
+
import type { FamilyProduct } from "./family-svg.ts";
|
|
2
3
|
export type Command = {
|
|
3
4
|
/** Unique dans toute la palette. */
|
|
4
5
|
id: string;
|
|
5
6
|
label: string;
|
|
6
7
|
icon?: IconSvgElement;
|
|
8
|
+
/** Pastille d'un produit (« Ouvrir Mail »), à la place de `icon`. */
|
|
9
|
+
badge?: FamilyProduct;
|
|
7
10
|
/** Autres mots qui trouvent la commande : « dns » pour « Ajouter un domaine ». */
|
|
8
11
|
keywords?: Array<string>;
|
|
9
12
|
/** Texte discret à droite : l'organisation d'un projet, « Page ». */
|
package/dist/command-menu.js
CHANGED
|
@@ -5,6 +5,7 @@ import Search01Icon from "@hugeicons/core-free-icons/Search01Icon";
|
|
|
5
5
|
import { HugeiconsIcon } from "@hugeicons/react";
|
|
6
6
|
import { useCallback, useEffect, useState, useSyncExternalStore } from "react";
|
|
7
7
|
import { FLOATING_SURFACE } from "./floating.js";
|
|
8
|
+
import { ProductBadge } from "./product-badge.js";
|
|
8
9
|
import { Kbd, KbdGroup, isApplePlatform } from "./kbd.js";
|
|
9
10
|
import { cn } from "./cn.js";
|
|
10
11
|
function normalize(text) {
|
|
@@ -63,7 +64,7 @@ export function CommandMenu({ groups, open: openProp, onOpenChange, placeholder
|
|
|
63
64
|
return (_jsx(DialogPrimitive.Root, { open: open, onOpenChange: setOpen, children: _jsxs(DialogPrimitive.Portal, { children: [_jsx(DialogPrimitive.Backdrop, { className: "fixed inset-0 z-50 bg-foreground/30 dark:bg-black/60" }), _jsx(DialogPrimitive.Viewport, { className: "fixed inset-0 z-50 flex items-start justify-center px-2 pt-[min(12dvh,6rem)] pb-2", children: _jsx(DialogPrimitive.Popup, { "data-slot": "command-menu", "aria-label": "Palette de commandes", className: cn(FLOATING_SURFACE, "flex max-h-[min(36rem,calc(100dvh-2rem))] w-full max-w-xl flex-col overflow-hidden rounded-3xl shadow-2xl shadow-black/10 outline-none"), children: _jsxs(Autocomplete.Root, { open: true, inline: true, items: visibleGroups, itemToStringValue: (command) => command.label, filter: (command, query) => matchCommand(command, query), autoHighlight: "always", keepHighlight: true, children: [_jsxs(Autocomplete.InputGroup, { className: "flex items-center gap-3 border-b border-foreground/5 px-4", children: [_jsx(HugeiconsIcon, { icon: Search01Icon, strokeWidth: 2, "aria-hidden": true, className: "size-4.5 shrink-0 text-muted-foreground" }), _jsx(Autocomplete.Input, { "aria-label": "Rechercher une commande", placeholder: placeholder, className: "h-14 w-full min-w-0 bg-transparent text-base outline-none placeholder:text-muted-foreground" }), _jsx(DialogPrimitive.Close, { className: "sr-only", children: "Fermer la palette" })] }), _jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto overscroll-contain p-1.5 [scroll-padding-block:0.375rem]", children: [_jsx(Autocomplete.Empty, { className: "px-3 py-10 text-center text-sm text-muted-foreground empty:hidden", children: emptyLabel }), _jsx(Autocomplete.List, { children: (group) => (_jsxs(Autocomplete.Group, { items: group.items, className: "not-last:mb-1.5", children: [_jsx(Autocomplete.GroupLabel, { className: "px-3 pt-2 pb-1.5 text-xs font-medium text-muted-foreground", children: group.label }), _jsx(Autocomplete.Collection, { children: (command) => (_jsxs(Autocomplete.Item, { value: command, onClick: () => {
|
|
64
65
|
setOpen(false);
|
|
65
66
|
command.onSelect();
|
|
66
|
-
}, className: "flex min-h-11 cursor-default items-center gap-3 rounded-xl px-3 py-2 text-sm font-medium outline-none select-none data-highlighted:bg-muted lg:min-h-10", children: [command.icon ? (_jsx(HugeiconsIcon, { icon: command.icon, strokeWidth: 1.8, "aria-hidden": true, className: "size-4.5 shrink-0 text-muted-foreground" })) : null, _jsx("span", { className: "min-w-0 flex-1 truncate", children: command.label }), command.hint ? (_jsx("span", { className: "shrink-0 truncate text-xs font-normal text-muted-foreground", children: command.hint })) : null, command.shortcut ? (_jsx(KbdGroup, { className: "hidden shrink-0 sm:inline-flex", children: command.shortcut.map((key) => (_jsx(Kbd, { children: key }, key))) })) : null] }, command.id)) })] }, group.label)) })] }), _jsxs("div", { className: "hidden items-center gap-4 border-t border-foreground/5 px-4 py-2.5 text-xs text-muted-foreground sm:flex", children: [_jsxs("span", { className: "flex items-center gap-1.5", children: [_jsxs(KbdGroup, { children: [_jsx(Kbd, { children: "\u2191" }), _jsx(Kbd, { children: "\u2193" })] }), "Choisir"] }), _jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx(Kbd, { children: "Entr\u00E9e" }), "Ouvrir"] }), _jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx(Kbd, { children: "\u00C9chap" }), "Fermer"] })] })] }) }) })] }) }));
|
|
67
|
+
}, className: "flex min-h-11 cursor-default items-center gap-3 rounded-xl px-3 py-2 text-sm font-medium outline-none select-none data-highlighted:bg-muted lg:min-h-10", children: [command.badge ? (_jsx(ProductBadge, { product: command.badge, className: "size-4.5" })) : command.icon ? (_jsx(HugeiconsIcon, { icon: command.icon, strokeWidth: 1.8, "aria-hidden": true, className: "size-4.5 shrink-0 text-muted-foreground" })) : null, _jsx("span", { className: "min-w-0 flex-1 truncate", children: command.label }), command.hint ? (_jsx("span", { className: "shrink-0 truncate text-xs font-normal text-muted-foreground", children: command.hint })) : null, command.shortcut ? (_jsx(KbdGroup, { className: "hidden shrink-0 sm:inline-flex", children: command.shortcut.map((key) => (_jsx(Kbd, { children: key }, key))) })) : null] }, command.id)) })] }, group.label)) })] }), _jsxs("div", { className: "hidden items-center gap-4 border-t border-foreground/5 px-4 py-2.5 text-xs text-muted-foreground sm:flex", children: [_jsxs("span", { className: "flex items-center gap-1.5", children: [_jsxs(KbdGroup, { children: [_jsx(Kbd, { children: "\u2191" }), _jsx(Kbd, { children: "\u2193" })] }), "Choisir"] }), _jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx(Kbd, { children: "Entr\u00E9e" }), "Ouvrir"] }), _jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx(Kbd, { children: "\u00C9chap" }), "Fermer"] })] })] }) }) })] }) }));
|
|
67
68
|
}
|
|
68
69
|
function noop() { }
|
|
69
70
|
function subscribeNever() {
|
package/dist/email.js
CHANGED
|
@@ -85,7 +85,7 @@ export function EmailLayout({ product: ref, preview, children, name, iconSrc, ac
|
|
|
85
85
|
border: `1px solid ${l.border}`,
|
|
86
86
|
borderRadius: 16,
|
|
87
87
|
padding: "32px 28px",
|
|
88
|
-
}, children: children }), _jsxs(Section, { style: { padding: "20px 4px 0" }, children: [footer ? (_jsx(Text, { className: "hg-muted", style: { ...muted, margin: "0 0 12px" }, children: footer })) : null, _jsxs(Text, { className: "hg-muted", style: muted, children: [_jsx(Img, { src: emailIconUrl("mail"), width: 16, height: 16, alt: "", style: { display: "inline-block", verticalAlign: "middle", borderRadius: 8, marginRight: 6 } }), _jsxs("span", { style: { verticalAlign: "middle" }, children: ["
|
|
88
|
+
}, children: children }), _jsxs(Section, { style: { padding: "20px 4px 0" }, children: [footer ? (_jsx(Text, { className: "hg-muted", style: { ...muted, margin: "0 0 12px" }, children: footer })) : null, _jsxs(Text, { className: "hg-muted", style: muted, children: [_jsx(Img, { src: emailIconUrl("mail"), width: 16, height: 16, alt: "", style: { display: "inline-block", verticalAlign: "middle", borderRadius: 8, marginRight: 6 } }), _jsxs("span", { style: { verticalAlign: "middle" }, children: ["Envoy\u00E9 avec", " ", _jsx(Link, { href: MAIL_HREF, className: "hg-muted", style: { color: l.muted, textDecoration: "underline" }, children: "Mail" }), " ", "de la H family"] })] }), _jsx(Text, { className: "hg-muted", style: { ...muted, margin: "4px 0 0" }, children: LEGAL })] })] }) }) })] }));
|
|
89
89
|
}
|
|
90
90
|
export function EmailHeading({ children }) {
|
|
91
91
|
return (_jsx(Heading, { as: "h1", className: "hg-ink hg-heading", style: { color: EMAIL_LIGHT.ink, fontSize: 22, fontWeight: 600, lineHeight: "28px", margin: "0 0 12px" }, children: children }));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Les produits de la H family qui ont une pastille, par leur mot en minuscules. */
|
|
2
|
+
export declare const FAMILY_PRODUCTS: readonly ["account", "host", "mail", "portal", "status", "analytics", "docs"];
|
|
3
|
+
export type FamilyProduct = (typeof FAMILY_PRODUCTS)[number];
|
|
4
|
+
/** Vrai si le mot en minuscules (« host ») désigne un produit qui a une pastille. */
|
|
5
|
+
export declare function isFamilyProduct(word: string): word is FamilyProduct;
|
|
6
|
+
/** SVG de la pastille de chaque produit (`family/<mot>.svg`). */
|
|
7
|
+
export declare const PRODUCT_BADGE_SVG: Readonly<Record<FamilyProduct, string>>;
|
|
8
|
+
/** SVG du H tricolore, l'icône de la famille (`family/hfamily-icon.svg`). */
|
|
9
|
+
export declare const FAMILY_MARK_SVG = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\"><defs><linearGradient id=\"l\" x1=\"0\" y1=\"24\" x2=\"0\" y2=\"232\" gradientUnits=\"userSpaceOnUse\"><stop offset=\"0\" stop-color=\"#2c78fd\"/><stop offset=\"1\" stop-color=\"#7343fe\"/></linearGradient><linearGradient id=\"r\" x1=\"0\" y1=\"24\" x2=\"0\" y2=\"232\" gradientUnits=\"userSpaceOnUse\"><stop offset=\"0\" stop-color=\"#fe5292\"/><stop offset=\"1\" stop-color=\"#ff9a46\"/></linearGradient><linearGradient id=\"b\" x1=\"64\" y1=\"0\" x2=\"192\" y2=\"0\" gradientUnits=\"userSpaceOnUse\"><stop offset=\"0\" stop-color=\"#8145ff\"/><stop offset=\"1\" stop-color=\"#fe5292\"/></linearGradient></defs><rect x=\"43\" y=\"24\" width=\"57\" height=\"208\" rx=\"18\" fill=\"url(#l)\"/><rect x=\"156\" y=\"24\" width=\"57\" height=\"208\" rx=\"18\" fill=\"url(#r)\"/><rect x=\"67\" y=\"107\" width=\"122\" height=\"43\" rx=\"14\" fill=\"url(#b)\"/></svg>";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Généré par `bun scripts/family.ts` depuis `packages/ui/family/`. Ne pas modifier à la main.
|
|
2
|
+
/** Les produits de la H family qui ont une pastille, par leur mot en minuscules. */
|
|
3
|
+
export const FAMILY_PRODUCTS = ["account", "host", "mail", "portal", "status", "analytics", "docs"];
|
|
4
|
+
/** Vrai si le mot en minuscules (« host ») désigne un produit qui a une pastille. */
|
|
5
|
+
export function isFamilyProduct(word) {
|
|
6
|
+
return FAMILY_PRODUCTS.includes(word);
|
|
7
|
+
}
|
|
8
|
+
/** SVG de la pastille de chaque produit (`family/<mot>.svg`). */
|
|
9
|
+
export const PRODUCT_BADGE_SVG = {
|
|
10
|
+
account: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="66 66 380 380"><defs><linearGradient id="sky" x1="96" y1="76" x2="416" y2="436" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ff3b47"/><stop offset="1" stop-color="#ff6352"/></linearGradient><linearGradient id="acc" x1="150" y1="160" x2="362" y2="352" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ff3b47"/><stop offset="1" stop-color="#ff6352"/></linearGradient></defs><circle cx="256" cy="256" r="190" fill="url(#sky)"/><rect x="128" y="162" width="256" height="188" rx="48" fill="#fff"/><g fill="url(#acc)"><circle cx="202" cy="232" r="30"/><path d="M154 314A48 44 0 0 1 250 314Z"/><rect x="276" y="216" width="72" height="24" rx="12"/><rect x="276" y="262" width="48" height="24" rx="12"/></g></svg>',
|
|
11
|
+
host: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="66 66 380 380"><defs><linearGradient id="sky" x1="96" y1="76" x2="416" y2="436" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#194ff7"/><stop offset="1" stop-color="#02b4ff"/></linearGradient><linearGradient id="acc" x1="150" y1="160" x2="362" y2="352" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#194ff7"/><stop offset="1" stop-color="#02b4ff"/></linearGradient></defs><circle cx="256" cy="256" r="190" fill="url(#sky)"/><g transform="translate(0 6)"><g fill="#fff"><path d="M158.7 331.3A262.3 262.3 0 0 0 353.3 331.3L334 283H178Z"/><circle cx="178" cy="283" r="52"/><circle cx="334" cy="283" r="52"/><circle cx="214" cy="238" r="54"/><circle cx="288" cy="222" r="72"/><rect x="178" y="232" width="156" height="100"/></g><path fill="none" stroke="url(#acc)" stroke-width="24" stroke-linecap="round" stroke-linejoin="round" d="M256 302V234M224 266L256 234L288 266"/></g></svg>',
|
|
12
|
+
mail: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="66 66 380 380"><defs><linearGradient id="sky" x1="96" y1="76" x2="416" y2="436" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f61f8e"/><stop offset="1" stop-color="#ff5cb8"/></linearGradient><linearGradient id="acc" x1="150" y1="160" x2="362" y2="352" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f61f8e"/><stop offset="1" stop-color="#ff5cb8"/></linearGradient></defs><circle cx="256" cy="256" r="190" fill="url(#sky)"/><rect x="128" y="162" width="256" height="188" rx="56" fill="#fff"/><path fill="url(#acc)" d="M182 194H330A22 22 0 0 1 341.82 234.56L271.04 279.62A28 28 0 0 1 240.96 279.62L170.18 234.56A22 22 0 0 1 182 194Z"/></svg>',
|
|
13
|
+
portal: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="66 66 380 380"><defs><linearGradient id="sky" x1="96" y1="76" x2="416" y2="436" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#4732ed"/><stop offset="1" stop-color="#b03cff"/></linearGradient><linearGradient id="acc" x1="150" y1="160" x2="362" y2="352" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#4732ed"/><stop offset="1" stop-color="#b03cff"/></linearGradient></defs><circle cx="256" cy="256" r="190" fill="url(#sky)"/><path fill="#fff" d="M168 160H222A24 24 0 0 1 240 168L258 188H344A40 40 0 0 1 384 228V316A40 40 0 0 1 344 356H168A40 40 0 0 1 128 316V200A40 40 0 0 1 168 160Z"/><path fill="none" stroke="url(#acc)" stroke-width="30" stroke-linecap="round" stroke-linejoin="round" d="M212 272L244 302L304 240"/></svg>',
|
|
14
|
+
status: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="66 66 380 380"><defs><linearGradient id="sky" x1="96" y1="76" x2="416" y2="436" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#199f62"/><stop offset="1" stop-color="#56db29"/></linearGradient><linearGradient id="acc" x1="150" y1="160" x2="362" y2="352" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#199f62"/><stop offset="1" stop-color="#56db29"/></linearGradient></defs><circle cx="256" cy="256" r="190" fill="url(#sky)"/><rect x="128" y="160" width="256" height="192" rx="48" fill="#fff"/><path fill="none" stroke="url(#acc)" stroke-width="26" stroke-linecap="round" stroke-linejoin="round" d="M164 262H206L230 214L262 304L288 248H348"/></svg>',
|
|
15
|
+
analytics: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="66 66 380 380"><defs><linearGradient id="sky" x1="96" y1="76" x2="416" y2="436" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fe5623"/><stop offset="1" stop-color="#ffb003"/></linearGradient><linearGradient id="acc" x1="150" y1="160" x2="362" y2="352" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fe5623"/><stop offset="1" stop-color="#ffb003"/></linearGradient></defs><circle cx="256" cy="256" r="190" fill="url(#sky)"/><rect x="128" y="160" width="256" height="192" rx="48" fill="#fff"/><path fill="none" stroke="url(#acc)" stroke-width="30" stroke-linecap="round" d="M198 304V276M256 304V240M314 304V206"/></svg>',
|
|
16
|
+
docs: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="66 66 380 380"><defs><linearGradient id="sky" x1="96" y1="76" x2="416" y2="436" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1bb9cf"/><stop offset="1" stop-color="#13e4c8"/></linearGradient><linearGradient id="acc" x1="150" y1="160" x2="362" y2="352" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1bb9cf"/><stop offset="1" stop-color="#13e4c8"/></linearGradient></defs><circle cx="256" cy="256" r="190" fill="url(#sky)"/><g fill="#fff"><path d="M248 196C222 176 184 168 146 174A14 14 0 0 0 134 188V324A12 12 0 0 0 148 336C184 332 220 338 248 356Z"/><path d="M264 196C290 176 328 168 366 174A14 14 0 0 1 378 188V324A12 12 0 0 1 364 336C328 332 292 338 264 356Z"/></g><path fill="none" stroke="url(#acc)" stroke-width="16" stroke-linecap="round" d="M166 226C188 224 208 228 224 238M166 266C188 264 208 268 224 278M288 238C304 228 324 224 346 226M288 278C304 268 324 264 346 266"/></svg>',
|
|
17
|
+
};
|
|
18
|
+
/** SVG du H tricolore, l'icône de la famille (`family/hfamily-icon.svg`). */
|
|
19
|
+
export const FAMILY_MARK_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><defs><linearGradient id="l" x1="0" y1="24" x2="0" y2="232" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2c78fd"/><stop offset="1" stop-color="#7343fe"/></linearGradient><linearGradient id="r" x1="0" y1="24" x2="0" y2="232" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fe5292"/><stop offset="1" stop-color="#ff9a46"/></linearGradient><linearGradient id="b" x1="64" y1="0" x2="192" y2="0" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#8145ff"/><stop offset="1" stop-color="#fe5292"/></linearGradient></defs><rect x="43" y="24" width="57" height="208" rx="18" fill="url(#l)"/><rect x="156" y="24" width="57" height="208" rx="18" fill="url(#r)"/><rect x="67" y="107" width="122" height="43" rx="14" fill="url(#b)"/></svg>';
|
package/dist/org-switcher.d.ts
CHANGED
|
@@ -16,7 +16,9 @@ export declare function OrgMark({ org, className, }: {
|
|
|
16
16
|
className?: string;
|
|
17
17
|
}): React.JSX.Element;
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* L'organisation active, et le menu pour en changer ou en créer une. En haut de la barre des
|
|
20
|
+
* produits d'`AppShell` (la marque seule, le nom quand la barre s'élargit), dans le tiroir sur
|
|
21
|
+
* téléphone, ou en haut de la barre latérale quand le produit coupe la barre des produits.
|
|
20
22
|
* Présentationnel : le produit passe la liste, l'active et ce qu'il faut faire au choix (changer
|
|
21
23
|
* l'URL `/o/<slug>/…`), le kit n'appelle rien.
|
|
22
24
|
*/
|
package/dist/org-switcher.js
CHANGED
|
@@ -5,6 +5,7 @@ import UnfoldMoreIcon from "@hugeicons/core-free-icons/UnfoldMoreIcon";
|
|
|
5
5
|
import { HugeiconsIcon } from "@hugeicons/react";
|
|
6
6
|
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
|
|
7
7
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "./dropdown-menu.js";
|
|
8
|
+
import { useShellPlacement } from "./shell-placement.js";
|
|
8
9
|
import { cn } from "./cn.js";
|
|
9
10
|
export const ORG_ROLE_LABEL = {
|
|
10
11
|
owner: "Propriétaire",
|
|
@@ -20,13 +21,24 @@ export function OrgMark({ org, className, }) {
|
|
|
20
21
|
return (_jsx("span", { "aria-hidden": true, className: cn("grid size-8 shrink-0 place-items-center rounded-lg bg-foreground text-xs font-semibold text-background uppercase", className), children: org.name.slice(0, 2) }));
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
23
|
-
*
|
|
24
|
+
* L'organisation active, et le menu pour en changer ou en créer une. En haut de la barre des
|
|
25
|
+
* produits d'`AppShell` (la marque seule, le nom quand la barre s'élargit), dans le tiroir sur
|
|
26
|
+
* téléphone, ou en haut de la barre latérale quand le produit coupe la barre des produits.
|
|
24
27
|
* Présentationnel : le produit passe la liste, l'active et ce qu'il faut faire au choix (changer
|
|
25
28
|
* l'URL `/o/<slug>/…`), le kit n'appelle rien.
|
|
26
29
|
*/
|
|
27
30
|
export function OrgSwitcher({ organizations, activeId, onSelect, createHref, onCreate, className, }) {
|
|
31
|
+
const placement = useShellPlacement();
|
|
28
32
|
const active = organizations.find((org) => org.id === activeId);
|
|
29
33
|
if (!active)
|
|
30
34
|
return null;
|
|
31
|
-
|
|
35
|
+
// En haut de la barre des produits : la marque seule dans 40 px, le nom quand la barre s'élargit.
|
|
36
|
+
const inRail = placement === "product-rail";
|
|
37
|
+
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 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
|
|
38
|
+
? "shrink-0 gap-3 px-1 hover:bg-shell-hover aria-expanded:bg-shell-hover"
|
|
39
|
+
: "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
|
|
40
|
+
? "opacity-0 transition-opacity duration-(--duration-fast) ease-out group-data-expanded/rail:opacity-100 motion-reduce:transition-none"
|
|
41
|
+
: "lg:rail:hidden"), children: [_jsx("span", { className: "block truncate text-sm font-medium", 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
|
|
42
|
+
? "opacity-0 transition-opacity duration-(--duration-fast) group-data-expanded/rail:opacity-100"
|
|
43
|
+
: "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 bg-muted text-[10px] text-foreground" }), _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] })] }));
|
|
32
44
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type FamilyProduct } from "./family-svg.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Dégradé de chaque pastille, du coin haut gauche au coin bas droit. Mêmes valeurs que les jetons
|
|
4
|
+
* `--product-<mot>-from` / `--product-<mot>-to` de `styles.css` et que les SVG de `family/`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const PRODUCT_GRADIENT: Readonly<Record<FamilyProduct, {
|
|
7
|
+
from: string;
|
|
8
|
+
to: string;
|
|
9
|
+
}>>;
|
|
10
|
+
type MarkProps = {
|
|
11
|
+
className?: string;
|
|
12
|
+
/** Texte de remplacement. Absent : image décorative, le nom du produit est écrit à côté. */
|
|
13
|
+
label?: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Pastille d'un produit (dégradé plein, pictogramme blanc), toujours seule : jamais dans une tuile
|
|
17
|
+
* de couleur. `size-4` par défaut, à agrandir par `className`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function ProductBadge({ product, className, label }: MarkProps & {
|
|
20
|
+
product: FamilyProduct;
|
|
21
|
+
}): React.JSX.Element;
|
|
22
|
+
/** Le H tricolore de la famille (« Propulsé par », hub). `size-4` par défaut. */
|
|
23
|
+
export declare function FamilyMark({ className, label }: MarkProps): React.JSX.Element;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "./cn.js";
|
|
3
|
+
import { FAMILY_MARK_SVG, PRODUCT_BADGE_SVG } from "./family-svg.js";
|
|
4
|
+
/**
|
|
5
|
+
* Dégradé de chaque pastille, du coin haut gauche au coin bas droit. Mêmes valeurs que les jetons
|
|
6
|
+
* `--product-<mot>-from` / `--product-<mot>-to` de `styles.css` et que les SVG de `family/`.
|
|
7
|
+
*/
|
|
8
|
+
export const PRODUCT_GRADIENT = {
|
|
9
|
+
account: { from: "#ff3b47", to: "#ff6352" },
|
|
10
|
+
host: { from: "#194ff7", to: "#02b4ff" },
|
|
11
|
+
mail: { from: "#f61f8e", to: "#ff5cb8" },
|
|
12
|
+
portal: { from: "#4732ed", to: "#b03cff" },
|
|
13
|
+
status: { from: "#199f62", to: "#56db29" },
|
|
14
|
+
analytics: { from: "#fe5623", to: "#ffb003" },
|
|
15
|
+
docs: { from: "#1bb9cf", to: "#13e4c8" },
|
|
16
|
+
};
|
|
17
|
+
// En image plutôt qu'en SVG en ligne : chaque pastille garde ses identifiants de dégradé pour elle,
|
|
18
|
+
// plusieurs pastilles dans une même page ne se marchent pas dessus.
|
|
19
|
+
function dataUri(svg) {
|
|
20
|
+
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Pastille d'un produit (dégradé plein, pictogramme blanc), toujours seule : jamais dans une tuile
|
|
24
|
+
* de couleur. `size-4` par défaut, à agrandir par `className`.
|
|
25
|
+
*/
|
|
26
|
+
export function ProductBadge({ product, className, label }) {
|
|
27
|
+
return (_jsx("img", { src: dataUri(PRODUCT_BADGE_SVG[product]), alt: label ?? "", "aria-hidden": label ? undefined : true, width: 16, height: 16, draggable: false, className: cn("size-4 shrink-0 select-none", className) }));
|
|
28
|
+
}
|
|
29
|
+
/** Le H tricolore de la famille (« Propulsé par », hub). `size-4` par défaut. */
|
|
30
|
+
export function FamilyMark({ className, label }) {
|
|
31
|
+
return (_jsx("img", { src: dataUri(FAMILY_MARK_SVG), alt: label ?? "", "aria-hidden": label ? undefined : true, width: 16, height: 16, draggable: false, className: cn("size-4 shrink-0 select-none", className) }));
|
|
32
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type HgdcCatalogProduct } from "./products.ts";
|
|
3
|
+
/** Délai d'intention avant d'élargir la barre au survol : un passage de souris ne l'ouvre pas. */
|
|
4
|
+
export declare const PRODUCT_RAIL_OPEN_DELAY = 150;
|
|
5
|
+
/**
|
|
6
|
+
* La barre des produits de la famille, tout à gauche de la coque sur ordinateur : organisation en
|
|
7
|
+
* haut, pastilles des produits, Account en bas. 56 px de large ; au survol (après un court délai
|
|
8
|
+
* d'intention) ou au focus clavier, elle s'élargit par-dessus la page et écrit le nom de chaque
|
|
9
|
+
* produit, sans rien décaler. Elle se referme dès que la souris sort.
|
|
10
|
+
*/
|
|
11
|
+
export declare function ProductRail({ current, organization, accountHref, catalog, }: {
|
|
12
|
+
/** Le mot du produit affiché (« host ») : marqué en teinte. */
|
|
13
|
+
current?: string | undefined;
|
|
14
|
+
/** `OrgSwitcher`, rendu compact. */
|
|
15
|
+
organization?: ReactNode;
|
|
16
|
+
/** Adresse d'Account (un Account local en développement). */
|
|
17
|
+
accountHref?: string | undefined;
|
|
18
|
+
catalog?: ReadonlyArray<HgdcCatalogProduct> | undefined;
|
|
19
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { ProductBadge } from "./product-badge.js";
|
|
4
|
+
import { productRailEntries } from "./products.js";
|
|
5
|
+
import { ShellPlacementContext } from "./shell-placement.js";
|
|
6
|
+
import { cn } from "./cn.js";
|
|
7
|
+
/** Délai d'intention avant d'élargir la barre au survol : un passage de souris ne l'ouvre pas. */
|
|
8
|
+
export const PRODUCT_RAIL_OPEN_DELAY = 150;
|
|
9
|
+
function ProductLink({ entry }) {
|
|
10
|
+
return (_jsx("li", { children: _jsxs("a", { href: entry.href, "aria-current": entry.current ? "page" : undefined, className: cn("flex h-10 items-center gap-3 rounded-xl px-2 text-shell-muted outline-none transition-colors duration-(--duration-fast) ease-out", "hover:bg-shell-hover hover:text-shell-foreground focus-visible:ring-2 focus-visible:ring-ring/50", "aria-[current=page]:bg-shell-active aria-[current=page]:text-shell-foreground"), children: [_jsx(ProductBadge, { product: entry.badge, className: "size-6" }), _jsxs("span", { className: "min-w-0 flex-1 truncate opacity-0 transition-opacity duration-(--duration-fast) ease-out group-data-expanded/rail:opacity-100 motion-reduce:transition-none", children: [_jsx("span", { className: "font-heading text-sm font-medium", children: entry.name }), entry.hint ? _jsx("span", { className: "sr-only", children: ", " }) : null, entry.hint ? _jsx("span", { className: "ml-1.5 text-xs text-shell-muted", children: entry.hint }) : null] })] }) }));
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* La barre des produits de la famille, tout à gauche de la coque sur ordinateur : organisation en
|
|
14
|
+
* haut, pastilles des produits, Account en bas. 56 px de large ; au survol (après un court délai
|
|
15
|
+
* d'intention) ou au focus clavier, elle s'élargit par-dessus la page et écrit le nom de chaque
|
|
16
|
+
* produit, sans rien décaler. Elle se referme dès que la souris sort.
|
|
17
|
+
*/
|
|
18
|
+
export function ProductRail({ current, organization, accountHref, catalog, }) {
|
|
19
|
+
const [expanded, setExpanded] = useState(false);
|
|
20
|
+
const ref = useRef(null);
|
|
21
|
+
const { products, account } = productRailEntries({ current, accountHref, catalog });
|
|
22
|
+
// Écouteurs natifs : la barre est un repère `nav`, pas un contrôle ; c'est son survol et le
|
|
23
|
+
// focus de ses liens qui l'élargissent.
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const rail = ref.current;
|
|
26
|
+
if (!rail)
|
|
27
|
+
return;
|
|
28
|
+
let timer;
|
|
29
|
+
const cancel = () => {
|
|
30
|
+
window.clearTimeout(timer);
|
|
31
|
+
timer = undefined;
|
|
32
|
+
};
|
|
33
|
+
const onEnter = (event) => {
|
|
34
|
+
if (event.pointerType !== "mouse")
|
|
35
|
+
return;
|
|
36
|
+
cancel();
|
|
37
|
+
timer = window.setTimeout(() => setExpanded(true), PRODUCT_RAIL_OPEN_DELAY);
|
|
38
|
+
};
|
|
39
|
+
const onLeave = () => {
|
|
40
|
+
cancel();
|
|
41
|
+
setExpanded(false);
|
|
42
|
+
};
|
|
43
|
+
// Au clavier seulement : un clic de souris ne laisse pas la barre ouverte.
|
|
44
|
+
const onFocusIn = (event) => {
|
|
45
|
+
if (event.target && focusVisible(event.target))
|
|
46
|
+
setExpanded(true);
|
|
47
|
+
};
|
|
48
|
+
const onFocusOut = (event) => {
|
|
49
|
+
if (!(event.relatedTarget instanceof Node) || !rail.contains(event.relatedTarget))
|
|
50
|
+
setExpanded(false);
|
|
51
|
+
};
|
|
52
|
+
const onKey = (event) => {
|
|
53
|
+
if (event.key !== "Escape")
|
|
54
|
+
return;
|
|
55
|
+
cancel();
|
|
56
|
+
setExpanded(false);
|
|
57
|
+
};
|
|
58
|
+
rail.addEventListener("pointerenter", onEnter);
|
|
59
|
+
rail.addEventListener("pointerleave", onLeave);
|
|
60
|
+
rail.addEventListener("focusin", onFocusIn);
|
|
61
|
+
rail.addEventListener("focusout", onFocusOut);
|
|
62
|
+
rail.addEventListener("keydown", onKey);
|
|
63
|
+
return () => {
|
|
64
|
+
cancel();
|
|
65
|
+
rail.removeEventListener("pointerenter", onEnter);
|
|
66
|
+
rail.removeEventListener("pointerleave", onLeave);
|
|
67
|
+
rail.removeEventListener("focusin", onFocusIn);
|
|
68
|
+
rail.removeEventListener("focusout", onFocusOut);
|
|
69
|
+
rail.removeEventListener("keydown", onKey);
|
|
70
|
+
};
|
|
71
|
+
}, []);
|
|
72
|
+
return (_jsx("div", { "data-slot": "product-rail", className: "relative hidden w-14 shrink-0 lg:block", children: _jsxs("nav", { ref: ref, "aria-label": "Produits", "data-expanded": expanded ? "" : undefined, className: cn("group/rail absolute inset-y-0 left-0 z-40 flex w-14 flex-col gap-2 overflow-hidden bg-shell-rail px-2 py-3 text-shell-foreground", "transition-[width,box-shadow] duration-(--duration-base) ease-ios motion-reduce:transition-none",
|
|
73
|
+
// L'ombre seulement quand elle flotte ; en sombre, où l'ombre ne se voit pas, un filet.
|
|
74
|
+
"data-expanded:w-55 data-expanded:shadow-xl data-expanded:shadow-black/10 dark:data-expanded:shadow-black/50 dark:data-expanded:ring-1 dark:data-expanded:ring-white/8"), children: [organization ? (_jsxs(_Fragment, { children: [_jsx(ShellPlacementContext, { value: "product-rail", children: organization }), _jsx("div", { "aria-hidden": true, className: "mx-2 h-px shrink-0 bg-foreground/8" })] })) : null, _jsx("ul", { className: "grid min-h-0 gap-1 overflow-x-hidden overflow-y-auto", children: products.map((entry) => (_jsx(ProductLink, { entry: entry }, entry.badge))) }), _jsx("ul", { className: "mt-auto grid", children: _jsx(ProductLink, { entry: account }) })] }) }));
|
|
75
|
+
}
|
|
76
|
+
function focusVisible(target) {
|
|
77
|
+
if (!(target instanceof Element))
|
|
78
|
+
return false;
|
|
79
|
+
try {
|
|
80
|
+
return target.matches(":focus-visible");
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
package/dist/products.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { IconSvgElement } from "@hugeicons/react";
|
|
2
2
|
import { type EmailAccent } from "./email-palette.ts";
|
|
3
|
+
import { type FamilyProduct } from "./family-svg.ts";
|
|
3
4
|
/** Où en est un produit : seul ce qui répond s'ouvre depuis les menus. */
|
|
4
5
|
export type ProductStage = "live" | "preview" | "soon";
|
|
5
6
|
export declare const PRODUCT_STAGE_LABEL: Readonly<Record<ProductStage, string>>;
|
|
6
7
|
/** Un produit de la famille, tel que la vision le décrit. */
|
|
7
8
|
export type HgdcCatalogProduct = {
|
|
8
|
-
/** Le mot
|
|
9
|
+
/** Le mot du produit, qui sert de sous-domaine : « Host ». */
|
|
9
10
|
word: string;
|
|
10
|
-
/** «
|
|
11
|
+
/** Nom affiché, le mot seul (« Host ») : la famille ne se répète pas devant chaque produit. */
|
|
11
12
|
name: string;
|
|
12
13
|
/** « host.hgdc.ch ». */
|
|
13
14
|
subdomain: string;
|
|
@@ -20,6 +21,9 @@ export type HgdcCatalogProduct = {
|
|
|
20
21
|
entryHref: string;
|
|
21
22
|
/** Une phrase, au tutoiement. */
|
|
22
23
|
pitch: string;
|
|
24
|
+
/** Pastille du produit (`ProductBadge` de `@hgdc/ui/product-badge`) : le mot en minuscules. */
|
|
25
|
+
badge: FamilyProduct;
|
|
26
|
+
/** Pictogramme Hugeicons, pour une liste qui en veut un (palette, navigation). Le logo est `badge`. */
|
|
23
27
|
icon: IconSvgElement;
|
|
24
28
|
stage: ProductStage;
|
|
25
29
|
/**
|
|
@@ -39,12 +43,14 @@ export declare const HGDC_ACCOUNT: HgdcCatalogProduct;
|
|
|
39
43
|
export declare const HGDC_PRODUCTS: ReadonlyArray<HgdcCatalogProduct>;
|
|
40
44
|
/** Vrai si le produit répond déjà (en service ou en préversion). */
|
|
41
45
|
export declare function isProductOpen(item: Pick<HgdcCatalogProduct, "stage">): boolean;
|
|
42
|
-
/** Une entrée du menu « Produits
|
|
46
|
+
/** Une entrée du menu « Produits » d'`AccountMenu`. */
|
|
43
47
|
export type ProductMenuEntry = {
|
|
44
48
|
name: string;
|
|
45
49
|
href: string;
|
|
46
50
|
/** Le produit ouvert : coché, sans lien. */
|
|
47
51
|
current?: boolean;
|
|
52
|
+
/** Pastille du produit, affichée à la place de `icon`. */
|
|
53
|
+
badge?: FamilyProduct;
|
|
48
54
|
icon?: IconSvgElement;
|
|
49
55
|
/** Texte discret à droite : « Préversion ». */
|
|
50
56
|
hint?: string;
|
|
@@ -60,3 +66,27 @@ export declare function productMenuEntries(options?: {
|
|
|
60
66
|
accountHref?: string | undefined;
|
|
61
67
|
catalog?: ReadonlyArray<HgdcCatalogProduct> | undefined;
|
|
62
68
|
}): Array<ProductMenuEntry>;
|
|
69
|
+
/** Une entrée de la barre des produits d'`AppShell`. */
|
|
70
|
+
export type ProductRailEntry = {
|
|
71
|
+
name: string;
|
|
72
|
+
href: string;
|
|
73
|
+
badge: FamilyProduct;
|
|
74
|
+
/** Le produit ouvert : « vous êtes ici », lien vers sa propre racine. */
|
|
75
|
+
current?: boolean;
|
|
76
|
+
/** Texte discret sous le nom : « Préversion ». */
|
|
77
|
+
hint?: string;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Les entrées de la barre des produits : les produits ouverts du catalogue, puis Account, à part
|
|
81
|
+
* en bas (le compte commun, pas un produit). Chaque produit pointe vers son entrée
|
|
82
|
+
* `/auth/hgdc?next=/` (ADR 0010), Account vers sa racine (`accountHref` en développement). Le
|
|
83
|
+
* produit courant pointe vers `/`, sans détour par l'entrée.
|
|
84
|
+
*/
|
|
85
|
+
export declare function productRailEntries(options?: {
|
|
86
|
+
current?: string | undefined;
|
|
87
|
+
accountHref?: string | undefined;
|
|
88
|
+
catalog?: ReadonlyArray<HgdcCatalogProduct> | undefined;
|
|
89
|
+
}): {
|
|
90
|
+
products: Array<ProductRailEntry>;
|
|
91
|
+
account: ProductRailEntry;
|
|
92
|
+
};
|
package/dist/products.js
CHANGED
|
@@ -6,6 +6,7 @@ import Mail01Icon from "@hugeicons/core-free-icons/Mail01Icon";
|
|
|
6
6
|
import Rocket01Icon from "@hugeicons/core-free-icons/Rocket01Icon";
|
|
7
7
|
import UserCircleIcon from "@hugeicons/core-free-icons/UserCircleIcon";
|
|
8
8
|
import { HGDC_EMAIL_ACCENT } from "./email-palette.js";
|
|
9
|
+
import { isFamilyProduct } from "./family-svg.js";
|
|
9
10
|
export const PRODUCT_STAGE_LABEL = {
|
|
10
11
|
live: "En service",
|
|
11
12
|
preview: "Préversion",
|
|
@@ -18,22 +19,26 @@ export const PRODUCT_STAGE_LABEL = {
|
|
|
18
19
|
*/
|
|
19
20
|
export const PRODUCT_ENTRY_PATH = "/auth/hgdc";
|
|
20
21
|
function product(word, pitch, icon, stage) {
|
|
21
|
-
const
|
|
22
|
+
const badge = word.toLowerCase();
|
|
23
|
+
if (!isFamilyProduct(badge))
|
|
24
|
+
throw new Error(`@hgdc/ui/products : pas de pastille pour « ${word} »`);
|
|
25
|
+
const subdomain = `${badge}.hgdc.ch`;
|
|
22
26
|
const href = `https://${subdomain}`;
|
|
23
27
|
return {
|
|
24
28
|
word,
|
|
25
|
-
name:
|
|
29
|
+
name: word,
|
|
26
30
|
subdomain,
|
|
27
31
|
href,
|
|
28
32
|
entryHref: `${href}${PRODUCT_ENTRY_PATH}`,
|
|
29
33
|
pitch,
|
|
34
|
+
badge,
|
|
30
35
|
icon,
|
|
31
36
|
stage,
|
|
32
37
|
accent: HGDC_EMAIL_ACCENT,
|
|
33
38
|
};
|
|
34
39
|
}
|
|
35
40
|
/** HGDC Account : le compte commun (profil, sécurité, organisations), hors du catalogue des produits. */
|
|
36
|
-
const account = product("Account", "Ton compte
|
|
41
|
+
const account = product("Account", "Ton compte : profil, sécurité et organisations, pour tous les produits de la famille.", UserCircleIcon, "live");
|
|
37
42
|
export const HGDC_ACCOUNT = { ...account, entryHref: account.href };
|
|
38
43
|
/** Les produits de la famille, dans leur ordre d'affichage. Account n'en fait pas partie (`HGDC_ACCOUNT`). */
|
|
39
44
|
export const HGDC_PRODUCTS = [
|
|
@@ -61,8 +66,30 @@ export function productMenuEntries(options = {}) {
|
|
|
61
66
|
return [account, ...(options.catalog ?? HGDC_PRODUCTS).filter(isProductOpen)].map((item) => ({
|
|
62
67
|
name: item.name,
|
|
63
68
|
href: item.entryHref,
|
|
69
|
+
badge: item.badge,
|
|
64
70
|
icon: item.icon,
|
|
65
71
|
...(item.word.toLowerCase() === current ? { current: true } : {}),
|
|
66
72
|
...(item.stage === "preview" ? { hint: PRODUCT_STAGE_LABEL.preview } : {}),
|
|
67
73
|
}));
|
|
68
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Les entrées de la barre des produits : les produits ouverts du catalogue, puis Account, à part
|
|
77
|
+
* en bas (le compte commun, pas un produit). Chaque produit pointe vers son entrée
|
|
78
|
+
* `/auth/hgdc?next=/` (ADR 0010), Account vers sa racine (`accountHref` en développement). Le
|
|
79
|
+
* produit courant pointe vers `/`, sans détour par l'entrée.
|
|
80
|
+
*/
|
|
81
|
+
export function productRailEntries(options = {}) {
|
|
82
|
+
const current = options.current?.toLowerCase();
|
|
83
|
+
const entry = (item, href) => ({
|
|
84
|
+
name: item.name,
|
|
85
|
+
badge: item.badge,
|
|
86
|
+
...(item.badge === current ? { href: "/", current: true } : { href }),
|
|
87
|
+
...(item.stage === "preview" ? { hint: PRODUCT_STAGE_LABEL.preview } : {}),
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
products: (options.catalog ?? HGDC_PRODUCTS)
|
|
91
|
+
.filter(isProductOpen)
|
|
92
|
+
.map((item) => entry(item, `${item.entryHref}?next=/`)),
|
|
93
|
+
account: entry(HGDC_ACCOUNT, options.accountHref ?? HGDC_ACCOUNT.entryHref),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Où `AppShell` pose un élément du produit (le sélecteur d'organisation) : dans la barre latérale
|
|
3
|
+
* ou le tiroir mobile, ou en haut de la barre des produits, où il n'a qu'une colonne de 56 px.
|
|
4
|
+
*/
|
|
5
|
+
export type ShellPlacement = "sidebar" | "product-rail";
|
|
6
|
+
export declare const ShellPlacementContext: React.Context<ShellPlacement>;
|
|
7
|
+
export declare function useShellPlacement(): ShellPlacement;
|
package/email-icons/account.png
CHANGED
|
Binary file
|
|
Binary file
|
package/email-icons/docs.png
CHANGED
|
Binary file
|
package/email-icons/host.png
CHANGED
|
Binary file
|
package/email-icons/mail.png
CHANGED
|
Binary file
|
package/email-icons/portal.png
CHANGED
|
Binary file
|
package/email-icons/status.png
CHANGED
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="66 66 380 380"><defs><linearGradient id="sky" x1="96" y1="76" x2="416" y2="436" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ff3b47"/><stop offset="1" stop-color="#ff6352"/></linearGradient><linearGradient id="acc" x1="150" y1="160" x2="362" y2="352" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ff3b47"/><stop offset="1" stop-color="#ff6352"/></linearGradient></defs><circle cx="256" cy="256" r="190" fill="url(#sky)"/><rect x="128" y="162" width="256" height="188" rx="48" fill="#fff"/><g fill="url(#acc)"><circle cx="202" cy="232" r="30"/><path d="M154 314A48 44 0 0 1 250 314Z"/><rect x="276" y="216" width="72" height="24" rx="12"/><rect x="276" y="262" width="48" height="24" rx="12"/></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="66 66 380 380"><defs><linearGradient id="sky" x1="96" y1="76" x2="416" y2="436" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fe5623"/><stop offset="1" stop-color="#ffb003"/></linearGradient><linearGradient id="acc" x1="150" y1="160" x2="362" y2="352" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fe5623"/><stop offset="1" stop-color="#ffb003"/></linearGradient></defs><circle cx="256" cy="256" r="190" fill="url(#sky)"/><rect x="128" y="160" width="256" height="192" rx="48" fill="#fff"/><path fill="none" stroke="url(#acc)" stroke-width="30" stroke-linecap="round" d="M198 304V276M256 304V240M314 304V206"/></svg>
|
package/family/docs.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="66 66 380 380"><defs><linearGradient id="sky" x1="96" y1="76" x2="416" y2="436" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1bb9cf"/><stop offset="1" stop-color="#13e4c8"/></linearGradient><linearGradient id="acc" x1="150" y1="160" x2="362" y2="352" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1bb9cf"/><stop offset="1" stop-color="#13e4c8"/></linearGradient></defs><circle cx="256" cy="256" r="190" fill="url(#sky)"/><g fill="#fff"><path d="M248 196C222 176 184 168 146 174A14 14 0 0 0 134 188V324A12 12 0 0 0 148 336C184 332 220 338 248 356Z"/><path d="M264 196C290 176 328 168 366 174A14 14 0 0 1 378 188V324A12 12 0 0 1 364 336C328 332 292 338 264 356Z"/></g><path fill="none" stroke="url(#acc)" stroke-width="16" stroke-linecap="round" d="M166 226C188 224 208 228 224 238M166 266C188 264 208 268 224 278M288 238C304 228 324 224 346 226M288 278C304 268 324 264 346 266"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="43 24 991.1 293.5"><defs><linearGradient id="l" x1="0" y1="24" x2="0" y2="232" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2c78fd"/><stop offset="1" stop-color="#7343fe"/></linearGradient><linearGradient id="r" x1="0" y1="24" x2="0" y2="232" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fe5292"/><stop offset="1" stop-color="#ff9a46"/></linearGradient><linearGradient id="b" x1="64" y1="0" x2="192" y2="0" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#8145ff"/><stop offset="1" stop-color="#fe5292"/></linearGradient></defs><rect x="43" y="24" width="57" height="208" rx="18" fill="url(#l)"/><rect x="156" y="24" width="57" height="208" rx="18" fill="url(#r)"/><rect x="67" y="107" width="122" height="43" rx="14" fill="url(#b)"/><path fill="#fafafa" fill-rule="evenodd" d="M957.4 317.3C935 314.4 918.7 300.9 914.8 282C911.5 265.8 919.2 250 936.1 238.5C940.6 235.5 940.6 235.4 937.2 234.7C918.8 230.4 904.1 219.3 895.6 203.4C892.8 198.2 890 190.3 889.1 185.1C888.9 184.1 888.7 183.3 888.7 183.1C888.5 182.5 887.6 176.3 887.4 173.4C886.9 167.6 886.9 85.7 887.5 85.2C888 84.6 920.3 84.6 920.8 85.1C921 85.4 921.1 89 921.1 129C921.2 177.2 921.1 173.9 922.4 178.9C926.6 195.4 940 206.5 957.2 207.7C977.4 209.1 994.7 196.5 999.5 176.7C1000.8 171.3 1000.8 174.4 1000.8 127.5C1000.9 88.2 1000.9 85.2 1001.2 85C1001.4 84.8 1005 84.8 1017.6 84.8C1033.7 84.8 1033.7 84.8 1034 85.3C1034.5 86 1034.3 239.4 1033.8 244.1C1031.8 263.7 1027.6 275.8 1018 289.3C1004.4 308.5 979.3 320.1 957.4 317.3ZM970.7 289C979.9 287.4 988.4 281.2 993.7 272.2C996.4 267.8 998.8 260 999.7 253.2C1000.2 249.7 1000.5 220.8 1000.1 220C999.8 219.5 999.8 219.5 997.1 221.6C991.4 225.9 984 230.4 978 233.2C956.7 243.2 947.5 250.9 944 261.7C941.2 270.2 943.8 279 950.5 284.2C954 286.9 957.8 288.4 963.4 289.2C964.9 289.5 968.5 289.3 970.7 289ZM867 235.3C855.2 234.4 843.9 227.7 837.9 218.1C833 210.1 830.6 202.2 829.4 190.4C829.1 187.4 829 26.8 829.3 26C829.3 25.9 829.5 25.7 829.7 25.6C830.5 25.4 862.5 25.4 862.8 25.7C863.1 25.9 863.1 33.7 863.1 105.6C863.1 189.4 863.1 187.7 864 192.3C865.8 201.5 869.8 205.9 876.6 206.1C878.9 206.1 878.8 206 880 208.8C882.2 213.4 886.5 220.3 890.1 224.8C893.1 228.6 893.1 228.5 887.4 231.3C880.9 234.5 874 235.8 867 235.3ZM276 231.9C275.6 231.6 275.6 231.6 275.6 178.5C275.6 126.2 275.6 125.4 275.2 125.3C275 125.1 271.9 125.1 266.4 125.1C259.4 125.1 257.8 125 257.5 124.8C256.9 124.4 256.9 98.9 257.4 98.3C257.7 98 258.4 98 266.4 97.9C273.7 97.9 275 97.8 275.3 97.6C275.5 97.3 275.6 96.4 275.7 88.6C275.8 76.1 276.4 70.6 278.5 63.4C282.9 47.5 293.8 35.1 309.1 28.5C312.1 27.2 319.4 25.2 322.9 24.6C328.4 23.8 346.7 24 347.7 24.9C348.2 25.3 348.2 53.2 347.7 53.7C347.4 53.9 345.7 53.9 338.2 53.9C328.2 53.9 327.9 53.9 324 55.3C317.6 57.4 312.2 63.5 310.5 70.5C309.4 74.6 309.4 75.2 309.3 86.3C309.1 96.3 309.2 97.4 309.7 97.7C309.8 97.8 318.2 97.9 328.4 97.9C344.1 97.9 347 97.9 347.3 98.2C347.8 98.5 347.9 124.2 347.5 124.7C347.3 124.9 343.5 125 328.4 125.1C314.1 125.1 309.6 125.2 309.4 125.4C309.2 125.6 309.2 137.6 309.2 178.6C309.1 230.9 309.1 231.7 308.8 231.9C308.2 232.3 276.6 232.3 276 231.9ZM536.7 231.9C536.2 231.4 536.4 143.4 536.9 139.3C538 129.6 540 122.9 544.2 114.7C555.4 92.8 580.1 79.6 604.6 82.7C621.1 84.7 633.2 92 643.3 105.7C644.5 107.3 644.7 107.3 645.8 105.9C652.4 97.2 657.9 92.6 666.5 88.3C686.2 78.5 711.2 80.9 728.5 94.3C737.2 101 743 108.3 747.4 118C750.8 125.6 752.3 131.3 753.2 140.1C753.7 145.3 753.8 231.4 753.2 231.8C752.8 232.2 720.6 232.2 720.3 231.8C720.1 231.6 720 222.9 719.9 185.2C719.8 144.2 719.8 138.6 719.5 137.2C716.7 122.3 707.5 112.3 694.7 110.5C679.5 108.3 666 118 661.6 134C660.3 139 660.4 134.7 660.3 185.7C660.2 223 660.1 231.6 659.9 231.8C659.7 232.1 629.6 232.3 628.2 232.1C627.9 232 627.5 231.9 627.3 231.7C627.1 231.4 627 226.6 627 186.6C627 161.1 626.9 140.8 626.8 139.5C625.5 121.3 611.2 108.3 594.9 110.5C583.3 112.1 574.3 120.8 571.3 133.5C570.3 137.6 570.4 135.6 570.2 184.9C570.1 229.2 570.1 231.4 569.8 231.8C569.3 232.3 537.2 232.4 536.7 231.9ZM775.3 232C775 231.9 774.8 231.7 774.7 231.6C774.6 231.4 774.5 198.5 774.5 158.4C774.5 87.4 774.5 85.5 774.8 85.1C775.5 84.5 807.9 84.6 808.3 85.2C808.9 86 808.7 231.5 808.2 231.9C807.7 232.2 776 232.3 775.3 232ZM789.3 67.4C789 67.3 788 67.2 787.2 67C783.5 66.4 779.3 64.1 776.1 61.1C770.8 56 768.5 47.9 770.3 40.7C773.5 27.7 788.9 20.5 800.8 26.5C806.6 29.4 810.5 34 812.5 40.3C813.3 43.2 813.4 48.3 812.6 51.2C810.1 59.3 804 65.1 795.9 66.9C794.4 67.3 790.3 67.6 789.3 67.4ZM432.5 77.2A84.1 80.9 0 0 1 516.7 158.1L516.7 232L484.2 232L484.2 220.9A81.9 80.9 0 1 1 432.5 77.2ZM432.5 109.7A49.4 48.4 0 1 0 432.5 206.5A49.4 48.4 0 1 0 432.5 109.7Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><defs><linearGradient id="l" x1="0" y1="24" x2="0" y2="232" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2c78fd"/><stop offset="1" stop-color="#7343fe"/></linearGradient><linearGradient id="r" x1="0" y1="24" x2="0" y2="232" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fe5292"/><stop offset="1" stop-color="#ff9a46"/></linearGradient><linearGradient id="b" x1="64" y1="0" x2="192" y2="0" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#8145ff"/><stop offset="1" stop-color="#fe5292"/></linearGradient></defs><rect x="43" y="24" width="57" height="208" rx="18" fill="url(#l)"/><rect x="156" y="24" width="57" height="208" rx="18" fill="url(#r)"/><rect x="67" y="107" width="122" height="43" rx="14" fill="url(#b)"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="43 24 991.1 293.5"><defs><linearGradient id="l" x1="0" y1="24" x2="0" y2="232" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2c78fd"/><stop offset="1" stop-color="#7343fe"/></linearGradient><linearGradient id="r" x1="0" y1="24" x2="0" y2="232" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fe5292"/><stop offset="1" stop-color="#ff9a46"/></linearGradient><linearGradient id="b" x1="64" y1="0" x2="192" y2="0" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#8145ff"/><stop offset="1" stop-color="#fe5292"/></linearGradient></defs><rect x="43" y="24" width="57" height="208" rx="18" fill="url(#l)"/><rect x="156" y="24" width="57" height="208" rx="18" fill="url(#r)"/><rect x="67" y="107" width="122" height="43" rx="14" fill="url(#b)"/><path fill="#0a0a0a" fill-rule="evenodd" d="M957.4 317.3C935 314.4 918.7 300.9 914.8 282C911.5 265.8 919.2 250 936.1 238.5C940.6 235.5 940.6 235.4 937.2 234.7C918.8 230.4 904.1 219.3 895.6 203.4C892.8 198.2 890 190.3 889.1 185.1C888.9 184.1 888.7 183.3 888.7 183.1C888.5 182.5 887.6 176.3 887.4 173.4C886.9 167.6 886.9 85.7 887.5 85.2C888 84.6 920.3 84.6 920.8 85.1C921 85.4 921.1 89 921.1 129C921.2 177.2 921.1 173.9 922.4 178.9C926.6 195.4 940 206.5 957.2 207.7C977.4 209.1 994.7 196.5 999.5 176.7C1000.8 171.3 1000.8 174.4 1000.8 127.5C1000.9 88.2 1000.9 85.2 1001.2 85C1001.4 84.8 1005 84.8 1017.6 84.8C1033.7 84.8 1033.7 84.8 1034 85.3C1034.5 86 1034.3 239.4 1033.8 244.1C1031.8 263.7 1027.6 275.8 1018 289.3C1004.4 308.5 979.3 320.1 957.4 317.3ZM970.7 289C979.9 287.4 988.4 281.2 993.7 272.2C996.4 267.8 998.8 260 999.7 253.2C1000.2 249.7 1000.5 220.8 1000.1 220C999.8 219.5 999.8 219.5 997.1 221.6C991.4 225.9 984 230.4 978 233.2C956.7 243.2 947.5 250.9 944 261.7C941.2 270.2 943.8 279 950.5 284.2C954 286.9 957.8 288.4 963.4 289.2C964.9 289.5 968.5 289.3 970.7 289ZM867 235.3C855.2 234.4 843.9 227.7 837.9 218.1C833 210.1 830.6 202.2 829.4 190.4C829.1 187.4 829 26.8 829.3 26C829.3 25.9 829.5 25.7 829.7 25.6C830.5 25.4 862.5 25.4 862.8 25.7C863.1 25.9 863.1 33.7 863.1 105.6C863.1 189.4 863.1 187.7 864 192.3C865.8 201.5 869.8 205.9 876.6 206.1C878.9 206.1 878.8 206 880 208.8C882.2 213.4 886.5 220.3 890.1 224.8C893.1 228.6 893.1 228.5 887.4 231.3C880.9 234.5 874 235.8 867 235.3ZM276 231.9C275.6 231.6 275.6 231.6 275.6 178.5C275.6 126.2 275.6 125.4 275.2 125.3C275 125.1 271.9 125.1 266.4 125.1C259.4 125.1 257.8 125 257.5 124.8C256.9 124.4 256.9 98.9 257.4 98.3C257.7 98 258.4 98 266.4 97.9C273.7 97.9 275 97.8 275.3 97.6C275.5 97.3 275.6 96.4 275.7 88.6C275.8 76.1 276.4 70.6 278.5 63.4C282.9 47.5 293.8 35.1 309.1 28.5C312.1 27.2 319.4 25.2 322.9 24.6C328.4 23.8 346.7 24 347.7 24.9C348.2 25.3 348.2 53.2 347.7 53.7C347.4 53.9 345.7 53.9 338.2 53.9C328.2 53.9 327.9 53.9 324 55.3C317.6 57.4 312.2 63.5 310.5 70.5C309.4 74.6 309.4 75.2 309.3 86.3C309.1 96.3 309.2 97.4 309.7 97.7C309.8 97.8 318.2 97.9 328.4 97.9C344.1 97.9 347 97.9 347.3 98.2C347.8 98.5 347.9 124.2 347.5 124.7C347.3 124.9 343.5 125 328.4 125.1C314.1 125.1 309.6 125.2 309.4 125.4C309.2 125.6 309.2 137.6 309.2 178.6C309.1 230.9 309.1 231.7 308.8 231.9C308.2 232.3 276.6 232.3 276 231.9ZM536.7 231.9C536.2 231.4 536.4 143.4 536.9 139.3C538 129.6 540 122.9 544.2 114.7C555.4 92.8 580.1 79.6 604.6 82.7C621.1 84.7 633.2 92 643.3 105.7C644.5 107.3 644.7 107.3 645.8 105.9C652.4 97.2 657.9 92.6 666.5 88.3C686.2 78.5 711.2 80.9 728.5 94.3C737.2 101 743 108.3 747.4 118C750.8 125.6 752.3 131.3 753.2 140.1C753.7 145.3 753.8 231.4 753.2 231.8C752.8 232.2 720.6 232.2 720.3 231.8C720.1 231.6 720 222.9 719.9 185.2C719.8 144.2 719.8 138.6 719.5 137.2C716.7 122.3 707.5 112.3 694.7 110.5C679.5 108.3 666 118 661.6 134C660.3 139 660.4 134.7 660.3 185.7C660.2 223 660.1 231.6 659.9 231.8C659.7 232.1 629.6 232.3 628.2 232.1C627.9 232 627.5 231.9 627.3 231.7C627.1 231.4 627 226.6 627 186.6C627 161.1 626.9 140.8 626.8 139.5C625.5 121.3 611.2 108.3 594.9 110.5C583.3 112.1 574.3 120.8 571.3 133.5C570.3 137.6 570.4 135.6 570.2 184.9C570.1 229.2 570.1 231.4 569.8 231.8C569.3 232.3 537.2 232.4 536.7 231.9ZM775.3 232C775 231.9 774.8 231.7 774.7 231.6C774.6 231.4 774.5 198.5 774.5 158.4C774.5 87.4 774.5 85.5 774.8 85.1C775.5 84.5 807.9 84.6 808.3 85.2C808.9 86 808.7 231.5 808.2 231.9C807.7 232.2 776 232.3 775.3 232ZM789.3 67.4C789 67.3 788 67.2 787.2 67C783.5 66.4 779.3 64.1 776.1 61.1C770.8 56 768.5 47.9 770.3 40.7C773.5 27.7 788.9 20.5 800.8 26.5C806.6 29.4 810.5 34 812.5 40.3C813.3 43.2 813.4 48.3 812.6 51.2C810.1 59.3 804 65.1 795.9 66.9C794.4 67.3 790.3 67.6 789.3 67.4ZM432.5 77.2A84.1 80.9 0 0 1 516.7 158.1L516.7 232L484.2 232L484.2 220.9A81.9 80.9 0 1 1 432.5 77.2ZM432.5 109.7A49.4 48.4 0 1 0 432.5 206.5A49.4 48.4 0 1 0 432.5 109.7Z"/></svg>
|
package/family/host.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="66 66 380 380"><defs><linearGradient id="sky" x1="96" y1="76" x2="416" y2="436" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#194ff7"/><stop offset="1" stop-color="#02b4ff"/></linearGradient><linearGradient id="acc" x1="150" y1="160" x2="362" y2="352" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#194ff7"/><stop offset="1" stop-color="#02b4ff"/></linearGradient></defs><circle cx="256" cy="256" r="190" fill="url(#sky)"/><g transform="translate(0 6)"><g fill="#fff"><path d="M158.7 331.3A262.3 262.3 0 0 0 353.3 331.3L334 283H178Z"/><circle cx="178" cy="283" r="52"/><circle cx="334" cy="283" r="52"/><circle cx="214" cy="238" r="54"/><circle cx="288" cy="222" r="72"/><rect x="178" y="232" width="156" height="100"/></g><path fill="none" stroke="url(#acc)" stroke-width="24" stroke-linecap="round" stroke-linejoin="round" d="M256 302V234M224 266L256 234L288 266"/></g></svg>
|
package/family/mail.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="66 66 380 380"><defs><linearGradient id="sky" x1="96" y1="76" x2="416" y2="436" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f61f8e"/><stop offset="1" stop-color="#ff5cb8"/></linearGradient><linearGradient id="acc" x1="150" y1="160" x2="362" y2="352" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f61f8e"/><stop offset="1" stop-color="#ff5cb8"/></linearGradient></defs><circle cx="256" cy="256" r="190" fill="url(#sky)"/><rect x="128" y="162" width="256" height="188" rx="56" fill="#fff"/><path fill="url(#acc)" d="M182 194H330A22 22 0 0 1 341.82 234.56L271.04 279.62A28 28 0 0 1 240.96 279.62L170.18 234.56A22 22 0 0 1 182 194Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="66 66 380 380"><defs><linearGradient id="sky" x1="96" y1="76" x2="416" y2="436" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#4732ed"/><stop offset="1" stop-color="#b03cff"/></linearGradient><linearGradient id="acc" x1="150" y1="160" x2="362" y2="352" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#4732ed"/><stop offset="1" stop-color="#b03cff"/></linearGradient></defs><circle cx="256" cy="256" r="190" fill="url(#sky)"/><path fill="#fff" d="M168 160H222A24 24 0 0 1 240 168L258 188H344A40 40 0 0 1 384 228V316A40 40 0 0 1 344 356H168A40 40 0 0 1 128 316V200A40 40 0 0 1 168 160Z"/><path fill="none" stroke="url(#acc)" stroke-width="30" stroke-linecap="round" stroke-linejoin="round" d="M212 272L244 302L304 240"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="66 66 380 380"><defs><linearGradient id="sky" x1="96" y1="76" x2="416" y2="436" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#199f62"/><stop offset="1" stop-color="#56db29"/></linearGradient><linearGradient id="acc" x1="150" y1="160" x2="362" y2="352" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#199f62"/><stop offset="1" stop-color="#56db29"/></linearGradient></defs><circle cx="256" cy="256" r="190" fill="url(#sky)"/><rect x="128" y="160" width="256" height="192" rx="48" fill="#fff"/><path fill="none" stroke="url(#acc)" stroke-width="26" stroke-linecap="round" stroke-linejoin="round" d="M164 262H206L230 214L262 304L288 248H348"/></svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hgdc/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
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",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"dist",
|
|
19
19
|
"styles.css",
|
|
20
|
-
"email-icons"
|
|
20
|
+
"email-icons",
|
|
21
|
+
"family"
|
|
21
22
|
],
|
|
22
23
|
"sideEffects": [
|
|
23
24
|
"*.css"
|
|
@@ -26,6 +27,7 @@
|
|
|
26
27
|
"./styles.css": "./styles.css",
|
|
27
28
|
"./package.json": "./package.json",
|
|
28
29
|
"./email-icons/*": "./email-icons/*",
|
|
30
|
+
"./family/*": "./family/*",
|
|
29
31
|
"./*": {
|
|
30
32
|
"types": "./dist/*.d.ts",
|
|
31
33
|
"default": "./dist/*.js"
|
package/styles.css
CHANGED
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
--color-shell-muted: var(--shell-muted-foreground);
|
|
64
64
|
--color-shell-hover: var(--shell-hover);
|
|
65
65
|
--color-shell-active: var(--shell-active);
|
|
66
|
+
--color-shell-rail: var(--shell-rail);
|
|
66
67
|
--color-success: var(--success);
|
|
67
68
|
--color-success-foreground: var(--success-foreground);
|
|
68
69
|
--color-status-active: var(--status-active);
|
|
@@ -135,6 +136,8 @@
|
|
|
135
136
|
--shell-muted-foreground: oklch(0.45 0 0);
|
|
136
137
|
--shell-hover: oklch(0 0 0 / 5%);
|
|
137
138
|
--shell-active: oklch(0 0 0 / 8%);
|
|
139
|
+
/* Barre des produits, un cran sous la coque. */
|
|
140
|
+
--shell-rail: oklch(0.93 0 0);
|
|
138
141
|
/* Séries de graphique, dans cet ordre, jamais recyclées : une 7e série se replie en « Autres ».
|
|
139
142
|
Validées (daltonisme, écart entre voisines) sur `surface` en clair et sur `card` en sombre.
|
|
140
143
|
En clair, 2 à 5 passent sous 3:1 : un graphique les accompagne d'une légende et de libellés. */
|
|
@@ -198,6 +201,7 @@
|
|
|
198
201
|
--shell-muted-foreground: oklch(0.708 0 0);
|
|
199
202
|
--shell-hover: oklch(1 0 0 / 6%);
|
|
200
203
|
--shell-active: oklch(1 0 0 / 12%);
|
|
204
|
+
--shell-rail: oklch(0.05 0 0);
|
|
201
205
|
--chart-1: oklch(0.622 0.161 255.1);
|
|
202
206
|
--chart-2: oklch(0.622 0.173 40.1);
|
|
203
207
|
--chart-3: oklch(0.621 0.128 163.1);
|
|
@@ -216,6 +220,31 @@
|
|
|
216
220
|
--inverse: oklch(0.1 0 0);
|
|
217
221
|
}
|
|
218
222
|
|
|
223
|
+
/* Identité H family, la même en clair et en sombre. Dégradé de chaque pastille produit
|
|
224
|
+
(`ProductBadge`, `family/<mot>.svg`) et palette du H tricolore. Réservés aux visuels de marque
|
|
225
|
+
(pastille, carte de produit), jamais au chrome. Couleurs pleines ; pour éclaircir, mélanger au
|
|
226
|
+
blanc `in oklab`, jamais `in oklch` (la teinte du blanc s'y mêle, tout vire au rose). */
|
|
227
|
+
:root {
|
|
228
|
+
--product-account-from: #ff3b47;
|
|
229
|
+
--product-account-to: #ff6352;
|
|
230
|
+
--product-host-from: #194ff7;
|
|
231
|
+
--product-host-to: #02b4ff;
|
|
232
|
+
--product-mail-from: #f61f8e;
|
|
233
|
+
--product-mail-to: #ff5cb8;
|
|
234
|
+
--product-portal-from: #4732ed;
|
|
235
|
+
--product-portal-to: #b03cff;
|
|
236
|
+
--product-status-from: #199f62;
|
|
237
|
+
--product-status-to: #56db29;
|
|
238
|
+
--product-analytics-from: #fe5623;
|
|
239
|
+
--product-analytics-to: #ffb003;
|
|
240
|
+
--product-docs-from: #1bb9cf;
|
|
241
|
+
--product-docs-to: #13e4c8;
|
|
242
|
+
--glow-blue: #2c78fd;
|
|
243
|
+
--glow-violet: #7343fe;
|
|
244
|
+
--glow-pink: #fe5292;
|
|
245
|
+
--glow-orange: #ff9a46;
|
|
246
|
+
}
|
|
247
|
+
|
|
219
248
|
@layer base {
|
|
220
249
|
/* Une grille sans colonnes déclarées a une seule piste qui ne s'élargit jamais pour un
|
|
221
250
|
texte long : sans ça, `min-width: auto` pousse la page en défilement horizontal sur
|