@sixbell-telco/sdk
Version:
A collection of reusable components designed for use in Sixbell Telco Angular projects
1,087 lines (1,079 loc) • 50.5 kB
JavaScript
import { injectFlexRenderContext } from '@tanstack/angular-table';
export * from '@tanstack/angular-table';
import { CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { signal, Injectable, inject, computed, ChangeDetectionStrategy, Component, input, output, effect } from '@angular/core';
import { ButtonComponent } from '@sixbell-telco/sdk/components/button';
import { IconComponent } from '@sixbell-telco/sdk/components/icon';
import { matKeyboardArrowDown, matSwapVert, matArrowUpward, matInfo } from '@sixbell-telco/sdk/components/icon/material/baseline';
import { CheckboxComponent } from '@sixbell-telco/sdk/components/forms/checkbox';
import { heroDocumentText, heroXCircle } from '@ng-icons/heroicons/outline';
import { SelectComponent } from '@sixbell-telco/sdk/components/forms/select';
import { PaginatorComponent } from '@sixbell-telco/sdk/components/paginator';
import { TypographyDirective } from '@sixbell-telco/sdk/directives/typography';
import { cn } from '@sixbell-telco/sdk/utils/cn';
import { TranslateService, TranslatePipe } from '@sixbell-telco/sdk/utils/translation';
import { cva } from 'class-variance-authority';
/**
* Context service to share data table configuration with sub-components
* This allows sub-components to automatically sync their sizes with the parent data table
*/
class DataTableContextService {
/**
* Current table size that sub-components can subscribe to
*/
size = signal('sm');
/**
* Update the table size - called by the main data table component
*/
setSize(size) {
this.size.set(size);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableContextService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableContextService, decorators: [{
type: Injectable
}] });
/**
* Expandable cell component that shows row data and expand/collapse toggle
*/
class DataTableExpandableCellComponent {
/**
* TanStack Table context injected by flexRender
*/
context = injectFlexRenderContext();
/**
* Data table context for size synchronization
*/
dataTableContext = inject(DataTableContextService);
/**
* Get the row instance from context
*/
get row() {
return this.context.row;
}
/** @internal */
iconChevronDown = matKeyboardArrowDown;
/**
* @internal
* Computes button size based on data table size
*/
buttonSize = computed(() => {
switch (this.dataTableContext.size()) {
case 'xs':
return 'xs';
case 'sm':
return 'xs';
case 'md':
return 'sm';
case 'lg':
return 'sm';
case 'xl':
return 'md';
default:
return 'xs';
}
});
/**
* @internal
* Computes icon size based on data table size
*/
iconSize = computed(() => {
switch (this.dataTableContext.size()) {
case 'xs':
return 'xs';
case 'sm':
return 'xs';
case 'md':
return 'sm';
case 'lg':
return 'sm';
case 'xl':
return 'md';
default:
return 'xs';
}
});
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableExpandableCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.0", type: DataTableExpandableCellComponent, isStandalone: true, selector: "st-data-table-expandable-cell", ngImport: i0, template: `
<div [style.--depth]="row.depth">
<div>
@if (row.getCanExpand()) {
<st-button
variant="base"
[size]="buttonSize()"
[square]="true"
[link]="true"
class="group"
(click)="row.getToggleExpandedHandler()()"
[attr.aria-expanded]="row.getIsExpanded()"
>
<st-icon
class="transition-transform duration-300 ease-in-out group-aria-[expanded=false]:rotate-0 group-aria-[expanded=true]:rotate-180"
[icon]="iconChevronDown"
[size]="iconSize()"
></st-icon>
</st-button>
} @else {
<span> </span>
}
{{ context.getValue() }}
</div>
</div>
`, isInline: true, styles: [":host{>div{padding-left:calc(2rem * var(--depth, 1))}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IconComponent, selector: "st-icon", inputs: ["color", "size", "icon"] }, { kind: "component", type: ButtonComponent, selector: "st-button", inputs: ["variant", "ghost", "outline", "link", "soft", "dash", "wide", "circle", "square", "glass", "block", "loader", "size", "shadow", "focusable", "icon", "iconPosition", "type", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableExpandableCellComponent, decorators: [{
type: Component,
args: [{ standalone: true, selector: 'st-data-table-expandable-cell', imports: [CommonModule, IconComponent, ButtonComponent], template: `
<div [style.--depth]="row.depth">
<div>
@if (row.getCanExpand()) {
<st-button
variant="base"
[size]="buttonSize()"
[square]="true"
[link]="true"
class="group"
(click)="row.getToggleExpandedHandler()()"
[attr.aria-expanded]="row.getIsExpanded()"
>
<st-icon
class="transition-transform duration-300 ease-in-out group-aria-[expanded=false]:rotate-0 group-aria-[expanded=true]:rotate-180"
[icon]="iconChevronDown"
[size]="iconSize()"
></st-icon>
</st-button>
} @else {
<span> </span>
}
{{ context.getValue() }}
</div>
</div>
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{>div{padding-left:calc(2rem * var(--depth, 1))}}\n"] }]
}] });
/**
* Expandable header cell component with sorting capability and expand all functionality
*/
class DataTableExpandableHeaderCellComponent {
/**
* TanStack Table context injected by flexRender
*/
context = injectFlexRenderContext();
/**
* Data table context for size synchronization
*/
dataTableContext = inject(DataTableContextService);
/**
* Display label for the column
*/
label = input.required();
/**
* Whether this column can be sorted
*/
canSort = input(false);
/**
* Current sort state of the column
*/
sortState = input(false);
/**
* Event emitted when sort toggle is requested
*/
sortToggle = output();
/**
* Get the table instance from context
*/
get table() {
return this.context.table;
}
/** @internal */
iconChevronDown = matKeyboardArrowDown;
/** @internal */
iconDefaultSort = matSwapVert;
/** @internal */
iconAscending = matArrowUpward;
/**
* @internal
* Computes button size based on data table size
*/
buttonSize = computed(() => {
switch (this.dataTableContext.size()) {
case 'xs':
return 'xs';
case 'sm':
return 'xs';
case 'md':
return 'sm';
case 'lg':
return 'sm';
case 'xl':
return 'md';
default:
return 'xs';
}
});
/**
* @internal
* Computes icon size based on data table size
*/
iconSize = computed(() => {
switch (this.dataTableContext.size()) {
case 'xs':
return 'xs';
case 'sm':
return 'xs';
case 'md':
return 'sm';
case 'lg':
return 'sm';
case 'xl':
return 'md';
default:
return 'xs';
}
});
/**
* @internal
* Computes the appropriate icon based on current sort state
*/
currentSortIcon = computed(() => {
switch (this.sortState()) {
case 'asc':
case 'desc':
return this.iconAscending;
default:
return this.iconDefaultSort;
}
});
/**
* @internal
* Computes aria-sort attribute for accessibility
*/
computedAriaSortAttribute = computed(() => {
switch (this.sortState()) {
case 'asc':
return 'ascending';
case 'desc':
return 'descending';
default:
return 'none';
}
});
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableExpandableHeaderCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.0", type: DataTableExpandableHeaderCellComponent, isStandalone: true, selector: "st-data-table-expandable-header-cell", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, canSort: { classPropertyName: "canSort", publicName: "canSort", isSignal: true, isRequired: false, transformFunction: null }, sortState: { classPropertyName: "sortState", publicName: "sortState", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sortToggle: "sortToggle" }, ngImport: i0, template: `
<div class="inline-flex items-center gap-1">
<st-button
variant="base"
[size]="buttonSize()"
[square]="true"
[link]="true"
(click)="context.table.getToggleAllRowsExpandedHandler()($event)"
class="group"
[attr.aria-expanded]="context.table.getIsAllRowsExpanded()"
>
<st-icon
class="transition-transform duration-300 ease-in-out group-aria-[expanded=false]:rotate-0 group-aria-[expanded=true]:rotate-180"
[icon]="iconChevronDown"
[size]="iconSize()"
></st-icon>
</st-button>
<!-- Column Label -->
<span class="font-bold">{{ label() }}</span>
<!-- Button for sorting -->
@if (canSort()) {
<st-button
variant="secondary"
[size]="buttonSize()"
[square]="true"
[ghost]="true"
(click)="sortToggle.emit(); $event.stopPropagation()"
class="group"
[attr.aria-label]="'Sort by ' + label()"
[attr.aria-sort]="computedAriaSortAttribute()"
>
<st-icon
class="transition-transform duration-300 ease-in-out group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180"
[icon]="currentSortIcon()"
[size]="iconSize()"
></st-icon>
</st-button>
}
</div>
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IconComponent, selector: "st-icon", inputs: ["color", "size", "icon"] }, { kind: "component", type: ButtonComponent, selector: "st-button", inputs: ["variant", "ghost", "outline", "link", "soft", "dash", "wide", "circle", "square", "glass", "block", "loader", "size", "shadow", "focusable", "icon", "iconPosition", "type", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableExpandableHeaderCellComponent, decorators: [{
type: Component,
args: [{
standalone: true,
selector: 'st-data-table-expandable-header-cell',
imports: [CommonModule, IconComponent, ButtonComponent],
template: `
<div class="inline-flex items-center gap-1">
<st-button
variant="base"
[size]="buttonSize()"
[square]="true"
[link]="true"
(click)="context.table.getToggleAllRowsExpandedHandler()($event)"
class="group"
[attr.aria-expanded]="context.table.getIsAllRowsExpanded()"
>
<st-icon
class="transition-transform duration-300 ease-in-out group-aria-[expanded=false]:rotate-0 group-aria-[expanded=true]:rotate-180"
[icon]="iconChevronDown"
[size]="iconSize()"
></st-icon>
</st-button>
<!-- Column Label -->
<span class="font-bold">{{ label() }}</span>
<!-- Button for sorting -->
@if (canSort()) {
<st-button
variant="secondary"
[size]="buttonSize()"
[square]="true"
[ghost]="true"
(click)="sortToggle.emit(); $event.stopPropagation()"
class="group"
[attr.aria-label]="'Sort by ' + label()"
[attr.aria-sort]="computedAriaSortAttribute()"
>
<st-icon
class="transition-transform duration-300 ease-in-out group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180"
[icon]="currentSortIcon()"
[size]="iconSize()"
></st-icon>
</st-button>
}
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
}]
}] });
/**
* Sub-component toggle cell for controlling custom sub-component visibility
*/
class DataTableSubToggleCellComponent {
/**
* The table row instance
*/
row = input.required();
/**
* Whether the sub-component is currently expanded
*/
isExpanded = input(false);
/**
* Whether this row can toggle a sub-component
*/
canToggleSubComponent = input(false);
/**
* Event emitted when toggle is clicked
*/
toggled = output();
/** @internal */
expandIcon = matKeyboardArrowDown;
/** @internal */
infoIcon = matInfo;
/**
* @internal
* Handles the toggle click
*/
handleToggle() {
this.toggled.emit(this.row().id);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableSubToggleCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.0", type: DataTableSubToggleCellComponent, isStandalone: true, selector: "st-data-table-sub-toggle-cell", inputs: { row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: true, transformFunction: null }, isExpanded: { classPropertyName: "isExpanded", publicName: "isExpanded", isSignal: true, isRequired: false, transformFunction: null }, canToggleSubComponent: { classPropertyName: "canToggleSubComponent", publicName: "canToggleSubComponent", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toggled: "toggled" }, ngImport: i0, template: `
@if (canToggleSubComponent()) {
<st-button
variant="secondary"
size="xs"
[circle]="true"
[ghost]="true"
class="group"
(click)="handleToggle(); $event.stopPropagation()"
[attr.aria-expanded]="isExpanded()"
[attr.title]="isExpanded() ? 'Hide Details' : 'Show Details'"
>
<st-icon
class="transition-transform duration-300 ease-in-out group-aria-[expanded=false]:rotate-0 group-aria-[expanded=true]:rotate-180"
[icon]="expandIcon"
size="sm"
></st-icon>
<span class="sr-only">{{ isExpanded() ? 'Hide Details' : 'Show Details' }}</span>
</st-button>
} @else {
<!-- Display an informational icon if a sub-component cannot be shown for this row -->
<st-button
variant="secondary"
size="xs"
[circle]="true"
[ghost]="true"
[attr.aria-expanded]="isExpanded()"
[attr.title]="isExpanded() ? 'Hide Details' : 'Show Details'"
[disabled]="true"
>
<st-icon [icon]="infoIcon" size="sm"></st-icon>
<span class="sr-only">{{ isExpanded() ? 'Hide Details' : 'Show Details' }}</span>
</st-button>
}
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IconComponent, selector: "st-icon", inputs: ["color", "size", "icon"] }, { kind: "component", type: ButtonComponent, selector: "st-button", inputs: ["variant", "ghost", "outline", "link", "soft", "dash", "wide", "circle", "square", "glass", "block", "loader", "size", "shadow", "focusable", "icon", "iconPosition", "type", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableSubToggleCellComponent, decorators: [{
type: Component,
args: [{
standalone: true,
selector: 'st-data-table-sub-toggle-cell',
imports: [CommonModule, IconComponent, ButtonComponent],
template: `
@if (canToggleSubComponent()) {
<st-button
variant="secondary"
size="xs"
[circle]="true"
[ghost]="true"
class="group"
(click)="handleToggle(); $event.stopPropagation()"
[attr.aria-expanded]="isExpanded()"
[attr.title]="isExpanded() ? 'Hide Details' : 'Show Details'"
>
<st-icon
class="transition-transform duration-300 ease-in-out group-aria-[expanded=false]:rotate-0 group-aria-[expanded=true]:rotate-180"
[icon]="expandIcon"
size="sm"
></st-icon>
<span class="sr-only">{{ isExpanded() ? 'Hide Details' : 'Show Details' }}</span>
</st-button>
} @else {
<!-- Display an informational icon if a sub-component cannot be shown for this row -->
<st-button
variant="secondary"
size="xs"
[circle]="true"
[ghost]="true"
[attr.aria-expanded]="isExpanded()"
[attr.title]="isExpanded() ? 'Hide Details' : 'Show Details'"
[disabled]="true"
>
<st-icon [icon]="infoIcon" size="sm"></st-icon>
<span class="sr-only">{{ isExpanded() ? 'Hide Details' : 'Show Details' }}</span>
</st-button>
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
}]
}] });
/**
* Header checkbox component for selecting/deselecting all rows on current page
*/
class DataTableHeadSelectionComponent {
/**
* TanStack Table context injected by flexRender
*/
context = injectFlexRenderContext();
/**
* Data table context for size synchronization
*/
dataTableContext = inject(DataTableContextService);
/**
* Computed checkbox size based on table size
*/
checkboxSize = computed(() => {
const tableSize = this.dataTableContext.size();
switch (tableSize) {
case 'xs':
return 'xs';
case 'sm':
return 'sm';
case 'md':
return 'md';
case 'lg':
return 'lg';
case 'xl':
return 'lg';
default:
return 'sm';
}
});
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableHeadSelectionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.0", type: DataTableHeadSelectionComponent, isStandalone: true, selector: "st-data-table-head-selection", ngImport: i0, template: `
<st-checkbox
[value]="context.table.getIsAllPageRowsSelected()"
(valueUpdated)="context.table.toggleAllPageRowsSelected()"
[size]="checkboxSize()"
></st-checkbox>
`, isInline: true, dependencies: [{ kind: "component", type: CheckboxComponent, selector: "st-checkbox", inputs: ["variant", "size", "label", "name", "value", "disabled"], outputs: ["valueChange", "valueUpdated", "disabledChange"] }, { kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableHeadSelectionComponent, decorators: [{
type: Component,
args: [{
standalone: true,
selector: 'st-data-table-head-selection',
imports: [CheckboxComponent, CommonModule],
template: `
<st-checkbox
[value]="context.table.getIsAllPageRowsSelected()"
(valueUpdated)="context.table.toggleAllPageRowsSelected()"
[size]="checkboxSize()"
></st-checkbox>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
}]
}] });
/**
* Row checkbox component for individual row selection
*/
class DataTableRowSelectionComponent {
/**
* TanStack Table context injected by flexRender
*/
context = injectFlexRenderContext();
/**
* Data table context for size synchronization
*/
dataTableContext = inject(DataTableContextService);
/**
* Computed checkbox size based on table size
*/
checkboxSize = computed(() => {
const tableSize = this.dataTableContext.size();
switch (tableSize) {
case 'xs':
return 'xs';
case 'sm':
return 'sm';
case 'md':
return 'md';
case 'lg':
return 'lg';
case 'xl':
return 'lg';
default:
return 'sm';
}
});
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableRowSelectionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.0", type: DataTableRowSelectionComponent, isStandalone: true, selector: "st-data-table-row-selection", ngImport: i0, template: `
<st-checkbox
[value]="context.row.getIsSelected()"
[disabled]="!context.row.getCanSelect()"
(valueUpdated)="context.row.toggleSelected()"
[size]="checkboxSize()"
></st-checkbox>
`, isInline: true, dependencies: [{ kind: "component", type: CheckboxComponent, selector: "st-checkbox", inputs: ["variant", "size", "label", "name", "value", "disabled"], outputs: ["valueChange", "valueUpdated", "disabledChange"] }, { kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableRowSelectionComponent, decorators: [{
type: Component,
args: [{
standalone: true,
selector: 'st-data-table-row-selection',
imports: [CheckboxComponent, CommonModule],
template: `
<st-checkbox
[value]="context.row.getIsSelected()"
[disabled]="!context.row.getCanSelect()"
(valueUpdated)="context.row.toggleSelected()"
[size]="checkboxSize()"
></st-checkbox>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
}]
}] });
/**
* Sortable header component for data table columns
*
* @example
* ```typescript
* flexRenderComponent(DataTableSortableHeaderComponent, {
* inputs: {
* label: 'Column Name',
* sortState: column.getIsSorted(),
* },
* outputs: {
* sortToggle: () => column.toggleSorting(),
* },
* })
* ```
*/
class DataTableSortableHeaderComponent {
dataTableContext = inject(DataTableContextService);
/**
* Display label for the column
*/
label = input.required();
/**
* Current sort state of the column
*/
sortState = input(false);
/**
* Event emitted when sort toggle is requested
*/
sortToggle = output();
/** @internal */
iconDefaultSort = matSwapVert;
/** @internal */
iconAscending = matArrowUpward;
/**
* @internal
* Computes button size based on data table size
*/
buttonSize = computed(() => {
switch (this.dataTableContext.size()) {
case 'xs':
return 'xs';
case 'sm':
return 'xs';
case 'md':
return 'sm';
case 'lg':
return 'sm';
case 'xl':
return 'md';
default:
return 'xs';
}
});
/**
* @internal
* Computes icon size based on data table size
*/
iconSize = computed(() => {
switch (this.dataTableContext.size()) {
case 'xs':
return 'xs';
case 'sm':
return 'xs';
case 'md':
return 'sm';
case 'lg':
return 'sm';
case 'xl':
return 'md';
default:
return 'xs';
}
});
/**
* @internal
* Computes the appropriate icon based on current sort state
*/
currentSortIcon = computed(() => {
switch (this.sortState()) {
case 'asc':
case 'desc':
return this.iconAscending;
default:
return this.iconDefaultSort;
}
});
/**
* @internal
* Computes aria-sort attribute for accessibility
*/
computedAriaSortAttribute = computed(() => {
switch (this.sortState()) {
case 'asc':
return 'ascending';
case 'desc':
return 'descending';
default:
return 'none';
}
});
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableSortableHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.0", type: DataTableSortableHeaderComponent, isStandalone: true, selector: "st-data-table-sortable-header", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, sortState: { classPropertyName: "sortState", publicName: "sortState", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sortToggle: "sortToggle" }, ngImport: i0, template: `
<div class="inline-flex items-center gap-1">
<span class="font-bold">{{ label() }}</span>
<st-button
variant="secondary"
[size]="buttonSize()"
[square]="true"
[ghost]="true"
(click)="sortToggle.emit(); $event.stopPropagation()"
class="group"
[attr.aria-label]="'Sort by ' + label()"
[attr.aria-sort]="computedAriaSortAttribute()"
>
<st-icon
class="transition-transform duration-300 ease-in-out group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180"
[icon]="currentSortIcon()"
[size]="iconSize()"
></st-icon>
</st-button>
</div>
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IconComponent, selector: "st-icon", inputs: ["color", "size", "icon"] }, { kind: "component", type: ButtonComponent, selector: "st-button", inputs: ["variant", "ghost", "outline", "link", "soft", "dash", "wide", "circle", "square", "glass", "block", "loader", "size", "shadow", "focusable", "icon", "iconPosition", "type", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableSortableHeaderComponent, decorators: [{
type: Component,
args: [{
standalone: true,
selector: 'st-data-table-sortable-header',
imports: [CommonModule, IconComponent, ButtonComponent],
template: `
<div class="inline-flex items-center gap-1">
<span class="font-bold">{{ label() }}</span>
<st-button
variant="secondary"
[size]="buttonSize()"
[square]="true"
[ghost]="true"
(click)="sortToggle.emit(); $event.stopPropagation()"
class="group"
[attr.aria-label]="'Sort by ' + label()"
[attr.aria-sort]="computedAriaSortAttribute()"
>
<st-icon
class="transition-transform duration-300 ease-in-out group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180"
[icon]="currentSortIcon()"
[size]="iconSize()"
></st-icon>
</st-button>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
}]
}] });
/**
* @internal
* Generates data table classes with size and height variants
*/
const dataTableContent = cva([
'table',
'relative',
'text-pretty',
'[&_tbody]:font-body',
'[&_thead]:font-heading',
'[&_thead]:leading-normal',
'[&_tbody]:leading-tight',
'[&_th]:bg-neutral [&_th]:text-neutral-content',
'[&_th]:px-4',
'[&_td]:px-4',
'[&_tbody_tr]:border-transparent',
'rounded-t-box',
'[&_tbody_tr]:border-transparent',
], {
variants: {
size: {
xs: ['table-xs'],
sm: ['table-sm'],
md: ['table-md'],
lg: ['table-lg'],
xl: ['table-xl'],
},
},
defaultVariants: {
size: 'sm',
},
});
/**
* A styled container component for data tables - provides styling shell only
*
* This component is similar to st-table but with additional data table specific styling.
* Users should compose their own table structure using TanStack Table and SDK components.
*
* @example
* ```html
* <st-data-table-container size="md" height="lg">
* <table>
* <thead>
* <tr>
* <th><st-data-table-sortable-header>Name</st-data-table-sortable-header></th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>John Doe</td>
* </tr>
* </tbody>
* </table>
* </st-data-table-container>
* ```
*/
class DataTableContainerComponent {
translationService = inject(TranslateService);
dataTableContext = inject(DataTableContextService);
// Icon references for template
noDataIcon = heroDocumentText;
noResultsIcon = heroXCircle;
/**
* Table size variant
* @defaultValue 'sm'
*/
size = input('sm');
constructor() {
// Update context when size changes
effect(() => {
this.dataTableContext.setSize(this.size());
});
}
/**
* Enable sticky header row
* @defaultValue false
*/
pinRows = input(false);
/**
* Enable sticky first column
* @defaultValue false
*/
pinColumns = input(false);
/**
* Apply zebra-striping to rows
* @defaultValue false
*/
zebra = input(false);
/**
* Pagination control display variant
* @defaultValue 'default'
*/
paginatorType = input('default');
/**
* Total number of items across all pages
* @defaultValue 0
*/
totalItems = input(0);
/**
* Pagination configuration
* @defaultValue { id: 'paginator', currentPage: 1, itemsPerPage: 10, search: '', sortColumn: null, sortDirection: null, filters: {} }
*/
pagination = input({
id: 'paginator',
currentPage: 1,
itemsPerPage: 10,
search: '',
sortColumn: null,
sortDirection: null,
filters: {},
});
/**
* Maximum number of pagination buttons to display
* @defaultValue 4
*/
maxSize = input(4);
/** Event emitted when page changes */
pageChanged = output();
/** Event emitted when page size changes */
pageSizeChanged = output();
ENTRIES = [5, 10, 20, 50, 100];
entriesPerPageOptions = computed(() => {
return this.ENTRIES.map((n) => {
const translatedText = this.translationService.instant('sdk.dataTable.perPage', { count: n });
// Fallback if translation fails
const fallbackText = `${n} / page`;
return {
value: n,
text: translatedText && translatedText !== 'sdk.dataTable.perPage' ? translatedText : fallbackText,
};
});
});
/**
* Computed total number of pages for the internal paginator.
* Calculated from totalItems and itemsPerPage.
*/
totalPages = computed(() => {
const itemsPerPage = this.pagination().itemsPerPage || 1;
const totalItems = this.totalItems();
return Math.ceil(totalItems / itemsPerPage);
});
/**
* Computed display range for current page entries
* @example "1-10" of 100 entries
*/
paginationRange = computed(() => {
const start = this.pagination().currentPage * this.pagination().itemsPerPage - this.pagination().itemsPerPage + 1;
const end = Math.min(this.pagination().currentPage * this.pagination().itemsPerPage, this.totalItems());
return `${start}-${end}`;
});
/**
* Function to determine if any filters are currently active
* This allows the parent component to define what constitutes an "active filter"
*/
hasActiveFiltersCallback = input();
/**
* Computed boolean indicating if any filters are currently active
*/
hasActiveFilters = computed(() => {
const callback = this.hasActiveFiltersCallback();
if (callback) {
return callback();
}
// Fallback: check for basic search term only
const pagination = this.pagination();
return !!pagination.search?.trim();
});
/**
* Computed boolean indicating if we should show "no results" vs "no data"
*/
hasNoResults = computed(() => {
return this.totalItems() === 0 && this.hasActiveFilters();
});
/**
* Computed boolean indicating if we should show "no data" state
*/
hasNoData = computed(() => {
return this.totalItems() === 0 && !this.hasActiveFilters();
});
/**
* Handles page navigation events
* @param page - New page number
*/
handlePageChange(page) {
this.pageChanged.emit(page);
}
/**
* Handles page size changes
* @param size - New items per page value
*/
handlePageSizeChange(size) {
let resolved;
if (typeof size === 'number') {
resolved = size;
}
else if (size && typeof size === 'object' && 'value' in size) {
const v = size['value'];
const safeStringify = (x) => (['string', 'number', 'boolean'].includes(typeof x) ? String(x) : '0');
resolved = typeof v === 'number' ? v : Number(safeStringify(v));
}
else {
const safeStringify = (x) => (['string', 'number', 'boolean'].includes(typeof x) ? String(x) : '0');
resolved = Number(safeStringify(size)) || undefined;
}
const s = Number(resolved || 1);
this.pageSizeChanged.emit(s);
}
/**
* @internal
* Computes typography variant for paginator text based on table size
*/
paginatorTextSize = computed(() => {
switch (this.size()) {
case 'xs':
return 'body-xxs';
case 'sm':
return 'body-xs';
case 'md':
return 'body-sm';
case 'lg':
return 'body';
case 'xl':
return 'body';
default:
return 'body-xs';
}
});
/**
* @internal
* Computes table element classes based on current configuration
*/
tableClass = computed(() => cn(dataTableContent({
size: this.size(),
}), {
'table-pin-rows': this.pinRows(),
'table-pin-cols': this.pinColumns(),
'table-zebra': this.zebra(),
}));
/**
* @internal
* Computes table container classes based on height input
*/
tableContainerClass = computed(() => cn('w-full flex-grow'));
/**
* @internal
* Computes outer container classes based on size
*/
containerClass = computed(() => cn('rounded-box border-neutral flex flex-col overflow-x-auto border border-solid gap-3 @container'));
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.0", type: DataTableContainerComponent, isStandalone: true, selector: "st-data-table", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, pinRows: { classPropertyName: "pinRows", publicName: "pinRows", isSignal: true, isRequired: false, transformFunction: null }, pinColumns: { classPropertyName: "pinColumns", publicName: "pinColumns", isSignal: true, isRequired: false, transformFunction: null }, zebra: { classPropertyName: "zebra", publicName: "zebra", isSignal: true, isRequired: false, transformFunction: null }, paginatorType: { classPropertyName: "paginatorType", publicName: "paginatorType", isSignal: true, isRequired: false, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: false, transformFunction: null }, pagination: { classPropertyName: "pagination", publicName: "pagination", isSignal: true, isRequired: false, transformFunction: null }, maxSize: { classPropertyName: "maxSize", publicName: "maxSize", isSignal: true, isRequired: false, transformFunction: null }, hasActiveFiltersCallback: { classPropertyName: "hasActiveFiltersCallback", publicName: "hasActiveFiltersCallback", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageChanged: "pageChanged", pageSizeChanged: "pageSizeChanged" }, providers: [DataTableContextService], ngImport: i0, template: "<div [class]=\"containerClass()\">\n\t<!-- Table container -->\n\t<div [class]=\"tableContainerClass()\">\n\t\t<div class=\"w-full flex-grow overflow-x-auto\">\n\t\t\t@if (hasNoData()) {\n\t\t\t\t<!-- No data state -->\n\t\t\t\t<div class=\"flex flex-col items-center justify-center py-12 text-center\">\n\t\t\t\t\t<div class=\"text-base-content/40 mb-3\">\n\t\t\t\t\t\t<st-icon [icon]=\"noDataIcon\" size=\"lg\" color=\"base-content\" class=\"mx-auto opacity-40\"></st-icon>\n\t\t\t\t\t</div>\n\t\t\t\t\t<h3 typography tyVariant=\"h6\" class=\"text-base-content/60 mb-2\">{{ 'sdk.dataTable.noData.title' | translate }}</h3>\n\t\t\t\t\t<p typography tyVariant=\"body-sm\" class=\"text-base-content/40\">{{ 'sdk.dataTable.noData.message' | translate }}</p>\n\t\t\t\t</div>\n\t\t\t} @else if (hasNoResults()) {\n\t\t\t\t<!-- No results from filters state -->\n\t\t\t\t<div class=\"flex flex-col items-center justify-center py-12 text-center\">\n\t\t\t\t\t<div class=\"text-base-content/40 mb-3\">\n\t\t\t\t\t\t<st-icon [icon]=\"noResultsIcon\" size=\"xl\" color=\"base-content\" class=\"mx-auto opacity-40\"></st-icon>\n\t\t\t\t\t</div>\n\t\t\t\t\t<h3 typography tyVariant=\"h6\" class=\"text-base-content/60 mb-2\">{{ 'sdk.dataTable.noResults.title' | translate }}</h3>\n\t\t\t\t\t<p typography tyVariant=\"body-sm\" class=\"text-base-content/40\">{{ 'sdk.dataTable.noResults.message' | translate }}</p>\n\t\t\t\t</div>\n\t\t\t} @else {\n\t\t\t\t<table [class]=\"tableClass()\">\n\t\t\t\t\t<ng-content></ng-content>\n\t\t\t\t</table>\n\t\t\t}\n\t\t</div>\n\t</div>\n\n\t<!-- Pagination from showcase -->\n\t<div class=\"@container flex w-full flex-col gap-4 overflow-x-auto pb-3\">\n\t\t<!-- Divider -->\n\t\t<div class=\"divider divider-neutral my-0 h-[0.0625rem] px-4 before:h-[0.0625rem] after:h-[0.0625rem]\"></div>\n\t\t<!-- Paginator Container -->\n\t\t<div\n\t\t\tclass=\"flex flex-col items-center gap-4 px-4 @2xl:flex-row @2xl:justify-between @2xl:gap-2 @4xl:grid @4xl:grid-cols-[auto_1fr_auto] @4xl:items-center\"\n\t\t>\n\t\t\t<!-- Entries range text -->\n\t\t\t<div class=\"flex items-center justify-end\">\n\t\t\t\t<span typography [tyVariant]=\"paginatorTextSize()\" [tyColor]=\"'inherit'\">{{\n\t\t\t\t\t'sdk.dataTable.entriesRange' | translate: { range: paginationRange(), total: totalItems() }\n\t\t\t\t}}</span>\n\t\t\t</div>\n\n\t\t\t<!-- Paginator -->\n\t\t\t<div class=\"flex justify-center\">\n\t\t\t\t<st-paginator\n\t\t\t\t\t[pages]=\"totalPages()\"\n\t\t\t\t\t[currentPage]=\"pagination().currentPage\"\n\t\t\t\t\t[maxSize]=\"maxSize()\"\n\t\t\t\t\ttype=\"default\"\n\t\t\t\t\t(pageChanged)=\"handlePageChange($event)\"\n\t\t\t\t></st-paginator>\n\t\t\t</div>\n\n\t\t\t<!-- Entries per page dropdown -->\n\t\t\t<st-select\n\t\t\t\t[options]=\"entriesPerPageOptions()\"\n\t\t\t\t[value]=\"pagination().itemsPerPage\"\n\t\t\t\tvalueKey=\"value\"\n\t\t\t\tdisplayKey=\"text\"\n\t\t\t\tsize=\"sm\"\n\t\t\t\t(valueUpdated)=\"handlePageSizeChange($event)\"\n\t\t\t\tvariant=\"secondary\"\n\t\t\t\t[allowClear]=\"false\"\n\t\t\t></st-select>\n\t\t</div>\n\t</div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: PaginatorComponent, selector: "st-paginator", inputs: ["paginatorId", "maxSize", "type", "pages", "currentPage"], outputs: ["pageChanged", "currentPageChange"] }, { kind: "directive", type: TypographyDirective, selector: "[typography]", inputs: ["tyVariant", "tyColor", "tyFontWeight", "tyTextOverflow"] }, { kind: "component", type: SelectComponent, selector: "st-select", inputs: ["variant", "size", "ghost", "name", "placeholder", "label", "value", "options", "displayKey", "valueKey", "allowClear", "parentForm", "formControlName", "disabled"], outputs: ["valueChange", "disabledChange", "blurred", "valueUpdated"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "component", type: IconComponent, selector: "st-icon", inputs: ["color", "size", "icon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableContainerComponent, decorators: [{
type: Component,
args: [{ selector: 'st-data-table', imports: [CommonModule, PaginatorComponent, TypographyDirective, SelectComponent, TranslatePipe, IconComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [DataTableContextService], template: "<div [class]=\"containerClass()\">\n\t<!-- Table container -->\n\t<div [class]=\"tableContainerClass()\">\n\t\t<div class=\"w-full flex-grow overflow-x-auto\">\n\t\t\t@if (hasNoData()) {\n\t\t\t\t<!-- No data state -->\n\t\t\t\t<div class=\"flex flex-col items-center justify-center py-12 text-center\">\n\t\t\t\t\t<div class=\"text-base-content/40 mb-3\">\n\t\t\t\t\t\t<st-icon [icon]=\"noDataIcon\" size=\"lg\" color=\"base-content\" class=\"mx-auto opacity-40\"></st-icon>\n\t\t\t\t\t</div>\n\t\t\t\t\t<h3 typography tyVariant=\"h6\" class=\"text-base-content/60 mb-2\">{{ 'sdk.dataTable.noData.title' | translate }}</h3>\n\t\t\t\t\t<p typography tyVariant=\"body-sm\" class=\"text-base-content/40\">{{ 'sdk.dataTable.noData.message' | translate }}</p>\n\t\t\t\t</div>\n\t\t\t} @else if (hasNoResults()) {\n\t\t\t\t<!-- No results from filters state -->\n\t\t\t\t<div class=\"flex flex-col items-center justify-center py-12 text-center\">\n\t\t\t\t\t<div class=\"text-base-content/40 mb-3\">\n\t\t\t\t\t\t<st-icon [icon]=\"noResultsIcon\" size=\"xl\" color=\"base-content\" class=\"mx-auto opacity-40\"></st-icon>\n\t\t\t\t\t</div>\n\t\t\t\t\t<h3 typography tyVariant=\"h6\" class=\"text-base-content/60 mb-2\">{{ 'sdk.dataTable.noResults.title' | translate }}</h3>\n\t\t\t\t\t<p typography tyVariant=\"body-sm\" class=\"text-base-content/40\">{{ 'sdk.dataTable.noResults.message' | translate }}</p>\n\t\t\t\t</div>\n\t\t\t} @else {\n\t\t\t\t<table [class]=\"tableClass()\">\n\t\t\t\t\t<ng-content></ng-content>\n\t\t\t\t</table>\n\t\t\t}\n\t\t</div>\n\t</div>\n\n\t<!-- Pagination from showcase -->\n\t<div class=\"@container flex w-full flex-col gap-4 overflow-x-auto pb-3\">\n\t\t<!-- Divider -->\n\t\t<div class=\"divider divider-neutral my-0 h-[0.0625rem] px-4 before:h-[0.0625rem] after:h-[0.0625rem]\"></div>\n\t\t<!-- Paginator Container -->\n\t\t<div\n\t\t\tclass=\"flex flex-col items-center gap-4 px-4 @2xl:flex-row @2xl:justify-between @2xl:gap-2 @4xl:grid @4xl:grid-cols-[auto_1fr_auto] @4xl:items-center\"\n\t\t>\n\t\t\t<!-- Entries range text -->\n\t\t\t<div class=\"flex items-center justify-end\">\n\t\t\t\t<span typography [tyVariant]=\"paginatorTextSize()\" [tyColor]=\"'inherit'\">{{\n\t\t\t\t\t'sdk.dataTable.entriesRange' | translate: { range: paginationRange(), total: totalItems() }\n\t\t\t\t}}</span>\n\t\t\t</div>\n\n\t\t\t<!-- Paginator -->\n\t\t\t<div class=\"flex justify-center\">\n\t\t\t\t<st-paginator\n\t\t\t\t\t[pages]=\"totalPages()\"\n\t\t\t\t\t[currentPage]=\"pagination().currentPage\"\n\t\t\t\t\t[maxSize]=\"maxSize()\"\n\t\t\t\t\ttype=\"default\"\n\t\t\t\t\t(pageChanged)=\"handlePageChange($event)\"\n\t\t\t\t></st-paginator>\n\t\t\t</div>\n\n\t\t\t<!-- Entries per page dropdown -->\n\t\t\t<st-select\n\t\t\t\t[options]=\"entriesPerPageOptions()\"\n\t\t\t\t[value]=\"pagination().itemsPerPage\"\n\t\t\t\tvalueKey=\"value\"\n\t\t\t\tdisplayKey=\"text\"\n\t\t\t\tsize=\"sm\"\n\t\t\t\t(valueUpdated)=\"handlePageSizeChange($event)\"\n\t\t\t\tvariant=\"secondary\"\n\t\t\t\t[allowClear]=\"false\"\n\t\t\t></st-select>\n\t\t</div>\n\t</div>\n</div>\n" }]
}], ctorParameters: () => [] });
class DataTableSubComponentToggleCellComponent {
row = input.required();
isExpanded = input.required(); // Current state of the sub-component for this row
toggled = output(); // Emits row.id when clicked to trigger parent component update
// Input to determine if this specific row is eligible to display a sub-component
canToggleSubComponent = input(false);
iconChevronDown = matKeyboardArrowDown;
iconInfo = matInfo;
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableSubComponentToggleCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.0", type: DataTableSubComponentToggleCellComponent, isStandalone: true, selector: "st-data-table-sub-component-toggle-cell", inputs: { row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: true, transformFunction: null }, isExpanded: { classPropertyName: "isExpanded", publicName: "isExpanded", isSignal: true, isRequired: true, transformFunction: null }, canToggleSubComponent: { classPropertyName: "canToggleSubComponent", publicName: "canToggleSubComponent", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toggled: "toggled" }, ngImport: i0, template: `
@if (canToggleSubComponent()) {
<st-button
variant="secondary"
size="xs"
[circle]="true"
[ghost]="true"
class="group"
(click)="toggled.emit(row().id)"
[attr.aria-expanded]="isExpanded()"
title="{{ isExpanded() ? 'Hide Details' : 'Show Details' }}"
>
<st-icon
class="transition-transform duration-300 ease-in-out group-aria-[expanded=false]:rotate-0 group-aria-[expanded=true]:rotate-180"
[icon]="iconChevronDown"
size="sm"
></st-icon>
<span class="sr-only">{{ isExpanded() ? 'Hide Details' : 'Show Details' }}</span>
</st-button>
} @else {
<!-- Display an informational icon if a sub-component cannot be shown for this row -->
<st-button
variant="secondary"
size="xs"
[circle]="true"
[ghost]="true"
[attr.aria-expanded]="isExpanded()"
title="{{ isExpanded() ? 'Hide Details' : 'Show Details' }}"
[disabled]="true"
>
<st-icon [icon]="iconInfo" size="sm"></st-icon>
<span class="sr-only">{{ isExpanded() ? 'Hide Details' : 'Show Details' }}</span>
</st-button>
}
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IconComponent, selector: "st-icon", inputs: ["color", "size", "icon"] }, { kind: "component", type: ButtonComponent, selector: "st-button", inputs: ["variant", "ghost", "outline", "link", "soft", "dash", "wide", "circle", "square", "glass", "block", "loader", "size", "shadow", "focusable", "icon", "iconPosition", "type", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: DataTableSubComponentToggleCellComponent, decorators: [{
type: Component,
args: [{
standalone: true,
selector: 'st-data-table-sub-component-toggle-cell',
imports: [CommonModule, IconComponent, ButtonComponent],
template: `
@if (canToggleSubComponent()) {
<st-button
variant="secondary"
size="xs"
[circle]="true"
[ghost]="true"
class="group"
(click)="toggled.emit(row().id)"
[attr.aria-expanded]="isExpanded()"
title="{{ isExpanded() ? 'Hide Details' : 'Show Details' }}"
>
<st-icon
class="transition-transform duration-300 ease-in-out group-aria-[expanded=false]:rotate-0 group-aria-[expanded=true]:rotate-180"
[icon]="iconChevronDown"
size="sm"
></st-icon>
<span class="sr-only">{{ isExpanded() ? 'Hide Details' : 'Show Details' }}</span>
</st-button>
} @else {
<!-- Display an informational icon if a sub-component cannot be shown for this row -->
<st-button
variant="secondary"
size="xs"
[circle]="true"
[ghost]="true"
[attr.aria-expanded]="isExpanded()"
title="{{ isExpanded() ? 'Hide Details' : 'Show Details' }}"
[disabled]="true"
>
<st-icon [icon]="iconInfo" size="sm"></st-icon>
<span class="sr-only">{{ isExpanded() ? 'Hide Details' : 'Show Details' }}</span>
</st-button>
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
}]
}] });
/**