diff --git a/examples/bpk-component-card-list/examples.tsx b/examples/bpk-component-card-list/examples.tsx index b0b73eaf2b..81d800ab1a 100644 --- a/examples/bpk-component-card-list/examples.tsx +++ b/examples/bpk-component-card-list/examples.tsx @@ -16,16 +16,97 @@ * limitations under the License. */ -import BpkCardList from "../../packages/bpk-component-card-list"; +import { useState } from 'react'; + +import BpkCard from '../../packages/bpk-component-card'; +import BpkCardList from '../../packages/bpk-component-card-list'; +import BpkImage from '../../packages/bpk-component-image'; +import BpkText, { + TEXT_STYLES, +} from '../../packages/bpk-component-text/src/BpkText'; + +import STYLES from './exampless.module.scss'; + +const DestinationCard = (i: number) => ( + + + + + + + + {`Destination Name ${i}`} + + Country + + + + Direct + £100 + + + + +); + +type ExampleCard = typeof DestinationCard; + +const cards = (cardType: ExampleCard) => { + const cardList = []; + for (let i = 0; i < 14; i += 1) { + cardList.push(cardType(i)); + } + return cardList; +}; const BasicExample = () => ( {}} + buttonHref="" + onButtonClick={() => null} + cardList={cards(DestinationCard)} + layoutDesktop="grid" + layoutMobile="stack" /> ); -export default BasicExample; \ No newline at end of file +const GridToStackExample = () => ( + null} + accessory="button" + buttonText="Explore more" + /> +); + +const GridToStackWithExpandExample = () => { + const [expandText, setExpandText] = useState('Show more'); + + return ( + + setExpandText(expandText === 'Show more' ? 'Show less' : 'Show more') + } + accessory="expand" + buttonText="Explore more" + expandText={expandText} + /> + ); +}; + +export { BasicExample, GridToStackExample, GridToStackWithExpandExample }; diff --git a/examples/bpk-component-card-list/exampless.module.scss b/examples/bpk-component-card-list/exampless.module.scss new file mode 100644 index 0000000000..4bf7efaf05 --- /dev/null +++ b/examples/bpk-component-card-list/exampless.module.scss @@ -0,0 +1,66 @@ +/* + * 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'; + +.bpkdocs-consumer-level { + img { + border-top-left-radius: tokens.$bpk-border-radius-md; + border-top-right-radius: tokens.$bpk-border-radius-md; + overflow: hidden; + } + + .bpk-bottom { + display: flex; + padding: tokens.bpk-spacing-lg(); + justify-content: space-between; + } + + .bpk-column { + display: flex; + flex-direction: column; + } +} + +.bpkdocs-consumer-level__internal-card { + display: flex; + flex-direction: row; + align-items: center; + overflow: hidden; + + .bpkdocs-internal-link-img { + width: 20%; + } + + img { + border-top-left-radius: tokens.$bpk-border-radius-md; + border-bottom-left-radius: tokens.$bpk-border-radius-md; + } + + .bpk-info { + margin: auto tokens.bpk-spacing-lg(); + flex-direction: column; + } + + .bpk-verticalLinks { + &_bullet { + margin: 0 tokens.bpk-spacing-sm(); + color: tokens.$bpk-core-accent-day; + } + } +} diff --git a/examples/bpk-component-card-list/stories.ts b/examples/bpk-component-card-list/stories.ts index b14a760653..f2867071eb 100644 --- a/examples/bpk-component-card-list/stories.ts +++ b/examples/bpk-component-card-list/stories.ts @@ -16,9 +16,13 @@ * limitations under the License. */ -import BpkCardList from "../../packages/bpk-component-card-list"; +import BpkCardList from '../../packages/bpk-component-card-list'; -import BasicExample from './examples'; +import { + BasicExample, + GridToStackExample, + GridToStackWithExpandExample, +} from './examples'; export default { title: 'bpk-component-card-list', @@ -26,12 +30,13 @@ export default { }; export const Basic = BasicExample; +export const GridToStack = GridToStackExample; +export const GridToStackWithExpand = GridToStackWithExpandExample; export const VisualTest = Basic; - export const VisualTestWithZoom = { render: VisualTest, args: { - zoomEnabled: true - } -} \ No newline at end of file + zoomEnabled: true, + }, +}; diff --git a/packages/bpk-component-card-list/index.ts b/packages/bpk-component-card-list/index.ts index a94c05fdbd..05fe35805c 100644 --- a/packages/bpk-component-card-list/index.ts +++ b/packages/bpk-component-card-list/index.ts @@ -16,6 +16,6 @@ * limitations under the License. */ -import BpkCardList from "./src/BpkCardList"; +import BpkCardList from './src/BpkCardList'; -export default BpkCardList; \ No newline at end of file +export default BpkCardList; diff --git a/packages/bpk-component-card-list/src/BpkCardList-test.tsx b/packages/bpk-component-card-list/src/BpkCardList-test.tsx index 93d491dc6f..d466b5acb5 100644 --- a/packages/bpk-component-card-list/src/BpkCardList-test.tsx +++ b/packages/bpk-component-card-list/src/BpkCardList-test.tsx @@ -16,24 +16,107 @@ * limitations under the License. */ -import { render, screen } from "@testing-library/react"; +import { getByText, render, screen } from '@testing-library/react'; -import BpkCardList from "./BpkCardList"; +import mockCards from '../testMocks'; + +import BpkCardList from './BpkCardList'; describe('BpkCardList', () => { - it('should render correctly', () => { + it('should render correctly with grid, stack and no accessory', () => { render( , + ); + + const cardsSection = screen.getByTestId('bpk-card-list--card-list') + .firstChild?.firstChild; + expect(screen.getByText('Title')).toBeInTheDocument(); + expect(screen.getByText('Description')).toBeInTheDocument(); + expect(cardsSection?.childNodes.length).toBe(2); + }); + + it('should render correctly with grid, stack, header button and no accessory', () => { + render( + {}} + />, + ); + + const header = screen.getByTestId('bpk-card-list').firstElementChild; + const cardsSection = screen.getByTestId('bpk-card-list--card-list') + .firstChild?.firstChild; + expect(screen.getByText('Title')).toBeInTheDocument(); + expect(screen.getByText('Description')).toBeInTheDocument(); + expect( + getByText(header as HTMLElement, 'Header Button'), + ).toBeInTheDocument(); + expect(cardsSection?.childNodes.length).toBe(2); + }); + + it('should render correctly with grid, stack and expand accessory', () => { + render( + , + ); + + const cardsAndAccessorySection = screen.getByTestId( + 'bpk-card-list--card-list', + ).firstChild; + const cardsSection = cardsAndAccessorySection?.firstChild; + expect(screen.getByText('Title')).toBeInTheDocument(); + expect(screen.getByText('Description')).toBeInTheDocument(); + expect(screen.getByText('Expand')).toBeInTheDocument(); + expect(cardsSection?.childNodes.length).toBe(2); + expect( + getByText(cardsAndAccessorySection as HTMLElement, 'Expand'), + ).toBeInTheDocument(); + }); + + it('should render correctly with grid, stack and button accessory', () => { + render( + {}} - /> + />, ); + const cardsAndAccessorySection = screen.getByTestId( + 'bpk-card-list--card-list', + ).firstChild; + const cardsSection = cardsAndAccessorySection?.firstChild; + expect(screen.getByText('Title')).toBeInTheDocument(); expect(screen.getByText('Description')).toBeInTheDocument(); expect(screen.getByText('Button')).toBeInTheDocument(); + expect(cardsSection?.childNodes.length).toBe(2); + expect( + getByText(cardsAndAccessorySection as HTMLElement, 'Button'), + ).toBeInTheDocument(); }); -}); \ No newline at end of file +}); diff --git a/packages/bpk-component-card-list/src/BpkCardList.tsx b/packages/bpk-component-card-list/src/BpkCardList.tsx index 79e4aa2684..cbf082d4c9 100644 --- a/packages/bpk-component-card-list/src/BpkCardList.tsx +++ b/packages/bpk-component-card-list/src/BpkCardList.tsx @@ -16,31 +16,45 @@ * limitations under the License. */ +import BpkBreakpoint, { BREAKPOINTS } from '../../bpk-component-breakpoint'; import { BpkButtonV2 } from '../../bpk-component-button'; import BpkSectionHeader from '../../bpk-component-section-header'; import { cssModules } from '../../bpk-react-utils'; +import BpkCardListGridStack from './BpkCardListGridStack'; +import { LAYOUTS } from './common-types'; + import type CardListProps from './common-types'; import STYLES from './BpkCardList.module.scss'; const getClassName = cssModules(STYLES); +const DEFAULT_ITEMS = 3; + const BpkCardList = (props: CardListProps) => { const { + accessory, buttonHref, buttonText, + cardList, description, + expandText, + initiallyShownCards = DEFAULT_ITEMS, + layoutDesktop, + layoutMobile, onButtonClick, title, } = props; - const button = buttonText && ( - {buttonText} + const button = buttonText && accessory !== 'button' && ( + + {buttonText} + ); return ( - + { className={getClassName('bpk-card-list--card-list')} data-testid="bpk-card-list--card-list" > - TODO: CARDS + + {(isActive) => { + if (isActive) { + if (layoutMobile === LAYOUTS.stack) { + return ( + + {cardList} + + ); + } + return ; + } + + if (layoutDesktop === LAYOUTS.grid) { + return ( + + {cardList} + + ); + } + return ; + }} + ); diff --git a/packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack-test.tsx b/packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack-test.tsx new file mode 100644 index 0000000000..da267e1625 --- /dev/null +++ b/packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack-test.tsx @@ -0,0 +1,116 @@ +/* + * 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 mockCards from '../../testMocks'; + +import BpkCardListGridStack from './BpkCardListGridStack'; + +describe('BpkCardListGridStack', () => { + it('should render correctly with grid and no accessory', () => { + render( + + {mockCards(3)} + , + ); + + const cards = screen.getByTestId('bpk-card-list-grid-stack__content'); + expect(cards.childNodes.length).toBe(3); + }); + + it('should render correctly with stack and no accessory', () => { + render( + + {mockCards(3)} + , + ); + + const cards = screen.getByTestId('bpk-card-list-grid-stack__content'); + expect(cards.childNodes.length).toBe(3); + }); + + it('should render correctly with expand accessory', () => { + render( + + {mockCards(3)} + , + ); + + const container = screen.getByTestId('bpk-card-list-grid-stack'); + const cards = screen.getByTestId('bpk-card-list-grid-stack__content'); + expect(cards.childNodes.length).toBe(3); + expect(container.lastChild).toHaveRole('button'); + expect(container.lastChild).toHaveTextContent('Show more'); + }); + + it('should render correctly with button accessory', () => { + render( + {}} + initiallyShownCards={3} + > + {mockCards(3)} + , + ); + + const container = screen.getByTestId('bpk-card-list-grid-stack'); + const cards = screen.getByTestId('bpk-card-list-grid-stack__content'); + const accessory = container.lastChild; + expect(cards.childNodes.length).toBe(3); + expect(accessory?.firstChild).toHaveRole('button'); + expect(accessory).toHaveTextContent('Explore more'); + }); + + it('should show and hide cards when expand button is clicked', async () => { + const user = userEvent.setup(); + + render( + + {mockCards(6)} + , + ); + + const container = screen.getByTestId('bpk-card-list-grid-stack'); + const cards = screen.getByTestId('bpk-card-list-grid-stack__content'); + const accessory = container.lastChild; + expect(cards.childNodes.length).toBe(3); + expect(accessory).toHaveRole('button'); + expect(accessory).toHaveTextContent('Show more'); + + await user.click(accessory as HTMLElement); + expect(cards.childNodes.length).toBe(6); + + await user.click(accessory as HTMLElement); + expect(cards.childNodes.length).toBe(3); + }); +}); diff --git a/packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack.module.scss b/packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack.module.scss new file mode 100644 index 0000000000..1a7bd7fe0b --- /dev/null +++ b/packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack.module.scss @@ -0,0 +1,45 @@ +/* + * 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'; + +.bpk-card-list-grid-stack { + display: grid; + gap: tokens.bpk-spacing-lg(); + + &__grid { + display: grid; + grid-template-columns: repeat( + auto-fit, + minmax(tokens.$bpk-one-pixel-rem * 281, auto) + ); + gap: tokens.bpk-spacing-lg(); + } + + &__stack { + display: grid; + grid-template-columns: 1; + gap: tokens.bpk-spacing-lg(); + } + + &__accessory { + &__button { + width: auto; + } + } +} diff --git a/packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack.tsx b/packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack.tsx new file mode 100644 index 0000000000..4acd311f48 --- /dev/null +++ b/packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack.tsx @@ -0,0 +1,103 @@ +/* + * 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 { useState } from 'react'; + +import { BpkButtonV2 } from '../../../bpk-component-button'; +import { cssModules } from '../../../bpk-react-utils'; +import BpkExpand from '../BpkExpand'; +import { ACCESSORY_TYPES, type CardListGridStackProps } from '../common-types'; + +import STYLES from './BpkCardListGridStack.module.scss'; + +const getClassName = cssModules(STYLES); + +const BpkCardListGridStack = (props: CardListGridStackProps) => { + const { + accessory, + buttonText, + children, + expandText, + initiallyShownCards, + layout, + onButtonClick, + } = props; + + let defaultInitiallyShownCards: number; + if (accessory === ACCESSORY_TYPES.Expand) { + defaultInitiallyShownCards = initiallyShownCards; + } else { + defaultInitiallyShownCards = children.length; + } + + const [collapsed, setCollapsed] = useState(true); + const [visibleCards, setVisibleCards] = useState( + children.slice(0, defaultInitiallyShownCards), + ); + + const showContent = () => { + setVisibleCards(children); + setCollapsed(false); + onButtonClick?.(); + }; + + const hideContent = () => { + setVisibleCards(children.slice(0, initiallyShownCards)); + setCollapsed(true); + onButtonClick?.(); + }; + + let accessoryContent; + if (accessory === ACCESSORY_TYPES.Expand) { + accessoryContent = ( + + {expandText || ''} + + ); + } else if (accessory === ACCESSORY_TYPES.Button) { + accessoryContent = ( + + {buttonText} + + ); + } + + return ( + + + {visibleCards} + + {accessoryContent} + + ); +}; + +export default BpkCardListGridStack; diff --git a/packages/bpk-component-card-list/src/BpkCardListGridStack/accessibility-test.tsx b/packages/bpk-component-card-list/src/BpkCardListGridStack/accessibility-test.tsx new file mode 100644 index 0000000000..3a7513cf1f --- /dev/null +++ b/packages/bpk-component-card-list/src/BpkCardListGridStack/accessibility-test.tsx @@ -0,0 +1,81 @@ +/* + * 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 mockCards from '../../testMocks'; + +import BpkCardListGridStack from './BpkCardListGridStack'; + +describe('BpkCardListGridStack', () => { + it('should have no accessibility issues for grid and no accessory', async () => { + const { container } = render( + + {mockCards(3)} + , + ); + + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('should have no accessibility issues for stack and no accessory', async () => { + const { container } = render( + + {mockCards(3)} + , + ); + + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('should have no accessibility issues for expand accessory', async () => { + const { container } = render( + + {mockCards(3)} + , + ); + + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('should have no accessibility issues for button accessory', async () => { + const { container } = render( + {}} + > + {mockCards(3)} + , + ); + + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); +}); diff --git a/packages/bpk-component-card-list/src/BpkCardListGridStack/index.ts b/packages/bpk-component-card-list/src/BpkCardListGridStack/index.ts new file mode 100644 index 0000000000..d6cdd0cadc --- /dev/null +++ b/packages/bpk-component-card-list/src/BpkCardListGridStack/index.ts @@ -0,0 +1,21 @@ +/* + * 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 BpkCardListGridStack from './BpkCardListGridStack'; + +export default BpkCardListGridStack; diff --git a/packages/bpk-component-card-list/src/BpkExpand/BpkExpand-test.tsx b/packages/bpk-component-card-list/src/BpkExpand/BpkExpand-test.tsx new file mode 100644 index 0000000000..9086c9ee7c --- /dev/null +++ b/packages/bpk-component-card-list/src/BpkExpand/BpkExpand-test.tsx @@ -0,0 +1,78 @@ +/* + * 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 BpkExpand from './BpkExpand'; + +describe('BpkExpand', () => { + const hideContent = jest.fn(); + const setCollapsed = jest.fn(); + const showContent = jest.fn(); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should render correctly when collapsed', async () => { + const user = userEvent.setup(); + + render( + + Expand + , + ); + + const button = screen.getByTestId('expand-button'); + expect(button).toHaveTextContent('Expand'); + + await user.click(button); + expect(showContent).toHaveBeenCalled(); + expect(setCollapsed).toHaveBeenCalledWith(false); + expect(hideContent).not.toHaveBeenCalled(); + }); + + it('should render correctly when expanded', async () => { + const user = userEvent.setup(); + + render( + + Collapse + , + ); + + const button = screen.getByTestId('expand-button'); + expect(button).toHaveTextContent('Collapse'); + + await user.click(button); + expect(hideContent).toHaveBeenCalled(); + expect(setCollapsed).toHaveBeenCalledWith(true); + expect(showContent).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/bpk-component-card-list/src/BpkExpand/BpkExpand.tsx b/packages/bpk-component-card-list/src/BpkExpand/BpkExpand.tsx new file mode 100644 index 0000000000..d5036818f9 --- /dev/null +++ b/packages/bpk-component-card-list/src/BpkExpand/BpkExpand.tsx @@ -0,0 +1,67 @@ +/* + * 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 { BUTTON_TYPES, BpkButtonV2 } from '../../../bpk-component-button'; +import { + withButtonAlignment, + withRtlSupport, +} from '../../../bpk-component-icon'; +import ChevronDown from '../../../bpk-component-icon/sm/chevron-down'; +import ChevronUp from '../../../bpk-component-icon/sm/chevron-up'; + +import type { ExpandProps } from '../common-types'; + +const AlignedChevronDownIcon = withButtonAlignment(withRtlSupport(ChevronDown)); +const AlignedChevronUpIcon = withButtonAlignment(withRtlSupport(ChevronUp)); + +const BpkExpand = ({ + children, + collapsed, + hideContent, + setCollapsed, + showContent, +}: ExpandProps) => { + const buttonIcon = collapsed ? ( + + ) : ( + + ); + const buttonOnClick = () => { + if (collapsed) { + showContent(); + setCollapsed(false); + } else { + hideContent(); + setCollapsed(true); + } + }; + + return ( + buttonOnClick()} + aria-expanded={!collapsed} + > + {children} + {buttonIcon} + + ); +}; + +export default BpkExpand; diff --git a/packages/bpk-component-card-list/src/BpkExpand/index.ts b/packages/bpk-component-card-list/src/BpkExpand/index.ts new file mode 100644 index 0000000000..6ca5e12c22 --- /dev/null +++ b/packages/bpk-component-card-list/src/BpkExpand/index.ts @@ -0,0 +1,21 @@ +/* + * 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 BpkExpand from './BpkExpand'; + +export default BpkExpand; diff --git a/packages/bpk-component-card-list/src/accessibility-test.tsx b/packages/bpk-component-card-list/src/accessibility-test.tsx index 49e3d01b36..e0dd074b42 100644 --- a/packages/bpk-component-card-list/src/accessibility-test.tsx +++ b/packages/bpk-component-card-list/src/accessibility-test.tsx @@ -16,24 +16,79 @@ * limitations under the License. */ -import { render } from "@testing-library/react"; -import { axe } from "jest-axe"; +import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; -import BpkCardList from "./BpkCardList"; +import mockCards from '../testMocks'; -describe('BpkCardList accessibility tests', () => { - it('should not have any accessibility issues', async () => { +import BpkCardList from './BpkCardList'; + +describe('BpkCardList', () => { + it('should not have any accessibility issues with grid, stack and no accessory', async () => { + const { container } = render( + , + ); + + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('should not have any accessibility issues with grid, stack, header button and no accessory', async () => { + const { container } = render( + {}} + />, + ); + + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('should not have any accessibility issues with grid, stack, expand accessory', async () => { + const { container } = render( + , + ); + + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('should not have any accessibility issues with grid, stack, button accessory', async () => { const { container } = render( {}} - /> + />, ); const results = await axe(container); expect(results).toHaveNoViolations(); }); -}); \ No newline at end of file +}); diff --git a/packages/bpk-component-card-list/src/common-types.ts b/packages/bpk-component-card-list/src/common-types.ts index b56a19f0ac..96edf766f6 100644 --- a/packages/bpk-component-card-list/src/common-types.ts +++ b/packages/bpk-component-card-list/src/common-types.ts @@ -16,12 +16,60 @@ * limitations under the License. */ -type CardListProps = { - title: string, - description?: string, - buttonText?: string, - onButtonClick?: () => void, - buttonHref?: string, -} - -export default CardListProps; \ No newline at end of file +import type { Dispatch, ReactElement, SetStateAction } from 'react'; + +const LAYOUTS = { + grid: 'grid', + stack: 'stack', +} as const; + +type DesktopLayouts = typeof LAYOUTS.grid; +type MobileLayouts = typeof LAYOUTS.stack; + +const ACCESSORY_TYPES = { + Expand: 'expand', + Button: 'button', +} as const; + +type ExpandProps = { + children: string | ReactElement; + collapsed: boolean; + hideContent: () => void; + setCollapsed: Dispatch>; + showContent: () => void; +}; + +type CardListBaseProps = { + title: string; + description?: string; + buttonText?: string; + onButtonClick?: () => void; + buttonHref?: string; + layoutMobile: MobileLayouts; + layoutDesktop: DesktopLayouts; + initiallyShownCards?: number; + cardList: ReactElement[]; + expandText?: string; + accessory?: (typeof ACCESSORY_TYPES)[keyof typeof ACCESSORY_TYPES]; +}; + +type CardListGridStackProps = { + children: ReactElement[]; + accessory?: typeof ACCESSORY_TYPES.Expand | typeof ACCESSORY_TYPES.Button; + expandText?: string; + buttonText?: string; + onButtonClick?: () => void; + initiallyShownCards: number; + layout: typeof LAYOUTS.grid | typeof LAYOUTS.stack; +}; + +type CardListProps = CardListBaseProps; + +export default CardListProps; +export { LAYOUTS, ACCESSORY_TYPES }; +export type { + DesktopLayouts, + MobileLayouts, + CardListGridStackProps, + ExpandProps, +}; diff --git a/packages/bpk-component-card-list/testMocks.tsx b/packages/bpk-component-card-list/testMocks.tsx new file mode 100644 index 0000000000..50b7c7ced2 --- /dev/null +++ b/packages/bpk-component-card-list/testMocks.tsx @@ -0,0 +1,29 @@ +/* + * 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 BpkCard from '../bpk-component-card'; + +const mockCards = (numberOfCards: number) => { + const cards = []; + for (let i = 0; i < numberOfCards; i += 1) { + cards.push({`Card ${i}`}); + } + return cards; +}; + +export default mockCards;