From 6ccc0cf43eebf481ee06a00f348a9bf622d8e1d0 Mon Sep 17 00:00:00 2001 From: Noel Rajan Date: Tue, 17 Dec 2024 16:35:02 +0000 Subject: [PATCH 1/4] add grid and stack layouts --- examples/bpk-component-card-list/examples.tsx | 94 ++++++++++++++++++- .../exampless.module.scss | 66 +++++++++++++ examples/bpk-component-card-list/stories.ts | 5 +- .../src/BpkCardList.tsx | 56 ++++++++++- .../BpkCardListGridStack.module.scss | 45 +++++++++ .../BpkCardListGridStack.tsx | 86 +++++++++++++++++ .../src/BpkCardListGridStack/index.ts | 21 +++++ .../src/BpkExpand/BpkExpand.tsx | 65 +++++++++++++ .../src/BpkExpand/index.ts | 21 +++++ .../src/common-types.ts | 66 +++++++++++-- 10 files changed, 508 insertions(+), 17 deletions(-) create mode 100644 examples/bpk-component-card-list/exampless.module.scss create mode 100644 packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack.module.scss create mode 100644 packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack.tsx create mode 100644 packages/bpk-component-card-list/src/BpkCardListGridStack/index.ts create mode 100644 packages/bpk-component-card-list/src/BpkExpand/BpkExpand.tsx create mode 100644 packages/bpk-component-card-list/src/BpkExpand/index.ts diff --git a/examples/bpk-component-card-list/examples.tsx b/examples/bpk-component-card-list/examples.tsx index b0b73eaf2b..268e1a402d 100644 --- a/examples/bpk-component-card-list/examples.tsx +++ b/examples/bpk-component-card-list/examples.tsx @@ -16,16 +16,104 @@ * limitations under the License. */ +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..12f7e7cabf 100644 --- a/examples/bpk-component-card-list/stories.ts +++ b/examples/bpk-component-card-list/stories.ts @@ -18,7 +18,7 @@ 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,9 +26,10 @@ export default { }; export const Basic = BasicExample; +export const GridToStack = GridToStackExample; +export const GridToStackWithExpand = GridToStackWithExpandExample; export const VisualTest = Basic; - export const VisualTestWithZoom = { render: VisualTest, args: { diff --git a/packages/bpk-component-card-list/src/BpkCardList.tsx b/packages/bpk-component-card-list/src/BpkCardList.tsx index 79e4aa2684..2856adbe07 100644 --- a/packages/bpk-component-card-list/src/BpkCardList.tsx +++ b/packages/bpk-component-card-list/src/BpkCardList.tsx @@ -16,27 +16,41 @@ * 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 ( @@ -51,7 +65,43 @@ const BpkCardList = (props: CardListProps) => { 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.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..4aaf1c2b16 --- /dev/null +++ b/packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack.tsx @@ -0,0 +1,86 @@ +/* + * 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; \ No newline at end of file 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..66da970035 --- /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.tsx b/packages/bpk-component-card-list/src/BpkExpand/BpkExpand.tsx new file mode 100644 index 0000000000..0a80095473 --- /dev/null +++ b/packages/bpk-component-card-list/src/BpkExpand/BpkExpand.tsx @@ -0,0 +1,65 @@ +/* + * 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()} + > + {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..d3459a62ba --- /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/common-types.ts b/packages/bpk-component-card-list/src/common-types.ts index b56a19f0ac..a463caa1ce 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, +}; From 643e7ea2fc3a35271dc4b6475c75527b0b91c818 Mon Sep 17 00:00:00 2001 From: Noel Rajan Date: Tue, 17 Dec 2024 16:38:29 +0000 Subject: [PATCH 2/4] prettier --- examples/bpk-component-card-list/examples.tsx | 33 +++++++---------- examples/bpk-component-card-list/stories.ts | 14 +++++--- packages/bpk-component-card-list/index.ts | 4 +-- .../src/BpkCardList-test.tsx | 8 ++--- .../src/BpkCardList.tsx | 4 +-- .../BpkCardListGridStack.tsx | 35 ++++++++++++------- .../src/BpkCardListGridStack/index.ts | 2 +- .../src/BpkExpand/index.ts | 2 +- .../src/accessibility-test.tsx | 10 +++--- .../src/common-types.ts | 2 +- 10 files changed, 61 insertions(+), 53 deletions(-) diff --git a/examples/bpk-component-card-list/examples.tsx b/examples/bpk-component-card-list/examples.tsx index 268e1a402d..81d800ab1a 100644 --- a/examples/bpk-component-card-list/examples.tsx +++ b/examples/bpk-component-card-list/examples.tsx @@ -16,14 +16,16 @@ * limitations under the License. */ -import { useState } from "react"; +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 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"; +import STYLES from './exampless.module.scss'; const DestinationCard = (i: number) => ( @@ -97,23 +99,14 @@ const GridToStackWithExpandExample = () => { cardList={cards(DestinationCard)} layoutDesktop="grid" layoutMobile="stack" - onButtonClick={() => setExpandText(expandText === 'Show more' ? 'Show less' : 'Show more')} + onButtonClick={() => + setExpandText(expandText === 'Show more' ? 'Show less' : 'Show more') + } accessory="expand" buttonText="Explore more" expandText={expandText} /> ); -} - - - - - - - - -export { - BasicExample, - GridToStackExample, - GridToStackWithExpandExample, }; + +export { BasicExample, GridToStackExample, GridToStackWithExpandExample }; diff --git a/examples/bpk-component-card-list/stories.ts b/examples/bpk-component-card-list/stories.ts index 12f7e7cabf..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, GridToStackExample, GridToStackWithExpandExample } from './examples'; +import { + BasicExample, + GridToStackExample, + GridToStackWithExpandExample, +} from './examples'; export default { title: 'bpk-component-card-list', @@ -33,6 +37,6 @@ 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..229fef7178 100644 --- a/packages/bpk-component-card-list/src/BpkCardList-test.tsx +++ b/packages/bpk-component-card-list/src/BpkCardList-test.tsx @@ -16,9 +16,9 @@ * limitations under the License. */ -import { render, screen } from "@testing-library/react"; +import { render, screen } from '@testing-library/react'; -import BpkCardList from "./BpkCardList"; +import BpkCardList from './BpkCardList'; describe('BpkCardList', () => { it('should render correctly', () => { @@ -29,11 +29,11 @@ describe('BpkCardList', () => { buttonText="Button" buttonHref="https://www.skyscanner.net" onButtonClick={() => {}} - /> + />, ); expect(screen.getByText('Title')).toBeInTheDocument(); expect(screen.getByText('Description')).toBeInTheDocument(); expect(screen.getByText('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 2856adbe07..c7b7d99816 100644 --- a/packages/bpk-component-card-list/src/BpkCardList.tsx +++ b/packages/bpk-component-card-list/src/BpkCardList.tsx @@ -47,7 +47,7 @@ const BpkCardList = (props: CardListProps) => { title, } = props; - const button = buttonText && accessory !== "button" && ( + const button = buttonText && accessory !== 'button' && ( {buttonText} @@ -97,7 +97,7 @@ const BpkCardList = (props: CardListProps) => { > {cardList} - ) + ); } return
; }} diff --git a/packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack.tsx b/packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack.tsx index 4aaf1c2b16..d91746a105 100644 --- a/packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack.tsx +++ b/packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack.tsx @@ -21,17 +21,22 @@ 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 { 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; + const { + accessory, + buttonText, + children, + expandText, + initiallyShownCards, + layout, + onButtonClick, + } = props; let defaultInitiallyShownCards: number; if (accessory === ACCESSORY_TYPES.Expand) { @@ -41,19 +46,21 @@ const BpkCardListGridStack = (props: CardListGridStackProps) => { } const [collapsed, setCollapsed] = useState(true); - const [visibleCards, setVisibleCards] = useState(children.slice(0, defaultInitiallyShownCards)); + 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) { @@ -69,18 +76,22 @@ const BpkCardListGridStack = (props: CardListGridStackProps) => { ); } else if (accessory === ACCESSORY_TYPES.Button) { accessoryContent = ( -
- {buttonText} +
+ {buttonText}
); } return (
-
{visibleCards}
+
+ {visibleCards} +
{accessoryContent}
); }; -export default BpkCardListGridStack; \ No newline at end of file +export default BpkCardListGridStack; diff --git a/packages/bpk-component-card-list/src/BpkCardListGridStack/index.ts b/packages/bpk-component-card-list/src/BpkCardListGridStack/index.ts index 66da970035..d6cdd0cadc 100644 --- a/packages/bpk-component-card-list/src/BpkCardListGridStack/index.ts +++ b/packages/bpk-component-card-list/src/BpkCardListGridStack/index.ts @@ -16,6 +16,6 @@ * limitations under the License. */ -import BpkCardListGridStack from "./BpkCardListGridStack"; +import BpkCardListGridStack from './BpkCardListGridStack'; export default BpkCardListGridStack; diff --git a/packages/bpk-component-card-list/src/BpkExpand/index.ts b/packages/bpk-component-card-list/src/BpkExpand/index.ts index d3459a62ba..6ca5e12c22 100644 --- a/packages/bpk-component-card-list/src/BpkExpand/index.ts +++ b/packages/bpk-component-card-list/src/BpkExpand/index.ts @@ -16,6 +16,6 @@ * limitations under the License. */ -import BpkExpand from "./BpkExpand"; +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..c72a802811 100644 --- a/packages/bpk-component-card-list/src/accessibility-test.tsx +++ b/packages/bpk-component-card-list/src/accessibility-test.tsx @@ -16,10 +16,10 @@ * 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 BpkCardList from './BpkCardList'; describe('BpkCardList accessibility tests', () => { it('should not have any accessibility issues', async () => { @@ -30,10 +30,10 @@ describe('BpkCardList accessibility tests', () => { buttonText="Button" buttonHref="https://www.skyscanner.net" onButtonClick={() => {}} - /> + />, ); 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 a463caa1ce..96edf766f6 100644 --- a/packages/bpk-component-card-list/src/common-types.ts +++ b/packages/bpk-component-card-list/src/common-types.ts @@ -50,7 +50,7 @@ type CardListBaseProps = { initiallyShownCards?: number; cardList: ReactElement[]; expandText?: string; - accessory?: typeof ACCESSORY_TYPES[keyof typeof ACCESSORY_TYPES]; + accessory?: (typeof ACCESSORY_TYPES)[keyof typeof ACCESSORY_TYPES]; }; type CardListGridStackProps = { From 77914990f04573cc387a35c7d2f79dacc23903e6 Mon Sep 17 00:00:00 2001 From: Noel Rajan Date: Thu, 19 Dec 2024 14:17:48 +0000 Subject: [PATCH 3/4] add tests --- .../src/BpkCardList-test.tsx | 89 +++++++++++++- .../src/BpkCardList.tsx | 2 +- .../BpkCardListGridStack-test.tsx | 116 ++++++++++++++++++ .../BpkCardListGridStack.tsx | 10 +- .../accessibility-test.tsx | 81 ++++++++++++ .../src/BpkExpand/BpkExpand-test.tsx | 78 ++++++++++++ .../src/BpkExpand/BpkExpand.tsx | 3 +- .../src/accessibility-test.tsx | 61 ++++++++- .../bpk-component-card-list/testMocks.tsx | 29 +++++ 9 files changed, 459 insertions(+), 10 deletions(-) create mode 100644 packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack-test.tsx create mode 100644 packages/bpk-component-card-list/src/BpkCardListGridStack/accessibility-test.tsx create mode 100644 packages/bpk-component-card-list/src/BpkExpand/BpkExpand-test.tsx create mode 100644 packages/bpk-component-card-list/testMocks.tsx diff --git a/packages/bpk-component-card-list/src/BpkCardList-test.tsx b/packages/bpk-component-card-list/src/BpkCardList-test.tsx index 229fef7178..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 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(); }); }); diff --git a/packages/bpk-component-card-list/src/BpkCardList.tsx b/packages/bpk-component-card-list/src/BpkCardList.tsx index c7b7d99816..cbf082d4c9 100644 --- a/packages/bpk-component-card-list/src/BpkCardList.tsx +++ b/packages/bpk-component-card-list/src/BpkCardList.tsx @@ -54,7 +54,7 @@ const BpkCardList = (props: CardListProps) => { ); return ( -
+
{ + 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.tsx b/packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack.tsx index d91746a105..4acd311f48 100644 --- a/packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack.tsx +++ b/packages/bpk-component-card-list/src/BpkCardListGridStack/BpkCardListGridStack.tsx @@ -85,8 +85,14 @@ const BpkCardListGridStack = (props: CardListGridStackProps) => { } return ( -
-
+
+
{visibleCards}
{accessoryContent} 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/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 index 0a80095473..722b394381 100644 --- a/packages/bpk-component-card-list/src/BpkExpand/BpkExpand.tsx +++ b/packages/bpk-component-card-list/src/BpkExpand/BpkExpand.tsx @@ -15,6 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { BUTTON_TYPES, BpkButtonV2 } from '../../../bpk-component-button'; import { withButtonAlignment, @@ -52,7 +53,7 @@ const BpkExpand = ({ return ( buttonOnClick()} > diff --git a/packages/bpk-component-card-list/src/accessibility-test.tsx b/packages/bpk-component-card-list/src/accessibility-test.tsx index c72a802811..e0dd074b42 100644 --- a/packages/bpk-component-card-list/src/accessibility-test.tsx +++ b/packages/bpk-component-card-list/src/accessibility-test.tsx @@ -19,16 +19,71 @@ import { render } from '@testing-library/react'; import { axe } from 'jest-axe'; +import mockCards from '../testMocks'; + import BpkCardList from './BpkCardList'; -describe('BpkCardList accessibility tests', () => { - it('should not have any accessibility issues', async () => { +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( {}} />, ); 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; From e911f2679f03a9b0fda81f7afef1ead4d6590d61 Mon Sep 17 00:00:00 2001 From: Noel Rajan Date: Thu, 19 Dec 2024 14:34:44 +0000 Subject: [PATCH 4/4] a11y fix --- packages/bpk-component-card-list/src/BpkExpand/BpkExpand.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/bpk-component-card-list/src/BpkExpand/BpkExpand.tsx b/packages/bpk-component-card-list/src/BpkExpand/BpkExpand.tsx index 722b394381..d5036818f9 100644 --- a/packages/bpk-component-card-list/src/BpkExpand/BpkExpand.tsx +++ b/packages/bpk-component-card-list/src/BpkExpand/BpkExpand.tsx @@ -56,6 +56,7 @@ const BpkExpand = ({ data-testid="expand-button" type={BUTTON_TYPES.link} onClick={() => buttonOnClick()} + aria-expanded={!collapsed} > {children} {buttonIcon}