@besaitech/ng-design-system 0.0.63 → 0.0.64
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.
|
@@ -13109,6 +13109,8 @@ class SdsTabsStore {
|
|
|
13109
13109
|
id = `sds-tabs-${nextId$3++}`;
|
|
13110
13110
|
/** Whether the tab list scrolls horizontally (mirrors [scrollable]). */
|
|
13111
13111
|
scrollable = signal(false, ...(ngDevMode ? [{ debugName: "scrollable" }] : []));
|
|
13112
|
+
/** Header style (mirrors [variant]). */
|
|
13113
|
+
variant = signal('underline', ...(ngDevMode ? [{ debugName: "variant" }] : []));
|
|
13112
13114
|
/** Controlled mode (mirrors [controlled]): a click only REQUESTS a tab, the host sets [value]. */
|
|
13113
13115
|
controlled = signal(false, ...(ngDevMode ? [{ debugName: "controlled" }] : []));
|
|
13114
13116
|
/** Called with every tab the user picks (wired to the host's (tabSelect) output). */
|
|
@@ -13120,6 +13122,8 @@ class SdsTabsStore {
|
|
|
13120
13122
|
connect(value) {
|
|
13121
13123
|
this.value = value;
|
|
13122
13124
|
}
|
|
13125
|
+
/** Bumped when a tab's size may have changed (label, icon or count updated), so the indicator re-measures. */
|
|
13126
|
+
layoutVersion = signal(0, ...(ngDevMode ? [{ debugName: "layoutVersion" }] : []));
|
|
13123
13127
|
activeValue() {
|
|
13124
13128
|
return this.value();
|
|
13125
13129
|
}
|
|
@@ -13175,6 +13179,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
13175
13179
|
* `?tab=` query param — or when switching can be refused (an unsaved-changes guard): the tab never
|
|
13176
13180
|
* shows as selected before the host agrees. `(tabSelect)` also fires in the default mode.
|
|
13177
13181
|
*
|
|
13182
|
+
* **Two looks** (`variant`): `underline` (default) for a page's sections — a brand underline slides to the active
|
|
13183
|
+
* tab, inactive tabs get a soft hover; `segmented` for a compact switch between 2–4 views (drawers, cards) — a raised
|
|
13184
|
+
* thumb slides between the options. Either can carry an icon (`[icon]`) and a count (`[count]`) per tab. A
|
|
13185
|
+
* `scrollable` underline list that overflows fades its edges and shows arrow buttons, and keeps the active tab in view.
|
|
13186
|
+
*
|
|
13178
13187
|
* **Panels are optional.** A tab set whose content is rendered by the host (an `@switch` on the value, a
|
|
13179
13188
|
* router outlet) can omit <sds-tab-panels>; a tab then carries no `aria-controls` to a missing panel.
|
|
13180
13189
|
*
|
|
@@ -13191,6 +13200,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
13191
13200
|
* </sds-tabs>
|
|
13192
13201
|
*
|
|
13193
13202
|
* @example
|
|
13203
|
+
* <!-- icons + counts; a compact switch in a drawer -->
|
|
13204
|
+
* <sds-tabs scrollable [(value)]="tab">
|
|
13205
|
+
* <sds-tab-list ariaLabel="Orders">
|
|
13206
|
+
* <sds-tab value="active" icon="clipboard-list" [count]="activeCount()">Active</sds-tab>
|
|
13207
|
+
* <sds-tab value="history" icon="clock">History</sds-tab>
|
|
13208
|
+
* </sds-tab-list>
|
|
13209
|
+
* </sds-tabs>
|
|
13210
|
+
* <sds-tabs variant="segmented" [(value)]="view">
|
|
13211
|
+
* <sds-tab-list class="w-full">…</sds-tab-list>
|
|
13212
|
+
* </sds-tabs>
|
|
13213
|
+
*
|
|
13214
|
+
* @example
|
|
13194
13215
|
* <!-- driven by a query param, content rendered by the host -->
|
|
13195
13216
|
* <sds-tabs controlled [value]="activeTab()" (tabSelect)="selectTab($event)">
|
|
13196
13217
|
* <sds-tab-list>
|
|
@@ -13204,8 +13225,10 @@ class SdsTabs {
|
|
|
13204
13225
|
store = inject(SdsTabsStore);
|
|
13205
13226
|
/** Value of the active tab (two-way). */
|
|
13206
13227
|
value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
13207
|
-
/** Let the tab list scroll horizontally instead of overflowing. */
|
|
13228
|
+
/** Let the tab list scroll horizontally instead of overflowing (edge fades + arrow buttons when it does). */
|
|
13208
13229
|
scrollable = input(false, ...(ngDevMode ? [{ debugName: "scrollable", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
13230
|
+
/** `underline` (page sections, default) or `segmented` (a compact switch between 2–4 views). */
|
|
13231
|
+
variant = input('underline', ...(ngDevMode ? [{ debugName: "variant" }] : []));
|
|
13209
13232
|
/**
|
|
13210
13233
|
* Controlled mode: a user pick only emits (tabSelect); the host decides by setting [value]. See the
|
|
13211
13234
|
* class note.
|
|
@@ -13217,10 +13240,11 @@ class SdsTabs {
|
|
|
13217
13240
|
this.store.connect(this.value);
|
|
13218
13241
|
this.store.onSelect = (value) => this.tabSelect.emit(value);
|
|
13219
13242
|
effect(() => this.store.scrollable.set(this.scrollable()));
|
|
13243
|
+
effect(() => this.store.variant.set(this.variant()));
|
|
13220
13244
|
effect(() => this.store.controlled.set(this.controlled()));
|
|
13221
13245
|
}
|
|
13222
13246
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTabs, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13223
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.25", type: SdsTabs, isStandalone: true, selector: "sds-tabs", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, scrollable: { classPropertyName: "scrollable", publicName: "scrollable", isSignal: true, isRequired: false, transformFunction: null }, controlled: { classPropertyName: "controlled", publicName: "controlled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", tabSelect: "tabSelect" }, host: { classAttribute: "block" }, providers: [SdsTabsStore], ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13247
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.25", type: SdsTabs, isStandalone: true, selector: "sds-tabs", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, scrollable: { classPropertyName: "scrollable", publicName: "scrollable", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, controlled: { classPropertyName: "controlled", publicName: "controlled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", tabSelect: "tabSelect" }, host: { classAttribute: "block" }, providers: [SdsTabsStore], ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13224
13248
|
}
|
|
13225
13249
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTabs, decorators: [{
|
|
13226
13250
|
type: Component,
|
|
@@ -13231,12 +13255,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
13231
13255
|
host: { class: 'block' },
|
|
13232
13256
|
template: `<ng-content />`,
|
|
13233
13257
|
}]
|
|
13234
|
-
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], scrollable: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollable", required: false }] }], controlled: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlled", required: false }] }], tabSelect: [{ type: i0.Output, args: ["tabSelect"] }] } });
|
|
13258
|
+
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], scrollable: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollable", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], controlled: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlled", required: false }] }], tabSelect: [{ type: i0.Output, args: ["tabSelect"] }] } });
|
|
13235
13259
|
|
|
13236
13260
|
/**
|
|
13237
|
-
* Container (role="tablist") for the <sds-tab> headers
|
|
13238
|
-
*
|
|
13239
|
-
*
|
|
13261
|
+
* Container (role="tablist") for the <sds-tab> headers: draws the active-tab indicator and handles roving-focus
|
|
13262
|
+
* keyboard navigation (Arrow keys, Home, End) with automatic activation.
|
|
13263
|
+
*
|
|
13264
|
+
* **Underline** (default): a hairline rule with a 3px brand underline that slides to the active tab. When the tab set
|
|
13265
|
+
* is `scrollable` and the tabs don't fit, the hidden side fades out and an arrow button scrolls by a page; picking a
|
|
13266
|
+
* tab (click or keyboard) scrolls it into view. **Segmented**: a tinted track with a raised thumb that slides under the
|
|
13267
|
+
* active option; put `class="w-full"` on the list to stretch the options evenly.
|
|
13240
13268
|
*
|
|
13241
13269
|
* Name the tab set with [ariaLabel] (or [ariaLabelledby] pointing at a visible heading) when the page has
|
|
13242
13270
|
* more than one, or when the tabs alone do not say what they switch.
|
|
@@ -13245,26 +13273,124 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
13245
13273
|
* <sds-tab-list [ariaLabel]="'Order status'">…</sds-tab-list>
|
|
13246
13274
|
*/
|
|
13247
13275
|
class SdsTabList {
|
|
13248
|
-
host = inject(ElementRef);
|
|
13249
13276
|
store = inject(SdsTabsStore);
|
|
13277
|
+
scroller = viewChild.required('scroller');
|
|
13250
13278
|
/** Accessible name of the tablist. */
|
|
13251
13279
|
ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
13252
13280
|
/** Id of a visible element that names the tablist (takes precedence over [ariaLabel] for AT). */
|
|
13253
13281
|
ariaLabelledby = input('', ...(ngDevMode ? [{ debugName: "ariaLabelledby" }] : []));
|
|
13282
|
+
segmented = computed(() => this.store.variant() === 'segmented', ...(ngDevMode ? [{ debugName: "segmented" }] : []));
|
|
13283
|
+
indicator = signal(null, ...(ngDevMode ? [{ debugName: "indicator" }] : []));
|
|
13284
|
+
/** Off for the first placement, so the indicator doesn't fly in from the left on load. */
|
|
13285
|
+
animate = signal(false, ...(ngDevMode ? [{ debugName: "animate" }] : []));
|
|
13286
|
+
canScrollStart = signal(false, ...(ngDevMode ? [{ debugName: "canScrollStart" }] : []));
|
|
13287
|
+
canScrollEnd = signal(false, ...(ngDevMode ? [{ debugName: "canScrollEnd" }] : []));
|
|
13288
|
+
arrows = computed(() => this.store.scrollable() && !this.segmented(), ...(ngDevMode ? [{ debugName: "arrows" }] : []));
|
|
13289
|
+
hostClass = computed(() => this.segmented() ? 'relative inline-flex max-w-full' : 'relative block', ...(ngDevMode ? [{ debugName: "hostClass" }] : []));
|
|
13254
13290
|
/**
|
|
13255
|
-
*
|
|
13256
|
-
*
|
|
13257
|
-
*
|
|
13258
|
-
* underline still covers it with no overlap — and vertical overflow is hidden.
|
|
13291
|
+
* The scroller is the tablist. Underline: the rule is an inset shadow, painted under the tabs, so nothing overflows
|
|
13292
|
+
* vertically — `overflow-x: auto` turns the other axis to `auto` too (CSS), and a 1px overflow showed a vertical
|
|
13293
|
+
* scrollbar. Segmented: a tinted track (ink at 5%, so it reads on any surface) around the options.
|
|
13259
13294
|
*/
|
|
13260
|
-
|
|
13261
|
-
|
|
13262
|
-
|
|
13295
|
+
scrollerClass = computed(() => {
|
|
13296
|
+
const scroll = this.store.scrollable()
|
|
13297
|
+
? 'overflow-x-auto overflow-y-hidden overscroll-x-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'
|
|
13298
|
+
: '';
|
|
13299
|
+
return this.segmented()
|
|
13300
|
+
? `relative flex w-full gap-1 rounded-control bg-ink/5 p-1 ${scroll}`
|
|
13301
|
+
: `relative flex gap-1 shadow-[inset_0_-1px_0_var(--color-border)] ${scroll}`;
|
|
13302
|
+
}, ...(ngDevMode ? [{ debugName: "scrollerClass" }] : []));
|
|
13303
|
+
/** Fade the side(s) with hidden tabs, so the overflow reads as "more this way". */
|
|
13304
|
+
fadeMask = computed(() => {
|
|
13305
|
+
if (!this.arrows())
|
|
13306
|
+
return null;
|
|
13307
|
+
const start = this.canScrollStart();
|
|
13308
|
+
const end = this.canScrollEnd();
|
|
13309
|
+
if (!start && !end)
|
|
13310
|
+
return null;
|
|
13311
|
+
return `linear-gradient(to right, ${start ? 'transparent, black 48px' : 'black'}, ${end ? 'black calc(100% - 48px), transparent' : 'black'})`;
|
|
13312
|
+
}, ...(ngDevMode ? [{ debugName: "fadeMask" }] : []));
|
|
13313
|
+
constructor() {
|
|
13314
|
+
// Place the indicator after every render that may move the active tab (selection, labels, counts, variant).
|
|
13315
|
+
afterRenderEffect(() => {
|
|
13316
|
+
this.store.activeValue();
|
|
13317
|
+
this.store.layoutVersion();
|
|
13318
|
+
this.store.variant();
|
|
13319
|
+
this.place();
|
|
13320
|
+
});
|
|
13321
|
+
// Fonts loading, a parent resizing or labels translating all change tab widths without a signal.
|
|
13322
|
+
const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(() => this.place(false));
|
|
13323
|
+
afterRenderEffect(() => {
|
|
13324
|
+
const el = this.scroller().nativeElement;
|
|
13325
|
+
if (!observer)
|
|
13326
|
+
return;
|
|
13327
|
+
observer.disconnect();
|
|
13328
|
+
observer.observe(el);
|
|
13329
|
+
el.querySelectorAll('[data-sds-tab]').forEach((tab) => observer.observe(tab));
|
|
13330
|
+
});
|
|
13331
|
+
inject(DestroyRef).onDestroy(() => observer?.disconnect());
|
|
13332
|
+
}
|
|
13333
|
+
/** Measure the active tab and move the indicator there; keep it in view when the selection changed. */
|
|
13334
|
+
place(reveal = true) {
|
|
13335
|
+
const scroller = this.scroller().nativeElement;
|
|
13336
|
+
const tab = scroller.querySelector('[role="tab"][aria-selected="true"]');
|
|
13337
|
+
this.measureScroll();
|
|
13338
|
+
if (!tab) {
|
|
13339
|
+
this.indicator.set(null);
|
|
13340
|
+
return;
|
|
13341
|
+
}
|
|
13342
|
+
// Underline: as wide as the label area (the tab's padding is the hover pill's, not the underline's).
|
|
13343
|
+
const inset = this.segmented() ? 0 : 12;
|
|
13344
|
+
const box = { left: tab.offsetLeft + inset, width: Math.max(tab.offsetWidth - inset * 2, 12) };
|
|
13345
|
+
const prev = this.indicator();
|
|
13346
|
+
if (prev && prev.left === box.left && prev.width === box.width)
|
|
13347
|
+
return;
|
|
13348
|
+
this.indicator.set(box);
|
|
13349
|
+
if (prev) {
|
|
13350
|
+
this.animate.set(true);
|
|
13351
|
+
}
|
|
13352
|
+
else {
|
|
13353
|
+
// First placement: no transition, then enable it for the moves that follow.
|
|
13354
|
+
requestAnimationFrame(() => this.animate.set(true));
|
|
13355
|
+
}
|
|
13356
|
+
if (reveal)
|
|
13357
|
+
this.reveal(tab);
|
|
13358
|
+
}
|
|
13359
|
+
/** Scroll just enough to show the active tab clear of the edge fades (never scrolls the page vertically). */
|
|
13360
|
+
reveal(tab) {
|
|
13361
|
+
const scroller = this.scroller().nativeElement;
|
|
13362
|
+
if (scroller.scrollWidth <= scroller.clientWidth)
|
|
13363
|
+
return;
|
|
13364
|
+
const margin = 48;
|
|
13365
|
+
const start = tab.offsetLeft - margin;
|
|
13366
|
+
const end = tab.offsetLeft + tab.offsetWidth + margin - scroller.clientWidth;
|
|
13367
|
+
if (scroller.scrollLeft > start) {
|
|
13368
|
+
scroller.scrollTo({ left: Math.max(start, 0), behavior: this.smooth() });
|
|
13369
|
+
}
|
|
13370
|
+
else if (scroller.scrollLeft < end) {
|
|
13371
|
+
scroller.scrollTo({ left: end, behavior: this.smooth() });
|
|
13372
|
+
}
|
|
13373
|
+
}
|
|
13374
|
+
measureScroll() {
|
|
13375
|
+
const el = this.scroller().nativeElement;
|
|
13376
|
+
const max = el.scrollWidth - el.clientWidth;
|
|
13377
|
+
this.canScrollStart.set(el.scrollLeft > 1);
|
|
13378
|
+
this.canScrollEnd.set(max > 1 && el.scrollLeft < max - 1);
|
|
13379
|
+
}
|
|
13380
|
+
scrollByPage(direction) {
|
|
13381
|
+
const el = this.scroller().nativeElement;
|
|
13382
|
+
el.scrollBy({ left: direction * el.clientWidth * 0.75, behavior: this.smooth() });
|
|
13383
|
+
}
|
|
13384
|
+
smooth() {
|
|
13385
|
+
return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
13386
|
+
? 'auto'
|
|
13387
|
+
: 'smooth';
|
|
13388
|
+
}
|
|
13263
13389
|
onKeydown(event) {
|
|
13264
13390
|
const keys = ['ArrowRight', 'ArrowLeft', 'Home', 'End'];
|
|
13265
13391
|
if (!keys.includes(event.key))
|
|
13266
13392
|
return;
|
|
13267
|
-
const tabs = Array.from(this.
|
|
13393
|
+
const tabs = Array.from(this.scroller().nativeElement.querySelectorAll('[role="tab"]')).filter((b) => !b.disabled);
|
|
13268
13394
|
if (!tabs.length)
|
|
13269
13395
|
return;
|
|
13270
13396
|
event.preventDefault();
|
|
@@ -13289,33 +13415,141 @@ class SdsTabList {
|
|
|
13289
13415
|
el.click(); // automatic activation
|
|
13290
13416
|
}
|
|
13291
13417
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTabList, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13292
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
13418
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsTabList, isStandalone: true, selector: "sds-tab-list", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "ariaLabelledby", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClass()" } }, viewQueries: [{ propertyName: "scroller", first: true, predicate: ["scroller"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
13419
|
+
@if (arrows() && canScrollStart()) {
|
|
13420
|
+
<button
|
|
13421
|
+
type="button"
|
|
13422
|
+
tabindex="-1"
|
|
13423
|
+
aria-hidden="true"
|
|
13424
|
+
class="absolute left-0 top-1 z-20 flex h-9 w-8 items-center justify-center rounded-control bg-surface text-ink-soft shadow-sm ring-1 ring-border transition-colors hover:text-ink"
|
|
13425
|
+
(click)="scrollByPage(-1)"
|
|
13426
|
+
>
|
|
13427
|
+
<sds-icon name="chevron-left" [size]="16" />
|
|
13428
|
+
</button>
|
|
13429
|
+
}
|
|
13430
|
+
<div
|
|
13431
|
+
#scroller
|
|
13432
|
+
role="tablist"
|
|
13433
|
+
[attr.aria-label]="ariaLabel() || null"
|
|
13434
|
+
[attr.aria-labelledby]="ariaLabelledby() || null"
|
|
13435
|
+
[class]="scrollerClass()"
|
|
13436
|
+
[style.mask-image]="fadeMask()"
|
|
13437
|
+
(keydown)="onKeydown($event)"
|
|
13438
|
+
(scroll)="measureScroll()"
|
|
13439
|
+
>
|
|
13440
|
+
@if (segmented() && indicator(); as box) {
|
|
13441
|
+
<span
|
|
13442
|
+
aria-hidden="true"
|
|
13443
|
+
class="pointer-events-none absolute bottom-1 top-1 rounded-[calc(var(--radius-control)-2px)] bg-surface shadow-sm ring-1 ring-border dark:bg-ink/15 dark:ring-transparent"
|
|
13444
|
+
[class.sds-tabs-animate]="animate()"
|
|
13445
|
+
[style.left.px]="box.left"
|
|
13446
|
+
[style.width.px]="box.width"
|
|
13447
|
+
></span>
|
|
13448
|
+
}
|
|
13449
|
+
<ng-content />
|
|
13450
|
+
@if (!segmented() && indicator(); as box) {
|
|
13451
|
+
<span
|
|
13452
|
+
aria-hidden="true"
|
|
13453
|
+
class="pointer-events-none absolute bottom-0 h-[3px] rounded-t-full bg-brand-600"
|
|
13454
|
+
[class.sds-tabs-animate]="animate()"
|
|
13455
|
+
[style.left.px]="box.left"
|
|
13456
|
+
[style.width.px]="box.width"
|
|
13457
|
+
></span>
|
|
13458
|
+
}
|
|
13459
|
+
</div>
|
|
13460
|
+
@if (arrows() && canScrollEnd()) {
|
|
13461
|
+
<button
|
|
13462
|
+
type="button"
|
|
13463
|
+
tabindex="-1"
|
|
13464
|
+
aria-hidden="true"
|
|
13465
|
+
class="absolute right-0 top-1 z-20 flex h-9 w-8 items-center justify-center rounded-control bg-surface text-ink-soft shadow-sm ring-1 ring-border transition-colors hover:text-ink"
|
|
13466
|
+
(click)="scrollByPage(1)"
|
|
13467
|
+
>
|
|
13468
|
+
<sds-icon name="chevron-right" [size]="16" />
|
|
13469
|
+
</button>
|
|
13470
|
+
}
|
|
13471
|
+
`, isInline: true, styles: [".sds-tabs-animate{transition:left .26s cubic-bezier(.2,.8,.2,1),width .26s cubic-bezier(.2,.8,.2,1)}@media(prefers-reduced-motion:reduce){.sds-tabs-animate{transition:none}}\n"], dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13293
13472
|
}
|
|
13294
13473
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTabList, decorators: [{
|
|
13295
13474
|
type: Component,
|
|
13296
|
-
args: [{
|
|
13297
|
-
|
|
13298
|
-
|
|
13299
|
-
|
|
13300
|
-
|
|
13301
|
-
|
|
13302
|
-
|
|
13303
|
-
|
|
13304
|
-
|
|
13305
|
-
|
|
13306
|
-
|
|
13307
|
-
|
|
13308
|
-
|
|
13475
|
+
args: [{ selector: 'sds-tab-list', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdsIcon], host: { '[class]': 'hostClass()' }, template: `
|
|
13476
|
+
@if (arrows() && canScrollStart()) {
|
|
13477
|
+
<button
|
|
13478
|
+
type="button"
|
|
13479
|
+
tabindex="-1"
|
|
13480
|
+
aria-hidden="true"
|
|
13481
|
+
class="absolute left-0 top-1 z-20 flex h-9 w-8 items-center justify-center rounded-control bg-surface text-ink-soft shadow-sm ring-1 ring-border transition-colors hover:text-ink"
|
|
13482
|
+
(click)="scrollByPage(-1)"
|
|
13483
|
+
>
|
|
13484
|
+
<sds-icon name="chevron-left" [size]="16" />
|
|
13485
|
+
</button>
|
|
13486
|
+
}
|
|
13487
|
+
<div
|
|
13488
|
+
#scroller
|
|
13489
|
+
role="tablist"
|
|
13490
|
+
[attr.aria-label]="ariaLabel() || null"
|
|
13491
|
+
[attr.aria-labelledby]="ariaLabelledby() || null"
|
|
13492
|
+
[class]="scrollerClass()"
|
|
13493
|
+
[style.mask-image]="fadeMask()"
|
|
13494
|
+
(keydown)="onKeydown($event)"
|
|
13495
|
+
(scroll)="measureScroll()"
|
|
13496
|
+
>
|
|
13497
|
+
@if (segmented() && indicator(); as box) {
|
|
13498
|
+
<span
|
|
13499
|
+
aria-hidden="true"
|
|
13500
|
+
class="pointer-events-none absolute bottom-1 top-1 rounded-[calc(var(--radius-control)-2px)] bg-surface shadow-sm ring-1 ring-border dark:bg-ink/15 dark:ring-transparent"
|
|
13501
|
+
[class.sds-tabs-animate]="animate()"
|
|
13502
|
+
[style.left.px]="box.left"
|
|
13503
|
+
[style.width.px]="box.width"
|
|
13504
|
+
></span>
|
|
13505
|
+
}
|
|
13506
|
+
<ng-content />
|
|
13507
|
+
@if (!segmented() && indicator(); as box) {
|
|
13508
|
+
<span
|
|
13509
|
+
aria-hidden="true"
|
|
13510
|
+
class="pointer-events-none absolute bottom-0 h-[3px] rounded-t-full bg-brand-600"
|
|
13511
|
+
[class.sds-tabs-animate]="animate()"
|
|
13512
|
+
[style.left.px]="box.left"
|
|
13513
|
+
[style.width.px]="box.width"
|
|
13514
|
+
></span>
|
|
13515
|
+
}
|
|
13516
|
+
</div>
|
|
13517
|
+
@if (arrows() && canScrollEnd()) {
|
|
13518
|
+
<button
|
|
13519
|
+
type="button"
|
|
13520
|
+
tabindex="-1"
|
|
13521
|
+
aria-hidden="true"
|
|
13522
|
+
class="absolute right-0 top-1 z-20 flex h-9 w-8 items-center justify-center rounded-control bg-surface text-ink-soft shadow-sm ring-1 ring-border transition-colors hover:text-ink"
|
|
13523
|
+
(click)="scrollByPage(1)"
|
|
13524
|
+
>
|
|
13525
|
+
<sds-icon name="chevron-right" [size]="16" />
|
|
13526
|
+
</button>
|
|
13527
|
+
}
|
|
13528
|
+
`, styles: [".sds-tabs-animate{transition:left .26s cubic-bezier(.2,.8,.2,1),width .26s cubic-bezier(.2,.8,.2,1)}@media(prefers-reduced-motion:reduce){.sds-tabs-animate{transition:none}}\n"] }]
|
|
13529
|
+
}], ctorParameters: () => [], propDecorators: { scroller: [{ type: i0.ViewChild, args: ['scroller', { isSignal: true }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledby", required: false }] }] } });
|
|
13309
13530
|
|
|
13310
13531
|
/**
|
|
13311
|
-
* A single tab header (role="tab"). Project the label
|
|
13312
|
-
*
|
|
13532
|
+
* A single tab header (role="tab"). Project the label; optionally give it an [icon] (a registry name, drawn before
|
|
13533
|
+
* the label) and a [count] (a small badge after it — e.g. how many orders wait in that tab; `null` hides it, `0`
|
|
13534
|
+
* shows it). Set [disabled] to make it unselectable. Its look follows the tab set's `variant`.
|
|
13313
13535
|
*/
|
|
13314
13536
|
class SdsTab {
|
|
13315
13537
|
store = inject(SdsTabsStore);
|
|
13316
13538
|
value = input.required(...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
13317
13539
|
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
13540
|
+
/** An icon from the library registry, drawn before the label. */
|
|
13541
|
+
icon = input(null, ...(ngDevMode ? [{ debugName: "icon" }] : []));
|
|
13542
|
+
/** A count badge after the label (`null` / unset: none; `0` is shown). */
|
|
13543
|
+
count = input(null, ...(ngDevMode ? [{ debugName: "count" }] : []));
|
|
13318
13544
|
active = computed(() => this.store.isActive(this.value()), ...(ngDevMode ? [{ debugName: "active" }] : []));
|
|
13545
|
+
constructor() {
|
|
13546
|
+
// A new count or icon changes the tab's width: let the list move its indicator.
|
|
13547
|
+
effect(() => {
|
|
13548
|
+
this.count();
|
|
13549
|
+
this.icon();
|
|
13550
|
+
untracked(() => this.store.layoutVersion.update((v) => v + 1));
|
|
13551
|
+
});
|
|
13552
|
+
}
|
|
13319
13553
|
ngOnInit() {
|
|
13320
13554
|
this.store.registerTab(this.value());
|
|
13321
13555
|
}
|
|
@@ -13327,21 +13561,36 @@ class SdsTab {
|
|
|
13327
13561
|
return;
|
|
13328
13562
|
this.store.select(this.value());
|
|
13329
13563
|
}
|
|
13330
|
-
tabClass() {
|
|
13331
|
-
|
|
13332
|
-
const
|
|
13333
|
-
|
|
13334
|
-
|
|
13335
|
-
|
|
13336
|
-
|
|
13337
|
-
|
|
13338
|
-
|
|
13339
|
-
|
|
13340
|
-
|
|
13341
|
-
|
|
13342
|
-
|
|
13564
|
+
tabClass = computed(() => {
|
|
13565
|
+
const focus = 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:ring-offset-0';
|
|
13566
|
+
const state = this.disabled()
|
|
13567
|
+
? 'cursor-not-allowed text-ink-faint opacity-50'
|
|
13568
|
+
: this.active()
|
|
13569
|
+
? 'cursor-pointer text-ink'
|
|
13570
|
+
: 'cursor-pointer text-ink-soft hover:text-ink';
|
|
13571
|
+
if (this.store.variant() === 'segmented') {
|
|
13572
|
+
// Sits on the thumb the list slides under the active tab (z-10 keeps the label above it). Sized to its label,
|
|
13573
|
+
// sharing any extra width when the list is stretched (class="w-full").
|
|
13574
|
+
return ('relative z-10 inline-flex min-w-0 flex-auto items-center justify-center gap-2 whitespace-nowrap ' +
|
|
13575
|
+
`rounded-[calc(var(--radius-control)-2px)] px-3.5 py-1.5 text-sm font-medium transition-colors ${focus} ${state}`);
|
|
13576
|
+
}
|
|
13577
|
+
// Underline: a soft hover pill above the rule; the list draws the sliding underline under the active tab.
|
|
13578
|
+
const hover = this.disabled() || this.active() ? '' : 'hover:bg-ink/5';
|
|
13579
|
+
return ('relative inline-flex shrink-0 items-center gap-2 whitespace-nowrap rounded-control px-3 py-2 mt-1 mb-2 ' +
|
|
13580
|
+
`text-sm font-medium transition-colors ${focus} ${hover} ${state}`);
|
|
13581
|
+
}, ...(ngDevMode ? [{ debugName: "tabClass" }] : []));
|
|
13582
|
+
/** Underline: the active tab's icon takes the brand colour, like its underline. Segmented: the label's colour. */
|
|
13583
|
+
iconClass = computed(() => this.active() && !this.disabled() && this.store.variant() === 'underline'
|
|
13584
|
+
? 'shrink-0 text-brand-600'
|
|
13585
|
+
: 'shrink-0', ...(ngDevMode ? [{ debugName: "iconClass" }] : []));
|
|
13586
|
+
countClass = computed(() => {
|
|
13587
|
+
const base = 'inline-flex h-5 min-w-5 items-center justify-center rounded-full px-1.5 text-xs font-semibold tabular-nums transition-colors';
|
|
13588
|
+
return this.active() && !this.disabled()
|
|
13589
|
+
? `${base} bg-brand-100 text-brand-700`
|
|
13590
|
+
: `${base} bg-ink/[0.07] text-ink-soft`;
|
|
13591
|
+
}, ...(ngDevMode ? [{ debugName: "countClass" }] : []));
|
|
13343
13592
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTab, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13344
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
13593
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdsTab, isStandalone: true, selector: "sds-tab", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "contents" }, ngImport: i0, template: `
|
|
13345
13594
|
<button
|
|
13346
13595
|
type="button"
|
|
13347
13596
|
role="tab"
|
|
@@ -13350,19 +13599,27 @@ class SdsTab {
|
|
|
13350
13599
|
[attr.aria-controls]="store.hasPanel(value()) ? store.panelId(value()) : null"
|
|
13351
13600
|
[attr.aria-disabled]="disabled() ? 'true' : null"
|
|
13352
13601
|
[attr.tabindex]="active() && !disabled() ? 0 : -1"
|
|
13602
|
+
[attr.data-sds-tab]="''"
|
|
13353
13603
|
[disabled]="disabled()"
|
|
13354
13604
|
[class]="tabClass()"
|
|
13355
13605
|
(click)="select()"
|
|
13356
13606
|
>
|
|
13357
|
-
|
|
13607
|
+
@if (icon(); as name) {
|
|
13608
|
+
<sds-icon [name]="name" [size]="16" [class]="iconClass()" aria-hidden="true" />
|
|
13609
|
+
}
|
|
13610
|
+
<span class="truncate"><ng-content /></span>
|
|
13611
|
+
@if (count() !== null && count() !== undefined) {
|
|
13612
|
+
<span [class]="countClass()">{{ count() }}</span>
|
|
13613
|
+
}
|
|
13358
13614
|
</button>
|
|
13359
|
-
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13615
|
+
`, isInline: true, dependencies: [{ kind: "component", type: SdsIcon, selector: "sds-icon", inputs: ["name", "size", "strokeWidth", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13360
13616
|
}
|
|
13361
13617
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdsTab, decorators: [{
|
|
13362
13618
|
type: Component,
|
|
13363
13619
|
args: [{
|
|
13364
13620
|
selector: 'sds-tab',
|
|
13365
13621
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
13622
|
+
imports: [SdsIcon],
|
|
13366
13623
|
host: { class: 'contents' },
|
|
13367
13624
|
template: `
|
|
13368
13625
|
<button
|
|
@@ -13373,15 +13630,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
13373
13630
|
[attr.aria-controls]="store.hasPanel(value()) ? store.panelId(value()) : null"
|
|
13374
13631
|
[attr.aria-disabled]="disabled() ? 'true' : null"
|
|
13375
13632
|
[attr.tabindex]="active() && !disabled() ? 0 : -1"
|
|
13633
|
+
[attr.data-sds-tab]="''"
|
|
13376
13634
|
[disabled]="disabled()"
|
|
13377
13635
|
[class]="tabClass()"
|
|
13378
13636
|
(click)="select()"
|
|
13379
13637
|
>
|
|
13380
|
-
|
|
13638
|
+
@if (icon(); as name) {
|
|
13639
|
+
<sds-icon [name]="name" [size]="16" [class]="iconClass()" aria-hidden="true" />
|
|
13640
|
+
}
|
|
13641
|
+
<span class="truncate"><ng-content /></span>
|
|
13642
|
+
@if (count() !== null && count() !== undefined) {
|
|
13643
|
+
<span [class]="countClass()">{{ count() }}</span>
|
|
13644
|
+
}
|
|
13381
13645
|
</button>
|
|
13382
13646
|
`,
|
|
13383
13647
|
}]
|
|
13384
|
-
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
13648
|
+
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }] } });
|
|
13385
13649
|
|
|
13386
13650
|
/** Wrapper for the <sds-tab-panel> content blocks of a tab set. */
|
|
13387
13651
|
class SdsTabPanels {
|