@sentropic/design-system-svelte 0.10.1 → 0.10.2

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.
@@ -0,0 +1,54 @@
1
+ export type ForceGraphTone = "category1" | "category2" | "category3" | "category4" | "category5" | "category6" | "category7" | "category8";
2
+ export type ForceGraphNode = {
3
+ /** Stable identifier; referenced by edges. */
4
+ id: string;
5
+ /** Visible label (falls back to id). */
6
+ label?: string;
7
+ /**
8
+ * Grouping key (e.g. node type or community). Nodes sharing a group get
9
+ * the same tone when `tone` is not set explicitly.
10
+ */
11
+ group?: string | number;
12
+ /** Explicit data-vis tone; overrides the group-derived tone. */
13
+ tone?: ForceGraphTone;
14
+ /** Relative node radius weight (defaults to 1). */
15
+ weight?: number;
16
+ /** Pin the node to a fixed position (ignored by the simulation). */
17
+ fx?: number;
18
+ fy?: number;
19
+ };
20
+ export type ForceGraphEdge = {
21
+ /** Source node id. */
22
+ source: string;
23
+ /** Target node id. */
24
+ target: string;
25
+ /** Optional relation label, surfaced in the tooltip on hover/focus. */
26
+ relation?: string;
27
+ /**
28
+ * When true the link renders as a dashed/faded "weak" link. Lets callers
29
+ * map a confidence dimension onto link strength without extra props.
30
+ */
31
+ weak?: boolean;
32
+ };
33
+ type ForceGraphProps = {
34
+ nodes: ForceGraphNode[];
35
+ edges: ForceGraphEdge[];
36
+ /** Accessible name for the figure (required). */
37
+ label: string;
38
+ width?: number;
39
+ height?: number;
40
+ /** Base node radius in px (scaled by node.weight). */
41
+ nodeRadius?: number;
42
+ /** Show text labels next to nodes. */
43
+ showLabels?: boolean;
44
+ /**
45
+ * Number of cooling ticks. The simulation runs a synchronous warmup then
46
+ * animates the remainder unless reduced motion is requested.
47
+ */
48
+ iterations?: number;
49
+ class?: string;
50
+ };
51
+ declare const ForceGraph: import("svelte").Component<ForceGraphProps, {}, "">;
52
+ type ForceGraph = ReturnType<typeof ForceGraph>;
53
+ export default ForceGraph;
54
+ //# sourceMappingURL=ForceGraph.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ForceGraph.svelte.d.ts","sourceRoot":"","sources":["../src/lib/ForceGraph.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,cAAc,GACtB,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GACrD,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;AAE1D,MAAM,MAAM,cAAc,GAAG;IAC3B,8CAA8C;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,gEAAgE;IAChE,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAoQJ,QAAA,MAAM,UAAU,qDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -1,3 +1,35 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * Identité de l'utilisateur connecté à afficher dans la zone compte du Header.
4
+ *
5
+ * Le composant garantit qu'une identité connectée ne se réduit JAMAIS à une
6
+ * icône carrée nue : le nom est toujours rendu, et l'email l'accompagne quand
7
+ * il est fourni. L'avatar utilise la photo si `avatarUrl` est présente, sinon
8
+ * il retombe sur les `initials` (calculées depuis le nom si non fournies).
9
+ */
10
+ export type HeaderAccount = {
11
+ /** Nom affiché de la personne connectée. Obligatoire : pas de carré sans nom. */
12
+ name: string;
13
+ /** Email optionnel, affiché sous le nom et dans le menu compte. */
14
+ email?: string;
15
+ /** URL de la photo de profil. Si absente, on rend les initiales. */
16
+ avatarUrl?: string | null;
17
+ /** Initiales explicites. Si absentes, dérivées du `name`. */
18
+ initials?: string;
19
+ };
20
+
21
+ /** Dérive des initiales lisibles (max 2 lettres) à partir d'un nom complet. */
22
+ export function deriveInitials(name: string): string {
23
+ const parts = name
24
+ .trim()
25
+ .split(/\s+/)
26
+ .filter(Boolean);
27
+ if (parts.length === 0) return "?";
28
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
29
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
30
+ }
31
+ </script>
32
+
1
33
  <script lang="ts">
2
34
  import type { Snippet } from "svelte";
3
35
  import type { HTMLAttributes } from "svelte/elements";
@@ -11,6 +43,23 @@
11
43
  navigation?: Snippet;
12
44
  actions?: Snippet;
13
45
  children?: Snippet;
46
+ /**
47
+ * Identité connectée (additif). Quand fourni, le Header rend une zone compte
48
+ * complète (avatar OU initiales + nom + email + déclencheur de menu). Sans
49
+ * cet objet, l'état « anonyme » s'affiche via le snippet `actions` du parent
50
+ * (typiquement un CTA « Se connecter »).
51
+ */
52
+ account?: HeaderAccount;
53
+ /** Texte du déclencheur de connexion rendu quand `account` est absent. */
54
+ signInLabel?: string;
55
+ /** Snippet du menu compte (contenu du panneau ouvert sous l'avatar). */
56
+ accountMenu?: Snippet;
57
+ /** Indique si le menu compte est ouvert (état contrôlé par le parent). */
58
+ accountMenuOpen?: boolean;
59
+ /** Callback déclenché au clic sur le bouton compte. */
60
+ onAccountTriggerClick?: () => void;
61
+ /** Callback déclenché au clic sur le CTA de connexion (état anonyme). */
62
+ onSignIn?: () => void;
14
63
  };
15
64
 
16
65
  let {
@@ -22,11 +71,22 @@
22
71
  navigation,
23
72
  actions,
24
73
  children,
74
+ account,
75
+ signInLabel = "Se connecter",
76
+ accountMenu,
77
+ accountMenuOpen = false,
78
+ onAccountTriggerClick,
79
+ onSignIn,
25
80
  ...rest
26
81
  }: HeaderProps = $props();
27
82
 
28
83
  const classes = () =>
29
84
  ["st-header", sticky ? "st-header--sticky" : null, className].filter(Boolean).join(" ");
85
+
86
+ const resolvedInitials = $derived(
87
+ account ? (account.initials?.trim() || deriveInitials(account.name)) : ""
88
+ );
89
+ const hasPhoto = $derived(Boolean(account?.avatarUrl));
30
90
  </script>
31
91
 
32
92
  <header {...rest} class={classes()} aria-label={label}>
@@ -43,9 +103,68 @@
43
103
  {@render navigation()}
44
104
  </nav>
45
105
  {/if}
46
- {#if actions}
106
+ {#if actions || account || onSignIn}
47
107
  <div class="st-header__actions">
48
- {@render actions()}
108
+ {#if actions}
109
+ {@render actions()}
110
+ {/if}
111
+ {#if account}
112
+ <!-- État connecté : identité arbitrée. Le nom est TOUJOURS visible. -->
113
+ <div class="st-header__account">
114
+ <button
115
+ type="button"
116
+ class="st-header__account-trigger"
117
+ aria-haspopup="menu"
118
+ aria-expanded={accountMenuOpen}
119
+ aria-label={`Compte de ${account.name}`}
120
+ onclick={onAccountTriggerClick}
121
+ >
122
+ {#if hasPhoto}
123
+ <span class="st-header__avatar st-header__avatar--photo" aria-hidden="true">
124
+ <img
125
+ class="st-header__avatar-image"
126
+ src={account.avatarUrl}
127
+ alt=""
128
+ />
129
+ </span>
130
+ {:else}
131
+ <span class="st-header__avatar st-header__avatar--initials" aria-hidden="true">
132
+ {resolvedInitials}
133
+ </span>
134
+ {/if}
135
+ <span class="st-header__account-meta">
136
+ <span class="st-header__account-name">{account.name}</span>
137
+ {#if account.email}
138
+ <span class="st-header__account-email">{account.email}</span>
139
+ {/if}
140
+ </span>
141
+ <svg
142
+ class="st-header__account-caret"
143
+ width="12"
144
+ height="12"
145
+ viewBox="0 0 24 24"
146
+ fill="none"
147
+ stroke="currentColor"
148
+ stroke-width="2.4"
149
+ stroke-linecap="round"
150
+ stroke-linejoin="round"
151
+ aria-hidden="true"
152
+ >
153
+ <polyline points="6 9 12 15 18 9" />
154
+ </svg>
155
+ </button>
156
+ {#if accountMenu && accountMenuOpen}
157
+ <div class="st-header__account-menu" role="menu" aria-label={`Menu de ${account.name}`}>
158
+ {@render accountMenu()}
159
+ </div>
160
+ {/if}
161
+ </div>
162
+ {:else if onSignIn}
163
+ <!-- État anonyme : CTA de connexion explicite. -->
164
+ <button type="button" class="st-header__signin" onclick={onSignIn}>
165
+ {signInLabel}
166
+ </button>
167
+ {/if}
49
168
  </div>
50
169
  {/if}
51
170
  {#if children}
@@ -114,4 +233,124 @@
114
233
  * When no navigation snippet is provided, the actions area still flushes
115
234
  * to the right because flex:1 leading + auto margin keep the layout balanced.
116
235
  */
236
+
237
+ /* --- Zone compte (G8) : identité connectée, jamais un carré nu --- */
238
+ .st-header__account {
239
+ position: relative;
240
+ }
241
+
242
+ .st-header__account-trigger {
243
+ align-items: center;
244
+ background: transparent;
245
+ border: 1px solid var(--st-component-header-border, var(--st-semantic-border-subtle));
246
+ border-radius: var(--st-radius-sm, 0.375rem);
247
+ color: inherit;
248
+ cursor: pointer;
249
+ display: inline-flex;
250
+ font: inherit;
251
+ gap: var(--st-spacing-2, 0.5rem);
252
+ max-width: 18rem;
253
+ min-height: 2.25rem;
254
+ padding: 0.25rem 0.5rem;
255
+ }
256
+
257
+ .st-header__account-trigger:hover,
258
+ .st-header__account-trigger:focus-visible {
259
+ border-color: var(--st-semantic-border-interactive, var(--st-semantic-action-primary));
260
+ outline: none;
261
+ }
262
+
263
+ .st-header__avatar {
264
+ aspect-ratio: 1;
265
+ border-radius: var(--st-radius-sm, 0.375rem);
266
+ flex: 0 0 auto;
267
+ height: 2rem;
268
+ overflow: hidden;
269
+ width: 2rem;
270
+ }
271
+
272
+ .st-header__avatar--initials {
273
+ align-items: center;
274
+ background: var(--st-semantic-surface-subtle, #eef2f7);
275
+ color: var(--st-semantic-text-primary, #0f172a);
276
+ display: inline-flex;
277
+ font-size: 0.8125rem;
278
+ font-weight: 700;
279
+ justify-content: center;
280
+ letter-spacing: 0.01em;
281
+ }
282
+
283
+ .st-header__avatar-image {
284
+ background: var(--st-semantic-surface-subtle, #eef2f7);
285
+ display: block;
286
+ height: 100%;
287
+ object-fit: cover;
288
+ object-position: center;
289
+ width: 100%;
290
+ }
291
+
292
+ .st-header__account-meta {
293
+ display: grid;
294
+ gap: 0.05rem;
295
+ min-width: 0;
296
+ text-align: left;
297
+ }
298
+
299
+ .st-header__account-name {
300
+ color: var(--st-semantic-text-primary, #0f172a);
301
+ font-size: 0.82rem;
302
+ font-weight: 650;
303
+ line-height: 1.2;
304
+ overflow: hidden;
305
+ text-overflow: ellipsis;
306
+ white-space: nowrap;
307
+ }
308
+
309
+ .st-header__account-email {
310
+ color: var(--st-semantic-text-secondary, #64748b);
311
+ font-size: 0.72rem;
312
+ line-height: 1.2;
313
+ overflow: hidden;
314
+ text-overflow: ellipsis;
315
+ white-space: nowrap;
316
+ }
317
+
318
+ .st-header__account-caret {
319
+ color: var(--st-semantic-text-secondary, #64748b);
320
+ flex: 0 0 auto;
321
+ }
322
+
323
+ .st-header__account-menu {
324
+ background: var(--st-component-popover-background, var(--st-semantic-surface-raised, #ffffff));
325
+ border: 1px solid var(--st-component-popover-border, var(--st-semantic-border-subtle, #e2e8f0));
326
+ border-radius: var(--st-component-popover-radius, 0.5rem);
327
+ box-shadow: var(--st-component-popover-shadow, 0 18px 45px rgb(15 23 42 / 0.18));
328
+ color: var(--st-semantic-text-primary, #0f172a);
329
+ display: grid;
330
+ gap: 0.35rem;
331
+ min-width: 14rem;
332
+ padding: var(--st-spacing-3, 0.75rem);
333
+ position: absolute;
334
+ right: 0;
335
+ top: calc(100% + var(--st-spacing-2, 0.5rem));
336
+ z-index: var(--st-component-popover-zIndex, 80);
337
+ }
338
+
339
+ .st-header__signin {
340
+ background: transparent;
341
+ border: 1px solid transparent;
342
+ border-radius: var(--st-radius-sm, 0.375rem);
343
+ color: var(--st-semantic-text-link, var(--st-semantic-action-primary));
344
+ cursor: pointer;
345
+ font: inherit;
346
+ font-weight: 600;
347
+ min-height: 2.25rem;
348
+ padding: 0 0.875rem;
349
+ }
350
+
351
+ .st-header__signin:hover,
352
+ .st-header__signin:focus-visible {
353
+ background: var(--st-semantic-surface-subtle, #f1f5f9);
354
+ outline: none;
355
+ }
117
356
  </style>
@@ -1,3 +1,23 @@
1
+ /**
2
+ * Identité de l'utilisateur connecté à afficher dans la zone compte du Header.
3
+ *
4
+ * Le composant garantit qu'une identité connectée ne se réduit JAMAIS à une
5
+ * icône carrée nue : le nom est toujours rendu, et l'email l'accompagne quand
6
+ * il est fourni. L'avatar utilise la photo si `avatarUrl` est présente, sinon
7
+ * il retombe sur les `initials` (calculées depuis le nom si non fournies).
8
+ */
9
+ export type HeaderAccount = {
10
+ /** Nom affiché de la personne connectée. Obligatoire : pas de carré sans nom. */
11
+ name: string;
12
+ /** Email optionnel, affiché sous le nom et dans le menu compte. */
13
+ email?: string;
14
+ /** URL de la photo de profil. Si absente, on rend les initiales. */
15
+ avatarUrl?: string | null;
16
+ /** Initiales explicites. Si absentes, dérivées du `name`. */
17
+ initials?: string;
18
+ };
19
+ /** Dérive des initiales lisibles (max 2 lettres) à partir d'un nom complet. */
20
+ export declare function deriveInitials(name: string): string;
1
21
  import type { Snippet } from "svelte";
2
22
  import type { HTMLAttributes } from "svelte/elements";
3
23
  type HeaderProps = Omit<HTMLAttributes<HTMLElement>, "class"> & {
@@ -9,6 +29,23 @@ type HeaderProps = Omit<HTMLAttributes<HTMLElement>, "class"> & {
9
29
  navigation?: Snippet;
10
30
  actions?: Snippet;
11
31
  children?: Snippet;
32
+ /**
33
+ * Identité connectée (additif). Quand fourni, le Header rend une zone compte
34
+ * complète (avatar OU initiales + nom + email + déclencheur de menu). Sans
35
+ * cet objet, l'état « anonyme » s'affiche via le snippet `actions` du parent
36
+ * (typiquement un CTA « Se connecter »).
37
+ */
38
+ account?: HeaderAccount;
39
+ /** Texte du déclencheur de connexion rendu quand `account` est absent. */
40
+ signInLabel?: string;
41
+ /** Snippet du menu compte (contenu du panneau ouvert sous l'avatar). */
42
+ accountMenu?: Snippet;
43
+ /** Indique si le menu compte est ouvert (état contrôlé par le parent). */
44
+ accountMenuOpen?: boolean;
45
+ /** Callback déclenché au clic sur le bouton compte. */
46
+ onAccountTriggerClick?: () => void;
47
+ /** Callback déclenché au clic sur le CTA de connexion (état anonyme). */
48
+ onSignIn?: () => void;
12
49
  };
13
50
  declare const Header: import("svelte").Component<HeaderProps, {}, "">;
14
51
  type Header = ReturnType<typeof Header>;
@@ -1 +1 @@
1
- {"version":3,"file":"Header.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Header.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGpD,KAAK,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC,GAAG;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAkDJ,QAAA,MAAM,MAAM,iDAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"Header.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Header.svelte.ts"],"names":[],"mappings":"AAGE;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQnD;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGpD,KAAK,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC,GAAG;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,0EAA0E;IAC1E,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,uDAAuD;IACvD,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;IACnC,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB,CAAC;AAoGJ,QAAA,MAAM,MAAM,iDAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
@@ -52,19 +52,37 @@
52
52
 
53
53
  .st-pagination button {
54
54
  background: var(--st-component-pagination-background, var(--st-semantic-surface-default));
55
- border: 1px solid var(--st-component-pagination-border, var(--st-semantic-border-subtle));
55
+ border: var(--st-component-pagination-borderWidth, 1px) solid
56
+ var(--st-component-pagination-border, var(--st-semantic-border-subtle));
56
57
  border-radius: var(--st-component-pagination-radius, 0.375rem);
57
58
  color: var(--st-component-pagination-text, var(--st-semantic-text-primary));
58
59
  cursor: pointer;
59
- font: inherit;
60
- min-height: 2.25rem;
61
- min-width: 2.25rem;
62
- padding: 0 0.75rem;
60
+ font-family: inherit;
61
+ font-size: var(--st-component-pagination-fontSize, inherit);
62
+ line-height: var(--st-component-pagination-lineHeight, normal);
63
+ letter-spacing: var(--st-component-pagination-letterSpacing, normal);
64
+ min-height: var(--st-component-pagination-minSize, 2.25rem);
65
+ min-width: var(--st-component-pagination-minSize, 2.25rem);
66
+ padding: var(--st-component-pagination-paddingBlock, 0)
67
+ var(--st-component-pagination-paddingInline, 0.75rem);
63
68
  }
64
69
 
65
- .st-pagination__page--active {
70
+ /* Active page — scoped under `.st-pagination button` so it outweighs the base
71
+ button rule. The prior single-class selector (specificity 0,1,0) LOST to the
72
+ element+class base rule (0,1,1), so the active fill/colour never applied
73
+ (the report measured the active page on white, not the action fill). */
74
+ .st-pagination button.st-pagination__page--active {
66
75
  background: var(--st-component-pagination-activeBackground, var(--st-semantic-action-primary));
76
+ border-color: var(
77
+ --st-component-pagination-activeBorder,
78
+ var(--st-component-pagination-border, var(--st-semantic-border-subtle))
79
+ );
80
+ border-width: var(
81
+ --st-component-pagination-activeBorderWidth,
82
+ var(--st-component-pagination-borderWidth, 1px)
83
+ );
67
84
  color: var(--st-component-pagination-activeText, var(--st-semantic-action-primaryText));
85
+ font-weight: var(--st-component-pagination-activeWeight, inherit);
68
86
  }
69
87
 
70
88
  .st-pagination button:disabled {
package/dist/Radio.svelte CHANGED
@@ -9,7 +9,7 @@
9
9
  };
10
10
 
11
11
  let { label, helperText, invalid = false, class: className, ...rest }: RadioProps = $props();
12
- const classes = () => ["st-choice", className].filter(Boolean).join(" ");
12
+ const classes = () => ["st-choice", "st-choice--radio", className].filter(Boolean).join(" ");
13
13
  </script>
14
14
 
15
15
  <label class={classes()}>
@@ -69,7 +69,15 @@
69
69
  }
70
70
 
71
71
  .st-choice__label {
72
- font-size: 0.9375rem;
72
+ /* P-D: label typography per theme (base = 15px / normal / inherited colour).
73
+ Radio carries its own line-height (Carbon $body-01 = 20px, vs the checkbox
74
+ $body-compact-01 = 18px); the var defaults to the shared choice line-height
75
+ so a theme that does not split them stays consistent. */
76
+ color: var(--st-component-selection-choiceLabelColor, inherit);
77
+ font-size: var(--st-component-selection-choiceLabelFontSize, 0.9375rem);
78
+ line-height: var(--st-component-selection-choiceRadioLineHeight,
79
+ var(--st-component-selection-choiceLabelLineHeight, normal));
80
+ letter-spacing: var(--st-component-selection-choiceLabelLetterSpacing, normal);
73
81
  }
74
82
 
75
83
  .st-choice__help {
@@ -117,9 +117,21 @@
117
117
  border-right: var(--st-component-control-anatomy-field-borderRight, var(--st-component-control-anatomy-shape-borderWidth, 1px) var(--st-component-control-anatomy-shape-borderStyle, solid) var(--st-component-control-border, var(--st-semantic-border-subtle)));
118
118
  border-bottom: var(--st-component-control-anatomy-field-borderBottom, var(--st-component-control-anatomy-shape-borderWidth, 1px) var(--st-component-control-anatomy-shape-borderStyle, solid) var(--st-component-control-border, var(--st-semantic-border-subtle)));
119
119
  border-left: var(--st-component-control-anatomy-field-borderLeft, var(--st-component-control-anatomy-shape-borderWidth, 1px) var(--st-component-control-anatomy-shape-borderStyle, solid) var(--st-component-control-border, var(--st-semantic-border-subtle)));
120
- border-radius: var(--st-component-control-anatomy-shape-radius, 0.375rem);
120
+ /* Top corners ride the shared field `radiusTop` (DSFR rounds top 4px, like
121
+ the Input field box); bottom corners keep the field shape radius. */
122
+ border-top-left-radius: var(--st-component-control-anatomy-field-radiusTop, var(--st-component-control-anatomy-shape-radius, 0.375rem));
123
+ border-top-right-radius: var(--st-component-control-anatomy-field-radiusTop, var(--st-component-control-anatomy-shape-radius, 0.375rem));
124
+ border-bottom-right-radius: var(--st-component-control-anatomy-shape-radius, 0.375rem);
125
+ border-bottom-left-radius: var(--st-component-control-anatomy-shape-radius, 0.375rem);
121
126
  color: var(--st-component-control-text, var(--st-semantic-text-primary));
122
127
  display: inline-flex;
128
+ /* P-D: field-box padding + input typography per theme. Default = the prior
129
+ render (0 padding on the wrapper, inherited 16px / `normal` typography);
130
+ DSFR pads 8/16px, Carbon 0/40px to match the measured reference input. */
131
+ padding: var(--st-component-search-paddingBlock, 0) var(--st-component-search-paddingInline, 0);
132
+ font-size: var(--st-component-search-fontSize, 1rem);
133
+ line-height: var(--st-component-search-lineHeight, normal);
134
+ letter-spacing: var(--st-component-search-letterSpacing, normal);
123
135
  transition:
124
136
  border-color var(--st-motion-fast, 120ms) var(--st-motion-easing, ease),
125
137
  box-shadow var(--st-motion-fast, 120ms) var(--st-motion-easing, ease);
@@ -33,31 +33,33 @@
33
33
  }
34
34
 
35
35
  .st-switch__input {
36
- height: 1.25rem;
36
+ height: var(--st-component-selection-toggleTrackHeight, 1.25rem);
37
37
  margin: 0;
38
38
  opacity: 0;
39
39
  position: absolute;
40
- width: 2.25rem;
40
+ width: var(--st-component-selection-toggleTrackWidth, 2.25rem);
41
41
  }
42
42
 
43
43
  .st-switch__track {
44
44
  align-items: center;
45
45
  background: var(--st-component-selection-switchTrack, var(--st-semantic-border-strong));
46
- border-radius: var(--st-radius-pill, 999px);
46
+ /* P-D: dims/radius/padding ride the per-theme toggle anatomy (default = the
47
+ prior 36×20 pill → base unchanged), so Switch stays coherent with Toggle. */
48
+ border-radius: var(--st-component-selection-toggleTrackRadius, var(--st-radius-pill, 999px));
47
49
  display: inline-flex;
48
- height: 1.25rem;
49
- padding: 0.125rem;
50
+ height: var(--st-component-selection-toggleTrackHeight, 1.25rem);
51
+ padding: var(--st-component-selection-toggleTrackPadding, 0.125rem);
50
52
  transition: background var(--st-motion-fast, 120ms) var(--st-motion-easing, ease);
51
- width: 2.25rem;
53
+ width: var(--st-component-selection-toggleTrackWidth, 2.25rem);
52
54
  }
53
55
 
54
56
  .st-switch__thumb {
55
57
  background: var(--st-component-selection-switchThumb, var(--st-semantic-surface-default));
56
58
  border-radius: var(--st-radius-pill, 999px);
57
- height: 1rem;
59
+ height: var(--st-component-selection-toggleThumbSize, 1rem);
58
60
  transform: translateX(0);
59
61
  transition: transform var(--st-motion-fast, 120ms) var(--st-motion-easing, ease);
60
- width: 1rem;
62
+ width: var(--st-component-selection-toggleThumbSize, 1rem);
61
63
  }
62
64
 
63
65
  .st-switch__input:checked + .st-switch__track {
@@ -65,7 +67,7 @@
65
67
  }
66
68
 
67
69
  .st-switch__input:checked + .st-switch__track .st-switch__thumb {
68
- transform: translateX(1rem);
70
+ transform: translateX(calc(var(--st-component-selection-toggleTrackWidth, 2.25rem) - var(--st-component-selection-toggleThumbSize, 1rem) - 2 * var(--st-component-selection-toggleTrackPadding, 0.125rem)));
69
71
  }
70
72
 
71
73
  /* Focus = stratégie d'anatomie partagée (outline DSFR / inset Carbon / ring base). */
package/dist/Tabs.svelte CHANGED
@@ -77,7 +77,10 @@
77
77
  }
78
78
 
79
79
  .st-tabs__tab {
80
- background: transparent;
80
+ /* Resting tab fill (G2): per-theme. Base/Carbon keep transparent; DSFR puts
81
+ a light grey-blue fill on the inactive tabs so the white active tab reads
82
+ as raised. Fallback = transparent → base render unchanged. */
83
+ background: var(--st-component-tabs-inactiveBackground, transparent);
81
84
  border: 0;
82
85
  /* Indicator edge (F7/F8): base/Carbon carry it on the BOTTOM, DSFR on the
83
86
  TOP. The resting tab keeps the same-width transparent stroke on each edge
package/dist/Tag.svelte CHANGED
@@ -53,29 +53,37 @@
53
53
  </span>
54
54
 
55
55
  <style>
56
+ /* P-C: per-theme tag anatomy. Every var falls back to the prior base literal,
57
+ so a theme that emits no `--st-component-tag-*` renders byte-identically. */
56
58
  .st-tag {
57
59
  align-items: center;
58
- border-radius: var(--st-radius-pill, 999px);
60
+ border-radius: var(--st-component-tag-radius, var(--st-radius-pill, 999px));
59
61
  display: inline-flex;
60
- font-weight: 600;
62
+ font-weight: var(--st-component-tag-fontWeight, 600);
61
63
  gap: 0.25rem;
62
- line-height: 1;
63
- padding: 0.25rem 0.5rem;
64
+ letter-spacing: var(--st-component-tag-letterSpacing, normal);
65
+ line-height: var(--st-component-tag-lineHeight, 1);
66
+ min-height: var(--st-component-tag-minHeight, 0);
67
+ padding: var(--st-component-tag-paddingBlock, 0.25rem)
68
+ var(--st-component-tag-paddingInline, 0.5rem);
69
+ text-transform: var(--st-component-tag-textTransform, none);
64
70
  }
65
71
 
66
72
  .st-tag--sm {
67
- font-size: 0.6875rem;
68
- padding: 0.1875rem 0.5rem;
73
+ font-size: var(--st-component-tag-fontSize, 0.6875rem);
74
+ padding: var(--st-component-tag-paddingBlock, 0.1875rem)
75
+ var(--st-component-tag-paddingInline, 0.5rem);
69
76
  }
70
77
 
71
78
  .st-tag--md {
72
- font-size: 0.75rem;
73
- padding: 0.25rem 0.625rem;
79
+ font-size: var(--st-component-tag-fontSize, 0.75rem);
80
+ padding: var(--st-component-tag-paddingBlock, 0.25rem)
81
+ var(--st-component-tag-paddingInline, 0.625rem);
74
82
  }
75
83
 
76
84
  .st-tag--neutral {
77
- background: var(--st-semantic-surface-subtle);
78
- color: var(--st-semantic-text-secondary);
85
+ background: var(--st-component-tag-neutralBackground, var(--st-semantic-surface-subtle));
86
+ color: var(--st-component-tag-neutralText, var(--st-semantic-text-secondary));
79
87
  }
80
88
 
81
89
  .st-tag--success {
@@ -75,11 +75,20 @@
75
75
  .st-toggle__track {
76
76
  align-items: center;
77
77
  background: var(--st-component-selection-switchTrack, var(--st-semantic-border-strong));
78
- border-radius: var(--st-radius-pill, 999px);
78
+ /* P-D: radius/padding + label typography/colour per theme. Track stays a
79
+ rounded pill by default (base unchanged); Carbon pins the 48×24 switch box
80
+ and its grey label colour / 12px-16px/0.32px typography (the track carries
81
+ no text, so the typography/colour leaves are visually inert on our side but
82
+ align the measured surface to the reference switch). */
83
+ border-radius: var(--st-component-selection-toggleTrackRadius, var(--st-radius-pill, 999px));
84
+ color: var(--st-component-selection-toggleTextColor, var(--st-semantic-text-primary));
79
85
  display: inline-flex;
80
86
  flex: 0 0 auto;
87
+ font-size: var(--st-component-selection-toggleFontSize, inherit);
81
88
  height: var(--st-toggle-trackHeight, 1.25rem);
82
- padding: 0.125rem;
89
+ letter-spacing: var(--st-component-selection-toggleLetterSpacing, normal);
90
+ line-height: var(--st-component-selection-toggleLineHeight, normal);
91
+ padding: var(--st-component-selection-toggleTrackPadding, 0.125rem);
83
92
  transition: background var(--st-motion-fast, 120ms) var(--st-motion-easing, ease);
84
93
  width: var(--st-toggle-trackWidth, 2.25rem);
85
94
  }
@@ -98,7 +107,7 @@
98
107
  }
99
108
 
100
109
  .st-toggle__input:checked + .st-toggle__track .st-toggle__thumb {
101
- transform: translateX(calc(var(--st-toggle-trackWidth, 2.25rem) - var(--st-toggle-thumbSize, 1rem) - 0.25rem));
110
+ transform: translateX(calc(var(--st-toggle-trackWidth, 2.25rem) - var(--st-toggle-thumbSize, 1rem) - 2 * var(--st-component-selection-toggleTrackPadding, 0.125rem)));
102
111
  }
103
112
 
104
113
  /* Focus = stratégie d'anatomie partagée (outline DSFR / inset Carbon / ring base). */
@@ -132,8 +141,10 @@
132
141
  }
133
142
 
134
143
  .st-toggle--md {
135
- --st-toggle-trackHeight: 1.25rem;
136
- --st-toggle-trackWidth: 2.25rem;
137
- --st-toggle-thumbSize: 1rem;
144
+ /* P-D: md track dims ride the per-theme toggle anatomy (default = the prior
145
+ 36×20 / 16px thumb → base unchanged; Carbon pins its 48×24 switch box). */
146
+ --st-toggle-trackHeight: var(--st-component-selection-toggleTrackHeight, 1.25rem);
147
+ --st-toggle-trackWidth: var(--st-component-selection-toggleTrackWidth, 2.25rem);
148
+ --st-toggle-thumbSize: var(--st-component-selection-toggleThumbSize, 1rem);
138
149
  }
139
150
  </style>
package/dist/index.d.ts CHANGED
@@ -26,6 +26,7 @@ export { default as Dropdown } from "./Dropdown.svelte";
26
26
  export { default as EmptyState } from "./EmptyState.svelte";
27
27
  export { default as FileUploader } from "./FileUploader.svelte";
28
28
  export { default as Footer } from "./Footer.svelte";
29
+ export { default as ForceGraph } from "./ForceGraph.svelte";
29
30
  export { default as Form } from "./Form.svelte";
30
31
  export { default as FormGroup } from "./FormGroup.svelte";
31
32
  export { default as Header } from "./Header.svelte";
@@ -91,7 +92,10 @@ export type { ComboboxOption } from "./Combobox.svelte";
91
92
  export type { DataTableColumn, DataTableRow, DataTableSelectMode, DataTableSort } from "./DataTable.svelte";
92
93
  export type { DatePickerRange } from "./DatePicker.svelte";
93
94
  export type { DonutChartDatum, DonutChartTone } from "./DonutChart.svelte";
95
+ export type { ForceGraphNode, ForceGraphEdge, ForceGraphTone } from "./ForceGraph.svelte";
94
96
  export type { DropdownOption } from "./Dropdown.svelte";
97
+ export type { HeaderAccount } from "./Header.svelte";
98
+ export { deriveInitials } from "./Header.svelte";
95
99
  export type { FileUploadItem, FileUploadStatus } from "./FileUploader.svelte";
96
100
  export type { MenuItem } from "./Menu.svelte";
97
101
  export type { MultiSelectOption } from "./MultiSelect.svelte";