Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge #2

Merged
merged 22 commits into from
Nov 28, 2022
Merged
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2cc623c
chore: bump loader-utils from 3.2.0 to 3.2.1 (#482)
dependabot[bot] Nov 18, 2022
162110d
fix: Split-panel should have accessible name and consistent identific…
karmanya79 Nov 18, 2022
916caba
feat: Show popover for truncated Breadcrumb text on hover or focus (…
YueyingLu Nov 18, 2022
64ecd38
fix: Fix prevent anchor navigation in disabled tabs with `href` (#484)
taheramr Nov 18, 2022
9e164b3
fix: Table header stays sticky with few itmes and open split panel (#…
johannes-weber Nov 21, 2022
cd31ba3
fix: Add ariaLabelledBy and tabIndex attributes to Tab content (#500)
jperals Nov 21, 2022
0c905fe
feat: Add steps information to annotation trigger aria label (#507)
YueyingLu Nov 22, 2022
52c2f36
fix: Prevent resetting StickyBackground from elements using InternalC…
johannes-weber Nov 22, 2022
bc9db2b
revert: Show popover for truncated Breadcrumb text on hover or focus …
YueyingLu Nov 22, 2022
1ffc10f
feat: Add descriptive aria-label to tutorial pabel task list steps ti…
YueyingLu Nov 22, 2022
2deaa03
fix: Use deterministic table items for AppLayout page (#513)
johannes-weber Nov 22, 2022
d8d4ad5
fix: Fix tag editor key suggestions getting stuck in React 18's stric…
avinashbot Nov 23, 2022
5648d1e
chore: Print stack trace in error message (#516)
just-boris Nov 23, 2022
09e3562
feat: Screen reader should announce tutorial completion message (#510)
YueyingLu Nov 23, 2022
14c2f36
refactor: Join all state changes in a single place (#520)
just-boris Nov 23, 2022
70d3224
fix: Tutorial panel margin on top of task list (#524)
YueyingLu Nov 24, 2022
aa135d7
chore: Disable code coverage when running locally (#519)
just-boris Nov 24, 2022
7dbd788
chore: Add analytics beta hook
michaeldowseza Nov 21, 2022
218aa7e
fix: Add aria live announcer to code editor for errors and warnings (…
jorycunningham Nov 24, 2022
4bb4f3a
fix: Allow popovers in charts to be dismissed by pressing Esc (#514)
avinashbot Nov 25, 2022
b8f915c
refactor: Make range calendar selection controllable (#526)
just-boris Nov 25, 2022
1095cef
fix: Annotation focus on the start of the popover on clicking next (#…
YueyingLu Nov 28, 2022
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
Prev Previous commit
Next Next commit
fix: Add ariaLabelledBy and tabIndex attributes to Tab content (cloud…
jperals authored Nov 21, 2022
commit cd31ba36be9d96c719a533f5c5050900ea50af8e
8 changes: 4 additions & 4 deletions src/__integ__/__snapshots__/themes.test.ts.snap
Original file line number Diff line number Diff line change
@@ -565,7 +565,7 @@ Object {
"space-table-header-focus-outline-gutter": "0px",
"space-table-header-horizontal": "20px",
"space-table-horizontal": "0px",
"space-tabs-content-top": "0px",
"space-tabs-content-top": "12px",
"space-tabs-focus-outline-gutter": "0px",
"space-text-grid-vertical": "16px",
"space-xl": "24px",
@@ -1142,7 +1142,7 @@ Object {
"space-table-header-focus-outline-gutter": "0px",
"space-table-header-horizontal": "20px",
"space-table-horizontal": "0px",
"space-tabs-content-top": "0px",
"space-tabs-content-top": "16px",
"space-tabs-focus-outline-gutter": "0px",
"space-text-grid-vertical": "20px",
"space-xl": "24px",
@@ -1719,7 +1719,7 @@ Object {
"space-table-header-focus-outline-gutter": "0px",
"space-table-header-horizontal": "20px",
"space-table-horizontal": "0px",
"space-tabs-content-top": "0px",
"space-tabs-content-top": "16px",
"space-tabs-focus-outline-gutter": "0px",
"space-text-grid-vertical": "20px",
"space-xl": "24px",
@@ -2296,7 +2296,7 @@ Object {
"space-table-header-focus-outline-gutter": "0px",
"space-table-header-horizontal": "20px",
"space-table-horizontal": "0px",
"space-tabs-content-top": "0px",
"space-tabs-content-top": "16px",
"space-tabs-focus-outline-gutter": "0px",
"space-text-grid-vertical": "20px",
"space-xl": "24px",
11 changes: 10 additions & 1 deletion src/tabs/__integ__/tabs.test.ts
Original file line number Diff line number Diff line change
@@ -239,7 +239,7 @@ test(
'has the same tab behavior when paginated (pagination links not tabbable)',
setupTest(async page => {
await page.click('#before');
await page.keys(['Tab', 'Tab']);
await page.keys(['Tab', 'Tab', 'Tab']);
await expect(page.isFocused('#after')).resolves.toBe(true);
}, smallViewport)
);
@@ -286,3 +286,12 @@ test(
expect(urlAfter).toEqual(urlBefore);
}, true)
);

test(
'allows to focus on tab content area',
setupTest(async page => {
await page.focusTabHeader();
await page.keys(['Tab']);
await expect(page.isFocused(wrapper.findTabContent().toSelector())).resolves.toBe(true);
})
);
60 changes: 58 additions & 2 deletions src/tabs/__tests__/tabs.test.tsx
Original file line number Diff line number Diff line change
@@ -145,7 +145,7 @@ describe('Tabs', () => {
const tabs = renderTabs(<Tabs tabs={defaultTabs} activeTabId="third" onChange={() => void 0} />).wrapper;

expect(tabs.findActiveTab()).toBe(null);
expect(tabs.findTabContent()).toBe(null);
expect(tabs.findTabContent()!.getElement()).toBeEmptyDOMElement();
});

test('displays active tab content', () => {
@@ -164,7 +164,7 @@ describe('Tabs', () => {
const tabs = renderTabs(<Tabs tabs={defaultTabs} activeTabId="fourth" onChange={() => void 0} />).wrapper;

expect(tabs.findActiveTab()!.getElement()).toHaveTextContent('Fourth tab');
expect(tabs.findTabContent()).toBe(null);
expect(tabs.findTabContent()!.getElement()).toBeEmptyDOMElement();
});

test('displays first tab by default if uncontrolled', () => {
@@ -652,4 +652,60 @@ describe('Tabs', () => {
).toThrow('A javascript: URL was blocked as a security precaution.');
});
});

describe('Tab header', () => {
test('keeps the ids of the tab links unchanged across re-renders', () => {
const firstTabId = defaultTabs[0].id;
const secondTabId = defaultTabs[1].id;
const { wrapper, rerender } = renderTabs(
<Tabs tabs={defaultTabs} activeTabId={firstTabId} onChange={() => void 0} />
);
const getFirstTabLinkElementId = () => wrapper.findTabLinkById(firstTabId)!.getElement()!.id;
const firstTabLinkElementId = getFirstTabLinkElementId();
expect(firstTabLinkElementId).toBeTruthy();
rerender(<Tabs tabs={defaultTabs} activeTabId={secondTabId} onChange={() => void 0} />);
expect(getFirstTabLinkElementId()).toEqual(firstTabLinkElementId);
});
});

describe('Tab content', () => {
test('has tabindex attribute', () => {
const tabs = renderTabs(<Tabs tabs={defaultTabs} />).wrapper;
expect(tabs.findTabContent()!.getElement()).toHaveAttribute('tabindex', '0');
});

test('has tabindex attribute even for empty tab content', () => {
const tabs = renderTabs(<Tabs tabs={defaultTabs} activeTabId={'fourth'} onChange={() => void 0} />).wrapper;
expect(tabs.findTabContent()!.getElement()).toHaveAttribute('tabindex', '0');
});

test('has aria-labelledby attribute set to the id of the corresponding tab', () => {
const activeTabId = defaultTabs[0].id;
const tabs = renderTabs(<Tabs tabs={defaultTabs} activeTabId={activeTabId} onChange={() => void 0} />).wrapper;
const firstTabLink = tabs.findTabLinkById(activeTabId)!.getElement()!;
const tabLinkElementId = firstTabLink.id;
expect(tabLinkElementId).toBeTruthy();
expect(tabs.findTabContent()!.getElement()).toHaveAttribute('aria-labelledby', tabLinkElementId);
});

test('changes aria-labelledby attribute accordingly when the active tab changes', () => {
const firstTabId = defaultTabs[0].id;
const secondTabId = defaultTabs[1].id;

const { wrapper, rerender } = renderTabs(
<Tabs tabs={defaultTabs} activeTabId={firstTabId} onChange={() => void 0} />
);

const verifyTabContentLabelledBy = (tabId: string) => {
const tabLink = wrapper.findTabLinkById(tabId)!.getElement()!;
const tabLinkElementId = tabLink.id;
expect(tabLinkElementId).toBeTruthy();
expect(wrapper.findTabContent()!.getElement()).toHaveAttribute('aria-labelledby', tabLinkElementId);
};

verifyTabContentLabelledBy(firstTabId);
rerender(<Tabs tabs={defaultTabs} activeTabId={secondTabId} onChange={() => void 0} />);
verifyTabContentLabelledBy(secondTabId);
});
});
});
17 changes: 12 additions & 5 deletions src/tabs/index.tsx
Original file line number Diff line number Diff line change
@@ -7,11 +7,12 @@ import InternalContainer from '../container/internal';
import { TabsProps } from './interfaces';
import clsx from 'clsx';
import styles from './styles.css.js';
import { TabHeaderBar } from './tab-header-bar';
import { getTabElementId, TabHeaderBar } from './tab-header-bar';
import { useControllable } from '../internal/hooks/use-controllable';
import { applyDisplayName } from '../internal/utils/apply-display-name';
import useBaseComponent from '../internal/hooks/use-base-component';
import { checkSafeUrl } from '../internal/utils/check-safe-url';
import useFocusVisible from '../internal/hooks/focus-visible';

export { TabsProps };

@@ -50,24 +51,30 @@ export default function Tabs({

const baseProps = getBaseProps(rest);

const focusVisible = useFocusVisible();

const content = () => {
const selectedTab = tabs.filter(tab => tab.id === activeTabId)[0];
const renderContent = (tab: TabsProps.Tab) => {
const isContentActive = tab === selectedTab && !selectedTab.disabled && selectedTab.content;
const isTabSelected = tab === selectedTab;

const classes = clsx({
[styles['tabs-content']]: true,
[styles['tabs-content-active']]: isContentActive,
[styles['tabs-content-active']]: isTabSelected,
});

const contentAttributes: JSX.IntrinsicElements['div'] = {
...focusVisible,
className: classes,
role: 'tabpanel',
id: `${idNamespace}-${tab.id}-panel`,
key: `${idNamespace}-${tab.id}-panel`,
tabIndex: 0,
'aria-labelledby': getTabElementId({ namespace: idNamespace, tabId: tab.id }),
};

return <div {...contentAttributes}>{isContentActive && selectedTab.content}</div>;
const isContentShown = isTabSelected && !selectedTab.disabled;
return <div {...contentAttributes}>{isContentShown && selectedTab.content}</div>;
};

return (
@@ -107,7 +114,7 @@ export default function Tabs({
{...baseProps}
className={clsx(baseProps.className, styles.root)}
__internalRootRef={__internalRootRef}
disableContentPaddings={disableContentPaddings}
disableContentPaddings={true}
>
{content()}
</InternalContainer>
26 changes: 15 additions & 11 deletions src/tabs/styles.scss
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@

@use '../internal/styles' as styles;
@use '../internal/styles/tokens' as awsui;
@use '../internal/hooks/focus-visible' as focus-visible;

@import './tab-header-bar';

@@ -18,22 +19,25 @@
width: 100%;
}

.tabs-content-wrapper {
&.with-paddings {
padding: awsui.$space-scaled-m 0;
}
.tabs-content {
display: none;
}

.tabs-container-content-wrapper {
&.with-paddings {
padding-top: awsui.$space-tabs-content-top;
.tabs-content-active {
display: block;
@include focus-visible.when-visible {
@include styles.container-focus();
}
}

.tabs-content {
display: none;
.tabs-content-wrapper {
&.with-paddings > .tabs-content {
padding: awsui.$space-scaled-m 0;
}
}

.tabs-content-active {
display: block;
.tabs-container-content-wrapper {
&.with-paddings > .tabs-content {
padding: awsui.$space-tabs-content-top awsui.$space-container-horizontal awsui.$space-scaled-l;
}
}
5 changes: 5 additions & 0 deletions src/tabs/tab-header-bar.tsx
Original file line number Diff line number Diff line change
@@ -268,6 +268,7 @@ export function TabHeaderBar({
'aria-selected': tab.id === activeTabId,
'aria-controls': `${idNamespace}-${tab.id}-panel`,
'data-testid': tab.id,
id: getTabElementId({ namespace: idNamespace, tabId: tab.id }),
children: <span className={styles['tabs-tab-label']}>{tab.label}</span>,
};

@@ -313,3 +314,7 @@ export function TabHeaderBar({
);
}
}

export function getTabElementId({ namespace, tabId }: { namespace: string; tabId: string }) {
return namespace + '-' + tabId;
}
2 changes: 1 addition & 1 deletion style-dictionary/classic/spacing.ts
Original file line number Diff line number Diff line change
@@ -39,7 +39,7 @@ const tokens: StyleDictionary.SpacingDictionary = {
spacePanelSplitTop: '0px',
spaceSegmentedControlFocusOutlineGutter: '0px',
spaceTableHeaderFocusOutlineGutter: { compact: '0px' },
spaceTabsContentTop: '0px',
spaceTabsContentTop: '{spaceScaledM}',
spaceTableHorizontal: '0px',
spaceTableHeaderHorizontal: '{spaceContainerHorizontal}',
spaceTableContentBottom: '0px',