From 78b7b372c4ba619d752b06aa8d5b301a5bcc9660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C4=8Curda?= Date: Wed, 11 Dec 2024 13:34:18 +0100 Subject: [PATCH] fixup! Feat(web-react): Introduce Navigation #DS-1524 --- .../components/Navigation/NavigationLink.tsx | 14 +-- .../__tests__/NavigationLink.test.tsx | 7 +- .../__tests__/useNavigationLinkProps.test.ts | 112 ++++++++++++++++++ .../src/components/Navigation/index.ts | 1 + .../Navigation/useNavigationLinkProps.ts | 27 +++++ 5 files changed, 152 insertions(+), 9 deletions(-) create mode 100644 packages/web-react/src/components/Navigation/__tests__/useNavigationLinkProps.test.ts create mode 100644 packages/web-react/src/components/Navigation/useNavigationLinkProps.ts diff --git a/packages/web-react/src/components/Navigation/NavigationLink.tsx b/packages/web-react/src/components/Navigation/NavigationLink.tsx index 571b969808..c47c532ac5 100644 --- a/packages/web-react/src/components/Navigation/NavigationLink.tsx +++ b/packages/web-react/src/components/Navigation/NavigationLink.tsx @@ -4,6 +4,7 @@ import classNames from 'classnames'; import React, { ElementType, forwardRef } from 'react'; import { useStyleProps } from '../../hooks'; import { PolymorphicRef, SpiritNavigationLinkProps } from '../../types'; +import { useNavigationLinkProps } from './useNavigationLinkProps'; import { useNavigationLinkStyleProps } from './useNavigationLinkStyleProps'; const defaultProps: Partial = { @@ -17,11 +18,10 @@ const _NavigationLink = ( ref: PolymorphicRef, ): JSX.Element => { const propsWithDefaults = { ...defaultProps, ...props }; - const { - elementType: ElementTag = defaultProps.elementType as ElementType, - children, - ...restProps - } = propsWithDefaults; + const { elementType = defaultProps.elementType as ElementType, children, ...restProps } = propsWithDefaults; + const ElementTag = propsWithDefaults.isDisabled ? 'span' : elementType; + + const { navigationLinkProps } = useNavigationLinkProps(propsWithDefaults); const { classProps, props: modifiedProps } = useNavigationLinkStyleProps(restProps); const { styleProps, props: otherProps } = useStyleProps(modifiedProps); @@ -29,7 +29,7 @@ const _NavigationLink = ( @@ -38,6 +38,6 @@ const _NavigationLink = ( ); }; -const NavigationLink = forwardRef>(_NavigationLink); +const NavigationLink = forwardRef>(_NavigationLink); export default NavigationLink; diff --git a/packages/web-react/src/components/Navigation/__tests__/NavigationLink.test.tsx b/packages/web-react/src/components/Navigation/__tests__/NavigationLink.test.tsx index 17a4115ccd..351f956221 100644 --- a/packages/web-react/src/components/Navigation/__tests__/NavigationLink.test.tsx +++ b/packages/web-react/src/components/Navigation/__tests__/NavigationLink.test.tsx @@ -27,16 +27,19 @@ describe('NavigationLink', () => { ); expect(screen.getByRole('link')).toHaveClass('NavigationLink NavigationLink--selected'); + expect(screen.getByRole('link')).toHaveAttribute('aria-current', 'page'); }); - it('should have disabled classname', () => { + it('should have disabled classname and correct elementType', () => { render( Content , ); - expect(screen.getByRole('link')).toHaveClass('NavigationLink NavigationLink--disabled'); + expect(screen.getByText('Content')).toHaveClass('NavigationLink NavigationLink--disabled'); + expect(screen.getByText('Content')).toContainHTML('span'); + expect(screen.queryByRole('link')).not.toBeInTheDocument(); }); it('should render children', () => { diff --git a/packages/web-react/src/components/Navigation/__tests__/useNavigationLinkProps.test.ts b/packages/web-react/src/components/Navigation/__tests__/useNavigationLinkProps.test.ts new file mode 100644 index 0000000000..89626c8823 --- /dev/null +++ b/packages/web-react/src/components/Navigation/__tests__/useNavigationLinkProps.test.ts @@ -0,0 +1,112 @@ +import { renderHook } from '@testing-library/react'; +import { SpiritNavigationLinkProps } from '../../../types'; +import { useNavigationLinkProps } from '../useNavigationLinkProps'; + +describe('useNavigationLinkProps', () => { + it('should return defaults props', () => { + const props: SpiritNavigationLinkProps = { + href: '/', + target: '_blank', + isSelected: false, + isDisabled: false, + ariaLabel: 'label', + }; + const { result } = renderHook(() => useNavigationLinkProps(props)); + + expect(result.current).toStrictEqual({ + navigationLinkProps: { + 'aria-current': undefined, + 'aria-label': 'label', + href: '/', + rel: undefined, + target: '_blank', + }, + }); + }); + + it('should return defaults if element is different from anchor', () => { + const props: SpiritNavigationLinkProps<'span'> = { + elementType: 'span', + href: '/', + target: '_blank', + isSelected: false, + isDisabled: false, + ariaLabel: 'label', + }; + const { result } = renderHook(() => useNavigationLinkProps(props)); + + expect(result.current).toStrictEqual({ + navigationLinkProps: { + 'aria-current': undefined, + 'aria-label': 'label', + href: undefined, + rel: undefined, + target: undefined, + }, + }); + }); + + it('should return for isDisabled', () => { + const props: SpiritNavigationLinkProps = { + href: '/', + target: '_blank', + isSelected: false, + isDisabled: true, + ariaLabel: 'label', + }; + const { result } = renderHook(() => useNavigationLinkProps(props)); + + expect(result.current).toStrictEqual({ + navigationLinkProps: { + 'aria-current': undefined, + 'aria-label': 'label', + href: undefined, + rel: undefined, + target: undefined, + }, + }); + }); + + it('should return for isSelected', () => { + const props: SpiritNavigationLinkProps = { + href: '/', + target: '_blank', + isSelected: true, + isDisabled: false, + ariaLabel: 'label', + }; + const { result } = renderHook(() => useNavigationLinkProps(props)); + + expect(result.current).toStrictEqual({ + navigationLinkProps: { + 'aria-current': 'page', + 'aria-label': 'label', + href: '/', + rel: undefined, + target: '_blank', + }, + }); + }); + + it('should return for elementType', () => { + const props: SpiritNavigationLinkProps<'div'> = { + elementType: 'div', + href: '/', + target: '_blank', + isSelected: false, + isDisabled: false, + ariaLabel: 'label', + }; + const { result } = renderHook(() => useNavigationLinkProps(props)); + + expect(result.current).toStrictEqual({ + navigationLinkProps: { + 'aria-current': undefined, + 'aria-label': 'label', + href: undefined, + rel: undefined, + target: undefined, + }, + }); + }); +}); diff --git a/packages/web-react/src/components/Navigation/index.ts b/packages/web-react/src/components/Navigation/index.ts index 12c7096d84..8cf2651bce 100644 --- a/packages/web-react/src/components/Navigation/index.ts +++ b/packages/web-react/src/components/Navigation/index.ts @@ -4,4 +4,5 @@ export { default as Navigation } from './Navigation'; export { default as NavigationItem } from './NavigationItem'; export { default as NavigationLink } from './NavigationLink'; export * from './useNavigationStyleProps'; +export * from './useNavigationLinkProps'; export * from './useNavigationLinkStyleProps'; diff --git a/packages/web-react/src/components/Navigation/useNavigationLinkProps.ts b/packages/web-react/src/components/Navigation/useNavigationLinkProps.ts new file mode 100644 index 0000000000..4f9045989c --- /dev/null +++ b/packages/web-react/src/components/Navigation/useNavigationLinkProps.ts @@ -0,0 +1,27 @@ +import { ElementType } from 'react'; +import { SpiritNavigationLinkProps } from '../../types'; + +export type UseNavigationLinkProps = Partial>; +export type UseNavigationLinkReturn = { + navigationLinkProps: UseNavigationLinkProps; +}; + +export const useNavigationLinkProps = ( + props: UseNavigationLinkProps, +): UseNavigationLinkReturn => { + const { elementType = 'a', isDisabled, isSelected, href, target, rel, ariaLabel } = props; + + const additionalProps: Partial = { + href: elementType === 'a' && !isDisabled ? href : undefined, + target: elementType === 'a' && !isDisabled ? target : undefined, + rel: elementType === 'a' ? rel : undefined, + }; + + return { + navigationLinkProps: { + ...additionalProps, + 'aria-label': ariaLabel || undefined, + 'aria-current': isSelected ? 'page' : undefined, + }, + }; +};