UNPKG

analytica-frontend-lib

Version:

Repositório público dos componentes utilizados nas plataformas da Analytica Ensino

484 lines (482 loc) 15.1 kB
import { EmptyState_default } from "./chunk-6WVHF7N3.mjs"; import { SkeletonTable } from "./chunk-NKVUUWCA.mjs"; import { NoSearchResult_default } from "./chunk-ZBEW4SPB.mjs"; import { cn } from "./chunk-53ICLDGS.mjs"; // src/components/Table/Table.tsx import { forwardRef, useCallback, useState, useMemo, useEffect, useRef, Children, isValidElement } from "react"; import { ArrowUpIcon } from "@phosphor-icons/react/dist/csr/ArrowUp"; import { ArrowDownIcon } from "@phosphor-icons/react/dist/csr/ArrowDown"; import { jsx, jsxs } from "react/jsx-runtime"; var getUrlKeys = (prefix) => ({ sortBy: prefix ? `${prefix}_sortBy` : "sortBy", sortOrder: prefix ? `${prefix}_sortOrder` : "sortOrder", /** Contrato antigo (`sort=ASC`), ainda lido para não quebrar links salvos. */ legacySort: prefix ? `${prefix}_sort` : "sort" }); function useTableSort(data, options = {}) { const { syncWithUrl = false, mode = "client", urlKeyPrefix, allowedColumns, defaultSort, onSortChange } = options; const urlKeys = useMemo(() => getUrlKeys(urlKeyPrefix), [urlKeyPrefix]); const allowedColumnsRef = useRef(allowedColumns); allowedColumnsRef.current = allowedColumns; const onSortChangeRef = useRef(onSortChange); onSortChangeRef.current = onSortChange; const defaultSortRef = useRef(defaultSort); defaultSortRef.current = defaultSort; const readFromUrl = useCallback(() => { const fallback = defaultSortRef.current ? { column: defaultSortRef.current.sortBy, direction: defaultSortRef.current.sortOrder } : { column: null, direction: null }; if (!syncWithUrl || globalThis.window === void 0) { return fallback; } const params = new URLSearchParams(globalThis.location.search); const column = params.get(urlKeys.sortBy); if (!column) return fallback; const rawOrder = params.get(urlKeys.sortOrder) ?? params.get(urlKeys.legacySort); const direction = rawOrder?.toLowerCase(); if (direction !== "asc" && direction !== "desc") return fallback; const allowed = allowedColumnsRef.current; if (allowed && !allowed.includes(column)) return fallback; return { column, direction }; }, [syncWithUrl, urlKeys]); const [initialState] = useState(readFromUrl); const [sortColumn, setSortColumn] = useState( initialState.column ); const [sortDirection, setSortDirection] = useState( initialState.direction ); const hasWrittenUrlRef = useRef(false); useEffect(() => { if (!syncWithUrl || globalThis.window === void 0) return; const url = new URL(globalThis.location.href); const params = url.searchParams; if (sortColumn && sortDirection) { params.set(urlKeys.sortBy, sortColumn); params.set(urlKeys.sortOrder, sortDirection); params.delete(urlKeys.legacySort); hasWrittenUrlRef.current = true; } else if (hasWrittenUrlRef.current) { params.delete(urlKeys.sortBy); params.delete(urlKeys.sortOrder); params.delete(urlKeys.legacySort); } else { return; } globalThis.history.replaceState({}, "", url.toString()); }, [sortColumn, sortDirection, syncWithUrl, urlKeys]); useEffect(() => { if (!syncWithUrl || globalThis.window === void 0) return; const handlePopState = () => { const next = readFromUrl(); setSortColumn(next.column); setSortDirection(next.direction); }; globalThis.addEventListener("popstate", handlePopState); return () => globalThis.removeEventListener("popstate", handlePopState); }, [syncWithUrl, readFromUrl]); const handleSort = (column) => { if (sortColumn === column && sortDirection === "asc") { setSortDirection("desc"); } else if (sortColumn === column && sortDirection === "desc") { setSortColumn(null); setSortDirection(null); } else { setSortColumn(column); setSortDirection("asc"); } onSortChangeRef.current?.(); }; const sortedData = useMemo(() => { if (mode === "server" || !sortColumn || !sortDirection) { return data; } return [...data].sort((a, b) => { const aValue = a[sortColumn]; const bValue = b[sortColumn]; if (typeof aValue === "string" && typeof bValue === "string") { const comparison = aValue.localeCompare(bValue); return sortDirection === "asc" ? comparison : -comparison; } if (typeof aValue === "number" && typeof bValue === "number") { return sortDirection === "asc" ? aValue - bValue : bValue - aValue; } return 0; }); }, [data, sortColumn, sortDirection, mode]); return { sortedData, sortColumn, sortDirection, handleSort }; } var renderHeaderElements = (children) => { return Children.map(children, (child) => { if (isValidElement(child) && (child.type === TableCaption || child.type === TableHeader)) { return child; } return null; }); }; var getNoSearchResultContent = (config, defaultTitle, defaultDescription) => { if (config.component) { return config.component; } if (config.image) { return /* @__PURE__ */ jsx( NoSearchResult_default, { image: config.image, title: config.title || defaultTitle, description: config.description || defaultDescription } ); } return /* @__PURE__ */ jsxs("div", { className: "text-center", children: [ /* @__PURE__ */ jsx("p", { className: "text-text-600 text-lg font-semibold mb-2", children: config.title || defaultTitle }), /* @__PURE__ */ jsx("p", { className: "text-text-500 text-sm", children: config.description || defaultDescription }) ] }); }; var getEmptyStateContent = (config, defaultTitle, defaultDescription) => { if (config?.component) { return config.component; } return /* @__PURE__ */ jsx( EmptyState_default, { image: config?.image, title: config?.title || defaultTitle, description: config?.description || defaultDescription, buttonText: config?.buttonText, buttonIcon: config?.buttonIcon, onButtonClick: config?.onButtonClick, buttonVariant: config?.buttonVariant, buttonAction: config?.buttonAction } ); }; var renderTableWrapper = (variant, tableRef, className, children, stateContent, tableProps) => { return /* @__PURE__ */ jsxs( "div", { className: cn( "relative w-full overflow-x-auto", variant === "default" && "border border-border-200 rounded-xl" ), children: [ /* @__PURE__ */ jsx( "table", { ref: tableRef, className: cn( "analytica-table w-full caption-bottom text-sm border-separate border-spacing-0", className ), ...tableProps, children: renderHeaderElements(children) } ), /* @__PURE__ */ jsx("div", { className: "py-8 flex justify-center", children: stateContent }) ] } ); }; var Table = forwardRef( ({ variant = "default", className, children, showLoading = false, loadingState, showNoSearchResult = false, noSearchResultState, showEmpty = false, emptyState, ...props }, ref) => { const defaultNoSearchResultState = { title: "Nenhum resultado encontrado", description: "N\xE3o encontramos nenhum resultado com esse nome. Tente revisar a busca ou usar outra palavra-chave." }; const defaultEmptyState = { title: "Nenhum dado dispon\xEDvel", description: "N\xE3o h\xE1 dados para exibir no momento." }; const finalNoSearchResultState = noSearchResultState || defaultNoSearchResultState; const finalEmptyState = emptyState || defaultEmptyState; if (showLoading) { const loadingContent = loadingState?.component || /* @__PURE__ */ jsx(SkeletonTable, { rows: 5, columns: 4, showHeader: false }); return renderTableWrapper( variant, ref, className, children, loadingContent, props ); } if (showNoSearchResult) { const noSearchContent = getNoSearchResultContent( finalNoSearchResultState, defaultNoSearchResultState.title || "", defaultNoSearchResultState.description || "" ); return renderTableWrapper( variant, ref, className, children, noSearchContent, props ); } if (showEmpty) { const emptyContent = getEmptyStateContent( finalEmptyState, defaultEmptyState.title || "Nenhum dado dispon\xEDvel", defaultEmptyState.description || "N\xE3o h\xE1 dados para exibir no momento." ); return renderTableWrapper( variant, ref, className, children, emptyContent, props ); } return /* @__PURE__ */ jsx( "div", { className: cn( "relative w-full overflow-x-auto", variant === "default" && "border border-border-200 rounded-xl" ), children: /* @__PURE__ */ jsxs( "table", { ref, className: cn( variant === "default" && "analytica-table", variant === "default" && "border-separate border-spacing-0", "w-full caption-bottom text-sm", className ), ...props, children: [ !Children.toArray(children).some( (child) => isValidElement(child) && child.type === TableCaption ) && /* @__PURE__ */ jsx("caption", { className: "sr-only", children: "My Table" }), children ] } ) } ); } ); Table.displayName = "Table"; var TableHeader = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx( "thead", { ref, className: cn("[&_tr:first-child]:border-0", className), ...props } )); TableHeader.displayName = "TableHeader"; var TableBody = forwardRef( ({ className, variant = "default", ...props }, ref) => /* @__PURE__ */ jsx( "tbody", { ref, className: cn( "[&_tr:last-child]:border-border-200", variant === "default" && "border-t border-border-200", className ), ...props } ) ); TableBody.displayName = "TableBody"; var TableFooter = forwardRef( ({ variant = "default", className, ...props }, ref) => /* @__PURE__ */ jsx( "tfoot", { ref, className: cn( "bg-background-50 font-medium [&>tr]:last:border-b-0 px-6 py-3.5", variant === "default" && "border-t border-border-200", className ), ...props } ) ); TableFooter.displayName = "TableFooter"; var VARIANT_STATES_ROW = { default: { default: "border border-border-200", defaultBorderless: "border-b border-border-200", borderless: "" }, selected: { default: "border-b-2 border-indicator-primary", defaultBorderless: "border-b border-indicator-primary", borderless: "bg-indicator-primary/10" }, invalid: { default: "border-b-2 border-indicator-error", defaultBorderless: "border-b border-indicator-error", borderless: "bg-indicator-error/10" }, disabled: { default: "border-b border-border-100 bg-background-50 opacity-50 cursor-not-allowed", defaultBorderless: "border-b border-border-100 bg-background-50 opacity-50 cursor-not-allowed", borderless: "bg-background-50 opacity-50 cursor-not-allowed" } }; var TableRow = forwardRef( ({ variant = "default", state = "default", clickable = false, className, ...props }, ref) => { return /* @__PURE__ */ jsx( "tr", { ref, className: cn( "transition-colors", state === "disabled" ? "" : "hover:bg-muted/50", state === "disabled" || !clickable ? "" : "cursor-pointer", VARIANT_STATES_ROW[state][variant], className ), "aria-disabled": state === "disabled", ...props } ); } ); TableRow.displayName = "TableRow"; var TableHead = forwardRef( ({ className, sortable = false, sortDirection = null, onSort, filterSlot, children, ...props }, ref) => { const handleClick = () => { if (sortable && onSort) { onSort(); } }; const ariaSort = (() => { if (!sortable) return void 0; if (sortDirection === "asc") return "ascending"; if (sortDirection === "desc") return "descending"; return "none"; })(); const sortArrow = sortable && sortDirection && /* @__PURE__ */ jsx("span", { className: "flex shrink-0", children: sortDirection === "asc" ? /* @__PURE__ */ jsx(ArrowUpIcon, { size: 16, className: "text-text-800" }) : /* @__PURE__ */ jsx(ArrowDownIcon, { size: 16, className: "text-text-800" }) }); return /* @__PURE__ */ jsx( "th", { ref, className: cn( "h-10 px-6 py-3.5 text-left align-middle font-bold text-base text-text-800 tracking-[0.2px] leading-none [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] whitespace-nowrap", sortable && "cursor-pointer select-none hover:bg-muted/30", className ), "aria-sort": ariaSort, ...props, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [ sortable ? /* @__PURE__ */ jsxs( "button", { type: "button", onClick: handleClick, className: cn( "flex items-center gap-2 text-left cursor-pointer select-none hover:opacity-80", // Span the cell so pointer users keep a full-width target — but // not when a filter caret follows, or it would be shoved to the // far edge of the column instead of sitting next to the label. !filterSlot && "flex-1" ), children: [ children, sortArrow ] } ) : children, filterSlot ] }) } ); } ); TableHead.displayName = "TableHead"; var TableCell = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx( "td", { ref, className: cn( "p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] text-base font-normal text-text-800 leading-[150%] tracking-normal px-6 py-3.5 whitespace-nowrap", className ), ...props } )); TableCell.displayName = "TableCell"; var TableCaption = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx( "caption", { ref, className: cn( "border-t border-border-200 text-sm text-text-800 px-6 py-3.5", className ), ...props } )); TableCaption.displayName = "TableCaption"; var Table_default = Table; export { useTableSort, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption, Table_default }; //# sourceMappingURL=chunk-66F2QDAX.mjs.map