import { forwardRef, useState, useRef, useEffect, useCallback } from 'react' import { Filter, Search, ListFilter, X } from 'lucide-react' import { DataTableHeaderProps } from './types' import { Button } from '../../Button' import { TextInput } from '../../Inputs/TextInput' import { ButtonSize, ButtonType } from '../../Button/types' import Block from '../../Primitives/Block/Block' import PrimitiveText from '../../Primitives/PrimitiveText/PrimitiveText' import PrimitiveButton from '../../Primitives/PrimitiveButton/PrimitiveButton' import { FOUNDATION_THEME } from '../../../tokens' import { TableTokenType } from '../dataTable.tokens' import { Popover } from '../../Popover' import { PopoverSize } from '../../Popover/types' import { useMobileDataTable } from '../hooks/useMobileDataTable' import { Drawer, DrawerPortal, DrawerOverlay, DrawerContent, DrawerBody, } from '../../Drawer' import { useResponsiveTokens } from '../../../hooks/useResponsiveTokens' import Tooltip from '../../Tooltip/Tooltip' import { TooltipSize, TooltipSide, TooltipAlign } from '../../Tooltip/types' const SearchCloseButton = ({ onClose, tableToken, }: { onClose: () => void tableToken: TableTokenType }) => ( ) const MobileSearchInput = ({ searchPlaceholder, searchConfig, onSearch, onClose, tableToken, }: { searchPlaceholder: string searchConfig: { query: string } onSearch: (query: string) => void onClose: () => void tableToken: TableTokenType }) => ( ) => onSearch(event.target.value) } onBlur={() => { if (!searchConfig.query.trim()) { onClose() } }} onKeyDown={(event: React.KeyboardEvent) => { if (event.key === 'Escape') { onClose() } }} rightSlot={ } autoFocus /> ) const TruncatedTextWithTooltip = ({ children, tooltipProps, ...textProps }: { // This component is only used for the `description` prop in this file. // `description` is a string, so we keep the typing narrow to avoid ReactNode/bigint mismatches. children: string tooltipProps?: { side?: TooltipSide align?: TooltipAlign size?: TooltipSize showArrow?: boolean delayDuration?: number offset?: number } [key: string]: unknown }) => { const wrapperRef = useRef(null) const [isTruncated, setIsTruncated] = useState(false) const checkTruncation = useCallback(() => { const wrapper = wrapperRef.current if (!wrapper) return const textElement = (wrapper.querySelector('p') as HTMLElement) || (wrapper.firstElementChild as HTMLElement) if (!textElement) { setIsTruncated(false) return } if (textElement.offsetWidth === 0 || textElement.offsetHeight === 0) { setIsTruncated(false) return } const truncated = textElement.scrollWidth > textElement.clientWidth setIsTruncated(truncated) }, []) useEffect(() => { const wrapper = wrapperRef.current if (!wrapper) return const timeoutId = setTimeout(checkTruncation, 0) const resizeObserver = new ResizeObserver(() => { requestAnimationFrame(checkTruncation) }) resizeObserver.observe(wrapper) const observeTextElement = () => { const textElement = wrapper.querySelector('p') as HTMLElement if (textElement) { resizeObserver.observe(textElement) } } const observeTimeoutId = setTimeout(observeTextElement, 100) window.addEventListener('resize', checkTruncation, { passive: true }) return () => { clearTimeout(timeoutId) clearTimeout(observeTimeoutId) resizeObserver.disconnect() window.removeEventListener('resize', checkTruncation) } }, [children, checkTruncation]) const handleMouseEnter = useCallback(() => { if (!isTruncated) { checkTruncation() } }, [isTruncated, checkTruncation]) const content = ( {children} ) if (isTruncated && children) { return ( {content} ) } return content } const DataTableHeader = forwardRef< HTMLDivElement, DataTableHeaderProps> >( ( { title, description, showHeader = true, showToolbar = true, enableSearch = false, searchPlaceholder = 'Search...', searchConfig, enableAdvancedFilter = false, advancedFilterComponent: AdvancedFilterComponent, advancedFilters = [], onSearch, onAdvancedFiltersChange, onClearAllFilters, headerSlot1, headerSlot2, headerSlot3, mobileToolbarSlot, descriptionTooltipProps, }, ref ) => { const tableToken = useResponsiveTokens('TABLE') const mobileConfig = useMobileDataTable() const [isDrawerOpen, setIsDrawerOpen] = useState(false) const [isSearchOpen, setIsSearchOpen] = useState(false) // Don't render anything if showHeader is false if (!showHeader) { return null } const hasToolbarContent = showToolbar && (enableSearch || enableAdvancedFilter || headerSlot1 || headerSlot2 || headerSlot3 || mobileToolbarSlot) if (!title && !description && !hasToolbarContent) { return null } if (mobileConfig.isMobile) { return ( {title && ( {title} )} {showToolbar && ( {enableSearch && isSearchOpen && ( setIsSearchOpen(false)} tableToken={tableToken} /> )} {enableSearch && !isSearchOpen && ( )} ) } const hasTitleOrDescription = Boolean(title || description) return ( {hasTitleOrDescription && ( {title && ( {title} )} {description && ( {description} )} )} {showToolbar && ( {(enableSearch || enableAdvancedFilter) && ( {enableSearch && ( ) => onSearch(event.target.value)} /> )} {enableAdvancedFilter && AdvancedFilterComponent && ( 0 ? ButtonType.PRIMARY : ButtonType.SECONDARY } leadingIcon={ )} )} {headerSlot1 && ( {headerSlot1} )} {headerSlot2 && ( {headerSlot2} )} {headerSlot3 && ( {headerSlot3} )} )} ) } ) DataTableHeader.displayName = 'DataTableHeader' export default DataTableHeader