diff --git a/src/components/NavigationTab/NavigationTab.stories.tsx b/src/components/NavigationTab/NavigationTab.stories.tsx index f8f65ec72..1274019c4 100644 --- a/src/components/NavigationTab/NavigationTab.stories.tsx +++ b/src/components/NavigationTab/NavigationTab.stories.tsx @@ -15,6 +15,9 @@ export default { page: DocumentationPage(Documentation, StyleDocs), }, }, + args: { + icon: 'chat', + }, }; const Example = Template(NavigationTab).bind({}); @@ -27,7 +30,7 @@ Sizes.argTypes = { ...argTypes }; delete Sizes.argTypes.size; Sizes.parameters = { - variants: [{ size: undefined }, { size: 40 }, { size: 48 }, { size: 200 }], + variants: [{ size: undefined }, { size: 40 }, { size: 48 }, { size: 200, label: 'Chat' }], }; const Active = MultiTemplate(NavigationTab).bind({}); @@ -39,4 +42,4 @@ Active.parameters = { variants: [{ active: undefined }, { active: true }, { active: false }], }; -export { Example, Sizes }; +export { Example, Sizes, Active }; diff --git a/src/components/NavigationTab/NavigationTab.tsx b/src/components/NavigationTab/NavigationTab.tsx index 02c92cb0a..c244c2964 100644 --- a/src/components/NavigationTab/NavigationTab.tsx +++ b/src/components/NavigationTab/NavigationTab.tsx @@ -14,12 +14,14 @@ import './NavigationTab.style.scss'; const NavigationTab: FC = (props: Props) => { const { icon, label, count = 0, className, id, size, style, active, ...otherProps } = props; + const isExpanded = size == 200; + const iconComponent = icon ? ( ) : null; const labelComponent = - size == 200 && label ? ( + isExpanded && label ? ( {label} @@ -45,6 +47,7 @@ const NavigationTab: FC = (props: Props) => { data-size={size || DEFAULTS.SIZE} id={id} style={style} + aria-label={isExpanded && label ? props['aria-label'] : props['aria-label'] || label} // if expanded with label, the accessible name will default to the visible label inside. {...otherProps} > {iconComponent} diff --git a/src/components/NavigationTab/NavigationTab.types.ts b/src/components/NavigationTab/NavigationTab.types.ts index 58c727dfc..44f266d0b 100644 --- a/src/components/NavigationTab/NavigationTab.types.ts +++ b/src/components/NavigationTab/NavigationTab.types.ts @@ -1,47 +1,55 @@ import { CSSProperties } from 'react'; import { AriaButtonProps } from '@react-types/button'; +import { AriaLabelingProps } from '@react-types/shared'; import { InferredIconName } from '../Icon/Icon.types'; - -export interface Props extends AriaButtonProps { - /** - * Custom class for overriding this component's CSS. - */ - className?: string; - - /** - * Custom id for overriding this component's CSS. - */ - id?: string; - - /** - * Custom style for overriding this component's CSS. - */ - style?: CSSProperties; - - /** - * Size index of this NavTab. - */ - size?: number; - - /** - * Size index of this NavTab. - */ - label?: string; - - /** - * Size index of this NavTab. - */ - icon?: InferredIconName; - - /** - * The amount inside the badge of components of this NavTab. If 0, then it is not shown. - */ - count?: number; - - /** - * True if the tab is active. - */ - active?: boolean; -} +import { AriaLabelRequired } from '../../utils/a11y'; + +export type Props = AriaButtonProps & + AriaLabelingProps & + ( + | { + /** + * Visible label for this tab + */ + label: string; + } + | ({ label?: never } & AriaLabelRequired) + ) & { + // if a label is not provided, an aria-label(lledby) is required + /** + * Custom class for overriding this component's CSS. + */ + className?: string; + + /** + * Custom id for overriding this component's CSS. + */ + id?: string; + + /** + * Custom style for overriding this component's CSS. + */ + style?: CSSProperties; + + /** + * Size index of this NavTab. + */ + size?: number; + + /** + * Size index of this NavTab. + */ + icon?: InferredIconName; + + /** + * The amount inside the badge of components of this NavTab. If 0, then it is not shown. + */ + count?: number; + + /** + * True if the tab is active. + */ + active?: boolean; + }; export type NavTabSize = 40 | 48 | 200; diff --git a/src/components/NavigationTab/NavigationTab.typetest.ts b/src/components/NavigationTab/NavigationTab.typetest.ts new file mode 100644 index 000000000..86c992383 --- /dev/null +++ b/src/components/NavigationTab/NavigationTab.typetest.ts @@ -0,0 +1,10 @@ +import { Expect, ExpectExtends, ExpectFalse } from '../../utils/typetest.util'; +import { Props } from './NavigationTab.types'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +type cases = [ + Expect>, + Expect>, + Expect>, + ExpectFalse>> +]; diff --git a/src/components/NavigationTab/NavigationTab.unit.test.tsx b/src/components/NavigationTab/NavigationTab.unit.test.tsx index 6c4329feb..269b2db99 100644 --- a/src/components/NavigationTab/NavigationTab.unit.test.tsx +++ b/src/components/NavigationTab/NavigationTab.unit.test.tsx @@ -11,7 +11,7 @@ describe('', () => { it('should match snapshot', async () => { expect.assertions(1); - const container = await mountAndWait(); + const container = await mountAndWait(); expect(container).toMatchSnapshot(); }); @@ -21,7 +21,9 @@ describe('', () => { const className = 'example-class'; - const container = await mountAndWait(); + const container = await mountAndWait( + + ); expect(container).toMatchSnapshot(); }); @@ -31,7 +33,7 @@ describe('', () => { const id = 'example-id'; - const container = await mountAndWait(); + const container = await mountAndWait(); expect(container).toMatchSnapshot(); }); @@ -41,7 +43,7 @@ describe('', () => { const style = { color: 'pink' }; - const container = await mountAndWait(); + const container = await mountAndWait(); expect(container).toMatchSnapshot(); }); @@ -50,7 +52,7 @@ describe('', () => { expect.assertions(1); const sizes = Object.values(SIZES).map((size, index) => { - return ; + return ; }); const container = await mountAndWait(
{sizes}
); @@ -72,7 +74,7 @@ describe('', () => { const icon = 'contacts'; - const container = await mountAndWait(); + const container = await mountAndWait(); expect(container).toMatchSnapshot(); }); @@ -82,7 +84,7 @@ describe('', () => { const count = 100; - const container = await mountAndWait(); + const container = await mountAndWait(); expect(container).toMatchSnapshot(); }); @@ -92,7 +94,7 @@ describe('', () => { const active = true; - const container = await mountAndWait(); + const container = await mountAndWait(); expect(container).toMatchSnapshot(); }); @@ -104,7 +106,9 @@ describe('', () => { const label = 'Contacts'; const icon = 'contacts'; - const container = await mountAndWait(); + const container = await mountAndWait( + + ); expect(container).toMatchSnapshot(); }); @@ -119,7 +123,14 @@ describe('', () => { const active = false; const container = await mountAndWait( - + ); expect(container).toMatchSnapshot(); @@ -130,7 +141,7 @@ describe('', () => { it('should have its wrapper class', async () => { expect.assertions(1); - const wrapper = await mountAndWait(); + const wrapper = await mountAndWait(); const element = wrapper.find(NavigationTab).getDOMNode(); expect(element.classList.contains(STYLE.wrapper)).toBe(true); @@ -141,7 +152,7 @@ describe('', () => { const className = 'example-class'; - const wrapper = await mountAndWait(); + const wrapper = await mountAndWait(); const element = wrapper.find(NavigationTab).getDOMNode(); expect(element.classList.contains(className)).toBe(true); @@ -152,7 +163,7 @@ describe('', () => { const id = 'example-id'; - const wrapper = mount(); + const wrapper = mount(); const element = wrapper.find(NavigationTab).getDOMNode(); expect(element.id).toBe(id); @@ -164,7 +175,7 @@ describe('', () => { const style = { color: 'pink' }; const styleString = 'color: pink;'; - const wrapper = await mountAndWait(); + const wrapper = await mountAndWait(); const element = wrapper.find(NavigationTab).getDOMNode(); expect(element.getAttribute('style')).toBe(styleString); @@ -175,7 +186,7 @@ describe('', () => { const size = CONSTANTS.DEFAULTS.SIZE; - const wrapper = await mountAndWait(); + const wrapper = await mountAndWait(); const element = wrapper.find(NavigationTab).getDOMNode(); expect(element.getAttribute('data-size')).toBe(`${size}`); @@ -199,7 +210,7 @@ describe('', () => { const count = 1; - const wrapper = await mountAndWait(); + const wrapper = await mountAndWait(); const element = wrapper.find(NavigationTab).getDOMNode(); const target = element.getElementsByClassName(STYLE.count)[0]; @@ -212,7 +223,7 @@ describe('', () => { const active = CONSTANTS.DEFAULTS.ACTIVE; - const wrapper = await mountAndWait(); + const wrapper = await mountAndWait(); const element = wrapper.find(NavigationTab).getDOMNode(); expect(element.getAttribute('data-active')).toBe(`${active}`); @@ -225,7 +236,9 @@ describe('', () => { const mockCallback = jest.fn(); - const wrapper = await mountAndWait(); + const wrapper = await mountAndWait( + + ); const component = wrapper.find(NavigationTab); component.props().onPress({ diff --git a/src/components/NavigationTab/NavigationTab.unit.test.tsx.snap b/src/components/NavigationTab/NavigationTab.unit.test.tsx.snap index 465fb4eb4..468687649 100644 --- a/src/components/NavigationTab/NavigationTab.unit.test.tsx.snap +++ b/src/components/NavigationTab/NavigationTab.unit.test.tsx.snap @@ -1,13 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` snapshot should match snapshot 1`] = ` - + snapshot should match snapshot 1`] = ` focusRingClass="md-focus-ring-wrapper children" >