UNPKG

@kloudlite/design-system

Version:

A design system for building ambitious products.

673 lines (667 loc) 23 kB
// components/molecule/pagination.tsx import * as RovingFocusGroup from "@radix-ui/react-roving-focus"; import { useEffect, useId, useRef, useState } from "react"; // components/icons.tsx import { BellSimple, Warning, WarningCircleFill, Domain, ArrowLeftLg, ArrowRightLg, ArrowUpLg, ArrowDownLg, ArrowsDownUp, Plus, Trash, PencilLine, PencilSimple, GithubLogoFill, GitlabLogoFill, GitBranchFill, Users, Check, ChevronLeft, ChevronRight, X, SmileySad, InfoFill, CheckCircleFill, WarningFill, WarningOctagonFill, LockSimple, XCircleFill, LockSimpleOpen, MinusCircle, Search, ArrowsCounterClockwise, ArrowClockwise, Copy, GearSix, QrCode, WireGuardlogo, ChevronUpDown, ChevronDown, Buildings, Project, InfraAsCode, Container, File, TreeStructure, CirclesFour, BackingServices, VirtualMachine, Database, ArrowsClockwise, Info, Fan, WarningCircle, ChecksFill, CircleNotch, Circle, CircleFill, Spinner, Globe, ShieldCheck, NoOps, Nodeless, GitMerge, PencilLine as PencilLine2, AWSlogoFill, GoogleCloudlogo, ArrowCounterClockwise, CopySimple, RecordFill, CheckCircle, ArrowLeftLg as ArrowLeftLg2, EyeSlash, Eye, CaretUpFill, CaretDownFill, XFill, HamburgerFill, CalendarCheckFill, GearFill, EnvelopeSimple } from "@jengaicons/react"; import { jsx } from "react/jsx-runtime"; // components/atoms/button.tsx import { AnimatePresence, motion } from "framer-motion"; import React from "react"; // components/utils.tsx import classNames from "classnames"; import { useMemo } from "react"; import { v4 } from "uuid"; var cn = (...props) => { return classNames(...props); }; // components/atoms/button.tsx import { jsx as jsx2, jsxs } from "react/jsx-runtime"; var ButtonBase = React.forwardRef((props, ref) => { const { onClick = () => { }, to = "", linkComponent = motion.button, disabled = false, suffix, prefix, block = false, type = "button", variant = "primary", // noRing, noRounded = false, noBorder = false, sharpLeft = false, sharpRight = false, selected = false, iconOnly = false, className = "", content, size = "md", loading = false, tabIndex, toLabel = "to", target, iconSize, ...mprops } = props; let Component = linkComponent; let tempToLabel = toLabel; let extraProps = {}; if (to) { if (linkComponent === motion.button) { Component = motion.a; tempToLabel = "href"; } else { Component = linkComponent; } } if (Component === motion.button || Component === motion.a) { extraProps = { initial: { scale: 1 }, whileTap: { scale: 0.99 } }; } const noRing = false; return /* @__PURE__ */ jsxs( Component, { ...mprops, ...{ [tempToLabel]: to }, disabled, onClick, ...extraProps, ref, type, tabIndex, target, className: cn( "pulsable kl-flex-nowrap", { "kl-w-full": !!block, "kl-w-fit": !block, selected }, { "kl-pointer-events-none": loading }, { "kl-bodyMd-medium": !variant?.includes("plain"), "kl-bodyMd": variant?.includes("plain") }, { "kl-pointer-events-none !kl-text-text-disabled kl-bg-surface-basic-disabled": disabled, "!kl-border-border-disabled": disabled && ![ "plain", "primary-plain", "critical-plain", "secondary-plain" ].includes(variant) }, "kl-relative kl-ring-offset-1", "kl-outline-none", "kl-flex kl-flex-row kl-gap-lg kl-items-center kl-justify-center", "disabled:kl-text-text-disabled disabled:kl-bg-surface-basic-disabled", { // noRing "focus-visible:kl-ring-2 focus:kl-ring-border-focus focus:kl-z-10": !noRing }, { ...!noRounded && { "kl-rounded-none": sharpLeft && sharpRight, "kl-rounded-r": sharpLeft && !sharpRight, "kl-rounded-l": !sharpLeft && sharpRight, "kl-rounded": !sharpLeft && !sharpRight } }, "disabled:kl-pointer-events-none", { "kl-border-none": noBorder, ...!noBorder && { "kl-border-border-default disabled:kl-border-border-disabled": variant === "basic" || variant === "outline" || variant === "secondary-outline", "kl-border-border-primary disabled:kl-border-border-disabled": variant === "primary" || variant === "primary-outline", "kl-border-border-secondary disabled:kl-border-border-disabled": variant === "secondary", "kl-border-border-critical disabled:kl-border-border-disabled": variant === "critical-outline" || variant === "critical", "kl-border-border-purple": variant === "purple", "kl-border-border-warning": variant === "warning", "kl-border-border-tertiary": variant === "tertiary", "kl-border-none": variant === "plain" || variant === "primary-plain" || variant === "critical-plain" || variant === "secondary-plain", "kl-border": !(variant === "plain" || variant === "primary-plain" || variant === "critical-plain" || variant === "secondary-plain") } }, !disabled ? { "kl-bg-surface-basic-default hover:kl-bg-surface-basic-hovered active:kl-bg-surface-basic-pressed disabled:kl-bg-surface-basic-default": variant === "basic" && !selected, "kl-bg-surface-basic-pressed hover:kl-bg-surface-basic-pressed active:kl-bg-surface-basic-pressed disabled:kl-bg-surface-basic-default": variant === "basic" && selected, "kl-bg-surface-primary-default hover:kl-bg-surface-primary-hovered active:kl-bg-surface-primary-pressed disabled:kl-bg-surface-basic-default": variant === "primary", "kl-bg-surface-secondary-default hover:kl-bg-surface-secondary-hovered active:kl-bg-surface-secondary-pressed disabled:kl-bg-surface-basic-default": variant === "secondary", "kl-bg-surface-critical-default hover:kl-bg-surface-critical-hovered active:kl-bg-surface-critical-pressed disabled:kl-bg-surface-basic-default": variant === "critical", "kl-bg-none hover:kl-bg-surface-critical-subdued active:kl-bg-surface-critical-subdued": variant === "critical-outline", "kl-bg-none hover:kl-bg-surface-primary-subdued active:kl-bg-surface-primary-subdued": variant === "primary-outline", "kl-bg-none hover:kl-bg-surface-secondary-subdued active:kl-bg-surface-secondary-subdued": variant === "secondary-outline", "kl-bg-none hover:kl-bg-surface-basic-hovered active:kl-bg-surface-basic-pressed": variant === "outline", "kl-bg-surface-basic-pressed kl-shadow-none hover:kl-bg-surface-basic-hovered active:kl-bg-surface-basic-pressed hover:kl-shadow-button": variant === "outline" && selected, "kl-bg-none kl-shadow-none": (variant === "plain" || variant === "primary-plain" || variant === "secondary-plain" || variant === "critical-plain") && !iconOnly, "kl-shadow-none active:kl-shadow-button kl-bg-surface-basic-pressed": variant === "plain" && !iconOnly && selected, "kl-bg-none kl-shadow-none hover:kl-bg-surface-basic-hovered active:kl-bg-surface-basic-pressed active:kl-shadow-button active:kl-shadow-button": variant === "plain" && iconOnly, "kl-bg-surface-basic-pressed kl-shadow-none hover:kl-bg-surface-basic-hovered active:kl-bg-surface-basic-pressed active:kl-shadow-button": variant === "plain" && iconOnly && selected, "kl-bg-surface-purple-default hover:kl-bg-surface-purple-hovered active:kl-bg-surface-purple-pressed": variant === "purple", "kl-bg-surface-tertiary-default hover:kl-bg-surface-tertiary-hovered active:kl-bg-surface-tertiary-pressed": variant === "tertiary", "kl-bg-surface-warning-default hover:kl-bg-surface-warning-hovered active:kl-bg-surface-warning-pressed": variant === "warning" } : {}, { "kl-text-text-default": variant === "basic" || variant === "plain" || variant === "outline", "kl-text-text-on-primary": variant === "primary" || variant === "critical" || variant === "secondary" || variant === "secondary-outline" || variant === "purple" || variant === "warning", "kl-text-text-critical": variant === "critical-outline" || variant === "critical-plain", "kl-text-text-primary": variant === "primary-outline" || variant === "primary-plain", "kl-text-text-secondary": variant === "secondary-plain", "kl-text-text-on-secondary": variant === "tertiary" }, { "focus:kl-underline": noRing }, { "hover:kl-underline": variant === "plain" || variant === "primary-plain" || variant === "critical-plain" || variant === "secondary-plain" }, { // icon ...!iconOnly && !(variant === "plain" || variant === "primary-plain" || variant === "critical-plain" || variant === "secondary-plain") && { "kl-py-md kl-px-lg": size === "sm", "kl-py-lg kl-px-2xl": size === "md", "kl-py-xl kl-px-4xl": size === "lg", "kl-py-2xl kl-px-6xl": size === "xl", "kl-py-2xl kl-px-9xl": size === "2xl" } }, { ...!iconOnly && (variant === "plain" || variant === "primary-plain" || variant === "critical-plain" || variant === "secondary-plain") && { "kl-px-md kl-py-sm": size === "sm", "kl-py-sm kl-px-md": size === "md", "kl-py-md kl-px-lg": size === "lg" } }, { "kl-p-lg !kl-h-[36px] !kl-w-[36px]": iconOnly && (size === "md" || size === "lg"), "kl-p-md": iconOnly && size === "sm", "kl-p-sm": iconOnly && size === "xs" }, className ), children: [ /* @__PURE__ */ jsx2(AnimatePresence, { children: loading && /* @__PURE__ */ jsx2( motion.span, { initial: { width: 0 }, animate: { width: "auto", paddingRight: 0 }, exit: { width: 0 }, className: cn( "kl-flex kl-items-center kl-justify-center kl-aspect-square kl-overflow-hidden" ), children: /* @__PURE__ */ jsx2("span", { className: cn("kl-animate-spin"), children: /* @__PURE__ */ jsx2(Spinner, { color: "currentColor", weight: 2, size: 18 }) }) } ) }), !!prefix && React.cloneElement(prefix, { size: iconSize || (iconOnly && size === "lg" ? 20 : 16), color: "currentColor" }), !iconOnly && /* @__PURE__ */ jsx2("span", { className: cn("kl-block kl-truncate"), children: content }), !!suffix && React.cloneElement(suffix, { size: iconSize || 16, color: "currentColor" }) ] } ); }); var IconButton = React.forwardRef( (props, ref) => { const { icon, block } = props; return /* @__PURE__ */ jsx2( ButtonBase, { ...props, ref, iconOnly: true, content: null, prefix: icon, block: !!block } ); } ); var Button = React.forwardRef( (props, ref) => { const { block } = props; return /* @__PURE__ */ jsx2(ButtonBase, { ...props, iconOnly: false, ref, block: !!block }); } ); // components/molecule/pagination.tsx import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime"; var usePagination = ({ items, itemsPerPage }) => { const [listItems, setListItems] = useState(items); const [page, setPage] = useState(); const [pageNumber, setPageNumber] = useState(1); const [hasNext, setHasNext] = useState(false); const [hasPrevious, setHasPrevious] = useState(false); useEffect(() => { if (listItems.length > 0) { let tempItems = listItems.slice( (pageNumber - 1) * itemsPerPage, pageNumber * itemsPerPage ); if (tempItems.length === 0) { tempItems = listItems.slice( (Math.ceil(listItems.length / itemsPerPage) - 1) * itemsPerPage, listItems.length ); setPageNumber((prev) => prev - 1); } setPage(tempItems); } else { setPageNumber(1); } }, [listItems]); useEffect(() => { if (pageNumber * itemsPerPage >= listItems.length) { setHasNext(false); } else { setHasNext(true); } if (pageNumber * itemsPerPage > itemsPerPage) { setHasPrevious(true); } else { setHasPrevious(false); } }, [page]); const onNext = () => { if (pageNumber < Math.ceil(listItems.length / itemsPerPage)) { setPage( listItems.slice( pageNumber * itemsPerPage, (pageNumber + 1) * itemsPerPage ) ); setPageNumber((prev) => prev + 1); } }; const onPrev = () => { if (pageNumber > 1) { setPage( listItems.slice( (pageNumber - 1 - 1) * itemsPerPage, (pageNumber - 1) * itemsPerPage ) ); setPageNumber((prev) => prev - 1); } }; const onPageChange = () => { }; const setPageNumberExt = (extpage) => { if (extpage <= Math.ceil(listItems.length / itemsPerPage)) { setPage( listItems.slice( (extpage - 1) * itemsPerPage, extpage * itemsPerPage ) ); setPageNumber(extpage); } }; return { page: page || [], pageNumber, hasNext, hasPrevious, onNext, onPrev, onPageChange, setPageNumber: setPageNumberExt, setItems: setListItems, items: listItems, itemsPerPage }; }; var ITEMS_PER_PAGE = [ 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100 ]; var Pagination = ({ currentPage = 3, totalItems = 90, itemsPerPage = 15, onPageChanged = () => { }, onItemsPerPageChanged = () => { }, disabled = false, itemPerPageDisabled = false, onClickNext = () => { }, onClickPrev = () => { }, isNextDisabled = false, isPrevDisabled = false, showNumbers = true, showItemsPerPage = true }) => { const [focusItem, setFocusItem] = useState(null); const [focusCallback, setFocusCallback] = useState(false); const [itemsPerPageValue, setItemsPerPageValue] = useState(itemsPerPage); const [startPages, setStartPages] = useState([]); const [middlePages, setMiddlePages] = useState([]); const [endPages, setEndPages] = useState([]); const totalPages = Math.ceil(totalItems / itemsPerPage); const itemsPerPageId = useId(); const ref = useRef(null); useEffect(() => { if (onItemsPerPageChanged) onItemsPerPageChanged(itemsPerPageValue); }, [itemsPerPageValue]); useEffect(() => { if (currentPage > totalPages) { onPageChanged(totalPages); } if (totalPages < 7) { setStartPages( new Array(totalPages).fill(0).map((value, index) => index + 1) ); setMiddlePages([]); setEndPages([]); return; } if (currentPage <= 3) { setStartPages([1, 2, 3, 4]); setMiddlePages([]); setEndPages([totalPages]); } else if (currentPage > 3 && currentPage <= totalPages - 3) { setStartPages([1]); setMiddlePages([currentPage - 1, currentPage, currentPage + 1]); setEndPages([totalPages]); } else { setStartPages([1]); setMiddlePages([]); setEndPages([totalPages - 3, totalPages - 2, totalPages - 1, totalPages]); } setFocusCallback(true); }, [currentPage, itemsPerPage, totalItems]); useEffect(() => { if (focusCallback) { if (focusItem && ref?.current?.children) { const itemsArray = Array.from( ref.current?.children ); if (itemsArray.find((e) => e.value === `${focusItem}`)) { itemsArray?.find((e) => e.value === `${focusItem}`)?.focus(); } else { const divElement = itemsArray?.find( (e) => e.tagName.toLowerCase() === "div" ); if (divElement) { const divElementArray = Array.from( divElement.children ); if (divElementArray) { divElementArray.find((e) => e.value === `${focusItem}`)?.focus(); } } } } } setFocusItem(null); setFocusCallback(false); }, [focusCallback]); const restoreFocus = (index) => { setFocusItem(index); }; return /* @__PURE__ */ jsxs2( "div", { className: cn( "kl-flex kl-flex-row kl-items-center kl-gap-3xl kl-w-full", { "kl-justify-end": !showItemsPerPage } ), children: [ showItemsPerPage && /* @__PURE__ */ jsxs2("div", { className: "kl-flex kl-flex-row kl-items-center kl-flex-1 kl-gap-lg kl-text-icon-default kl-bodyMd", children: [ /* @__PURE__ */ jsx3("label", { htmlFor: itemsPerPageId, children: "Item per page" }), /* @__PURE__ */ jsx3( "select", { name: "itemperpage", id: itemsPerPageId, disabled: itemPerPageDisabled, value: itemsPerPageValue, onChange: (e) => { setItemsPerPageValue(Number(e.target.value)); }, className: cn( "kl-py-md kl-pl-lg kl-pr-5xl kl-text-text-default kl-border-border-default kl-bg-surface-basic-input kl-transition-all kl-rounded kl-border kl-flex kl-flex-row kl-items-center kl-relative kl-outline-none disabled:kl-bg-surface-basic-input disabled:kl-text-text-disabled kl-ring-offset-1 focus-within:kl-ring-2 focus-within:kl-ring-border-focus kl-appearance-none", { "kl-text-text-disabled kl-border-border-disabled kl-bg-surface-basic-input": disabled } ), children: ITEMS_PER_PAGE.map((ipp) => /* @__PURE__ */ jsx3("option", { value: ipp, children: ipp }, ipp)) } ), showNumbers ? /* @__PURE__ */ jsxs2("span", { children: [ currentPage * itemsPerPage - itemsPerPage + 1, " -", " ", currentPage * itemsPerPage, " of ", totalItems, " items" ] }) : /* @__PURE__ */ jsxs2("span", { children: [ " total ", totalItems, " items" ] }) ] }), /* @__PURE__ */ jsx3(RovingFocusGroup.Root, { loop: true, children: /* @__PURE__ */ jsxs2("div", { className: "kl-flex kl-flex-row kl-items-center kl-gap-xl", children: [ /* @__PURE__ */ jsx3(RovingFocusGroup.Item, { asChild: true, focusable: true, children: /* @__PURE__ */ jsx3( Button, { content: "Previous", prefix: /* @__PURE__ */ jsx3(ChevronLeft, {}), variant: "plain", onClick: () => { if (onPageChanged) { onPageChanged(currentPage - 1); } onClickPrev(); }, disabled: showNumbers && currentPage < 2 || disabled || isPrevDisabled } ) }), showNumbers && /* @__PURE__ */ jsxs2( "div", { className: "kl-flex kl-flex-row kl-items-center kl-gap-lg", ref, children: [ startPages.map((sP) => /* @__PURE__ */ jsx3(RovingFocusGroup.Item, { asChild: true, focusable: true, children: /* @__PURE__ */ jsx3( Button, { content: sP.toString().padStart(2, "0"), variant: "plain", selected: sP === currentPage, onClick: () => onPageChanged && onPageChanged(sP), disabled, value: sP, onKeyDown: (e) => { if (e.key === " " || e.key === "Enter") { restoreFocus(sP); } } } ) }, sP)), /* @__PURE__ */ jsxs2("div", { className: "kl-flex kl-flex-row kl-items-center kl-gap-lg", children: [ middlePages.length > 0 && /* @__PURE__ */ jsx3("span", { className: "kl-bodyMd kl-text-text-default", children: "....." }), middlePages.map((mP) => /* @__PURE__ */ jsx3(RovingFocusGroup.Item, { asChild: true, focusable: true, children: /* @__PURE__ */ jsx3( Button, { content: mP.toString().padStart(2, "0"), variant: "plain", selected: mP === currentPage, onClick: () => onPageChanged && onPageChanged(mP), disabled, value: mP, onKeyDown: (e) => { if (e.key === " " || e.key === "Enter") { restoreFocus(mP); } } }, mP ) }, mP)), totalPages >= 7 && /* @__PURE__ */ jsx3("span", { className: "kl-bodyMd kl-text-text-default", children: "....." }) ] }), endPages.map((eP) => /* @__PURE__ */ jsx3(RovingFocusGroup.Item, { asChild: true, focusable: true, children: /* @__PURE__ */ jsx3( Button, { content: eP.toString().padStart(2, "0"), variant: "plain", selected: eP === currentPage, onClick: () => onPageChanged && onPageChanged(eP), disabled, value: eP, onKeyDown: (e) => { if (e.key === " " || e.key === "Enter") { restoreFocus(eP); } } }, eP ) }, eP)) ] } ), /* @__PURE__ */ jsx3(RovingFocusGroup.Item, { asChild: true, focusable: true, children: /* @__PURE__ */ jsx3( Button, { content: "Next", suffix: /* @__PURE__ */ jsx3(ChevronRight, {}), variant: "plain", onClick: () => { if (onPageChanged) { onPageChanged(currentPage + 1); } onClickNext(); }, disabled: showNumbers && currentPage >= totalPages || disabled || isNextDisabled } ) }) ] }) }) ] } ); }; var pagination_default = Pagination; export { pagination_default as default, usePagination };