UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

247 lines (244 loc) 8.52 kB
'use client'; import { jsx, jsxs } from 'react/jsx-runtime'; import { cn } from '../../lib/utilsComprehensive.js'; import { ChevronsLeft, ChevronLeft, MoreHorizontal, ChevronRight, ChevronsRight } from 'lucide-react'; import React from 'react'; import '../../primitives/GlassCore.js'; import '../../primitives/glass/GlassAdvanced.js'; import { OptimizedGlassCore } from '../../primitives/OptimizedGlassCore.js'; import '../../primitives/glass/OptimizedGlassAdvanced.js'; import '../../primitives/MotionNative.js'; import { MotionFramer } from '../../primitives/motion/MotionFramer.js'; import { announceToScreenReader, createPaginationA11y } from '../../utils/a11y.js'; /** * GlassPagination component * A glassmorphism pagination component */ const GlassPagination = ({ currentPage, totalPages, onPageChange, maxPageButtons = 7, showFirstLast = true, showPrevNext = true, size = "md", className, disabled = false, loading = false, "aria-label": ariaLabel = "Pagination", announcePageChanges = true }) => { const getPageNumbers = () => { const pages = []; const halfMax = Math.floor(maxPageButtons / 2); if (totalPages <= maxPageButtons) { // Show all pages if total is less than max buttons for (let i = 1; i <= totalPages; i++) { pages.push(i); } } else { // Always show first page pages.push(1); // Calculate start and end of middle section let start = Math.max(2, currentPage - halfMax); let end = Math.min(totalPages - 1, currentPage + halfMax); // Adjust if we're near the beginning if (currentPage <= halfMax + 1) { end = Math.min(totalPages - 1, maxPageButtons - 1); } // Adjust if we're near the end else if (currentPage >= totalPages - halfMax) { start = Math.max(2, totalPages - maxPageButtons + 2); } // Add ellipsis after first page if needed if (start > 2) { pages.push("..."); } // Add middle pages for (let i = start; i <= end; i++) { pages.push(i); } // Add ellipsis before last page if needed if (end < totalPages - 1) { pages.push("..."); } // Always show last page if (totalPages > 1) { pages.push(totalPages); } } return pages; }; const handlePageChange = page => { if (page >= 1 && page <= totalPages && page !== currentPage && !disabled && !loading) { onPageChange(page); // Announce page change to screen readers if (announcePageChanges) { announceToScreenReader(`Moved to page ${page} of ${totalPages}`, "polite"); } } }; const pageNumbers = getPageNumbers(); // Ink highlight for active page number const pagesRef = React.useRef(null); const pageRefMap = React.useRef(new Map()); const [ink, setInk] = React.useState({ left: 0, width: 0 }); const updateInk = React.useCallback(() => { const activeEl = pageRefMap.current.get(currentPage); const container = pagesRef.current; if (!activeEl || !container) return; const cr = container.getBoundingClientRect(); const ar = activeEl.getBoundingClientRect(); setInk({ left: ar.left - cr.left, width: ar.width }); }, [currentPage]); const registerPageRef = (page, el) => { const map = pageRefMap.current; if (el) map.set(page, el);else map.delete(page); // Defer measure to next frame requestAnimationFrame(updateInk); }; React.useEffect(() => { updateInk(); const onResize = () => updateInk(); window.addEventListener("resize", onResize); return () => window.removeEventListener("resize", onResize); }, [updateInk]); return jsx("nav", { "data-glass-component": true, "aria-label": ariaLabel, role: "navigation", children: jsxs(OptimizedGlassCore, { intent: "neutral", elevation: "level2", intensity: "medium", depth: 2, tint: "neutral", border: "subtle", animation: "none", performanceMode: "medium", className: cn("inline-flex items-center glass-gap-1 glass-p-1 glass-backdrop-blur-md ring-1 ring-white/10 bg-white/5", disabled && "opacity-50 pointer-events-none", className), "aria-busy": loading || undefined, children: [showFirstLast && totalPages > 3 && jsx(GlassPaginationItem, { onClick: () => handlePageChange(1), disabled: currentPage === 1 || disabled || loading, size: size, ariaLabel: "First page", children: jsx(ChevronsLeft, { className: 'w-4 h-4' }) }), showPrevNext && jsx(GlassPaginationItem, { onClick: () => handlePageChange(currentPage - 1), disabled: currentPage === 1 || disabled || loading, size: size, ariaLabel: "Previous page", children: jsx(ChevronLeft, { className: 'w-4 h-4' }) }), jsxs("div", { ref: pagesRef, className: 'relative glass-inline-flex glass-items-center glass-gap-1', children: [jsx("div", { className: 'absolute bottom-0 h-0-5 glass-surface-primary transition-all duration-200', style: { left: ink.left, width: ink.width } }), pageNumbers.map((page, index) => jsx(React.Fragment, { children: page === "..." ? jsx(GlassPaginationItem, { disabled: true, size: size, children: jsx(MoreHorizontal, { className: 'w-4 h-4' }) }) : jsx(GlassPaginationItem, { isActive: page === currentPage, onClick: () => handlePageChange(page), disabled: disabled || loading, size: size, ariaLabel: `Page ${page}`, innerRef: el => registerPageRef(page, el), children: page }) }, index))] }), showPrevNext && jsx(GlassPaginationItem, { onClick: () => handlePageChange(currentPage + 1), disabled: currentPage === totalPages || disabled || loading, size: size, ariaLabel: "Next page", children: jsx(ChevronRight, { className: 'w-4 h-4' }) }), showFirstLast && totalPages > 3 && jsx(GlassPaginationItem, { onClick: () => handlePageChange(totalPages), disabled: currentPage === totalPages || disabled || loading, size: size, ariaLabel: "Last page", children: jsx(ChevronsRight, { className: 'w-4 h-4' }) }), loading && jsx("div", { className: "glass-ml-2", children: jsx("div", { className: 'w-4 h-4 glass-border-2 glass-border-white/30 glass-border-t-white/60 glass-radius-full animate-spin' }) })] }) }); }; /** * GlassPaginationItem component * Individual pagination item/button */ const GlassPaginationItem = ({ children, isActive = false, disabled = false, onClick, size = "md", className, ariaLabel, innerRef }) => { // Create accessibility attributes const a11yProps = createPaginationA11y({ label: ariaLabel, current: isActive, disabled }); const sizeClasses = { sm: "h-8 w-8 glass-text-sm", md: "h-10 w-10 glass-text-base", lg: "h-12 w-12 glass-text-lg" }; return jsx(MotionFramer, { preset: "none", children: jsx(OptimizedGlassCore, { intent: "neutral", elevation: isActive ? "level3" : "level1", intensity: "medium", depth: 2, tint: "neutral", border: "subtle", animation: "none", performanceMode: "medium", liftOnHover: true, press: true, ref: innerRef, className: cn("relative flex items-center justify-center font-medium glass-radius-md", "glass-backdrop-blur-md border border-white/20", "transition-all duration-200 glass-sheen", "focus:outline-none focus:ring-2 focus:ring-white/30 focus:ring-offset-2 focus:ring-offset-transparent", "disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:transform-none", sizeClasses[size], { "bg-black/40 glass-text-primary shadow-lg ring-1 ring-white/30 border-white/30": isActive, "bg-black/20 hover:bg-black/30 glass-text-primary/80 hover:glass-text-primary hover:-translate-y-0.5 border-white/20 hover:border-white/30": !isActive && !disabled }, className), onClick: onClick, ...a11yProps, children: children }) }); }; export { GlassPagination, GlassPaginationItem }; //# sourceMappingURL=GlassPagination.js.map