Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions pages/table-root/header-truncation.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React from 'react';

import TableBody from '~components/table-body';
import TableBodyCell from '~components/table-body-cell';
import TableHead from '~components/table-head';
import TableHeaderCell from '~components/table-header-cell';
import TableRoot, { TableRootProps } from '~components/table-root';
import TableRow from '~components/table-row';

import { SimplePage } from '../app/templates';

import styles from './styles.scss';

// Each column is narrower than its header label, so every header truncates.
const NARROW_COLUMNS: ReadonlyArray<TableRootProps.ColumnDefinition> = [{ size: 120 }, { size: 100 }, { size: 100 }];

export default function TableHeaderTruncationPage() {
return (
<SimplePage title="Table atomics — header truncation">
<TableRoot
columnLayout={{ type: 'grid', columns: NARROW_COLUMNS }}
ariaLabel="Narrow columns"
data-testid="narrow-table"
>
<TableHead>
<TableRow>
<TableHeaderCell>
<button type="button" className={styles['sort-button']} data-testid="first-header-button">
@timestampMilliseconds
</button>
</TableHeaderCell>
<TableHeaderCell>@billedDuration</TableHeaderCell>
<TableHeaderCell>@memoryAllocated</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableBodyCell>1,700,000</TableBodyCell>
<TableBodyCell>120</TableBodyCell>
<TableBodyCell>512</TableBodyCell>
</TableRow>
</TableBody>
</TableRoot>
</SimplePage>
);
}
62 changes: 62 additions & 0 deletions src/table-header-cell/__integ__/truncation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { BasePageObject } from '@cloudscape-design/browser-test-tools/page-objects';
import useBrowser from '@cloudscape-design/browser-test-tools/use-browser';

import bodyCellStyles from '../../../lib/components/table/body-cell/styles.selectors.js';
import styles from '../../../lib/components/table-header-cell/styles.selectors.js';

const pageUrl = '#/light/table-root/header-truncation';
const headerContent = `[data-testid="narrow-table"] .${styles['header-cell-content']}`;
const firstHeaderButton = '[data-testid="first-header-button"]';

test(
'truncates header text that is wider than its column with an ellipsis',
useBrowser(async browser => {
await browser.url(pageUrl);
const page = new BasePageObject(browser);
await page.waitForVisible(headerContent);

const contents = await browser.execute((selector: string) => {
return Array.from(document.querySelectorAll<HTMLElement>(selector)).map(element => ({
overflowsContent: element.scrollWidth > element.clientWidth,
textOverflow: getComputedStyle(element).textOverflow,
rightOverhang: element.getBoundingClientRect().right - element.parentElement!.getBoundingClientRect().right,
}));
}, headerContent);

expect(contents).toHaveLength(3);
for (const content of contents) {
expect(content.overflowsContent).toBe(true);
expect(content.textOverflow).toBe('ellipsis');
expect(content.rightOverhang).toBeLessThanOrEqual(0);
}
})
);

test(
'leaves room inside the clip for a focus ring on the first column, without moving its text',
useBrowser(async browser => {
await browser.url(pageUrl);
const page = new BasePageObject(browser);
await page.waitForVisible(firstHeaderButton);

const { buttonLeft, clipLeft, bodyTextLeft } = await browser.execute(
(buttonSelector: string, bodyContentClass: string) => {
const button = document.querySelector<HTMLElement>(buttonSelector)!;
const bodyContent = document.querySelector<HTMLElement>(`.${bodyContentClass}`)!;
return {
buttonLeft: button.getBoundingClientRect().left,
clipLeft: button.parentElement!.getBoundingClientRect().left,
bodyTextLeft:
bodyContent.getBoundingClientRect().left + parseFloat(getComputedStyle(bodyContent).paddingInlineStart),
};
},
firstHeaderButton,
bodyCellStyles['body-cell-content']
);

expect(buttonLeft - clipLeft).toBeGreaterThanOrEqual(4);
expect(buttonLeft).toBe(bodyTextLeft);
})
);
13 changes: 11 additions & 2 deletions src/table-header-cell/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,18 @@
padding-inline: 0;
}

.header-cell:not(.disable-paddings) > .header-cell-content {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

// VR-gated: the substrate is shared with the existing Table, which still supports non-VR mode.
.header-cell.is-visual-refresh:first-child > .header-cell-content {
padding-inline-start: 0;
// The text sits flush with the cell's start edge, but the padding/margin pair keeps room inside the
// truncation clip so a focus ring on the first control isn't cut off.
.header-cell.is-visual-refresh:first-child:not(.disable-paddings) > .header-cell-content {
padding-inline-start: awsui.$space-button-focus-outline-gutter;
margin-inline-start: calc(-1 * #{awsui.$space-button-focus-outline-gutter});
}

// Stretch to the row track, else an empty control header collapses to ~1px, leaving a stray divider stub.
Expand Down
Loading