import { LitElement, html, css } from 'lit'; import { property } from 'lit/decorators.js'; /** * Content item for navigation */ export interface ContentNavigationItem { title: string; href?: string; } /** * Event detail for navigate event */ export interface NavigateEventDetail { direction: 'previous' | 'next' | 'parent'; title: string; href?: string; } /** * Custom event dispatched when navigation occurs */ export type NavigateEvent = CustomEvent; /** * Props interface for ContentPagination component */ export interface ContentPaginationProps { /** * Previous content item */ previous?: ContentNavigationItem; /** * Next content item */ next?: ContentNavigationItem; /** * Parent/overview content item */ parent?: ContentNavigationItem; /** * Alternative aria-label for the navigation */ ariaLabel?: string; /** * Whether to display borders around navigation links */ bordered?: boolean; /** * Event callback fired when navigation occurs (for SPA routing) */ onNavigate?: (event: NavigateEvent) => void; } /** * ContentPagination component for navigating between content pages * * @fires {NavigateEvent} navigate - Fired when a navigation link is clicked * @csspart ag-content-pagination-container - The outer container element * @csspart ag-content-pagination-parent - The parent navigation item * @csspart ag-content-pagination-nav - The previous/next navigation container * @csspart ag-content-pagination-link - Individual navigation link/button * * @slot previous-icon - Icon for previous navigation (default: ←) * @slot next-icon - Icon for next navigation (default: →) * @slot parent-icon - Icon for parent navigation (default: ↑) * * @example * ```html * * ``` */ export class ContentPagination extends LitElement implements ContentPaginationProps { @property({ type: Object, attribute: false }) declare previous?: ContentNavigationItem; @property({ type: Object, attribute: false }) declare next?: ContentNavigationItem; @property({ type: Object, attribute: false }) declare parent?: ContentNavigationItem; @property({ type: String, attribute: 'aria-label' }) declare ariaLabel: string; @property({ type: Boolean, reflect: true }) declare bordered: boolean; @property({ attribute: false }) declare onNavigate?: (event: NavigateEvent) => void; @property({ type: Boolean, reflect: true, attribute: 'has-parent' }) private _hasParentAndChild = false; constructor() { super(); this.ariaLabel = 'content navigation'; this.bordered = false; } willUpdate(changedProperties: Map) { if (changedProperties.has('parent') || changedProperties.has('previous') || changedProperties.has('next')) { this._hasParentAndChild = !!this.parent && (!!this.previous || !!this.next); } } private _renderChevronIcon(rotationClass = '') { return html``; } private _handleNavigate( direction: 'previous' | 'next' | 'parent', item: ContentNavigationItem | undefined, event: MouseEvent ) { if (!item) return; // safeguard for TypeScript & eslint // If href is provided and no custom handler, let browser handle it if (item.href && !this.onNavigate) { return; } if (this.onNavigate) { event.preventDefault(); } const navigateEvent = new CustomEvent('navigate', { detail: { direction, title: item.title, href: item.href, }, bubbles: true, composed: true, }); this.dispatchEvent(navigateEvent); if (this.onNavigate) { this.onNavigate(navigateEvent); } } static styles = css` :host { display: block; width: 100%; } .rotate-180 { transform: rotate(180deg); } .content-pagination-container { display: flex; flex-direction: column; gap: var(--ag-space-1); } .content-pagination-parent { display: flex; align-items: center; padding: var(--ag-space-2) var(--ag-space-5); border-radius: var(--ag-radius-md); } .content-pagination-parent-link { display: flex; align-items: center; gap: var(--ag-space-1); color: var(--ag-text-primary); text-decoration: none; font-size: var(--ag-font-size-sm); font-weight: 500; cursor: pointer; background: none; border: none; font-family: inherit; } .content-pagination-parent-link:hover { color: var(--ag-primary); } .content-pagination-parent-link:focus-visible { outline: var(--ag-focus-width) solid rgba(var(--ag-focus), 0.5); outline-offset: var(--ag-focus-offset); border-radius: var(--ag-radius-sm); } .content-pagination-nav { display: flex; justify-content: space-between; gap: var(--ag-space-4); padding-block-start: var(--ag-space-2); border-top: 0px solid transparent; } .content-pagination-nav-with-parent { border-top: 1px solid var(--ag-border); } .content-pagination-item { display: flex; flex: 0 1 auto; min-inline-size: 0; max-inline-size: 45%; } .content-pagination-item-previous { justify-content: flex-start; } .content-pagination-item-next { justify-content: flex-end; margin-inline-start: auto; } .content-pagination-link { display: flex; align-items: center; gap: var(--ag-space-2, 0.5rem); padding: var(--ag-space-3) var(--ag-space-4); border-radius: var(--ag-radius-md); background-color: var(--ag-background-primary); color: var(--ag-text-primary); text-decoration: none; font-size: var(--ag-font-size-base); font-weight: 500; cursor: pointer; transition: all 0.15s ease-in-out; font-family: inherit; min-width: 0; } :host([bordered]) .content-pagination-link { border: 1px solid var(--ag-border); } .content-pagination-link:hover { background-color: var(--ag-background-secondary); color: var(--ag-primary); } .content-pagination-link:focus-visible { outline: var(--ag-focus-width) solid rgba(var(--ag-focus), 0.5); outline-offset: var(--ag-focus-offset); } .content-pagination-link:active { transform: translateY(1px); } button.content-pagination-link { background-color: transparent; border: 0; } :host([bordered]) button.content-pagination-link { border: 1px solid var(--ag-border); } .content-pagination-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; } .content-pagination-icon { flex-shrink: 0; font-size: 1.25em; line-height: 1; } /* Responsive: stack on very small screens */ @media (max-width: 640px) { .content-pagination-nav { flex-direction: column; gap: var(--ag-space-3); } .content-pagination-item { max-inline-size: 100%; } .content-pagination-item-next { margin-inline-start: 0; } } @media (prefers-reduced-motion) { .content-pagination-link { transition: none; } .content-pagination-link:active { transform: none; } } `; render() { return html` `; } }