diff --git a/src/components/Footer/Footer/Footer.stories.tsx b/src/components/Footer/Footer/Footer.stories.tsx index 9ae97202b6..64dfd01307 100644 --- a/src/components/Footer/Footer/Footer.stories.tsx +++ b/src/components/Footer/Footer/Footer.stories.tsx @@ -139,7 +139,7 @@ export const MediumFooter = (): React.ReactElement => ( />
-

Agency Contact Center

+

Agency Contact Center

( />
-

Agency Contact Center

+

Agency Contact Center

{ const link = container.querySelector('.usa-social-link') expect(link).toBeInTheDocument() expect(link).toHaveAttribute('title', 'Instagram') - expect(screen.getByRole('img')).toHaveAttribute('name', 'Instagram') }) }) diff --git a/src/components/Footer/SocialLinks/SocialLinks.tsx b/src/components/Footer/SocialLinks/SocialLinks.tsx index 1f70126720..a806da44f9 100644 --- a/src/components/Footer/SocialLinks/SocialLinks.tsx +++ b/src/components/Footer/SocialLinks/SocialLinks.tsx @@ -57,7 +57,11 @@ export const SocialLink = ({ return ( {IconComponent && ( - + ) diff --git a/src/components/Icon/Icon.stories.tsx b/src/components/Icon/Icon.stories.tsx index 98329c0b7b..3092f87705 100644 --- a/src/components/Icon/Icon.stories.tsx +++ b/src/components/Icon/Icon.stories.tsx @@ -23,13 +23,23 @@ Source: https://designsystem.digital.gov/components/icon/ argTypes: { size: { control: { - type: 'select', - options: [3, 4, 5, 6, 7, 8, 9], + type: 'number', + min: 3, + max: 9, + step: 1, + }, + }, + 'aria-label': { + name: 'aria-label', + description: 'Add this if and only aria-hidden is false', + control: { + type: 'text', }, }, }, args: { size: 4, + 'aria-hidden': true, }, } diff --git a/src/components/Icon/Icon.tsx b/src/components/Icon/Icon.tsx index 4752f8fd25..976da96efa 100644 --- a/src/components/Icon/Icon.tsx +++ b/src/components/Icon/Icon.tsx @@ -37,6 +37,16 @@ export const makeUSWDSIcon = ( ...iconProps, } + if ( + 'img' === role && + !iconProps['aria-hidden'] && + !iconProps['aria-label'] && + !iconProps['aria-labelledby'] + ) { + console.warn( + `Icon with img role is missing an accessible label. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Img_role#associated_wai-aria_roles_states_and_properties` + ) + } return } IconFunctionalComponent.displayName = Component.displayName diff --git a/src/components/Icon/Icons.stories.tsx b/src/components/Icon/Icons.stories.tsx index ab6a9e6fd4..9393de32e3 100644 --- a/src/components/Icon/Icons.stories.tsx +++ b/src/components/Icon/Icons.stories.tsx @@ -21,13 +21,16 @@ Source: https://designsystem.digital.gov/components/icon/ argTypes: { size: { control: { - type: 'select', - options: [3, 4, 5, 6, 7, 8, 9], + type: 'number', + min: 3, + max: 9, + step: 1, }, }, }, args: { size: 4, + 'aria-hidden': true, }, } diff --git a/src/components/IconList/IconList.stories.tsx b/src/components/IconList/IconList.stories.tsx index 7f850b127c..bd7c7f69b5 100644 --- a/src/components/IconList/IconList.stories.tsx +++ b/src/components/IconList/IconList.stories.tsx @@ -24,7 +24,7 @@ export const Default = (): ReactElement => ( - + Wash your hands for 20 seconds with soap @@ -32,13 +32,13 @@ export const Default = (): ReactElement => ( - + Stay six feet away from others - + Avoid large gatherings @@ -51,13 +51,13 @@ export const SimpleContent = (): ReactElement => ( - + No processing lines - + Access to expedited entry benefits in other countries @@ -65,13 +65,13 @@ export const SimpleContent = (): ReactElement => ( - + Available at major U.S. airports - + Reduced wait times @@ -83,7 +83,7 @@ export const RichContent = (): ReactElement => ( - + Donate cash when possible. @@ -98,7 +98,7 @@ export const RichContent = (): ReactElement => ( - + @@ -113,7 +113,7 @@ export const RichContent = (): ReactElement => ( - + @@ -140,7 +140,7 @@ export const CustomSizeWithRichContent = (): ReactElement => ( - + Let the sun shine. @@ -154,7 +154,7 @@ export const CustomSizeWithRichContent = (): ReactElement => ( - + Adjust your schedule. @@ -168,7 +168,7 @@ export const CustomSizeWithRichContent = (): ReactElement => ( - + Fill it up. @@ -188,7 +188,7 @@ export const CustomSize = (): ReactElement => ( - + Timing. Is now the right time to @@ -197,7 +197,7 @@ export const CustomSize = (): ReactElement => ( - + Funding. Do I have enough money to @@ -206,7 +206,7 @@ export const CustomSize = (): ReactElement => ( - + Need. Will this business fill a real diff --git a/src/components/LanguageSelector/LanguageSelectorButton.tsx b/src/components/LanguageSelector/LanguageSelectorButton.tsx index 23bda36374..3efceca537 100644 --- a/src/components/LanguageSelector/LanguageSelectorButton.tsx +++ b/src/components/LanguageSelector/LanguageSelectorButton.tsx @@ -6,6 +6,7 @@ type LanguageSelectorButtonProps = { labelAttr?: string isOpen?: boolean onToggle: () => void + controls?: string } export const LanguageSelectorButton = ({ @@ -14,6 +15,7 @@ export const LanguageSelectorButton = ({ isOpen, onToggle, className, + controls, ...buttonProps }: LanguageSelectorButtonProps & JSX.IntrinsicElements['button']): React.ReactElement => { @@ -28,7 +30,7 @@ export const LanguageSelectorButton = ({ data-testid="languageSelectorButton" className={classes} aria-expanded={isOpen} - aria-controls="language-options" + aria-controls={controls} onClick={(): void => onToggle()} type="button" {...buttonProps}> diff --git a/src/components/LanguageSelector/LanguageSelectorDropdown.tsx b/src/components/LanguageSelector/LanguageSelectorDropdown.tsx index 7b22236ca7..1c5589f895 100644 --- a/src/components/LanguageSelector/LanguageSelectorDropdown.tsx +++ b/src/components/LanguageSelector/LanguageSelectorDropdown.tsx @@ -54,6 +54,7 @@ const LanguageSelectorDropdown: React.FC = ({ className ) const displayLabel = langs.find((langDef) => langDef.attr === displayLang) + const menuID = 'language-options' return (
@@ -63,12 +64,13 @@ const LanguageSelectorDropdown: React.FC = ({ className={classes} label={displayLabel?.label || label || langs[0].label} isOpen={isOpen} + controls={menuID} onToggle={() => setIsOpen((prevIsOpen) => !prevIsOpen)} /> diff --git a/src/components/Pagination/Pagination.stories.tsx b/src/components/Pagination/Pagination.stories.tsx index 2c314eb56b..f41cc1ec77 100644 --- a/src/components/Pagination/Pagination.stories.tsx +++ b/src/components/Pagination/Pagination.stories.tsx @@ -1,8 +1,8 @@ import React, { useEffect, useState } from 'react' import { Pagination } from './Pagination' -import type { Meta, StoryFn } from '@storybook/react' +import type { Meta, StoryObj } from '@storybook/react' -export default { +const meta: Meta = { title: 'Components/Pagination', component: Pagination, argTypes: { @@ -11,10 +11,12 @@ export default { pathname: { control: 'string' }, totalPages: { control: 'number' }, }, -} as Meta +} +export default meta +type Story = StoryObj const pathname = '/test-pathname' -const Template: StoryFn = (args) => { +const Template = ({ ...args }) => { const [current, setCurrentPage] = useState(args.currentPage) useEffect(() => { @@ -46,7 +48,7 @@ const Template: StoryFn = (args) => { totalPages={args.totalPages} currentPage={current} maxSlots={args.maxSlots} - pathname={args.pathname} + pathname={pathname} onClickNext={handleNext} onClickPrevious={handlePrevious} onClickPageNumber={handlePageNumber} @@ -54,68 +56,78 @@ const Template: StoryFn = (args) => { ) } -export const Sandbox = { +export const Sandbox: Story = { render: Template, - args: { currentPage: 10, maxSlots: 7, }, } -export const Default = (): React.ReactElement => ( - -) +export const Basic: Story = { + render: Template, + args: { + currentPage: 10, + totalPages: 10, + }, +} -export const Unbounded = (): React.ReactElement => ( - -) +export const Unbounded: Story = { + render: Template, + args: { currentPage: 10 }, +} -export const ThreePagesFirst = (): React.ReactElement => ( - -) +export const ThreePagesFirst: Story = { + render: Template, + args: { currentPage: 1, totalPages: 3 }, +} -export const ThreePages = (): React.ReactElement => ( - -) +export const ThreePages: Story = { + render: Template, + args: { currentPage: 2, totalPages: 3 }, +} -export const ThreePagesLast = (): React.ReactElement => ( - -) +export const ThreePagesLast: Story = { + render: Template, + args: { currentPage: 3, totalPages: 3 }, +} -export const SevenPages = (): React.ReactElement => ( - -) +export const SevenPages: Story = { + render: Template, + args: { currentPage: 4, totalPages: 7 }, +} -export const EightPagesFirst = (): React.ReactElement => ( - -) +export const EightPagesFirst: Story = { + render: Template, + args: { currentPage: 1, totalPages: 8 }, +} -export const EightPagesFour = (): React.ReactElement => ( - -) +export const EightPagesFour: Story = { + render: Template, + args: { currentPage: 4, totalPages: 8 }, +} -export const EightPagesFive = (): React.ReactElement => ( - -) +export const EightPagesFive: Story = { + render: Template, + args: { currentPage: 5, totalPages: 8 }, +} -export const EightPagesSix = (): React.ReactElement => ( - -) +export const EightPagesSix: Story = { + render: Template, + args: { currentPage: 6, totalPages: 8 }, +} -export const EightPagesLast = (): React.ReactElement => ( - -) +export const EightPagesLast: Story = { + render: Template, + args: { currentPage: 8, totalPages: 8 }, +} -export const NinePagesFive = (): React.ReactElement => ( - -) +export const NinePagesFive: Story = { + render: Template, + args: { currentPage: 5, totalPages: 9 }, +} -export const TenSlots = (): React.ReactElement => ( - -) +export const TenSlots: Story = { + render: Template, + args: { currentPage: 10, totalPages: 24, maxSlots: 10 }, +} diff --git a/src/components/Pagination/Pagination.test.tsx b/src/components/Pagination/Pagination.test.tsx index eaee7ddfb8..29104654e6 100644 --- a/src/components/Pagination/Pagination.test.tsx +++ b/src/components/Pagination/Pagination.test.tsx @@ -61,7 +61,7 @@ describe('Pagination component', () => { pathname={testPathname} /> ) - expect(screen.getAllByRole('listitem')).toHaveLength(7) // overflow slots don't count + expect(screen.getAllByRole('listitem')).toHaveLength(9) // overflow slots don't count }) it('renders pagination when the first page is current', () => { @@ -307,7 +307,7 @@ describe('Pagination component', () => { maxSlots={10} /> ) - expect(screen.getAllByRole('listitem')).toHaveLength(10) + expect(screen.getAllByRole('listitem')).toHaveLength(12) }) }) }) diff --git a/src/components/Pagination/Pagination.tsx b/src/components/Pagination/Pagination.tsx index 342b9fd28d..696d01d99c 100644 --- a/src/components/Pagination/Pagination.tsx +++ b/src/components/Pagination/Pagination.tsx @@ -68,7 +68,7 @@ const PaginationPage = ({ const PaginationOverflow = () => (
  • + aria-label="ellipsis indicating non-visible pages">
  • ) @@ -174,7 +174,7 @@ export const Pagination = ({ aria-label="Previous page" data-testid="pagination-previous" onClick={onClickPrevious}> - + Previous ) : ( @@ -182,7 +182,7 @@ export const Pagination = ({ href={`${pathname}?page=${prevPage}`} className="usa-pagination__link usa-pagination__previous-page" aria-label="Previous page"> - + Previous )} @@ -214,7 +214,7 @@ export const Pagination = ({ data-testid="pagination-next" onClick={onClickNext}> Next - + ) : ( Next - + )} diff --git a/src/components/Search/SearchButton/SearchButton.tsx b/src/components/Search/SearchButton/SearchButton.tsx index aae112b1e2..9e2be0c0a1 100644 --- a/src/components/Search/SearchButton/SearchButton.tsx +++ b/src/components/Search/SearchButton/SearchButton.tsx @@ -43,6 +43,7 @@ export const SearchButton = ({ className="usa-search__submit-icon" name={buttonText} size={3} + aria-hidden={true} />
    diff --git a/src/components/Tag/Tag.stories.tsx b/src/components/Tag/Tag.stories.tsx index d886a0a7dd..768e1096cf 100644 --- a/src/components/Tag/Tag.stories.tsx +++ b/src/components/Tag/Tag.stories.tsx @@ -20,9 +20,9 @@ Source: https://designsystem.digital.gov/components/tag/ export const DefaultTag = (): React.ReactElement => My Tag export const CustomBg = (): React.ReactElement => ( - My Tag + My Tag ) export const CustomClass = (): React.ReactElement => ( - My Tag + My Tag ) diff --git a/src/components/card/CardHeader/CardHeader.tsx b/src/components/card/CardHeader/CardHeader.tsx index 61268ba0c0..32f4537617 100644 --- a/src/components/card/CardHeader/CardHeader.tsx +++ b/src/components/card/CardHeader/CardHeader.tsx @@ -8,7 +8,7 @@ export const CardHeader = ({ ...headerProps }: { exdent?: boolean -} & JSX.IntrinsicElements['header']): React.ReactElement => { +} & JSX.IntrinsicElements['div']): React.ReactElement => { const classes = classnames( 'usa-card__header', { @@ -18,9 +18,9 @@ export const CardHeader = ({ ) return ( -
    +
    {children} -
    +
    ) } diff --git a/src/components/forms/DatePicker/DatePicker.stories.tsx b/src/components/forms/DatePicker/DatePicker.stories.tsx index ec774f4854..91e318206b 100644 --- a/src/components/forms/DatePicker/DatePicker.stories.tsx +++ b/src/components/forms/DatePicker/DatePicker.stories.tsx @@ -83,19 +83,27 @@ export const CompleteDatePicker = { ), } -export const DefaultDatePicker = (): React.ReactElement => ( - +const Template = ({ ...args }) => ( + <> + + + ) -export const Disabled = (): React.ReactElement => ( - -) +export const Basic = { + render: Template, +} -export const WithDefaultValue = { - render: (): React.ReactElement => ( - - ), +export const Disabled = { + render: Template, + args: { disabled: true }, +} +export const WithDefaultValue = { + render: Template, + args: { defaultValue: '1988-05-16' }, parameters: { happo: { waitForContent: '05/16/1988', @@ -103,49 +111,32 @@ export const WithDefaultValue = { }, } -const withDefaultInvalidValue = (): React.ReactElement => ( - -) -export { withDefaultInvalidValue } - -export const WithMinMaxInSameMonth = (): React.ReactElement => ( - -) +export const WithDefaultInvalidValue = { + render: Template, + args: { defaultValue: '1988-05-16', minDate: '2020-01-01' }, +} -export const WithMinMax = (): React.ReactElement => ( - -) +export const WithMinMaxInSameMonth = { + render: Template, + args: { minDate: '2021-01-01', maxDate: '2021-01-20' }, +} -const withRangeDate = (): React.ReactElement => ( - -) -withRangeDate.parameters = { - happo: { - waitForContent: '01/20/2021', +export const WithMinMax = { + render: Template, + args: { minDate: '2020-01-01', maxDate: '2021-5-31' }, +} + +export const WithRangeDate = { + render: Template, + args: { defaultValue: '2021-01-20', rangeDate: '2021-01-08' }, + parameters: { + happo: { + waitForContent: '01/20/2021', + }, }, } -export { withRangeDate } -export const WithLocalizations = (): React.ReactElement => ( - -) +export const WithLocalizations = { + render: Template, + args: { i18n: sampleLocalization }, +} diff --git a/src/components/forms/InputPrefix/InputPrefix.stories.tsx b/src/components/forms/InputPrefix/InputPrefix.stories.tsx index e3b64bd3db..da2189fcc2 100644 --- a/src/components/forms/InputPrefix/InputPrefix.stories.tsx +++ b/src/components/forms/InputPrefix/InputPrefix.stories.tsx @@ -4,6 +4,7 @@ import { Icon } from '../../Icon/Icons' import { TextInput } from '../TextInput/TextInput' import { InputGroup } from '../InputGroup/InputGroup' import { FormGroup } from '../FormGroup/FormGroup' +import { Label } from '../Label/Label' export default { title: 'Components/Input prefix or suffix/InputPrefix', @@ -21,31 +22,22 @@ Source: https://designsystem.digital.gov/components/input-prefix-suffix/ }, } -export const InputWithTextInputPrefix = (): React.ReactElement => ( +const Template = ({ ...args }) => ( + - cvc + {args.prefix} ) -export const InputWithTextInputPrefixError = (): React.ReactElement => ( - - - cvc - - - -) +export const AsText = { + render: Template, + args: { prefix: 'cvc' }, +} -export const InputWithIconInputPrefix = (): React.ReactElement => ( - - - - - - - - -) +export const AsIcon = { + render: Template, + args: { prefix: }, +} diff --git a/src/components/forms/InputSuffix/InputSuffix.stories.tsx b/src/components/forms/InputSuffix/InputSuffix.stories.tsx index acb060ab33..e8198f772d 100644 --- a/src/components/forms/InputSuffix/InputSuffix.stories.tsx +++ b/src/components/forms/InputSuffix/InputSuffix.stories.tsx @@ -4,6 +4,7 @@ import { InputGroup } from '../InputGroup/InputGroup' import { FormGroup } from '../FormGroup/FormGroup' import { TextInput } from '../TextInput/TextInput' import { Icon } from '../../Icon/Icons' +import { Label } from '../Label/Label' export default { title: 'Components/Input prefix or suffix/InputSuffix', @@ -21,38 +22,22 @@ Source: https://designsystem.digital.gov/components/input-prefix-suffix/ }, } -export const InputWithIconInputSuffix = (): React.ReactElement => ( +const Template = ({ ...args }) => ( + - - - + {args.suffix} ) -export const InputWithIconInputSuffixError = (): React.ReactElement => ( - - - - - - - - -) +export const AsText = { + render: Template, + args: { suffix: 'lbs.' }, +} -export const InputWithTextInputSuffix = (): React.ReactElement => ( - - - - lbs. - - -) +export const AsIcon = { + render: Template, + args: { suffix: }, +} diff --git a/src/components/forms/RangeInput/RangeInput.stories.tsx b/src/components/forms/RangeInput/RangeInput.stories.tsx index d833e5d236..ac16fd02c5 100644 --- a/src/components/forms/RangeInput/RangeInput.stories.tsx +++ b/src/components/forms/RangeInput/RangeInput.stories.tsx @@ -18,66 +18,61 @@ Source: https://designsystem.digital.gov/components/range-slider }, } -const labelChildren = ( +const Template = ({ ...args }) => ( <> -
    - Start: 0 -
    -
    - End: 100 -
    - -) - -const labelHint = <>(drag to adjust or use arrow keys) - -export const DefaultRange = (): React.ReactElement => ( - <> -