import React, { forwardRef, useState } from "react"; import { ArrowsUpDownIcon, CaretLeftCircleFillIcon, CaretRightCircleFillIcon, SortDownIcon, SortUpIcon, } from "@navikt/aksel-icons"; import { cl } from "../../../utils/helpers"; import { useMergeRefs } from "../../../utils/hooks"; import { useDataTableContext } from "../root/DataTableRoot.context"; import { type ResizeProps, useTableColumnResize } from "./useTableColumnResize"; type SortDirection = "asc" | "desc" | "none"; interface DataTableThProps extends React.HTMLAttributes, ResizeProps { resizeHandler?: ( event: | React.MouseEvent | React.TouchEvent, ) => void; /** * Content alignment inside cell * @default "left" */ textAlign?: "left" | "center" | "right"; /** * Makes the column header sortable. The entire header cell content becomes * a clickable button when true. */ sortable?: boolean; /** * Current sort direction. Only relevant when `sortable` is true. * Uses values matching the `aria-sort` attribute directly. * @default "none" */ sortDirection?: SortDirection; /** * Called when the user clicks the sortable header. * The consumer is responsible for determining and setting the next sort state. */ onSortClick?: (event: React.MouseEvent) => void; render?: { filterMenu?: { title: string; content: React.ReactNode; }; }; /** * TODO: Shouldnt be needed to declare these here... But getting type-errors if not */ colSpan?: number; rowSpan?: number; /** * Temp hack to solve overflow and alignment */ UNSAFE_isSelection?: boolean; } const SORT_ICON: Record = { asc: SortUpIcon, desc: SortDownIcon, none: ArrowsUpDownIcon, }; /** * TODO: * - Plan for pinning: Move it into "settings" dialog like here: https://cloudscape.design/examples/react/table.html * - Keyboard-nav breaks in headers now because of the resize-handles. * Toggling `data-block-keyboard-nav` does not work since the created "grid" does not update when toggling this attribute. */ const DataTableTh = forwardRef( ( { className, children, sortable = false, sortDirection = "none", onSortClick, style, textAlign = "left", width, minWidth, maxWidth, onWidthChange, defaultWidth, colSpan, UNSAFE_isSelection, ...rest }, forwardedRef, ) => { const { withKeyboardNav } = useDataTableContext(); const [isOverflowing, setIsOverflowing] = React.useState(false); const contentRef = React.useRef(null); const [thRefState, setThRefState] = useState( null, ); const mergedRef = useMergeRefs(forwardedRef, setThRefState); const resizeResult = useTableColumnResize({ ref: thRefState, width, defaultWidth, minWidth, maxWidth, onWidthChange, style, colSpan, }); const SortIcon = sortable ? SORT_ICON[sortDirection] : null; return ( { const el = contentRef.current; setIsOverflowing(el ? el.scrollWidth > el.offsetWidth : false); console.info("is overflowing", isOverflowing); }} onPointerLeave={() => setIsOverflowing(false)} tabIndex={withKeyboardNav ? -1 : undefined} data-align={textAlign} colSpan={colSpan} > {sortable ? ( ) : (
{children}
)} {resizeResult.enabled && !UNSAFE_isSelection && ( )} ); }, ); function getAriaSort( sortDirection: SortDirection | undefined, ): "ascending" | "descending" | "none" | undefined { if (sortDirection === "asc") return "ascending"; if (sortDirection === "desc") return "descending"; if (sortDirection === "none") return "none"; return undefined; } export { DataTableTh }; export type { DataTableThProps };