@hgdc/ui 0.2.0 → 0.2.1

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
@@ -41,7 +41,11 @@ Un sous-chemin par composant, sans fichier d'index : `import { Badge } from "@hg
41
41
  - Chrome monochrome, un seul accent violet : focus, lien, élément actif, un geste principal par écran.
42
42
  - Profondeur par la teinte : ni ombre ni bordure dans le flux, ombre réservée à ce qui flotte.
43
43
  - Boutons en pilule, champs et menus en `rounded-2xl`, cartes en `rounded-3xl`.
44
+ - Au survol, seule la couleur change : ni ombre, ni zoom. Le rebond est réservé à l'appui.
45
+ - Couleurs de statut : la couleur vive pour les pastilles, son encre (`text-status-active-ink`, `text-info-ink`…) pour le texte. L'information est bleue, le violet reste l'accent.
46
+ - Chargement : l'anneau de la DA, `<Spinner />` dans un bouton, `<Spinner size={8} tone="accent" />` seul sur une page.
44
47
  - Un `Button` rendu en lien (`render={<Link />}`) reste un lien pour les lecteurs d'écran.
48
+ - Référence de l'interface : Host. Couleur d'un client : `--brand-accent`.
45
49
 
46
50
  Mode d'emploi complet : [Adopter le kit](https://github.com/hugogebel-boop/hgdc-kit/blob/main/docs/guides/adopter-le-kit.md#7-interface). Décisions : [ADR 0005](https://github.com/hugogebel-boop/hgdc-kit/blob/main/docs/adr/0005-ui-css-brute-et-sous-chemins.md).
47
51
 
package/dist/badge.js CHANGED
@@ -5,9 +5,9 @@ const badgeVariants = cva("inline-flex w-fit items-center gap-1 rounded-full px-
5
5
  variants: {
6
6
  tone: {
7
7
  neutral: "bg-muted text-muted-foreground",
8
- success: "bg-status-active/15 text-status-active",
9
- warning: "bg-status-paused/15 text-status-paused",
10
- info: "bg-status-done/15 text-status-done",
8
+ success: "bg-status-active/15 text-status-active-ink",
9
+ warning: "bg-status-paused/15 text-status-paused-ink",
10
+ info: "bg-info/12 text-info-ink",
11
11
  destructive: "bg-destructive/10 text-destructive",
12
12
  },
13
13
  },
package/dist/button.js CHANGED
@@ -22,17 +22,18 @@ const buttonVariants = cva(
22
22
  // regarde à cet instant précis ; la relâche revient en 300 ms avec un petit
23
23
  // rebond (`--ease-bounce`, révision du 2026-09-24).
24
24
  //
25
- // Au survol d'un vrai pointeur (`hover:` ne s'applique qu'aux pointeurs
26
- // fins en Tailwind v4), le bouton s'avance d'un rien (1.02), comme sur le
27
- // site ; aucune ombre, jamais : ce qui répond au survol est la couleur et
28
- // l'échelle.
25
+ // Au survol, seule la couleur change : ni ombre, ni échelle. Le zoom de
26
+ // 1.02 faisait clignoter le libellé (le texte passe sur un calque GPU au
27
+ // lissage différent pendant l'animation, puis revient), et la couleur, qui
28
+ // suivait la courbe à rebond, dépassait sa cible : un éclair sur `ghost`.
29
+ // La couleur suit donc `--ease-out` en 150 ms ; le rebond reste à l'échelle.
29
30
  //
30
31
  // Ni bordure transparente ni `bg-clip-padding` (retirés le 2026-09-27) : le
31
32
  // fond s'arrêtait un pixel avant le bord, et dans une feuille du bas posée à
32
33
  // une coordonnée fractionnaire (hauteur en `svh`, glissement), iOS laissait
33
34
  // voir ce pixel comme un filet sombre en haut de la pilule, en thème sombre.
34
35
  // Le focus et l'erreur passent par l'anneau seul.
35
- "group/button touch-target inline-flex shrink-0 items-center justify-center rounded-full text-sm font-medium whitespace-nowrap [transition-property:color,background-color,opacity,scale,box-shadow] duration-(--duration-slow) ease-(--ease-bounce) outline-none select-none focus-visible:ring-3 focus-visible:ring-ring/50 active:scale-[0.96] active:duration-(--duration-instant) active:ease-out disabled:pointer-events-none disabled:opacity-50 aria-invalid:ring-3 aria-invalid:ring-destructive/40 data-[pressed]:scale-[0.96] data-[pressed]:duration-(--duration-instant) data-[pressed]:ease-out motion-safe:hover:scale-[1.02] dark:aria-invalid:ring-destructive/50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
36
+ "group/button touch-target inline-flex shrink-0 items-center justify-center rounded-full text-sm font-medium whitespace-nowrap [transition:color_var(--duration-fast)_var(--ease-out),background-color_var(--duration-fast)_var(--ease-out),box-shadow_var(--duration-fast)_var(--ease-out),opacity_var(--duration-fast)_var(--ease-out),scale_var(--duration-slow)_var(--ease-bounce)] outline-none select-none focus-visible:ring-3 focus-visible:ring-ring/50 active:scale-[0.96] active:duration-(--duration-instant) active:ease-out disabled:pointer-events-none disabled:opacity-50 aria-invalid:ring-3 aria-invalid:ring-destructive/40 data-[pressed]:scale-[0.96] data-[pressed]:duration-(--duration-instant) data-[pressed]:ease-out dark:aria-invalid:ring-destructive/50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
36
37
  variants: {
37
38
  variant: {
38
39
  default: "bg-primary text-primary-foreground hover:bg-primary/85",
@@ -13,9 +13,7 @@ import { cn } from "./cn.js";
13
13
  * signification reste atteignable, elle n'occupe plus la place.
14
14
  */
15
15
  export function SegmentedControl({ value, onValueChange, options, size = "md", label, className, }) {
16
- return (_jsx("div", { role: "group", "aria-label": label, className: cn(
17
- // Fond `muted` plutôt qu'un contour (ADR 0013).
18
- "inline-flex w-fit max-w-full shrink-0 overflow-hidden rounded-3xl bg-muted p-1", className), children: options.map((opt) => {
16
+ return (_jsx("div", { role: "group", "aria-label": label, className: cn("inline-flex w-fit max-w-full shrink-0 overflow-hidden rounded-3xl border border-border bg-background p-1", className), children: options.map((opt) => {
19
17
  const active = value === opt.value;
20
18
  return (_jsx("button", { type: "button", "aria-pressed": active, "aria-label": opt.icon ? opt.label : undefined, title: opt.icon ? opt.label : undefined, onClick: () => onValueChange(opt.value), className: cn(
21
19
  // Pas de flex-1 : une demi-colonne trop étroite rend le segment
package/dist/sonner.js CHANGED
@@ -2,10 +2,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import Alert02Icon from "@hugeicons/core-free-icons/Alert02Icon";
3
3
  import CheckmarkCircle02Icon from "@hugeicons/core-free-icons/CheckmarkCircle02Icon";
4
4
  import InformationCircleIcon from "@hugeicons/core-free-icons/InformationCircleIcon";
5
- import Loading03Icon from "@hugeicons/core-free-icons/Loading03Icon";
6
5
  import MultiplicationSignCircleIcon from "@hugeicons/core-free-icons/MultiplicationSignCircleIcon";
7
6
  import { HugeiconsIcon } from "@hugeicons/react";
8
7
  import { Toaster as Sonner } from "sonner";
8
+ import { Spinner } from "./spinner.js";
9
9
  import { useTheme } from "./theme.js";
10
10
  function Toaster(props) {
11
11
  const { theme } = useTheme();
@@ -18,7 +18,7 @@ function Toaster(props) {
18
18
  info: _jsx(HugeiconsIcon, { icon: InformationCircleIcon, strokeWidth: 2, className: "size-4" }),
19
19
  warning: _jsx(HugeiconsIcon, { icon: Alert02Icon, strokeWidth: 2, className: "size-4" }),
20
20
  error: _jsx(HugeiconsIcon, { icon: MultiplicationSignCircleIcon, strokeWidth: 2, className: "size-4" }),
21
- loading: _jsx(HugeiconsIcon, { icon: Loading03Icon, strokeWidth: 2, className: "size-4 animate-spin" }),
21
+ loading: (_jsx("span", { "aria-hidden": true, children: _jsx(Spinner, {}) })),
22
22
  }, style: {
23
23
  "--normal-bg": "var(--popover)",
24
24
  "--normal-text": "var(--popover-foreground)",
package/dist/spinner.d.ts CHANGED
@@ -1,9 +1,20 @@
1
+ declare const SIZES: {
2
+ readonly 3: "size-3";
3
+ readonly 4: "size-4";
4
+ readonly 5: "size-5";
5
+ readonly 6: "size-6";
6
+ readonly 8: "size-8";
7
+ };
1
8
  /**
2
- * Inline spinner - Hugeicons `Loading03Icon` + animate-spin.
3
- * Hugeicons gardent l'identite DA, contrairement a un raw SVG ou un Tailwind border-spin.
9
+ * L'anneau d'attente de la DA : un arc arrondi qui tourne sur une piste à peine visible, le
10
+ * même que la page d'attente du portail. Dans un bouton ou une ligne, il prend la couleur du
11
+ * texte (`tone="current"`) ; seul sur une page, il prend l'accent (`tone="accent"`).
12
+ * Mouvement réduit : l'arc ne tourne plus, il respire.
4
13
  */
5
- export declare function Spinner({ className, label, size, }: {
14
+ export declare function Spinner({ className, label, size, tone, }: {
6
15
  className?: string;
7
16
  label?: string;
8
- size?: 3 | 4 | 5 | 6;
17
+ size?: keyof typeof SIZES;
18
+ tone?: "current" | "accent";
9
19
  }): React.JSX.Element;
20
+ export {};
package/dist/spinner.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import Loading03Icon from "@hugeicons/core-free-icons/Loading03Icon";
3
- import { HugeiconsIcon } from "@hugeicons/react";
4
2
  import { cn } from "./cn.js";
3
+ const SIZES = { 3: "size-3", 4: "size-4", 5: "size-5", 6: "size-6", 8: "size-8" };
5
4
  /**
6
- * Inline spinner - Hugeicons `Loading03Icon` + animate-spin.
7
- * Hugeicons gardent l'identite DA, contrairement a un raw SVG ou un Tailwind border-spin.
5
+ * L'anneau d'attente de la DA : un arc arrondi qui tourne sur une piste à peine visible, le
6
+ * même que la page d'attente du portail. Dans un bouton ou une ligne, il prend la couleur du
7
+ * texte (`tone="current"`) ; seul sur une page, il prend l'accent (`tone="accent"`).
8
+ * Mouvement réduit : l'arc ne tourne plus, il respire.
8
9
  */
9
- export function Spinner({ className, label, size = 4, }) {
10
- return (_jsxs("span", { className: cn("inline-flex items-center gap-2", className), children: [_jsx(HugeiconsIcon, { icon: Loading03Icon, strokeWidth: 2, className: cn("animate-spin", size === 3 && "size-3", size === 4 && "size-4", size === 5 && "size-5", size === 6 && "size-6") }), label ? _jsx("span", { children: label }) : null] }));
10
+ export function Spinner({ className, label, size = 4, tone = "current", }) {
11
+ return (_jsxs("span", { role: "status", className: cn("inline-flex items-center gap-2", className), children: [_jsxs("svg", { viewBox: "0 0 32 32", fill: "none", "aria-hidden": true, className: cn("shrink-0", SIZES[size]), children: [_jsx("circle", { cx: "16", cy: "16", r: "13", strokeWidth: "4", className: "stroke-current opacity-15" }), _jsx("circle", { cx: "16", cy: "16", r: "13", strokeWidth: "4", strokeLinecap: "round", strokeDasharray: "22 60", className: cn("origin-center animate-spin motion-reduce:animate-pulse", tone === "accent" ? "stroke-accent" : "stroke-current") })] }), label ? _jsx("span", { children: label }) : _jsx("span", { className: "sr-only", children: "Chargement" })] }));
11
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hgdc/ui",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
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",
package/styles.css CHANGED
@@ -55,7 +55,6 @@
55
55
  --color-border: var(--border);
56
56
  --color-input: var(--input);
57
57
  --color-ring: var(--ring);
58
- --color-surface-hover: var(--surface-hover);
59
58
  --color-shell: var(--shell);
60
59
  --color-shell-foreground: var(--shell-foreground);
61
60
  --color-shell-muted: var(--shell-muted-foreground);
@@ -66,6 +65,10 @@
66
65
  --color-status-active: var(--status-active);
67
66
  --color-status-paused: var(--status-paused);
68
67
  --color-status-done: var(--status-done);
68
+ --color-status-active-ink: var(--status-active-ink);
69
+ --color-status-paused-ink: var(--status-paused-ink);
70
+ --color-info: var(--info);
71
+ --color-info-ink: var(--info-ink);
69
72
  --color-chart-1: var(--chart-1);
70
73
  --color-inverse: var(--inverse);
71
74
  --color-inverse-foreground: var(--inverse-foreground);
@@ -87,26 +90,27 @@
87
90
  --foreground: oklch(0.145 0 0);
88
91
  --card: oklch(1 0 0);
89
92
  --card-foreground: oklch(0.145 0 0);
90
- --popover: oklch(1 0 0);
91
- --popover-foreground: oklch(0.145 0 0);
93
+ /* Synonymes gardés pour les primitives shadcn : ils suivent leur jeton de rôle. */
94
+ --popover: var(--card);
95
+ --popover-foreground: var(--card-foreground);
92
96
  /* Menus et sélecteurs (`floating.ts`) : fond opaque, un cran au-dessus de la carte en sombre. */
93
97
  --floating: oklch(1 0 0);
94
98
  --floating-muted: oklch(0.97 0 0);
95
99
  --primary: oklch(0.205 0 0);
96
100
  --primary-foreground: oklch(0.985 0 0);
97
- --secondary: oklch(0.97 0 0);
101
+ --secondary: var(--muted);
98
102
  --secondary-foreground: oklch(0.205 0 0);
99
103
  --muted: oklch(0.97 0 0);
100
104
  /* L=0.52 : 4,5:1 sur `muted`. */
101
105
  --muted-foreground: oklch(0.52 0 0);
102
- --accent: oklch(0.55 0.25 296);
103
- --accent-foreground: oklch(0.985 0 0);
106
+ /* `--brand-accent*` : couleur d’un client posée par le produit, sans toucher au kit. */
107
+ --accent: var(--brand-accent, oklch(0.55 0.25 296));
108
+ --accent-foreground: var(--brand-accent-foreground, oklch(0.985 0 0));
104
109
  --accent-soft: color-mix(in oklch, var(--accent) 10%, transparent);
105
110
  --destructive: oklch(0.577 0.245 27.325);
106
111
  --border: oklch(0.922 0 0);
107
112
  --input: oklch(0.922 0 0);
108
113
  --ring: var(--accent);
109
- --surface-hover: oklch(0.945 0 0);
110
114
  /* Coque de l'application : barre latérale et en-tête. */
111
115
  --shell: oklch(0.955 0 0);
112
116
  --shell-foreground: oklch(0.145 0 0);
@@ -117,6 +121,12 @@
117
121
  --status-active: oklch(0.55 0.15 156);
118
122
  --status-paused: oklch(0.62 0.14 75);
119
123
  --status-done: oklch(0.52 0.2 293);
124
+ /* Encres : le texte d'un statut sur son lavis (4,5:1), la couleur vive restant aux pastilles. */
125
+ --status-active-ink: oklch(0.45 0.12 155);
126
+ --status-paused-ink: oklch(0.5 0.12 70);
127
+ /* Information : bleu, pour que le violet reste l'accent. */
128
+ --info: oklch(0.62 0.19 256);
129
+ --info-ink: oklch(0.48 0.17 256);
120
130
  --success: oklch(0.58 0.15 152);
121
131
  --success-foreground: oklch(0.985 0 0);
122
132
  /* Panneau inversé de la vitrine : encre sombre dans les deux thèmes. */
@@ -133,22 +143,18 @@
133
143
  --foreground: oklch(0.985 0 0);
134
144
  --card: oklch(0.205 0 0);
135
145
  --card-foreground: oklch(0.985 0 0);
136
- --popover: oklch(0.205 0 0);
137
- --popover-foreground: oklch(0.985 0 0);
138
146
  --floating: oklch(0.255 0 0);
139
147
  --floating-muted: oklch(0.315 0 0);
140
148
  --primary: oklch(0.922 0 0);
141
149
  --primary-foreground: oklch(0.205 0 0);
142
- --secondary: oklch(0.269 0 0);
143
150
  --secondary-foreground: oklch(0.985 0 0);
144
151
  --muted: oklch(0.269 0 0);
145
152
  --muted-foreground: oklch(0.708 0 0);
146
- --accent: oklch(0.74 0.19 296);
147
- --accent-foreground: oklch(0.205 0 0);
153
+ --accent: var(--brand-accent-dark, oklch(0.74 0.19 296));
154
+ --accent-foreground: var(--brand-accent-dark-foreground, oklch(0.205 0 0));
148
155
  --destructive: oklch(0.704 0.191 22.216);
149
156
  --border: oklch(1 0 0 / 10%);
150
157
  --input: oklch(1 0 0 / 15%);
151
- --surface-hover: oklch(0.25 0 0);
152
158
  --shell: oklch(0.08 0 0);
153
159
  --shell-foreground: oklch(0.985 0 0);
154
160
  --shell-muted-foreground: oklch(0.708 0 0);
@@ -158,6 +164,10 @@
158
164
  --status-active: oklch(0.74 0.14 156);
159
165
  --status-paused: oklch(0.82 0.15 86);
160
166
  --status-done: oklch(0.68 0.17 293);
167
+ --status-active-ink: var(--status-active);
168
+ --status-paused-ink: var(--status-paused);
169
+ --info: oklch(0.7 0.16 256);
170
+ --info-ink: var(--info);
161
171
  --success: oklch(0.7 0.15 152);
162
172
  --success-foreground: oklch(0.16 0.02 152);
163
173
  --inverse: oklch(0.1 0 0);
@@ -209,15 +219,11 @@
209
219
 
210
220
  /* ── Surfaces ──────────────────────────────────────────────────────────── */
211
221
 
212
- /* Niveau 1 de la DA : une teinte, ni ombre ni bordure. `muted` en clair, `card` en sombre.
213
- Dans la surface, un élément `muted` devient blanc au lieu de disparaître, sans carte dans
214
- la carte. En utilitaire pour qu'un `hover:` posé sur la carte garde la main. */
222
+ /* Niveau 1 de la DA : une teinte, ni ombre ni bordure. `muted` en clair, `card` en sombre
223
+ (un cran plus clair que la page). */
215
224
  @utility surface {
216
225
  background-color: var(--muted);
217
- & > * {
218
- --muted: var(--background);
219
- }
220
- .dark & {
226
+ :where(.dark) & {
221
227
  background-color: var(--card);
222
228
  }
223
229
  }
@@ -227,12 +233,12 @@
227
233
  background-color: var(--muted);
228
234
  transition: background-color var(--duration-fast) var(--ease-out);
229
235
  &:hover {
230
- background-color: var(--surface-hover);
236
+ background-color: oklch(0.94 0 0);
231
237
  }
232
- .dark & {
238
+ :where(.dark) & {
233
239
  background-color: var(--card);
234
240
  }
235
- .dark &:hover {
241
+ :where(.dark) &:hover {
236
242
  background-color: var(--muted);
237
243
  }
238
244
  }