/* * Copyright 2017 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 { AbstractComponent, type Props } from "@blueprintjs/core"; import * as Classes from "../common/classes"; import * as Errors from "../common/errors"; import type { Grid } from "../common/grid"; export enum QuadrantType { /** * The main quadrant beneath any frozen rows or columns. */ MAIN = "main", /** * The top quadrant, containing column headers and frozen rows. */ TOP = "top", /** * The left quadrant, containing row headers and frozen columns. */ LEFT = "left", /** * The top-left quadrant, containing the headers and cells common to both * the frozen columns and frozen rows. */ TOP_LEFT = "top-left", } export interface TableQuadrantProps extends Props { /** * A callback that receives a `ref` to the quadrant's body-wrapping element. Will need to be * provided only for the MAIN quadrant, because that quadrant contains the main table body. */ bodyRef?: React.Ref; /** * The grid computes sizes of cells, rows, or columns from the * configurable `columnWidths` and `rowHeights`. */ grid: Grid; /** * If `false`, hides the row headers and settings menu. * * @default true */ enableRowHeader?: boolean; /** * An optional callback invoked when the quadrant is scrolled via the scrollbar OR the trackpad/mouse wheel. * This callback really only makes sense for the MAIN quadrant, because that's the only quadrant whose * scrollbar is visible. Other quadrants should simply provide an `onWheel` callback. */ onScroll?: React.EventHandler>; /** * An optional callback invoked when the quadrant is scrolled via the trackpad/mouse wheel. This * callback should be provided for all quadrant types except MAIN, which should provide the more * generic `onScroll` callback. */ onWheel?: React.EventHandler>; /** * A callback that receives a `ref` to the quadrant's outermost element. */ quadrantRef?: React.Ref; /** * The quadrant type. Informs the values of the parameters that will be passed to the * `render...` callbacks, assuming an expected stacking order of the four quadrants. */ quadrantType: QuadrantType; /** * A callback that renders the table menu (the rectangle in the top-left corner). */ menuRenderer?: () => React.JSX.Element | undefined; /** * A callback that renders either all of or just the frozen section of the column header. */ columnHeaderCellRenderer?: (showFrozenColumnsOnly: boolean) => React.JSX.Element | undefined; /** * A callback that renders either all of or just the frozen section of the row header. */ rowHeaderCellRenderer?: (showFrozenRowsOnly: boolean) => React.JSX.Element | undefined; /** * A callback that renders either all of or just frozen sections of the table body. */ bodyRenderer: ( quadrantType: QuadrantType, showFrozenRowsOnly?: boolean, showFrozenColumnsOnly?: boolean, ) => React.JSX.Element | undefined; /** * A callback that receives a `ref` to the quadrant's scroll-container element. */ scrollContainerRef?: React.Ref; /** * CSS styles to apply to the quadrant's outermost element. */ style?: React.CSSProperties; /** * If `false`, hides the column headers. * * @default true */ enableColumnHeader?: boolean; } export class TableQuadrant extends AbstractComponent { // we want the user to explicitly pass a quadrantType. define defaultProps as a Partial to avoid // declaring that and other required props here. public static defaultProps: Partial = { enableColumnHeader: true, enableRowHeader: true, }; public render() { const { grid, enableRowHeader, quadrantType, bodyRenderer, enableColumnHeader } = this.props; const showFrozenRowsOnly = quadrantType === QuadrantType.TOP || quadrantType === QuadrantType.TOP_LEFT; const showFrozenColumnsOnly = quadrantType === QuadrantType.LEFT || quadrantType === QuadrantType.TOP_LEFT; const className = classNames(Classes.TABLE_QUADRANT, this.getQuadrantCssClass(), this.props.className); const maybeMenu = enableRowHeader && this.props.menuRenderer?.(); const maybeRowHeader = enableRowHeader && this.props.rowHeaderCellRenderer?.(showFrozenRowsOnly); const maybeColumnHeader = enableColumnHeader && this.props.columnHeaderCellRenderer?.(showFrozenColumnsOnly); const body = bodyRenderer(quadrantType, showFrozenRowsOnly, showFrozenColumnsOnly); // need to set bottom container size to prevent overlay clipping on scroll const bottomContainerStyle = { height: grid.getHeight(), width: grid.getWidth(), }; return (
{maybeMenu} {maybeColumnHeader}
{maybeRowHeader}
{body}
); } protected validateProps(nextProps: TableQuadrantProps) { const { quadrantType } = nextProps; if (nextProps.onScroll != null && quadrantType != null && quadrantType !== QuadrantType.MAIN) { console.warn(Errors.QUADRANT_ON_SCROLL_UNNECESSARILY_DEFINED); } } private getQuadrantCssClass() { switch (this.props.quadrantType) { case QuadrantType.MAIN: return Classes.TABLE_QUADRANT_MAIN; case QuadrantType.TOP: return Classes.TABLE_QUADRANT_TOP; case QuadrantType.LEFT: return Classes.TABLE_QUADRANT_LEFT; case QuadrantType.TOP_LEFT: return Classes.TABLE_QUADRANT_TOP_LEFT; default: return undefined; } } }