Skip to content

Commit

Permalink
chore: Add RTL support for the Table component. (#2155)
Browse files Browse the repository at this point in the history
  • Loading branch information
DaemonCahill authored Apr 22, 2024
1 parent 9a292af commit 34b4ff4
Show file tree
Hide file tree
Showing 24 changed files with 324 additions and 217 deletions.
6 changes: 3 additions & 3 deletions pages/table-fragments/sticky-columns-custom.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ function TableCell({
stickyColumns,
getClassName: props => ({
[styles['sticky-cell']]: !!props,
[styles['sticky-cell-last-left']]: !!props?.lastLeft,
[styles['sticky-cell-last-right']]: !!props?.lastRight,
[styles['sticky-cell-pad-left']]: !!props?.padLeft,
[styles['sticky-cell-last-left']]: !!props?.lastInsetInlineStart,
[styles['sticky-cell-last-right']]: !!props?.lastInsetInlineEnd,
[styles['sticky-cell-pad-left']]: !!props?.padInlineStart,
}),
});
return (
Expand Down
2 changes: 1 addition & 1 deletion pages/table/resizable-coloumns-flex-grow.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default function () {
display: 'flex',
alignItems: 'stretch',
flexWrap: 'nowrap',
width: 'calc(100% - 32px)',
inlineSize: 'calc(100% - 32px)',
margin: '16px',
}}
>
Expand Down
11 changes: 11 additions & 0 deletions src/internal/direction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,14 @@ export function getLogicalBoundingClientRect(element: HTMLElement | SVGElement)
insetInlineEnd,
};
}

/**
* The pageX position needs to be converted so it is relative to the right of
* the document in order for computations to yield the same result in both
* element directions.
*/
export function getLogicalPageX(event: MouseEvent) {
return event.target instanceof HTMLElement && isRtl(event.target)
? document.documentElement.clientWidth - event.pageX
: event.pageX;
}
11 changes: 10 additions & 1 deletion src/internal/utils/handle-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
import { KeyCode } from '../keycode';
import { isRtl } from '../direction';

interface EventLike {
export function isEventLike(event: any): event is EventLike {
return event.currentTarget instanceof HTMLElement;
}

export interface EventLike {
keyCode: number;
currentTarget: HTMLElement;
}
Expand All @@ -15,6 +19,7 @@ export default function handleKey(
onBlockEnd,
onBlockStart,
onEnd,
onEscape,
onHome,
onInlineEnd,
onInlineStart,
Expand All @@ -25,6 +30,7 @@ export default function handleKey(
onBlockEnd?: () => void;
onBlockStart?: () => void;
onEnd?: () => void;
onEscape?: () => void;
onHome?: () => void;
onInlineEnd?: () => void;
onInlineStart?: () => void;
Expand All @@ -43,6 +49,9 @@ export default function handleKey(
case KeyCode.space:
onActivate?.();
break;
case KeyCode.escape:
onEscape?.();
break;
case KeyCode.home:
onHome?.();
break;
Expand Down
8 changes: 4 additions & 4 deletions src/table/__tests__/empty-state.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,23 +83,23 @@ function mockResizeObserver(contentBoxWidth: number) {
test('should apply width to the empty state container', () => {
const fireCallbacks = mockResizeObserver(600);
const { wrapper } = renderTable(<Table columnDefinitions={defaultColumns} items={[]} />);
expect(findStickyParent(wrapper).style.width).toEqual('600px');
expect(findStickyParent(wrapper).style.inlineSize).toEqual('600px');

fireCallbacks({ contentBoxWidth: 700 } as unknown as ContainerQueryEntry);
expect(findStickyParent(wrapper).style.width).toEqual('700px');
expect(findStickyParent(wrapper).style.inlineSize).toEqual('700px');
});

test('should floor the value to prevent unwanted horizontal scrolling', () => {
mockResizeObserver(123.4);
const { wrapper } = renderTable(<Table columnDefinitions={defaultColumns} items={[]} />);
expect(findStickyParent(wrapper).style.width).toEqual('123px');
expect(findStickyParent(wrapper).style.inlineSize).toEqual('123px');
});

test('should not apply width when browser does not support position sticky', () => {
mockResizeObserver(600);
jest.mocked(supportsStickyPosition).mockReturnValue(false);
const { wrapper } = renderTable(<Table columnDefinitions={defaultColumns} items={[]} />);
expect(findStickyParent(wrapper).style.width).toEqual('');
expect(findStickyParent(wrapper).style.inlineSize).toEqual('');
});

test('should set colspan attribute matching to the total number of columns', () => {
Expand Down
36 changes: 36 additions & 0 deletions src/table/__tests__/resizable-columns.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -465,3 +465,39 @@ test('should set last column width to "auto" when container width exceeds total
fireCallbacks({ contentBoxWidth: totalColumnsWidth + 1 } as unknown as ContainerQueryEntry);
expect(wrapper.findColumnHeaders().map(w => w.getElement().style.width)).toEqual(['150px', 'auto']);
});

describe('resize in rtl', () => {
beforeEach(() => {
document.body.style.direction = 'rtl';
});

test('should resize column to grow', () => {
const { wrapper } = renderTable(<Table {...defaultProps} />);
expect(wrapper.findColumnHeaders()[0].getElement()).toHaveStyle({ width: '150px' });

fireMousedown(wrapper.findColumnResizer(1)!);
fireMouseMove(-200);
fireMouseup(-200);
expect(wrapper.findColumnHeaders()[0].getElement()).toHaveStyle({ width: '200px' });
});

test('should resize column to shrink', () => {
const { wrapper } = renderTable(<Table {...defaultProps} />);
expect(wrapper.findColumnHeaders()[0].getElement()).toHaveStyle({ width: '150px' });

fireMousedown(wrapper.findColumnResizer(1)!);
fireMouseMove(-130);
fireMouseup(-130);
expect(wrapper.findColumnHeaders()[0].getElement()).toHaveStyle({ width: '130px' });
});

test('should not allow to resize column below the min width', () => {
const { wrapper } = renderTable(<Table {...defaultProps} />);
expect(wrapper.findColumnHeaders()[0].getElement()).toHaveStyle({ width: '150px' });

fireMousedown(wrapper.findColumnResizer(1)!);
fireMouseMove(-10);
fireMouseup(-10);
expect(wrapper.findColumnHeaders()[0].getElement()).toHaveStyle({ width: '80px' });
});
});
23 changes: 19 additions & 4 deletions src/table/body-cell/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ $success-icon-padding-right: calc(#{$edit-button-padding-right} + #{$icon-width-
*/
&:first-child.has-striped-rows {
@include cell-offset(awsui.$space-xxs);
&-sticky-cell-pad-left {
&-sticky-cell-pad-inline-start {
@include cell-offset(awsui.$space-table-horizontal);
}
}
Expand Down Expand Up @@ -184,19 +184,34 @@ $success-icon-padding-right: calc(#{$edit-button-padding-right} + #{$icon-width-
&:last-child {
box-shadow: 4px 0 0 0 awsui.$color-background-container-content;
clip-path: inset(0 0 0 0);
&.sticky-cell-last-right {
&.sticky-cell-last-inline-end {
box-shadow: awsui.$shadow-sticky-column-last, 8px 0 0 0 awsui.$color-background-container-content;
clip-path: inset(0 0 0 -24px);

@include styles.with-direction('rtl') {
box-shadow: awsui.$shadow-sticky-column-first;
clip-path: inset(0 -24px 0 0);
}
}
}
}
&-last-left {
&-last-inline-start {
box-shadow: awsui.$shadow-sticky-column-first;
clip-path: inset(0px -24px 0px 0px);

@include styles.with-direction('rtl') {
box-shadow: awsui.$shadow-sticky-column-last;
clip-path: inset(0 0 0 -24px);
}
}
&-last-right {
&-last-inline-end {
box-shadow: awsui.$shadow-sticky-column-last;
clip-path: inset(0 0 0 -24px);

@include styles.with-direction('rtl') {
box-shadow: awsui.$shadow-sticky-column-first;
clip-path: inset(0 -24px 0 0);
}
}
@include styles.with-motion {
transition-property: padding;
Expand Down
8 changes: 8 additions & 0 deletions src/table/expandable-rows/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,16 @@
.expand-toggle-icon {
transform: rotate(-90deg);

@include styles.with-direction('rtl') {
transform: rotate(90deg);
}

&-expanded {
transform: rotate(0deg);

@include styles.with-direction('rtl') {
transform: rotate(0deg);
}
}
}

Expand Down
17 changes: 14 additions & 3 deletions src/table/header-cell/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,28 @@
&-pad-left:not(.has-selection) {
padding-inline-start: awsui.$space-table-horizontal;
}
&-last-left {
&-last-inline-start {
box-shadow: awsui.$shadow-sticky-column-first;
clip-path: inset(0px -24px 0px 0px);
& > .resize-divider {
display: none;
}

@include styles.with-direction('rtl') {
box-shadow: awsui.$shadow-sticky-column-last;
clip-path: inset(0 0 0 -24px);
}
}
&-last-right {
&-last-inline-end {
box-shadow: awsui.$shadow-sticky-column-last;
clip-path: inset(0 0 0 -24px);

@include styles.with-direction('rtl') {
box-shadow: awsui.$shadow-sticky-column-first;
clip-path: inset(0 -24px 0 0);
}
}

@include styles.with-motion {
transition-property: padding;
transition-duration: awsui.$motion-duration-transition-show-quick;
Expand Down Expand Up @@ -167,7 +178,7 @@ settings icon in the pagination slot.
.header-cell.is-visual-refresh {
&:first-child:not(.has-striped-rows) {
@include cell-offset(awsui.$space-xxxs);
&.sticky-cell-pad-left {
&.sticky-cell-pad-inline-start {
@include cell-offset(awsui.$space-table-horizontal);
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/table/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { NoDataCell } from './no-data-cell';
import { usePerformanceMarks } from '../internal/hooks/use-performance-marks';
import { getContentHeaderClassName } from '../internal/utils/content-header-utils';
import { useExpandableTableProps } from './expandable-rows/expandable-rows-utils';
import { getLogicalBoundingClientRect } from '../internal/direction';

const GRID_NAVIGATION_PAGE_SIZE = 10;
const SELECTION_COLUMN_WIDTH = 54;
Expand Down Expand Up @@ -309,8 +310,9 @@ const InternalTable = React.forwardRef(
});
const toolsHeaderWrapper = useRef<HTMLDivElement>(null);
// If is mobile, we take into consideration the AppLayout's mobile bar and we subtract the tools wrapper height so only the table header is sticky
const toolsHeaderHeight =
(toolsHeaderWrapper?.current as HTMLDivElement | null)?.getBoundingClientRect().height ?? 0;
const toolsHeaderHeight = toolsHeaderWrapper?.current
? getLogicalBoundingClientRect(toolsHeaderWrapper.current).blockSize
: 0;

const colIndexOffset = selectionType ? 1 : 0;
const totalColumnsCount = visibleColumnDefinitions.length + colIndexOffset;
Expand Down
10 changes: 5 additions & 5 deletions src/table/no-data-cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ export function NoDataCell({
}: NoDataCellProps) {
const cellContentRef = useRef<HTMLDivElement>(null);

useResizeObserver(containerRef, ({ contentBoxWidth: containerWidth }) => {
useResizeObserver(containerRef, ({ contentBoxWidth: containerInlineSize }) => {
if (tableRef.current && cellContentRef.current && supportsStickyPosition()) {
const tablePaddingLeft = parseFloat(getComputedStyle(tableRef.current).paddingLeft) || 0;
const tablePaddingRight = parseFloat(getComputedStyle(tableRef.current).paddingRight) || 0;
const contentWidth = containerWidth + tablePaddingLeft + tablePaddingRight;
cellContentRef.current.style.width = Math.floor(contentWidth) + 'px';
const tablePaddingInlineStart = parseFloat(getComputedStyle(tableRef.current).paddingInlineStart) || 0;
const tablePaddingInlineEnd = parseFloat(getComputedStyle(tableRef.current).paddingInlineEnd) || 0;
const inlineSize = containerInlineSize + tablePaddingInlineStart + tablePaddingInlineEnd;
cellContentRef.current.style.inlineSize = Math.floor(inlineSize) + 'px';
}
});

Expand Down
Loading

0 comments on commit 34b4ff4

Please sign in to comment.