import type { ReactNode } from 'react';
import { useCallback } from 'react';
import type { GestureResponderHandlers } from 'react-native';
import { Pressable, TextInput, View } from 'react-native';
import { AddLine as Plus } from 'rn-motion-ui-icons/icons/add-line';
import { Delete2Line as Trash2 } from 'rn-motion-ui-icons/icons/delete-2-line';
import { DotsVerticalLine as GripVertical } from 'rn-motion-ui-icons/icons/dots-vertical-line';
import { UpLine as ChevronUp } from 'rn-motion-ui-icons/icons/up-line';
import { useIsRTL } from '../../../hooks/use-direction';
import { cn } from '../../../lib/cn';
import { MotiView } from '../../../moti/components/view';
import { useThemeColor } from '../../../theme/use-theme-color';
import { Text } from '../../typography/Text/text';
import type { SortDirection, TableColumn } from './table-types';
import { alignToJustifyClass, alignToTextClass, columnLayoutStyle } from './table-utils';
type SortIndicatorProps = {
isActive: boolean;
activeDirection: SortDirection | undefined;
reduce: boolean;
activeColor: string;
mutedColor: string;
sortIcon?: ReactNode;
};
function SortIndicator({ isActive, activeDirection, reduce, activeColor, mutedColor, sortIcon }: SortIndicatorProps) {
const rotate = isActive && activeDirection === 'desc' ? '180deg' : '0deg';
const opacity = isActive ? 1 : 0.35;
return (
{sortIcon ?? }
);
}
type ColumnActionOverlayProps = {
isRTL: boolean;
header: ReactNode;
insertColor: string;
onInsert?: () => void;
onDelete?: () => void;
};
function ColumnActionOverlay({ isRTL, header, insertColor, onInsert, onDelete }: ColumnActionOverlayProps) {
return (
{onInsert ? (
) : null}
{onDelete ? (
) : null}
);
}
type ReorderGripProps = {
isRTL: boolean;
columnKey: string;
mutedColor: string;
gripHandlers: (key: string) => GestureResponderHandlers;
testID?: string;
};
function ReorderGrip({ isRTL, columnKey, mutedColor, gripHandlers, testID }: ReorderGripProps) {
return (
);
}
export type HeaderCellProps = {
column: TableColumn;
colIndex: number;
/** Resolved pixel width for this column (from `computeColumnWidths`). */
colWidth?: number;
isActive: boolean;
activeDirection: SortDirection | undefined;
isColPressed: boolean;
isDragging: boolean;
reorderable: boolean;
hasColMenu: boolean;
reduce: boolean;
/**
* Effective sortability for this column — `table-level sortable && column.sortable`.
* When false the header renders as non-interactive even if `column.sortable` is true.
*/
sortEnabled: boolean;
gripHandlers: (key: string) => GestureResponderHandlers;
toggleSort: (key: string) => void;
setPressedColKey: (key: string | null) => void;
onColumnRename?: (key: string, value: string) => void;
onInsertColumn?: (index: number, position: 'before' | 'after') => void;
onDeleteColumn?: (key: string) => void;
testID?: string;
/** Replace the sort-direction chevron. Default: ``. */
sortIcon?: ReactNode;
};
// One column header. Sort/menu/rename callbacks are stable useCallbacks bound to
// this column so the header JSX never binds inline arrows.
export function HeaderCell({
column,
colIndex,
colWidth,
isActive,
activeDirection,
isColPressed,
isDragging,
reorderable,
hasColMenu,
reduce,
sortEnabled,
gripHandlers,
toggleSort,
setPressedColKey,
onColumnRename,
onInsertColumn,
onDeleteColumn,
testID,
sortIcon,
}: HeaderCellProps) {
const isRTL = useIsRTL();
const textAlignClass = alignToTextClass(column.align, isRTL);
const mutedForeground = useThemeColor('muted-foreground');
const foregroundForeground = useThemeColor('foreground');
const primaryForeground = useThemeColor('primary-foreground');
const handleLongPress = useCallback(() => {
if (hasColMenu) setPressedColKey(isColPressed ? null : column.key);
}, [hasColMenu, isColPressed, column.key, setPressedColKey]);
const handleSort = useCallback(() => toggleSort(column.key), [toggleSort, column.key]);
const handleRename = useCallback((v: string) => onColumnRename?.(column.key, v), [onColumnRename, column.key]);
const handleInsertColumn = useCallback(() => {
onInsertColumn?.(colIndex, 'before');
setPressedColKey(null);
}, [onInsertColumn, colIndex, setPressedColKey]);
const handleDeleteColumn = useCallback(() => {
onDeleteColumn?.(column.key);
setPressedColKey(null);
}, [onDeleteColumn, column.key, setPressedColKey]);
const dragOpacity = isDragging ? 0.5 : 1;
const dragScale = isDragging ? 1.04 : 1;
const dragTransition = { type: 'timing' as const, duration: reduce ? 0 : 180 };
return (
{/* Header content lifts (scale + fade) while its column is being dragged. */}
{/* Reorder grip — a PanResponder here claims the drag so it never sorts. */}
{reorderable ? (
) : null}
{onColumnRename && !sortEnabled ? (
) : (
{column.header}
{sortEnabled ? (
) : null}
)}
{/* Column action overlay on long-press, pinned to the trailing edge — the
left one under RTL, since `right` stays physical in both directions. */}
{isColPressed && hasColMenu ? (
) : null}
);
}