From d33e3c04711b64b1c6115f4cb47d49d508e170d1 Mon Sep 17 00:00:00 2001 From: Kerrie Wu Date: Thu, 29 Aug 2024 13:55:46 +0800 Subject: [PATCH 1/6] add Navigation-tab-group --- .../examples.module.scss | 31 +++ .../examples.tsx | 215 ++++++++++++++++++ .../stories.tsx | 61 +++++ .../README.md | 52 +++++ .../index.ts | 24 ++ .../src/BpkNavigationTabGroup-test.tsx | 106 +++++++++ .../src/BpkNavigationTabGroup.module.scss | 107 +++++++++ .../src/BpkNavigationTabGroup.tsx | 144 ++++++++++++ .../BpkNavigationTabGroup-test.tsx.snap | 43 ++++ .../src/accessibility-test.tsx | 62 +++++ 10 files changed, 845 insertions(+) create mode 100644 examples/bpk-component-navigation-tab-group/examples.module.scss create mode 100644 examples/bpk-component-navigation-tab-group/examples.tsx create mode 100644 examples/bpk-component-navigation-tab-group/stories.tsx create mode 100644 packages/bpk-component-navigation-tab-group/README.md create mode 100644 packages/bpk-component-navigation-tab-group/index.ts create mode 100644 packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup-test.tsx create mode 100644 packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.module.scss create mode 100644 packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.tsx create mode 100644 packages/bpk-component-navigation-tab-group/src/__snapshots__/BpkNavigationTabGroup-test.tsx.snap create mode 100644 packages/bpk-component-navigation-tab-group/src/accessibility-test.tsx diff --git a/examples/bpk-component-navigation-tab-group/examples.module.scss b/examples/bpk-component-navigation-tab-group/examples.module.scss new file mode 100644 index 0000000000..ba4cf4ffbc --- /dev/null +++ b/examples/bpk-component-navigation-tab-group/examples.module.scss @@ -0,0 +1,31 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@use '../../packages/unstable__bpk-mixins/tokens'; + +.bpk-navigation-tab-group-story { + padding: tokens.bpk-spacing-base(); + background-color: tokens.$bpk-core-primary-day; + + &__mixed-container { + h3 { + margin-top: tokens.bpk-spacing-xl(); + margin-bottom: tokens.bpk-spacing-md(); + } + } +} diff --git a/examples/bpk-component-navigation-tab-group/examples.tsx b/examples/bpk-component-navigation-tab-group/examples.tsx new file mode 100644 index 0000000000..9da263e497 --- /dev/null +++ b/examples/bpk-component-navigation-tab-group/examples.tsx @@ -0,0 +1,215 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { withRtlSupport } from '../../packages/bpk-component-icon'; +import Car from '../../packages/bpk-component-icon/sm/cars'; +import Explore from '../../packages/bpk-component-icon/sm/explore'; +import Flight from '../../packages/bpk-component-icon/sm/flight'; +import Hotel from '../../packages/bpk-component-icon/sm/hotels'; +import BpkNavigationTabGroup from '../../packages/bpk-component-navigation-tab-group'; +import { NAVIGATION_TAB_GROUP_TYPES } from '../../packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup'; +import BpkText, { TEXT_STYLES } from '../../packages/bpk-component-text'; +import { cssModules } from '../../packages/bpk-react-utils'; + +import type { BpkNavigationTabGroupProps } from '../../packages/bpk-component-navigation-tab-group'; + +import STYLES from './examples.module.scss'; + +const getClassNames = cssModules(STYLES); + +const exploreIcons = withRtlSupport(Explore); + +const hotelIcons = withRtlSupport(Hotel); + +const carIcons = withRtlSupport(Car); + +const flightIcons = withRtlSupport(Flight); + +const tabs: BpkNavigationTabGroupProps['tabs'] = [ + { text: 'Flights', href: '/' }, + { text: 'Hotels', href: '/hotel' }, + { text: 'Car hire', href: '/carhire' }, + { text: 'Explore', href: '/Explore' }, +]; + +const tabsWithIcon: BpkNavigationTabGroupProps['tabs'] = [ + { text: 'Flights', href: '/', icon: flightIcons }, + { text: 'Hotels', href: '/hotel', icon: hotelIcons }, + { text: 'Car hire', href: '/carhire', icon: carIcons }, + { text: 'Explore', href: '/Explore', icon: exploreIcons }, +]; + +const tabsNoHref: BpkNavigationTabGroupProps['tabs'] = [ + { text: 'Flights', icon: flightIcons }, + { text: 'Hotels', icon: hotelIcons }, + { text: 'Car hire', icon: carIcons }, + { text: 'Explore', icon: exploreIcons }, +]; + +const tabsOnlyText: BpkNavigationTabGroupProps['tabs'] = [ + { text: 'Flights'}, + { text: 'Hotels' }, + { text: 'Car hire'}, + { text: 'Explore'}, +]; + +// Simple Navigation Tab Group +const SimpleSurfaceContrast = () => ( +
+ {}} + selectedIndex={2} + type={NAVIGATION_TAB_GROUP_TYPES.SurfaceContrast} + /> +
+); + +// Simple Navigation Tab Group Canvas Default +const SimpleCanvasDefault = () => ( +
+ {}} + selectedIndex={0} + type={NAVIGATION_TAB_GROUP_TYPES.CanvasDefault} + /> +
+); +// With Icon Navigation Tab Group +const WithIconSurfaceContrastForExample = () => ( +
+ {}} + selectedIndex={0} + type={NAVIGATION_TAB_GROUP_TYPES.SurfaceContrast} + /> +
+); + +// With Icon Navigation Tab Group +const WithIconCanvasDefaultForExample = () => ( +
+ {}} + selectedIndex={0} + type={NAVIGATION_TAB_GROUP_TYPES.CanvasDefault} + /> +
+); + +// Tabs No Href SurfaceContrast Navigation Tab Group +const TabsNoHrefSurfaceContrastForExample = () => ( +
+ {}} + selectedIndex={0} + type={NAVIGATION_TAB_GROUP_TYPES.SurfaceContrast} + /> +
+); + +// Tabs No Href CanvasDefault Navigation Tab Group +const TabsNoHrefCanvasDefaultForExample = () => ( +
+ {}} + selectedIndex={0} + type={NAVIGATION_TAB_GROUP_TYPES.CanvasDefault} + /> +
+); + +// Tabs Only Text SurfaceContrast Navigation Tab Group +const TabsOnlyTextSurfaceContrastForExample = () => ( +
+ {}} + selectedIndex={0} + type={NAVIGATION_TAB_GROUP_TYPES.SurfaceContrast} + /> +
+); + +// Tabs Only Text CanvasDefault Navigation Tab Group +const TabsOnlyTextCanvasDefaultForExample = () => ( +
+ {}} + selectedIndex={0} + type={NAVIGATION_TAB_GROUP_TYPES.CanvasDefault} + /> +
+); + +const VisualTestExample = () => ( +
+ + Simple SurfaceContrast + + + + Only Text SurfaceContrast + + + + With Icon SurfaceContrast + + + + No Href SurfaceContrast + + + + Simple CanvasDefault + + + + Only Text CanvasDefault + + + + With Icon CanvasDefault + + + + No Href CanvasDefault + + +
+
+); + +export { + SimpleSurfaceContrast, + SimpleCanvasDefault, + WithIconSurfaceContrastForExample, + WithIconCanvasDefaultForExample, + TabsNoHrefSurfaceContrastForExample, + TabsNoHrefCanvasDefaultForExample, + TabsOnlyTextSurfaceContrastForExample, + TabsOnlyTextCanvasDefaultForExample, + VisualTestExample, +}; diff --git a/examples/bpk-component-navigation-tab-group/stories.tsx b/examples/bpk-component-navigation-tab-group/stories.tsx new file mode 100644 index 0000000000..73fbe7bebf --- /dev/null +++ b/examples/bpk-component-navigation-tab-group/stories.tsx @@ -0,0 +1,61 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import BpkNavigationTabGroup from '../../packages/bpk-component-navigation-tab-group'; + +import { + SimpleSurfaceContrast, + SimpleCanvasDefault, + WithIconSurfaceContrastForExample, + WithIconCanvasDefaultForExample, + TabsNoHrefSurfaceContrastForExample, + TabsNoHrefCanvasDefaultForExample, + TabsOnlyTextSurfaceContrastForExample, + TabsOnlyTextCanvasDefaultForExample, + VisualTestExample, +} from './examples'; + +export default { + title: 'bpk-component-navigation-tab-group', + component: BpkNavigationTabGroup, +}; + +export const SurfaceContrast = SimpleSurfaceContrast; + +export const TabsNoHrefSurfaceContrast = TabsNoHrefSurfaceContrastForExample; + +export const TabsNoHrefCanvasDefault =TabsNoHrefCanvasDefaultForExample + +export const CanvasDefault = SimpleCanvasDefault; + +export const WithIconSurfaceContrast = WithIconSurfaceContrastForExample; + +export const WithIconCanvasDefault = WithIconCanvasDefaultForExample; + +export const OnlyTextSurfaceContrast = TabsOnlyTextSurfaceContrastForExample; + +export const OnlyTextCanvasDefault = TabsOnlyTextCanvasDefaultForExample; + +export const VisualTest = VisualTestExample; + +export const VisualTestWithZoom = { + render: VisualTest, + args: { + zoomEnabled: true + } +} diff --git a/packages/bpk-component-navigation-tab-group/README.md b/packages/bpk-component-navigation-tab-group/README.md new file mode 100644 index 0000000000..e400a4abab --- /dev/null +++ b/packages/bpk-component-navigation-tab-group/README.md @@ -0,0 +1,52 @@ +# bpk-component-navigation-tab-group + +> Backpack navigation tab group component. + +## Installation + +Check the main [Readme](https://github.com/skyscanner/backpack#usage) for a complete installation guide. + +## Usage + +### BpkNavigationTabGroup + +This is a NavigationTab Group that only allows a single tab to be `selected`, determined by the `selectedIndex` prop. State of selected tab should be managed using the `onItemClick` prop. + +```tsx +import { withRtlSupport } from '../../packages/bpk-component-icon'; +import Car from '../../packages/bpk-component-icon/sm/cars'; +import Explore from '../../packages/bpk-component-icon/sm/explore'; +import Flight from '../../packages/bpk-component-icon/sm/flight'; +import Hotel from '../../packages/bpk-component-icon/sm/hotels'; +import BpkNavigationTabGroup from '../../packages/bpk-component-navigation-tab-group'; +import { NAVIGATION_TAB_GROUP_TYPES } from '../../packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup'; +import type { BpkNavigationTabGroupProps } from '../../packages/bpk-component-navigation-tab-group'; + +const exploreIcons = withRtlSupport(Explore); + +const hotelIcons = withRtlSupport(Hotel); + +const carIcons = withRtlSupport(Car); + +const flightIcons = withRtlSupport(Flight); + +const tabs: BpkNavigationTabGroupProps['tabs'] = [ + { text: 'Flights', href: '/', icon: flightIcons }, + { text: 'Hotels', href: '/hotel', icon: hotelIcons }, + { text: 'Car hire', href: '/carhire', icon: carIcons }, + { text: 'Explore', href: '/Explore', icon: exploreIcons }, +]; + +export default () => ( + {}} + selectedIndex={0} + type={NAVIGATION_TAB_GROUP_TYPES.SurfaceContrast} + /> +); +``` + +## Props + +Check out the full list of props on Skyscanner's [design system documentation website](https://www.skyscanner.design/latest/components/navigation-tab-group/web-4eQsMvYv). diff --git a/packages/bpk-component-navigation-tab-group/index.ts b/packages/bpk-component-navigation-tab-group/index.ts new file mode 100644 index 0000000000..698d3c1501 --- /dev/null +++ b/packages/bpk-component-navigation-tab-group/index.ts @@ -0,0 +1,24 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import BpkNavigationTabGroup, { + type Props as BpkNavigationTabGroupProps, +} from './src/BpkNavigationTabGroup'; + +export type { BpkNavigationTabGroupProps }; +export default BpkNavigationTabGroup; diff --git a/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup-test.tsx b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup-test.tsx new file mode 100644 index 0000000000..9f8ccc479c --- /dev/null +++ b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup-test.tsx @@ -0,0 +1,106 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import BpkNavigationTabGroup from './BpkNavigationTabGroup'; + +import type { Props } from './BpkNavigationTabGroup'; + +const tabs: Props['tabs'] = [ + { text: 'Flights', href: '/' }, + { text: 'Hotels', href: '/hotel' }, + { text: 'Car hire', href: '/carhire' }, +]; + +describe('BpkNavigationTabGroup', () => { + beforeEach(() => { + window.matchMedia = jest.fn().mockImplementation(() => ({ + matches: true, + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + })); + }); + + it('should render correctly', () => { + const { asFragment } = render( + {}} + selectedIndex={0} + />, + ); + expect(asFragment()).toMatchSnapshot(); + }); + + it('should render selected link', () => { + render( + {}} + selectedIndex={0} + />, + ); + + const selectedTextElement = screen.getByText('Flights'); + const selectedLink = selectedTextElement.closest('a'); + + expect(selectedLink).toHaveClass( + 'bpk-navigation-tab-wrap--surface-contrast-selected', + ); + }); + + it('should call onItemClick when a tab is clicked', async () => { + const user = userEvent.setup(); + + const onItemClick = jest.fn(); + + render( + , + ); + + await user.click(screen.getByText('Hotels')); + + expect(onItemClick).toHaveBeenCalledTimes(1); + expect(onItemClick).toHaveBeenCalledWith( + { text: 'Hotels', href: '/hotel' }, + 1, + ); + }); + + it('should render correctly when type is CanvasDefault', () => { + render( + {}} + selectedIndex={0} + type="CanvasDefault" + />, + ); + + const flightTextElement = screen.getByText('Flights'); + const flightLink = flightTextElement.closest('a'); + + expect(flightLink).toHaveClass('bpk-navigation-tab-wrap--CanvasDefault'); + }); +}); diff --git a/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.module.scss b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.module.scss new file mode 100644 index 0000000000..973fd22b96 --- /dev/null +++ b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.module.scss @@ -0,0 +1,107 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@use '../../unstable__bpk-mixins/tokens'; +@use '../../unstable__bpk-mixins/borders'; + +@mixin tab-link { + display: flex; + align-items: center; + text-decoration: none; +} + +.bpk-navigation-tab-group { + display: flex; + + .bpk-navigation-tab-wrap { + height: tokens.$bpk-one-pixel-rem * 36; + padding: 0 tokens.bpk-spacing-base(); + border: none; + border-radius: tokens.$bpk-one-pixel-rem * 100; + margin-inline-end: tokens.bpk-spacing-sm(); + + @include tab-link; + + &--surface-contrast { + background-color: tokens.$bpk-surface-contrast-day; + color: tokens.$bpk-text-primary-inverse-day; + + @include borders.bpk-border-sm(tokens.$bpk-text-disabled-on-dark-day); + + &:hover { + background-color: tokens.$bpk-core-primary-night; + + @include borders.bpk-border-sm(tokens.$bpk-core-primary-night); + } + + &-selected { + background-color: tokens.$bpk-core-accent-day; + + @include borders.bpk-border-sm(tokens.$bpk-core-accent-day); + + &:hover { + background-color: tokens.$bpk-core-accent-day; + + @include borders.bpk-border-sm(tokens.$bpk-core-accent-day); + } + } + } + + &--canvas-default { + background-color: tokens.$bpk-canvas-day; + color: tokens.$bpk-text-primary-day; + + @include borders.bpk-border-sm(tokens.$bpk-text-disabled-day); + + &:hover { + color: tokens.$bpk-text-primary-day; + + @include borders.bpk-border-sm(tokens.$bpk-text-primary-day); + } + + &-selected { + background-color: tokens.$bpk-core-accent-day; + color: tokens.$bpk-text-primary-inverse-day; + + @include borders.bpk-border-sm(tokens.$bpk-core-accent-day); + + &:hover { + background-color: tokens.$bpk-core-accent-day; + color: tokens.$bpk-text-primary-inverse-day; + + @include borders.bpk-border-sm(tokens.$bpk-core-accent-day); + } + } + } + } + + .bpk-navigation-tab-icon { + height: tokens.bpk-spacing-base(); + margin-inline-end: tokens.bpk-spacing-md(); + + &--surface-contrast { + fill: tokens.$bpk-text-primary-inverse-day; + } + + &--canvas-default { + &-selected { + fill: tokens.$bpk-text-primary-inverse-day; + } + } + } +} diff --git a/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.tsx b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.tsx new file mode 100644 index 0000000000..ccdebb1f06 --- /dev/null +++ b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.tsx @@ -0,0 +1,144 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { FunctionComponent, ReactElement } from 'react'; +import { useState } from 'react'; + +import BpkText, { TEXT_STYLES } from '../../bpk-component-text'; +import { cssModules } from '../../bpk-react-utils'; + +import STYLES from './BpkNavigationTabGroup.module.scss'; + +const getClassName = cssModules(STYLES); + +export const NAVIGATION_TAB_GROUP_TYPES = { + CanvasDefault: 'canvas-default', + SurfaceContrast: 'surface-contrast', +}; +export type NavigationTabGroupTypes = + (typeof NAVIGATION_TAB_GROUP_TYPES)[keyof typeof NAVIGATION_TAB_GROUP_TYPES]; + +type TabItem = { + text: string; + icon?: FunctionComponent | null; + href?: string; +}; +export type Props = { + tabs: TabItem[]; + type?: NavigationTabGroupTypes; + /* + * Index parameter to track which is clicked + */ + onItemClick: (tab: TabItem, index: number) => void; + selectedIndex: number; +}; + +type TabWrapProps = { + tab: TabItem; + type: NavigationTabGroupTypes; + selected: boolean; + children: ReactElement; + onClick: () => void; +}; + +const TabWrap = ({ children, onClick, selected, tab, type }: TabWrapProps) => { + const tabStyling = getClassName( + 'bpk-navigation-tab-wrap', + `bpk-navigation-tab-wrap--${type}`, + selected && `bpk-navigation-tab-wrap--${type}-selected`, + ); + + return tab.href ? ( + + {children} + + ) : ( + + ); +}; + +const BpkNavigationTabGroup = ({ + onItemClick, + selectedIndex, + tabs, + type = NAVIGATION_TAB_GROUP_TYPES.SurfaceContrast, +}: Props) => { + const [selectedTab, setSelectedTab] = useState(selectedIndex); + const handleButtonClick = (tab: TabItem, index: number) => { + if (index !== selectedTab) { + setSelectedTab(index); + } + onItemClick(tab, index); + }; + + const containerStyling = getClassName('bpk-navigation-tab-group'); + + return ( + + ); +}; + +export default BpkNavigationTabGroup; diff --git a/packages/bpk-component-navigation-tab-group/src/__snapshots__/BpkNavigationTabGroup-test.tsx.snap b/packages/bpk-component-navigation-tab-group/src/__snapshots__/BpkNavigationTabGroup-test.tsx.snap new file mode 100644 index 0000000000..d424bf4b69 --- /dev/null +++ b/packages/bpk-component-navigation-tab-group/src/__snapshots__/BpkNavigationTabGroup-test.tsx.snap @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BpkNavigationTabGroup should render correctly 1`] = ` + + + +`; diff --git a/packages/bpk-component-navigation-tab-group/src/accessibility-test.tsx b/packages/bpk-component-navigation-tab-group/src/accessibility-test.tsx new file mode 100644 index 0000000000..94c1cbe042 --- /dev/null +++ b/packages/bpk-component-navigation-tab-group/src/accessibility-test.tsx @@ -0,0 +1,62 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; + +import BpkNavigationTabGroup from './BpkNavigationTabGroup'; + +import type { Props } from './BpkNavigationTabGroup'; + +const tabs: Props['tabs'] = [ + { text: 'Flights', href: '/' }, + { text: 'Hotels', href: '/hotel' }, + { text: 'Car hire', href: '/carhire' }, +]; + +const tabsNoHref: Props['tabs'] = [ + { text: 'Flights'}, + { text: 'Hotels'}, + { text: 'Car hire'}, +]; + +describe('BpkNavigationTabGroup accessibility tests', () => { + it('should not have programmatically-detectable accessibility issues', async () => { + const { container } = render( + {}} + selectedIndex={0} + />, + ); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('should not have programmatically-detectable accessibility issues without href', async () => { + const { container } = render( + {}} + selectedIndex={0} + />, + ); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); +}); From 78c4ed9ca2414fcae1c5b03251ce42c0453a7e69 Mon Sep 17 00:00:00 2001 From: Kerrie Wu Date: Thu, 29 Aug 2024 16:13:14 +0800 Subject: [PATCH 2/6] modify READ.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5546bd39ae..c63c5c52af 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ To contribute please see [contributing.md](CONTRIBUTING.md). [`bpk-component-mobile-scroll-container`](/packages/bpk-component-mobile-scroll-container) [`bpk-component-modal`](/packages/bpk-component-modal) [`bpk-component-navigation-bar`](/packages/bpk-component-navigation-bar) +[`bpk-component-navigation-tab-group`](packages/bpk-component-navigation-tab-group) [`bpk-component-nudger`](/packages/bpk-component-nudger) [`bpk-component-page-indicator`](/packages/bpk-component-page-indicator) [`bpk-component-pagination`](/packages/bpk-component-pagination) From 586eb133545f2614ae5476c20cad35bc6f00f0bd Mon Sep 17 00:00:00 2001 From: Kerrie Wu Date: Tue, 3 Sep 2024 13:52:56 +0800 Subject: [PATCH 3/6] update css style --- .../src/BpkNavigationTabGroup-test.tsx | 2 +- .../src/BpkNavigationTabGroup.module.scss | 18 +++++++++--------- .../src/BpkNavigationTabGroup.tsx | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup-test.tsx b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup-test.tsx index 9f8ccc479c..85a885155f 100644 --- a/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup-test.tsx +++ b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup-test.tsx @@ -101,6 +101,6 @@ describe('BpkNavigationTabGroup', () => { const flightTextElement = screen.getByText('Flights'); const flightLink = flightTextElement.closest('a'); - expect(flightLink).toHaveClass('bpk-navigation-tab-wrap--CanvasDefault'); + expect(flightLink).toHaveClass('bpk-navigation-tab-wrap--canvas-default'); }); }); diff --git a/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.module.scss b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.module.scss index 973fd22b96..545aa63d8a 100644 --- a/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.module.scss +++ b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.module.scss @@ -38,10 +38,10 @@ @include tab-link; &--surface-contrast { - background-color: tokens.$bpk-surface-contrast-day; - color: tokens.$bpk-text-primary-inverse-day; + background-color: transparent; + color: tokens.$bpk-text-on-dark-day; - @include borders.bpk-border-sm(tokens.$bpk-text-disabled-on-dark-day); + @include borders.bpk-border-sm(tokens.$bpk-line-day); &:hover { background-color: tokens.$bpk-core-primary-night; @@ -63,10 +63,10 @@ } &--canvas-default { - background-color: tokens.$bpk-canvas-day; + background-color: transparent; color: tokens.$bpk-text-primary-day; - @include borders.bpk-border-sm(tokens.$bpk-text-disabled-day); + @include borders.bpk-border-sm(tokens.$bpk-line-day); &:hover { color: tokens.$bpk-text-primary-day; @@ -76,13 +76,13 @@ &-selected { background-color: tokens.$bpk-core-accent-day; - color: tokens.$bpk-text-primary-inverse-day; + color: tokens.$bpk-text-on-dark-day; @include borders.bpk-border-sm(tokens.$bpk-core-accent-day); &:hover { background-color: tokens.$bpk-core-accent-day; - color: tokens.$bpk-text-primary-inverse-day; + color: tokens.$bpk-text-on-dark-day; @include borders.bpk-border-sm(tokens.$bpk-core-accent-day); } @@ -95,12 +95,12 @@ margin-inline-end: tokens.bpk-spacing-md(); &--surface-contrast { - fill: tokens.$bpk-text-primary-inverse-day; + fill: tokens.$bpk-text-on-dark-day; } &--canvas-default { &-selected { - fill: tokens.$bpk-text-primary-inverse-day; + fill: tokens.$bpk-text-on-dark-day; } } } diff --git a/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.tsx b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.tsx index ccdebb1f06..95c5ca747c 100644 --- a/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.tsx +++ b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.tsx @@ -68,7 +68,7 @@ const TabWrap = ({ children, onClick, selected, tab, type }: TabWrapProps) => { className={tabStyling} href={tab.href} onClick={onClick} - aria-current={selected ? 'page' : undefined} + aria-current={selected ? 'page' : false} > {children} @@ -77,8 +77,8 @@ const TabWrap = ({ children, onClick, selected, tab, type }: TabWrapProps) => { className={tabStyling} type="button" onClick={onClick} - aria-current={selected ? 'page' : undefined} - role='link' + aria-current={selected ? 'page' : false} + role="link" > {children} @@ -119,7 +119,7 @@ const BpkNavigationTabGroup = ({ type={type} > <> - {Icon ? ( + {Icon && ( - ) : null} + )} {tab.text} From 911824c84a19d66760a34331e8e044eb2efc9b05 Mon Sep 17 00:00:00 2001 From: Kerrie Wu Date: Tue, 3 Sep 2024 15:27:06 +0800 Subject: [PATCH 4/6] add arialabel props and modify test case --- .../examples.tsx | 8 ++++ .../README.md | 1 + .../src/BpkNavigationTabGroup-test.tsx | 16 +++++-- .../src/BpkNavigationTabGroup.tsx | 4 +- .../BpkNavigationTabGroup-test.tsx.snap | 43 ------------------- .../src/accessibility-test.tsx | 2 + 6 files changed, 26 insertions(+), 48 deletions(-) delete mode 100644 packages/bpk-component-navigation-tab-group/src/__snapshots__/BpkNavigationTabGroup-test.tsx.snap diff --git a/examples/bpk-component-navigation-tab-group/examples.tsx b/examples/bpk-component-navigation-tab-group/examples.tsx index 9da263e497..eb4ddf7355 100644 --- a/examples/bpk-component-navigation-tab-group/examples.tsx +++ b/examples/bpk-component-navigation-tab-group/examples.tsx @@ -75,6 +75,7 @@ const SimpleSurfaceContrast = () => ( onItemClick={() => {}} selectedIndex={2} type={NAVIGATION_TAB_GROUP_TYPES.SurfaceContrast} + ariaLabel="Navigation tabs" /> ); @@ -89,6 +90,7 @@ const SimpleCanvasDefault = () => ( onItemClick={() => {}} selectedIndex={0} type={NAVIGATION_TAB_GROUP_TYPES.CanvasDefault} + ariaLabel="Navigation tabs" /> ); @@ -100,6 +102,7 @@ const WithIconSurfaceContrastForExample = () => ( onItemClick={() => {}} selectedIndex={0} type={NAVIGATION_TAB_GROUP_TYPES.SurfaceContrast} + ariaLabel="Navigation tabs" /> ); @@ -112,6 +115,7 @@ const WithIconCanvasDefaultForExample = () => ( onItemClick={() => {}} selectedIndex={0} type={NAVIGATION_TAB_GROUP_TYPES.CanvasDefault} + ariaLabel="Navigation tabs" /> ); @@ -124,6 +128,7 @@ const TabsNoHrefSurfaceContrastForExample = () => ( onItemClick={() => {}} selectedIndex={0} type={NAVIGATION_TAB_GROUP_TYPES.SurfaceContrast} + ariaLabel="Navigation tabs" /> ); @@ -136,6 +141,7 @@ const TabsNoHrefCanvasDefaultForExample = () => ( onItemClick={() => {}} selectedIndex={0} type={NAVIGATION_TAB_GROUP_TYPES.CanvasDefault} + ariaLabel="Navigation tabs" /> ); @@ -148,6 +154,7 @@ const TabsOnlyTextSurfaceContrastForExample = () => ( onItemClick={() => {}} selectedIndex={0} type={NAVIGATION_TAB_GROUP_TYPES.SurfaceContrast} + ariaLabel="Navigation tabs" /> ); @@ -160,6 +167,7 @@ const TabsOnlyTextCanvasDefaultForExample = () => ( onItemClick={() => {}} selectedIndex={0} type={NAVIGATION_TAB_GROUP_TYPES.CanvasDefault} + ariaLabel="Navigation tabs" /> ); diff --git a/packages/bpk-component-navigation-tab-group/README.md b/packages/bpk-component-navigation-tab-group/README.md index e400a4abab..a95b9173dd 100644 --- a/packages/bpk-component-navigation-tab-group/README.md +++ b/packages/bpk-component-navigation-tab-group/README.md @@ -43,6 +43,7 @@ export default () => ( onItemClick={() => {}} selectedIndex={0} type={NAVIGATION_TAB_GROUP_TYPES.SurfaceContrast} + ariaLabel="Navigation tabs" /> ); ``` diff --git a/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup-test.tsx b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup-test.tsx index 85a885155f..85536adb96 100644 --- a/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup-test.tsx +++ b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup-test.tsx @@ -19,7 +19,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import BpkNavigationTabGroup from './BpkNavigationTabGroup'; +import BpkNavigationTabGroup, { NAVIGATION_TAB_GROUP_TYPES } from './BpkNavigationTabGroup'; import type { Props } from './BpkNavigationTabGroup'; @@ -39,14 +39,19 @@ describe('BpkNavigationTabGroup', () => { }); it('should render correctly', () => { - const { asFragment } = render( + render( {}} selectedIndex={0} + ariaLabel="Navigation tabs" />, ); - expect(asFragment()).toMatchSnapshot(); + expect(screen.getByRole('navigation')).toBeInTheDocument(); + expect(screen.getByRole('navigation')).toHaveAttribute('aria-label', 'Navigation tabs'); + expect(screen.getByText('Flights')).toBeInTheDocument(); + expect(screen.getByText('Hotels')).toBeInTheDocument(); + expect(screen.getByText('Car hire')).toBeInTheDocument(); }); it('should render selected link', () => { @@ -55,6 +60,7 @@ describe('BpkNavigationTabGroup', () => { tabs={tabs} onItemClick={() => {}} selectedIndex={0} + ariaLabel="Navigation tabs" />, ); @@ -76,6 +82,7 @@ describe('BpkNavigationTabGroup', () => { tabs={tabs} onItemClick={onItemClick} selectedIndex={0} + ariaLabel="Navigation tabs" />, ); @@ -94,7 +101,8 @@ describe('BpkNavigationTabGroup', () => { tabs={tabs} onItemClick={() => {}} selectedIndex={0} - type="CanvasDefault" + type={NAVIGATION_TAB_GROUP_TYPES.CanvasDefault} + ariaLabel="Navigation tabs" />, ); diff --git a/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.tsx b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.tsx index 95c5ca747c..e3973e645c 100644 --- a/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.tsx +++ b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.tsx @@ -46,6 +46,7 @@ export type Props = { */ onItemClick: (tab: TabItem, index: number) => void; selectedIndex: number; + ariaLabel: string; }; type TabWrapProps = { @@ -86,6 +87,7 @@ const TabWrap = ({ children, onClick, selected, tab, type }: TabWrapProps) => { }; const BpkNavigationTabGroup = ({ + ariaLabel, onItemClick, selectedIndex, tabs, @@ -105,7 +107,7 @@ const BpkNavigationTabGroup = ({