UNPKG

@furystack/shades-common-components

Version:

Common UI components for FuryStack Shades

165 lines 7.42 kB
import { Shade, createComponent } from '@furystack/shades'; import { buildTransition, cssVariableTheme } from '../services/css-variable-theme.js'; import { paletteMainColors } from '../services/palette-css-vars.js'; /** * Generates the list of pagination items (page numbers and ellipsis markers). */ const getPaginationRange = (count, page, siblingCount, boundaryCount) => { const totalPageNumbers = boundaryCount * 2 + siblingCount * 2 + 3; // boundaries + siblings + current + 2 ellipses if (totalPageNumbers >= count) { return Array.from({ length: count }, (_, i) => ({ type: 'page', page: i + 1 })); } const leftSiblingIndex = Math.max(page - siblingCount, boundaryCount + 1); const rightSiblingIndex = Math.min(page + siblingCount, count - boundaryCount); const showLeftEllipsis = leftSiblingIndex > boundaryCount + 2; const showRightEllipsis = rightSiblingIndex < count - boundaryCount - 1; const items = []; // Left boundary pages for (let i = 1; i <= boundaryCount; i++) { items.push({ type: 'page', page: i }); } if (showLeftEllipsis) { items.push({ type: 'ellipsis', key: 'start-ellipsis' }); } else { // Fill in pages between boundary and sibling range for (let i = boundaryCount + 1; i < leftSiblingIndex; i++) { items.push({ type: 'page', page: i }); } } // Sibling pages + current page for (let i = leftSiblingIndex; i <= rightSiblingIndex; i++) { items.push({ type: 'page', page: i }); } if (showRightEllipsis) { items.push({ type: 'ellipsis', key: 'end-ellipsis' }); } else { // Fill in pages between sibling range and right boundary for (let i = rightSiblingIndex + 1; i <= count - boundaryCount; i++) { items.push({ type: 'page', page: i }); } } // Right boundary pages for (let i = count - boundaryCount + 1; i <= count; i++) { items.push({ type: 'page', page: i }); } return items; }; const defaultColors = { main: cssVariableTheme.text.primary, mainContrast: cssVariableTheme.background.default, }; export const Pagination = Shade({ customElementName: 'shade-pagination', css: { display: 'inline-flex', alignItems: 'center', gap: cssVariableTheme.spacing.xs, fontFamily: cssVariableTheme.typography.fontFamily, userSelect: 'none', '& .pagination-item': { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', border: 'none', borderRadius: cssVariableTheme.shape.borderRadius.md, background: 'transparent', color: cssVariableTheme.text.primary, cursor: 'pointer', fontFamily: 'inherit', fontWeight: cssVariableTheme.typography.fontWeight.medium, lineHeight: '1', padding: '0', transition: buildTransition(['background', cssVariableTheme.transitions.duration.fast, cssVariableTheme.transitions.easing.default], ['color', cssVariableTheme.transitions.duration.fast, cssVariableTheme.transitions.easing.default]), }, '& .pagination-item:hover:not([data-disabled]):not([data-selected])': { background: 'color-mix(in srgb, var(--pagination-color-main) 12%, transparent)', }, '& .pagination-item[data-selected]': { background: 'var(--pagination-color-main)', color: 'var(--pagination-color-contrast)', }, '& .pagination-item[data-disabled]': { opacity: '0.4', cursor: 'not-allowed', pointerEvents: 'none', }, '& .pagination-ellipsis': { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: cssVariableTheme.text.secondary, pointerEvents: 'none', letterSpacing: '2px', }, // Size: medium (default) '&:not([data-size]) .pagination-item, &[data-size="medium"] .pagination-item': { minWidth: '32px', height: '32px', fontSize: cssVariableTheme.typography.fontSize.sm, }, '&:not([data-size]) .pagination-ellipsis, &[data-size="medium"] .pagination-ellipsis': { minWidth: '32px', height: '32px', fontSize: cssVariableTheme.typography.fontSize.sm, }, // Size: small '&[data-size="small"] .pagination-item': { minWidth: '26px', height: '26px', fontSize: cssVariableTheme.typography.fontSize.xs, }, '&[data-size="small"] .pagination-ellipsis': { minWidth: '26px', height: '26px', fontSize: cssVariableTheme.typography.fontSize.xs, }, // Size: large '&[data-size="large"] .pagination-item': { minWidth: '40px', height: '40px', fontSize: cssVariableTheme.typography.fontSize.md, }, '&[data-size="large"] .pagination-ellipsis': { minWidth: '40px', height: '40px', fontSize: cssVariableTheme.typography.fontSize.md, }, // Disabled state on the host '&[data-disabled]': { opacity: cssVariableTheme.action.disabledOpacity, pointerEvents: 'none', }, }, render: ({ props, useHostProps }) => { const { count, page, onPageChange, siblingCount = 1, boundaryCount = 1, disabled, size, color, style } = props; const colors = color ? paletteMainColors[color] : defaultColors; useHostProps({ 'data-size': size || undefined, 'data-disabled': disabled ? '' : undefined, style: { '--pagination-color-main': colors.main, '--pagination-color-contrast': colors.mainContrast, ...style, }, }); const items = getPaginationRange(count, page, siblingCount, boundaryCount); const isPrevDisabled = disabled || page <= 1; const isNextDisabled = disabled || page >= count; return (createComponent(createComponent, null, createComponent("button", { className: "pagination-item", "aria-label": "Go to previous page", ...(isPrevDisabled ? { 'data-disabled': '' } : {}), onclick: () => { if (!isPrevDisabled) onPageChange(page - 1); } }, "\u2039"), items.map((item) => item.type === 'page' ? (createComponent("button", { className: "pagination-item", "aria-label": `Go to page ${item.page}`, ...(item.page === page ? { 'data-selected': '' } : {}), ...(disabled ? { 'data-disabled': '' } : {}), onclick: () => { if (!disabled && item.page !== page) onPageChange(item.page); } }, item.page.toString())) : (createComponent("span", { className: "pagination-ellipsis", "aria-hidden": "true" }, "\u2026"))), createComponent("button", { className: "pagination-item", "aria-label": "Go to next page", ...(isNextDisabled ? { 'data-disabled': '' } : {}), onclick: () => { if (!isNextDisabled) onPageChange(page + 1); } }, "\u203A"))); }, }); //# sourceMappingURL=pagination.js.map