@besaitech/ng-design-system 0.0.16 → 0.0.29
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.
|
@@ -4,7 +4,9 @@ import { DomSanitizer } from '@angular/platform-browser';
|
|
|
4
4
|
import * as i1 from '@angular/forms';
|
|
5
5
|
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, FormsModule } from '@angular/forms';
|
|
6
6
|
import { NgTemplateOutlet } from '@angular/common';
|
|
7
|
-
import {
|
|
7
|
+
import { toSignal } from '@angular/core/rxjs-interop';
|
|
8
|
+
import { Router, NavigationEnd, RouterLink, RouterLinkActive } from '@angular/router';
|
|
9
|
+
import { filter, map } from 'rxjs';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Curated set of line icons (Lucide-style, 24x24 viewBox, stroke-based).
|
|
@@ -12,10 +14,24 @@ import { RouterLink, RouterLinkActive } from '@angular/router';
|
|
|
12
14
|
* Add new icons here and they become available everywhere via name="...".
|
|
13
15
|
*/
|
|
14
16
|
const SDS_ICONS = {
|
|
17
|
+
// Added for the Belvigo merchant shell (nav + header). Lucide 24x24 stroke paths, matching the
|
|
18
|
+
// set the app already used before it moved onto this library.
|
|
19
|
+
'chart-column': '<path d="M3 3v16a2 2 0 0 0 2 2h16"/><path d="M18 17V9"/><path d="M13 17V5"/><path d="M8 17v-3"/>',
|
|
20
|
+
'clipboard-list': '<rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M12 11h4"/><path d="M12 16h4"/><path d="M8 11h.01"/><path d="M8 16h.01"/>',
|
|
21
|
+
'credit-card': '<rect width="20" height="14" x="2" y="5" rx="2"/><line x1="2" x2="22" y1="10" y2="10"/>',
|
|
22
|
+
'external-link': '<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>',
|
|
23
|
+
'languages': '<path d="m5 8 6 6"/><path d="m4 14 6-6 2-3"/><path d="M2 5h12"/><path d="M7 2h1"/><path d="m22 22-5-10-5 10"/><path d="M14 18h6"/>',
|
|
24
|
+
'megaphone': '<path d="m3 11 18-5v12L3 14v-3z"/><path d="M11.6 16.8a3 3 0 1 1-5.8-1.6"/>',
|
|
25
|
+
'monitor': '<rect width="20" height="14" x="2" y="3" rx="2"/><line x1="8" x2="16" y1="21" y2="21"/><line x1="12" x2="12" y1="17" y2="21"/>',
|
|
26
|
+
'store': '<path d="m2 7 4.41-4.41A2 2 0 0 1 7.83 2h8.34a2 2 0 0 1 1.42.59L22 7"/><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><path d="M15 22v-4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4"/><path d="M2 7h20"/><path d="M22 7v3a2 2 0 0 1-2 2a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 16 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 12 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 8 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 4 12a2 2 0 0 1-2-2V7"/>',
|
|
27
|
+
'user-round': '<circle cx="12" cy="8" r="5"/><path d="M20 21a8 8 0 0 0-16 0"/>',
|
|
15
28
|
home: '<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>',
|
|
16
29
|
'user-plus': '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><line x1="19" x2="19" y1="8" y2="14"/><line x1="22" x2="16" y1="11" y2="11"/>',
|
|
17
30
|
hand: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-hand-icon lucide-hand"><path d="M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2"/><path d="M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2"/><path d="M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8"/><path d="M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15"/></svg>',
|
|
18
31
|
users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
|
|
32
|
+
// Couriers. The back-office and the courier-facing screens both name a rider, and every Belvigo
|
|
33
|
+
// surface that does should draw the same one.
|
|
34
|
+
bike: '<circle cx="18.5" cy="17.5" r="3.5"/><circle cx="5.5" cy="17.5" r="3.5"/><circle cx="15" cy="5" r="1"/><path d="M12 17.5V14l-3-3 4-3 2 3h2"/>',
|
|
19
35
|
'building-2': '<path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z"/><path d="M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2"/><path d="M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2"/><path d="M10 6h4"/><path d="M10 10h4"/><path d="M10 14h4"/><path d="M10 18h4"/>',
|
|
20
36
|
package: '<path d="M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z"/><path d="M12 22V12"/><polyline points="3.29 7 12 12 20.71 7"/><path d="m7.5 4.27 9 5.15"/>',
|
|
21
37
|
'package-plus': '<path d="M16 16h6"/><path d="M19 13v6"/><path d="M21 10V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0"/><path d="m7.5 4.27 9 5.15"/><polyline points="3.29 7 12 12 20.71 7"/><path d="M12 22V12"/>',
|
|
@@ -260,11 +276,33 @@ class SdsInput {
|
|
|
260
276
|
placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
|
|
261
277
|
hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : []));
|
|
262
278
|
error = input('', ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
279
|
+
/**
|
|
280
|
+
* `search` is the semantically right type for a search field, and the template strips the WebKit
|
|
281
|
+
* search decorations that come with it. Without that, a `search` field with {@link clearable} draws
|
|
282
|
+
* TWO clear buttons — the component's and the browser's own, which no stylesheet can reach and which
|
|
283
|
+
* sits outside the field's own padding.
|
|
284
|
+
*/
|
|
263
285
|
type = input('text', ...(ngDevMode ? [{ debugName: "type" }] : []));
|
|
264
286
|
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
|
|
287
|
+
/**
|
|
288
|
+
* `filled` is a borderless field on a muted ground, for a search box that has to sit inside a page
|
|
289
|
+
* rather than announce itself as a form control — a storefront's search pill above all.
|
|
290
|
+
* `on-brand` is the same field tuned for a dark surface.
|
|
291
|
+
*/
|
|
265
292
|
appearance = input('default', ...(ngDevMode ? [{ debugName: "appearance" }] : []));
|
|
293
|
+
/**
|
|
294
|
+
* `pill` fully rounds the field. Separate from {@link appearance} on purpose: a bordered pill and a
|
|
295
|
+
* filled box are both real designs, and folding the two axes into one enum would forbid half of them.
|
|
296
|
+
*/
|
|
297
|
+
shape = input('control', ...(ngDevMode ? [{ debugName: "shape" }] : []));
|
|
266
298
|
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
267
299
|
clearable = input(false, ...(ngDevMode ? [{ debugName: "clearable", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
300
|
+
/**
|
|
301
|
+
* Accessible name for the clear button. French default so existing consumers are unchanged, but an
|
|
302
|
+
* app serving more than one language MUST translate it — it is an icon-only control, so this string
|
|
303
|
+
* is its only name.
|
|
304
|
+
*/
|
|
305
|
+
clearLabel = input('Effacer', ...(ngDevMode ? [{ debugName: "clearLabel" }] : []));
|
|
268
306
|
leadingIcon = input(null, ...(ngDevMode ? [{ debugName: "leadingIcon" }] : []));
|
|
269
307
|
trailingIcon = input(null, ...(ngDevMode ? [{ debugName: "trailingIcon" }] : []));
|
|
270
308
|
/**
|
|
@@ -286,6 +324,8 @@ class SdsInput {
|
|
|
286
324
|
value = signal('', ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
287
325
|
cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : []));
|
|
288
326
|
onBrand = computed(() => this.appearance() === 'on-brand', ...(ngDevMode ? [{ debugName: "onBrand" }] : []));
|
|
327
|
+
filled = computed(() => this.appearance() === 'filled', ...(ngDevMode ? [{ debugName: "filled" }] : []));
|
|
328
|
+
pill = computed(() => this.shape() === 'pill', ...(ngDevMode ? [{ debugName: "pill" }] : []));
|
|
289
329
|
isDisabled = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
290
330
|
describedBy = computed(() => {
|
|
291
331
|
if (this.error())
|
|
@@ -295,15 +335,29 @@ class SdsInput {
|
|
|
295
335
|
return null;
|
|
296
336
|
}, ...(ngDevMode ? [{ debugName: "describedBy" }] : []));
|
|
297
337
|
fieldPadding = computed(() => {
|
|
298
|
-
const
|
|
299
|
-
const
|
|
338
|
+
const inset = this.pill() ? 'pl-11' : 'pl-10';
|
|
339
|
+
const bare = this.pill() ? 'pl-5' : 'pl-3.5';
|
|
340
|
+
const left = this.leadingIcon() ? inset : bare;
|
|
341
|
+
const right = this.trailingIcon() || this.clearable() || this.trailingAction()
|
|
342
|
+
? 'pr-10'
|
|
343
|
+
: this.pill()
|
|
344
|
+
? 'pr-5'
|
|
345
|
+
: 'pr-3.5';
|
|
300
346
|
return `${left} ${right}`;
|
|
301
347
|
}, ...(ngDevMode ? [{ debugName: "fieldPadding" }] : []));
|
|
302
348
|
wrapperClasses = computed(() => {
|
|
303
|
-
const parts = [SIZES$8[this.size()]];
|
|
349
|
+
const parts = [SIZES$8[this.size()], this.pill() ? 'rounded-full' : 'rounded-control'];
|
|
304
350
|
if (this.isDisabled())
|
|
305
351
|
parts.push('opacity-60 cursor-not-allowed');
|
|
306
|
-
if (this.
|
|
352
|
+
if (this.filled()) {
|
|
353
|
+
// border-transparent, not border-0: the border box is what keeps a filled field the same height
|
|
354
|
+
// as a bordered one, so the two sit level when a toolbar mixes them.
|
|
355
|
+
parts.push('border-transparent bg-muted text-ink');
|
|
356
|
+
parts.push(this.error()
|
|
357
|
+
? 'border-danger focus-within:ring-2 focus-within:ring-danger/30'
|
|
358
|
+
: 'focus-within:ring-2 focus-within:ring-brand-500/25');
|
|
359
|
+
}
|
|
360
|
+
else if (this.onBrand()) {
|
|
307
361
|
parts.push('bg-white/10 text-white');
|
|
308
362
|
parts.push(this.error()
|
|
309
363
|
? 'border-danger focus-within:ring-2 focus-within:ring-danger/30'
|
|
@@ -343,7 +397,7 @@ class SdsInput {
|
|
|
343
397
|
this.onTouched();
|
|
344
398
|
}
|
|
345
399
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
346
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsInput, isStandalone: true, selector: "sds-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, leadingIcon: { classPropertyName: "leadingIcon", publicName: "leadingIcon", isSignal: true, isRequired: false, transformFunction: null }, trailingIcon: { classPropertyName: "trailingIcon", publicName: "trailingIcon", isSignal: true, isRequired: false, transformFunction: null }, trailingAction: { classPropertyName: "trailingAction", publicName: "trailingAction", isSignal: true, isRequired: false, transformFunction: null }, trailingActionLabel: { classPropertyName: "trailingActionLabel", publicName: "trailingActionLabel", isSignal: true, isRequired: false, transformFunction: null }, trailingActionPressed: { classPropertyName: "trailingActionPressed", publicName: "trailingActionPressed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { trailingActionClick: "trailingActionClick" }, host: { classAttribute: "block" }, providers: [
|
|
400
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsInput, isStandalone: true, selector: "sds-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, leadingIcon: { classPropertyName: "leadingIcon", publicName: "leadingIcon", isSignal: true, isRequired: false, transformFunction: null }, trailingIcon: { classPropertyName: "trailingIcon", publicName: "trailingIcon", isSignal: true, isRequired: false, transformFunction: null }, trailingAction: { classPropertyName: "trailingAction", publicName: "trailingAction", isSignal: true, isRequired: false, transformFunction: null }, trailingActionLabel: { classPropertyName: "trailingActionLabel", publicName: "trailingActionLabel", isSignal: true, isRequired: false, transformFunction: null }, trailingActionPressed: { classPropertyName: "trailingActionPressed", publicName: "trailingActionPressed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { trailingActionClick: "trailingActionClick" }, host: { classAttribute: "block" }, providers: [
|
|
347
401
|
{
|
|
348
402
|
provide: NG_VALUE_ACCESSOR,
|
|
349
403
|
useExisting: forwardRef(() => SdsInput),
|
|
@@ -361,14 +415,17 @@ class SdsInput {
|
|
|
361
415
|
}
|
|
362
416
|
|
|
363
417
|
<div
|
|
364
|
-
class="relative flex items-center
|
|
418
|
+
class="relative flex items-center border transition-colors"
|
|
365
419
|
[class]="wrapperClasses()"
|
|
366
420
|
>
|
|
367
421
|
@if (leadingIcon()) {
|
|
422
|
+
<!-- A pill needs its icon further in: on a fully-rounded field an icon at left-3 sits over
|
|
423
|
+
the curve rather than inside the field. -->
|
|
368
424
|
<sds-icon
|
|
369
425
|
[name]="leadingIcon()!"
|
|
370
426
|
[size]="18"
|
|
371
|
-
class="pointer-events-none absolute
|
|
427
|
+
class="pointer-events-none absolute opacity-70"
|
|
428
|
+
[class]="pill() ? 'left-4' : 'left-3'"
|
|
372
429
|
/>
|
|
373
430
|
}
|
|
374
431
|
|
|
@@ -381,7 +438,9 @@ class SdsInput {
|
|
|
381
438
|
[attr.aria-label]="!label() && ariaLabel() ? ariaLabel() : null"
|
|
382
439
|
[attr.aria-invalid]="error() ? 'true' : null"
|
|
383
440
|
[attr.aria-describedby]="describedBy()"
|
|
384
|
-
class="w-full bg-transparent outline-none placeholder:text-current/50 disabled:cursor-not-allowed
|
|
441
|
+
class="w-full bg-transparent outline-none placeholder:text-current/50 disabled:cursor-not-allowed
|
|
442
|
+
[&::-webkit-search-cancel-button]:appearance-none
|
|
443
|
+
[&::-webkit-search-decoration]:appearance-none"
|
|
385
444
|
[class]="fieldPadding()"
|
|
386
445
|
(input)="onInput($event)"
|
|
387
446
|
(blur)="onTouched()"
|
|
@@ -391,7 +450,7 @@ class SdsInput {
|
|
|
391
450
|
<button
|
|
392
451
|
type="button"
|
|
393
452
|
tabindex="-1"
|
|
394
|
-
aria-label="
|
|
453
|
+
[attr.aria-label]="clearLabel()"
|
|
395
454
|
class="absolute right-2.5 inline-flex h-6 w-6 items-center justify-center rounded-full opacity-60 hover:opacity-100 hover:bg-black/5"
|
|
396
455
|
(click)="clear()"
|
|
397
456
|
>
|
|
@@ -459,14 +518,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
459
518
|
}
|
|
460
519
|
|
|
461
520
|
<div
|
|
462
|
-
class="relative flex items-center
|
|
521
|
+
class="relative flex items-center border transition-colors"
|
|
463
522
|
[class]="wrapperClasses()"
|
|
464
523
|
>
|
|
465
524
|
@if (leadingIcon()) {
|
|
525
|
+
<!-- A pill needs its icon further in: on a fully-rounded field an icon at left-3 sits over
|
|
526
|
+
the curve rather than inside the field. -->
|
|
466
527
|
<sds-icon
|
|
467
528
|
[name]="leadingIcon()!"
|
|
468
529
|
[size]="18"
|
|
469
|
-
class="pointer-events-none absolute
|
|
530
|
+
class="pointer-events-none absolute opacity-70"
|
|
531
|
+
[class]="pill() ? 'left-4' : 'left-3'"
|
|
470
532
|
/>
|
|
471
533
|
}
|
|
472
534
|
|
|
@@ -479,7 +541,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
479
541
|
[attr.aria-label]="!label() && ariaLabel() ? ariaLabel() : null"
|
|
480
542
|
[attr.aria-invalid]="error() ? 'true' : null"
|
|
481
543
|
[attr.aria-describedby]="describedBy()"
|
|
482
|
-
class="w-full bg-transparent outline-none placeholder:text-current/50 disabled:cursor-not-allowed
|
|
544
|
+
class="w-full bg-transparent outline-none placeholder:text-current/50 disabled:cursor-not-allowed
|
|
545
|
+
[&::-webkit-search-cancel-button]:appearance-none
|
|
546
|
+
[&::-webkit-search-decoration]:appearance-none"
|
|
483
547
|
[class]="fieldPadding()"
|
|
484
548
|
(input)="onInput($event)"
|
|
485
549
|
(blur)="onTouched()"
|
|
@@ -489,7 +553,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
489
553
|
<button
|
|
490
554
|
type="button"
|
|
491
555
|
tabindex="-1"
|
|
492
|
-
aria-label="
|
|
556
|
+
[attr.aria-label]="clearLabel()"
|
|
493
557
|
class="absolute right-2.5 inline-flex h-6 w-6 items-center justify-center rounded-full opacity-60 hover:opacity-100 hover:bg-black/5"
|
|
494
558
|
(click)="clear()"
|
|
495
559
|
>
|
|
@@ -531,7 +595,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
531
595
|
}
|
|
532
596
|
`,
|
|
533
597
|
}]
|
|
534
|
-
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], leadingIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "leadingIcon", required: false }] }], trailingIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingIcon", required: false }] }], trailingAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingAction", required: false }] }], trailingActionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingActionLabel", required: false }] }], trailingActionPressed: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingActionPressed", required: false }] }], trailingActionClick: [{ type: i0.Output, args: ["trailingActionClick"] }] } });
|
|
598
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], leadingIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "leadingIcon", required: false }] }], trailingIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingIcon", required: false }] }], trailingAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingAction", required: false }] }], trailingActionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingActionLabel", required: false }] }], trailingActionPressed: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingActionPressed", required: false }] }], trailingActionClick: [{ type: i0.Output, args: ["trailingActionClick"] }] } });
|
|
535
599
|
|
|
536
600
|
let nextId$9 = 0;
|
|
537
601
|
const SIZES$7 = {
|
|
@@ -6501,29 +6565,72 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
6501
6565
|
}] });
|
|
6502
6566
|
|
|
6503
6567
|
/**
|
|
6504
|
-
* Application top bar:
|
|
6505
|
-
*
|
|
6568
|
+
* Application top bar: a logo slot, the app name, and three slots the app fills.
|
|
6569
|
+
*
|
|
6570
|
+
* Two appearances. `brand` is the gradient-and-wave bar an internal tool wants. `surface` is white on
|
|
6571
|
+
* a hairline border, for a bar that has to sit under a customer-facing page without dominating it —
|
|
6572
|
+
* a storefront header is a bar, but it is not a banner. The slots and the layout are identical in
|
|
6573
|
+
* both; only the paint differs, so a consumer app gets the same structure without the brand fill.
|
|
6574
|
+
*
|
|
6575
|
+
* It deliberately owns NO account UI. It used to render the signed-in user and a logout button, which
|
|
6576
|
+
* forced every consumer into one shape — no dropdown, no email, no menu, no busy state, and an
|
|
6577
|
+
* icon-only button whose accessible name the library had to guess a language for. What an app needs
|
|
6578
|
+
* beside its own brand bar differs per app, so the bar supplies the surface and the app supplies the
|
|
6579
|
+
* contents: project them into `[sdsActions]`.
|
|
6580
|
+
*
|
|
6581
|
+
* Slots, left to right: `[sdsLogo]`, `[sdsLead]` (beside the name — a location or workspace picker),
|
|
6582
|
+
* `[sdsCenter]` (the flexible middle — a search field), `[sdsActions]` (the right edge).
|
|
6506
6583
|
*
|
|
6507
6584
|
* @example
|
|
6508
|
-
* <sds-topbar appName="WebApp"
|
|
6585
|
+
* <sds-topbar appName="WebApp">
|
|
6509
6586
|
* <img sdsLogo src="logo.svg" alt="" />
|
|
6587
|
+
* <my-address-picker sdsLead />
|
|
6588
|
+
* <my-search sdsCenter />
|
|
6589
|
+
* <my-account-menu sdsActions />
|
|
6510
6590
|
* </sds-topbar>
|
|
6511
6591
|
*/
|
|
6512
6592
|
class SdsTopbar {
|
|
6513
6593
|
store = inject(SidebarStore);
|
|
6514
6594
|
appName = input('', ...(ngDevMode ? [{ debugName: "appName" }] : []));
|
|
6515
|
-
|
|
6516
|
-
|
|
6595
|
+
/**
|
|
6596
|
+
* Width of the bar's CONTENTS, as Tailwind width utilities — e.g. `"w-full max-w-7xl"`.
|
|
6597
|
+
*
|
|
6598
|
+
* The coloured band always spans the viewport; this constrains only what sits on it, so the logo and
|
|
6599
|
+
* the actions line up with a page whose own content is centred at the same width. Default is empty,
|
|
6600
|
+
* i.e. contents run edge to edge, which is right for a full-bleed dashboard shell.
|
|
6601
|
+
*
|
|
6602
|
+
* A class string rather than an enum on purpose: apps already state their content width as a Tailwind
|
|
6603
|
+
* utility, and this way the bar is given the SAME string rather than a name that has to be kept in
|
|
6604
|
+
* sync with it.
|
|
6605
|
+
*/
|
|
6606
|
+
contentWidth = input('w-full', ...(ngDevMode ? [{ debugName: "contentWidth" }] : []));
|
|
6517
6607
|
/** Show the mobile hamburger that toggles the sidebar drawer (< lg). */
|
|
6518
6608
|
menuButton = input(true, ...(ngDevMode ? [{ debugName: "menuButton", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
6519
|
-
|
|
6609
|
+
/**
|
|
6610
|
+
* Accessible name for the hamburger. Defaults to French so nothing changes for existing consumers,
|
|
6611
|
+
* but any app serving more than one language MUST pass a translated value — an icon-only control's
|
|
6612
|
+
* aria-label is its only name, and a hardcoded one is simply wrong in every other locale.
|
|
6613
|
+
*/
|
|
6614
|
+
menuLabel = input('Ouvrir le menu', ...(ngDevMode ? [{ debugName: "menuLabel" }] : []));
|
|
6615
|
+
/** `brand` (default) paints the gradient and the wave; `surface` is white on a hairline border. */
|
|
6616
|
+
appearance = input('brand', ...(ngDevMode ? [{ debugName: "appearance" }] : []));
|
|
6617
|
+
shellClasses = computed(() => this.appearance() === 'brand'
|
|
6618
|
+
? 'bg-linear-to-r from-brand-800 via-brand-700 to-brand-600 text-white'
|
|
6619
|
+
: 'border-b border-border bg-surface text-ink', ...(ngDevMode ? [{ debugName: "shellClasses" }] : []));
|
|
6620
|
+
menuButtonClasses = computed(() => this.appearance() === 'brand'
|
|
6621
|
+
? 'text-white/85 hover:bg-white/10 hover:text-white focus-visible:ring-white/40'
|
|
6622
|
+
: 'text-ink-soft hover:bg-muted hover:text-ink focus-visible:ring-brand-500/40', ...(ngDevMode ? [{ debugName: "menuButtonClasses" }] : []));
|
|
6520
6623
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTopbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6521
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsTopbar, isStandalone: true, selector: "sds-topbar", inputs: { appName: { classPropertyName: "appName", publicName: "appName", isSignal: true, isRequired: false, transformFunction: null },
|
|
6522
|
-
<header
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6624
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsTopbar, isStandalone: true, selector: "sds-topbar", inputs: { appName: { classPropertyName: "appName", publicName: "appName", isSignal: true, isRequired: false, transformFunction: null }, contentWidth: { classPropertyName: "contentWidth", publicName: "contentWidth", isSignal: true, isRequired: false, transformFunction: null }, menuButton: { classPropertyName: "menuButton", publicName: "menuButton", isSignal: true, isRequired: false, transformFunction: null }, menuLabel: { classPropertyName: "menuLabel", publicName: "menuLabel", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
6625
|
+
<header class="relative z-30" [class]="shellClasses()" style="height: var(--sds-topbar-h)">
|
|
6626
|
+
<!--
|
|
6627
|
+
Decorative wave. The clip lives HERE, on the wave's own wrapper, and not on the header.
|
|
6628
|
+
The header used to carry overflow-hidden, which also clipped anything projected into
|
|
6629
|
+
[sdsActions] — so an app menu or dropdown, the very thing that slot exists for, rendered as a
|
|
6630
|
+
4px sliver. The wave still needs clipping; nothing else does.
|
|
6631
|
+
-->
|
|
6632
|
+
@if (appearance() === 'brand') {
|
|
6633
|
+
<span class="pointer-events-none absolute inset-0 overflow-hidden" aria-hidden="true">
|
|
6527
6634
|
<svg
|
|
6528
6635
|
class="pointer-events-none absolute inset-y-0 right-0 h-full w-2/3 min-w-[420px]"
|
|
6529
6636
|
viewBox="0 0 600 64"
|
|
@@ -6549,66 +6656,80 @@ class SdsTopbar {
|
|
|
6549
6656
|
stroke-width="1.5"
|
|
6550
6657
|
/>
|
|
6551
6658
|
</svg>
|
|
6659
|
+
</span>
|
|
6660
|
+
}
|
|
6552
6661
|
|
|
6662
|
+
<!--
|
|
6663
|
+
The band is always full-bleed — the gradient and the wave span the viewport — while the
|
|
6664
|
+
CONTENTS are constrained by [contentWidth], so a bar over a centred page lines up with it
|
|
6665
|
+
instead of running to the edges. Empty (the default) leaves the contents full-bleed too,
|
|
6666
|
+
which is what a dashboard shell wants.
|
|
6667
|
+
-->
|
|
6668
|
+
<div
|
|
6669
|
+
class="relative mx-auto flex h-full items-center justify-between px-4 sm:px-6"
|
|
6670
|
+
[class]="contentWidth()"
|
|
6671
|
+
>
|
|
6553
6672
|
<!-- Left: mobile menu button + logo + app name -->
|
|
6554
6673
|
<div class="relative flex items-center gap-3">
|
|
6555
6674
|
@if (menuButton()) {
|
|
6556
6675
|
<button
|
|
6557
6676
|
type="button"
|
|
6558
|
-
class="-ml-1 inline-flex h-9 w-9 items-center justify-center rounded-control
|
|
6559
|
-
|
|
6677
|
+
class="-ml-1 inline-flex h-9 w-9 items-center justify-center rounded-control transition-colors focus-visible:outline-none focus-visible:ring-2 lg:hidden"
|
|
6678
|
+
[class]="menuButtonClasses()"
|
|
6679
|
+
[attr.aria-label]="menuLabel()"
|
|
6560
6680
|
(click)="store.toggleMobile()"
|
|
6561
6681
|
>
|
|
6562
6682
|
<sds-icon name="menu" [size]="22" />
|
|
6563
6683
|
</button>
|
|
6564
6684
|
}
|
|
6565
6685
|
<ng-content select="[sdsLogo]" />
|
|
6686
|
+
<!--
|
|
6687
|
+
shrink-0 + whitespace-nowrap, both required. The left block is a flex row, so without them a
|
|
6688
|
+
two-word app name wraps to a second line INSIDE a bar whose height is fixed at
|
|
6689
|
+
--sds-topbar-h, and the name is clipped by its own bar. It surfaced at 375px on a bar
|
|
6690
|
+
carrying a logo, a wordmark and a two-syllable name; it is wrong at every width.
|
|
6691
|
+
|
|
6692
|
+
The name never truncates and never hides: it is the answer to "which app am I in", which on a
|
|
6693
|
+
platform of five same-branded apps is the one thing the bar must not drop. An app whose left
|
|
6694
|
+
block is too crowded for a phone hides ITS OWN wordmark under sm and keeps the mark.
|
|
6695
|
+
-->
|
|
6566
6696
|
@if (appName()) {
|
|
6567
|
-
<span class="h-7 w-px bg-white/30"></span>
|
|
6568
|
-
<span class="text-xl font-bold tracking-tight">{{ appName() }}</span>
|
|
6697
|
+
<span class="h-7 w-px shrink-0" [class]="appearance() === 'brand' ? 'bg-white/30' : 'bg-border'"></span>
|
|
6698
|
+
<span class="shrink-0 whitespace-nowrap text-xl font-bold tracking-tight">{{ appName() }}</span>
|
|
6569
6699
|
}
|
|
6700
|
+
<ng-content select="[sdsLead]" />
|
|
6701
|
+
</div>
|
|
6702
|
+
|
|
6703
|
+
<!-- Flexible middle: takes the space the two ends leave, and is skipped entirely when empty. -->
|
|
6704
|
+
<div class="relative flex min-w-0 flex-1 items-center justify-center px-2">
|
|
6705
|
+
<ng-content select="[sdsCenter]" />
|
|
6570
6706
|
</div>
|
|
6571
6707
|
|
|
6572
|
-
<!-- Right:
|
|
6708
|
+
<!-- Right: whatever the app puts here -->
|
|
6573
6709
|
<div class="relative flex items-center gap-3 sm:gap-4">
|
|
6574
6710
|
<ng-content select="[sdsActions]" />
|
|
6575
|
-
|
|
6576
|
-
<span class="hidden text-sm font-medium sm:block">{{ u.name }}</span>
|
|
6577
|
-
<sds-avatar
|
|
6578
|
-
[name]="u.name"
|
|
6579
|
-
[initials]="u.initials ?? ''"
|
|
6580
|
-
[status]="u.status ?? 'online'"
|
|
6581
|
-
[size]="38"
|
|
6582
|
-
bgClass="bg-white/15"
|
|
6583
|
-
/>
|
|
6584
|
-
}
|
|
6585
|
-
@if (showLogout()) {
|
|
6586
|
-
<button
|
|
6587
|
-
type="button"
|
|
6588
|
-
class="inline-flex h-9 w-9 items-center justify-center rounded-control text-white/85 transition-colors hover:bg-white/10 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40"
|
|
6589
|
-
aria-label="Se déconnecter"
|
|
6590
|
-
(click)="logout.emit()"
|
|
6591
|
-
>
|
|
6592
|
-
<sds-icon name="log-out" [size]="20" />
|
|
6593
|
-
</button>
|
|
6594
|
-
}
|
|
6711
|
+
</div>
|
|
6595
6712
|
</div>
|
|
6596
6713
|
</header>
|
|
6597
|
-
`, isInline: true, dependencies: [{ kind: "component", type:
|
|
6714
|
+
`, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6598
6715
|
}
|
|
6599
6716
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTopbar, decorators: [{
|
|
6600
6717
|
type: Component,
|
|
6601
6718
|
args: [{
|
|
6602
6719
|
selector: 'sds-topbar',
|
|
6603
6720
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
6604
|
-
imports: [
|
|
6721
|
+
imports: [SdsIcon],
|
|
6605
6722
|
host: { class: 'block' },
|
|
6606
6723
|
template: `
|
|
6607
|
-
<header
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6724
|
+
<header class="relative z-30" [class]="shellClasses()" style="height: var(--sds-topbar-h)">
|
|
6725
|
+
<!--
|
|
6726
|
+
Decorative wave. The clip lives HERE, on the wave's own wrapper, and not on the header.
|
|
6727
|
+
The header used to carry overflow-hidden, which also clipped anything projected into
|
|
6728
|
+
[sdsActions] — so an app menu or dropdown, the very thing that slot exists for, rendered as a
|
|
6729
|
+
4px sliver. The wave still needs clipping; nothing else does.
|
|
6730
|
+
-->
|
|
6731
|
+
@if (appearance() === 'brand') {
|
|
6732
|
+
<span class="pointer-events-none absolute inset-0 overflow-hidden" aria-hidden="true">
|
|
6612
6733
|
<svg
|
|
6613
6734
|
class="pointer-events-none absolute inset-y-0 right-0 h-full w-2/3 min-w-[420px]"
|
|
6614
6735
|
viewBox="0 0 600 64"
|
|
@@ -6634,54 +6755,64 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
6634
6755
|
stroke-width="1.5"
|
|
6635
6756
|
/>
|
|
6636
6757
|
</svg>
|
|
6758
|
+
</span>
|
|
6759
|
+
}
|
|
6637
6760
|
|
|
6761
|
+
<!--
|
|
6762
|
+
The band is always full-bleed — the gradient and the wave span the viewport — while the
|
|
6763
|
+
CONTENTS are constrained by [contentWidth], so a bar over a centred page lines up with it
|
|
6764
|
+
instead of running to the edges. Empty (the default) leaves the contents full-bleed too,
|
|
6765
|
+
which is what a dashboard shell wants.
|
|
6766
|
+
-->
|
|
6767
|
+
<div
|
|
6768
|
+
class="relative mx-auto flex h-full items-center justify-between px-4 sm:px-6"
|
|
6769
|
+
[class]="contentWidth()"
|
|
6770
|
+
>
|
|
6638
6771
|
<!-- Left: mobile menu button + logo + app name -->
|
|
6639
6772
|
<div class="relative flex items-center gap-3">
|
|
6640
6773
|
@if (menuButton()) {
|
|
6641
6774
|
<button
|
|
6642
6775
|
type="button"
|
|
6643
|
-
class="-ml-1 inline-flex h-9 w-9 items-center justify-center rounded-control
|
|
6644
|
-
|
|
6776
|
+
class="-ml-1 inline-flex h-9 w-9 items-center justify-center rounded-control transition-colors focus-visible:outline-none focus-visible:ring-2 lg:hidden"
|
|
6777
|
+
[class]="menuButtonClasses()"
|
|
6778
|
+
[attr.aria-label]="menuLabel()"
|
|
6645
6779
|
(click)="store.toggleMobile()"
|
|
6646
6780
|
>
|
|
6647
6781
|
<sds-icon name="menu" [size]="22" />
|
|
6648
6782
|
</button>
|
|
6649
6783
|
}
|
|
6650
6784
|
<ng-content select="[sdsLogo]" />
|
|
6785
|
+
<!--
|
|
6786
|
+
shrink-0 + whitespace-nowrap, both required. The left block is a flex row, so without them a
|
|
6787
|
+
two-word app name wraps to a second line INSIDE a bar whose height is fixed at
|
|
6788
|
+
--sds-topbar-h, and the name is clipped by its own bar. It surfaced at 375px on a bar
|
|
6789
|
+
carrying a logo, a wordmark and a two-syllable name; it is wrong at every width.
|
|
6790
|
+
|
|
6791
|
+
The name never truncates and never hides: it is the answer to "which app am I in", which on a
|
|
6792
|
+
platform of five same-branded apps is the one thing the bar must not drop. An app whose left
|
|
6793
|
+
block is too crowded for a phone hides ITS OWN wordmark under sm and keeps the mark.
|
|
6794
|
+
-->
|
|
6651
6795
|
@if (appName()) {
|
|
6652
|
-
<span class="h-7 w-px bg-white/30"></span>
|
|
6653
|
-
<span class="text-xl font-bold tracking-tight">{{ appName() }}</span>
|
|
6796
|
+
<span class="h-7 w-px shrink-0" [class]="appearance() === 'brand' ? 'bg-white/30' : 'bg-border'"></span>
|
|
6797
|
+
<span class="shrink-0 whitespace-nowrap text-xl font-bold tracking-tight">{{ appName() }}</span>
|
|
6654
6798
|
}
|
|
6799
|
+
<ng-content select="[sdsLead]" />
|
|
6655
6800
|
</div>
|
|
6656
6801
|
|
|
6657
|
-
<!--
|
|
6802
|
+
<!-- Flexible middle: takes the space the two ends leave, and is skipped entirely when empty. -->
|
|
6803
|
+
<div class="relative flex min-w-0 flex-1 items-center justify-center px-2">
|
|
6804
|
+
<ng-content select="[sdsCenter]" />
|
|
6805
|
+
</div>
|
|
6806
|
+
|
|
6807
|
+
<!-- Right: whatever the app puts here -->
|
|
6658
6808
|
<div class="relative flex items-center gap-3 sm:gap-4">
|
|
6659
6809
|
<ng-content select="[sdsActions]" />
|
|
6660
|
-
|
|
6661
|
-
<span class="hidden text-sm font-medium sm:block">{{ u.name }}</span>
|
|
6662
|
-
<sds-avatar
|
|
6663
|
-
[name]="u.name"
|
|
6664
|
-
[initials]="u.initials ?? ''"
|
|
6665
|
-
[status]="u.status ?? 'online'"
|
|
6666
|
-
[size]="38"
|
|
6667
|
-
bgClass="bg-white/15"
|
|
6668
|
-
/>
|
|
6669
|
-
}
|
|
6670
|
-
@if (showLogout()) {
|
|
6671
|
-
<button
|
|
6672
|
-
type="button"
|
|
6673
|
-
class="inline-flex h-9 w-9 items-center justify-center rounded-control text-white/85 transition-colors hover:bg-white/10 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40"
|
|
6674
|
-
aria-label="Se déconnecter"
|
|
6675
|
-
(click)="logout.emit()"
|
|
6676
|
-
>
|
|
6677
|
-
<sds-icon name="log-out" [size]="20" />
|
|
6678
|
-
</button>
|
|
6679
|
-
}
|
|
6810
|
+
</div>
|
|
6680
6811
|
</div>
|
|
6681
6812
|
</header>
|
|
6682
6813
|
`,
|
|
6683
6814
|
}]
|
|
6684
|
-
}], propDecorators: { appName: [{ type: i0.Input, args: [{ isSignal: true, alias: "appName", required: false }] }],
|
|
6815
|
+
}], propDecorators: { appName: [{ type: i0.Input, args: [{ isSignal: true, alias: "appName", required: false }] }], contentWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentWidth", required: false }] }], menuButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuButton", required: false }] }], menuLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuLabel", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }] } });
|
|
6685
6816
|
|
|
6686
6817
|
/**
|
|
6687
6818
|
* App layout shell: a full-height column with the top bar across the top and a
|
|
@@ -6744,6 +6875,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
6744
6875
|
*/
|
|
6745
6876
|
class SdsSidebar {
|
|
6746
6877
|
store = inject(SidebarStore);
|
|
6878
|
+
/**
|
|
6879
|
+
* Accessible names for the rail's three icon-only controls. French defaults keep existing consumers
|
|
6880
|
+
* unchanged; a multilingual app must pass translated values, since an icon-only button's aria-label
|
|
6881
|
+
* is its only name.
|
|
6882
|
+
*/
|
|
6883
|
+
collapseLabel = input('Replier le menu', ...(ngDevMode ? [{ debugName: "collapseLabel" }] : []));
|
|
6884
|
+
expandLabel = input('Déplier le menu', ...(ngDevMode ? [{ debugName: "expandLabel" }] : []));
|
|
6885
|
+
closeLabel = input('Fermer le menu', ...(ngDevMode ? [{ debugName: "closeLabel" }] : []));
|
|
6747
6886
|
collapsed = this.store.collapsed;
|
|
6748
6887
|
mobileOpen = this.store.mobileOpen;
|
|
6749
6888
|
/** Desktop rail width (full vs. collapsed); mobile always uses full width. */
|
|
@@ -6751,7 +6890,7 @@ class SdsSidebar {
|
|
|
6751
6890
|
? 'var(--sds-sidebar-w-collapsed)'
|
|
6752
6891
|
: 'var(--sds-sidebar-w)', ...(ngDevMode ? [{ debugName: "railWidth" }] : []));
|
|
6753
6892
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsSidebar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6754
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsSidebar, isStandalone: true, selector: "sds-sidebar", host: { classAttribute: "block h-full" }, ngImport: i0, template: `
|
|
6893
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsSidebar, isStandalone: true, selector: "sds-sidebar", inputs: { collapseLabel: { classPropertyName: "collapseLabel", publicName: "collapseLabel", isSignal: true, isRequired: false, transformFunction: null }, expandLabel: { classPropertyName: "expandLabel", publicName: "expandLabel", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block h-full" }, ngImport: i0, template: `
|
|
6755
6894
|
<!-- Mobile backdrop (only when the drawer is open, < lg) -->
|
|
6756
6895
|
@if (mobileOpen()) {
|
|
6757
6896
|
<div
|
|
@@ -6779,7 +6918,7 @@ class SdsSidebar {
|
|
|
6779
6918
|
<button
|
|
6780
6919
|
type="button"
|
|
6781
6920
|
class="hidden h-9 w-9 items-center justify-center rounded-control text-white/70 transition-colors hover:bg-white/10 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40 lg:inline-flex"
|
|
6782
|
-
[attr.aria-label]="collapsed() ?
|
|
6921
|
+
[attr.aria-label]="collapsed() ? expandLabel() : collapseLabel()"
|
|
6783
6922
|
[attr.aria-expanded]="!collapsed()"
|
|
6784
6923
|
(click)="store.toggle()"
|
|
6785
6924
|
>
|
|
@@ -6791,7 +6930,7 @@ class SdsSidebar {
|
|
|
6791
6930
|
<button
|
|
6792
6931
|
type="button"
|
|
6793
6932
|
class="inline-flex h-9 w-9 items-center justify-center rounded-control text-white/70 transition-colors hover:bg-white/10 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40 lg:hidden"
|
|
6794
|
-
aria-label="
|
|
6933
|
+
[attr.aria-label]="closeLabel()"
|
|
6795
6934
|
(click)="store.closeMobile()"
|
|
6796
6935
|
>
|
|
6797
6936
|
<sds-icon name="x" [size]="20" />
|
|
@@ -6845,7 +6984,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
6845
6984
|
<button
|
|
6846
6985
|
type="button"
|
|
6847
6986
|
class="hidden h-9 w-9 items-center justify-center rounded-control text-white/70 transition-colors hover:bg-white/10 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40 lg:inline-flex"
|
|
6848
|
-
[attr.aria-label]="collapsed() ?
|
|
6987
|
+
[attr.aria-label]="collapsed() ? expandLabel() : collapseLabel()"
|
|
6849
6988
|
[attr.aria-expanded]="!collapsed()"
|
|
6850
6989
|
(click)="store.toggle()"
|
|
6851
6990
|
>
|
|
@@ -6857,7 +6996,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
6857
6996
|
<button
|
|
6858
6997
|
type="button"
|
|
6859
6998
|
class="inline-flex h-9 w-9 items-center justify-center rounded-control text-white/70 transition-colors hover:bg-white/10 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40 lg:hidden"
|
|
6860
|
-
aria-label="
|
|
6999
|
+
[attr.aria-label]="closeLabel()"
|
|
6861
7000
|
(click)="store.closeMobile()"
|
|
6862
7001
|
>
|
|
6863
7002
|
<sds-icon name="x" [size]="20" />
|
|
@@ -6876,62 +7015,206 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
6876
7015
|
</aside>
|
|
6877
7016
|
`,
|
|
6878
7017
|
}]
|
|
6879
|
-
}] });
|
|
7018
|
+
}], propDecorators: { collapseLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapseLabel", required: false }] }], expandLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandLabel", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }] } });
|
|
6880
7019
|
|
|
6881
7020
|
/**
|
|
6882
|
-
* Groups nav items under an optional uppercase section label.
|
|
6883
|
-
*
|
|
7021
|
+
* Groups nav items under an optional uppercase section label.
|
|
7022
|
+
*
|
|
7023
|
+
* Two modes:
|
|
7024
|
+
* - **Section** (default) — a static label over its items. The label is replaced by a divider when
|
|
7025
|
+
* the rail is collapsed.
|
|
7026
|
+
* - **Collapsible** (`[collapsible]`) — the label becomes a button that expands and collapses the
|
|
7027
|
+
* group. Set `[basePath]` and the group opens itself whenever the active route is inside it, so a
|
|
7028
|
+
* deep link or a browser Back lands with the right section already open instead of the person
|
|
7029
|
+
* having to find and re-open it.
|
|
7030
|
+
*
|
|
7031
|
+
* Collapsible mode exists because a flat rail stops scaling somewhere around a dozen destinations,
|
|
7032
|
+
* and the alternative — showing every child of every section at once — buries the ones in use.
|
|
7033
|
+
*
|
|
7034
|
+
* On the collapsed desktop rail there is nowhere to put a header, so a collapsible group renders
|
|
7035
|
+
* exactly like a section: divider, children visible. Hiding them there would leave the rail with
|
|
7036
|
+
* unreachable destinations and no affordance to reveal them.
|
|
6884
7037
|
*/
|
|
6885
7038
|
class SdsNavGroup {
|
|
6886
7039
|
store = inject(SidebarStore);
|
|
7040
|
+
router = inject(Router);
|
|
6887
7041
|
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
7042
|
+
/** Turn the label into an expand/collapse control. */
|
|
7043
|
+
collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
7044
|
+
/** Icon for a collapsible header, so it reads at the same weight as the items it sits among. */
|
|
7045
|
+
icon = input(null, ...(ngDevMode ? [{ debugName: "icon" }] : []));
|
|
7046
|
+
/**
|
|
7047
|
+
* URL prefix owned by this group, e.g. `/payments`. While the active route starts with it the
|
|
7048
|
+
* group counts as active and opens itself — unless the person has explicitly toggled it since.
|
|
7049
|
+
*/
|
|
7050
|
+
basePath = input(null, ...(ngDevMode ? [{ debugName: "basePath" }] : []));
|
|
7051
|
+
/** Emits the new state on every manual toggle. Not emitted for route-driven auto-expansion. */
|
|
7052
|
+
expandedChange = output();
|
|
7053
|
+
/** Explicit user choice; null means "follow the route". */
|
|
7054
|
+
manual = signal(null, ...(ngDevMode ? [{ debugName: "manual" }] : []));
|
|
7055
|
+
url = toSignal(this.router.events.pipe(filter((e) => e instanceof NavigationEnd), map((e) => e.urlAfterRedirects)), { initialValue: this.router.url });
|
|
7056
|
+
/** True while the active route lives inside this group. */
|
|
7057
|
+
routeActive = computed(() => {
|
|
7058
|
+
const base = this.basePath();
|
|
7059
|
+
return !!base && this.url().startsWith(base);
|
|
7060
|
+
}, ...(ngDevMode ? [{ debugName: "routeActive" }] : []));
|
|
7061
|
+
expanded = computed(() => this.manual() ?? this.routeActive(), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
6888
7062
|
collapsed = this.store.collapsed;
|
|
7063
|
+
/**
|
|
7064
|
+
* A plain section always shows its items — they carry their own icons, so they survive the collapsed
|
|
7065
|
+
* rail. A collapsible group hides its children there instead: its children are sub-destinations and
|
|
7066
|
+
* typically have no icon, which on an icon-only rail is a row with nothing in it.
|
|
7067
|
+
*/
|
|
7068
|
+
childrenVisible = computed(() => !this.collapsible() || (!this.collapsed() && this.expanded()), ...(ngDevMode ? [{ debugName: "childrenVisible" }] : []));
|
|
7069
|
+
/**
|
|
7070
|
+
* One binding rather than `[class]` alongside `[class.hidden]` — mixing a whole-list binding with a
|
|
7071
|
+
* single-class toggle on the same element depends on Angular's styling precedence to resolve, which
|
|
7072
|
+
* is a subtlety no template should rest on.
|
|
7073
|
+
*
|
|
7074
|
+
* The indent rule appears only on an expanded collapsible group on the full-width rail: it is what
|
|
7075
|
+
* shows the children belong to the header above them.
|
|
7076
|
+
*/
|
|
7077
|
+
childrenClass = computed(() => {
|
|
7078
|
+
const parts = ['flex flex-col gap-1'];
|
|
7079
|
+
if (!this.childrenVisible()) {
|
|
7080
|
+
parts.push('hidden');
|
|
7081
|
+
}
|
|
7082
|
+
if (this.collapsible() && !this.collapsed()) {
|
|
7083
|
+
parts.push('ml-5 border-l border-white/10 pl-3');
|
|
7084
|
+
}
|
|
7085
|
+
return parts.join(' ');
|
|
7086
|
+
}, ...(ngDevMode ? [{ debugName: "childrenClass" }] : []));
|
|
7087
|
+
/**
|
|
7088
|
+
* On the collapsed rail there is nothing to expand into, so the toggle always reports "opening" —
|
|
7089
|
+
* the app's handler takes that as the cue to navigate into the section, which is the only useful
|
|
7090
|
+
* meaning the control can have there.
|
|
7091
|
+
*/
|
|
7092
|
+
toggle() {
|
|
7093
|
+
const next = this.collapsed() ? true : !this.expanded();
|
|
7094
|
+
this.manual.set(next);
|
|
7095
|
+
this.expandedChange.emit(next);
|
|
7096
|
+
}
|
|
6889
7097
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsNavGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6890
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsNavGroup, isStandalone: true, selector: "sds-nav-group", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
7098
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsNavGroup, isStandalone: true, selector: "sds-nav-group", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, basePath: { classPropertyName: "basePath", publicName: "basePath", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandedChange: "expandedChange" }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
6891
7099
|
<div class="mt-5 first:mt-3">
|
|
6892
7100
|
@if (label()) {
|
|
6893
|
-
@if (
|
|
6894
|
-
|
|
6895
|
-
|
|
7101
|
+
@if (collapsible()) {
|
|
7102
|
+
<!--
|
|
7103
|
+
A collapsible header is styled as a NAV ROW, not as a section caption. A small uppercase
|
|
7104
|
+
caption says "everything under me belongs to me" — which is a lie the moment the group is
|
|
7105
|
+
collapsed and the next section's items sit directly beneath it. At row scale, with an icon
|
|
7106
|
+
and a chevron, it reads as the control it is.
|
|
7107
|
+
|
|
7108
|
+
It renders on the collapsed rail too, as an icon alone, because there it is the group's
|
|
7109
|
+
ONLY representation: the children are hidden (a child with no icon of its own would be a
|
|
7110
|
+
blank row), so the app is expected to treat the toggle as "take me into this section" —
|
|
7111
|
+
which is what expandedChange already means.
|
|
7112
|
+
-->
|
|
7113
|
+
<button
|
|
7114
|
+
type="button"
|
|
7115
|
+
class="mb-1 flex h-10 w-full items-center gap-3 rounded-control px-3 text-sm font-medium
|
|
7116
|
+
text-white/70 transition-colors hover:bg-white/8 hover:text-white
|
|
7117
|
+
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40"
|
|
7118
|
+
[class.lg:justify-center]="collapsed()"
|
|
7119
|
+
[class.text-white]="routeActive()"
|
|
7120
|
+
[attr.aria-expanded]="collapsed() ? null : expanded()"
|
|
7121
|
+
[attr.title]="collapsed() ? label() : null"
|
|
7122
|
+
[attr.aria-label]="collapsed() ? label() : null"
|
|
7123
|
+
(click)="toggle()"
|
|
6896
7124
|
>
|
|
7125
|
+
@if (icon()) {
|
|
7126
|
+
<sds-icon [name]="icon()!" [size]="20" class="shrink-0" />
|
|
7127
|
+
}
|
|
7128
|
+
<span class="flex-1 truncate text-left" [class.lg:hidden]="collapsed()">
|
|
7129
|
+
{{ label() }}
|
|
7130
|
+
</span>
|
|
7131
|
+
<sds-icon
|
|
7132
|
+
name="chevron-down"
|
|
7133
|
+
[size]="16"
|
|
7134
|
+
class="shrink-0 text-white/50 transition-transform"
|
|
7135
|
+
[class.lg:hidden]="collapsed()"
|
|
7136
|
+
[class.rotate-180]="expanded()"
|
|
7137
|
+
/>
|
|
7138
|
+
</button>
|
|
7139
|
+
} @else if (collapsed()) {
|
|
7140
|
+
<div class="mx-3 mb-2 mt-2 h-px bg-white/10"></div>
|
|
7141
|
+
} @else {
|
|
7142
|
+
<p class="mb-1.5 px-3 text-[11px] font-semibold uppercase tracking-wider text-white/60">
|
|
6897
7143
|
{{ label() }}
|
|
6898
7144
|
</p>
|
|
6899
|
-
} @else {
|
|
6900
|
-
<div class="mx-3 mb-2 mt-2 h-px bg-white/10"></div>
|
|
6901
7145
|
}
|
|
6902
7146
|
}
|
|
6903
|
-
|
|
7147
|
+
|
|
7148
|
+
<div [class]="childrenClass()">
|
|
6904
7149
|
<ng-content />
|
|
6905
7150
|
</div>
|
|
6906
7151
|
</div>
|
|
6907
|
-
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7152
|
+
`, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6908
7153
|
}
|
|
6909
7154
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsNavGroup, decorators: [{
|
|
6910
7155
|
type: Component,
|
|
6911
7156
|
args: [{
|
|
6912
7157
|
selector: 'sds-nav-group',
|
|
6913
7158
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7159
|
+
imports: [SdsIcon],
|
|
6914
7160
|
host: { class: 'block' },
|
|
6915
7161
|
template: `
|
|
6916
7162
|
<div class="mt-5 first:mt-3">
|
|
6917
7163
|
@if (label()) {
|
|
6918
|
-
@if (
|
|
6919
|
-
|
|
6920
|
-
|
|
7164
|
+
@if (collapsible()) {
|
|
7165
|
+
<!--
|
|
7166
|
+
A collapsible header is styled as a NAV ROW, not as a section caption. A small uppercase
|
|
7167
|
+
caption says "everything under me belongs to me" — which is a lie the moment the group is
|
|
7168
|
+
collapsed and the next section's items sit directly beneath it. At row scale, with an icon
|
|
7169
|
+
and a chevron, it reads as the control it is.
|
|
7170
|
+
|
|
7171
|
+
It renders on the collapsed rail too, as an icon alone, because there it is the group's
|
|
7172
|
+
ONLY representation: the children are hidden (a child with no icon of its own would be a
|
|
7173
|
+
blank row), so the app is expected to treat the toggle as "take me into this section" —
|
|
7174
|
+
which is what expandedChange already means.
|
|
7175
|
+
-->
|
|
7176
|
+
<button
|
|
7177
|
+
type="button"
|
|
7178
|
+
class="mb-1 flex h-10 w-full items-center gap-3 rounded-control px-3 text-sm font-medium
|
|
7179
|
+
text-white/70 transition-colors hover:bg-white/8 hover:text-white
|
|
7180
|
+
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40"
|
|
7181
|
+
[class.lg:justify-center]="collapsed()"
|
|
7182
|
+
[class.text-white]="routeActive()"
|
|
7183
|
+
[attr.aria-expanded]="collapsed() ? null : expanded()"
|
|
7184
|
+
[attr.title]="collapsed() ? label() : null"
|
|
7185
|
+
[attr.aria-label]="collapsed() ? label() : null"
|
|
7186
|
+
(click)="toggle()"
|
|
6921
7187
|
>
|
|
7188
|
+
@if (icon()) {
|
|
7189
|
+
<sds-icon [name]="icon()!" [size]="20" class="shrink-0" />
|
|
7190
|
+
}
|
|
7191
|
+
<span class="flex-1 truncate text-left" [class.lg:hidden]="collapsed()">
|
|
7192
|
+
{{ label() }}
|
|
7193
|
+
</span>
|
|
7194
|
+
<sds-icon
|
|
7195
|
+
name="chevron-down"
|
|
7196
|
+
[size]="16"
|
|
7197
|
+
class="shrink-0 text-white/50 transition-transform"
|
|
7198
|
+
[class.lg:hidden]="collapsed()"
|
|
7199
|
+
[class.rotate-180]="expanded()"
|
|
7200
|
+
/>
|
|
7201
|
+
</button>
|
|
7202
|
+
} @else if (collapsed()) {
|
|
7203
|
+
<div class="mx-3 mb-2 mt-2 h-px bg-white/10"></div>
|
|
7204
|
+
} @else {
|
|
7205
|
+
<p class="mb-1.5 px-3 text-[11px] font-semibold uppercase tracking-wider text-white/60">
|
|
6922
7206
|
{{ label() }}
|
|
6923
7207
|
</p>
|
|
6924
|
-
} @else {
|
|
6925
|
-
<div class="mx-3 mb-2 mt-2 h-px bg-white/10"></div>
|
|
6926
7208
|
}
|
|
6927
7209
|
}
|
|
6928
|
-
|
|
7210
|
+
|
|
7211
|
+
<div [class]="childrenClass()">
|
|
6929
7212
|
<ng-content />
|
|
6930
7213
|
</div>
|
|
6931
7214
|
</div>
|
|
6932
7215
|
`,
|
|
6933
7216
|
}]
|
|
6934
|
-
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
|
|
7217
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], basePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "basePath", required: false }] }], expandedChange: [{ type: i0.Output, args: ["expandedChange"] }] } });
|
|
6935
7218
|
|
|
6936
7219
|
/**
|
|
6937
7220
|
* A single sidebar navigation entry. Renders an anchor when [link] is set
|