import React, { useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { ArrowDownIcon, ArrowUpIcon, CheckIcon, ChevronDownIcon, FunnelIcon, XMarkIcon } from '@heroicons/react/16/solid'; import type { IMutField, ISemanticType } from '../../interfaces'; import { cn } from '../../utils'; import DataTypeIcon from '../dataTypeIcon'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '../ui/dropdown-menu'; export type ISortDirection = 'ascending' | 'descending'; interface ColumnHeaderProps { field: IMutField; sort?: ISortDirection; filtered?: boolean; hideSemanticType?: boolean; semanticTypeOptions: { value: ISemanticType; label: string }[]; /** cycles descending → ascending → none; omitted when sorting is disabled */ onToggleSort?: () => void; onSort?: (sort: ISortDirection | null) => void; /** opens the filter dialog; omitted when filtering is disabled */ onFilter?: () => void; onClearFilter?: () => void; /** omitted when the semantic type is read-only */ onChangeSemanticType?: (semanticType: ISemanticType) => void; } const itemClassName = 'gap-2.5 text-[13px]'; function SemanticTypeItems(props: Pick) { const { field, semanticTypeOptions, onChangeSemanticType } = props; return ( <> {semanticTypeOptions.map((option) => ( onChangeSemanticType?.(option.value)}> {option.label} {option.value === field.semanticType && } ))} ); } export function ColumnHeader(props: ColumnHeaderProps) { const { field, sort, filtered, hideSemanticType, semanticTypeOptions, onToggleSort, onSort, onFilter, onClearFilter, onChangeSemanticType } = props; const { t } = useTranslation('translation', { keyPrefix: 'data_table' }); const name = field.basename || field.name || field.fid; const typeLabel = semanticTypeOptions.find((x) => x.value === field.semanticType)?.label ?? field.semanticType; const chipClassName = cn( 'inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-md transition-colors', field.analyticType === 'dimension' ? 'bg-dimension/[0.14]' : 'bg-measure/[0.14]' ); const canChangeType = !!onChangeSemanticType && !hideSemanticType; const hasMenu = !!(onSort || onFilter || canChangeType); // keeps focus inside the filter dialog instead of handing it back to the menu trigger const openingDialog = useRef(false); return (
{!hideSemanticType && (canChangeType ? ( {t('semantic_type')} ) : ( ))} {onToggleSort ? ( ) : ( {name} )} {sort === 'descending' && } {sort === 'ascending' && } {filtered && } {hasMenu && ( { if (openingDialog.current) { openingDialog.current = false; e.preventDefault(); } }} > {onSort && ( <> onSort('ascending')}> {t('sort_asc')} {sort === 'ascending' && } onSort('descending')}> {t('sort_desc')} {sort === 'descending' && } {sort && ( onSort(null)}> {t('clear_sort')} )} )} {onFilter && ( <> {onSort && } { openingDialog.current = true; onFilter(); }} > {t('filter')} {filtered && onClearFilter && ( {t('clear_filter')} )} )} {canChangeType && ( <> {(onSort || onFilter) && } {t('semantic_type')} )} )}
); }