diff --git a/pages/app/index.tsx b/pages/app/index.tsx index a5b0b16aaa..f6ea0d8b87 100644 --- a/pages/app/index.tsx +++ b/pages/app/index.tsx @@ -24,8 +24,7 @@ function App() { urlParams: { density, motionDisabled }, } = useContext(AppContext); - const isAppLayout = - pageId !== undefined && (pageId.indexOf('app-layout') > -1 || pageId.indexOf('content-layout') > -1); + const isAppLayout = pageId !== undefined && (pageId.includes('app-layout') || pageId.includes('content-layout')); // AppLayout already contains
// Also, AppLayout pages should resemble the ConsoleNav 2.0 styles const ContentTag = isAppLayout ? 'div' : 'main'; diff --git a/pages/cards/permutations.page.tsx b/pages/cards/permutations.page.tsx index 417f1e64bb..b53ce5052e 100644 --- a/pages/cards/permutations.page.tsx +++ b/pages/cards/permutations.page.tsx @@ -28,7 +28,14 @@ function createSimpleItems(count: number) { } const cardDefinition: CardsProps.CardDefinition = { - header: item => (item.number === 2 ? {item.text} : item.text), + header: item => + item.number === 2 ? ( + + {item.text} + + ) : ( + item.text + ), sections: [ { id: 'description', diff --git a/pages/cards/selection.page.tsx b/pages/cards/selection.page.tsx index 0c699f0ccc..3ec389b931 100644 --- a/pages/cards/selection.page.tsx +++ b/pages/cards/selection.page.tsx @@ -13,7 +13,7 @@ interface Item { const ariaLabels: CardsProps['ariaLabels'] = { selectionGroupLabel: 'group label', itemSelectionLabel: ({ selectedItems }, item) => - `${item.text} is ${selectedItems.indexOf(item) < 0 ? 'not ' : ''}selected`, + `${item.text} is ${!selectedItems.includes(item) ? 'not ' : ''}selected`, }; function createSimpleItems(count: number) { diff --git a/pages/tsconfig.json b/pages/tsconfig.json index edf008d36e..5c14178b3d 100644 --- a/pages/tsconfig.json +++ b/pages/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "lib": ["ES2015", "DOM"], + "lib": ["ES2020", "DOM"], "target": "ES2015", "declaration": false, "declarationMap": false, diff --git a/src/__a11y__/run-a11y-tests.ts b/src/__a11y__/run-a11y-tests.ts index 007c72d11b..4e2a41a9f0 100644 --- a/src/__a11y__/run-a11y-tests.ts +++ b/src/__a11y__/run-a11y-tests.ts @@ -6,7 +6,7 @@ import useBrowser from '@cloudscape-design/browser-test-tools/use-browser'; import { findAllPages } from '../__integ__/utils'; import A11yPageObject from './a11y-page-object'; -type Theme = string; +type Theme = 'default' | 'visual-refresh'; type Mode = 'light' | 'dark'; function setupTest(url: string, testFn: (page: A11yPageObject) => Promise) { @@ -19,21 +19,19 @@ function setupTest(url: string, testFn: (page: A11yPageObject) => Promise) } function urlFormatter(inputUrl: string, theme: Theme, mode: Mode) { - return `#/${mode}/${inputUrl}?visualRefresh=${theme.indexOf('visual-refresh') !== -1 ? 'true' : 'false'}`; + return `#/${mode}/${inputUrl}?visualRefresh=${theme === 'visual-refresh' ? 'true' : 'false'}`; } export default function runA11yTests(theme: Theme, mode: Mode, skip: string[] = []) { describe(`A11y checks for ${mode} ${theme}`, () => { findAllPages().forEach(inputUrl => { - const testFunction = - [ - ...skip, - 'theming/tokens', - // this page intentionally has issues to test the helper - 'undefined-texts', - ].indexOf(inputUrl) === -1 - ? test - : test.skip; + const skipPages = [ + ...skip, + 'theming/tokens', + // this page intentionally has issues to test the helper + 'undefined-texts', + ]; + const testFunction = skipPages.includes(inputUrl) ? test.skip : test; const url = urlFormatter(inputUrl, theme, mode); testFunction( url, diff --git a/src/__tests__/__snapshots__/documenter.test.ts.snap b/src/__tests__/__snapshots__/documenter.test.ts.snap index 3152e5dd33..23aa3af98d 100644 --- a/src/__tests__/__snapshots__/documenter.test.ts.snap +++ b/src/__tests__/__snapshots__/documenter.test.ts.snap @@ -3587,7 +3587,7 @@ add a meaningful description to the whole selection.", Object { "description": " Defines what to display in each card. It has the following properties: * \`header\` ((item) => ReactNode) - Responsible for displaying the card header. You receive the current item as an argument. - Use \`fontSize=\\"heading-m\\"\` on [link](/components/link/) components inside card header. + Use \`fontSize=\\"inherit\\"\` on [link](/components/link/) components inside card header. * \`sections\` (array) - Responsible for displaying the card content. Cards can have many sections in their body. Each entry in the array is responsible for displaying a section. An entry has the following properties: * \`id\`: (string) - A unique identifier for the section. The property is used as a [keys](https://reactjs.org/docs/lists-and-keys.html#keys) @@ -6522,6 +6522,7 @@ A flash message object contains the following properties: When a user clicks on this button the \`onDismiss\` handler is called. * \`dismissLabel\` (string) - Specifies an \`aria-label\` for to the dismiss icon button for improved accessibility. * \`statusIconAriaLabel\` (string) - Specifies an \`aria-label\` for to the status icon for improved accessibility. +If not provided, \`i18nStrings.{type}IconAriaLabel\` will be used as a fallback. * \`ariaRole\` (string) - For flash messages added after page load, specifies how this message is communicated to assistive technology. Use \\"status\\" for status updates or informational content. Use \\"alert\\" for important messages that need the user's attention. diff --git a/src/__tests__/form-field-controls-integration.test.tsx b/src/__tests__/form-field-controls-integration.test.tsx index 213cae8a81..94c80a551d 100644 --- a/src/__tests__/form-field-controls-integration.test.tsx +++ b/src/__tests__/form-field-controls-integration.test.tsx @@ -82,7 +82,7 @@ formFieldControlComponents.forEach(({ componentName, findNativeElement }) => { } describe(`${componentName}`, () => { - const isGroupComponent = ['radio-group', 'tiles'].indexOf(componentName) !== -1; + const isGroupComponent = ['radio-group', 'tiles'].includes(componentName); if (!isGroupComponent) { describe('controlId', () => { test('applies controlId from FormField when controlId is not set on itself', () => { diff --git a/src/app-layout/index.tsx b/src/app-layout/index.tsx index d1dd6a7e6b..8462b68e9c 100644 --- a/src/app-layout/index.tsx +++ b/src/app-layout/index.tsx @@ -256,6 +256,14 @@ const OldAppLayout = React.forwardRef( } }; + useEffect(() => { + // We forcely close the navigation on mobile on the initial load + if (isMobile) { + onNavigationToggle(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const navigationVisible = !navigationHide && navigationOpen; const toolsVisible = !toolsHide && toolsOpen; diff --git a/src/app-layout/visual-refresh/context.tsx b/src/app-layout/visual-refresh/context.tsx index 27a9c97fdb..132384308d 100644 --- a/src/app-layout/visual-refresh/context.tsx +++ b/src/app-layout/visual-refresh/context.tsx @@ -174,6 +174,14 @@ export const AppLayoutInternalsProvider = React.forwardRef( { componentName: 'AppLayout', controlledProp: 'navigationOpen', changeHandler: 'onNavigationChange' } ); + useEffect(() => { + // We forcely close the navigation on mobile on the initial load + if (isMobile) { + handleNavigationClick(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const { refs: navigationRefs, setFocus: focusNavButtons } = useFocusControl(isNavigationOpen); const handleNavigationClick = useCallback( diff --git a/src/cards/interfaces.tsx b/src/cards/interfaces.tsx index 757767aa71..acc07e8a87 100644 --- a/src/cards/interfaces.tsx +++ b/src/cards/interfaces.tsx @@ -64,7 +64,7 @@ export interface CardsProps extends BaseComponentProps { /** * Defines what to display in each card. It has the following properties: * * `header` ((item) => ReactNode) - Responsible for displaying the card header. You receive the current item as an argument. - * Use `fontSize="heading-m"` on [link](/components/link/) components inside card header. + * Use `fontSize="inherit"` on [link](/components/link/) components inside card header. * * `sections` (array) - Responsible for displaying the card content. Cards can have many sections in their * body. Each entry in the array is responsible for displaying a section. An entry has the following properties: * * `id`: (string) - A unique identifier for the section. The property is used as a [keys](https://reactjs.org/docs/lists-and-keys.html#keys) diff --git a/src/container/styles.scss b/src/container/styles.scss index e964e19c92..cddb942127 100644 --- a/src/container/styles.scss +++ b/src/container/styles.scss @@ -159,7 +159,7 @@ &.with-paddings { padding: shared.$header-padding; &.header-variant-cards { - padding: awsui.$space-scaled-s awsui.$space-container-horizontal; + padding: awsui.$space-container-header-top awsui.$space-container-horizontal; } } diff --git a/src/expandable-section/styles.scss b/src/expandable-section/styles.scss index 66b88828b1..40e46a8dd9 100644 --- a/src/expandable-section/styles.scss +++ b/src/expandable-section/styles.scss @@ -109,6 +109,10 @@ $icon-total-space-medium: calc(#{$icon-width-medium} + #{$icon-margin-left} + #{ padding-bottom: awsui.$space-container-header-bottom; padding-right: container.$header-padding-horizontal; + &:not(.wrapper-expanded) { + // Equal top and bottom padding so standalone header has vertical symmetry. + padding-bottom: awsui.$space-container-header-top; + } &.header-deprecated { padding-left: container.$header-padding-horizontal; } diff --git a/src/flashbar/__tests__/collapsible.test.tsx b/src/flashbar/__tests__/collapsible.test.tsx index 486ac1ecc5..f236c6e7ad 100644 --- a/src/flashbar/__tests__/collapsible.test.tsx +++ b/src/flashbar/__tests__/collapsible.test.tsx @@ -301,6 +301,31 @@ describe('Collapsible Flashbar', () => { expect(innerCounter!.querySelector(`[title="${ariaLabel}"]`)).toBeTruthy(); } }); + + test.each([['success'], ['error'], ['info'], ['warning'], ['in-progress']] as FlashbarProps.Type[][])( + 'item icon has aria-label from i18nStrings when no statusIconAriaLabel provided: type %s', + type => { + const wrapper = renderFlashbar({ + i18nStrings: { + successIconAriaLabel: 'success', + errorIconAriaLabel: 'error', + infoIconAriaLabel: 'info', + warningIconAriaLabel: 'warning', + inProgressIconAriaLabel: 'in-progress', + }, + items: [ + { + header: 'The header', + content: 'The content', + type: type === 'in-progress' ? 'info' : type, + loading: type === 'in-progress', + }, + ], + }); + + expect(wrapper.findItems()[0].find('[role="img"]')?.getElement()).toHaveAccessibleName(type); + } + ); }); describe('Sticky', () => { diff --git a/src/flashbar/__tests__/flashbar.test.tsx b/src/flashbar/__tests__/flashbar.test.tsx index 88fcff7dd6..3f93a94a9e 100644 --- a/src/flashbar/__tests__/flashbar.test.tsx +++ b/src/flashbar/__tests__/flashbar.test.tsx @@ -343,7 +343,7 @@ describe('Flashbar component', () => { }); test('icon has an aria-label when statusIconAriaLabel is provided', () => { - const iconLabel = 'Warning'; + const iconLabel = 'Info'; const wrapper = createFlashbarWrapper( { /> ); - expect(wrapper.findItems()[0].find(`:scope [aria-label]`)?.getElement()).toHaveAttribute( - 'aria-label', - iconLabel - ); + expect(wrapper.findItems()[0].find('[role="img"]')?.getElement()).toHaveAccessibleName(iconLabel); }); + test.each([['success'], ['error'], ['info'], ['warning'], ['in-progress']] as FlashbarProps.Type[][])( + 'icon has aria-label from i18nStrings when no statusIconAriaLabel provided: type %s', + type => { + const wrapper = createFlashbarWrapper( + Click me, + type: type === 'in-progress' ? 'info' : type, + loading: type === 'in-progress', + }, + ]} + /> + ); + + expect(wrapper.findItems()[0].find('[role="img"]')?.getElement()).toHaveAccessibleName(type); + } + ); + describe('Accessibility', () => { test('renders items in an unordered list', () => { const flashbar = createFlashbarWrapper( diff --git a/src/flashbar/collapsible-flashbar.tsx b/src/flashbar/collapsible-flashbar.tsx index 14935e9121..a32edc599d 100644 --- a/src/flashbar/collapsible-flashbar.tsx +++ b/src/flashbar/collapsible-flashbar.tsx @@ -277,6 +277,7 @@ export default function CollapsibleFlashbar({ items, ...restProps }: FlashbarPro key={getItemId(item)} ref={shouldUseStandardAnimation(item, index) ? transitionRootElement : undefined} transitionState={shouldUseStandardAnimation(item, index) ? state : undefined} + i18nStrings={iconAriaLabels} {...item} /> )} diff --git a/src/flashbar/flash.tsx b/src/flashbar/flash.tsx index 086328fa51..9807326718 100644 --- a/src/flashbar/flash.tsx +++ b/src/flashbar/flash.tsx @@ -68,6 +68,7 @@ export const focusFlashById = throttle( export interface FlashProps extends FlashbarProps.MessageDefinition { className: string; transitionState?: string; + i18nStrings?: FlashbarProps.I18nStrings; } export const Flash = React.forwardRef( @@ -87,6 +88,7 @@ export const Flash = React.forwardRef( className, transitionState, ariaRole, + i18nStrings, type = 'info', }: FlashProps, ref: React.Ref @@ -153,7 +155,10 @@ export const Flash = React.forwardRef(
{icon}
diff --git a/src/flashbar/interfaces.ts b/src/flashbar/interfaces.ts index c5cc389b41..ed6837ff0f 100644 --- a/src/flashbar/interfaces.ts +++ b/src/flashbar/interfaces.ts @@ -50,6 +50,7 @@ export interface FlashbarProps extends BaseComponentProps { * When a user clicks on this button the `onDismiss` handler is called. * * `dismissLabel` (string) - Specifies an `aria-label` for to the dismiss icon button for improved accessibility. * * `statusIconAriaLabel` (string) - Specifies an `aria-label` for to the status icon for improved accessibility. + * If not provided, `i18nStrings.{type}IconAriaLabel` will be used as a fallback. * * `ariaRole` (string) - For flash messages added after page load, specifies how this message is communicated to assistive * technology. Use "status" for status updates or informational content. Use "alert" for important messages that need the * user's attention. diff --git a/src/flashbar/non-collapsible-flashbar.tsx b/src/flashbar/non-collapsible-flashbar.tsx index c7cbabc2e6..bd8bbf0c92 100644 --- a/src/flashbar/non-collapsible-flashbar.tsx +++ b/src/flashbar/non-collapsible-flashbar.tsx @@ -22,6 +22,13 @@ export default function NonCollapsibleFlashbar({ items, i18nStrings, ...restProp const i18n = useInternalI18n('flashbar'); const ariaLabel = i18n('i18nStrings.ariaLabel', i18nStrings?.ariaLabel); + const iconAriaLabels = { + errorIconAriaLabel: i18n('i18nStrings.errorIconAriaLabel', i18nStrings?.errorIconAriaLabel), + inProgressIconAriaLabel: i18n('i18nStrings.inProgressIconAriaLabel', i18nStrings?.inProgressIconAriaLabel), + infoIconAriaLabel: i18n('i18nStrings.infoIconAriaLabel', i18nStrings?.infoIconAriaLabel), + successIconAriaLabel: i18n('i18nStrings.successIconAriaLabel', i18nStrings?.successIconAriaLabel), + warningIconAriaLabel: i18n('i18nStrings.warningIconAriaLabel', i18nStrings?.warningIconAriaLabel), + }; /** * All the flash items should have ids so we can identify which DOM element is being @@ -99,6 +106,7 @@ export default function NonCollapsibleFlashbar({ items, i18nStrings, ...restProp key={key} ref={transitionRootElement} transitionState={transitionState} + i18nStrings={iconAriaLabels} {...item} /> ); diff --git a/src/header/internal.tsx b/src/header/internal.tsx index 300db08ec4..21ea90159d 100644 --- a/src/header/internal.tsx +++ b/src/header/internal.tsx @@ -55,14 +55,7 @@ export default function InternalHeader({ ref={__internalRootRef} >
-
+
{ getOptionInGroup(groupNumber: number, optionNumber: number) { @@ -15,15 +13,6 @@ export default class MultiselectPageObject extends SelectPageObject