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