Skip to content

Commit 9298076

Browse files
authored
feat: Table cell align for all cells (#3318)
1 parent b4f0b2f commit 9298076

File tree

10 files changed

+115
-11
lines changed

10 files changed

+115
-11
lines changed

pages/table/cell-permutations.page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,6 @@ function TableCellsDemo() {
224224
sortingField: index === 2 ? 'field-1' : index === 3 ? 'field-2' : undefined,
225225
activeSorting: index === 3,
226226
cell: item => cellRenderer.render(item),
227-
verticalAlign: settings.verticalAlign,
228227
editConfig: settings.isEditable
229228
? {
230229
ariaLabel: 'Edit dialog aria label',
@@ -288,6 +287,7 @@ function TableCellsDemo() {
288287
stickyColumns={{ first: settings.stickyColumnsFirst, last: settings.stickyColumnsLast }}
289288
selectionType={selectionType}
290289
selectedItems={selectedItems}
290+
cellVerticalAlign={settings.verticalAlign}
291291
empty="Empty"
292292
loading={settings.tableLoading}
293293
loadingText="Loading"

pages/table/permutations.page.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,17 @@ const VERTICAL_ALIGN_COLUMNS: TableProps.ColumnDefinition<any>[] = [
8181
verticalAlign: 'top',
8282
},
8383
{
84-
id: 'type-2',
85-
header: 'Value',
84+
id: 'value-top',
85+
header: 'Value (top)',
8686
cell: item => item.text,
8787
verticalAlign: 'top',
8888
},
89+
{
90+
id: 'value-middle',
91+
header: 'Value (middle)',
92+
cell: item => item.text,
93+
verticalAlign: 'middle',
94+
},
8995
];
9096

9197
/* eslint-disable react/jsx-key */
@@ -314,9 +320,12 @@ const permutations = createPermutations<TableProps>([
314320
items: [createSimpleItems(3)],
315321
},
316322
{
323+
header: ['Vertical align'],
317324
columnDefinitions: [VERTICAL_ALIGN_COLUMNS],
325+
cellVerticalAlign: ['top'],
318326
items: [createSimpleItems(3)],
319327
variant: [undefined, 'full-page'],
328+
selectionType: ['multi'],
320329
},
321330
{
322331
columnDefinitions: [

src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16568,6 +16568,23 @@ add a meaningful description to the whole selection.
1656816568
"optional": true,
1656916569
"type": "TableProps.AriaLabels<T>",
1657016570
},
16571+
{
16572+
"defaultValue": "'middle'",
16573+
"description": "Determines the alignment of the content inside table cells.
16574+
This property affects all cells, including the ones in the selection column.
16575+
To target individual cells use \`columnDefinitions.verticalAlign\`, that takes precedence over \`cellVerticalAlign\`.",
16576+
"inlineType": {
16577+
"name": "",
16578+
"type": "union",
16579+
"values": [
16580+
"middle",
16581+
"top",
16582+
],
16583+
},
16584+
"name": "cellVerticalAlign",
16585+
"optional": true,
16586+
"type": "string",
16587+
},
1657116588
{
1657216589
"deprecatedTag": "Custom CSS is not supported. For testing and other use cases, use [data attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes).",
1657316590
"description": "Adds the specified classes to the root element of the component.",
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import * as React from 'react';
4+
import { render } from '@testing-library/react';
5+
6+
import * as baseComponentHooks from '../../../lib/components/internal/hooks/use-base-component';
7+
import Table from '../../../lib/components/table';
8+
9+
const useBaseComponentSpy = jest.spyOn(baseComponentHooks, 'default');
10+
11+
test('reports cellVerticalAlign and columnDefinitionsVerticalAlign correctly', () => {
12+
const def = (id: string, verticalAlign: 'middle' | 'top') => ({ id, header: '', cell: () => null, verticalAlign });
13+
14+
render(<Table columnDefinitions={[def('1', 'middle')]} items={[]} />);
15+
16+
expect(useBaseComponentSpy).toHaveBeenCalledWith(
17+
'Table',
18+
{
19+
props: expect.objectContaining({ cellVerticalAlign: 'middle' }),
20+
metadata: expect.objectContaining({ usesColumnDefinitionsVerticalAlign: false }),
21+
},
22+
expect.anything()
23+
);
24+
25+
render(<Table columnDefinitions={[def('1', 'middle')]} items={[]} cellVerticalAlign="top" />);
26+
27+
expect(useBaseComponentSpy).toHaveBeenCalledWith(
28+
'Table',
29+
{
30+
props: expect.objectContaining({ cellVerticalAlign: 'top' }),
31+
metadata: expect.objectContaining({ usesColumnDefinitionsVerticalAlign: true }),
32+
},
33+
expect.anything()
34+
);
35+
36+
render(<Table columnDefinitions={[def('1', 'top'), def('2', 'top')]} items={[]} cellVerticalAlign="top" />);
37+
38+
expect(useBaseComponentSpy).toHaveBeenCalledWith(
39+
'Table',
40+
{
41+
props: expect.objectContaining({ cellVerticalAlign: 'top' }),
42+
metadata: expect.objectContaining({ usesColumnDefinitionsVerticalAlign: false }),
43+
},
44+
expect.anything()
45+
);
46+
});

src/table/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const Table = React.forwardRef(
2222
selectedItems = [],
2323
variant = 'container',
2424
contentDensity = 'comfortable',
25+
cellVerticalAlign = 'middle',
2526
firstIndex = 1,
2627
...props
2728
}: TableProps<T>,
@@ -46,6 +47,7 @@ const Table = React.forwardRef(
4647
enableKeyboardNavigation: props.enableKeyboardNavigation,
4748
totalItemsCount: props.totalItemsCount,
4849
flowType: analyticsMetadata.flowType,
50+
cellVerticalAlign,
4951
},
5052
metadata: {
5153
expandableRows: !!props.expandableRows,
@@ -61,6 +63,9 @@ const Table = React.forwardRef(
6163
hasInstanceIdentifier: Boolean(analyticsMetadata?.instanceIdentifier),
6264
usesVisibleColumns: !!props.visibleColumns,
6365
usesColumnDisplay: !!props.columnDisplay,
66+
usesColumnDefinitionsVerticalAlign: props.columnDefinitions.some(
67+
def => def.verticalAlign !== cellVerticalAlign
68+
),
6469
},
6570
},
6671
analyticsMetadata
@@ -89,6 +94,7 @@ const Table = React.forwardRef(
8994
variant,
9095
contentDensity,
9196
firstIndex,
97+
cellVerticalAlign,
9298
...props,
9399
...baseComponentProps,
94100
ref,

src/table/interfaces.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,14 @@ export interface TableProps<T = any> extends BaseComponentProps {
117117
* * `verticalAlign` ('middle' | 'top') - Determines the alignment of the content in the table cell.
118118
*/
119119
columnDefinitions: ReadonlyArray<TableProps.ColumnDefinition<T>>;
120+
121+
/**
122+
* Determines the alignment of the content inside table cells.
123+
* This property affects all cells, including the ones in the selection column.
124+
* To target individual cells use `columnDefinitions.verticalAlign`, that takes precedence over `cellVerticalAlign`.
125+
*/
126+
cellVerticalAlign?: 'middle' | 'top';
127+
120128
/**
121129
* Specifies the selection type (`'single' | 'multi'`).
122130
*/

src/table/internal.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ const GRID_NAVIGATION_PAGE_SIZE = 10;
6868
const SELECTION_COLUMN_WIDTH = 54;
6969
const selectionColumnId = Symbol('selection-column-id');
7070

71-
type InternalTableProps<T> = SomeRequired<TableProps<T>, 'items' | 'selectedItems' | 'variant' | 'firstIndex'> &
71+
type InternalTableProps<T> = SomeRequired<
72+
TableProps<T>,
73+
'items' | 'selectedItems' | 'variant' | 'firstIndex' | 'cellVerticalAlign'
74+
> &
7275
InternalBaseComponentProps & {
7376
__funnelSubStepProps?: InternalContainerProps['__funnelSubStepProps'];
7477
};
@@ -135,6 +138,7 @@ const InternalTable = React.forwardRef(
135138
renderLoaderLoading,
136139
renderLoaderError,
137140
renderLoaderEmpty,
141+
cellVerticalAlign,
138142
__funnelSubStepProps,
139143
...rest
140144
}: InternalTableProps<T>,
@@ -595,6 +599,7 @@ const InternalTable = React.forwardRef(
595599
rowIndex,
596600
itemKey: `${getTableItemKey(row.item)}`,
597601
}}
602+
verticalAlign={cellVerticalAlign}
598603
/>
599604
)}
600605

@@ -644,7 +649,7 @@ const InternalTable = React.forwardRef(
644649
submitEdit={cellEditing.submitEdit}
645650
columnId={column.id ?? colIndex}
646651
colIndex={colIndex + colIndexOffset}
647-
verticalAlign={column.verticalAlign}
652+
verticalAlign={column.verticalAlign ?? cellVerticalAlign}
648653
{...cellExpandableProps}
649654
{...getAnalyticsMetadataAttribute(analyticsMetadata)}
650655
/>
@@ -670,7 +675,11 @@ const InternalTable = React.forwardRef(
670675
{...rowRoleProps}
671676
>
672677
{getItemSelectionProps && (
673-
<TableBodySelectionCell {...sharedCellProps} columnId={selectionColumnId} />
678+
<TableBodySelectionCell
679+
{...sharedCellProps}
680+
columnId={selectionColumnId}
681+
verticalAlign={cellVerticalAlign}
682+
/>
674683
)}
675684
{visibleColumnDefinitions.map((column, colIndex) => (
676685
<TableLoaderCell

src/table/selection/selection-cell.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ export function TableHeaderSelectionCell({
5959
export function TableBodySelectionCell({ selectionControlProps, ...props }: TableBodySelectionCellProps) {
6060
return (
6161
<TableTdElement {...props} isSelection={true} wrapLines={false} isEditable={false} isEditing={false} colIndex={0}>
62-
{selectionControlProps ? <SelectionControl {...selectionControlProps} /> : null}
62+
{selectionControlProps ? (
63+
<SelectionControl {...selectionControlProps} verticalAlign={props.verticalAlign} />
64+
) : null}
6365
</TableTdElement>
6466
);
6567
}

src/table/selection/selection-control.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface SelectionControlProps extends SelectionProps {
2424
focusedComponent?: null | string;
2525
rowIndex?: number;
2626
itemKey?: string;
27+
verticalAlign?: 'middle' | 'top';
2728
}
2829

2930
export function SelectionControl({
@@ -37,6 +38,7 @@ export function SelectionControl({
3738
focusedComponent,
3839
rowIndex,
3940
itemKey,
41+
verticalAlign = 'middle',
4042
...sharedProps
4143
}: SelectionControlProps) {
4244
const controlId = useUniqueId();
@@ -45,7 +47,7 @@ export function SelectionControl({
4547

4648
const setShiftState = (event: KeyboardEvent | MouseEvent) => {
4749
if (isMultiSelection) {
48-
onShiftToggle && onShiftToggle(event.shiftKey);
50+
onShiftToggle?.(event.shiftKey);
4951
}
5052
};
5153

@@ -65,11 +67,11 @@ export function SelectionControl({
6567
if (isMultiSelection && !navigationActive) {
6668
if (event.keyCode === KeyCode.up) {
6769
event.preventDefault();
68-
onFocusUp && onFocusUp(event);
70+
onFocusUp?.(event);
6971
}
7072
if (event.keyCode === KeyCode.down) {
7173
event.preventDefault();
72-
onFocusDown && onFocusDown(event);
74+
onFocusDown?.(event);
7375
}
7476
}
7577
};
@@ -102,7 +104,7 @@ export function SelectionControl({
102104
onMouseUp={setShiftState}
103105
onClick={handleClick}
104106
htmlFor={controlId}
105-
className={clsx(styles.label, styles.root)}
107+
className={clsx(styles.label, styles.root, verticalAlign === 'top' && styles['label-top'])}
106108
aria-label={ariaLabel}
107109
title={ariaLabel}
108110
{...(rowIndex !== undefined && !sharedProps.disabled

src/table/selection/styles.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
border-inline-end: 0.1 * styles.$base-size solid transparent;
2525
}
2626

27+
.label-top {
28+
align-items: baseline;
29+
padding-block-start: awsui.$space-xs;
30+
}
31+
2732
.stud {
2833
visibility: hidden;
2934
}

0 commit comments

Comments
 (0)