@sentropic/design-system-svelte 0.34.67 → 0.34.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AppChrome.svelte +50 -5
- package/dist/AppChrome.svelte.d.ts +14 -0
- package/dist/AppChrome.svelte.d.ts.map +1 -1
- package/dist/AppChrome.test.js +14 -0
- package/dist/AppHeader.svelte +9 -0
- package/dist/Collapsible.svelte +1 -1
- package/dist/Collapsible.test.js +8 -0
- package/dist/Dropdown.svelte +6 -1
- package/dist/Dropdown.svelte.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/.srchash +0 -1
package/dist/AppChrome.svelte
CHANGED
|
@@ -7,6 +7,16 @@
|
|
|
7
7
|
href: string;
|
|
8
8
|
/** Marqué actif (souligné, aria-current=page). */
|
|
9
9
|
active?: boolean;
|
|
10
|
+
/** Roles autorisés à voir l'item. Vide/undefined = visible par tous. */
|
|
11
|
+
roles?: string[];
|
|
12
|
+
/** Alias pratique pour un seul rôle autorisé. */
|
|
13
|
+
role?: string;
|
|
14
|
+
/** Coupe la navigation tout en gardant l'item visible/annoncé. */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
ariaLabel?: string;
|
|
17
|
+
target?: string;
|
|
18
|
+
rel?: string;
|
|
19
|
+
onClick?: (event: MouseEvent) => void;
|
|
10
20
|
}
|
|
11
21
|
|
|
12
22
|
/** Une option du sélecteur de thème. */
|
|
@@ -38,6 +48,10 @@
|
|
|
38
48
|
nav?: AppChromeNavItem[];
|
|
39
49
|
/** aria-label de la nav principale. */
|
|
40
50
|
navLabel?: string;
|
|
51
|
+
/** Alignement de la nav desktop, forwardé à AppHeader. */
|
|
52
|
+
navAlign?: "start" | "center";
|
|
53
|
+
/** Roles de l'utilisateur courant pour filtrer les nav items gatés par rôle. */
|
|
54
|
+
userRoles?: string[];
|
|
41
55
|
|
|
42
56
|
// ── Contrôle thème (contrôlé) ─────────────────────────────────────────────
|
|
43
57
|
/** Options de thème. Vide => le sélecteur est masqué. */
|
|
@@ -118,6 +132,8 @@
|
|
|
118
132
|
brandLabel,
|
|
119
133
|
nav = [],
|
|
120
134
|
navLabel = "Primary",
|
|
135
|
+
navAlign = "start",
|
|
136
|
+
userRoles = [],
|
|
121
137
|
themes = [],
|
|
122
138
|
theme,
|
|
123
139
|
onThemeChange,
|
|
@@ -172,6 +188,23 @@
|
|
|
172
188
|
}
|
|
173
189
|
|
|
174
190
|
const classes = () => ["st-appChrome", className].filter(Boolean).join(" ");
|
|
191
|
+
const visibleNav = $derived(
|
|
192
|
+
nav.filter((item) => {
|
|
193
|
+
const allowed = item.roles ?? (item.role ? [item.role] : []);
|
|
194
|
+
return allowed.length === 0 || allowed.some((role) => userRoles.includes(role));
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
function navRel(item: AppChromeNavItem): string | undefined {
|
|
199
|
+
if (item.disabled) return undefined;
|
|
200
|
+
return item.rel ?? (item.target === "_blank" ? "noreferrer" : undefined);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function handleNavClick(event: MouseEvent, item: AppChromeNavItem, closeDrawer = false) {
|
|
204
|
+
if (item.disabled) event.preventDefault();
|
|
205
|
+
item.onClick?.(event);
|
|
206
|
+
if (!item.disabled && closeDrawer) onMobileMenuToggle?.();
|
|
207
|
+
}
|
|
175
208
|
|
|
176
209
|
function closeMenus(target: EventTarget | null) {
|
|
177
210
|
const el = target as Element | null;
|
|
@@ -207,11 +240,17 @@
|
|
|
207
240
|
|
|
208
241
|
<!-- ── Nav principale ─────────────────────────────────────────────────────── -->
|
|
209
242
|
{#snippet navContent()}
|
|
210
|
-
{#each
|
|
243
|
+
{#each visibleNav as item (item.href)}
|
|
211
244
|
<a
|
|
212
245
|
class="st-appChrome__navLink st-appHeader__navLink"
|
|
213
|
-
|
|
246
|
+
class:st-appChrome__navLink--disabled={item.disabled}
|
|
247
|
+
href={item.disabled ? undefined : item.href}
|
|
214
248
|
aria-current={item.active ? "page" : undefined}
|
|
249
|
+
aria-disabled={item.disabled ? "true" : undefined}
|
|
250
|
+
aria-label={item.ariaLabel}
|
|
251
|
+
target={item.disabled ? undefined : item.target}
|
|
252
|
+
rel={navRel(item)}
|
|
253
|
+
onclick={(event) => handleNavClick(event, item)}
|
|
215
254
|
>
|
|
216
255
|
{item.label}
|
|
217
256
|
</a>
|
|
@@ -351,18 +390,24 @@
|
|
|
351
390
|
brandLabel={brandLabel}
|
|
352
391
|
logo={brand}
|
|
353
392
|
nav={navContent}
|
|
393
|
+
navAlign={navAlign}
|
|
354
394
|
actions={actions}
|
|
355
395
|
/>
|
|
356
396
|
|
|
357
397
|
{#if mobileMenuOpen}
|
|
358
398
|
<nav id={drawerId} class="st-appChrome__drawer" aria-label={navLabel}>
|
|
359
399
|
<div class="st-appChrome__drawerSection">
|
|
360
|
-
{#each
|
|
400
|
+
{#each visibleNav as item (item.href)}
|
|
361
401
|
<a
|
|
362
402
|
class="st-appChrome__drawerLink"
|
|
363
|
-
|
|
403
|
+
class:st-appChrome__navLink--disabled={item.disabled}
|
|
404
|
+
href={item.disabled ? undefined : item.href}
|
|
364
405
|
aria-current={item.active ? "page" : undefined}
|
|
365
|
-
|
|
406
|
+
aria-disabled={item.disabled ? "true" : undefined}
|
|
407
|
+
aria-label={item.ariaLabel}
|
|
408
|
+
target={item.disabled ? undefined : item.target}
|
|
409
|
+
rel={navRel(item)}
|
|
410
|
+
onclick={(event) => handleNavClick(event, item, true)}
|
|
366
411
|
>
|
|
367
412
|
{item.label}
|
|
368
413
|
</a>
|
|
@@ -5,6 +5,16 @@ export interface AppChromeNavItem {
|
|
|
5
5
|
href: string;
|
|
6
6
|
/** Marqué actif (souligné, aria-current=page). */
|
|
7
7
|
active?: boolean;
|
|
8
|
+
/** Roles autorisés à voir l'item. Vide/undefined = visible par tous. */
|
|
9
|
+
roles?: string[];
|
|
10
|
+
/** Alias pratique pour un seul rôle autorisé. */
|
|
11
|
+
role?: string;
|
|
12
|
+
/** Coupe la navigation tout en gardant l'item visible/annoncé. */
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
ariaLabel?: string;
|
|
15
|
+
target?: string;
|
|
16
|
+
rel?: string;
|
|
17
|
+
onClick?: (event: MouseEvent) => void;
|
|
8
18
|
}
|
|
9
19
|
/** Une option du sélecteur de thème. */
|
|
10
20
|
export interface AppChromeThemeOption {
|
|
@@ -30,6 +40,10 @@ export interface AppChromeProps {
|
|
|
30
40
|
nav?: AppChromeNavItem[];
|
|
31
41
|
/** aria-label de la nav principale. */
|
|
32
42
|
navLabel?: string;
|
|
43
|
+
/** Alignement de la nav desktop, forwardé à AppHeader. */
|
|
44
|
+
navAlign?: "start" | "center";
|
|
45
|
+
/** Roles de l'utilisateur courant pour filtrer les nav items gatés par rôle. */
|
|
46
|
+
userRoles?: string[];
|
|
33
47
|
/** Options de thème. Vide => le sélecteur est masqué. */
|
|
34
48
|
themes?: AppChromeThemeOption[];
|
|
35
49
|
/** Id du thème actif. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppChrome.svelte.d.ts","sourceRoot":"","sources":["../src/lib/AppChrome.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,iDAAiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,MAAM,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"AppChrome.svelte.d.ts","sourceRoot":"","sources":["../src/lib/AppChrome.svelte.ts"],"names":[],"mappings":"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"}
|
package/dist/AppChrome.test.js
CHANGED
|
@@ -43,6 +43,20 @@ describe("AppChrome — navigation", () => {
|
|
|
43
43
|
expect(links[1].getAttribute("aria-current")).toBeNull();
|
|
44
44
|
expect(links[0].classList.contains("st-appHeader__navLink")).toBe(true);
|
|
45
45
|
});
|
|
46
|
+
it("forwards navAlign=center to AppHeader", () => {
|
|
47
|
+
const { container } = render(AppChrome, { props: { nav, navAlign: "center" } });
|
|
48
|
+
expect(container.querySelector(".st-appHeader__bar")?.classList.contains("st-appHeader__bar--navCenter")).toBe(true);
|
|
49
|
+
expect(container.querySelector(".st-appHeader__nav")?.classList.contains("st-appHeader__nav--center")).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
it("filters role-gated items and renders disabled nav without href", () => {
|
|
52
|
+
const { container } = render(AppChrome, {
|
|
53
|
+
props: { nav: [...nav, { label: "Admin", href: "/admin", role: "admin" }, { label: "Soon", href: "/soon", disabled: true }], userRoles: ["member"] },
|
|
54
|
+
});
|
|
55
|
+
const links = Array.from(container.querySelectorAll(".st-appChrome__navLink"));
|
|
56
|
+
expect(links.map((a) => a.textContent?.trim())).toEqual(["Vues", "Données", "Réglages", "Soon"]);
|
|
57
|
+
expect(links.at(-1)?.hasAttribute("href")).toBe(false);
|
|
58
|
+
expect(links.at(-1)?.getAttribute("aria-disabled")).toBe("true");
|
|
59
|
+
});
|
|
46
60
|
});
|
|
47
61
|
describe("AppChrome — contrôle thème", () => {
|
|
48
62
|
it("shows the active theme label and fires onThemeChange on selection", async () => {
|
package/dist/AppHeader.svelte
CHANGED
|
@@ -388,6 +388,15 @@
|
|
|
388
388
|
font-weight: 650;
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
+
:global(.st-appChrome__navLink--disabled),
|
|
392
|
+
:global(.st-appChrome__navLink--disabled:hover),
|
|
393
|
+
:global(.st-appChrome__navLink--disabled:focus-visible) {
|
|
394
|
+
color: var(--st-semantic-text-disabled, var(--st-semantic-text-secondary));
|
|
395
|
+
cursor: not-allowed;
|
|
396
|
+
opacity: 0.56;
|
|
397
|
+
pointer-events: none;
|
|
398
|
+
}
|
|
399
|
+
|
|
391
400
|
/* --- Contrôle utilitaire canonique (pill : thème / langue / icône) --- */
|
|
392
401
|
:global(.st-appHeader__control) {
|
|
393
402
|
align-items: center;
|
package/dist/Collapsible.svelte
CHANGED
package/dist/Collapsible.test.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
1
4
|
import { fireEvent, render } from "@testing-library/svelte";
|
|
2
5
|
import { createRawSnippet } from "svelte";
|
|
3
6
|
import { describe, expect, it } from "vitest";
|
|
@@ -66,3 +69,8 @@ describe("Collapsible — a11y unchanged", () => {
|
|
|
66
69
|
expect(trigger.getAttribute("aria-expanded")).toBe("true");
|
|
67
70
|
});
|
|
68
71
|
});
|
|
72
|
+
it("scopes open chevron rotation to the direct trigger icon", () => {
|
|
73
|
+
const source = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "Collapsible.svelte"), "utf8");
|
|
74
|
+
expect(source).toContain(".st-collapsible--open > .st-collapsible__trigger .st-collapsible__icon");
|
|
75
|
+
expect(source).not.toContain(".st-collapsible--open .st-collapsible__icon {\n transform");
|
|
76
|
+
});
|
package/dist/Dropdown.svelte
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { ChevronDown } from "@lucide/svelte";
|
|
3
3
|
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
import Portal from "./Portal.svelte";
|
|
4
5
|
|
|
5
6
|
export type DropdownOption = {
|
|
6
7
|
label: string;
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
let currentValue = $state<string | undefined>(undefined);
|
|
41
42
|
let syncedOpen = $state<boolean | undefined>(undefined);
|
|
42
43
|
let syncedValue = $state<string | undefined>(undefined);
|
|
44
|
+
let listEl: HTMLDivElement | undefined = $state();
|
|
43
45
|
let listPos = $state({ top: 0, left: 0, width: 0 });
|
|
44
46
|
|
|
45
47
|
const classes = () => ["st-dropdown", className].filter(Boolean).join(" ");
|
|
@@ -72,7 +74,7 @@
|
|
|
72
74
|
function onWindowPointerDown(event: MouseEvent) {
|
|
73
75
|
if (!expanded) return;
|
|
74
76
|
const target = event.target as Node | null;
|
|
75
|
-
if (
|
|
77
|
+
if (target && !host?.contains(target) && !listEl?.contains(target)) close();
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
function onWindowScroll() {
|
|
@@ -122,7 +124,9 @@
|
|
|
122
124
|
/>
|
|
123
125
|
</button>
|
|
124
126
|
{#if expanded}
|
|
127
|
+
<Portal>
|
|
125
128
|
<div
|
|
129
|
+
bind:this={listEl}
|
|
126
130
|
class="st-dropdown__list"
|
|
127
131
|
role="listbox"
|
|
128
132
|
aria-label={label}
|
|
@@ -142,6 +146,7 @@
|
|
|
142
146
|
</button>
|
|
143
147
|
{/each}
|
|
144
148
|
</div>
|
|
149
|
+
</Portal>
|
|
145
150
|
{/if}
|
|
146
151
|
</div>
|
|
147
152
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dropdown.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Dropdown.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"Dropdown.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Dropdown.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIpD,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,KAAK,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC,GAAG;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC,CAAC;AAoHJ,QAAA,MAAM,QAAQ,mDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
package/package.json
CHANGED
package/dist/.srchash
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
5fa4af626e8a967b1e34a8ab542a01e4b9b3051441b1a26defa3e5b10ca0e657
|