@besaitech/ng-design-system 0.0.16 → 0.0.26
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,6 +14,17 @@ 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>',
|
|
@@ -6501,29 +6514,72 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
6501
6514
|
}] });
|
|
6502
6515
|
|
|
6503
6516
|
/**
|
|
6504
|
-
* Application top bar:
|
|
6505
|
-
*
|
|
6517
|
+
* Application top bar: a logo slot, the app name, and three slots the app fills.
|
|
6518
|
+
*
|
|
6519
|
+
* Two appearances. `brand` is the gradient-and-wave bar an internal tool wants. `surface` is white on
|
|
6520
|
+
* a hairline border, for a bar that has to sit under a customer-facing page without dominating it —
|
|
6521
|
+
* a storefront header is a bar, but it is not a banner. The slots and the layout are identical in
|
|
6522
|
+
* both; only the paint differs, so a consumer app gets the same structure without the brand fill.
|
|
6523
|
+
*
|
|
6524
|
+
* It deliberately owns NO account UI. It used to render the signed-in user and a logout button, which
|
|
6525
|
+
* forced every consumer into one shape — no dropdown, no email, no menu, no busy state, and an
|
|
6526
|
+
* icon-only button whose accessible name the library had to guess a language for. What an app needs
|
|
6527
|
+
* beside its own brand bar differs per app, so the bar supplies the surface and the app supplies the
|
|
6528
|
+
* contents: project them into `[sdsActions]`.
|
|
6529
|
+
*
|
|
6530
|
+
* Slots, left to right: `[sdsLogo]`, `[sdsLead]` (beside the name — a location or workspace picker),
|
|
6531
|
+
* `[sdsCenter]` (the flexible middle — a search field), `[sdsActions]` (the right edge).
|
|
6506
6532
|
*
|
|
6507
6533
|
* @example
|
|
6508
|
-
* <sds-topbar appName="WebApp"
|
|
6534
|
+
* <sds-topbar appName="WebApp">
|
|
6509
6535
|
* <img sdsLogo src="logo.svg" alt="" />
|
|
6536
|
+
* <my-address-picker sdsLead />
|
|
6537
|
+
* <my-search sdsCenter />
|
|
6538
|
+
* <my-account-menu sdsActions />
|
|
6510
6539
|
* </sds-topbar>
|
|
6511
6540
|
*/
|
|
6512
6541
|
class SdsTopbar {
|
|
6513
6542
|
store = inject(SidebarStore);
|
|
6514
6543
|
appName = input('', ...(ngDevMode ? [{ debugName: "appName" }] : []));
|
|
6515
|
-
|
|
6516
|
-
|
|
6544
|
+
/**
|
|
6545
|
+
* Width of the bar's CONTENTS, as Tailwind width utilities — e.g. `"w-full max-w-7xl"`.
|
|
6546
|
+
*
|
|
6547
|
+
* The coloured band always spans the viewport; this constrains only what sits on it, so the logo and
|
|
6548
|
+
* the actions line up with a page whose own content is centred at the same width. Default is empty,
|
|
6549
|
+
* i.e. contents run edge to edge, which is right for a full-bleed dashboard shell.
|
|
6550
|
+
*
|
|
6551
|
+
* A class string rather than an enum on purpose: apps already state their content width as a Tailwind
|
|
6552
|
+
* utility, and this way the bar is given the SAME string rather than a name that has to be kept in
|
|
6553
|
+
* sync with it.
|
|
6554
|
+
*/
|
|
6555
|
+
contentWidth = input('w-full', ...(ngDevMode ? [{ debugName: "contentWidth" }] : []));
|
|
6517
6556
|
/** Show the mobile hamburger that toggles the sidebar drawer (< lg). */
|
|
6518
6557
|
menuButton = input(true, ...(ngDevMode ? [{ debugName: "menuButton", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
6519
|
-
|
|
6558
|
+
/**
|
|
6559
|
+
* Accessible name for the hamburger. Defaults to French so nothing changes for existing consumers,
|
|
6560
|
+
* but any app serving more than one language MUST pass a translated value — an icon-only control's
|
|
6561
|
+
* aria-label is its only name, and a hardcoded one is simply wrong in every other locale.
|
|
6562
|
+
*/
|
|
6563
|
+
menuLabel = input('Ouvrir le menu', ...(ngDevMode ? [{ debugName: "menuLabel" }] : []));
|
|
6564
|
+
/** `brand` (default) paints the gradient and the wave; `surface` is white on a hairline border. */
|
|
6565
|
+
appearance = input('brand', ...(ngDevMode ? [{ debugName: "appearance" }] : []));
|
|
6566
|
+
shellClasses = computed(() => this.appearance() === 'brand'
|
|
6567
|
+
? 'bg-linear-to-r from-brand-800 via-brand-700 to-brand-600 text-white'
|
|
6568
|
+
: 'border-b border-border bg-surface text-ink', ...(ngDevMode ? [{ debugName: "shellClasses" }] : []));
|
|
6569
|
+
menuButtonClasses = computed(() => this.appearance() === 'brand'
|
|
6570
|
+
? 'text-white/85 hover:bg-white/10 hover:text-white focus-visible:ring-white/40'
|
|
6571
|
+
: 'text-ink-soft hover:bg-muted hover:text-ink focus-visible:ring-brand-500/40', ...(ngDevMode ? [{ debugName: "menuButtonClasses" }] : []));
|
|
6520
6572
|
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
|
-
|
|
6573
|
+
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: `
|
|
6574
|
+
<header class="relative z-30" [class]="shellClasses()" style="height: var(--sds-topbar-h)">
|
|
6575
|
+
<!--
|
|
6576
|
+
Decorative wave. The clip lives HERE, on the wave's own wrapper, and not on the header.
|
|
6577
|
+
The header used to carry overflow-hidden, which also clipped anything projected into
|
|
6578
|
+
[sdsActions] — so an app menu or dropdown, the very thing that slot exists for, rendered as a
|
|
6579
|
+
4px sliver. The wave still needs clipping; nothing else does.
|
|
6580
|
+
-->
|
|
6581
|
+
@if (appearance() === 'brand') {
|
|
6582
|
+
<span class="pointer-events-none absolute inset-0 overflow-hidden" aria-hidden="true">
|
|
6527
6583
|
<svg
|
|
6528
6584
|
class="pointer-events-none absolute inset-y-0 right-0 h-full w-2/3 min-w-[420px]"
|
|
6529
6585
|
viewBox="0 0 600 64"
|
|
@@ -6549,14 +6605,27 @@ class SdsTopbar {
|
|
|
6549
6605
|
stroke-width="1.5"
|
|
6550
6606
|
/>
|
|
6551
6607
|
</svg>
|
|
6608
|
+
</span>
|
|
6609
|
+
}
|
|
6552
6610
|
|
|
6611
|
+
<!--
|
|
6612
|
+
The band is always full-bleed — the gradient and the wave span the viewport — while the
|
|
6613
|
+
CONTENTS are constrained by [contentWidth], so a bar over a centred page lines up with it
|
|
6614
|
+
instead of running to the edges. Empty (the default) leaves the contents full-bleed too,
|
|
6615
|
+
which is what a dashboard shell wants.
|
|
6616
|
+
-->
|
|
6617
|
+
<div
|
|
6618
|
+
class="relative mx-auto flex h-full items-center justify-between px-4 sm:px-6"
|
|
6619
|
+
[class]="contentWidth()"
|
|
6620
|
+
>
|
|
6553
6621
|
<!-- Left: mobile menu button + logo + app name -->
|
|
6554
6622
|
<div class="relative flex items-center gap-3">
|
|
6555
6623
|
@if (menuButton()) {
|
|
6556
6624
|
<button
|
|
6557
6625
|
type="button"
|
|
6558
|
-
class="-ml-1 inline-flex h-9 w-9 items-center justify-center rounded-control
|
|
6559
|
-
|
|
6626
|
+
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"
|
|
6627
|
+
[class]="menuButtonClasses()"
|
|
6628
|
+
[attr.aria-label]="menuLabel()"
|
|
6560
6629
|
(click)="store.toggleMobile()"
|
|
6561
6630
|
>
|
|
6562
6631
|
<sds-icon name="menu" [size]="22" />
|
|
@@ -6564,51 +6633,42 @@ class SdsTopbar {
|
|
|
6564
6633
|
}
|
|
6565
6634
|
<ng-content select="[sdsLogo]" />
|
|
6566
6635
|
@if (appName()) {
|
|
6567
|
-
<span class="h-7 w-px bg-white/30"></span>
|
|
6636
|
+
<span class="h-7 w-px" [class]="appearance() === 'brand' ? 'bg-white/30' : 'bg-border'"></span>
|
|
6568
6637
|
<span class="text-xl font-bold tracking-tight">{{ appName() }}</span>
|
|
6569
6638
|
}
|
|
6639
|
+
<ng-content select="[sdsLead]" />
|
|
6570
6640
|
</div>
|
|
6571
6641
|
|
|
6572
|
-
<!--
|
|
6642
|
+
<!-- Flexible middle: takes the space the two ends leave, and is skipped entirely when empty. -->
|
|
6643
|
+
<div class="relative flex min-w-0 flex-1 items-center justify-center px-2">
|
|
6644
|
+
<ng-content select="[sdsCenter]" />
|
|
6645
|
+
</div>
|
|
6646
|
+
|
|
6647
|
+
<!-- Right: whatever the app puts here -->
|
|
6573
6648
|
<div class="relative flex items-center gap-3 sm:gap-4">
|
|
6574
6649
|
<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
|
-
}
|
|
6650
|
+
</div>
|
|
6595
6651
|
</div>
|
|
6596
6652
|
</header>
|
|
6597
|
-
`, isInline: true, dependencies: [{ kind: "component", type:
|
|
6653
|
+
`, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6598
6654
|
}
|
|
6599
6655
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTopbar, decorators: [{
|
|
6600
6656
|
type: Component,
|
|
6601
6657
|
args: [{
|
|
6602
6658
|
selector: 'sds-topbar',
|
|
6603
6659
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
6604
|
-
imports: [
|
|
6660
|
+
imports: [SdsIcon],
|
|
6605
6661
|
host: { class: 'block' },
|
|
6606
6662
|
template: `
|
|
6607
|
-
<header
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6663
|
+
<header class="relative z-30" [class]="shellClasses()" style="height: var(--sds-topbar-h)">
|
|
6664
|
+
<!--
|
|
6665
|
+
Decorative wave. The clip lives HERE, on the wave's own wrapper, and not on the header.
|
|
6666
|
+
The header used to carry overflow-hidden, which also clipped anything projected into
|
|
6667
|
+
[sdsActions] — so an app menu or dropdown, the very thing that slot exists for, rendered as a
|
|
6668
|
+
4px sliver. The wave still needs clipping; nothing else does.
|
|
6669
|
+
-->
|
|
6670
|
+
@if (appearance() === 'brand') {
|
|
6671
|
+
<span class="pointer-events-none absolute inset-0 overflow-hidden" aria-hidden="true">
|
|
6612
6672
|
<svg
|
|
6613
6673
|
class="pointer-events-none absolute inset-y-0 right-0 h-full w-2/3 min-w-[420px]"
|
|
6614
6674
|
viewBox="0 0 600 64"
|
|
@@ -6634,14 +6694,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
6634
6694
|
stroke-width="1.5"
|
|
6635
6695
|
/>
|
|
6636
6696
|
</svg>
|
|
6697
|
+
</span>
|
|
6698
|
+
}
|
|
6637
6699
|
|
|
6700
|
+
<!--
|
|
6701
|
+
The band is always full-bleed — the gradient and the wave span the viewport — while the
|
|
6702
|
+
CONTENTS are constrained by [contentWidth], so a bar over a centred page lines up with it
|
|
6703
|
+
instead of running to the edges. Empty (the default) leaves the contents full-bleed too,
|
|
6704
|
+
which is what a dashboard shell wants.
|
|
6705
|
+
-->
|
|
6706
|
+
<div
|
|
6707
|
+
class="relative mx-auto flex h-full items-center justify-between px-4 sm:px-6"
|
|
6708
|
+
[class]="contentWidth()"
|
|
6709
|
+
>
|
|
6638
6710
|
<!-- Left: mobile menu button + logo + app name -->
|
|
6639
6711
|
<div class="relative flex items-center gap-3">
|
|
6640
6712
|
@if (menuButton()) {
|
|
6641
6713
|
<button
|
|
6642
6714
|
type="button"
|
|
6643
|
-
class="-ml-1 inline-flex h-9 w-9 items-center justify-center rounded-control
|
|
6644
|
-
|
|
6715
|
+
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"
|
|
6716
|
+
[class]="menuButtonClasses()"
|
|
6717
|
+
[attr.aria-label]="menuLabel()"
|
|
6645
6718
|
(click)="store.toggleMobile()"
|
|
6646
6719
|
>
|
|
6647
6720
|
<sds-icon name="menu" [size]="22" />
|
|
@@ -6649,39 +6722,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
6649
6722
|
}
|
|
6650
6723
|
<ng-content select="[sdsLogo]" />
|
|
6651
6724
|
@if (appName()) {
|
|
6652
|
-
<span class="h-7 w-px bg-white/30"></span>
|
|
6725
|
+
<span class="h-7 w-px" [class]="appearance() === 'brand' ? 'bg-white/30' : 'bg-border'"></span>
|
|
6653
6726
|
<span class="text-xl font-bold tracking-tight">{{ appName() }}</span>
|
|
6654
6727
|
}
|
|
6728
|
+
<ng-content select="[sdsLead]" />
|
|
6729
|
+
</div>
|
|
6730
|
+
|
|
6731
|
+
<!-- Flexible middle: takes the space the two ends leave, and is skipped entirely when empty. -->
|
|
6732
|
+
<div class="relative flex min-w-0 flex-1 items-center justify-center px-2">
|
|
6733
|
+
<ng-content select="[sdsCenter]" />
|
|
6655
6734
|
</div>
|
|
6656
6735
|
|
|
6657
|
-
<!-- Right:
|
|
6736
|
+
<!-- Right: whatever the app puts here -->
|
|
6658
6737
|
<div class="relative flex items-center gap-3 sm:gap-4">
|
|
6659
6738
|
<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
|
-
}
|
|
6739
|
+
</div>
|
|
6680
6740
|
</div>
|
|
6681
6741
|
</header>
|
|
6682
6742
|
`,
|
|
6683
6743
|
}]
|
|
6684
|
-
}], propDecorators: { appName: [{ type: i0.Input, args: [{ isSignal: true, alias: "appName", required: false }] }],
|
|
6744
|
+
}], 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
6745
|
|
|
6686
6746
|
/**
|
|
6687
6747
|
* App layout shell: a full-height column with the top bar across the top and a
|
|
@@ -6744,6 +6804,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
6744
6804
|
*/
|
|
6745
6805
|
class SdsSidebar {
|
|
6746
6806
|
store = inject(SidebarStore);
|
|
6807
|
+
/**
|
|
6808
|
+
* Accessible names for the rail's three icon-only controls. French defaults keep existing consumers
|
|
6809
|
+
* unchanged; a multilingual app must pass translated values, since an icon-only button's aria-label
|
|
6810
|
+
* is its only name.
|
|
6811
|
+
*/
|
|
6812
|
+
collapseLabel = input('Replier le menu', ...(ngDevMode ? [{ debugName: "collapseLabel" }] : []));
|
|
6813
|
+
expandLabel = input('Déplier le menu', ...(ngDevMode ? [{ debugName: "expandLabel" }] : []));
|
|
6814
|
+
closeLabel = input('Fermer le menu', ...(ngDevMode ? [{ debugName: "closeLabel" }] : []));
|
|
6747
6815
|
collapsed = this.store.collapsed;
|
|
6748
6816
|
mobileOpen = this.store.mobileOpen;
|
|
6749
6817
|
/** Desktop rail width (full vs. collapsed); mobile always uses full width. */
|
|
@@ -6751,7 +6819,7 @@ class SdsSidebar {
|
|
|
6751
6819
|
? 'var(--sds-sidebar-w-collapsed)'
|
|
6752
6820
|
: 'var(--sds-sidebar-w)', ...(ngDevMode ? [{ debugName: "railWidth" }] : []));
|
|
6753
6821
|
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: `
|
|
6822
|
+
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
6823
|
<!-- Mobile backdrop (only when the drawer is open, < lg) -->
|
|
6756
6824
|
@if (mobileOpen()) {
|
|
6757
6825
|
<div
|
|
@@ -6779,7 +6847,7 @@ class SdsSidebar {
|
|
|
6779
6847
|
<button
|
|
6780
6848
|
type="button"
|
|
6781
6849
|
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() ?
|
|
6850
|
+
[attr.aria-label]="collapsed() ? expandLabel() : collapseLabel()"
|
|
6783
6851
|
[attr.aria-expanded]="!collapsed()"
|
|
6784
6852
|
(click)="store.toggle()"
|
|
6785
6853
|
>
|
|
@@ -6791,7 +6859,7 @@ class SdsSidebar {
|
|
|
6791
6859
|
<button
|
|
6792
6860
|
type="button"
|
|
6793
6861
|
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="
|
|
6862
|
+
[attr.aria-label]="closeLabel()"
|
|
6795
6863
|
(click)="store.closeMobile()"
|
|
6796
6864
|
>
|
|
6797
6865
|
<sds-icon name="x" [size]="20" />
|
|
@@ -6845,7 +6913,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
6845
6913
|
<button
|
|
6846
6914
|
type="button"
|
|
6847
6915
|
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() ?
|
|
6916
|
+
[attr.aria-label]="collapsed() ? expandLabel() : collapseLabel()"
|
|
6849
6917
|
[attr.aria-expanded]="!collapsed()"
|
|
6850
6918
|
(click)="store.toggle()"
|
|
6851
6919
|
>
|
|
@@ -6857,7 +6925,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
6857
6925
|
<button
|
|
6858
6926
|
type="button"
|
|
6859
6927
|
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="
|
|
6928
|
+
[attr.aria-label]="closeLabel()"
|
|
6861
6929
|
(click)="store.closeMobile()"
|
|
6862
6930
|
>
|
|
6863
6931
|
<sds-icon name="x" [size]="20" />
|
|
@@ -6876,62 +6944,206 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
6876
6944
|
</aside>
|
|
6877
6945
|
`,
|
|
6878
6946
|
}]
|
|
6879
|
-
}] });
|
|
6947
|
+
}], 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
6948
|
|
|
6881
6949
|
/**
|
|
6882
|
-
* Groups nav items under an optional uppercase section label.
|
|
6883
|
-
*
|
|
6950
|
+
* Groups nav items under an optional uppercase section label.
|
|
6951
|
+
*
|
|
6952
|
+
* Two modes:
|
|
6953
|
+
* - **Section** (default) — a static label over its items. The label is replaced by a divider when
|
|
6954
|
+
* the rail is collapsed.
|
|
6955
|
+
* - **Collapsible** (`[collapsible]`) — the label becomes a button that expands and collapses the
|
|
6956
|
+
* group. Set `[basePath]` and the group opens itself whenever the active route is inside it, so a
|
|
6957
|
+
* deep link or a browser Back lands with the right section already open instead of the person
|
|
6958
|
+
* having to find and re-open it.
|
|
6959
|
+
*
|
|
6960
|
+
* Collapsible mode exists because a flat rail stops scaling somewhere around a dozen destinations,
|
|
6961
|
+
* and the alternative — showing every child of every section at once — buries the ones in use.
|
|
6962
|
+
*
|
|
6963
|
+
* On the collapsed desktop rail there is nowhere to put a header, so a collapsible group renders
|
|
6964
|
+
* exactly like a section: divider, children visible. Hiding them there would leave the rail with
|
|
6965
|
+
* unreachable destinations and no affordance to reveal them.
|
|
6884
6966
|
*/
|
|
6885
6967
|
class SdsNavGroup {
|
|
6886
6968
|
store = inject(SidebarStore);
|
|
6969
|
+
router = inject(Router);
|
|
6887
6970
|
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
6971
|
+
/** Turn the label into an expand/collapse control. */
|
|
6972
|
+
collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
6973
|
+
/** Icon for a collapsible header, so it reads at the same weight as the items it sits among. */
|
|
6974
|
+
icon = input(null, ...(ngDevMode ? [{ debugName: "icon" }] : []));
|
|
6975
|
+
/**
|
|
6976
|
+
* URL prefix owned by this group, e.g. `/payments`. While the active route starts with it the
|
|
6977
|
+
* group counts as active and opens itself — unless the person has explicitly toggled it since.
|
|
6978
|
+
*/
|
|
6979
|
+
basePath = input(null, ...(ngDevMode ? [{ debugName: "basePath" }] : []));
|
|
6980
|
+
/** Emits the new state on every manual toggle. Not emitted for route-driven auto-expansion. */
|
|
6981
|
+
expandedChange = output();
|
|
6982
|
+
/** Explicit user choice; null means "follow the route". */
|
|
6983
|
+
manual = signal(null, ...(ngDevMode ? [{ debugName: "manual" }] : []));
|
|
6984
|
+
url = toSignal(this.router.events.pipe(filter((e) => e instanceof NavigationEnd), map((e) => e.urlAfterRedirects)), { initialValue: this.router.url });
|
|
6985
|
+
/** True while the active route lives inside this group. */
|
|
6986
|
+
routeActive = computed(() => {
|
|
6987
|
+
const base = this.basePath();
|
|
6988
|
+
return !!base && this.url().startsWith(base);
|
|
6989
|
+
}, ...(ngDevMode ? [{ debugName: "routeActive" }] : []));
|
|
6990
|
+
expanded = computed(() => this.manual() ?? this.routeActive(), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
6888
6991
|
collapsed = this.store.collapsed;
|
|
6992
|
+
/**
|
|
6993
|
+
* A plain section always shows its items — they carry their own icons, so they survive the collapsed
|
|
6994
|
+
* rail. A collapsible group hides its children there instead: its children are sub-destinations and
|
|
6995
|
+
* typically have no icon, which on an icon-only rail is a row with nothing in it.
|
|
6996
|
+
*/
|
|
6997
|
+
childrenVisible = computed(() => !this.collapsible() || (!this.collapsed() && this.expanded()), ...(ngDevMode ? [{ debugName: "childrenVisible" }] : []));
|
|
6998
|
+
/**
|
|
6999
|
+
* One binding rather than `[class]` alongside `[class.hidden]` — mixing a whole-list binding with a
|
|
7000
|
+
* single-class toggle on the same element depends on Angular's styling precedence to resolve, which
|
|
7001
|
+
* is a subtlety no template should rest on.
|
|
7002
|
+
*
|
|
7003
|
+
* The indent rule appears only on an expanded collapsible group on the full-width rail: it is what
|
|
7004
|
+
* shows the children belong to the header above them.
|
|
7005
|
+
*/
|
|
7006
|
+
childrenClass = computed(() => {
|
|
7007
|
+
const parts = ['flex flex-col gap-1'];
|
|
7008
|
+
if (!this.childrenVisible()) {
|
|
7009
|
+
parts.push('hidden');
|
|
7010
|
+
}
|
|
7011
|
+
if (this.collapsible() && !this.collapsed()) {
|
|
7012
|
+
parts.push('ml-5 border-l border-white/10 pl-3');
|
|
7013
|
+
}
|
|
7014
|
+
return parts.join(' ');
|
|
7015
|
+
}, ...(ngDevMode ? [{ debugName: "childrenClass" }] : []));
|
|
7016
|
+
/**
|
|
7017
|
+
* On the collapsed rail there is nothing to expand into, so the toggle always reports "opening" —
|
|
7018
|
+
* the app's handler takes that as the cue to navigate into the section, which is the only useful
|
|
7019
|
+
* meaning the control can have there.
|
|
7020
|
+
*/
|
|
7021
|
+
toggle() {
|
|
7022
|
+
const next = this.collapsed() ? true : !this.expanded();
|
|
7023
|
+
this.manual.set(next);
|
|
7024
|
+
this.expandedChange.emit(next);
|
|
7025
|
+
}
|
|
6889
7026
|
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: `
|
|
7027
|
+
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
7028
|
<div class="mt-5 first:mt-3">
|
|
6892
7029
|
@if (label()) {
|
|
6893
|
-
@if (
|
|
6894
|
-
|
|
6895
|
-
|
|
7030
|
+
@if (collapsible()) {
|
|
7031
|
+
<!--
|
|
7032
|
+
A collapsible header is styled as a NAV ROW, not as a section caption. A small uppercase
|
|
7033
|
+
caption says "everything under me belongs to me" — which is a lie the moment the group is
|
|
7034
|
+
collapsed and the next section's items sit directly beneath it. At row scale, with an icon
|
|
7035
|
+
and a chevron, it reads as the control it is.
|
|
7036
|
+
|
|
7037
|
+
It renders on the collapsed rail too, as an icon alone, because there it is the group's
|
|
7038
|
+
ONLY representation: the children are hidden (a child with no icon of its own would be a
|
|
7039
|
+
blank row), so the app is expected to treat the toggle as "take me into this section" —
|
|
7040
|
+
which is what expandedChange already means.
|
|
7041
|
+
-->
|
|
7042
|
+
<button
|
|
7043
|
+
type="button"
|
|
7044
|
+
class="mb-1 flex h-10 w-full items-center gap-3 rounded-control px-3 text-sm font-medium
|
|
7045
|
+
text-white/70 transition-colors hover:bg-white/8 hover:text-white
|
|
7046
|
+
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40"
|
|
7047
|
+
[class.lg:justify-center]="collapsed()"
|
|
7048
|
+
[class.text-white]="routeActive()"
|
|
7049
|
+
[attr.aria-expanded]="collapsed() ? null : expanded()"
|
|
7050
|
+
[attr.title]="collapsed() ? label() : null"
|
|
7051
|
+
[attr.aria-label]="collapsed() ? label() : null"
|
|
7052
|
+
(click)="toggle()"
|
|
6896
7053
|
>
|
|
7054
|
+
@if (icon()) {
|
|
7055
|
+
<sds-icon [name]="icon()!" [size]="20" class="shrink-0" />
|
|
7056
|
+
}
|
|
7057
|
+
<span class="flex-1 truncate text-left" [class.lg:hidden]="collapsed()">
|
|
7058
|
+
{{ label() }}
|
|
7059
|
+
</span>
|
|
7060
|
+
<sds-icon
|
|
7061
|
+
name="chevron-down"
|
|
7062
|
+
[size]="16"
|
|
7063
|
+
class="shrink-0 text-white/50 transition-transform"
|
|
7064
|
+
[class.lg:hidden]="collapsed()"
|
|
7065
|
+
[class.rotate-180]="expanded()"
|
|
7066
|
+
/>
|
|
7067
|
+
</button>
|
|
7068
|
+
} @else if (collapsed()) {
|
|
7069
|
+
<div class="mx-3 mb-2 mt-2 h-px bg-white/10"></div>
|
|
7070
|
+
} @else {
|
|
7071
|
+
<p class="mb-1.5 px-3 text-[11px] font-semibold uppercase tracking-wider text-white/60">
|
|
6897
7072
|
{{ label() }}
|
|
6898
7073
|
</p>
|
|
6899
|
-
} @else {
|
|
6900
|
-
<div class="mx-3 mb-2 mt-2 h-px bg-white/10"></div>
|
|
6901
7074
|
}
|
|
6902
7075
|
}
|
|
6903
|
-
|
|
7076
|
+
|
|
7077
|
+
<div [class]="childrenClass()">
|
|
6904
7078
|
<ng-content />
|
|
6905
7079
|
</div>
|
|
6906
7080
|
</div>
|
|
6907
|
-
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7081
|
+
`, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6908
7082
|
}
|
|
6909
7083
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsNavGroup, decorators: [{
|
|
6910
7084
|
type: Component,
|
|
6911
7085
|
args: [{
|
|
6912
7086
|
selector: 'sds-nav-group',
|
|
6913
7087
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7088
|
+
imports: [SdsIcon],
|
|
6914
7089
|
host: { class: 'block' },
|
|
6915
7090
|
template: `
|
|
6916
7091
|
<div class="mt-5 first:mt-3">
|
|
6917
7092
|
@if (label()) {
|
|
6918
|
-
@if (
|
|
6919
|
-
|
|
6920
|
-
|
|
7093
|
+
@if (collapsible()) {
|
|
7094
|
+
<!--
|
|
7095
|
+
A collapsible header is styled as a NAV ROW, not as a section caption. A small uppercase
|
|
7096
|
+
caption says "everything under me belongs to me" — which is a lie the moment the group is
|
|
7097
|
+
collapsed and the next section's items sit directly beneath it. At row scale, with an icon
|
|
7098
|
+
and a chevron, it reads as the control it is.
|
|
7099
|
+
|
|
7100
|
+
It renders on the collapsed rail too, as an icon alone, because there it is the group's
|
|
7101
|
+
ONLY representation: the children are hidden (a child with no icon of its own would be a
|
|
7102
|
+
blank row), so the app is expected to treat the toggle as "take me into this section" —
|
|
7103
|
+
which is what expandedChange already means.
|
|
7104
|
+
-->
|
|
7105
|
+
<button
|
|
7106
|
+
type="button"
|
|
7107
|
+
class="mb-1 flex h-10 w-full items-center gap-3 rounded-control px-3 text-sm font-medium
|
|
7108
|
+
text-white/70 transition-colors hover:bg-white/8 hover:text-white
|
|
7109
|
+
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40"
|
|
7110
|
+
[class.lg:justify-center]="collapsed()"
|
|
7111
|
+
[class.text-white]="routeActive()"
|
|
7112
|
+
[attr.aria-expanded]="collapsed() ? null : expanded()"
|
|
7113
|
+
[attr.title]="collapsed() ? label() : null"
|
|
7114
|
+
[attr.aria-label]="collapsed() ? label() : null"
|
|
7115
|
+
(click)="toggle()"
|
|
6921
7116
|
>
|
|
7117
|
+
@if (icon()) {
|
|
7118
|
+
<sds-icon [name]="icon()!" [size]="20" class="shrink-0" />
|
|
7119
|
+
}
|
|
7120
|
+
<span class="flex-1 truncate text-left" [class.lg:hidden]="collapsed()">
|
|
7121
|
+
{{ label() }}
|
|
7122
|
+
</span>
|
|
7123
|
+
<sds-icon
|
|
7124
|
+
name="chevron-down"
|
|
7125
|
+
[size]="16"
|
|
7126
|
+
class="shrink-0 text-white/50 transition-transform"
|
|
7127
|
+
[class.lg:hidden]="collapsed()"
|
|
7128
|
+
[class.rotate-180]="expanded()"
|
|
7129
|
+
/>
|
|
7130
|
+
</button>
|
|
7131
|
+
} @else if (collapsed()) {
|
|
7132
|
+
<div class="mx-3 mb-2 mt-2 h-px bg-white/10"></div>
|
|
7133
|
+
} @else {
|
|
7134
|
+
<p class="mb-1.5 px-3 text-[11px] font-semibold uppercase tracking-wider text-white/60">
|
|
6922
7135
|
{{ label() }}
|
|
6923
7136
|
</p>
|
|
6924
|
-
} @else {
|
|
6925
|
-
<div class="mx-3 mb-2 mt-2 h-px bg-white/10"></div>
|
|
6926
7137
|
}
|
|
6927
7138
|
}
|
|
6928
|
-
|
|
7139
|
+
|
|
7140
|
+
<div [class]="childrenClass()">
|
|
6929
7141
|
<ng-content />
|
|
6930
7142
|
</div>
|
|
6931
7143
|
</div>
|
|
6932
7144
|
`,
|
|
6933
7145
|
}]
|
|
6934
|
-
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
|
|
7146
|
+
}], 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
7147
|
|
|
6936
7148
|
/**
|
|
6937
7149
|
* A single sidebar navigation entry. Renders an anchor when [link] is set
|