/* * Copyright 2022 Palantir Technologies, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import classNames from "classnames"; import { AbstractPureComponent, Utils as CoreUtils, DISPLAYNAME_PREFIX, Icon, type IconName, type OverlayLifecycleProps, Popover, type PopoverProps, type Props, } from "@blueprintjs/core"; import * as Classes from "../common/classes"; import { LoadableContent } from "../common/loadableContent"; import { CLASSNAME_EXCLUDED_FROM_TEXT_MEASUREMENT } from "../common/utils"; import { HeaderCell, type HeaderCellProps } from "./headerCell"; import { HorizontalCellDivider } from "./horizontalCellDivider"; export interface ColumnNameProps { /** * The name displayed in the header of the column. */ name?: string; /** * A callback to override the default name rendering behavior. The default * behavior is to simply use the `ColumnHeaderCell`s name prop. * * This render callback can be used, for example, to provide a * `EditableName` component for editing column names. * * If you define this callback, we recommend you also set * `` to avoid issues with menus or selection. * * The callback will also receive the column index if an `index` was originally * provided via props. */ nameRenderer?: (name: string, index?: number) => React.ReactElement; } export interface ColumnHeaderCellProps extends HeaderCellProps, ColumnNameProps { /** * If `true`, adds an interaction bar on top of all column header cells, and * moves interaction triggers into it. * * @default false */ enableColumnInteractionBar?: boolean; /** * Specifies if the column is reorderable. */ enableColumnReordering?: boolean; /** * Specifies if the full column is part of a selection. */ isColumnSelected?: boolean; /** * The icon name or element for the header's menu button. * * @default "chevron-down" */ menuIcon?: IconName | React.JSX.Element; /** * Optional props to forward to the dropdown menu popover. * This has no effect if `menuRenderer` is undefined. */ menuPopoverProps?: Omit; /** * If `true`, clicks on the header menu target element will cause the column's * cells to be selected. * * @default true */ selectCellsOnMenuClick?: boolean; } export interface ColumnHeaderCellState { isActive?: boolean; } /** * Column header cell component. * * @see https://blueprintjs.com/docs/#table/api.columnheadercell */ export class ColumnHeaderCell extends AbstractPureComponent { public static displayName = `${DISPLAYNAME_PREFIX}.ColumnHeaderCell`; public static defaultProps: ColumnHeaderCellProps = { enableColumnInteractionBar: false, isActive: false, menuIcon: "chevron-down", selectCellsOnMenuClick: true, }; /** * This method determines if a `MouseEvent` was triggered on a target that * should be used as the header click/drag target. This enables users of * this component to render fully interactive components in their header * cells without worry of selection or resize operations from capturing * their mouse events. */ public static isHeaderMouseTarget(target: HTMLElement) { return ( target.classList.contains(Classes.TABLE_HEADER) || target.classList.contains(Classes.TABLE_COLUMN_NAME) || target.classList.contains(Classes.TABLE_INTERACTION_BAR) || target.classList.contains(Classes.TABLE_HEADER_CONTENT) ); } public state = { isActive: false, }; public render() { const { enableColumnInteractionBar, enableColumnReordering, isColumnSelected, menuIcon, name, nameRenderer, ...spreadableProps } = this.props; const classes = classNames(spreadableProps.className, Classes.TABLE_COLUMN_HEADER_CELL, { [Classes.TABLE_HAS_INTERACTION_BAR]: enableColumnInteractionBar, [Classes.TABLE_HAS_REORDER_HANDLE]: this.props.reorderHandle != null, }); return ( {this.renderName()} {this.maybeRenderContent()} {this.props.loading ? undefined : this.props.resizeHandle} ); } private renderName() { const { enableColumnInteractionBar, index, loading, name, nameRenderer, reorderHandle } = this.props; const dropdownMenu = this.maybeRenderDropdownMenu(); const defaultName =
{name}
; const nameComponent = ( {nameRenderer?.(name!, index) ?? defaultName} ); if (enableColumnInteractionBar) { return (
{reorderHandle} {dropdownMenu}
{nameComponent}
); } else { return (
{reorderHandle} {dropdownMenu}
{nameComponent}
); } } private maybeRenderContent() { if (this.props.children === null) { return undefined; } return
{this.props.children}
; } private maybeRenderDropdownMenu() { const { index, menuIcon, menuPopoverProps, menuRenderer, selectCellsOnMenuClick } = this.props; if (!CoreUtils.isFunction(menuRenderer)) { return undefined; } const classes = classNames(Classes.TABLE_TH_MENU_CONTAINER, CLASSNAME_EXCLUDED_FROM_TEXT_MEASUREMENT, { [Classes.TABLE_TH_MENU_OPEN]: this.state.isActive, [Classes.TABLE_TH_MENU_SELECT_CELLS]: selectCellsOnMenuClick, }); return (
); } private handlePopoverOpened = () => this.setState({ isActive: true }); private handlePopoverClosing = () => this.setState({ isActive: false }); }