UNPKG

@sixbell-telco/sdk

Version:

A collection of reusable components designed for use in Sixbell Telco Angular projects

414 lines (408 loc) 23.6 kB
import * as i0 from '@angular/core'; import { input, computed, Component, Pipe, output, model, ChangeDetectionStrategy } from '@angular/core'; import { IconComponent } from '@sixbell-telco/sdk/components/icon'; import { matKeyboardArrowLeftOutline, matKeyboardDoubleArrowLeftOutline, matKeyboardArrowRightOutline, matKeyboardDoubleArrowRightOutline } from '@sixbell-telco/sdk/components/icon/material/outline'; import { TypographyDirective } from '@sixbell-telco/sdk/directives/typography'; import { cn } from '@sixbell-telco/sdk/utils/cn'; import { cva } from 'class-variance-authority'; import { CommonModule } from '@angular/common'; /** * @internal * Generates base pagination item classes with style variants */ const paginationItemComponent = cva([], { variants: { variant: { numeric: [ 'btn', 'btn-outline', 'flex', 'text-base-content', 'border-secondary', 'hover:text-secondary-content', 'hover:bg-secondary/40', 'hover:border-secondary', 'active:hover:bg-secondary-content', 'active:hover:bg-secondary', 'active:focus:border-secondary', 'min-h-[30px]', 'h-[30px]', 'min-w-[30px]', 'w-[30px]', 'p-0', ], icon: [ 'btn', 'btn-outline', 'flex', 'text-base-content', 'border-secondary', 'hover:text-secondary-content', 'hover:bg-secondary/40', 'hover:border-secondary', 'active:hover:bg-secondary-content', 'active:hover:bg-secondary', 'active:focus:border-secondary', 'min-h-[30px]', 'h-[30px]', 'min-w-[30px]', 'w-[30px]', 'p-0', ], dots: [ 'btn', 'btn-circle', 'flex', 'border-secondary', 'bg-secondary', 'hover:bg-secondary/40', 'hover:border-secondary', 'hover:scale-150', 'active:hover:bg-secondary', 'active:border-secondary', 'active:scale-150', 'active:focus:border-secondary', 'h-[10px]', 'min-h-[10px]', 'w-[10px]', 'min-w-[10px]', 'p-0', ], // New variant for ellipsis ellipsis: [ 'px-2', 'py-1', 'text-base-content', 'pointer-events-none', // Make it non-clickable 'select-none', // Prevent text selection ], }, }, compoundVariants: [], defaultVariants: { variant: 'numeric', }, }); /** * A compact pagination control component with interactive states * * @remarks * Built with Tailwind CSS and class-variance-authority for style management. * Supports three variants: numeric pages, navigation icons, and dot indicators. * Provides visual feedback for active and hover states. * * @example * ```html * <!-- Numeric page item --> * <st-pagination-item * variant="numeric" * [number]="2" * [active]="true" * ></st-pagination-item> * ``` * * @example * ```html * <!-- Icon navigation --> * <st-pagination-item * variant="icon" * icon="chevron-right" * ></st-pagination-item> * ``` * * @example * ```html * <!-- Active dot indicator --> * <st-pagination-item * variant="dots" * [active]="true" * ></st-pagination-item> * ``` */ class PaginationItemComponent { /** * Style variant for the pagination item * @defaultValue 'numeric' */ variant = input(); /** * Icon name from the icon library * @defaultValue matKeyboardArrowLeftOutline */ icon = input(matKeyboardArrowLeftOutline); /** * Display number or custom content * @defaultValue 1 */ number = input(1); /** * Active state styling * @defaultValue false */ active = input(false); /** * Disabled state styling * @defaultValue false */ disabled = input(false); // New input for disabled state /** * @internal * Computed class string for the pagination item */ componentClass = computed(() => { return cn(paginationItemComponent({ variant: this.variant(), }), { 'bg-secondary border-secondary text-secondary-content': this.active() && this.variant() !== 'dots', 'opacity-50 cursor-not-allowed': this.disabled(), // Add disabled styles }, { 'hover:scale-100 hover:bg-secondary/100 w-[16px] h-[16px] bg-secondary border-secondary text-secondary-content': this.active() && this.variant() === 'dots', }); }); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: PaginationItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.0", type: PaginationItemComponent, isStandalone: true, selector: "st-pagination-item", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, number: { classPropertyName: "number", publicName: "number", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@let variantVar = this.variant();\n@let componentClassVar = this.componentClass();\n\n@if (variantVar === 'numeric') {\n\t<button [class]=\"componentClassVar\" type=\"button\" [disabled]=\"disabled()\">\n\t\t<span typography [tyVariant]=\"'body-sm'\" [tyColor]=\"'inherit'\" [tyFontWeight]=\"'normal'\">{{ number() }}</span>\n\t</button>\n} @else if (variantVar === 'icon') {\n\t<button [class]=\"componentClassVar\" type=\"button\" [disabled]=\"disabled()\">\n\t\t<st-icon [icon]=\"this.icon()\" color=\"inherit\" size=\"sm\"></st-icon>\n\t</button>\n} @else if (variantVar === 'dots') {\n\t<button [class]=\"componentClassVar\" type=\"button\" [disabled]=\"disabled()\">\n\t\t<span></span>\n\t</button>\n} @else if (variantVar === 'ellipsis') {\n\t<!-- Ellipsis button is inherently not interactive -->\n\t<button [class]=\"componentClassVar\" type=\"button\" [disabled]=\"true\">\n\t\t<span typography [tyVariant]=\"'body-sm'\" [tyColor]=\"'inherit'\" [tyFontWeight]=\"'normal'\">{{ number() }}</span>\n\t</button>\n} @else {\n\t<button [class]=\"componentClassVar\" type=\"button\" [disabled]=\"disabled()\">\n\t\t<span typography [tyVariant]=\"'body-sm'\" [tyColor]=\"'inherit'\" [tyFontWeight]=\"'normal'\">{{ number() }}</span>\n\t</button>\n}\n", styles: [""], dependencies: [{ kind: "component", type: IconComponent, selector: "st-icon", inputs: ["color", "size", "icon"] }, { kind: "directive", type: TypographyDirective, selector: "[typography]", inputs: ["tyVariant", "tyColor", "tyFontWeight", "tyTextOverflow"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: PaginationItemComponent, decorators: [{ type: Component, args: [{ selector: 'st-pagination-item', imports: [IconComponent, TypographyDirective], standalone: true, template: "@let variantVar = this.variant();\n@let componentClassVar = this.componentClass();\n\n@if (variantVar === 'numeric') {\n\t<button [class]=\"componentClassVar\" type=\"button\" [disabled]=\"disabled()\">\n\t\t<span typography [tyVariant]=\"'body-sm'\" [tyColor]=\"'inherit'\" [tyFontWeight]=\"'normal'\">{{ number() }}</span>\n\t</button>\n} @else if (variantVar === 'icon') {\n\t<button [class]=\"componentClassVar\" type=\"button\" [disabled]=\"disabled()\">\n\t\t<st-icon [icon]=\"this.icon()\" color=\"inherit\" size=\"sm\"></st-icon>\n\t</button>\n} @else if (variantVar === 'dots') {\n\t<button [class]=\"componentClassVar\" type=\"button\" [disabled]=\"disabled()\">\n\t\t<span></span>\n\t</button>\n} @else if (variantVar === 'ellipsis') {\n\t<!-- Ellipsis button is inherently not interactive -->\n\t<button [class]=\"componentClassVar\" type=\"button\" [disabled]=\"true\">\n\t\t<span typography [tyVariant]=\"'body-sm'\" [tyColor]=\"'inherit'\" [tyFontWeight]=\"'normal'\">{{ number() }}</span>\n\t</button>\n} @else {\n\t<button [class]=\"componentClassVar\" type=\"button\" [disabled]=\"disabled()\">\n\t\t<span typography [tyVariant]=\"'body-sm'\" [tyColor]=\"'inherit'\" [tyFontWeight]=\"'normal'\">{{ number() }}</span>\n\t</button>\n}\n" }] }] }); // projects/sdk/components/paginator/src/pagination.pipe.ts /** * A pipe to manually paginate an array of items. * * @remarks * This pipe takes an array and a pagination configuration object (currentPage, itemsPerPage) * and returns a sliced version of the array, representing the items for the current page. * * @example * ```html * @for (item of items | pagination: { currentPage: 2, itemsPerPage: 10 }; track item.id) { * <li>{{ item.name }}</li> * } * ``` */ class PaginationPipe { transform(items, pagination) { if (!items || !Array.isArray(items) || pagination?.currentPage === undefined || pagination?.itemsPerPage === undefined) { return items; } const currentPage = Math.max(1, pagination.currentPage); const itemsPerPage = Math.max(1, pagination.itemsPerPage); const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; return items.slice(startIndex, endIndex); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: PaginationPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.0", ngImport: i0, type: PaginationPipe, isStandalone: true, name: "paginate" }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: PaginationPipe, decorators: [{ type: Pipe, args: [{ name: 'paginate', standalone: true, // Use standalone pipes for better tree-shaking and consistency }] }] }); /** * A customizable pagination component with navigation controls * * @remarks * Supports both traditional numbered pagination and dot indicators. * Provides custom styling and navigation controls. * * @example * ```html * <st-paginator * [pages]="10" * type="default" * (pageChanged)="onPageChange($event)" * ></st-paginator> * ``` * * @example * ```html * <st-paginator * [pages]="5" * type="dots" * [maxSize]="3" * ></st-paginator> * ``` */ class PaginatorComponent { /** * Unique identifier for the paginator instance * @defaultValue 'paginator' */ paginatorId = input('paginator'); /** * Maximum number of visible numeric page items (excluding arrows and ellipses). * This controls the window of page numbers shown around the current page. * @defaultValue 4 */ maxSize = input(4); /** * Pagination display variant * @defaultValue 'default' */ type = input('default'); /** * Event emitted when the current page changes */ pageChanged = output(); /** * Total number of pages * @defaultValue 0 */ pages = input(0); /** * Current active page (1-based) * @defaultValue 1 */ currentPage = model(1); /** * @internal * Left arrow icon reference */ iconArrowLeft = matKeyboardArrowLeftOutline; /** * @internal * Double left arrow icon reference */ iconDoubleArrowLeft = matKeyboardDoubleArrowLeftOutline; /** * @internal * Right arrow icon reference */ iconArrowRight = matKeyboardArrowRightOutline; /** * @internal * Double right arrow icon reference */ iconDoubleArrowRight = matKeyboardDoubleArrowRightOutline; /** * @internal * Computed array of page numbers and ellipses for display. * Implements the logic to show a limited range of pages around the current page * with '...' for skipped pages. */ pageNumbersForDisplay = computed(() => { const totalPages = this.pages(); const currentPage = this.currentPage(); const maxSize = this.maxSize(); // Number of visible numeric page links const pages = []; if (totalPages <= 1) { return []; } let startRange; let endRange; // Calculate the range of pages to display around the current page if (totalPages <= maxSize) { // If total pages are less than or equal to maxSize, show all pages startRange = 1; endRange = totalPages; } else { // Calculate the range to center the currentPage const half = Math.floor(maxSize / 2); startRange = currentPage - half; endRange = currentPage + (maxSize - 1 - half); // Adjust boundary conditions if (startRange < 1) { startRange = 1; endRange = maxSize; } if (endRange > totalPages) { endRange = totalPages; startRange = totalPages - maxSize + 1; } } // Add first page and leading ellipsis if necessary if (startRange > 1) { pages.push(1); if (startRange > 2) { // Only add ellipsis if there's at least one page between 1 and startRange pages.push('...'); } } // Add pages within the calculated range for (let i = startRange; i <= endRange; i++) { pages.push(i); } // Add trailing ellipsis and last page if necessary if (endRange < totalPages) { if (endRange < totalPages - 1) { // Only add ellipsis if there's at least one page between endRange and totalPages pages.push('...'); } // Ensure last page is only added once and if it's not already in the array if (!pages.includes(totalPages)) { pages.push(totalPages); } } // Final pass to remove potential duplicate adjacent '...' (e.g., if page 1, ..., 3, ..., 5 leads to 1, ..., ..., 5) const finalPages = []; for (let i = 0; i < pages.length; i++) { // Skip if current is '...' and previous was also '...' if (pages[i] === '...' && finalPages.length > 0 && finalPages[finalPages.length - 1] === '...') { continue; } finalPages.push(pages[i]); } return finalPages; }); /** * @internal * Handles page navigation changes * @param page - New page number (or '...' if clicked on ellipsis, which is ignored) */ handlePageChange(page) { // Updated signature to accept string if (typeof page !== 'number') { // Type guard: ignore clicks on '...' return; } const newPage = Math.min(Math.max(1, page), this.pages()); if (newPage !== this.currentPage()) { this.currentPage.set(newPage); this.pageChanged.emit(newPage); } } /** * @internal * Navigates to previous page if available */ previousPage() { this.handlePageChange(this.currentPage() - 1); } /** * @internal * Navigates to next page if available */ nextPage() { this.handlePageChange(this.currentPage() + 1); } /** * @internal * Jumps to first page */ firstPage() { this.handlePageChange(1); } /** * @internal * Jumps to last page */ lastPage() { this.handlePageChange(this.pages()); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: PaginatorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.0", type: PaginatorComponent, isStandalone: true, selector: "st-paginator", inputs: { paginatorId: { classPropertyName: "paginatorId", publicName: "paginatorId", isSignal: true, isRequired: false, transformFunction: null }, maxSize: { classPropertyName: "maxSize", publicName: "maxSize", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, pages: { classPropertyName: "pages", publicName: "pages", isSignal: true, isRequired: false, transformFunction: null }, currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageChanged: "pageChanged", currentPage: "currentPageChange" }, ngImport: i0, template: "@if (type() === 'default') {\n\t<div class=\"flex items-center justify-center gap-2\">\n\t\t<!-- First page button -->\n\t\t<st-pagination-item\n\t\t\t[icon]=\"iconDoubleArrowLeft\"\n\t\t\t(click)=\"firstPage()\"\n\t\t\tvariant=\"icon\"\n\t\t\tsize=\"md\"\n\t\t\tshape=\"round-xs\"\n\t\t\tpadding=\"none\"\n\t\t\t[disabled]=\"currentPage() === 1\"\n\t\t></st-pagination-item>\n\t\t<!-- Previous page button -->\n\t\t<st-pagination-item\n\t\t\t[icon]=\"iconArrowLeft\"\n\t\t\t(click)=\"previousPage()\"\n\t\t\tvariant=\"icon\"\n\t\t\tsize=\"md\"\n\t\t\tshape=\"round-xs\"\n\t\t\tpadding=\"none\"\n\t\t\t[disabled]=\"currentPage() === 1\"\n\t\t></st-pagination-item>\n\n\t\t@for (page of pageNumbersForDisplay(); track $index) {\n\t\t\t@if (page === '...') {\n\t\t\t\t<!-- Ellipsis for hidden pages -->\n\t\t\t\t<st-pagination-item variant=\"ellipsis\" size=\"md\" shape=\"round-xs\" padding=\"none\" [number]=\"'...'\"></st-pagination-item>\n\t\t\t} @else {\n\t\t\t\t<!-- Numeric page item -->\n\t\t\t\t<st-pagination-item\n\t\t\t\t\t(click)=\"handlePageChange(page)\"\n\t\t\t\t\tvariant=\"numeric\"\n\t\t\t\t\tsize=\"md\"\n\t\t\t\t\tshape=\"round-xs\"\n\t\t\t\t\tpadding=\"none\"\n\t\t\t\t\t[number]=\"page\"\n\t\t\t\t\t[active]=\"currentPage() === page\"\n\t\t\t\t></st-pagination-item>\n\t\t\t}\n\t\t}\n\n\t\t<!-- Next page button -->\n\t\t<st-pagination-item\n\t\t\t[icon]=\"iconArrowRight\"\n\t\t\t(click)=\"nextPage()\"\n\t\t\tvariant=\"icon\"\n\t\t\tsize=\"md\"\n\t\t\tshape=\"round-xs\"\n\t\t\tpadding=\"none\"\n\t\t\t[disabled]=\"currentPage() === pages()\"\n\t\t></st-pagination-item>\n\t\t<!-- Last page button -->\n\t\t<st-pagination-item\n\t\t\t[icon]=\"iconDoubleArrowRight\"\n\t\t\t(click)=\"lastPage()\"\n\t\t\tvariant=\"icon\"\n\t\t\tsize=\"md\"\n\t\t\tshape=\"round-xs\"\n\t\t\tpadding=\"none\"\n\t\t\t[disabled]=\"currentPage() === pages()\"\n\t\t></st-pagination-item>\n\t</div>\n}\n@if (type() === 'dots') {\n\t<div class=\"flex items-center justify-center gap-2\">\n\t\t@for (page of pageNumbersForDisplay(); track $index) {\n\t\t\t@if (page === '...') {\n\t\t\t\t<!-- Ellipsis for hidden pages -->\n\t\t\t\t<st-pagination-item variant=\"ellipsis\" size=\"xs\" shape=\"round-lg\" padding=\"none\" [number]=\"'...'\"></st-pagination-item>\n\t\t\t} @else {\n\t\t\t\t<st-pagination-item\n\t\t\t\t\t(click)=\"handlePageChange(page)\"\n\t\t\t\t\tvariant=\"dots\"\n\t\t\t\t\tsize=\"xs\"\n\t\t\t\t\tshape=\"round-lg\"\n\t\t\t\t\tpadding=\"none\"\n\t\t\t\t\t[active]=\"currentPage() === page\"\n\t\t\t\t></st-pagination-item>\n\t\t\t}\n\t\t}\n\t</div>\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: PaginationItemComponent, selector: "st-pagination-item", inputs: ["variant", "icon", "number", "active", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: PaginatorComponent, decorators: [{ type: Component, args: [{ selector: 'st-paginator', imports: [CommonModule, PaginationItemComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (type() === 'default') {\n\t<div class=\"flex items-center justify-center gap-2\">\n\t\t<!-- First page button -->\n\t\t<st-pagination-item\n\t\t\t[icon]=\"iconDoubleArrowLeft\"\n\t\t\t(click)=\"firstPage()\"\n\t\t\tvariant=\"icon\"\n\t\t\tsize=\"md\"\n\t\t\tshape=\"round-xs\"\n\t\t\tpadding=\"none\"\n\t\t\t[disabled]=\"currentPage() === 1\"\n\t\t></st-pagination-item>\n\t\t<!-- Previous page button -->\n\t\t<st-pagination-item\n\t\t\t[icon]=\"iconArrowLeft\"\n\t\t\t(click)=\"previousPage()\"\n\t\t\tvariant=\"icon\"\n\t\t\tsize=\"md\"\n\t\t\tshape=\"round-xs\"\n\t\t\tpadding=\"none\"\n\t\t\t[disabled]=\"currentPage() === 1\"\n\t\t></st-pagination-item>\n\n\t\t@for (page of pageNumbersForDisplay(); track $index) {\n\t\t\t@if (page === '...') {\n\t\t\t\t<!-- Ellipsis for hidden pages -->\n\t\t\t\t<st-pagination-item variant=\"ellipsis\" size=\"md\" shape=\"round-xs\" padding=\"none\" [number]=\"'...'\"></st-pagination-item>\n\t\t\t} @else {\n\t\t\t\t<!-- Numeric page item -->\n\t\t\t\t<st-pagination-item\n\t\t\t\t\t(click)=\"handlePageChange(page)\"\n\t\t\t\t\tvariant=\"numeric\"\n\t\t\t\t\tsize=\"md\"\n\t\t\t\t\tshape=\"round-xs\"\n\t\t\t\t\tpadding=\"none\"\n\t\t\t\t\t[number]=\"page\"\n\t\t\t\t\t[active]=\"currentPage() === page\"\n\t\t\t\t></st-pagination-item>\n\t\t\t}\n\t\t}\n\n\t\t<!-- Next page button -->\n\t\t<st-pagination-item\n\t\t\t[icon]=\"iconArrowRight\"\n\t\t\t(click)=\"nextPage()\"\n\t\t\tvariant=\"icon\"\n\t\t\tsize=\"md\"\n\t\t\tshape=\"round-xs\"\n\t\t\tpadding=\"none\"\n\t\t\t[disabled]=\"currentPage() === pages()\"\n\t\t></st-pagination-item>\n\t\t<!-- Last page button -->\n\t\t<st-pagination-item\n\t\t\t[icon]=\"iconDoubleArrowRight\"\n\t\t\t(click)=\"lastPage()\"\n\t\t\tvariant=\"icon\"\n\t\t\tsize=\"md\"\n\t\t\tshape=\"round-xs\"\n\t\t\tpadding=\"none\"\n\t\t\t[disabled]=\"currentPage() === pages()\"\n\t\t></st-pagination-item>\n\t</div>\n}\n@if (type() === 'dots') {\n\t<div class=\"flex items-center justify-center gap-2\">\n\t\t@for (page of pageNumbersForDisplay(); track $index) {\n\t\t\t@if (page === '...') {\n\t\t\t\t<!-- Ellipsis for hidden pages -->\n\t\t\t\t<st-pagination-item variant=\"ellipsis\" size=\"xs\" shape=\"round-lg\" padding=\"none\" [number]=\"'...'\"></st-pagination-item>\n\t\t\t} @else {\n\t\t\t\t<st-pagination-item\n\t\t\t\t\t(click)=\"handlePageChange(page)\"\n\t\t\t\t\tvariant=\"dots\"\n\t\t\t\t\tsize=\"xs\"\n\t\t\t\t\tshape=\"round-lg\"\n\t\t\t\t\tpadding=\"none\"\n\t\t\t\t\t[active]=\"currentPage() === page\"\n\t\t\t\t></st-pagination-item>\n\t\t\t}\n\t\t}\n\t</div>\n}\n" }] }] }); /** * Generated bundle index. Do not edit. */ export { PaginationItemComponent, PaginationPipe, PaginatorComponent, paginationItemComponent }; //# sourceMappingURL=sixbell-telco-sdk-components-paginator.mjs.map