diff --git a/change/@ni-nimble-components-a5fd15e6-373b-4e3c-b562-fc2e440df26c.json b/change/@ni-nimble-components-a5fd15e6-373b-4e3c-b562-fc2e440df26c.json
new file mode 100644
index 0000000000..cf6adffc95
--- /dev/null
+++ b/change/@ni-nimble-components-a5fd15e6-373b-4e3c-b562-fc2e440df26c.json
@@ -0,0 +1,7 @@
+{
+ "type": "major",
+ "comment": "Create token representing the table's height with all rows visible; **Breaking change:** the table now specifies a max-height; if a different max-height is required, it needs to be configured.",
+ "packageName": "@ni/nimble-components",
+ "email": "20542556+mollykreis@users.noreply.github.com",
+ "dependentChangeType": "major"
+}
diff --git a/change/@ni-nimble-tokens-2857f29e-c895-406b-a302-78a6ca683d9d.json b/change/@ni-nimble-tokens-2857f29e-c895-406b-a302-78a6ca683d9d.json
new file mode 100644
index 0000000000..6e88592f4b
--- /dev/null
+++ b/change/@ni-nimble-tokens-2857f29e-c895-406b-a302-78a6ca683d9d.json
@@ -0,0 +1,7 @@
+{
+ "type": "minor",
+ "comment": "Create token representing the table's height with all rows visible",
+ "packageName": "@ni/nimble-tokens",
+ "email": "20542556+mollykreis@users.noreply.github.com",
+ "dependentChangeType": "patch"
+}
diff --git a/packages/nimble-components/src/table/styles.ts b/packages/nimble-components/src/table/styles.ts
index 4fc0dec644..6f8b73e4eb 100644
--- a/packages/nimble-components/src/table/styles.ts
+++ b/packages/nimble-components/src/table/styles.ts
@@ -10,7 +10,10 @@ import {
mediumPadding,
standardPadding,
tableRowBorderColor,
- borderHoverColor
+ borderHoverColor,
+ controlHeight,
+ tableFitRowsHeight,
+ borderWidth
} from '../theme-provider/design-tokens';
import { Theme } from '../theme-provider/types';
import { hexToRgbaCssColor } from '../utilities/style/colors';
@@ -25,6 +28,16 @@ export const styles = css`
:host {
height: 480px;
+ ${tableFitRowsHeight.cssCustomProperty}: calc(var(--ni-private-table-scroll-height) + ${controlHeight});
+ ${
+ /**
+ * Set a default maximum height for the table of 40.5 rows plus the header row so
+ * that clients don't accidentally create a table that tries to render too many rows at once.
+ * If needed, the max-height can be overridden by the client, but setting a default ensures
+ * that the max-height is considered if a larger one is needed rather than being overlooked.
+ */ ''
+ }
+ max-height: calc(${controlHeight} + (40.5 * (2 * ${borderWidth} + ${controlHeight})));
--ni-private-column-divider-width: 2px;
--ni-private-column-divider-padding: 3px;
}
diff --git a/packages/nimble-components/src/table/template.ts b/packages/nimble-components/src/table/template.ts
index 32ae5addd7..ef635115fa 100644
--- a/packages/nimble-components/src/table/template.ts
+++ b/packages/nimble-components/src/table/template.ts
@@ -41,11 +41,11 @@ export const template = html
`
aria-multiselectable="${x => x.ariaMultiSelectable}"
${children({ property: 'childItems', filter: elements() })}
>
+
+ ${tableTag} {
+ height: var(${tableFitRowsHeight.cssCustomProperty});
+ max-height: none;
+ }
+
<${tableTag}
${ref('tableRef')}
data-unused="${x => x.updateData(x)}"
- ${/* Make the table big enough to remove vertical scrollbar */ ''}
- style="height: calc((34px * var(--data-length)) + 32px);"
>
<${tableColumnAnchorTag} target="_top"
column-id="component-name-column"
@@ -650,10 +655,6 @@ const metadata: Meta
= {
const data = components.filter(component => (x.status === 'future'
? isFuture(component)
: !isFuture(component)));
- x.tableRef.style.setProperty(
- '--data-length',
- data.length.toString()
- );
await x.tableRef.setData(data);
})();
},
diff --git a/packages/storybook/src/nimble/icon-base/icons.stories.ts b/packages/storybook/src/nimble/icon-base/icons.stories.ts
index 6809da619f..3786a2e938 100644
--- a/packages/storybook/src/nimble/icon-base/icons.stories.ts
+++ b/packages/storybook/src/nimble/icon-base/icons.stories.ts
@@ -12,6 +12,7 @@ import { mappingIconTag } from '../../../../nimble-components/src/mapping/icon';
import { tableColumnTextTag } from '../../../../nimble-components/src/table-column/text';
import { IconSeverity } from '../../../../nimble-components/src/icon-base/types';
import { iconMetadata } from '../../../../nimble-components/src/icon-base/tests/icon-metadata';
+import { tableFitRowsHeight } from '../../../../nimble-components/src/theme-provider/design-tokens';
import {
apiCategory,
createUserSelectedThemeStory,
@@ -57,7 +58,6 @@ const updateData = (tableRef: Table): void => {
// Safari workaround: the table element instance is made at this point
// but doesn't seem to be upgraded to a custom element yet
await customElements.whenDefined('nimble-table');
- tableRef.style.setProperty('--data-length', data.length.toString());
await tableRef.setData(data);
})();
};
@@ -82,10 +82,14 @@ export const icons: StoryObj = {
}
},
render: createUserSelectedThemeStory(html`
+
<${tableTag}
${ref('tableRef')}
- ${/* Make the table big enough to remove vertical scrollbar */ ''}
- style="height: calc((34px * var(--data-length)) + 32px);"
data-unused="${x => updateData(x.tableRef)}"
>
<${tableColumnMappingTag} field-name="tag" key-type="string" fractional-width="0.2" >
diff --git a/packages/storybook/src/nimble/table/table-fit-rows-height-matrix.stories.ts b/packages/storybook/src/nimble/table/table-fit-rows-height-matrix.stories.ts
new file mode 100644
index 0000000000..c6f5402650
--- /dev/null
+++ b/packages/storybook/src/nimble/table/table-fit-rows-height-matrix.stories.ts
@@ -0,0 +1,86 @@
+import type { Meta, StoryFn } from '@storybook/html';
+import { html, ViewTemplate } from '@microsoft/fast-element';
+import { tableColumnTextTag } from '../../../../nimble-components/src/table-column/text';
+import { Table, tableTag } from '../../../../nimble-components/src/table';
+import type { TableRecord } from '../../../../nimble-components/src/table/types';
+import { tableFitRowsHeight } from '../../../../nimble-components/src/theme-provider/design-tokens';
+import { createFixedThemeStory } from '../../utilities/storybook';
+import { createMatrix, sharedMatrixParameters } from '../../utilities/matrix';
+import { backgroundStates } from '../../utilities/states';
+
+interface SimpleData extends TableRecord {
+ firstName: string;
+ lastName: string;
+ favoriteColor: string;
+}
+
+const data: SimpleData[] = [];
+for (let i = 0; i < 50; i++) {
+ data.push({
+ firstName: `First Name ${i}`,
+ lastName: `Last Name ${i}`,
+ favoriteColor: `Favorite Color ${i}`
+ });
+}
+
+const groupingStates = [
+ ['Not Grouped', undefined],
+ ['Grouped', 0]
+] as const;
+type GroupingState = (typeof groupingStates)[number];
+
+const metadata: Meta = {
+ title: 'Tests/Table',
+ parameters: {
+ ...sharedMatrixParameters()
+ }
+};
+
+export default metadata;
+
+// prettier-ignore
+const component = (
+ [_groupingName, groupIndex]: GroupingState
+): ViewTemplate => html`
+
+ <${tableTag}>
+ <${tableColumnTextTag} field-name="firstName" group-index="${() => groupIndex}">First Name${tableColumnTextTag}>
+ <${tableColumnTextTag} field-name="lastName">Last Name${tableColumnTextTag}>
+ <${tableColumnTextTag} field-name="favoriteColor">Favorite Color${tableColumnTextTag}>
+ ${tableTag}>
+`;
+
+const playFunction = async (rowCount: number): Promise => {
+ const tableData = data.slice(0, rowCount);
+ await Promise.all(
+ Array.from(document.querySelectorAll(tableTag)).map(
+ async table => {
+ await table.setData(tableData);
+ }
+ )
+ );
+};
+
+export const tableFitRowsHeightWith5Rows: StoryFn = createFixedThemeStory(
+ createMatrix(component, [groupingStates]),
+ backgroundStates[0]
+);
+
+tableFitRowsHeightWith5Rows.play = async () => playFunction(5);
+
+export const tableFitRowsHeightWith10Rows: StoryFn = createFixedThemeStory(
+ createMatrix(component, [groupingStates]),
+ backgroundStates[0]
+);
+tableFitRowsHeightWith10Rows.play = async () => playFunction(10);
+
+export const tableFitRowsHeightWith50Rows: StoryFn = createFixedThemeStory(
+ createMatrix(component, [groupingStates]),
+ backgroundStates[0]
+);
+tableFitRowsHeightWith50Rows.play = async () => playFunction(50);
diff --git a/packages/storybook/src/nimble/table/table.mdx b/packages/storybook/src/nimble/table/table.mdx
index a51cb7022b..2de5143dde 100644
--- a/packages/storybook/src/nimble/table/table.mdx
+++ b/packages/storybook/src/nimble/table/table.mdx
@@ -2,6 +2,7 @@ import { Controls, Canvas, Meta, Title } from '@storybook/blocks';
import * as tableStories from './table.stories';
import ComponentApisLink from '../../docs/component-apis-link.mdx';
import { tableTag } from '../../../../nimble-components/src/table';
+import { tableFitRowsHeight } from '../../../../nimble-components/src/theme-provider/design-tokens';
@@ -23,10 +24,13 @@ and the **Table Column** pages for individual table column types.
### Sizing
-The should be sized explicitly or sized to fill the space
-of a parent container. The does not currently support
-being styled with `height: auto`. The ability to auto size the table is tracked
-with [issue 1624](https://github.com/ni/nimble/issues/1624).
+The table's height can be configured to grow to fit all rows without a scrollbar
+by styling the table's height with the {tableFitRowsHeight.name}
+token.
+
+The table has a default maximum height that will render approximately 40 rows to
+avoid performance problems. Use caution when overriding the maximum height of
+the table because this may lead to performance issues.
### Full bleed
diff --git a/packages/storybook/src/nimble/table/table.stories.ts b/packages/storybook/src/nimble/table/table.stories.ts
index 93b98982fa..c6e759796e 100644
--- a/packages/storybook/src/nimble/table/table.stories.ts
+++ b/packages/storybook/src/nimble/table/table.stories.ts
@@ -1,6 +1,7 @@
import { html, ref } from '@microsoft/fast-element';
import { withActions } from '@storybook/addon-actions/decorator';
import type { HtmlRenderer, Meta, StoryObj } from '@storybook/html';
+import { tableFitRowsHeight } from '../../../../nimble-components/src/theme-provider/design-tokens';
import { iconUserTag } from '../../../../nimble-components/src/icons/user';
import { menuTag } from '../../../../nimble-components/src/menu';
import { menuItemTag } from '../../../../nimble-components/src/menu-item';
@@ -13,6 +14,10 @@ import {
TableRowSelectionMode
} from '../../../../nimble-components/src/table/types';
import { ExampleDataType } from '../../../../nimble-components/src/table/tests/types';
+import {
+ scssPropertySetterMarkdown,
+ tokenNames
+} from '../../../../nimble-components/src/theme-provider/design-token-names';
import {
addLabelUseMetadata,
type LabelUserArgs
@@ -78,6 +83,7 @@ interface TableArgs extends BaseTableArgs {
selectionChange: undefined;
columnConfigurationChange: undefined;
rowExpandToggle: undefined;
+ fitRowsHeight: boolean;
}
const simpleData = [
@@ -263,9 +269,18 @@ const setSelectedRecordIdsDescription = `A function that makes the rows associat
If a record does not exist in the table's data, it will not be selected. If multiple record IDs are specified when the table's selection
mode is \`single\`, only the first record that exists in the table's data will become selected.`;
+const fitRowsHeightDescription = `Style the table with ${scssPropertySetterMarkdown(tokenNames.tableFitRowsHeight, 'height')} to make the table's height grow to fit all rows.
+
+See the **Sizing** section for information on sizing the table.`;
+
export const table: StoryObj = {
// prettier-ignore
render: createUserSelectedThemeStory(html`
+
<${tableTag}
${ref('tableRef')}
selection-mode="${x => TableRowSelectionMode[x.selectionMode]}"
@@ -485,6 +500,11 @@ export const table: StoryObj = {
'Event emitted when the user expands or collapses a row in a table with hierarchy. This does not emit when group rows are expanded or collapsed.',
control: false,
table: { category: apiCategory.events }
+ },
+ fitRowsHeight: {
+ name: 'Fit rows height',
+ description: fitRowsHeightDescription,
+ table: { category: apiCategory.styles }
}
},
args: {
@@ -494,6 +514,7 @@ export const table: StoryObj = {
validity: undefined,
checkValidity: undefined,
tableRef: undefined,
+ fitRowsHeight: false,
updateData: x => {
void (async () => {
// Safari workaround: the table element instance is made at this point