@hgdc/ui 0.47.1 → 0.49.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 CHANGED
@@ -159,14 +159,15 @@ Filtres, tri et page vivent dans l'URL du produit : les composants sont contrôl
159
159
 
160
160
  ## Formulaires et réglages
161
161
 
162
- | Import | Exemple |
163
- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
164
- | `settings-section` | `<SettingsSection title="Nom de l'espace" description="…" dirty={changed} saving={saving} onSave={save}>…</SettingsSection>` : un Enregistrer par bloc |
165
- | `date-range-picker` | `<DateRangePicker value={{ from: "2026-10-01", to: "2026-10-15" }} onChange={(range, preset) => …} />` : raccourcis et période libre |
166
- | `date-time-field` | `<DateTimeField value={sendAt} onChange={setSendAt} min={Date.now()} />` : millisecondes, heure du navigateur |
167
- | `color-field` | `<ColorField value="#7c3aed" onChange={…} />` : pastille, code hexadécimal, contraste avec le texte blanc et son verdict |
168
- | `money-field` | `<MoneyField value={centimes} onChange={…} />` : CHF en centimes, « 1’250.50 », accepte « 12.– » ; `formatChf`, `parseMoney` |
169
- | `address-fields` | `<AddressFields value={address} onChange={…} errors={validatePostalAddress(address)} />` : rue, numéro, NPA, localité, pays (facture QR) |
162
+ | Import | Exemple |
163
+ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
164
+ | `settings-section` | `<SettingsSection title="Nom de l'espace" description="…" dirty={changed} saving={saving} onSave={save}>…</SettingsSection>` : un Enregistrer par bloc |
165
+ | `date-range-picker` | `<DateRangePicker value={{ from: "2026-10-01", to: "2026-10-15" }} onChange={(range, preset) => …} />` : raccourcis et période libre |
166
+ | `date-time-field` | `<DateTimeField value={sendAt} onChange={setSendAt} min={Date.now()} />` : millisecondes, heure du navigateur |
167
+ | `color-field` | `<ColorField value="#7c3aed" onChange={…} />` : pastille, code hexadécimal, contraste avec le texte blanc et son verdict |
168
+ | `money-field` | `<MoneyField value={centimes} onChange={…} />` : CHF en centimes, « 1’250.50 », accepte « 12.– » ; `formatChf`, `parseMoney` |
169
+ | `address-fields` | `<AddressFields value={address} onChange={…} errors={validatePostalAddress(address)} />` : rue, numéro, NPA, localité, pays (facture QR) |
170
+ | `floating-field` | `<FloatingField label="Adresse e-mail" type="email" error={message} surface="card" />` : libellé dans le champ, monté sur le filet au focus (connexion) |
170
171
 
171
172
  `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.
172
173
 
@@ -0,0 +1,24 @@
1
+ import { type ReactNode } from "react";
2
+ /**
3
+ * Champ à libellé flottant, pour les écrans de connexion : le libellé est dans le champ vide et
4
+ * monte sur le filet dès que le champ a le focus ou une valeur (saisie, autoremplissage).
5
+ *
6
+ * Tout en CSS : `placeholder=" "` rend `:placeholder-shown` vrai tant que le champ est vide, le
7
+ * libellé (`peer-…`) suit sans JavaScript, dès le rendu serveur. Le libellé posé sur le filet
8
+ * reprend le fond de la surface où le champ est posé (`surface`, fond de page par défaut). Texte à
9
+ * 16 px : sous 16 px, Safari zoome au focus.
10
+ *
11
+ * L'erreur et la description sont citées par `aria-describedby` ; l'erreur garde une icône, pour ne
12
+ * pas reposer sur la seule couleur (WCAG 1.4.1).
13
+ */
14
+ declare function FloatingField({ label, error, description, surface, className, id, ...props }: Omit<React.ComponentProps<"input">, "placeholder"> & {
15
+ /** Libellé visible, aussi nom accessible du champ. */
16
+ label: string;
17
+ /** Message d'erreur sous le champ ; le champ passe en `aria-invalid`. */
18
+ error?: ReactNode;
19
+ /** Aide sous le champ, quand il n'y a pas d'erreur. */
20
+ description?: ReactNode;
21
+ /** Fond sous le champ : le libellé posé sur le filet le reprend. */
22
+ surface?: "background" | "card";
23
+ }): React.JSX.Element;
24
+ export { FloatingField };
@@ -0,0 +1,29 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import AlertCircleIcon from "@hugeicons/core-free-icons/AlertCircleIcon";
4
+ import { HugeiconsIcon } from "@hugeicons/react";
5
+ import { useId } from "react";
6
+ import { cn } from "./cn.js";
7
+ /**
8
+ * Champ à libellé flottant, pour les écrans de connexion : le libellé est dans le champ vide et
9
+ * monte sur le filet dès que le champ a le focus ou une valeur (saisie, autoremplissage).
10
+ *
11
+ * Tout en CSS : `placeholder=" "` rend `:placeholder-shown` vrai tant que le champ est vide, le
12
+ * libellé (`peer-…`) suit sans JavaScript, dès le rendu serveur. Le libellé posé sur le filet
13
+ * reprend le fond de la surface où le champ est posé (`surface`, fond de page par défaut). Texte à
14
+ * 16 px : sous 16 px, Safari zoome au focus.
15
+ *
16
+ * L'erreur et la description sont citées par `aria-describedby` ; l'erreur garde une icône, pour ne
17
+ * pas reposer sur la seule couleur (WCAG 1.4.1).
18
+ */
19
+ function FloatingField({ label, error, description, surface = "background", className, id, ...props }) {
20
+ const generated = useId();
21
+ const inputId = id ?? generated;
22
+ const errorId = `${inputId}-error`;
23
+ const descriptionId = `${inputId}-description`;
24
+ const describedBy = [error ? errorId : null, !error && description ? descriptionId : null, props["aria-describedby"]]
25
+ .filter(Boolean)
26
+ .join(" ");
27
+ return (_jsxs("div", { "data-slot": "floating-field", className: cn("grid gap-1.5", className), children: [_jsxs("div", { className: "relative", children: [_jsx("input", { ...props, id: inputId, placeholder: " ", "aria-invalid": error ? true : props["aria-invalid"], "aria-describedby": describedBy || undefined, className: "peer touch-field h-14 w-full min-w-0 rounded-md bg-transparent px-3.5 text-base text-foreground ring-1 ring-control-off transition-[box-shadow] duration-(--duration-fast) outline-none ring-inset focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-2 aria-invalid:ring-destructive" }), _jsx("label", { htmlFor: inputId, className: cn("pointer-events-none absolute top-1/2 left-2.5 max-w-[calc(100%-1.25rem)] -translate-y-1/2 truncate px-1 text-base text-muted-foreground transition-[top,font-size,color] duration-(--duration-fast) ease-(--ease-out) peer-focus-visible:top-0 peer-focus-visible:text-xs peer-focus-visible:text-accent peer-aria-invalid:text-destructive peer-[:not(:placeholder-shown)]:top-0 peer-[:not(:placeholder-shown)]:text-xs motion-reduce:transition-none", surface === "card" ? "bg-card" : "bg-background"), children: label })] }), error ? (_jsxs("p", { id: errorId, "data-slot": "floating-field-error", className: "flex gap-2 text-sm text-destructive", children: [_jsx(HugeiconsIcon, { icon: AlertCircleIcon, "aria-hidden": true, className: "mt-0.5 size-4 shrink-0" }), _jsx("span", { children: error })] })) : description ? (_jsx("p", { id: descriptionId, "data-slot": "floating-field-description", className: "text-sm text-muted-foreground", children: description })) : null] }));
28
+ }
29
+ export { FloatingField };
@@ -23,6 +23,11 @@ export type HgdcCatalogProduct = {
23
23
  * 0016). Pour Account, sa racine.
24
24
  */
25
25
  entryHref: string;
26
+ /**
27
+ * Page du produit sur le site : `https://hgdc.ch/h-collection/<mot>`. Absente tant que le produit
28
+ * est « soon » : sa page n'existe pas encore.
29
+ */
30
+ productPage?: string | undefined;
26
31
  /** Une phrase, au tutoiement. */
27
32
  pitch: string;
28
33
  /** Pastille du produit (`ProductBadge` de `@hgdc/ui/product-badge`) : le mot en minuscules. */
@@ -41,6 +46,8 @@ export type HgdcCatalogProduct = {
41
46
  * le monte (`createHgdcEntry`). Il ouvre l'app avec la session HGDC Account, sans formulaire
42
47
  * quand cette session existe.
43
48
  */
49
+ /** Base des pages produit du site : `<base>/<mot>`. */
50
+ export declare const PRODUCT_PAGE_BASE = "https://hgdc.ch/h-collection";
44
51
  export declare const PRODUCT_ENTRY_PATH = "/auth/hgdc";
45
52
  /** Où un produit est servi : l'origine et, s'il partage son hôte avec d'autres, sa base de chemin. */
46
53
  export type ProductOrigin = {
package/dist/products.js CHANGED
@@ -19,6 +19,8 @@ export const PRODUCT_STAGE_LABEL = {
19
19
  * le monte (`createHgdcEntry`). Il ouvre l'app avec la session HGDC Account, sans formulaire
20
20
  * quand cette session existe.
21
21
  */
22
+ /** Base des pages produit du site : `<base>/<mot>`. */
23
+ export const PRODUCT_PAGE_BASE = "https://hgdc.ch/h-collection";
22
24
  export const PRODUCT_ENTRY_PATH = "/auth/hgdc";
23
25
  /**
24
26
  * Adresse de l'app de chaque produit servi sous `https://app.hgdc.ch/<mot>`, par mot en minuscules,
@@ -56,6 +58,7 @@ function product(word, pitch, icon, stage) {
56
58
  subdomain,
57
59
  href,
58
60
  entryHref: productEntryHref(badge),
61
+ ...(stage === "soon" ? {} : { productPage: `${PRODUCT_PAGE_BASE}/${badge}` }),
59
62
  pitch,
60
63
  badge,
61
64
  icon,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hgdc/ui",
3
- "version": "0.47.1",
3
+ "version": "0.49.0",
4
4
  "description": "Interface de la H Collection : jetons de la DA, thèmes, polices et composants.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://github.com/hugogebel-boop/hgdc-kit/tree/main/packages/ui#readme",