From d29243c7806843187c19ef5ff2c2104fff5aa0fe Mon Sep 17 00:00:00 2001 From: alanv Date: Wed, 17 Dec 2025 16:07:23 -0600 Subject: [PATCH 01/14] Add getTextAlignClassName --- packages/components/src/index.ts | 3 +- .../src/internal/components/base/Grid.tsx | 56 ++++++++++--------- .../components/base/models/GridColumn.tsx | 16 ++++++ .../components/editable/EditableGrid.tsx | 11 ++-- .../ExpirationDateColumnRenderer.tsx | 16 ++---- .../renderers/StorageStatusRenderer.tsx | 2 +- packages/components/src/theme/grid.scss | 10 ++++ 7 files changed, 68 insertions(+), 46 deletions(-) diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index bafbf59c72..64dba46034 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -22,7 +22,7 @@ import { hasParameter, imageURL, toggleParameter } from './internal/url/ActionUR import { encodeFormDataQuote } from './internal/url/utils'; import { Container } from './internal/components/base/models/Container'; import { hasAllPermissions, hasAnyPermissions, hasPermissions, User } from './internal/components/base/models/User'; -import { GridColumn } from './internal/components/base/models/GridColumn'; +import { getTextAlignClassName, GridColumn } from './internal/components/base/models/GridColumn'; import { decodePart, encodePart, getSchemaQuery, resolveKey, SchemaQuery } from './public/SchemaQuery'; import { insertColumnFilter, Operation, QueryColumn, QueryLookup } from './public/QueryColumn'; import { QuerySort } from './public/QuerySort'; @@ -1414,6 +1414,7 @@ export { Grid, GRID_CHECKBOX_OPTIONS, GridAliquotViewSelector, + getTextAlignClassName, GridColumn, GridPanel, GridPanelWithModel, diff --git a/packages/components/src/internal/components/base/Grid.tsx b/packages/components/src/internal/components/base/Grid.tsx index 2b970c9176..ad5451caa6 100644 --- a/packages/components/src/internal/components/base/Grid.tsx +++ b/packages/components/src/internal/components/base/Grid.tsx @@ -22,7 +22,7 @@ import { HelpTipRenderer } from '../forms/HelpTipRenderer'; import { GRID_HEADER_CELL_BODY, GRID_SELECTION_INDEX } from '../../constants'; import { LabelHelpTip } from './LabelHelpTip'; -import { GridColumn } from './models/GridColumn'; +import { getTextAlignClassName, GridColumn } from './models/GridColumn'; function processColumns(columns: List): List { return columns @@ -211,7 +211,6 @@ export class GridHeader extends PureComponent { onDragOver={this.handleDragOver} onDragStart={this.handleDragStart} onDrop={this.handleDrop} - style={style} title={hideTooltip ? undefined : description} > {headerCell ? headerCell(column, i, columns.size) : title} @@ -224,7 +223,7 @@ export class GridHeader extends PureComponent { ); } - return ; + return ; }, this) .toArray()} @@ -259,33 +258,36 @@ interface GridRowProps { rowIdx: number; } -const GridRow: FC = memo(({ columns, highlight, row, rowIdx }) => { - // style cast to "any" type due to @types/react@16.3.14 switch to csstype package usage which does not declare - // "textAlign" property correctly for elements. - return ( - - {columns - .map((column: GridColumn, c: number) => - column.tableCell ? ( +const GridRow: FC = memo(({ columns, highlight, row, rowIdx }) => ( + + {columns + .map((column: GridColumn, c: number) => { + if (column.tableCell) { + return ( {column.cell(row.get(column.index), row, column, rowIdx, c)} - ) : ( - + ); + } + + const className = getTextAlignClassName(column); + return ( + +
{column.cell(row.get(column.index), row, column, rowIdx, c)} - - ) - ) - .toArray()} - - ); -}); +
+ + ); + }) + .toArray()} + +)); GridRow.displayName = 'GridRow'; interface EmptyGridRowProps { @@ -410,7 +412,7 @@ export const Grid: FC = memo(props => { highlightRowIndexes, }; - const tableClasses = classNames({ + const tableClasses = classNames('read-only-table', { table: !cellular, 'table-cellular': cellular, 'table-striped': striped, diff --git a/packages/components/src/internal/components/base/models/GridColumn.tsx b/packages/components/src/internal/components/base/models/GridColumn.tsx index 20a58649c0..5ed55146bd 100644 --- a/packages/components/src/internal/components/base/models/GridColumn.tsx +++ b/packages/components/src/internal/components/base/models/GridColumn.tsx @@ -86,3 +86,19 @@ export class GridColumn implements ColumnProps { this.hideTooltip = config.hideTooltip === true; // defaults to false } } + +// Special interface that lets us pass GridColumn and EditableColumnMetadata to getTextAlignClassname +interface WithAlignment { + align?: string; + jsonType?: string; +} + +const TEXT_ALIGN_CLASSES = { + center: 'text-center', + left: 'text-left', + right: 'text-right', +}; + +export function getTextAlignClassName(column: WithAlignment): string { + return TEXT_ALIGN_CLASSES[column?.align] ?? TEXT_ALIGN_CLASSES.left; +} diff --git a/packages/components/src/internal/components/editable/EditableGrid.tsx b/packages/components/src/internal/components/editable/EditableGrid.tsx index c37f4a25b7..e3388216f3 100644 --- a/packages/components/src/internal/components/editable/EditableGrid.tsx +++ b/packages/components/src/internal/components/editable/EditableGrid.tsx @@ -36,7 +36,7 @@ import { HeaderSelectionCell } from '../../renderers'; import { blurActiveElement, capitalizeFirstChar, not } from '../../util/utils'; import { Grid } from '../base/Grid'; -import { GridColumn, GridColumnCellRenderer } from '../base/models/GridColumn'; +import { getTextAlignClassName, GridColumn, GridColumnCellRenderer } from '../base/models/GridColumn'; import { BulkAddUpdateForm } from '../forms/BulkAddUpdateForm'; import { QueryInfoForm, QueryInfoFormProps } from '../forms/QueryInfoForm'; @@ -148,7 +148,7 @@ const COUNT_COL = new GridColumn({ // style cast to "any" type due to @types/react@16.3.14 switch to csstype package usage which does not declare // "textAlign" property correctly for elements. cell: (d, r, c, rn) => ( - +
{rn + 1}
), @@ -184,8 +184,9 @@ function inputCellFactory( const { isReadonlyCell, isReadonlyRow } = editorModel.getCellReadStatus(fieldKey, rowIdx, readonlyRows); const rowContainer = editorModel.getFolderValueForRow(rowIdx); const focused = editorModel.isFocused(colIdx, rowIdx); - const className = classNames({ 'grid-col-with-width': hasCellWidthOverride(columnMetadata) }); - const style = { textAlign: columnMetadata?.align ?? c.align ?? 'left' } as any; + const className = classNames(getTextAlignClassName(columnMetadata), { + 'grid-col-with-width': hasCellWidthOverride(columnMetadata), + }); // If we're updating then we want to use the container path from each row if present if (forUpdate && rowContainer) containerPath = rowContainer; @@ -225,7 +226,7 @@ function inputCellFactory( } return ( - + | { [key: string]: any }; + data: Map | Record; tableCell?: boolean; } @@ -35,19 +36,10 @@ export const ExpirationDateColumnRenderer: FC if (tableCell) { return ( -
- {displayValue} -
+
{displayValue}
); } diff --git a/packages/components/src/internal/renderers/StorageStatusRenderer.tsx b/packages/components/src/internal/renderers/StorageStatusRenderer.tsx index 217ea3b903..a00348e28f 100644 --- a/packages/components/src/internal/renderers/StorageStatusRenderer.tsx +++ b/packages/components/src/internal/renderers/StorageStatusRenderer.tsx @@ -14,7 +14,7 @@ export class StorageStatusRenderer extends React.PureComponent{value}; + return value; } else { return {value}; } diff --git a/packages/components/src/theme/grid.scss b/packages/components/src/theme/grid.scss index 6998764b2e..56233a1241 100644 --- a/packages/components/src/theme/grid.scss +++ b/packages/components/src/theme/grid.scss @@ -643,3 +643,13 @@ td > .expired-form-field { scroll-margin: 0; } } + +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} From 19ef1d77ae73ea2d62e3c49f8ad6ead26aa25ced Mon Sep 17 00:00:00 2001 From: alanv Date: Wed, 17 Dec 2025 16:32:30 -0600 Subject: [PATCH 02/14] DefaultRenderer: remove unnecessary styling no-wrap styling is alreayd provided by the server --- .../src/internal/renderers/DefaultRenderer.tsx | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/components/src/internal/renderers/DefaultRenderer.tsx b/packages/components/src/internal/renderers/DefaultRenderer.tsx index 4c34ad9057..eeae2303d7 100644 --- a/packages/components/src/internal/renderers/DefaultRenderer.tsx +++ b/packages/components/src/internal/renderers/DefaultRenderer.tsx @@ -43,10 +43,6 @@ const TARGET_BLANK = '_blank'; export const DefaultRenderer: FC = memo(({ col, data, noLink }) => { let display = null; let style; - // Issue 43474: Prevent text wrapping for date columns - const noWrap = col?.jsonType === 'date' || col?.jsonType === 'time'; - // Issue 36941: when using the default renderer, add css so that line breaks as preserved - let className = noWrap ? 'ws-no-wrap' : 'ws-pre-wrap'; if (data) { if (typeof data === 'string') { @@ -58,12 +54,12 @@ export const DefaultRenderer: FC = memo(({ col, data, noLink }) => { return ; } else if (col?.isFileInput) { return ; - } - else { + } else { + let className: string; if (isConditionalFormattingEnabled()) { style = getDataStyling(data); if (style?.backgroundColor) { - className += ' status-pill'; + className = 'status-pill'; } } if (data.has('formattedValue')) { @@ -83,14 +79,12 @@ export const DefaultRenderer: FC = memo(({ col, data, noLink }) => { ); } + + if (style !== undefined) return {display}; } } - return ( - - {display} - - ); + return display; }); DefaultRenderer.displayName = 'DefaultRenderer'; From 978faadb40676f4c414a0d831aec4e42a79a8358 Mon Sep 17 00:00:00 2001 From: alanv Date: Wed, 17 Dec 2025 16:33:40 -0600 Subject: [PATCH 03/14] Improve styling for read-only tables --- .../components/src/internal/renderers.tsx | 15 ++++++----- packages/components/src/theme/grid.scss | 26 +++++++++++++++++-- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/packages/components/src/internal/renderers.tsx b/packages/components/src/internal/renderers.tsx index 31ce9fbe74..091713a66c 100644 --- a/packages/components/src/internal/renderers.tsx +++ b/packages/components/src/internal/renderers.tsx @@ -203,15 +203,16 @@ const HeaderCellDropdownMenu: FC = memo(props => { let left; if (toggleEl.current && menuEl.current) { - const headerRect = toggleEl.current.parentElement.getBoundingClientRect(); + // The third parentElement is the element, and we want to pin the menu to the bottom of that + const headerRect = toggleEl.current.parentElement.parentElement.parentElement.getBoundingClientRect(); const menuRect = menuEl.current.getBoundingClientRect(); - left = headerRect.x - menuRect.width + 18 + 'px'; - top = headerRect.y + headerRect.height + 5 + 'px'; + left = headerRect.right - menuRect.width + 'px'; + top = headerRect.bottom + 'px'; // Issue 45553 // Render the dropdown menu above the header if the header is too close to the bottom of the screen. if (headerRect.bottom + menuRect.height > window.innerHeight) { - top = headerRect.y - menuRect.height - 10 + 'px'; + top = headerRect.top - menuRect.height - 10 + 'px'; } } @@ -329,7 +330,7 @@ const HeaderCellDropdownMenu: FC = memo(props => { ); return ( -
+
{/* Note: we don't need a click handler on this icon because there is one on the wrapping div above */} {createPortal(body, portalRef)} @@ -401,7 +402,7 @@ export const HeaderCellDropdown: FC = memo(props => { return (
- +
= memo(props => { )} - +
{includeDropdown && !editingTitle && ( .expired-form-field { .text-center { text-align: center; } + +.read-only-table td, +.read-only-table th { + white-space: pre-wrap; // pre-wrap for multiline column cells + vertical-align: top; +} + +.read-only-table .table-cell-content { + // min-width: 100% is needed to ensure that table-cell-content will always fill the available space, otherwise it + // will only get as wide as the content, which negates the right-aligned style we give for numbers. + min-width: 100%; + max-width: 300px; + // width: max-content makes the table-cell-content elements grow as wide as their content will allow, up to 300px, + // which is the behavior we want, instead of the typical table layout algorithm. + width: max-content; + word-break: break-word; +} From 50274374551b8737cd07337e5b7f8f1eafd7eaf2 Mon Sep 17 00:00:00 2001 From: alanv Date: Wed, 17 Dec 2025 17:14:14 -0600 Subject: [PATCH 04/14] GridColumn: Drop support for width/fixedWidth overrides --- .../internal/components/base/Grid.test.tsx | 49 ------------------- .../src/internal/components/base/Grid.tsx | 29 ++--------- .../components/base/models/GridColumn.tsx | 6 --- .../domainproperties/DomainPropertiesGrid.tsx | 2 +- .../components/domainproperties/models.tsx | 1 - .../components/editable/EditableGrid.tsx | 22 +-------- .../internal/components/lineage/constants.tsx | 3 -- .../src/public/QueryModel/GridPanel.tsx | 3 -- 8 files changed, 5 insertions(+), 110 deletions(-) diff --git a/packages/components/src/internal/components/base/Grid.test.tsx b/packages/components/src/internal/components/base/Grid.test.tsx index d458466e1a..b5ed98bad1 100644 --- a/packages/components/src/internal/components/base/Grid.test.tsx +++ b/packages/components/src/internal/components/base/Grid.test.tsx @@ -153,55 +153,6 @@ describe('Grid', () => { expect(document.querySelector('.table-striped')).toBeNull(); }); - test('header title and calcWidths', () => { - render( - - ); - - expect(document.querySelector('.grid-header-cell').getAttribute('style')).toBe('min-width: 189px;'); - expect(document.querySelector('.grid-header-cell')).not.toBeNull(); - expect(document.querySelector('.phi-protected')).not.toBeNull(); - }); - - test('rendering with fixedWidth and width', () => { - const columns = List([ - { - index: 'name', - showHeader: true, - fixedWidth: 321, - width: 567, - }, - { - index: 'number', - showHeader: true, - width: 123, - }, - { - index: 'position', - showHeader: true, - fixedWidth: 567, - }, - ]); - render(); - const headerCells = document.querySelectorAll('.grid-header-cell'); - expect(headerCells).toHaveLength(3); - expect(headerCells[0].getAttribute('style')).toBe('width: 321px;'); - expect(headerCells[1].getAttribute('style')).toBe('min-width: 123px;'); - expect(headerCells[2].getAttribute('style')).toBe('width: 567px;'); - }); - test('render with messages', () => { render(); validateHasData(); diff --git a/packages/components/src/internal/components/base/Grid.tsx b/packages/components/src/internal/components/base/Grid.tsx index ad5451caa6..b901702f01 100644 --- a/packages/components/src/internal/components/base/Grid.tsx +++ b/packages/components/src/internal/components/base/Grid.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { CSSProperties, FC, Fragment, memo, PureComponent, ReactNode, RefObject } from 'react'; +import React, { FC, Fragment, memo, PureComponent, ReactNode, RefObject } from 'react'; import classNames from 'classnames'; import { fromJS, List, Map } from 'immutable'; @@ -44,11 +44,9 @@ function processColumns(columns: List): List { helpTipRenderer: c.helpTipRenderer, hideTooltip: c.helpTipRenderer !== undefined, index: c.index, - fixedWidth: c.fixedWidth, raw: c, tableCell: c.tableCell, title: c.title || c.caption, - width: c.width, }); }) .toList(); @@ -96,7 +94,6 @@ export function getColumnHoverText(info: any): string { } interface GridHeaderProps { - calcWidths?: boolean; columns: List; headerCell?: any; onColumnDrop?: (sourceIndex: string, targetIndex: string) => void; @@ -156,7 +153,7 @@ export class GridHeader extends PureComponent { }; render() { - const { calcWidths, columns, headerCell, showHeader, onColumnDrop } = this.props; + const { columns, headerCell, showHeader, onColumnDrop } = this.props; const { dragTarget } = this.state; if (!showHeader) { @@ -169,26 +166,9 @@ export class GridHeader extends PureComponent { {columns .map((column, i) => { - const { headerCls, index, fixedWidth, raw, title, width, hideTooltip } = column; + const { headerCls, index, raw, title, hideTooltip } = column; const draggable = onColumnDrop !== undefined; - let style: CSSProperties; - if (fixedWidth) { - style = { - width: `${fixedWidth}px`, - }; - } - - let colMinWidth = width; - if (colMinWidth === undefined) { - // the additional 45px is to account for the grid column header icons for sort/filter and the dropdown toggle - colMinWidth = calcWidths && title ? Math.max(45 + title.length * 8, 150) : undefined; - } - if (!fixedWidth && colMinWidth !== undefined) { - if (!style) style = {}; - style.minWidth = `${colMinWidth}px`; - } - if (column.showHeader) { const className = classNames(headerCls, { 'grid-header-cell': headerCls === undefined, @@ -344,7 +324,6 @@ export type GridData = List> | Record[]; export interface GridProps { bordered?: boolean; - calcWidths?: boolean; cellular?: boolean; columns?: List; condensed?: boolean; @@ -372,7 +351,6 @@ export interface GridProps { export const Grid: FC = memo(props => { const { bordered = true, - calcWidths = false, cellular = false, condensed = false, data = List>(), @@ -395,7 +373,6 @@ export const Grid: FC = memo(props => { const gridData = processData(data); const gridColumns = columns !== undefined ? processColumns(columns) : resolveColumns(gridData); const headerProps: GridHeaderProps = { - calcWidths, columns: gridColumns, headerCell, onColumnDrop, diff --git a/packages/components/src/internal/components/base/models/GridColumn.tsx b/packages/components/src/internal/components/base/models/GridColumn.tsx index 5ed55146bd..fc1d7f8cc1 100644 --- a/packages/components/src/internal/components/base/models/GridColumn.tsx +++ b/packages/components/src/internal/components/base/models/GridColumn.tsx @@ -16,12 +16,10 @@ interface ColumnProps { helpTipRenderer?: string; hideTooltip?: boolean; index: string; - fixedWidth?: number; raw?: any; showHeader?: boolean; tableCell?: boolean; title: string; - width?: number; } const defaultCell: GridColumnCellRenderer = d => { @@ -56,23 +54,19 @@ export class GridColumn implements ColumnProps { helpTipRenderer?: string; hideTooltip?: boolean; index: string; - fixedWidth: number; raw: any; tableCell: boolean; title: string; showHeader: boolean; - width: number; constructor(config: ColumnProps) { this.align = config.align; this.cell = config.cell ?? defaultCell; this.format = config.format; this.index = config.index; - this.fixedWidth = config.fixedWidth; this.raw = config.raw; this.headerCls = config.headerCls; this.helpTipRenderer = config.helpTipRenderer; - this.width = config.width; // react render displays ' ', see: https://facebook.github.io/react/docs/jsx-gotchas.html if (config.title && config.title == ' ') { diff --git a/packages/components/src/internal/components/domainproperties/DomainPropertiesGrid.tsx b/packages/components/src/internal/components/domainproperties/DomainPropertiesGrid.tsx index 3d390911e7..cbc2d3b1f2 100644 --- a/packages/components/src/internal/components/domainproperties/DomainPropertiesGrid.tsx +++ b/packages/components/src/internal/components/domainproperties/DomainPropertiesGrid.tsx @@ -194,6 +194,6 @@ export class DomainPropertiesGrid extends React.PureComponent; + return ; } } diff --git a/packages/components/src/internal/components/domainproperties/models.tsx b/packages/components/src/internal/components/domainproperties/models.tsx index 50326689f3..67b8b21cb8 100644 --- a/packages/components/src/internal/components/domainproperties/models.tsx +++ b/packages/components/src/internal/components/domainproperties/models.tsx @@ -494,7 +494,6 @@ export class DomainDesign const selectionCol = new GridColumn({ index: GRID_SELECTION_INDEX, title: GRID_SELECTION_INDEX, - width: 20, cell: (data, row) => { const domainIndex = row.get('domainIndex'); const fieldIndex = row.get('fieldIndex'); diff --git a/packages/components/src/internal/components/editable/EditableGrid.tsx b/packages/components/src/internal/components/editable/EditableGrid.tsx index e3388216f3..727d641f19 100644 --- a/packages/components/src/internal/components/editable/EditableGrid.tsx +++ b/packages/components/src/internal/components/editable/EditableGrid.tsx @@ -98,10 +98,6 @@ function moveUp(colIdx: number, rowIdx: number): CellCoordinates { return { colIdx, rowIdx: rowIdx - 1 }; } -function hasCellWidthOverride(metadata: EditableColumnMetadata): boolean { - return !!metadata?.minWidth || !!metadata?.width; -} - function computeSelectionCellKeys( editorModel: EditorModel, minColIdx: number, @@ -144,9 +140,6 @@ const COUNT_COL = new GridColumn({ index: GRID_EDIT_INDEX, tableCell: true, title: 'Row', - width: 45, - // style cast to "any" type due to @types/react@16.3.14 switch to csstype package usage which does not declare - // "textAlign" property correctly for elements. cell: (d, r, c, rn) => (
{rn + 1}
@@ -184,9 +177,7 @@ function inputCellFactory( const { isReadonlyCell, isReadonlyRow } = editorModel.getCellReadStatus(fieldKey, rowIdx, readonlyRows); const rowContainer = editorModel.getFolderValueForRow(rowIdx); const focused = editorModel.isFocused(colIdx, rowIdx); - const className = classNames(getTextAlignClassName(columnMetadata), { - 'grid-col-with-width': hasCellWidthOverride(columnMetadata), - }); + const className = getTextAlignClassName(columnMetadata); // If we're updating then we want to use the container path from each row if present if (forUpdate && rowContainer) containerPath = rowContainer; @@ -854,14 +845,6 @@ export class EditableGrid extends PureComponent { const qCol = editorModel.columnMap.get(fieldKey); const metadata = editorModel.getColumnMetadata(qCol.fieldKey); - let width = 100; - let fixedWidth; - if (hasCellWidthOverride(metadata)) { - fixedWidth = metadata.width; - if (!fixedWidth) { - width = metadata.minWidth; - } - } const hideTooltip = metadata?.hideTitleTooltip ?? qCol.hasHelpTipData; gridColumns = gridColumns.push( new GridColumn({ @@ -879,10 +862,8 @@ export class EditableGrid extends PureComponent ) => { const indent = node.get('distance') * 10; const dupeCount = node.get('duplicateCount'); @@ -70,7 +69,6 @@ export const LINEAGE_GRID_COLUMNS = List([ new GridColumn({ index: 'distance', title: 'Distance', - width: 60, cell: (distance: any, node: Map) => { const gen = distance > 1 ? ' generations' : ' generation'; @@ -86,7 +84,6 @@ export const LINEAGE_GRID_COLUMNS = List([ new GridColumn({ index: 'lsid', title: 'Change Seed', - width: 70, cell: (lsid: string, node: Map) => { const lineageDistance = node.get('lineageDistance'); const baseURL = AppURL.create('lineage').addParams({ diff --git a/packages/components/src/public/QueryModel/GridPanel.tsx b/packages/components/src/public/QueryModel/GridPanel.tsx index 1586085ea5..b3c3ab5178 100644 --- a/packages/components/src/public/QueryModel/GridPanel.tsx +++ b/packages/components/src/public/QueryModel/GridPanel.tsx @@ -1057,8 +1057,6 @@ export class GridPanel extends PureComponent, State> { const { actions, allowSelections, - allowFiltering, - allowSorting, allowViewCustomization, hasHeader, asPanel, @@ -1176,7 +1174,6 @@ export class GridPanel extends PureComponent, State> { {hasData && ( Date: Wed, 17 Dec 2025 17:20:21 -0600 Subject: [PATCH 05/14] Update release notes --- packages/components/releaseNotes/components.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md index 111de213b5..15b855b153 100644 --- a/packages/components/releaseNotes/components.md +++ b/packages/components/releaseNotes/components.md @@ -1,6 +1,11 @@ # @labkey/components Components, models, actions, and utility functions for LabKey applications and pages +### version 7.?.? +*Released*: ?? December 2025 +- GridColumn: remove width, fixedWidth properties +- Grid: improve styling for columns + ### version 7.5.0 *Released*: 22 December 2025 - Chart builder updates for per-series line type option From 5e3322a6eb95c60d872e04e644b5890f36533af2 Mon Sep 17 00:00:00 2001 From: alanv Date: Wed, 17 Dec 2025 17:21:24 -0600 Subject: [PATCH 06/14] Update release notes --- packages/components/releaseNotes/components.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md index 15b855b153..b79fa0f116 100644 --- a/packages/components/releaseNotes/components.md +++ b/packages/components/releaseNotes/components.md @@ -4,6 +4,7 @@ Components, models, actions, and utility functions for LabKey applications and p ### version 7.?.? *Released*: ?? December 2025 - GridColumn: remove width, fixedWidth properties + - Add css class for text align and getTextAlignClassName helper - Grid: improve styling for columns ### version 7.5.0 From a783edd35dccf7f841c5d78ff645fe4f6bbe006f Mon Sep 17 00:00:00 2001 From: alanv Date: Mon, 22 Dec 2025 15:55:41 -0600 Subject: [PATCH 07/14] Fix issue with locked column having a visible gap on the left --- packages/components/src/theme/query-model.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/theme/query-model.scss b/packages/components/src/theme/query-model.scss index 63e347dec3..88ce10f853 100644 --- a/packages/components/src/theme/query-model.scss +++ b/packages/components/src/theme/query-model.scss @@ -269,7 +269,7 @@ } .editable-grid__container.grid-panel__lock-left-with-countcol .table-responsive { thead th:nth-child(2), tr td:nth-child(2) { - left: 43px; + left: 35px; } } From 6117585a9d22e7fb127582f1902029904bee6813 Mon Sep 17 00:00:00 2001 From: alanv Date: Mon, 22 Dec 2025 15:56:02 -0600 Subject: [PATCH 08/14] Fix issue with problematically long column names --- .../src/internal/components/base/Grid.tsx | 15 +++++++++------ .../internal/components/editable/EditableGrid.tsx | 5 +++-- packages/components/src/theme/grid.scss | 13 +++++++++---- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/packages/components/src/internal/components/base/Grid.tsx b/packages/components/src/internal/components/base/Grid.tsx index b901702f01..e56f04daf2 100644 --- a/packages/components/src/internal/components/base/Grid.tsx +++ b/packages/components/src/internal/components/base/Grid.tsx @@ -193,12 +193,15 @@ export class GridHeader extends PureComponent { onDrop={this.handleDrop} title={hideTooltip ? undefined : description} > - {headerCell ? headerCell(column, i, columns.size) : title} - {/* headerCell will render the helpTip, so only render here if not using headerCell() */} - {!headerCell && column.helpTipRenderer && ( - - - + {headerCell && headerCell(column, i, columns.size)} + {!headerCell && ( +
+ {title} + {/* headerCell will render the helpTip, so only render here if not using headerCell() */} + + + +
)} ); diff --git a/packages/components/src/internal/components/editable/EditableGrid.tsx b/packages/components/src/internal/components/editable/EditableGrid.tsx index 727d641f19..808a0062d0 100644 --- a/packages/components/src/internal/components/editable/EditableGrid.tsx +++ b/packages/components/src/internal/components/editable/EditableGrid.tsx @@ -27,6 +27,7 @@ import { CELL_SELECTION_HANDLE_CLASSNAME, GRID_CHECKBOX_OPTIONS, GRID_EDIT_INDEX, + GRID_HEADER_CELL_BODY, GRID_SELECTION_INDEX, MAX_EDITABLE_GRID_ROWS, } from '../../constants'; @@ -884,7 +885,7 @@ export class EditableGrid extends PureComponent +
{!showLabelOverlay && ( <> {label} @@ -909,7 +910,7 @@ export class EditableGrid extends PureComponent )} - +
); }; diff --git a/packages/components/src/theme/grid.scss b/packages/components/src/theme/grid.scss index fc196d7239..0771db6fb4 100644 --- a/packages/components/src/theme/grid.scss +++ b/packages/components/src/theme/grid.scss @@ -46,10 +46,10 @@ } .grid-header-cell { - position: relative; - padding: 0 9px; - font-weight: bold; - vertical-align: top !important; // important because bootstrap has a highly specific selector that sets it to bottom + position: relative; + padding: 0 9px; + font-weight: bold; + vertical-align: top !important; // important because bootstrap has a highly specific selector that sets it to bottom input:not([type=checkbox]) { width: 100% @@ -59,6 +59,11 @@ .grid-header-cell__body { display: flex; gap: 8px; + // Note: we have to have max-height and overflow-y set because some grid configurations can lead to extremely tall + // table headers that take up 100% of the table height, completely blocking the table rows. To repro add a column + // with a really long name (at or near the max length of 200 chars) and keep all values blank. + max-height: 250px; + overflow-y: auto; } .grid-header-cell__title-wrapper { From 8aa61346abf447d70dc9aa3a0ae0d7edbc638ce4 Mon Sep 17 00:00:00 2001 From: alanv Date: Mon, 22 Dec 2025 16:36:18 -0600 Subject: [PATCH 09/14] DefaultRenderer: Fix issue with conditional formatting --- .../src/internal/renderers/DefaultRenderer.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/components/src/internal/renderers/DefaultRenderer.tsx b/packages/components/src/internal/renderers/DefaultRenderer.tsx index eeae2303d7..f5bc74017e 100644 --- a/packages/components/src/internal/renderers/DefaultRenderer.tsx +++ b/packages/components/src/internal/renderers/DefaultRenderer.tsx @@ -51,7 +51,7 @@ export const DefaultRenderer: FC = memo(({ col, data, noLink }) => { display = data ? 'true' : 'false'; } else if (List.isList(data)) { // defensively return a MultiValueRenderer, this column likely wasn't declared properly as "multiValue" - return ; + return ; } else if (col?.isFileInput) { return ; } else { @@ -80,7 +80,13 @@ export const DefaultRenderer: FC = memo(({ col, data, noLink }) => { ); } - if (style !== undefined) return {display}; + if (style !== undefined) { + return ( + + {display} + + ); + } } } From 2302941b4aeecc650302ae05684989086b6130a5 Mon Sep 17 00:00:00 2001 From: alanv Date: Mon, 22 Dec 2025 16:41:40 -0600 Subject: [PATCH 10/14] Don't wrap expiration date columns --- packages/components/src/theme/grid.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/src/theme/grid.scss b/packages/components/src/theme/grid.scss index 0771db6fb4..c702472a77 100644 --- a/packages/components/src/theme/grid.scss +++ b/packages/components/src/theme/grid.scss @@ -628,6 +628,7 @@ $table-cell-padding: 4px 2px; td > .expired-grid-cell-content { padding: 5px; background-image: linear-gradient(225deg, red, red 5px, transparent 5px, transparent); + white-space: nowrap; } td > .expired-form-field { From b4671d14450efea00069130bfd6b90a7bbe4400b Mon Sep 17 00:00:00 2001 From: alanv Date: Mon, 22 Dec 2025 17:14:36 -0600 Subject: [PATCH 11/14] Only render LabelHelpTip if column has helpTipRenderer --- .../components/src/internal/components/base/Grid.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/components/src/internal/components/base/Grid.tsx b/packages/components/src/internal/components/base/Grid.tsx index e56f04daf2..88c1e3d031 100644 --- a/packages/components/src/internal/components/base/Grid.tsx +++ b/packages/components/src/internal/components/base/Grid.tsx @@ -197,10 +197,11 @@ export class GridHeader extends PureComponent { {!headerCell && (
{title} - {/* headerCell will render the helpTip, so only render here if not using headerCell() */} - - - + {column.helpTipRenderer && ( + + + + )}
)} From 4e61b545e64288f374d820f793f19d420147c3cd Mon Sep 17 00:00:00 2001 From: alanv Date: Mon, 22 Dec 2025 17:33:28 -0600 Subject: [PATCH 12/14] Update test snapshots --- .../__snapshots__/PreviewGrid.test.tsx.snap | 214 ++++--- .../DomainFieldsDisplay.test.tsx.snap | 114 +++- .../FilePreviewGrid.test.tsx.snap | 570 +++++++++++++----- .../DefaultRenderer.test.tsx.snap | 59 +- 4 files changed, 667 insertions(+), 290 deletions(-) diff --git a/packages/components/src/internal/components/__snapshots__/PreviewGrid.test.tsx.snap b/packages/components/src/internal/components/__snapshots__/PreviewGrid.test.tsx.snap index ebeea56e1f..2b303cab0e 100644 --- a/packages/components/src/internal/components/__snapshots__/PreviewGrid.test.tsx.snap +++ b/packages/components/src/internal/components/__snapshots__/PreviewGrid.test.tsx.snap @@ -34,7 +34,7 @@ exports[`PreviewGrid render PreviewGrid with data 1`] = ` class="grid-messages" /> @@ -47,7 +47,11 @@ exports[`PreviewGrid render PreviewGrid with data 1`] = ` If not provided, a unique name will be generated from the expression: null (Name)" > - Name +
+ Name +
@@ -83,120 +99,138 @@ null (Name)" class="grid-row-alternate" > @@ -221,7 +255,7 @@ exports[`PreviewGrid render PreviewGrid with different numCols and numRows 1`] = class="grid-messages" />
- Flag +
+ Flag +
- Mixture Type +
+ Mixture Type +
- Expiration Time +
+ Expiration Time +
- - DMEXP - + + DMEXP + + - - - Solution - + + Solution + + - 30 - +
- - TBS - + + TBS + + - - - Solution - + + Solution + + - 30 - +
- - PBS - + + PBS + + - - - Solution - + + Solution + + - 30 - +
@@ -234,7 +268,11 @@ exports[`PreviewGrid render PreviewGrid with different numCols and numRows 1`] = If not provided, a unique name will be generated from the expression: null (Name)" > - Name +
+ Name +
@@ -252,20 +294,23 @@ null (Name)" class="grid-row-alternate" > @@ -273,20 +318,23 @@ null (Name)" class="grid-row" > diff --git a/packages/components/src/internal/components/domainproperties/__snapshots__/DomainFieldsDisplay.test.tsx.snap b/packages/components/src/internal/components/domainproperties/__snapshots__/DomainFieldsDisplay.test.tsx.snap index d48fcde4f6..b8e15ba7bd 100644 --- a/packages/components/src/internal/components/domainproperties/__snapshots__/DomainFieldsDisplay.test.tsx.snap +++ b/packages/components/src/internal/components/domainproperties/__snapshots__/DomainFieldsDisplay.test.tsx.snap @@ -23,7 +23,7 @@ exports[`DomainFieldsDisplay with empty domain design 1`] = ` class="grid-messages" />
- Flag +
+ Flag +
- - DMEXP - + + DMEXP + + -
- - TBS - + + TBS + + -
@@ -32,42 +32,66 @@ exports[`DomainFieldsDisplay with empty domain design 1`] = ` draggable="false" id="name" > - Name +
+ Name +
@@ -114,7 +138,7 @@ exports[`DomainFieldsDisplay with title 1`] = ` class="grid-messages" />
- Label +
+ Label +
- Range URI +
+ Range URI +
- Concept URI +
+ Concept URI +
- Required +
+ Required +
- Scale +
+ Scale +
@@ -123,42 +147,66 @@ exports[`DomainFieldsDisplay with title 1`] = ` draggable="false" id="name" > - Name +
+ Name +
@@ -205,7 +253,7 @@ exports[`DomainFieldsDisplay without title 1`] = ` class="grid-messages" />
- Label +
+ Label +
- Range URI +
+ Range URI +
- Concept URI +
+ Concept URI +
- Required +
+ Required +
- Scale +
+ Scale +
@@ -214,42 +262,66 @@ exports[`DomainFieldsDisplay without title 1`] = ` draggable="false" id="name" > - Name +
+ Name +
diff --git a/packages/components/src/internal/components/files/__snapshots__/FilePreviewGrid.test.tsx.snap b/packages/components/src/internal/components/files/__snapshots__/FilePreviewGrid.test.tsx.snap index bedd6b3532..74deee800a 100644 --- a/packages/components/src/internal/components/files/__snapshots__/FilePreviewGrid.test.tsx.snap +++ b/packages/components/src/internal/components/files/__snapshots__/FilePreviewGrid.test.tsx.snap @@ -25,7 +25,7 @@ exports[` custom column headers 1`] = ` class="grid-messages" />
- Label +
+ Label +
- Range URI +
+ Range URI +
- Concept URI +
+ Concept URI +
- Required +
+ Required +
- Scale +
+ Scale +
@@ -34,21 +34,33 @@ exports[` custom column headers 1`] = ` draggable="false" id="col1" > - First Column +
+ First Column +
@@ -57,57 +69,93 @@ exports[` custom column headers 1`] = ` class="grid-row-alternate" > @@ -142,7 +190,7 @@ exports[` custom header and info message 1`] = ` class="grid-messages" />
- Second Column +
+ Second Column +
- Third Column +
+ Third Column +
- abc +
+ abc +
- 123 +
+ 123 +
- 2019-01-01 +
+ 2019-01-01 +
- def +
+ def +
- 456 +
+ 456 +
- 2019-01-02 +
+ 2019-01-02 +
- ghi +
+ ghi +
- 789 +
+ 789 +
- 2019-01-03 +
+ 2019-01-03 +
@@ -151,21 +199,33 @@ exports[` custom header and info message 1`] = ` draggable="false" id="col1" > - col1 +
+ col1 +
@@ -174,57 +234,93 @@ exports[` custom header and info message 1`] = ` class="grid-row-alternate" > @@ -280,7 +376,7 @@ exports[` no data 1`] = ` class="grid-messages" />
- col2 +
+ col2 +
- col3 +
+ col3 +
- abc +
+ abc +
- 123 +
+ 123 +
- 2019-01-01 +
+ 2019-01-01 +
- def +
+ def +
- 456 +
+ 456 +
- 2019-01-02 +
+ 2019-01-02 +
- ghi +
+ ghi +
- 789 +
+ 789 +
- 2019-01-03 +
+ 2019-01-03 +
@@ -326,7 +422,7 @@ exports[` one row of data 1`] = ` class="grid-messages" />
@@ -335,7 +431,11 @@ exports[` one row of data 1`] = ` draggable="false" id="test" > - test +
+ test +
@@ -344,9 +444,13 @@ exports[` one row of data 1`] = ` class="grid-row-alternate" > @@ -386,7 +490,7 @@ exports[` previewData.warningMsg 1`] = ` class="grid-messages" />
- 123 +
+ 123 +
@@ -395,21 +499,33 @@ exports[` previewData.warningMsg 1`] = ` draggable="false" id="col1" > - col1 +
+ col1 +
@@ -418,57 +534,93 @@ exports[` previewData.warningMsg 1`] = ` class="grid-row-alternate" > @@ -502,7 +654,7 @@ exports[` three rows of data 1`] = ` class="grid-messages" />
- col2 +
+ col2 +
- col3 +
+ col3 +
- abc +
+ abc +
- 123 +
+ 123 +
- 2019-01-01 +
+ 2019-01-01 +
- def +
+ def +
- 456 +
+ 456 +
- 2019-01-02 +
+ 2019-01-02 +
- ghi +
+ ghi +
- 789 +
+ 789 +
- 2019-01-03 +
+ 2019-01-03 +
@@ -511,21 +663,33 @@ exports[` three rows of data 1`] = ` draggable="false" id="col1" > - col1 +
+ col1 +
@@ -534,57 +698,93 @@ exports[` three rows of data 1`] = ` class="grid-row-alternate" > @@ -624,7 +824,7 @@ exports[` warning message 1`] = ` class="grid-messages" />
- col2 +
+ col2 +
- col3 +
+ col3 +
- abc +
+ abc +
- 123 +
+ 123 +
- 2019-01-01 +
+ 2019-01-01 +
- def +
+ def +
- 456 +
+ 456 +
- 2019-01-02 +
+ 2019-01-02 +
- ghi +
+ ghi +
- 789 +
+ 789 +
- 2019-01-03 +
+ 2019-01-03 +
@@ -633,21 +833,33 @@ exports[` warning message 1`] = ` draggable="false" id="col1" > - col1 +
+ col1 +
@@ -656,57 +868,93 @@ exports[` warning message 1`] = ` class="grid-row-alternate" > @@ -752,7 +1000,7 @@ exports[` warning message and previewData.warningMsg 1`] = ` class="grid-messages" />
- col2 +
+ col2 +
- col3 +
+ col3 +
- abc +
+ abc +
- 123 +
+ 123 +
- 2019-01-01 +
+ 2019-01-01 +
- def +
+ def +
- 456 +
+ 456 +
- 2019-01-02 +
+ 2019-01-02 +
- ghi +
+ ghi +
- 789 +
+ 789 +
- 2019-01-03 +
+ 2019-01-03 +
@@ -761,21 +1009,33 @@ exports[` warning message and previewData.warningMsg 1`] = ` draggable="false" id="col1" > - col1 +
+ col1 +
@@ -784,57 +1044,93 @@ exports[` warning message and previewData.warningMsg 1`] = ` class="grid-row-alternate" > diff --git a/packages/components/src/internal/renderers/__snapshots__/DefaultRenderer.test.tsx.snap b/packages/components/src/internal/renderers/__snapshots__/DefaultRenderer.test.tsx.snap index 7dea3529a8..e8623305a9 100644 --- a/packages/components/src/internal/renderers/__snapshots__/DefaultRenderer.test.tsx.snap +++ b/packages/components/src/internal/renderers/__snapshots__/DefaultRenderer.test.tsx.snap @@ -2,31 +2,19 @@ exports[`DefaultRenderer boolean 1`] = `
- - true - + true
`; exports[`DefaultRenderer displayValue 1`] = `
- - Value 1 - + Value 1
`; exports[`DefaultRenderer formattedValue 1`] = `
- - Value 1.00 - + Value 1.00
`; @@ -46,37 +34,22 @@ exports[`DefaultRenderer list 1`] = ` exports[`DefaultRenderer new line 1`] = `
- - test1 + test1 test2 -
`; exports[`DefaultRenderer string 1`] = `
- - test string - + test string
`; -exports[`DefaultRenderer undefined 1`] = ` -
- -
-`; +exports[`DefaultRenderer undefined 1`] = `
`; exports[`DefaultRenderer url 1`] = `
Value 1 @@ -86,28 +59,20 @@ exports[`DefaultRenderer url 1`] = ` exports[`DefaultRenderer url, but noLink 1`] = `
- - Value 1 - + Value 1
`; exports[`DefaultRenderer value 1`] = `
- - 1 - + 1
`; exports[`DefaultRenderer with style, conditional formatting enabled 1`] = `
Value 1.00 @@ -117,10 +82,6 @@ exports[`DefaultRenderer with style, conditional formatting enabled 1`] = ` exports[`DefaultRenderer with style, conditional formatting not enabled 1`] = `
- - Value 1.00 - + Value 1.00
`; From 9f20f71266c2179fb7ba131d4313bd0f5c1091db Mon Sep 17 00:00:00 2001 From: alanv Date: Tue, 23 Dec 2025 01:25:36 -0600 Subject: [PATCH 13/14] EditableGrid: Fix right-aligned columns --- .../src/internal/components/editable/EditableGrid.tsx | 3 +-- packages/components/src/theme/grid.scss | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/components/src/internal/components/editable/EditableGrid.tsx b/packages/components/src/internal/components/editable/EditableGrid.tsx index 808a0062d0..1ab9c6aa26 100644 --- a/packages/components/src/internal/components/editable/EditableGrid.tsx +++ b/packages/components/src/internal/components/editable/EditableGrid.tsx @@ -178,7 +178,6 @@ function inputCellFactory( const { isReadonlyCell, isReadonlyRow } = editorModel.getCellReadStatus(fieldKey, rowIdx, readonlyRows); const rowContainer = editorModel.getFolderValueForRow(rowIdx); const focused = editorModel.isFocused(colIdx, rowIdx); - const className = getTextAlignClassName(columnMetadata); // If we're updating then we want to use the container path from each row if present if (forUpdate && rowContainer) containerPath = rowContainer; @@ -218,7 +217,7 @@ function inputCellFactory( } return ( -
), }); diff --git a/packages/components/src/theme/grid.scss b/packages/components/src/theme/grid.scss index 354273d393..bb8c7d8588 100644 --- a/packages/components/src/theme/grid.scss +++ b/packages/components/src/theme/grid.scss @@ -248,19 +248,6 @@ $table-cell-padding: 4px 2px; font-size: small; text-align: center; width: 45px; - - .cellular-count-content { - position: absolute; - bottom: 0; - left: 0; - right: 0; - top: 0; - margin-top: 6px; - } - - .cellular-count-static-content { - text-align: center; - } } .cellular-display {
- col2 +
+ col2 +
- col3 +
+ col3 +
- abc +
+ abc +
- 123 +
+ 123 +
- 2019-01-01 +
+ 2019-01-01 +
- def +
+ def +
- 456 +
+ 456 +
- 2019-01-02 +
+ 2019-01-02 +
- ghi +
+ ghi +
- 789 +
+ 789 +
- 2019-01-03 +
+ 2019-01-03 +
+ Date: Tue, 23 Dec 2025 01:40:50 -0600 Subject: [PATCH 14/14] EditableGrid: Simplify count col rendering --- .../internal/components/base/models/GridColumn.tsx | 3 +-- .../internal/components/editable/EditableGrid.tsx | 6 +++--- packages/components/src/theme/grid.scss | 13 ------------- 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/packages/components/src/internal/components/base/models/GridColumn.tsx b/packages/components/src/internal/components/base/models/GridColumn.tsx index fc1d7f8cc1..39b4c60e05 100644 --- a/packages/components/src/internal/components/base/models/GridColumn.tsx +++ b/packages/components/src/internal/components/base/models/GridColumn.tsx @@ -81,10 +81,9 @@ export class GridColumn implements ColumnProps { } } -// Special interface that lets us pass GridColumn and EditableColumnMetadata to getTextAlignClassname +// Special interface that lets us pass GridColumn and QueryColumn to getTextAlignClassname interface WithAlignment { align?: string; - jsonType?: string; } const TEXT_ALIGN_CLASSES = { diff --git a/packages/components/src/internal/components/editable/EditableGrid.tsx b/packages/components/src/internal/components/editable/EditableGrid.tsx index 1ab9c6aa26..df7b9426de 100644 --- a/packages/components/src/internal/components/editable/EditableGrid.tsx +++ b/packages/components/src/internal/components/editable/EditableGrid.tsx @@ -37,7 +37,7 @@ import { HeaderSelectionCell } from '../../renderers'; import { blurActiveElement, capitalizeFirstChar, not } from '../../util/utils'; import { Grid } from '../base/Grid'; -import { getTextAlignClassName, GridColumn, GridColumnCellRenderer } from '../base/models/GridColumn'; +import { GridColumn, GridColumnCellRenderer } from '../base/models/GridColumn'; import { BulkAddUpdateForm } from '../forms/BulkAddUpdateForm'; import { QueryInfoForm, QueryInfoFormProps } from '../forms/QueryInfoForm'; @@ -142,8 +142,8 @@ const COUNT_COL = new GridColumn({ tableCell: true, title: 'Row', cell: (d, r, c, rn) => ( - -
{rn + 1}
+
+ {rn + 1}