From 1099fabdfb3eb312f5b564e983a7732d46718ff7 Mon Sep 17 00:00:00 2001 From: Adi Dahiya Date: Thu, 4 Aug 2022 12:12:13 -0400 Subject: [PATCH 01/10] [table] feat(ColumnHeaderCell, TruncatedFormat): use Popover2 --- packages/table-dev-app/src/index.scss | 49 +++++++++---------- .../src/cell/formats/truncatedFormat.tsx | 18 +++---- packages/table/src/docs/table.md | 9 +++- .../table/src/headers/columnHeaderCell.tsx | 25 +++------- 4 files changed, 48 insertions(+), 53 deletions(-) diff --git a/packages/table-dev-app/src/index.scss b/packages/table-dev-app/src/index.scss index 6c5f82e6f6c..9c2108c025d 100644 --- a/packages/table-dev-app/src/index.scss +++ b/packages/table-dev-app/src/index.scss @@ -4,34 +4,33 @@ @import "~normalize.css/normalize.css"; @import "~@blueprintjs/core/lib/css/blueprint.css"; @import "~@blueprintjs/icons/lib/css/blueprint-icons.css"; +@import "~@blueprintjs/popover2/lib/css/blueprint-popover2.css"; @import "~@blueprintjs/table/src/table"; -// TODO: convert below CSS to SCSS -/* stylelint-disable */ - body { - position: absolute; - top: 0; - right: 0; - left: 0; + + background-color: $pt-app-background-color; bottom: 0; + left: 0; margin: 0; padding: 0; - - background-color: $pt-app-background-color; + position: absolute; + right: 0; + top: 0; .#{$ns}-dark { background-color: $pt-dark-app-background-color; } } +/* stylelint-disable-next-line selector-max-id */ #page-content { - position: absolute; - top: $pt-navbar-height; - left: 0; bottom: 0; + left: 0; + position: absolute; right: 0; + top: $pt-navbar-height; } /** @@ -39,9 +38,9 @@ body { */ .layout-passthrough-fill { display: inline-block; - width: 100%; height: 100%; position: relative; + width: 100%; } /** @@ -49,27 +48,27 @@ body { * See: http://wilsonpage.co.uk/introducing-layout-boundaries/ */ .layout-boundary { - display: block; - position: absolute; - top: 0; bottom: 0; + display: block; left: 0; - right: 0; overflow: hidden; + position: absolute; + right: 0; + top: 0; } .container { display: flex; - overflow: hidden; height: 100%; + overflow: hidden; } .table { flex: 1 1 auto; order: 2; - z-index: 2; overflow: hidden; position: relative; + z-index: 2; &.is-inline { margin-left: -12.5%; @@ -98,7 +97,6 @@ body { h6.#{$ns}-heading { color: $pt-text-color-muted; - font-weight: normal; padding: 10px 0 3px; } @@ -112,8 +110,8 @@ body { } .tbl-select-label { - margin-top: -3px; margin-bottom: 7px; + margin-top: -3px; .#{$ns}-html-select { float: right; @@ -129,15 +127,16 @@ body { } .#{$ns}-dark & { - z-index: 1; background-color: $dark-gray2; color: $dark-gray2; + z-index: 1; h6.#{$ns}-heading { color: $pt-dark-text-color-muted; } } } + .sidebar-indented-group { margin-left: $pt-grid-size; } @@ -147,18 +146,18 @@ body { } .tbl-styled-region-success { - border: 1px solid $pt-intent-success; background-color: rgba($pt-intent-success, 0.1); + border: 1px solid $pt-intent-success; } .tbl-styled-region-warning { - border: 1px solid $pt-intent-warning; background-color: rgba($pt-intent-warning, 0.1); + border: 1px solid $pt-intent-warning; } .tbl-styled-region-danger { - border: 1px solid $pt-intent-danger; background-color: rgba($pt-intent-danger, 0.1); + border: 1px solid $pt-intent-danger; } .tbl-custom-column-header { diff --git a/packages/table/src/cell/formats/truncatedFormat.tsx b/packages/table/src/cell/formats/truncatedFormat.tsx index 48293603321..89d95dd5ccf 100644 --- a/packages/table/src/cell/formats/truncatedFormat.tsx +++ b/packages/table/src/cell/formats/truncatedFormat.tsx @@ -17,7 +17,8 @@ import classNames from "classnames"; import * as React from "react"; -import { DISPLAYNAME_PREFIX, Icon, Popover, Position, Props } from "@blueprintjs/core"; +import { DISPLAYNAME_PREFIX, Icon, Props } from "@blueprintjs/core"; +import { Popover2 } from "@blueprintjs/popover2"; import * as Classes from "../../common/classes"; import { Utils } from "../../common/utils"; @@ -216,7 +217,7 @@ export class TruncatedFormat extends React.PureComponent` will always check the content's position on update + // `` will always check the content's position on update // regardless if it is open or not. This negatively affects perf due to // layout thrashing. So instead we manage the popover state ourselves // and mimic its popover target @@ -227,22 +228,19 @@ export class TruncatedFormat extends React.PureComponent{children}; return ( - /* eslint-disable-next-line deprecation/deprecation */ - - {/* eslint-disable-next-line deprecation/deprecation */} - + ); } else { - // NOTE: This structure matches what `` does internally. If - // `` changes, this must be updated. + // NOTE: This structure matches what `` does internally. If `` changes, this must be updated. return ( diff --git a/packages/table/src/docs/table.md b/packages/table/src/docs/table.md index 6bb301cdee0..76a540497e8 100644 --- a/packages/table/src/docs/table.md +++ b/packages/table/src/docs/table.md @@ -14,10 +14,17 @@ Make sure to review the [getting started docs for installation info](#blueprint/ npm install --save @blueprintjs/table ``` -Do not forget to include `table.css` on your page: +Do not forget to include `table.css` and `blueprint-popover2.css` on your page. + +
+ +As of @blueprintjs/table v4.6.0, @blueprintjs/popover2 is a direct dependency and its styles must be imported +in order for some table features to display properly. +
```scss @import "~@blueprintjs/table/lib/css/table.css"; +@import "~@blueprintjs/popover2/lib/css/blueprint-popover2.css"; ```
diff --git a/packages/table/src/headers/columnHeaderCell.tsx b/packages/table/src/headers/columnHeaderCell.tsx index 84656df88a4..2f0b9c10bd2 100644 --- a/packages/table/src/headers/columnHeaderCell.tsx +++ b/packages/table/src/headers/columnHeaderCell.tsx @@ -17,15 +17,8 @@ import classNames from "classnames"; import * as React from "react"; -import { - AbstractPureComponent2, - Utils as CoreUtils, - Icon, - IconName, - Popover, - Position, - Props, -} from "@blueprintjs/core"; +import { AbstractPureComponent2, Utils as CoreUtils, Icon, IconName, Props } from "@blueprintjs/core"; +import { Popover2 } from "@blueprintjs/popover2"; import * as Classes from "../common/classes"; import { columnInteractionBarContextTypes, IColumnInteractionBarContextTypes } from "../common/context"; @@ -205,18 +198,16 @@ export class ColumnHeaderCell extends AbstractPureComponent2
- {/* eslint-disable-next-line deprecation/deprecation */} - - {/* eslint-disable-next-line deprecation/deprecation */} - +
); } From 7227cf7dfe3cda06b8d19553990ed7dd0c2de3de Mon Sep 17 00:00:00 2001 From: Adi Dahiya Date: Thu, 4 Aug 2022 12:40:36 -0400 Subject: [PATCH 02/10] fix tests --- packages/table/test/columnHeaderCellTests.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/table/test/columnHeaderCellTests.tsx b/packages/table/test/columnHeaderCellTests.tsx index bae317a828b..628ad2f86cb 100644 --- a/packages/table/test/columnHeaderCellTests.tsx +++ b/packages/table/test/columnHeaderCellTests.tsx @@ -19,8 +19,8 @@ import { shallow } from "enzyme"; import * as React from "react"; import * as sinon from "sinon"; -import { Classes as CoreClasses, H4, Menu } from "@blueprintjs/core"; -import { MenuItem2 } from "@blueprintjs/popover2"; +import { H4, Menu } from "@blueprintjs/core"; +import { MenuItem2, Classes as Popover2Classes } from "@blueprintjs/popover2"; import { ColumnHeaderCell, IColumnHeaderCellProps } from "../src"; import * as Classes from "../src/common/classes"; @@ -130,7 +130,7 @@ describe("", () => { function expectMenuToOpen(table: ElementHarness, menuClickSpy: sinon.SinonSpy) { table.find(`.${Classes.TABLE_COLUMN_HEADERS}`)!.mouse("mousemove"); - table.find(`.${Classes.TABLE_TH_MENU} .${CoreClasses.POPOVER_TARGET}`)!.mouse("click"); + table.find(`.${Classes.TABLE_TH_MENU}.${Popover2Classes.POPOVER2_TARGET}`)!.mouse("click"); ElementHarness.document().find('[data-icon="export"]')!.mouse("click"); expect(menuClickSpy.called).to.be.true; } From 3c4d626a98ec88fd67aa856b9bb212cbe585cde6 Mon Sep 17 00:00:00 2001 From: Adi Dahiya Date: Thu, 4 Aug 2022 15:05:52 -0400 Subject: [PATCH 03/10] refactor into v2 components --- .../src/cell/formats/truncatedFormat.tsx | 28 +- .../src/cell/formats/truncatedFormat2.tsx | 216 ++++++++++++++++ packages/table/src/common/context.ts | 4 +- .../table/src/headers/columnHeaderCell.tsx | 41 ++- .../table/src/headers/columnHeaderCell2.tsx | 175 +++++++++++++ packages/table/src/index.ts | 2 + packages/table/src/table.tsx | 6 +- packages/table/src/table2.tsx | 6 +- .../table/test/columnHeaderCell2Tests.tsx | 165 ++++++++++++ packages/table/test/columnHeaderCellTests.tsx | 13 +- .../table/test/formats/jsonFormatTests.tsx | 72 ++++++ .../test/formats/truncatedFormat2Tests.tsx | 197 ++++++++++++++ .../test/formats/truncatedFormatTests.tsx | 203 +++++++++++++++ packages/table/test/formatsTests.tsx | 242 ------------------ packages/table/test/index.ts | 5 +- packages/table/test/isotest.js | 3 +- 16 files changed, 1102 insertions(+), 276 deletions(-) create mode 100644 packages/table/src/cell/formats/truncatedFormat2.tsx create mode 100644 packages/table/src/headers/columnHeaderCell2.tsx create mode 100644 packages/table/test/columnHeaderCell2Tests.tsx create mode 100644 packages/table/test/formats/jsonFormatTests.tsx create mode 100644 packages/table/test/formats/truncatedFormat2Tests.tsx create mode 100644 packages/table/test/formats/truncatedFormatTests.tsx delete mode 100644 packages/table/test/formatsTests.tsx diff --git a/packages/table/src/cell/formats/truncatedFormat.tsx b/packages/table/src/cell/formats/truncatedFormat.tsx index 89d95dd5ccf..b8bca06d4de 100644 --- a/packages/table/src/cell/formats/truncatedFormat.tsx +++ b/packages/table/src/cell/formats/truncatedFormat.tsx @@ -14,11 +14,17 @@ * limitations under the License. */ +/** + * @fileoverview This component is DEPRECATED, and the code is frozen. + * All changes & bugfixes should be made to TruncatedFormat2 instead. + */ + +/* eslint-disable deprecation/deprecation, @blueprintjs/no-deprecated-components */ + import classNames from "classnames"; import * as React from "react"; -import { DISPLAYNAME_PREFIX, Icon, Props } from "@blueprintjs/core"; -import { Popover2 } from "@blueprintjs/popover2"; +import { DISPLAYNAME_PREFIX, Icon, Popover, Position, Props } from "@blueprintjs/core"; import * as Classes from "../../common/classes"; import { Utils } from "../../common/utils"; @@ -77,6 +83,7 @@ export interface ITrucatedFormateMeasureByApproximateOptions { } export type TruncatedFormatProps = ITruncatedFormatProps; +/** @deprecated use TruncatedFormatProps */ export interface ITruncatedFormatProps extends Props { children?: string; @@ -151,10 +158,11 @@ export interface ITruncatedFormatState { isPopoverOpen?: boolean; } -export class TruncatedFormat extends React.PureComponent { +/** @deprecated use TruncatedFormat2 */ +export class TruncatedFormat extends React.PureComponent { public static displayName = `${DISPLAYNAME_PREFIX}.TruncatedFormat`; - public static defaultProps: ITruncatedFormatProps = { + public static defaultProps: TruncatedFormatProps = { detectTruncation: false, measureByApproxOptions: { approximateCharWidth: 8, @@ -217,7 +225,7 @@ export class TruncatedFormat extends React.PureComponent` will always check the content's position on update + // `` will always check the content's position on update // regardless if it is open or not. This negatively affects perf due to // layout thrashing. So instead we manage the popover state ourselves // and mimic its popover target @@ -228,19 +236,19 @@ export class TruncatedFormat extends React.PureComponent{children}
; return ( - - +
); } else { - // NOTE: This structure matches what `` does internally. If `` changes, this must be updated. + // NOTE: This structure matches what `` does internally. If `` changes, this must be updated. return ( diff --git a/packages/table/src/cell/formats/truncatedFormat2.tsx b/packages/table/src/cell/formats/truncatedFormat2.tsx new file mode 100644 index 00000000000..c3e8a12fa45 --- /dev/null +++ b/packages/table/src/cell/formats/truncatedFormat2.tsx @@ -0,0 +1,216 @@ +/* + * 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 * as React from "react"; + +import { DISPLAYNAME_PREFIX, Icon } from "@blueprintjs/core"; +import { Popover2 } from "@blueprintjs/popover2"; + +import * as Classes from "../../common/classes"; +import { Utils } from "../../common/utils"; +import { Locator } from "../../locator"; +import { ITruncatedFormatState, TruncatedFormatProps, TruncatedPopoverMode } from "./truncatedFormat"; + +// amount in pixels that the content div width changes when truncated vs when +// not truncated. Note: could be modified by styles +// Note 2: this doesn't come from the width of the popover element, but the "right" style +// on the div, which comes from styles +const CONTENT_DIV_WIDTH_DELTA = 25; + +export class TruncatedFormat2 extends React.PureComponent { + public static displayName = `${DISPLAYNAME_PREFIX}.TruncatedFormat2`; + + public static defaultProps: TruncatedFormatProps = { + detectTruncation: false, + measureByApproxOptions: { + approximateCharWidth: 8, + approximateLineHeight: 18, + cellHorizontalPadding: 2 * Locator.CELL_HORIZONTAL_PADDING, + numBufferLines: 0, + }, + preformatted: false, + showPopover: TruncatedPopoverMode.WHEN_TRUNCATED, + truncateLength: 2000, + truncationSuffix: "...", + }; + + public state: ITruncatedFormatState = { + isPopoverOpen: false, + isTruncated: false, + }; + + private contentDiv: HTMLDivElement | null | undefined; + + private handleContentDivRef = (ref: HTMLDivElement | null) => (this.contentDiv = ref); + + public componentDidMount() { + this.setTruncationState(); + } + + public componentDidUpdate() { + this.setTruncationState(); + } + + public render() { + const { children, detectTruncation, truncateLength, truncationSuffix } = this.props; + const content = "" + children; + + let cellContent = content; + if (!detectTruncation && truncateLength! > 0 && cellContent.length > truncateLength!) { + cellContent = cellContent.substring(0, truncateLength) + truncationSuffix; + } + + if (this.shouldShowPopover(content)) { + const className = classNames(this.props.className, Classes.TABLE_TRUNCATED_FORMAT); + return ( +
+
+ {cellContent} +
+ {this.renderPopover()} +
+ ); + } else { + const className = classNames(this.props.className, Classes.TABLE_TRUNCATED_FORMAT_TEXT); + return ( +
+ {cellContent} +
+ ); + } + } + + private renderPopover() { + const { children, preformatted } = this.props; + + // `` will always check the content's position on update + // regardless if it is open or not. This negatively affects perf due to + // layout thrashing. So instead we manage the popover state ourselves + // and mimic its popover target + if (this.state.isPopoverOpen) { + const popoverClasses = classNames( + Classes.TABLE_TRUNCATED_POPOVER, + preformatted ? Classes.TABLE_POPOVER_WHITESPACE_PRE : Classes.TABLE_POPOVER_WHITESPACE_NORMAL, + ); + const popoverContent =
{children}
; + return ( + + + + ); + } else { + // NOTE: This structure matches what `` does internally. If `` changes, this must be updated. + return ( + + + + ); + } + } + + private handlePopoverOpen = () => { + this.setState({ isPopoverOpen: true }); + }; + + private handlePopoverClose = () => { + this.setState({ isPopoverOpen: false }); + }; + + private shouldShowPopover(content: string) { + const { detectTruncation, measureByApproxOptions, showPopover, truncateLength } = this.props; + + switch (showPopover) { + case TruncatedPopoverMode.ALWAYS: + return true; + case TruncatedPopoverMode.NEVER: + return false; + case TruncatedPopoverMode.WHEN_TRUNCATED: + return detectTruncation + ? this.state.isTruncated + : truncateLength! > 0 && content.length > truncateLength!; + case TruncatedPopoverMode.WHEN_TRUNCATED_APPROX: + if (!detectTruncation) { + return truncateLength! > 0 && content.length > truncateLength!; + } + if (this.props.parentCellHeight == null || this.props.parentCellWidth == null) { + return false; + } + + const { approximateCharWidth, approximateLineHeight, cellHorizontalPadding, numBufferLines } = + measureByApproxOptions!; + + const cellWidth = this.props.parentCellWidth; + const approxCellHeight = Utils.getApproxCellHeight( + content, + cellWidth, + approximateCharWidth, + approximateLineHeight, + cellHorizontalPadding, + numBufferLines, + ); + + const shouldTruncate = approxCellHeight > this.props.parentCellHeight; + return shouldTruncate; + default: + return false; + } + } + + private setTruncationState() { + if (!this.props.detectTruncation || this.props.showPopover !== TruncatedPopoverMode.WHEN_TRUNCATED) { + return; + } + + if (this.contentDiv == null) { + this.setState({ isTruncated: false }); + return; + } + + const { isTruncated } = this.state; + + // take all measurements at once to avoid excessive DOM reflows. + const { + clientHeight: containerHeight, + clientWidth: containerWidth, + scrollHeight: actualContentHeight, + scrollWidth: contentWidth, + } = this.contentDiv; + + // if the content is truncated, then a popover handle will be present as a + // sibling of the content. we don't want to consider that handle when + // calculating the width of the actual content, so subtract it. + const actualContentWidth = isTruncated ? contentWidth - CONTENT_DIV_WIDTH_DELTA : contentWidth; + + // we of course truncate the content if it doesn't fit in the container. but we + // also aggressively truncate if they're the same size with truncation enabled; + // this addresses browser-crashing stack-overflow bugs at various zoom levels. + // (see: https://github.com/palantir/blueprint/pull/1519) + const shouldTruncate = + (isTruncated && actualContentWidth === containerWidth) || + actualContentWidth > containerWidth || + actualContentHeight > containerHeight; + + this.setState({ isTruncated: shouldTruncate }); + } +} diff --git a/packages/table/src/common/context.ts b/packages/table/src/common/context.ts index 704e8504ed6..3b41ace3507 100644 --- a/packages/table/src/common/context.ts +++ b/packages/table/src/common/context.ts @@ -16,10 +16,10 @@ import * as PropTypes from "prop-types"; import * as React from "react"; -export interface IColumnInteractionBarContextTypes { +export interface ColumnInteractionBarContextTypes { enableColumnInteractionBar: boolean | null | undefined; } -export const columnInteractionBarContextTypes: React.ValidationMap = { +export const columnInteractionBarContextTypes: React.ValidationMap = { enableColumnInteractionBar: PropTypes.bool, }; diff --git a/packages/table/src/headers/columnHeaderCell.tsx b/packages/table/src/headers/columnHeaderCell.tsx index 2f0b9c10bd2..c4eccc4d2d1 100644 --- a/packages/table/src/headers/columnHeaderCell.tsx +++ b/packages/table/src/headers/columnHeaderCell.tsx @@ -14,14 +14,29 @@ * limitations under the License. */ +/** + * @fileoverview This component is DEPRECATED, and the code is frozen. + * All changes & bugfixes should be made to ColumnHeaderCell2 instead. + */ + +/* eslint-disable deprecation/deprecation, @blueprintjs/no-deprecated-components */ + import classNames from "classnames"; import * as React from "react"; -import { AbstractPureComponent2, Utils as CoreUtils, Icon, IconName, Props } from "@blueprintjs/core"; -import { Popover2 } from "@blueprintjs/popover2"; +import { + AbstractPureComponent2, + Utils as CoreUtils, + DISPLAYNAME_PREFIX, + Icon, + IconName, + Popover, + Position, + Props, +} from "@blueprintjs/core"; import * as Classes from "../common/classes"; -import { columnInteractionBarContextTypes, IColumnInteractionBarContextTypes } from "../common/context"; +import { columnInteractionBarContextTypes, ColumnInteractionBarContextTypes } from "../common/context"; import { LoadableContent } from "../common/loadableContent"; import { CLASSNAME_EXCLUDED_FROM_TEXT_MEASUREMENT } from "../common/utils"; import { HeaderCell, IHeaderCellProps } from "./headerCell"; @@ -48,6 +63,7 @@ export interface IColumnNameProps { nameRenderer?: (name: string, index?: number) => React.ReactElement; } +/** @deprecated use ColumnHeaderCellProps */ export interface IColumnHeaderCellProps extends IHeaderCellProps, IColumnNameProps { /** * Specifies if the column is reorderable. @@ -75,13 +91,16 @@ export function HorizontalCellDivider(): JSX.Element { return
; } +/** @deprecated use ColumnHeaderCell2 instead */ export class ColumnHeaderCell extends AbstractPureComponent2 { + public static displayName = `${DISPLAYNAME_PREFIX}.ColumnHeaderCell`; + public static defaultProps: IColumnHeaderCellProps = { isActive: false, menuIcon: "chevron-down", }; - public static contextTypes: React.ValidationMap = + public static contextTypes: React.ValidationMap = columnInteractionBarContextTypes; /** @@ -100,7 +119,7 @@ export class ColumnHeaderCell extends AbstractPureComponent2
- - +
); } diff --git a/packages/table/src/headers/columnHeaderCell2.tsx b/packages/table/src/headers/columnHeaderCell2.tsx new file mode 100644 index 00000000000..2f72b89bea3 --- /dev/null +++ b/packages/table/src/headers/columnHeaderCell2.tsx @@ -0,0 +1,175 @@ +/* + * 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 * as React from "react"; + +import { AbstractPureComponent2, Utils as CoreUtils, DISPLAYNAME_PREFIX, Icon } from "@blueprintjs/core"; +import { Popover2 } from "@blueprintjs/popover2"; + +import * as Classes from "../common/classes"; +import { columnInteractionBarContextTypes, ColumnInteractionBarContextTypes } from "../common/context"; +import { LoadableContent } from "../common/loadableContent"; +import { CLASSNAME_EXCLUDED_FROM_TEXT_MEASUREMENT } from "../common/utils"; +import { HorizontalCellDivider, IColumnHeaderCellProps, IColumnHeaderCellState } from "./columnHeaderCell"; +import { HeaderCell } from "./headerCell"; + +// eslint-disable-next-line deprecation/deprecation +export type ColumnHeaderCellProps = IColumnHeaderCellProps; + +export class ColumnHeaderCell2 extends AbstractPureComponent2 { + public static displayName = `${DISPLAYNAME_PREFIX}.ColumnHeaderCell2`; + + public static defaultProps: ColumnHeaderCellProps = { + isActive: false, + menuIcon: "chevron-down", + }; + + public static contextTypes: React.ValidationMap = + columnInteractionBarContextTypes; + + /** + * 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 context: ColumnInteractionBarContextTypes = { + enableColumnInteractionBar: false, + }; + + public state = { + isActive: false, + }; + + public render() { + const { + // from IColumnHeaderCellProps + enableColumnReordering, + isColumnSelected, + menuIcon, + + // from IColumnNameProps + name, + nameRenderer, + + // from IHeaderProps + ...spreadableProps + } = this.props; + + const classes = classNames(spreadableProps.className, Classes.TABLE_COLUMN_HEADER_CELL, { + [Classes.TABLE_HAS_INTERACTION_BAR]: this.context.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 { index, loading, name, nameRenderer, reorderHandle } = this.props; + + const dropdownMenu = this.maybeRenderDropdownMenu(); + const defaultName =
{name}
; + + const nameComponent = ( + + {nameRenderer?.(name!, index) ?? defaultName} + + ); + + if (this.context.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, menuRenderer } = 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, + }); + + return ( +
+
+ + + +
+ ); + } + + private handlePopoverOpened = () => this.setState({ isActive: true }); + + private handlePopoverClosing = () => this.setState({ isActive: false }); +} diff --git a/packages/table/src/index.ts b/packages/table/src/index.ts index 52d9f2d77e6..f7aed8f9041 100644 --- a/packages/table/src/index.ts +++ b/packages/table/src/index.ts @@ -67,6 +67,8 @@ export { RowHeaderRenderer } from "./headers/rowHeader"; export { ColumnHeaderCell, IColumnHeaderCellProps, HorizontalCellDivider } from "./headers/columnHeaderCell"; +export { ColumnHeaderCell2, ColumnHeaderCellProps } from "./headers/columnHeaderCell2"; + export { IRowHeaderCellProps, RowHeaderCell } from "./headers/rowHeaderCell"; export { IEditableNameProps, EditableNameProps, EditableName } from "./headers/editableName"; diff --git a/packages/table/src/table.tsx b/packages/table/src/table.tsx index ae1fd4f120b..79bc5aef5cf 100644 --- a/packages/table/src/table.tsx +++ b/packages/table/src/table.tsx @@ -32,7 +32,7 @@ import { CellRenderer } from "./cell/cell"; import { Column, IColumnProps } from "./column"; import type { IFocusedCellCoordinates } from "./common/cellTypes"; import * as Classes from "./common/classes"; -import { columnInteractionBarContextTypes, IColumnInteractionBarContextTypes } from "./common/context"; +import { columnInteractionBarContextTypes, ColumnInteractionBarContextTypes } from "./common/context"; import * as Errors from "./common/errors"; import { Grid, ICellMapper } from "./common/grid"; import * as FocusedCellUtils from "./common/internal/focusedCellUtils"; @@ -93,7 +93,7 @@ export class Table extends AbstractComponent2 = + public static childContextTypes: React.ValidationMap = columnInteractionBarContextTypes; public static getDerivedStateFromProps(props: TablePropsWithDefaults, state: TableState) { @@ -401,7 +401,7 @@ export class Table extends AbstractComponent2 = + public static childContextTypes: React.ValidationMap = columnInteractionBarContextTypes; public static getDerivedStateFromProps(props: TablePropsWithDefaults, state: TableState) { @@ -433,7 +433,7 @@ export class Table2 extends AbstractComponent2", () => { + const harness = new ReactHarness(); + + afterEach(() => { + harness.unmount(); + }); + + after(() => { + harness.destroy(); + }); + + it("Default renderer", () => { + const table = harness.mount(createTableOfSize(3, 2)); + const text = table.find(`.${Classes.TABLE_COLUMN_NAME_TEXT}`, 1)!.text(); + expect(text).to.equal("B"); + }); + + it("renders with custom className if provided", () => { + const CLASS_NAME = "my-custom-class-name"; + const table = harness.mount(); + const hasCustomClass = table.find(`.${Classes.TABLE_HEADER}`, 0)!.hasClass(CLASS_NAME); + expect(hasCustomClass).to.be.true; + }); + + it("passes index prop to nameRenderer callback if index was provided", () => { + const renderNameStub = sinon.stub(); + renderNameStub.returns("string"); + const NAME = "my-name"; + const INDEX = 17; + shallow(); + expect(renderNameStub.firstCall.args).to.deep.equal([NAME, INDEX]); + }); + + describe("Custom renderer", () => { + it("renders custom name", () => { + const columnHeaderCellRenderer = (columnIndex: number) => { + return ; + }; + const table = harness.mount(createTableOfSize(3, 2, { columnHeaderCellRenderer })); + const text = table.find(`.${Classes.TABLE_COLUMN_NAME_TEXT}`, 1)!.text(); + expect(text).to.equal("COLUMN-1"); + }); + + it("renders custom content", () => { + const columnHeaderCellRenderer = (columnIndex: number) => { + return ( + +

Header of {columnIndex}

+
+ ); + }; + const table = harness.mount(createTableOfSize(3, 2, { columnHeaderCellRenderer })); + const text = table.find(`.${Classes.TABLE_HEADER_CONTENT} h4`, 2)!.text(); + expect(text).to.equal("Header of 2"); + }); + + it("renders custom menu items", () => { + const menuClickSpy = sinon.spy(); + const menu = getMenuComponent(menuClickSpy); + const renderMenuFn = () => menu; + + const columnHeaderCellRenderer = (columnIndex: number) => { + return ; + }; + const table = harness.mount(createTableOfSize(3, 2, { columnHeaderCellRenderer })); + expectMenuToOpen(table, menuClickSpy); + }); + + it("renders custom menu items with a menuRenderer callback", () => { + const menuClickSpy = sinon.spy(); + const menu = getMenuComponent(menuClickSpy); + const menuRenderer = sinon.stub().returns(menu); + + const columnHeaderCellRenderer = (columnIndex: number) => ( + + ); + const table = harness.mount(createTableOfSize(3, 2, { columnHeaderCellRenderer })); + expectMenuToOpen(table, menuClickSpy); + }); + + it("renders loading state properly", () => { + const columnHeaderCellRenderer = (columnIndex: number) => { + return ; + }; + const table = harness.mount(createTableOfSize(2, 1, { columnHeaderCellRenderer })); + expect(table.find(`.${Classes.TABLE_COLUMN_HEADERS} .${Classes.TABLE_HEADER}`, 0)!.text()).to.equal(""); + expect(table.find(`.${Classes.TABLE_COLUMN_HEADERS} .${Classes.TABLE_HEADER}`, 1)!.text()).to.equal( + "Column Header", + ); + }); + + function getMenuComponent(menuClickSpy: sinon.SinonSpy) { + return ( + + + + + + ); + } + + function expectMenuToOpen(table: ElementHarness, menuClickSpy: sinon.SinonSpy) { + table.find(`.${Classes.TABLE_COLUMN_HEADERS}`)!.mouse("mousemove"); + table.find(`.${Classes.TABLE_TH_MENU}.${Popover2Classes.POPOVER2_TARGET}`)!.mouse("click"); + ElementHarness.document().find('[data-icon="export"]')!.mouse("click"); + expect(menuClickSpy.called).to.be.true; + } + }); + + // TODO: re-enable these tests when we switch to enzyme's testing harness instead of our own, + // so that we can supply a react context with enableColumnInteractionBar: true + // see https://github.com/palantir/blueprint/issues/2076 + describe.skip("Reorder handle", () => { + const REORDER_HANDLE_CLASS = Classes.TABLE_REORDER_HANDLE_TARGET; + + it("shows reorder handle in interaction bar if reordering and interaction bar are enabled", () => { + const element = mount({ enableColumnReordering: true }); + expect(element.find(`.${Classes.TABLE_INTERACTION_BAR} .${REORDER_HANDLE_CLASS}`)!.exists()).to.be.true; + }); + + it("shows reorder handle next to column name if reordering enabled but interaction bar disabled", () => { + const element = mount({ enableColumnReordering: true }); + expect(element.find(`.${Classes.TABLE_COLUMN_NAME} .${REORDER_HANDLE_CLASS}`)!.exists()).to.be.true; + }); + + function mount(props: Partial) { + const element = harness.mount( + } + />, + ); + return element; + } + }); +}); diff --git a/packages/table/test/columnHeaderCellTests.tsx b/packages/table/test/columnHeaderCellTests.tsx index 628ad2f86cb..f3dc694d8e4 100644 --- a/packages/table/test/columnHeaderCellTests.tsx +++ b/packages/table/test/columnHeaderCellTests.tsx @@ -14,13 +14,20 @@ * limitations under the License. */ +/** + * @fileoverview This component is DEPRECATED, and the code is frozen. + * All changes & bugfixes should be made to ColumnHeaderCell2 instead. + */ + +/* eslint-disable deprecation/deprecation, @blueprintjs/no-deprecated-components */ + import { expect } from "chai"; import { shallow } from "enzyme"; import * as React from "react"; import * as sinon from "sinon"; -import { H4, Menu } from "@blueprintjs/core"; -import { MenuItem2, Classes as Popover2Classes } from "@blueprintjs/popover2"; +import { Classes as CoreClasses, H4, Menu } from "@blueprintjs/core"; +import { MenuItem2 } from "@blueprintjs/popover2"; import { ColumnHeaderCell, IColumnHeaderCellProps } from "../src"; import * as Classes from "../src/common/classes"; @@ -130,7 +137,7 @@ describe("", () => { function expectMenuToOpen(table: ElementHarness, menuClickSpy: sinon.SinonSpy) { table.find(`.${Classes.TABLE_COLUMN_HEADERS}`)!.mouse("mousemove"); - table.find(`.${Classes.TABLE_TH_MENU}.${Popover2Classes.POPOVER2_TARGET}`)!.mouse("click"); + table.find(`.${Classes.TABLE_TH_MENU} .${CoreClasses.POPOVER_TARGET}`)!.mouse("click"); ElementHarness.document().find('[data-icon="export"]')!.mouse("click"); expect(menuClickSpy.called).to.be.true; } diff --git a/packages/table/test/formats/jsonFormatTests.tsx b/packages/table/test/formats/jsonFormatTests.tsx new file mode 100644 index 00000000000..2d9365020f5 --- /dev/null +++ b/packages/table/test/formats/jsonFormatTests.tsx @@ -0,0 +1,72 @@ +/* + * Copyright 2016 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 { expect } from "chai"; +import * as React from "react"; + +import { JSONFormat } from "../../src/cell/formats/jsonFormat"; +import { TruncatedPopoverMode } from "../../src/cell/formats/truncatedFormat"; +import * as Classes from "../../src/common/classes"; +import { ReactHarness } from "../harness"; + +describe("", () => { + const harness = new ReactHarness(); + + afterEach(() => { + harness.unmount(); + }); + + after(() => { + harness.destroy(); + }); + + it("stringifies JSON", () => { + const obj = { + help: "me", + "i'm": 1234, + }; + const str = JSON.stringify(obj, null, 2); + const comp = harness.mount({obj}); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_FORMAT_TEXT}`)!.text()).to.equal(str); + }); + + it("omits quotes on strings and null-likes", () => { + let comp = harness.mount({"a string"}); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_FORMAT_TEXT}`)!.text()).to.equal("a string"); + + comp = harness.mount({null}); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_FORMAT_TEXT}`)!.text()).to.equal("null"); + + comp = harness.mount({undefined}); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_FORMAT_TEXT}`)!.text()).to.equal("undefined"); + }); + + it("hides popover for null-likes, still passes showPopover prop", () => { + let comp = harness.mount({null}); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.not.exist; + + const str = `this is a very long string that will be truncated by the following settings`; + comp = harness.mount( + + {str} + , + ); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).exist; + + comp = harness.mount({str}); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.not.exist; + }); +}); diff --git a/packages/table/test/formats/truncatedFormat2Tests.tsx b/packages/table/test/formats/truncatedFormat2Tests.tsx new file mode 100644 index 00000000000..79a237b194f --- /dev/null +++ b/packages/table/test/formats/truncatedFormat2Tests.tsx @@ -0,0 +1,197 @@ +/* + * 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 { expect } from "chai"; +import * as React from "react"; + +import { TruncatedPopoverMode } from "../../src/cell/formats/truncatedFormat"; +import { TruncatedFormat2 } from "../../src/cell/formats/truncatedFormat2"; +import * as Classes from "../../src/common/classes"; +import { ReactHarness } from "../harness"; +import { createStringOfLength } from "../mocks/table"; + +describe("", () => { + const harness = new ReactHarness(); + + afterEach(() => { + harness.unmount(); + }); + + after(() => { + harness.destroy(); + }); + + it("can automatically truncate and show popover when truncated", () => { + const str = createStringOfLength(TruncatedFormat2.defaultProps.truncateLength! + 1); + + const comp = harness.mount( +
+ {str} +
, + ); + const textElement = comp.element!.querySelector(`.${Classes.TABLE_TRUNCATED_VALUE}`)!; + expect(textElement.scrollWidth).to.be.greaterThan(textElement.clientWidth); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.exist; + }); + + // This test was super flaky. It started failing without clear cause when the Table Frozen + // Columns/Rows changes merged, even though nothing about the TruncatedFormat2 component + // changed. Adding the position: relative rule fixes it, but more investigation is needed. + it("can automatically truncate and show popover when truncated and word wrapped", () => { + const str = ` + We are going to die, and that makes us the lucky ones. Most + people are never going to die because they are never going to + be born. The potential people who could have been here in my + place but who will in fact never see the light of day + outnumber the sand grains of Arabia. Certainly those unborn + ghosts include greater poets than Keats, scientists greater + than Newton. We know this because the set of possible people + allowed by our DNA so massively outnumbers the set of actual + people. In the teeth of these stupefying odds it is you and I, + in our ordinariness, that are here. We privileged few, who won + the lottery of birth against all odds, how dare we whine at + our inevitable return to that prior state from which the vast + majority have never stirred? + `; + + // fix the container's width and height to ensure this test passes + // regardless of the page's dimensions. + const style: React.CSSProperties = { + height: "300px", + position: "relative", + width: "300px", + }; + + const comp = harness.mount( +
+ {str} +
, + ); + const textElement = comp.element!.querySelector(`.${Classes.TABLE_TRUNCATED_VALUE}`)!; + expect(textElement.scrollHeight).to.be.greaterThan(textElement.clientHeight); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.exist; + }); + + it("can automatically truncate and show popover when truncated and word wrapped in approx mode", () => { + const str = ` + We are going to die, and that makes us the lucky ones. Most + people are never going to die because they are never going to + be born. The potential people who could have been here in my + place but who will in fact never see the light of day + outnumber the sand grains of Arabia. Certainly those unborn + ghosts include greater poets than Keats, scientists greater + than Newton. We know this because the set of possible people + allowed by our DNA so massively outnumbers the set of actual + people. In the teeth of these stupefying odds it is you and I, + in our ordinariness, that are here. We privileged few, who won + the lottery of birth against all odds, how dare we whine at + our inevitable return to that prior state from which the vast + majority have never stirred? + `; + + // fix the container's width and height to ensure this test passes + // regardless of the page's dimensions. + const style: React.CSSProperties = { + height: "300px", + position: "relative", + width: "300px", + }; + + const comp = harness.mount( +
+ + {str} + +
, + ); + const textElement = comp.element!.querySelector(`.${Classes.TABLE_TRUNCATED_VALUE}`)!; + expect(textElement.scrollHeight).to.be.greaterThan(textElement.clientHeight); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.exist; + }); + + it("can manually truncate and show popover when truncated", () => { + const str = createStringOfLength(TruncatedFormat2.defaultProps.truncateLength! + 1); + const comp = harness.mount({str}); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_VALUE}`)!.text()!.length).to.equal( + TruncatedFormat2.defaultProps.truncateLength! + 3, + ); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.exist; + }); + + it("can always show popover", () => { + const comp = harness.mount(); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.exist; + }); + + it("does not show popover if text is not truncated by default", () => { + const str = `Richard Dawkins`; + const comp = harness.mount({str}); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.not.exist; + }); + + it("doesn't truncate if truncation length is 0", () => { + const str = ` + To be, or not to be--that is the question: + Whether 'tis nobler in the mind to suffer + The slings and arrows of outrageous fortune + Or to take arms against a sea of troubles + And by opposing end them. To die, to sleep-- + No more--and by a sleep to say we end + The heartache, and the thousand natural shocks + That flesh is heir to. 'Tis a consummation + Devoutly to be wished. To die, to sleep-- + To sleep--perchance to dream: ay, there's the rub, + For in that sleep of death what dreams may come + When we have shuffled off this mortal coil, + Must give us pause. There's the respect + That makes calamity of so long life. + For who would bear the whips and scorns of time, + Th' oppressor's wrong, the proud man's contumely + The pangs of despised love, the law's delay, + The insolence of office, and the spurns + That patient merit of th' unworthy takes, + When he himself might his quietus make + With a bare bodkin? Who would fardels bear, + To grunt and sweat under a weary life, + But that the dread of something after death, + The undiscovered country, from whose bourn + No traveller returns, puzzles the will, + And makes us rather bear those ills we have + Than fly to others that we know not of? + Thus conscience does make cowards of us all, + And thus the native hue of resolution + Is sicklied o'er with the pale cast of thought, + And enterprise of great pitch and moment + With this regard their currents turn awry + And lose the name of action. -- Soft you now, + The fair Ophelia! -- Nymph, in thy orisons + Be all my sins remembered. + `; + const comp = harness.mount( +
+ + {str} + +
, + ); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_VALUE}`)!.text()).to.have.lengthOf(str.length); + }); +}); diff --git a/packages/table/test/formats/truncatedFormatTests.tsx b/packages/table/test/formats/truncatedFormatTests.tsx new file mode 100644 index 00000000000..f4c93dd7b93 --- /dev/null +++ b/packages/table/test/formats/truncatedFormatTests.tsx @@ -0,0 +1,203 @@ +/* + * Copyright 2016 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. + */ + +/** + * @fileoverview This component is DEPRECATED, and the code is frozen. + * All changes & bugfixes should be made to TruncatedFormat2 instead. + */ + +/* eslint-disable deprecation/deprecation, @blueprintjs/no-deprecated-components */ + +import { expect } from "chai"; +import * as React from "react"; + +import { TruncatedFormat, TruncatedPopoverMode } from "../../src/cell/formats/truncatedFormat"; +import * as Classes from "../../src/common/classes"; +import { ReactHarness } from "../harness"; +import { createStringOfLength } from "../mocks/table"; + +describe("", () => { + const harness = new ReactHarness(); + + afterEach(() => { + harness.unmount(); + }); + + after(() => { + harness.destroy(); + }); + + it("can automatically truncate and show popover when truncated", () => { + const str = createStringOfLength(TruncatedFormat.defaultProps.truncateLength! + 1); + + const comp = harness.mount( +
+ {str} +
, + ); + const textElement = comp.element!.querySelector(`.${Classes.TABLE_TRUNCATED_VALUE}`)!; + expect(textElement.scrollWidth).to.be.greaterThan(textElement.clientWidth); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.exist; + }); + + // This test was super flaky. It started failing without clear cause when the Table Frozen + // Columns/Rows changes merged, even though nothing about the TruncatedFormat component + // changed. Adding the position: relative rule fixes it, but more investigation is needed. + it("can automatically truncate and show popover when truncated and word wrapped", () => { + const str = ` + We are going to die, and that makes us the lucky ones. Most + people are never going to die because they are never going to + be born. The potential people who could have been here in my + place but who will in fact never see the light of day + outnumber the sand grains of Arabia. Certainly those unborn + ghosts include greater poets than Keats, scientists greater + than Newton. We know this because the set of possible people + allowed by our DNA so massively outnumbers the set of actual + people. In the teeth of these stupefying odds it is you and I, + in our ordinariness, that are here. We privileged few, who won + the lottery of birth against all odds, how dare we whine at + our inevitable return to that prior state from which the vast + majority have never stirred? + `; + + // fix the container's width and height to ensure this test passes + // regardless of the page's dimensions. + const style: React.CSSProperties = { + height: "300px", + position: "relative", + width: "300px", + }; + + const comp = harness.mount( +
+ {str} +
, + ); + const textElement = comp.element!.querySelector(`.${Classes.TABLE_TRUNCATED_VALUE}`)!; + expect(textElement.scrollHeight).to.be.greaterThan(textElement.clientHeight); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.exist; + }); + + it("can automatically truncate and show popover when truncated and word wrapped in approx mode", () => { + const str = ` + We are going to die, and that makes us the lucky ones. Most + people are never going to die because they are never going to + be born. The potential people who could have been here in my + place but who will in fact never see the light of day + outnumber the sand grains of Arabia. Certainly those unborn + ghosts include greater poets than Keats, scientists greater + than Newton. We know this because the set of possible people + allowed by our DNA so massively outnumbers the set of actual + people. In the teeth of these stupefying odds it is you and I, + in our ordinariness, that are here. We privileged few, who won + the lottery of birth against all odds, how dare we whine at + our inevitable return to that prior state from which the vast + majority have never stirred? + `; + + // fix the container's width and height to ensure this test passes + // regardless of the page's dimensions. + const style: React.CSSProperties = { + height: "300px", + position: "relative", + width: "300px", + }; + + const comp = harness.mount( +
+ + {str} + +
, + ); + const textElement = comp.element!.querySelector(`.${Classes.TABLE_TRUNCATED_VALUE}`)!; + expect(textElement.scrollHeight).to.be.greaterThan(textElement.clientHeight); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.exist; + }); + + it("can manually truncate and show popover when truncated", () => { + const str = createStringOfLength(TruncatedFormat.defaultProps.truncateLength! + 1); + const comp = harness.mount({str}); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_VALUE}`)!.text()!.length).to.equal( + TruncatedFormat.defaultProps.truncateLength! + 3, + ); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.exist; + }); + + it("can always show popover", () => { + const comp = harness.mount(); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.exist; + }); + + it("does not show popover if text is not truncated by default", () => { + const str = `Richard Dawkins`; + const comp = harness.mount({str}); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.not.exist; + }); + + it("doesn't truncate if truncation length is 0", () => { + const str = ` + To be, or not to be--that is the question: + Whether 'tis nobler in the mind to suffer + The slings and arrows of outrageous fortune + Or to take arms against a sea of troubles + And by opposing end them. To die, to sleep-- + No more--and by a sleep to say we end + The heartache, and the thousand natural shocks + That flesh is heir to. 'Tis a consummation + Devoutly to be wished. To die, to sleep-- + To sleep--perchance to dream: ay, there's the rub, + For in that sleep of death what dreams may come + When we have shuffled off this mortal coil, + Must give us pause. There's the respect + That makes calamity of so long life. + For who would bear the whips and scorns of time, + Th' oppressor's wrong, the proud man's contumely + The pangs of despised love, the law's delay, + The insolence of office, and the spurns + That patient merit of th' unworthy takes, + When he himself might his quietus make + With a bare bodkin? Who would fardels bear, + To grunt and sweat under a weary life, + But that the dread of something after death, + The undiscovered country, from whose bourn + No traveller returns, puzzles the will, + And makes us rather bear those ills we have + Than fly to others that we know not of? + Thus conscience does make cowards of us all, + And thus the native hue of resolution + Is sicklied o'er with the pale cast of thought, + And enterprise of great pitch and moment + With this regard their currents turn awry + And lose the name of action. -- Soft you now, + The fair Ophelia! -- Nymph, in thy orisons + Be all my sins remembered. + `; + const comp = harness.mount( +
+ + {str} + +
, + ); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_VALUE}`)!.text()).to.have.lengthOf(str.length); + }); +}); diff --git a/packages/table/test/formatsTests.tsx b/packages/table/test/formatsTests.tsx deleted file mode 100644 index 23c6dc4182f..00000000000 --- a/packages/table/test/formatsTests.tsx +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright 2016 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 { expect } from "chai"; -import * as React from "react"; - -import { JSONFormat } from "../src/cell/formats/jsonFormat"; -import { TruncatedFormat, TruncatedPopoverMode } from "../src/cell/formats/truncatedFormat"; -import * as Classes from "../src/common/classes"; -import { ReactHarness } from "./harness"; -import { createStringOfLength } from "./mocks/table"; - -describe("Formats", () => { - const harness = new ReactHarness(); - - afterEach(() => { - harness.unmount(); - }); - - after(() => { - harness.destroy(); - }); - - describe("Truncated Format", () => { - it("can automatically truncate and show popover when truncated", () => { - const str = createStringOfLength(TruncatedFormat.defaultProps.truncateLength! + 1); - - const comp = harness.mount( -
- {str} -
, - ); - const textElement = comp.element!.querySelector(`.${Classes.TABLE_TRUNCATED_VALUE}`)!; - expect(textElement.scrollWidth).to.be.greaterThan(textElement.clientWidth); - expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.exist; - }); - - // This test was super flaky. It started failing without clear cause when the Table Frozen - // Columns/Rows changes merged, even though nothing about the TruncatedFormat component - // changed. Adding the position: relative rule fixes it, but more investigation is needed. - it("can automatically truncate and show popover when truncated and word wrapped", () => { - const str = ` - We are going to die, and that makes us the lucky ones. Most - people are never going to die because they are never going to - be born. The potential people who could have been here in my - place but who will in fact never see the light of day - outnumber the sand grains of Arabia. Certainly those unborn - ghosts include greater poets than Keats, scientists greater - than Newton. We know this because the set of possible people - allowed by our DNA so massively outnumbers the set of actual - people. In the teeth of these stupefying odds it is you and I, - in our ordinariness, that are here. We privileged few, who won - the lottery of birth against all odds, how dare we whine at - our inevitable return to that prior state from which the vast - majority have never stirred? - `; - - // fix the container's width and height to ensure this test passes - // regardless of the page's dimensions. - const style: React.CSSProperties = { - height: "300px", - position: "relative", - width: "300px", - }; - - const comp = harness.mount( -
- {str} -
, - ); - const textElement = comp.element!.querySelector(`.${Classes.TABLE_TRUNCATED_VALUE}`)!; - expect(textElement.scrollHeight).to.be.greaterThan(textElement.clientHeight); - expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.exist; - }); - - it("can automatically truncate and show popover when truncated and word wrapped in approx mode", () => { - const str = ` - We are going to die, and that makes us the lucky ones. Most - people are never going to die because they are never going to - be born. The potential people who could have been here in my - place but who will in fact never see the light of day - outnumber the sand grains of Arabia. Certainly those unborn - ghosts include greater poets than Keats, scientists greater - than Newton. We know this because the set of possible people - allowed by our DNA so massively outnumbers the set of actual - people. In the teeth of these stupefying odds it is you and I, - in our ordinariness, that are here. We privileged few, who won - the lottery of birth against all odds, how dare we whine at - our inevitable return to that prior state from which the vast - majority have never stirred? - `; - - // fix the container's width and height to ensure this test passes - // regardless of the page's dimensions. - const style: React.CSSProperties = { - height: "300px", - position: "relative", - width: "300px", - }; - - const comp = harness.mount( -
- - {str} - -
, - ); - const textElement = comp.element!.querySelector(`.${Classes.TABLE_TRUNCATED_VALUE}`)!; - expect(textElement.scrollHeight).to.be.greaterThan(textElement.clientHeight); - expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.exist; - }); - - it("can manually truncate and show popover when truncated", () => { - const str = createStringOfLength(TruncatedFormat.defaultProps.truncateLength! + 1); - const comp = harness.mount({str}); - expect(comp.find(`.${Classes.TABLE_TRUNCATED_VALUE}`)!.text()!.length).to.equal( - TruncatedFormat.defaultProps.truncateLength! + 3, - ); - expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.exist; - }); - - it("can always show popover", () => { - const comp = harness.mount(); - expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.exist; - }); - - it("does not show popover if text is not truncated by default", () => { - const str = `Richard Dawkins`; - const comp = harness.mount({str}); - expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.not.exist; - }); - - it("doesn't truncate if truncation length is 0", () => { - const str = ` - To be, or not to be--that is the question: - Whether 'tis nobler in the mind to suffer - The slings and arrows of outrageous fortune - Or to take arms against a sea of troubles - And by opposing end them. To die, to sleep-- - No more--and by a sleep to say we end - The heartache, and the thousand natural shocks - That flesh is heir to. 'Tis a consummation - Devoutly to be wished. To die, to sleep-- - To sleep--perchance to dream: ay, there's the rub, - For in that sleep of death what dreams may come - When we have shuffled off this mortal coil, - Must give us pause. There's the respect - That makes calamity of so long life. - For who would bear the whips and scorns of time, - Th' oppressor's wrong, the proud man's contumely - The pangs of despised love, the law's delay, - The insolence of office, and the spurns - That patient merit of th' unworthy takes, - When he himself might his quietus make - With a bare bodkin? Who would fardels bear, - To grunt and sweat under a weary life, - But that the dread of something after death, - The undiscovered country, from whose bourn - No traveller returns, puzzles the will, - And makes us rather bear those ills we have - Than fly to others that we know not of? - Thus conscience does make cowards of us all, - And thus the native hue of resolution - Is sicklied o'er with the pale cast of thought, - And enterprise of great pitch and moment - With this regard their currents turn awry - And lose the name of action. -- Soft you now, - The fair Ophelia! -- Nymph, in thy orisons - Be all my sins remembered. - `; - const comp = harness.mount( -
- - {str} - -
, - ); - expect(comp.find(`.${Classes.TABLE_TRUNCATED_VALUE}`)!.text()).to.have.lengthOf(str.length); - }); - }); - - describe("JSON Format", () => { - it("stringifies JSON", () => { - const obj = { - help: "me", - "i'm": 1234, - }; - const str = JSON.stringify(obj, null, 2); - const comp = harness.mount({obj}); - expect(comp.find(`.${Classes.TABLE_TRUNCATED_FORMAT_TEXT}`)!.text()).to.equal(str); - }); - - it("omits quotes on strings and null-likes", () => { - let comp = harness.mount({"a string"}); - expect(comp.find(`.${Classes.TABLE_TRUNCATED_FORMAT_TEXT}`)!.text()).to.equal("a string"); - - comp = harness.mount({null}); - expect(comp.find(`.${Classes.TABLE_TRUNCATED_FORMAT_TEXT}`)!.text()).to.equal("null"); - - comp = harness.mount({undefined}); - expect(comp.find(`.${Classes.TABLE_TRUNCATED_FORMAT_TEXT}`)!.text()).to.equal("undefined"); - }); - - it("hides popover for null-likes, still passes showPopover prop", () => { - let comp = harness.mount({null}); - expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.not.exist; - - const str = `this is a very long string that will be truncated by the following settings`; - comp = harness.mount( - - {str} - , - ); - expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).exist; - - comp = harness.mount({str}); - expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.not.exist; - }); - }); -}); diff --git a/packages/table/test/index.ts b/packages/table/test/index.ts index 2fda7b0bbf3..323ab0231c2 100644 --- a/packages/table/test/index.ts +++ b/packages/table/test/index.ts @@ -20,12 +20,15 @@ import "./batcherTests.tsx"; import "./cellTests.tsx"; import "./clipboardTests.ts"; import "./columnHeaderCellTests.tsx"; +import "./columnHeaderCell2Tests.tsx"; import "./columnTests.tsx"; import "./common/internal/"; import "./editableCellTests.tsx"; import "./editableCell2Tests.tsx"; import "./editableNameTests.tsx"; -import "./formatsTests.tsx"; +import "./formats/truncatedFormatTests.tsx"; +import "./formats/truncatedFormat2Tests.tsx"; +import "./formats/jsonFormatTests.tsx"; import "./gridTests.ts"; import "./guidesTests.tsx"; import "./loadableContentTests.tsx"; diff --git a/packages/table/test/isotest.js b/packages/table/test/isotest.js index e867cd2725a..bb90b8bf44d 100644 --- a/packages/table/test/isotest.js +++ b/packages/table/test/isotest.js @@ -15,8 +15,9 @@ // @ts-check require("@blueprintjs/test-commons/bootstrap"); + const { generateIsomorphicTests } = require("@blueprintjs/test-commons"); -const React = require("react"); + const Table = require("../lib/cjs"); describe("Table isomorphic rendering", () => { From ebf18cda0d74a030b9812aa5f8117bc3c4854526 Mon Sep 17 00:00:00 2001 From: Adi Dahiya Date: Thu, 4 Aug 2022 15:26:15 -0400 Subject: [PATCH 04/10] update docs index page --- packages/table/src/docs/table.md | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/table/src/docs/table.md b/packages/table/src/docs/table.md index 76a540497e8..e3a83d49840 100644 --- a/packages/table/src/docs/table.md +++ b/packages/table/src/docs/table.md @@ -1,11 +1,12 @@ @# Table -A highly interactive table component. +The [__@blueprintjs/table__ package](https://www.npmjs.com/package/@blueprintjs/select) provides components +to build a highly interactive table or spreadsheet UI.
-If you are looking instead for the Blueprint-styled HTML ``, see -[`HTMLTable` in **@blueprintjs/core**](#core/components/html-table). +If you are looking instead for the simpler Blueprint-styled HTML `
`, see +[the `HTMLTable` component in **@blueprintjs/core**](#core/components/html-table). Make sure to review the [getting started docs for installation info](#blueprint/getting-started). @@ -14,23 +15,20 @@ Make sure to review the [getting started docs for installation info](#blueprint/ npm install --save @blueprintjs/table ``` -Do not forget to include `table.css` and `blueprint-popover2.css` on your page. +Do not forget to include `table.css` on your page: + +```scss +@import "~@blueprintjs/table/lib/css/table.css"; +```
-As of @blueprintjs/table v4.6.0, @blueprintjs/popover2 is a direct dependency and its styles must be imported -in order for some table features to display properly. -
+`ColumnHeaderCell2` and `TruncatedFormat2` (available since @blueprintjs/table v4.6.0) depend on +@blueprintjs/popover2 styles, so you must also import this CSS file for those components to display properly: ```scss -@import "~@blueprintjs/table/lib/css/table.css"; @import "~@blueprintjs/popover2/lib/css/blueprint-popover2.css"; ``` - -
- -There is an updated version of the table component with some new features and compatibility with the -[new hotkeys API](#core/components/hotkeys-target2): see [Table2](#table/table2).
### Features @@ -45,6 +43,12 @@ There is an updated version of the table component with some new features and co @## Basic usage +
+ +There is an updated version of the table component with some new features and compatibility with the +[new hotkeys API](#core/components/hotkeys-target2): see [Table2](#table/table2). +
+ To create a table, you must define the rows and columns. Add children to the `Table` to create columns, and change the `numRows` prop on the `Table` to set the number of rows. From 87f3a50f6071983a3f201b927ae8257ec607398d Mon Sep 17 00:00:00 2001 From: Adi Dahiya Date: Thu, 4 Aug 2022 15:28:54 -0400 Subject: [PATCH 05/10] fix lint --- packages/table-dev-app/src/features.tsx | 18 +++++++++--------- packages/table-dev-app/src/mutableTable.tsx | 10 +++++----- packages/table/src/index.ts | 2 ++ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/table-dev-app/src/features.tsx b/packages/table-dev-app/src/features.tsx index 963450ef249..405e222a403 100644 --- a/packages/table-dev-app/src/features.tsx +++ b/packages/table-dev-app/src/features.tsx @@ -25,11 +25,11 @@ import { MenuItem2 } from "@blueprintjs/popover2"; import { Cell, Column, - ColumnHeaderCell, + ColumnHeaderCell2, + ColumnHeaderCellProps, CopyCellsMenuItem, EditableCell2, EditableName, - IColumnHeaderCellProps, IMenuContext, JSONFormat, Region, @@ -238,7 +238,7 @@ class EditableTable extends React.Component<{}, IEditableTableState> { ); }; return ( - { - return ; + return ; }, }, { @@ -535,13 +535,13 @@ ReactDOM.render( columnHeaderCellRenderer: (columnIndex: number) => { const alpha = Utils.toBase26Alpha(columnIndex); return ( -

Header {alpha}

Whatever interactive header content goes here lorem ipsum.

-
+
); }, }, @@ -554,9 +554,9 @@ ReactDOM.render( document.getElementById("table-6"), ); -class CustomHeaderCell extends React.Component { +class CustomHeaderCell extends React.Component { public render() { - return Hey dawg.; + return Hey dawg.; } } @@ -687,7 +687,7 @@ ReactDOM.render(, document.getElementById("table-10") ReactDOM.render(
- } /> + } />
, document.getElementById("table-11"), diff --git a/packages/table-dev-app/src/mutableTable.tsx b/packages/table-dev-app/src/mutableTable.tsx index 93a63be93ea..f70d50b142e 100644 --- a/packages/table-dev-app/src/mutableTable.tsx +++ b/packages/table-dev-app/src/mutableTable.tsx @@ -38,7 +38,7 @@ import { MenuItem2 } from "@blueprintjs/popover2"; import { Cell, Column, - ColumnHeaderCell, + ColumnHeaderCell2, EditableCell2, EditableName, FocusedCellCoordinates, @@ -51,7 +51,7 @@ import { StyledRegionGroup, Table2, TableLoadingOption, - TruncatedFormat, + TruncatedFormat2, TruncatedPopoverMode, Utils, } from "@blueprintjs/table"; @@ -449,7 +449,7 @@ export class MutableTable extends React.Component<{}, IMutableTableState> { private renderColumnHeaderCell = (columnIndex: number) => { return ( - { } else if (this.state.enableCellTruncation) { return ( - { truncationSuffix="..." > {valueAsString} - + ); } else { diff --git a/packages/table/src/index.ts b/packages/table/src/index.ts index f7aed8f9041..161f13fbfb0 100644 --- a/packages/table/src/index.ts +++ b/packages/table/src/index.ts @@ -31,6 +31,8 @@ export { ITruncatedFormatProps, } from "./cell/formats/truncatedFormat"; +export { TruncatedFormat2 } from "./cell/formats/truncatedFormat2"; + export { Column, ColumnProps, IColumnProps } from "./column"; export { From c2e59508ec3df2040e0a7bd57a81810c293a2d02 Mon Sep 17 00:00:00 2001 From: Adi Dahiya Date: Thu, 4 Aug 2022 15:37:55 -0400 Subject: [PATCH 06/10] more migrations, docs updates --- packages/demo-app/src/examples/TableExample.tsx | 4 ++-- .../examples/table-examples/cellLoadingExample.tsx | 4 ++-- .../examples/table-examples/tableDollarExample.tsx | 4 ++-- .../table-examples/tableEditableExample.tsx | 4 ++-- .../table-examples/tableFormatsExample.tsx | 4 ++-- .../table-examples/tableSortableExample.tsx | 4 ++-- packages/table/src/column.tsx | 2 +- packages/table/src/docs/table-api.md | 14 +++++++------- packages/table/src/docs/table-features.md | 8 ++++---- packages/table/src/headers/columnHeader.tsx | 10 +++++----- packages/table/src/headers/columnHeaderCell.tsx | 2 +- .../table/src/quadrants/tableQuadrantStack.tsx | 2 +- 12 files changed, 31 insertions(+), 31 deletions(-) diff --git a/packages/demo-app/src/examples/TableExample.tsx b/packages/demo-app/src/examples/TableExample.tsx index 6c7fecbc482..ee40f202224 100644 --- a/packages/demo-app/src/examples/TableExample.tsx +++ b/packages/demo-app/src/examples/TableExample.tsx @@ -16,7 +16,7 @@ import * as React from "react"; -import { Cell, Column, ColumnHeaderCell, Table2 } from "@blueprintjs/table"; +import { Cell, Column, ColumnHeaderCell2, Table2 } from "@blueprintjs/table"; import { ExampleCard } from "./ExampleCard"; @@ -27,7 +27,7 @@ export const TableExample = React.memo(() => { [], ); const columnHeaderCellRenderer = React.useCallback( - (index: number) => , + (index: number) => , [], ); return ( diff --git a/packages/docs-app/src/examples/table-examples/cellLoadingExample.tsx b/packages/docs-app/src/examples/table-examples/cellLoadingExample.tsx index 6f0a04a71e6..6bf12162597 100644 --- a/packages/docs-app/src/examples/table-examples/cellLoadingExample.tsx +++ b/packages/docs-app/src/examples/table-examples/cellLoadingExample.tsx @@ -17,7 +17,7 @@ import * as React from "react"; import { RadioGroup } from "@blueprintjs/core"; import { Example, handleStringChange, IExampleProps } from "@blueprintjs/docs-theme"; -import { Cell, Column, ColumnHeaderCell, RowHeaderCell, Table2 } from "@blueprintjs/table"; +import { Cell, Column, ColumnHeaderCell2, RowHeaderCell, Table2 } from "@blueprintjs/table"; interface IBigSpaceRock { [key: string]: number | string; @@ -122,7 +122,7 @@ export class CellLoadingExample extends React.PureComponent firstCharacter.toUpperCase()); - return ; + return ; }; private renderRowHeaderCell = (rowIndex: number) => { diff --git a/packages/docs-app/src/examples/table-examples/tableDollarExample.tsx b/packages/docs-app/src/examples/table-examples/tableDollarExample.tsx index d0df6033acc..c27c8c9b16c 100644 --- a/packages/docs-app/src/examples/table-examples/tableDollarExample.tsx +++ b/packages/docs-app/src/examples/table-examples/tableDollarExample.tsx @@ -18,7 +18,7 @@ import * as React from "react"; import { Classes } from "@blueprintjs/core"; import { Example, IExampleProps } from "@blueprintjs/docs-theme"; -import { Cell, Column, ColumnHeaderCell, Table2 } from "@blueprintjs/table"; +import { Cell, Column, ColumnHeaderCell2, Table2 } from "@blueprintjs/table"; // this will obviously get outdated, it's valid only as of August 2021 const USD_TO_EURO_CONVERSION = 0.85; @@ -42,7 +42,7 @@ export class TableDollarExample extends React.PureComponent { function renderColumnHeader(index: number) { const name = ["Dollars", "Euros"][index]!; - return ; + return ; } function renderName(name: string) { diff --git a/packages/docs-app/src/examples/table-examples/tableEditableExample.tsx b/packages/docs-app/src/examples/table-examples/tableEditableExample.tsx index 0444f6df659..4d16c05232d 100644 --- a/packages/docs-app/src/examples/table-examples/tableEditableExample.tsx +++ b/packages/docs-app/src/examples/table-examples/tableEditableExample.tsx @@ -18,7 +18,7 @@ import * as React from "react"; import { Intent } from "@blueprintjs/core"; import { Example, IExampleProps } from "@blueprintjs/docs-theme"; -import { Column, ColumnHeaderCell, EditableCell2, EditableName, Table2 } from "@blueprintjs/table"; +import { Column, ColumnHeaderCell2, EditableCell2, EditableName, Table2 } from "@blueprintjs/table"; export interface ITableEditableExampleState { columnNames?: string[]; @@ -83,7 +83,7 @@ export class TableEditableExample extends React.PureComponent ); }; - return ; + return ; }; private isValidValue(value: string) { diff --git a/packages/docs-app/src/examples/table-examples/tableFormatsExample.tsx b/packages/docs-app/src/examples/table-examples/tableFormatsExample.tsx index 0719c27a734..f060471fced 100644 --- a/packages/docs-app/src/examples/table-examples/tableFormatsExample.tsx +++ b/packages/docs-app/src/examples/table-examples/tableFormatsExample.tsx @@ -17,7 +17,7 @@ import * as React from "react"; import { Example, IExampleProps } from "@blueprintjs/docs-theme"; -import { Cell, Column, JSONFormat, Table2, TruncatedFormat } from "@blueprintjs/table"; +import { Cell, Column, JSONFormat, Table2, TruncatedFormat2 } from "@blueprintjs/table"; interface ITimezone { name: string; @@ -113,7 +113,7 @@ export class TableFormatsExample extends React.PureComponent { }); return ( - {formattedDateTime} + {formattedDateTime} ); }; diff --git a/packages/docs-app/src/examples/table-examples/tableSortableExample.tsx b/packages/docs-app/src/examples/table-examples/tableSortableExample.tsx index a2fa19116aa..1d547dd5d98 100644 --- a/packages/docs-app/src/examples/table-examples/tableSortableExample.tsx +++ b/packages/docs-app/src/examples/table-examples/tableSortableExample.tsx @@ -24,7 +24,7 @@ import { MenuItem2 } from "@blueprintjs/popover2"; import { Cell, Column, - ColumnHeaderCell, + ColumnHeaderCell2, CopyCellsMenuItem, IMenuContext, SelectionModes, @@ -50,7 +50,7 @@ abstract class AbstractSortableColumn implements ISortableColumn { {getCellData(rowIndex, columnIndex)} ); const menuRenderer = this.renderMenu.bind(this, sortColumn); - const columnHeaderCellRenderer = () => ; + const columnHeaderCellRenderer = () => ; return ( {content} +return {content} ``` @interface ITruncatedFormatProps diff --git a/packages/table/src/docs/table-features.md b/packages/table/src/docs/table-features.md index 6ce10950f4a..899979f9b12 100644 --- a/packages/table/src/docs/table-features.md +++ b/packages/table/src/docs/table-features.md @@ -56,7 +56,7 @@ To make your table editable, use the [`EditableCell2`](#table/table2.editablecel `EditableName` components to create editable table cells and column names. To further extend the interactivity of the column headers, you can -add children components to each `ColumnHeaderCell` defined in the +add children components to each `ColumnHeaderCell2` defined in the `columnHeaderCellRenderer` prop of `Column`. The following example renders a table with editable column names (single @@ -144,7 +144,7 @@ individual column's header and body cells. Try selecting a different column in t @### Cells -`Cell`, `EditableCell`, `ColumnHeaderCell`, and `RowHeaderCell` expose a `loading` prop for granular +`Cell`, `EditableCell`, `ColumnHeaderCell2`, and `RowHeaderCell` expose a `loading` prop for granular control of which cells should show a loading state. Try selecting a different preset loading configuration. @@ -153,11 +153,11 @@ configuration. @## Formatting To display long strings or native JavaScript objects, we provide -`` and `` components, which are designed to be used +`` and `` components, which are designed to be used within a ``. Below is a table of timezones including the local time when this page was -rendered. It uses a `` component to show the long date string +rendered. It uses a `` component to show the long date string and a `` component to show the timezone info object. @reactExample TableFormatsExample diff --git a/packages/table/src/headers/columnHeader.tsx b/packages/table/src/headers/columnHeader.tsx index f0feb3c018b..0d18973b2ed 100644 --- a/packages/table/src/headers/columnHeader.tsx +++ b/packages/table/src/headers/columnHeader.tsx @@ -26,11 +26,11 @@ import { IClientCoordinates } from "../interactions/dragTypes"; import { IIndexedResizeCallback } from "../interactions/resizable"; import { Orientation } from "../interactions/resizeHandle"; import { RegionCardinality, Regions } from "../regions"; -import { ColumnHeaderCell, IColumnHeaderCellProps } from "./columnHeaderCell"; +import { ColumnHeaderCell2, ColumnHeaderCellProps } from "./columnHeaderCell2"; import { Header, IHeaderProps } from "./header"; /** @deprecated use ColumnHeaderRenderer */ -export type IColumnHeaderRenderer = (columnIndex: number) => React.ReactElement | null; +export type IColumnHeaderRenderer = (columnIndex: number) => React.ReactElement | null; // eslint-disable-next-line deprecation/deprecation export type ColumnHeaderRenderer = IColumnHeaderRenderer; @@ -44,8 +44,8 @@ export interface IColumnHeaderProps extends IHeaderProps, IColumnWidths, ColumnI /** * A ColumnHeaderRenderer that, for each ``, will delegate to: * 1. The `columnHeaderCellRenderer` method from the `` - * 2. A `` using the `name` prop from the `` - * 3. A `` with a `name` generated from `Utils.toBase26Alpha` + * 2. A `` using the `name` prop from the `` + * 3. A `` with a `name` generated from `Utils.toBase26Alpha` */ cellRenderer: ColumnHeaderRenderer; @@ -211,7 +211,7 @@ export class ColumnHeader extends React.Component { width: `${rect.width}px`, }; return ( - `. + * each ``. * * REQUIRES QUADRANT RESYNC * From 1d70e73a73b97941cbb68cdb393f8a8f6ae14ed9 Mon Sep 17 00:00:00 2001 From: Adi Dahiya Date: Thu, 4 Aug 2022 16:55:34 -0400 Subject: [PATCH 07/10] create JSONFormat2 --- .../src/examples/table-examples/tableFormatsExample.tsx | 4 ++-- packages/table-dev-app/src/features.tsx | 8 ++++---- packages/table-dev-app/src/mutableTable.tsx | 6 +++--- packages/table/src/cell/cell.tsx | 9 ++++++++- packages/table/src/cell/formats/_formats.scss | 2 +- packages/table/src/cell/formats/jsonFormat.tsx | 8 ++++++++ packages/table/src/docs/table-api.md | 4 ++-- packages/table/src/docs/table-features.md | 6 +++--- packages/table/src/index.ts | 2 ++ packages/table/src/table.tsx | 9 +++++++-- packages/table/src/table2.tsx | 8 ++++---- packages/table/test/formats/jsonFormatTests.tsx | 7 +++++++ 12 files changed, 51 insertions(+), 22 deletions(-) diff --git a/packages/docs-app/src/examples/table-examples/tableFormatsExample.tsx b/packages/docs-app/src/examples/table-examples/tableFormatsExample.tsx index f060471fced..99a7e0c37c8 100644 --- a/packages/docs-app/src/examples/table-examples/tableFormatsExample.tsx +++ b/packages/docs-app/src/examples/table-examples/tableFormatsExample.tsx @@ -17,7 +17,7 @@ import * as React from "react"; import { Example, IExampleProps } from "@blueprintjs/docs-theme"; -import { Cell, Column, JSONFormat, Table2, TruncatedFormat2 } from "@blueprintjs/table"; +import { Cell, Column, JSONFormat2, Table2, TruncatedFormat2 } from "@blueprintjs/table"; interface ITimezone { name: string; @@ -120,7 +120,7 @@ export class TableFormatsExample extends React.PureComponent { private renderJSON = (row: number) => ( - {this.data[row]} + {this.data[row]} ); } diff --git a/packages/table-dev-app/src/features.tsx b/packages/table-dev-app/src/features.tsx index 405e222a403..01eb6393d73 100644 --- a/packages/table-dev-app/src/features.tsx +++ b/packages/table-dev-app/src/features.tsx @@ -31,7 +31,7 @@ import { EditableCell2, EditableName, IMenuContext, - JSONFormat, + JSONFormat2, Region, RegionCardinality, Regions, @@ -153,19 +153,19 @@ class FormatsTable extends React.Component { private renderJSONCell = (row: number) => ( - {this.objects[row]} + {this.objects[row]} ); private renderJSONCellWrappedText = (row: number) => ( - {this.objects[row]} + {this.objects[row]} ); private renderJSONWrappedCell = (row: number) => ( - {this.objects[row]} + {this.objects[row]} ); } diff --git a/packages/table-dev-app/src/mutableTable.tsx b/packages/table-dev-app/src/mutableTable.tsx index f70d50b142e..f428c27afdb 100644 --- a/packages/table-dev-app/src/mutableTable.tsx +++ b/packages/table-dev-app/src/mutableTable.tsx @@ -42,7 +42,7 @@ import { EditableCell2, EditableName, FocusedCellCoordinates, - JSONFormat, + JSONFormat2, Region, RegionCardinality, Regions, @@ -582,14 +582,14 @@ export class MutableTable extends React.Component<{}, IMutableTableState> { } else if (this.state.cellContent === CellContent.LARGE_JSON) { return ( - {valueAsString} - + ); } else if (this.state.enableCellTruncation) { diff --git a/packages/table/src/cell/cell.tsx b/packages/table/src/cell/cell.tsx index 94446e09cb2..4a93b4fee62 100644 --- a/packages/table/src/cell/cell.tsx +++ b/packages/table/src/cell/cell.tsx @@ -28,7 +28,9 @@ import { import * as Classes from "../common/classes"; import { LoadableContent } from "../common/loadableContent"; import { JSONFormat } from "./formats/jsonFormat"; +import { JSONFormat2 } from "./formats/jsonFormat2"; import { TruncatedFormat } from "./formats/truncatedFormat"; +import { TruncatedFormat2 } from "./formats/truncatedFormat2"; export type CellProps = ICellProps; export interface ICellProps extends IntentProps, Props { @@ -173,7 +175,12 @@ export class Cell extends React.Component { // note: these aren't actually used by truncated format, just in shouldComponentUpdate const modifiedChildren = React.Children.map(this.props.children, child => { const isFormatElement = - CoreUtils.isElementOfType(child, TruncatedFormat) || CoreUtils.isElementOfType(child, JSONFormat); + // eslint-disable-next-line deprecation/deprecation + CoreUtils.isElementOfType(child, TruncatedFormat) || + CoreUtils.isElementOfType(child, TruncatedFormat2) || + // eslint-disable-next-line deprecation/deprecation + CoreUtils.isElementOfType(child, JSONFormat) || + CoreUtils.isElementOfType(child, JSONFormat2); if (style != null && React.isValidElement(child) && isFormatElement) { return React.cloneElement(child as React.ReactElement, { parentCellHeight: style.height === undefined ? undefined : parseInt(style.height.toString(), 10), diff --git a/packages/table/src/cell/formats/_formats.scss b/packages/table/src/cell/formats/_formats.scss index a6fdc014b6d..f9cb27ac340 100644 --- a/packages/table/src/cell/formats/_formats.scss +++ b/packages/table/src/cell/formats/_formats.scss @@ -32,7 +32,7 @@ } .#{$ns}-table-truncated-popover-target { - border-radius: 3px; + border-radius: $pt-border-radius; bottom: 0; cursor: pointer; diff --git a/packages/table/src/cell/formats/jsonFormat.tsx b/packages/table/src/cell/formats/jsonFormat.tsx index 9ce942650b5..ad8a1d2fc39 100644 --- a/packages/table/src/cell/formats/jsonFormat.tsx +++ b/packages/table/src/cell/formats/jsonFormat.tsx @@ -14,6 +14,13 @@ * limitations under the License. */ +/** + * @fileoverview This component is DEPRECATED, and the code is frozen. + * All changes & bugfixes should be made to JSONFormat2 instead. + */ + +/* eslint-disable deprecation/deprecation, @blueprintjs/no-deprecated-components */ + import classNames from "classnames"; import * as React from "react"; @@ -43,6 +50,7 @@ export interface IJSONFormatProps extends ITruncatedFormatProps { } /* istanbul ignore next */ +/** @deprecated use JSONFormat2 */ export class JSONFormat extends React.Component { public static displayName = `${DISPLAYNAME_PREFIX}.JSONFormat`; diff --git a/packages/table/src/docs/table-api.md b/packages/table/src/docs/table-api.md index 75bc4405147..0fe12f16eef 100644 --- a/packages/table/src/docs/table-api.md +++ b/packages/table/src/docs/table-api.md @@ -186,11 +186,11 @@ return {content} @## JSONFormat -Wrap your JavaScript object cell contents with a `JSONFormat` component like so: +Wrap your JavaScript object cell contents with a `JSONFormat2` component like so: ```tsx const content = { any: "javascript variable", even: [null, "is", "okay", "too"] }; -return {content} +return {content} ``` @interface IJSONFormatProps diff --git a/packages/table/src/docs/table-features.md b/packages/table/src/docs/table-features.md index 899979f9b12..1416b214d44 100644 --- a/packages/table/src/docs/table-features.md +++ b/packages/table/src/docs/table-features.md @@ -153,12 +153,12 @@ configuration. @## Formatting To display long strings or native JavaScript objects, we provide -`` and `` components, which are designed to be used -within a ``. +`` and `` components. These are designed to be used within a ``, +where they will render a popover to show the full cell contents on click. Below is a table of timezones including the local time when this page was rendered. It uses a `` component to show the long date string -and a `` component to show the timezone info object. +and a `` component to show the timezone info object. @reactExample TableFormatsExample diff --git a/packages/table/src/index.ts b/packages/table/src/index.ts index 161f13fbfb0..c946a5c1295 100644 --- a/packages/table/src/index.ts +++ b/packages/table/src/index.ts @@ -24,6 +24,8 @@ export { EditableCell2, EditableCell2Props } from "./cell/editableCell2"; export { JSONFormat, IJSONFormatProps, JSONFormatProps } from "./cell/formats/jsonFormat"; +export { JSONFormat2 } from "./cell/formats/jsonFormat2"; + export { TruncatedPopoverMode, TruncatedFormat, diff --git a/packages/table/src/table.tsx b/packages/table/src/table.tsx index 79bc5aef5cf..3eb6d03f882 100644 --- a/packages/table/src/table.tsx +++ b/packages/table/src/table.tsx @@ -14,6 +14,13 @@ * limitations under the License. */ +/** + * @fileoverview This component is DEPRECATED, and the code is frozen. + * All changes & bugfixes should be made to Table2 instead. + */ + +/* eslint-disable deprecation/deprecation, @blueprintjs/no-deprecated-components */ + import classNames from "classnames"; import * as React from "react"; import innerText from "react-innertext"; @@ -61,8 +68,6 @@ import type { TableProps, TablePropsDefaults, TablePropsWithDefaults } from "./t import type { TableSnapshot, TableState } from "./tableState"; import { clampNumFrozenColumns, clampNumFrozenRows, hasLoadingOption } from "./tableUtils"; -/* eslint-disable deprecation/deprecation */ - /** @deprecated use Table2, which supports usage of the new hotkeys API in the same application */ @HotkeysTarget export class Table extends AbstractComponent2 { diff --git a/packages/table/src/table2.tsx b/packages/table/src/table2.tsx index 64995cf09b8..7dbfaf121f4 100644 --- a/packages/table/src/table2.tsx +++ b/packages/table/src/table2.tsx @@ -40,7 +40,7 @@ import { Rect } from "./common/rect"; import { RenderMode } from "./common/renderMode"; import { Utils } from "./common/utils"; import { ColumnHeader } from "./headers/columnHeader"; -import { ColumnHeaderCell, IColumnHeaderCellProps } from "./headers/columnHeaderCell"; +import { ColumnHeaderCell2, ColumnHeaderCellProps } from "./headers/columnHeaderCell2"; import { renderDefaultRowHeader, RowHeader } from "./headers/rowHeader"; import { ResizeSensor } from "./interactions/resizeSensor"; import { GuideLayer } from "./layers/guides"; @@ -768,16 +768,16 @@ export class Table2 extends AbstractComponent2; + return ; } else { - return ; + return ; } }; diff --git a/packages/table/test/formats/jsonFormatTests.tsx b/packages/table/test/formats/jsonFormatTests.tsx index 2d9365020f5..28b3c07fb71 100644 --- a/packages/table/test/formats/jsonFormatTests.tsx +++ b/packages/table/test/formats/jsonFormatTests.tsx @@ -14,6 +14,13 @@ * limitations under the License. */ +/** + * @fileoverview This component is DEPRECATED, and the code is frozen. + * All changes & bugfixes should be made to JSONFormat2 instead. + */ + +/* eslint-disable deprecation/deprecation, @blueprintjs/no-deprecated-components */ + import { expect } from "chai"; import * as React from "react"; From 75a7c19064565c8031d524b43239cfdb02aa9f4c Mon Sep 17 00:00:00 2001 From: Adi Dahiya Date: Thu, 4 Aug 2022 17:12:43 -0400 Subject: [PATCH 08/10] update docs --- packages/table/src/docs/table-api.md | 12 +++++++++++- packages/table/src/docs/table-features.md | 10 ++++++++++ packages/table/src/docs/table.md | 5 +++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/table/src/docs/table-api.md b/packages/table/src/docs/table-api.md index 0fe12f16eef..97a81181769 100644 --- a/packages/table/src/docs/table-api.md +++ b/packages/table/src/docs/table-api.md @@ -53,7 +53,7 @@ returned from the `cellRenderer` method of each `Column`. @interface ICellProps -@## ColumnHeaderCell +@## ColumnHeaderCell2 Customize how each column header is displayed. @@ -62,6 +62,16 @@ The `columnHeaderCellRenderer` method on each `Column` should return a the name of the column. If you want to override the render behavior of the name, you can supply a `nameRenderer` prop to the `ColumnHeaderCell2`. +
+ +`ColumnHeaderCell2` depends on @blueprintjs/popover2 styles, so you must remember to import +that package's stylesheet in your application in addition to `table.css`: + +```scss +@import "~@blueprintjs/popover2/lib/css/blueprint-popover2.css"; +``` +
+ @interface IColumnHeaderCellProps @## EditableName diff --git a/packages/table/src/docs/table-features.md b/packages/table/src/docs/table-features.md index 1416b214d44..1dbe24491ce 100644 --- a/packages/table/src/docs/table-features.md +++ b/packages/table/src/docs/table-features.md @@ -160,6 +160,16 @@ Below is a table of timezones including the local time when this page was rendered. It uses a `` component to show the long date string and a `` component to show the timezone info object. +
+ +These cell formatting components depend on @blueprintjs/popover2 styles, so you must remember to import +that package's stylesheet in your application in addition to `table.css`: + +```scss +@import "~@blueprintjs/popover2/lib/css/blueprint-popover2.css"; +``` +
+ @reactExample TableFormatsExample @## Freezing diff --git a/packages/table/src/docs/table.md b/packages/table/src/docs/table.md index e3a83d49840..9a6abdc9f9c 100644 --- a/packages/table/src/docs/table.md +++ b/packages/table/src/docs/table.md @@ -23,8 +23,9 @@ Do not forget to include `table.css` on your page:
-`ColumnHeaderCell2` and `TruncatedFormat2` (available since @blueprintjs/table v4.6.0) depend on -@blueprintjs/popover2 styles, so you must also import this CSS file for those components to display properly: +`ColumnHeaderCell2`, `JSONFormat2`, and `TruncatedFormat2` (available since @blueprintjs/table v4.6.0) +depend on @blueprintjs/popover2 styles, so you must also import this CSS file for those components +to display properly: ```scss @import "~@blueprintjs/popover2/lib/css/blueprint-popover2.css"; From e28ae4d092f69bdda8264ec1290b5038a743a1b0 Mon Sep 17 00:00:00 2001 From: Adi Dahiya Date: Thu, 4 Aug 2022 17:12:51 -0400 Subject: [PATCH 09/10] commit missing files --- .../table/src/cell/formats/jsonFormat2.tsx | 62 ++++++++++++++++ .../table/test/formats/jsonFormat2Tests.tsx | 72 +++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 packages/table/src/cell/formats/jsonFormat2.tsx create mode 100644 packages/table/test/formats/jsonFormat2Tests.tsx diff --git a/packages/table/src/cell/formats/jsonFormat2.tsx b/packages/table/src/cell/formats/jsonFormat2.tsx new file mode 100644 index 00000000000..213efab1f95 --- /dev/null +++ b/packages/table/src/cell/formats/jsonFormat2.tsx @@ -0,0 +1,62 @@ +/* + * 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 * as React from "react"; + +import { DISPLAYNAME_PREFIX } from "@blueprintjs/core"; + +import * as Classes from "../../common/classes"; +import type { JSONFormatProps } from "./jsonFormat"; +import { TruncatedPopoverMode } from "./truncatedFormat"; +import { TruncatedFormat2 } from "./truncatedFormat2"; + +/* istanbul ignore next */ +export class JSONFormat2 extends React.Component { + public static displayName = `${DISPLAYNAME_PREFIX}.JSONFormat2`; + + public static defaultProps: JSONFormatProps = { + omitQuotesOnStrings: true, + stringify: (obj: any) => JSON.stringify(obj, null, 2), + }; + + public render() { + const { children, omitQuotesOnStrings, stringify } = this.props; + let { showPopover } = this.props; + + // always hide popover if value is nully + const isNully = children == null; + if (isNully) { + showPopover = TruncatedPopoverMode.NEVER; + } + const className = classNames(this.props.className, { + [Classes.TABLE_NULL]: isNully, + }); + + let displayValue = ""; + if (omitQuotesOnStrings && typeof children === "string") { + displayValue = children; + } else { + displayValue = stringify!(children); + } + + return ( + + {displayValue} + + ); + } +} diff --git a/packages/table/test/formats/jsonFormat2Tests.tsx b/packages/table/test/formats/jsonFormat2Tests.tsx new file mode 100644 index 00000000000..224e133c1ba --- /dev/null +++ b/packages/table/test/formats/jsonFormat2Tests.tsx @@ -0,0 +1,72 @@ +/* + * Copyright 2016 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 { expect } from "chai"; +import * as React from "react"; + +import { JSONFormat2 } from "../../src/cell/formats/jsonFormat2"; +import { TruncatedPopoverMode } from "../../src/cell/formats/truncatedFormat"; +import * as Classes from "../../src/common/classes"; +import { ReactHarness } from "../harness"; + +describe("", () => { + const harness = new ReactHarness(); + + afterEach(() => { + harness.unmount(); + }); + + after(() => { + harness.destroy(); + }); + + it("stringifies JSON", () => { + const obj = { + help: "me", + "i'm": 1234, + }; + const str = JSON.stringify(obj, null, 2); + const comp = harness.mount({obj}); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_FORMAT_TEXT}`)!.text()).to.equal(str); + }); + + it("omits quotes on strings and null-likes", () => { + let comp = harness.mount({"a string"}); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_FORMAT_TEXT}`)!.text()).to.equal("a string"); + + comp = harness.mount({null}); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_FORMAT_TEXT}`)!.text()).to.equal("null"); + + comp = harness.mount({undefined}); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_FORMAT_TEXT}`)!.text()).to.equal("undefined"); + }); + + it("hides popover for null-likes, still passes showPopover prop", () => { + let comp = harness.mount({null}); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.not.exist; + + const str = `this is a very long string that will be truncated by the following settings`; + comp = harness.mount( + + {str} + , + ); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).exist; + + comp = harness.mount({str}); + expect(comp.find(`.${Classes.TABLE_TRUNCATED_POPOVER_TARGET}`)!.element).to.not.exist; + }); +}); From bcb28a1d9f9041c5a4e4da19a4a438f64efa5601 Mon Sep 17 00:00:00 2001 From: Adi Dahiya Date: Thu, 4 Aug 2022 17:44:12 -0400 Subject: [PATCH 10/10] update docs, fix formatting example to detect truncation --- .../src/examples/table-examples/tableFormatsExample.tsx | 4 ++-- packages/table/src/docs/table-api.md | 4 +++- packages/table/src/docs/table-features.md | 6 ++++-- packages/table/src/docs/table.md | 6 ++++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/docs-app/src/examples/table-examples/tableFormatsExample.tsx b/packages/docs-app/src/examples/table-examples/tableFormatsExample.tsx index 99a7e0c37c8..bec43891ad7 100644 --- a/packages/docs-app/src/examples/table-examples/tableFormatsExample.tsx +++ b/packages/docs-app/src/examples/table-examples/tableFormatsExample.tsx @@ -113,14 +113,14 @@ export class TableFormatsExample extends React.PureComponent { }); return ( - {formattedDateTime} + {formattedDateTime} ); }; private renderJSON = (row: number) => ( - {this.data[row]} + {this.data[row]} ); } diff --git a/packages/table/src/docs/table-api.md b/packages/table/src/docs/table-api.md index 97a81181769..10bdaa8cd4f 100644 --- a/packages/table/src/docs/table-api.md +++ b/packages/table/src/docs/table-api.md @@ -64,7 +64,9 @@ name, you can supply a `nameRenderer` prop to the `ColumnHeaderCell2`.
-`ColumnHeaderCell2` depends on @blueprintjs/popover2 styles, so you must remember to import +

Additional CSS required

+ +__ColumnHeaderCell2__ depends on @blueprintjs/popover2 styles, so you must remember to import that package's stylesheet in your application in addition to `table.css`: ```scss diff --git a/packages/table/src/docs/table-features.md b/packages/table/src/docs/table-features.md index 1dbe24491ce..20c6734cf8e 100644 --- a/packages/table/src/docs/table-features.md +++ b/packages/table/src/docs/table-features.md @@ -157,11 +157,13 @@ To display long strings or native JavaScript objects, we provide where they will render a popover to show the full cell contents on click. Below is a table of timezones including the local time when this page was -rendered. It uses a `` component to show the long date string -and a `` component to show the timezone info object. +rendered. It uses a `` component to show the long date string +and a `` component to show the timezone info object.
+

Additional CSS required

+ These cell formatting components depend on @blueprintjs/popover2 styles, so you must remember to import that package's stylesheet in your application in addition to `table.css`: diff --git a/packages/table/src/docs/table.md b/packages/table/src/docs/table.md index 9a6abdc9f9c..870e5939adf 100644 --- a/packages/table/src/docs/table.md +++ b/packages/table/src/docs/table.md @@ -5,7 +5,7 @@ to build a highly interactive table or spreadsheet UI.
-If you are looking instead for the simpler Blueprint-styled HTML `
`, see +If you are looking for the simpler Blueprint-styled HTML `
` instead, see [the `HTMLTable` component in **@blueprintjs/core**](#core/components/html-table). @@ -23,7 +23,9 @@ Do not forget to include `table.css` on your page:
-`ColumnHeaderCell2`, `JSONFormat2`, and `TruncatedFormat2` (available since @blueprintjs/table v4.6.0) +

Additional CSS required

+ +__ColumnHeaderCell2__, __JSONFormat2__, and __TruncatedFormat2__ (available since @blueprintjs/table v4.6.0) depend on @blueprintjs/popover2 styles, so you must also import this CSS file for those components to display properly: