From 74516e68d04d14e2fc398212d489005822761d39 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Thu, 21 Sep 2023 16:02:50 +0100 Subject: [PATCH 01/79] wip --- README.md | 1 + examples/bpk-component-chip-group/examples.js | 184 +++++++++++++++++ examples/bpk-component-chip-group/stories.js | 34 ++++ packages/bpk-component-chip-group/README.md | 21 ++ packages/bpk-component-chip-group/index.d.ts | 24 +++ packages/bpk-component-chip-group/index.ts | 26 +++ .../src/BpkChipGroup-test.tsx | 41 ++++ .../src/BpkChipGroup.d.ts | 29 +++ .../src/BpkChipGroup.module.css | 18 ++ .../src/BpkChipGroup.module.scss | 55 +++++ .../src/BpkChipGroup.tsx | 188 ++++++++++++++++++ .../BpkBoilerplate-test.tsx.snap | 32 +++ .../src/accessibility-test.tsx | 31 +++ 13 files changed, 684 insertions(+) create mode 100644 examples/bpk-component-chip-group/examples.js create mode 100644 examples/bpk-component-chip-group/stories.js create mode 100644 packages/bpk-component-chip-group/README.md create mode 100644 packages/bpk-component-chip-group/index.d.ts create mode 100644 packages/bpk-component-chip-group/index.ts create mode 100644 packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx create mode 100644 packages/bpk-component-chip-group/src/BpkChipGroup.d.ts create mode 100644 packages/bpk-component-chip-group/src/BpkChipGroup.module.css create mode 100644 packages/bpk-component-chip-group/src/BpkChipGroup.module.scss create mode 100644 packages/bpk-component-chip-group/src/BpkChipGroup.tsx create mode 100644 packages/bpk-component-chip-group/src/__snapshots__/BpkBoilerplate-test.tsx.snap create mode 100644 packages/bpk-component-chip-group/src/accessibility-test.tsx diff --git a/README.md b/README.md index 8e4eca3096..700efff1dd 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ To contribute please see [contributing.md](CONTRIBUTING.md). [`bpk-component-card`](/packages/bpk-component-card) [`bpk-component-checkbox`](/packages/bpk-component-checkbox) [`bpk-component-chip`](/packages/bpk-component-chip) +[`bpk-component-chip-group`](/packages/bpk-component-chip-group) [`bpk-component-close-button`](/packages/bpk-component-close-button) [`bpk-component-code`](/packages/bpk-component-code) [`bpk-component-datatable`](/packages/bpk-component-datatable) diff --git a/examples/bpk-component-chip-group/examples.js b/examples/bpk-component-chip-group/examples.js new file mode 100644 index 0000000000..bcea6f8635 --- /dev/null +++ b/examples/bpk-component-chip-group/examples.js @@ -0,0 +1,184 @@ +/* + * 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 BpkSelectableChip from '../../packages/bpk-component-chip'; +import BpkChipGroup, { CHIP_GROUP_TYPES } from '../../packages/bpk-component-chip-group'; +import { + BpkChipGroupMultiSelect, + BpkChipGroupSingleSelect, +} from '../../packages/bpk-component-chip-group/src/BpkChipGroup'; +import { useState } from 'react'; +import FilterIconSm from '../../packages/bpk-component-icon/sm/filter'; + + +const chips = [ + { + text: 'London', + accessibilityLabel: 'London', + onClick: () => {}, + }, + { + text: 'Berlin', + accessibilityLabel: 'Berlin', + onClick: () => {}, + }, + { + text: 'Florence', + accessibilityLabel: 'Florence', + onClick: () => {}, + }, + { + text: 'Stockholm', + accessibilityLabel: 'Stockholm', + onClick: () => {}, + }, + { + text: 'Copenhagen', + accessibilityLabel: 'Copenhagen', + onClick: () => {}, + }, + { + text: 'Salzburg', + accessibilityLabel: 'Salzburg', + onClick: () => {}, + }, + { + text: 'Graz', + accessibilityLabel: 'Graz', + onClick: () => {}, + }, + { + text: 'Lanzarote', + accessibilityLabel: 'Lanzarote', + onClick: () => {}, + }, + { + text: 'Valencia', + accessibilityLabel: 'Valencia', + onClick: () => {}, + }, + { + text: 'Reykjavik', + accessibilityLabel: 'Copenhagen', + onClick: () => {}, + }, + { + text: 'Tallinn', + accessibilityLabel: 'Tallinn', + onClick: () => {}, + }, + { + text: 'Sofia', + accessibilityLabel: 'Sofia', + onClick: () => {}, + }, +] + + +export const BpkChipGroupWrapping = () => { + + return ( +
+ + +
+ ); +}; + +export const BpkSingleChipGroupWrapping = () => { + // const [selected] + + return ( +
+ + +
+ ); +}; + + +export const BpkChipGroupRail = () => { + + return ( +
+ + +
+ ); +}; + + +export const BpkChipGroupSticky = () => { + + // const chips = [ + // { + // text: 'London', + // accessibilityLabel: 'London', + // onClick: () => {}, + // }, + // { + // text: 'Berlin', + // accessibilityLabel: 'Berlin', + // onClick: () => {}, + // }, + // { + // text: 'Florence', + // accessibilityLabel: 'Florence', + // onClick: () => {}, + // }, + // { + // text: 'Stockholm', + // accessibilityLabel: 'Stockholm', + // onClick: () => {}, + // }, + // { + // text: 'Copenhagen', + // accessibilityLabel: 'Copenhagen', + // onClick: () => {}, + // }, + // ] + + const stickyChip = { + text: 'Sort & Filter', + accessibilityLabel: 'Sort & Filter', + leadingAccessoryView: + // onClick: () => {}, + } + + return ( +
+ setSelected(selected)} + stickyChip={stickyChip} + > + +
+ ); +}; diff --git a/examples/bpk-component-chip-group/stories.js b/examples/bpk-component-chip-group/stories.js new file mode 100644 index 0000000000..ea6fa8a015 --- /dev/null +++ b/examples/bpk-component-chip-group/stories.js @@ -0,0 +1,34 @@ +/* + * 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. + */ + +/* @flow strict */ + +import { + BpkChipGroupRail, + BpkChipGroupWrapping, + BpkChipGroupSticky, BpkSingleChipGroupWrapping, +} from './examples'; + +export default { + title: 'bpk-component-chip-group', +}; + +export const WrappedChipGroup = BpkChipGroupWrapping; +export const SingleSelectChipGroup = BpkSingleChipGroupWrapping; +export const RailChipGroup = BpkChipGroupRail; +export const StickyChipGroup = BpkChipGroupSticky; diff --git a/packages/bpk-component-chip-group/README.md b/packages/bpk-component-chip-group/README.md new file mode 100644 index 0000000000..3d35b690e5 --- /dev/null +++ b/packages/bpk-component-chip-group/README.md @@ -0,0 +1,21 @@ +# bpk-component-boilerplate + +> Backpack example component. + +## Installation + +Check the main [Readme](https://github.com/skyscanner/backpack#usage) for a complete installation guide. + +## Usage + +```ts +import BpkBoilerplate from '@skyscanner/backpack-web/bpk-component-code'; + +export default () => ; +``` + +## Props + +| Property | PropType | Required | Default Value | +| --------- | -------- | -------- | ------------- | +| className | string | false | null | diff --git a/packages/bpk-component-chip-group/index.d.ts b/packages/bpk-component-chip-group/index.d.ts new file mode 100644 index 0000000000..6bcbf81b89 --- /dev/null +++ b/packages/bpk-component-chip-group/index.d.ts @@ -0,0 +1,24 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2022 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 BpkBoilerplate, { + type Props as BpkBoilerplateProps, +} from './src/BpkBoilerplate'; + +export type { BpkBoilerplateProps }; +export default BpkBoilerplate; diff --git a/packages/bpk-component-chip-group/index.ts b/packages/bpk-component-chip-group/index.ts new file mode 100644 index 0000000000..307bb39ebf --- /dev/null +++ b/packages/bpk-component-chip-group/index.ts @@ -0,0 +1,26 @@ +/* + * 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 BpkChipGroup, { + type Props as BpkChipGroupProps, + CHIP_GROUP_TYPES, +} from './src/BpkChipGroup'; + +export type { BpkChipGroupProps }; +export { CHIP_GROUP_TYPES }; +export default BpkChipGroup; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx new file mode 100644 index 0000000000..45dd18b5c7 --- /dev/null +++ b/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx @@ -0,0 +1,41 @@ +/* + * 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. + */ +/* @flow strict */ + +import { render } from '@testing-library/react'; + +import BpkChipGroup from './BpkChipGroup'; + +describe('BpkBoilerplate', () => { + it('should render correctly', () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); + }); + + it('should support custom class names', () => { + const { asFragment } = render( + , + ); + expect(asFragment()).toMatchSnapshot(); + }); + + it('should support arbitrary props', () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); + }); +}); diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts b/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts new file mode 100644 index 0000000000..be5fe90b0e --- /dev/null +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts @@ -0,0 +1,29 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2022 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. + */ + +export type Props = { + className?: string | null; + [rest: string]: any; +}; +declare const BpkBoilerplate: { + ({ className, ...rest }: Props): JSX.Element; + defaultProps: { + className: null; + }; +}; +export default BpkBoilerplate; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.css b/packages/bpk-component-chip-group/src/BpkChipGroup.module.css new file mode 100644 index 0000000000..8f3a642b9d --- /dev/null +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.css @@ -0,0 +1,18 @@ +/* + * 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. +*/ +@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-boilerplate{display:flex} diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss new file mode 100644 index 0000000000..38b9926710 --- /dev/null +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss @@ -0,0 +1,55 @@ +/* + * 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 '~bpk-mixins/index.scss'; + +.bpk-chip-group-container { + display: flex; + white-space: nowrap; +} + +.bpk-chip-group { + display: flex; + gap: bpk-spacing-md(); + + &--rail { + flex-wrap: nowrap; + } + + &--wrap { + flex-wrap: wrap; + } +} + +.bpk-sticky-chip { + padding-inline-end: bpk-spacing-md(); + margin-inline-end: bpk-spacing-md(); + @include bpk-border-right-sm($bpk-line-day); + + @include bpk-rtl { + @include bpk-border-left-sm($bpk-line-day); + } + + &--on-dark { + @include bpk-border-right-sm($bpk-line-on-dark-day); + + @include bpk-rtl { + @include bpk-border-left-sm($bpk-line-on-dark-day); + } + } +} diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx new file mode 100644 index 0000000000..3fbd8ce2cf --- /dev/null +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -0,0 +1,188 @@ +/* + * 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 { cssModules } from '../../bpk-react-utils'; + +import STYLES from './BpkChipGroup.module.scss'; +import { ReactElement, SyntheticEvent, useCallback, useState } from 'react'; +import BpkSelectableChip, { CHIP_TYPES } from '../../bpk-component-chip'; +import BpkMobileScrollContainer from '../../bpk-component-mobile-scroll-container'; + +const getClassName = cssModules(STYLES); + +export const CHIP_GROUP_TYPES = { + rail: 'rail', + wrap: 'wrap', +} +export type ChipGroupType = (typeof CHIP_GROUP_TYPES)[keyof typeof CHIP_GROUP_TYPES]; + +export type ChipItem = { + text: string; + accessibilityLabel: string + selected?: boolean; + onClick?: (selected: boolean, index: number, event: SyntheticEvent) => void; + component?: () => ReactElement; // TODO Component + rest: [string: any]; +}; + +type Props = { + className?: string | null; + // children: Node; + type: ChipGroupType; + chips: ChipItem[]; + chipStyle: (typeof CHIP_TYPES)[keyof typeof CHIP_TYPES]; + stickyChip?: ChipItem; + onClick?: (selected: boolean[]) => void; +}; + + + +const BpkChipGroup = ({ className = null, chips, type = CHIP_GROUP_TYPES.rail, chipStyle = CHIP_TYPES.default, onClick, stickyChip }: Props) => { + const connectedChips = chips.map((chip, index) => ({ + ...chip, + onClick: (event: SyntheticEvent) => { + const nextSelected = chips.map(chip => Boolean(chip.selected)) + nextSelected[index] = !nextSelected[index]; + + if (chip.onClick) { + chip.onClick(nextSelected[index], index, event); + } + if (onClick) { + onClick(nextSelected); + } + }, + })); + + + const containerClassnames = getClassName( + className, + 'bpk-chip-group-container', + ) + + const chipGroupClassNames = getClassName( + 'bpk-chip-group', + `bpk-chip-group--${type}`, + ); + + const stickyChipClassnames = getClassName( + 'bpk-sticky-chip', + `bpk-sticky-chip--${chipStyle}`, + ); + + const renderChipItem = ({text, accessibilityLabel, selected, onClick, component, ...rest}: ChipItem) => { + const Chip = component ?? BpkSelectableChip; + + return ( + {})} + {...rest} + > + {text} + + ); + } + + const wrapRailInScroll = (children: ReactElement) => + type === CHIP_GROUP_TYPES.rail ? {children} : children; + + return ( +
+ {type === CHIP_GROUP_TYPES.rail && stickyChip && +
+ {renderChipItem(stickyChip)} +
+ } + {wrapRailInScroll( +
+ {connectedChips.map(chip => renderChipItem(chip))} +
+ )} +
+ + ); +}; + + + +export const BpkChipGroupMultiSelect = ({ chips, onClick, ...rest }: Props) => { + const [selected, setSelected] = useState(chips.map(c => Boolean(c.selected))); + + const statefulChips = chips.map((chip, index) => ({ + ...chip, + selected: selected[index], + })) + + const clickHandler = (selectedChips: boolean[]) => { + setSelected(selectedChips); + if (onClick) { + onClick(selectedChips); + } + } + + return ( + + ); +}; + + +export type SingleSelectProps = { + onClick?: (selected: ChipItem) => void, +} & Props; + + +export const BpkChipGroupSingleSelect = ({ chips, onClick, ...rest }: SingleSelectProps) => { + const [selectedIndex, setSelectedIndex] = useState(chips.findIndex(c => Boolean(c.selected))); + + const connectedChips = chips.map((chip, index) => ({ + ...chip, + selected: index === selectedIndex, + onClick: (selected: boolean, index: number, event: SyntheticEvent) => { + setSelectedIndex(selected ? index : -1); + + if (chip.onClick) { + chip.onClick(selected, index, event); + } + + if (onClick) { + onClick({ + ...chip, + selected, + }); + } + }, + })) + + return ( + + ); +}; + + + + +BpkChipGroup.defaultProps = { + className: null, + type: CHIP_GROUP_TYPES.rail, + chipStyle: CHIP_TYPES.default, +}; + +export default BpkChipGroup; diff --git a/packages/bpk-component-chip-group/src/__snapshots__/BpkBoilerplate-test.tsx.snap b/packages/bpk-component-chip-group/src/__snapshots__/BpkBoilerplate-test.tsx.snap new file mode 100644 index 0000000000..86a6aca630 --- /dev/null +++ b/packages/bpk-component-chip-group/src/__snapshots__/BpkBoilerplate-test.tsx.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BpkBoilerplate should render correctly 1`] = ` + +
+ I am an example component. +
+
+`; + +exports[`BpkBoilerplate should support arbitrary props 1`] = ` + +
+ I am an example component. +
+
+`; + +exports[`BpkBoilerplate should support custom class names 1`] = ` + +
+ I am an example component. +
+
+`; diff --git a/packages/bpk-component-chip-group/src/accessibility-test.tsx b/packages/bpk-component-chip-group/src/accessibility-test.tsx new file mode 100644 index 0000000000..d869ca64ef --- /dev/null +++ b/packages/bpk-component-chip-group/src/accessibility-test.tsx @@ -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. + */ +/* @flow strict */ + +import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; + +import BpkChipGroup from './BpkChipGroup'; + +describe('BpkBoilerplate accessibility tests', () => { + it('should not have programmatically-detectable accessibility issues', async () => { + const { container } = render(); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); +}); From 17d03c7ec625da1aec8a68b40131a650828955b4 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 5 Jan 2024 17:43:24 +0000 Subject: [PATCH 02/79] Create nudger for desktop --- .../src/BpkChipGroup.module.css | 2 +- .../src/BpkChipGroup.module.scss | 3 +- .../src/BpkChipGroup.tsx | 25 +++-- .../src/Nudger.module.scss | 37 +++++++ .../bpk-component-chip-group/src/Nudger.tsx | 96 +++++++++++++++++++ 5 files changed, 147 insertions(+), 16 deletions(-) create mode 100644 packages/bpk-component-chip-group/src/Nudger.module.scss create mode 100644 packages/bpk-component-chip-group/src/Nudger.tsx diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.css b/packages/bpk-component-chip-group/src/BpkChipGroup.module.css index 8f3a642b9d..be658e85d5 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.css +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.css @@ -15,4 +15,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-boilerplate{display:flex} +@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-container{display:flex;white-space:nowrap}.bpk-chip-group{display:flex;gap:0.5rem}.bpk-chip-group--rail{flex-wrap:nowrap}.bpk-chip-group--wrap{flex-wrap:wrap}.bpk-sticky-chip{padding-inline-end:0.5rem;margin-inline-end:0.5rem;box-shadow:-1px 0 0 0 #c2c9cd inset}html[dir='rtl'] .bpk-sticky-chip{box-shadow:1px 0 0 0 #c2c9cd inset}.bpk-sticky-chip--on-dark{box-shadow:-1px 0 0 0 rgba(255,255,255,0.5) inset}html[dir='rtl'] .bpk-sticky-chip--on-dark{box-shadow:1px 0 0 0 rgba(255,255,255,0.5) inset} diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss index 38b9926710..ca47ef088f 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss @@ -16,10 +16,11 @@ * limitations under the License. */ -@import '~bpk-mixins/index.scss'; +@import '../../bpk-mixins/index.scss'; .bpk-chip-group-container { display: flex; + align-items: center; white-space: nowrap; } diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx index 3fbd8ce2cf..50a6b766ac 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -19,9 +19,11 @@ import { cssModules } from '../../bpk-react-utils'; import STYLES from './BpkChipGroup.module.scss'; -import { ReactElement, SyntheticEvent, useCallback, useState } from 'react'; +import { ReactElement, SyntheticEvent, useCallback, useRef, useState } from 'react'; import BpkSelectableChip, { CHIP_TYPES } from '../../bpk-component-chip'; import BpkMobileScrollContainer from '../../bpk-component-mobile-scroll-container'; +import { BpkButtonV2, BUTTON_TYPES } from '../../bpk-component-button'; +import { Nudger } from './Nudger'; const getClassName = cssModules(STYLES); @@ -30,6 +32,7 @@ export const CHIP_GROUP_TYPES = { wrap: 'wrap', } export type ChipGroupType = (typeof CHIP_GROUP_TYPES)[keyof typeof CHIP_GROUP_TYPES]; +export type ChipStyleType = (typeof CHIP_TYPES)[keyof typeof CHIP_TYPES]; export type ChipItem = { text: string; @@ -45,7 +48,7 @@ type Props = { // children: Node; type: ChipGroupType; chips: ChipItem[]; - chipStyle: (typeof CHIP_TYPES)[keyof typeof CHIP_TYPES]; + chipStyle: ChipStyleType; stickyChip?: ChipItem; onClick?: (selected: boolean[]) => void; }; @@ -53,6 +56,8 @@ type Props = { const BpkChipGroup = ({ className = null, chips, type = CHIP_GROUP_TYPES.rail, chipStyle = CHIP_TYPES.default, onClick, stickyChip }: Props) => { + const scrollContainerRef = useRef(); + const connectedChips = chips.map((chip, index) => ({ ...chip, onClick: (event: SyntheticEvent) => { @@ -102,10 +107,11 @@ const BpkChipGroup = ({ className = null, chips, type = CHIP_GROUP_TYPES.rail, c } const wrapRailInScroll = (children: ReactElement) => - type === CHIP_GROUP_TYPES.rail ? {children} : children; + type === CHIP_GROUP_TYPES.rail ? {scrollContainerRef.current = el}}>{children} : children; return (
+ {type === CHIP_GROUP_TYPES.rail && } {type === CHIP_GROUP_TYPES.rail && stickyChip &&
{renderChipItem(stickyChip)} @@ -116,8 +122,8 @@ const BpkChipGroup = ({ className = null, chips, type = CHIP_GROUP_TYPES.rail, c {connectedChips.map(chip => renderChipItem(chip))}
)} + {type === CHIP_GROUP_TYPES.rail && }
- ); }; @@ -169,20 +175,11 @@ export const BpkChipGroupSingleSelect = ({ chips, onClick, ...rest }: SingleSele }); } }, - })) + })); return ( ); }; - - - -BpkChipGroup.defaultProps = { - className: null, - type: CHIP_GROUP_TYPES.rail, - chipStyle: CHIP_TYPES.default, -}; - export default BpkChipGroup; diff --git a/packages/bpk-component-chip-group/src/Nudger.module.scss b/packages/bpk-component-chip-group/src/Nudger.module.scss new file mode 100644 index 0000000000..4f1ce00b1b --- /dev/null +++ b/packages/bpk-component-chip-group/src/Nudger.module.scss @@ -0,0 +1,37 @@ +/* + * 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 '../../bpk-mixins/index.scss'; + +.bpk-chip-group-nudger { + display: none; + + @include bpk-breakpoint-desktop-only { + display: block; + } + + &--leading { + margin-inline-end: bpk-spacing-md(); + + } + + &--trailing { + margin-inline-start: bpk-spacing-md(); + + } +} diff --git a/packages/bpk-component-chip-group/src/Nudger.tsx b/packages/bpk-component-chip-group/src/Nudger.tsx new file mode 100644 index 0000000000..63edb4b19f --- /dev/null +++ b/packages/bpk-component-chip-group/src/Nudger.tsx @@ -0,0 +1,96 @@ +/* + * 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 { BpkButtonV2, BUTTON_TYPES } from '../../bpk-component-button'; +import { CHIP_GROUP_TYPES, ChipStyleType } from './BpkChipGroup'; +import { CHIP_TYPES } from '../../bpk-component-chip'; +import ArrowLeft from '../../bpk-component-icon/sm/long-arrow-left.js'; +import ArrowRight from '../../bpk-component-icon/sm/long-arrow-right.js'; +import { withButtonAlignment } from '../../bpk-component-icon/index'; + +import STYLES from './Nudger.module.scss'; +import { cssModules, isRTL } from '../../bpk-react-utils/index'; +import { useEffect, useState } from 'react'; +import { debounce } from 'lodash'; + +const getClassName = cssModules(STYLES); + + +const CHIP_STYLE_TO_BUTTON_STYLE = { + [CHIP_TYPES.default]: BUTTON_TYPES.secondary, + [CHIP_TYPES.onDark]: BUTTON_TYPES.secondaryOnDark, + [CHIP_TYPES.onImage]: BUTTON_TYPES.primaryOnDark, +} + + +type Props = { + chipStyle: ChipStyleType, + leading: boolean, +} + + +const AlignedLeftArrowIcon = withButtonAlignment(ArrowLeft); +const AlignedRightArrowIcon = withButtonAlignment(ArrowRight); + +const SCROLL_DISTANCE = 100; + +export const Nudger = ({chipStyle = CHIP_TYPES.default, leading = false, scrollContainerRef}: Props) => { + const classNames = getClassName( + 'bpk-chip-group-nudger', + `bpk-chip-group-nudger-${leading ? "leading" : "trailing"}` + ) + + const [show, setShow] = useState(false); + + const rtl = isRTL(); + const isLeft = (leading && !rtl) || (!leading && rtl); + + useEffect(() => { + const interval = setInterval(() => { + if (!scrollContainerRef.current) { + return; + } + + const { offsetWidth, scrollLeft, scrollWidth } = scrollContainerRef.current || {}; + + const scrollValue = rtl ? -Math.floor(scrollLeft) : Math.ceil(scrollLeft); + const showLeadingIndicator = scrollValue > 0; + const showTrailingIndicator = scrollValue < scrollWidth - offsetWidth; + + setShow((leading && showLeadingIndicator) || (!leading && showTrailingIndicator)) + }, 100); + return () => clearInterval(interval); + }, [leading, rtl]); + + return show && ( + { + scrollContainerRef.current.scrollBy({ + left: isLeft ? -SCROLL_DISTANCE : SCROLL_DISTANCE, + behavior: 'smooth', + }) + }} + > + {isLeft ? : } + + ); +} From 92ec095806246af2272933912d93a6e911acbf79 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Wed, 10 Jan 2024 16:28:28 +0000 Subject: [PATCH 03/79] Refactor chip groups to better match api --- examples/bpk-component-chip-group/examples.js | 90 ++--------- packages/bpk-component-chip-group/README.md | 2 +- packages/bpk-component-chip-group/index.ts | 13 +- .../src/BpkChipGroup.tsx | 144 ++++++------------ .../src/BpkChipGroupSingleSelect.tsx | 42 +++++ .../src/Nudger.module.scss | 1 - .../bpk-component-chip-group/src/Nudger.tsx | 35 +++-- 7 files changed, 137 insertions(+), 190 deletions(-) create mode 100644 packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx diff --git a/examples/bpk-component-chip-group/examples.js b/examples/bpk-component-chip-group/examples.js index bcea6f8635..207545c545 100644 --- a/examples/bpk-component-chip-group/examples.js +++ b/examples/bpk-component-chip-group/examples.js @@ -17,12 +17,8 @@ */ -import BpkSelectableChip from '../../packages/bpk-component-chip'; -import BpkChipGroup, { CHIP_GROUP_TYPES } from '../../packages/bpk-component-chip-group'; -import { - BpkChipGroupMultiSelect, - BpkChipGroupSingleSelect, -} from '../../packages/bpk-component-chip-group/src/BpkChipGroup'; +import { BpkDismissibleChip, BpkDropdownChip } from '../../packages/bpk-component-chip'; +import BpkChipGroup, { BpkChipGroupState, BpkChipGroupSingleSelectState, CHIP_GROUP_TYPES } from '../../packages/bpk-component-chip-group'; import { useState } from 'react'; import FilterIconSm from '../../packages/bpk-component-icon/sm/filter'; @@ -30,63 +26,39 @@ import FilterIconSm from '../../packages/bpk-component-icon/sm/filter'; const chips = [ { text: 'London', - accessibilityLabel: 'London', - onClick: () => {}, }, { text: 'Berlin', - accessibilityLabel: 'Berlin', - onClick: () => {}, }, { text: 'Florence', - accessibilityLabel: 'Florence', - onClick: () => {}, }, { text: 'Stockholm', - accessibilityLabel: 'Stockholm', - onClick: () => {}, }, { text: 'Copenhagen', - accessibilityLabel: 'Copenhagen', - onClick: () => {}, }, { text: 'Salzburg', - accessibilityLabel: 'Salzburg', - onClick: () => {}, }, { text: 'Graz', - accessibilityLabel: 'Graz', - onClick: () => {}, }, { text: 'Lanzarote', - accessibilityLabel: 'Lanzarote', - onClick: () => {}, }, { text: 'Valencia', - accessibilityLabel: 'Valencia', - onClick: () => {}, }, { text: 'Reykjavik', - accessibilityLabel: 'Copenhagen', - onClick: () => {}, }, { text: 'Tallinn', - accessibilityLabel: 'Tallinn', - onClick: () => {}, }, { text: 'Sofia', - accessibilityLabel: 'Sofia', - onClick: () => {}, }, ] @@ -95,39 +67,35 @@ export const BpkChipGroupWrapping = () => { return (
- - +
); }; export const BpkSingleChipGroupWrapping = () => { - // const [selected] - return (
- - +
); }; export const BpkChipGroupRail = () => { - return (
- - + />
); }; @@ -135,50 +103,24 @@ export const BpkChipGroupRail = () => { export const BpkChipGroupSticky = () => { - // const chips = [ - // { - // text: 'London', - // accessibilityLabel: 'London', - // onClick: () => {}, - // }, - // { - // text: 'Berlin', - // accessibilityLabel: 'Berlin', - // onClick: () => {}, - // }, - // { - // text: 'Florence', - // accessibilityLabel: 'Florence', - // onClick: () => {}, - // }, - // { - // text: 'Stockholm', - // accessibilityLabel: 'Stockholm', - // onClick: () => {}, - // }, - // { - // text: 'Copenhagen', - // accessibilityLabel: 'Copenhagen', - // onClick: () => {}, - // }, - // ] - const stickyChip = { text: 'Sort & Filter', - accessibilityLabel: 'Sort & Filter', leadingAccessoryView: - // onClick: () => {}, } return (
- setSelected(selected)} stickyChip={stickyChip} - > - + /> + {/*TODO*/} + {/**/}
); }; diff --git a/packages/bpk-component-chip-group/README.md b/packages/bpk-component-chip-group/README.md index 3d35b690e5..e38f612785 100644 --- a/packages/bpk-component-chip-group/README.md +++ b/packages/bpk-component-chip-group/README.md @@ -1,4 +1,4 @@ -# bpk-component-boilerplate +# bpk-component-chip-group > Backpack example component. diff --git a/packages/bpk-component-chip-group/index.ts b/packages/bpk-component-chip-group/index.ts index 307bb39ebf..e77efd050b 100644 --- a/packages/bpk-component-chip-group/index.ts +++ b/packages/bpk-component-chip-group/index.ts @@ -17,10 +17,17 @@ */ import BpkChipGroup, { - type Props as BpkChipGroupProps, + type ChipGroupProps, + BpkChipGroupState, CHIP_GROUP_TYPES, } from './src/BpkChipGroup'; -export type { BpkChipGroupProps }; -export { CHIP_GROUP_TYPES }; +import { + type SingleSelectProps, + BpkChipGroupSingleSelect, + BpkChipGroupSingleSelectState, +} from './src/BpkChipGroupSingleSelect'; + +export type { ChipGroupProps, SingleSelectProps }; +export { BpkChipGroupState, CHIP_GROUP_TYPES, BpkChipGroupSingleSelect , BpkChipGroupSingleSelectState}; export default BpkChipGroup; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx index 50a6b766ac..5938b87ef4 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -19,11 +19,10 @@ import { cssModules } from '../../bpk-react-utils'; import STYLES from './BpkChipGroup.module.scss'; -import { ReactElement, SyntheticEvent, useCallback, useRef, useState } from 'react'; +import { ReactElement, useRef, useState } from 'react'; import BpkSelectableChip, { CHIP_TYPES } from '../../bpk-component-chip'; import BpkMobileScrollContainer from '../../bpk-component-mobile-scroll-container'; -import { BpkButtonV2, BUTTON_TYPES } from '../../bpk-component-button'; -import { Nudger } from './Nudger'; +import Nudger from './Nudger'; const getClassName = cssModules(STYLES); @@ -34,46 +33,33 @@ export const CHIP_GROUP_TYPES = { export type ChipGroupType = (typeof CHIP_GROUP_TYPES)[keyof typeof CHIP_GROUP_TYPES]; export type ChipStyleType = (typeof CHIP_TYPES)[keyof typeof CHIP_TYPES]; -export type ChipItem = { +export type SingleSelectChipItem = { text: string; - accessibilityLabel: string - selected?: boolean; - onClick?: (selected: boolean, index: number, event: SyntheticEvent) => void; - component?: () => ReactElement; // TODO Component + accessibilityLabel?: string; rest: [string: any]; }; -type Props = { +export type ChipItem = { + component?: () => ReactElement; // TODO Component + onClick?: (selected: boolean, index: number) => void; + selected?: boolean; +} & SingleSelectChipItem; + + +export type CommonProps = { className?: string | null; - // children: Node; type: ChipGroupType; - chips: ChipItem[]; - chipStyle: ChipStyleType; - stickyChip?: ChipItem; - onClick?: (selected: boolean[]) => void; + style?: ChipStyleType; }; +export type ChipGroupProps = { + chips: ChipItem[]; + stickyChip?: ChipItem; +} & CommonProps; - -const BpkChipGroup = ({ className = null, chips, type = CHIP_GROUP_TYPES.rail, chipStyle = CHIP_TYPES.default, onClick, stickyChip }: Props) => { +const BpkChipGroup = ({ className = null, chips, type = CHIP_GROUP_TYPES.rail, style = CHIP_TYPES.default, stickyChip }: ChipGroupProps) => { const scrollContainerRef = useRef(); - const connectedChips = chips.map((chip, index) => ({ - ...chip, - onClick: (event: SyntheticEvent) => { - const nextSelected = chips.map(chip => Boolean(chip.selected)) - nextSelected[index] = !nextSelected[index]; - - if (chip.onClick) { - chip.onClick(nextSelected[index], index, event); - } - if (onClick) { - onClick(nextSelected); - } - }, - })); - - const containerClassnames = getClassName( className, 'bpk-chip-group-container', @@ -86,32 +72,32 @@ const BpkChipGroup = ({ className = null, chips, type = CHIP_GROUP_TYPES.rail, c const stickyChipClassnames = getClassName( 'bpk-sticky-chip', - `bpk-sticky-chip--${chipStyle}`, + `bpk-sticky-chip--${style}`, ); - const renderChipItem = ({text, accessibilityLabel, selected, onClick, component, ...rest}: ChipItem) => { - const Chip = component ?? BpkSelectableChip; - - return ( - {})} - {...rest} - > - {text} - - ); - } + const renderChipItem = ({text, accessibilityLabel, selected, onClick, component: Component = BpkSelectableChip, ...rest}: ChipItem, index: number) => ( + { + if (onClick) { + onClick(!selected, index) + } + }} + {...rest} + > + {text} + + ); const wrapRailInScroll = (children: ReactElement) => type === CHIP_GROUP_TYPES.rail ? {scrollContainerRef.current = el}}>{children} : children; return (
- {type === CHIP_GROUP_TYPES.rail && } + {type === CHIP_GROUP_TYPES.rail && } {type === CHIP_GROUP_TYPES.rail && stickyChip &&
{renderChipItem(stickyChip)} @@ -119,67 +105,35 @@ const BpkChipGroup = ({ className = null, chips, type = CHIP_GROUP_TYPES.rail, c } {wrapRailInScroll(
- {connectedChips.map(chip => renderChipItem(chip))} + {chips.map(renderChipItem)}
)} - {type === CHIP_GROUP_TYPES.rail && } + {type === CHIP_GROUP_TYPES.rail && }
); }; - - -export const BpkChipGroupMultiSelect = ({ chips, onClick, ...rest }: Props) => { - const [selected, setSelected] = useState(chips.map(c => Boolean(c.selected))); +export const BpkChipGroupState = ({ chips, ...rest }: ChipGroupProps) => { + const [selectedChips, setSelectedChips] = useState(chips.map(c => Boolean(c.selected))); const statefulChips = chips.map((chip, index) => ({ ...chip, - selected: selected[index], - })) - - const clickHandler = (selectedChips: boolean[]) => { - setSelected(selectedChips); - if (onClick) { - onClick(selectedChips); - } - } - - return ( - - ); -}; - - -export type SingleSelectProps = { - onClick?: (selected: ChipItem) => void, -} & Props; - - -export const BpkChipGroupSingleSelect = ({ chips, onClick, ...rest }: SingleSelectProps) => { - const [selectedIndex, setSelectedIndex] = useState(chips.findIndex(c => Boolean(c.selected))); - - const connectedChips = chips.map((chip, index) => ({ - ...chip, - selected: index === selectedIndex, - onClick: (selected: boolean, index: number, event: SyntheticEvent) => { - setSelectedIndex(selected ? index : -1); - + selected: selectedChips[index], + onClick: (selected: boolean, selectedIndex: number) => { if (chip.onClick) { - chip.onClick(selected, index, event); + chip.onClick(selected, selectedIndex); } - if (onClick) { - onClick({ - ...chip, - selected, - }); - } + const nextSelectedChips = [...selectedChips] + nextSelectedChips[selectedIndex] = selected; + setSelectedChips(nextSelectedChips); }, - })); + })) return ( - + ); }; + export default BpkChipGroup; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx new file mode 100644 index 0000000000..b0f1dd62aa --- /dev/null +++ b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx @@ -0,0 +1,42 @@ +import { useState } from 'react'; +import BpkChipGroup, { ChipItem, SingleSelectChipItem, CommonProps } from './BpkChipGroup'; + + +export type SingleSelectProps = { + chips: SingleSelectChipItem[]; + selectedIndex?: number; + onItemClick?: (item: SingleSelectChipItem, selected: boolean, index: number) => void, +} & CommonProps; + + +export const BpkChipGroupSingleSelect = ({ chips, onItemClick, selectedIndex, ...rest }: SingleSelectProps) => { + const chipsWithSelection = chips.map((chip, index) => ({ + ...chip, + selected: index === selectedIndex, + onClick: (selected: boolean, clickedIndex: number) => { + if (onItemClick) { + onItemClick(chip, selected, clickedIndex); + } + }, + })); + + return ( + + ); +}; + +export const BpkChipGroupSingleSelectState = ({ selectedIndex: defaultSelectedIndex = -1, onItemClick, ...rest }: SingleSelectProps) => { + const [selectedIndex, setSelectedIndex] = useState(defaultSelectedIndex); + + const onItemClickWithState = (item: ChipItem, selected: boolean, index: number) => { + if (onItemClick) { + onItemClick(item, selected, index); + } + setSelectedIndex(selected ? index : -1); + } + + return +}; + + +export default BpkChipGroupSingleSelect; diff --git a/packages/bpk-component-chip-group/src/Nudger.module.scss b/packages/bpk-component-chip-group/src/Nudger.module.scss index 4f1ce00b1b..cb6011c481 100644 --- a/packages/bpk-component-chip-group/src/Nudger.module.scss +++ b/packages/bpk-component-chip-group/src/Nudger.module.scss @@ -32,6 +32,5 @@ &--trailing { margin-inline-start: bpk-spacing-md(); - } } diff --git a/packages/bpk-component-chip-group/src/Nudger.tsx b/packages/bpk-component-chip-group/src/Nudger.tsx index 63edb4b19f..0df9bcde91 100644 --- a/packages/bpk-component-chip-group/src/Nudger.tsx +++ b/packages/bpk-component-chip-group/src/Nudger.tsx @@ -17,16 +17,15 @@ */ import { BpkButtonV2, BUTTON_TYPES } from '../../bpk-component-button'; -import { CHIP_GROUP_TYPES, ChipStyleType } from './BpkChipGroup'; +import type { ChipStyleType } from './BpkChipGroup'; import { CHIP_TYPES } from '../../bpk-component-chip'; -import ArrowLeft from '../../bpk-component-icon/sm/long-arrow-left.js'; -import ArrowRight from '../../bpk-component-icon/sm/long-arrow-right.js'; +import ArrowLeft from '../../bpk-component-icon/sm/long-arrow-left'; +import ArrowRight from '../../bpk-component-icon/sm/long-arrow-right'; import { withButtonAlignment } from '../../bpk-component-icon/index'; import STYLES from './Nudger.module.scss'; import { cssModules, isRTL } from '../../bpk-react-utils/index'; -import { useEffect, useState } from 'react'; -import { debounce } from 'lodash'; +import { RefObject, useEffect, useState } from 'react'; const getClassName = cssModules(STYLES); @@ -39,8 +38,9 @@ const CHIP_STYLE_TO_BUTTON_STYLE = { type Props = { - chipStyle: ChipStyleType, - leading: boolean, + chipStyle: ChipStyleType; + scrollContainerRef: RefObject; + leading?: boolean; } @@ -49,10 +49,10 @@ const AlignedRightArrowIcon = withButtonAlignment(ArrowRight); const SCROLL_DISTANCE = 100; -export const Nudger = ({chipStyle = CHIP_TYPES.default, leading = false, scrollContainerRef}: Props) => { +const Nudger = ({chipStyle = CHIP_TYPES.default, leading = false, scrollContainerRef}: Props) => { const classNames = getClassName( 'bpk-chip-group-nudger', - `bpk-chip-group-nudger-${leading ? "leading" : "trailing"}` + `bpk-chip-group-nudger--${leading ? "leading" : "trailing"}` ) const [show, setShow] = useState(false); @@ -66,8 +66,7 @@ export const Nudger = ({chipStyle = CHIP_TYPES.default, leading = false, scrollC return; } - const { offsetWidth, scrollLeft, scrollWidth } = scrollContainerRef.current || {}; - + const { offsetWidth, scrollLeft, scrollWidth } = scrollContainerRef.current; const scrollValue = rtl ? -Math.floor(scrollLeft) : Math.ceil(scrollLeft); const showLeadingIndicator = scrollValue > 0; const showTrailingIndicator = scrollValue < scrollWidth - offsetWidth; @@ -75,7 +74,7 @@ export const Nudger = ({chipStyle = CHIP_TYPES.default, leading = false, scrollC setShow((leading && showLeadingIndicator) || (!leading && showTrailingIndicator)) }, 100); return () => clearInterval(interval); - }, [leading, rtl]); + }, [leading, rtl, scrollContainerRef]); return show && ( { - scrollContainerRef.current.scrollBy({ - left: isLeft ? -SCROLL_DISTANCE : SCROLL_DISTANCE, - behavior: 'smooth', - }) + if (scrollContainerRef.current) { + scrollContainerRef.current.scrollBy({ + left: isLeft ? -SCROLL_DISTANCE : SCROLL_DISTANCE, + behavior: 'smooth', + }); + } }} > {isLeft ? : } ); } + +export default Nudger; From 40480ef9ead68e27c9d14a247b98dbec718feb64 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 12 Jan 2024 17:26:58 +0000 Subject: [PATCH 04/79] More examples, support for dismissable chips, tests --- examples/bpk-component-chip-group/examples.js | 126 --------- .../examples.module.css | 18 ++ .../examples.module.scss | 31 +++ .../bpk-component-chip-group/examples.tsx | 250 ++++++++++++++++++ .../{stories.js => stories.ts} | 19 +- packages/bpk-component-chip-group/README.md | 7 + packages/bpk-component-chip-group/index.d.ts | 29 +- packages/bpk-component-chip-group/index.ts | 7 +- .../src/BpkChipGroup-test.tsx | 53 +++- .../src/BpkChipGroup.d.ts | 57 ++-- .../src/BpkChipGroup.module.css | 2 +- .../src/BpkChipGroup.module.scss | 35 ++- .../src/BpkChipGroup.tsx | 80 ++++-- .../src/BpkChipGroupSingleSelect-test.tsx | 18 ++ .../src/BpkChipGroupSingleSelect.d.ts | 10 + .../src/BpkChipGroupSingleSelect.tsx | 23 +- .../bpk-component-chip-group/src/Nudger.d.ts | 9 + .../src/Nudger.module.css | 18 ++ .../src/Nudger.module.scss | 11 +- .../bpk-component-chip-group/src/Nudger.tsx | 19 +- .../src/accessibility-test.tsx | 40 ++- 21 files changed, 623 insertions(+), 239 deletions(-) delete mode 100644 examples/bpk-component-chip-group/examples.js create mode 100644 examples/bpk-component-chip-group/examples.module.css create mode 100644 examples/bpk-component-chip-group/examples.module.scss create mode 100644 examples/bpk-component-chip-group/examples.tsx rename examples/bpk-component-chip-group/{stories.js => stories.ts} (67%) create mode 100644 packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx create mode 100644 packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.d.ts create mode 100644 packages/bpk-component-chip-group/src/Nudger.d.ts create mode 100644 packages/bpk-component-chip-group/src/Nudger.module.css diff --git a/examples/bpk-component-chip-group/examples.js b/examples/bpk-component-chip-group/examples.js deleted file mode 100644 index 207545c545..0000000000 --- a/examples/bpk-component-chip-group/examples.js +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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 { BpkDismissibleChip, BpkDropdownChip } from '../../packages/bpk-component-chip'; -import BpkChipGroup, { BpkChipGroupState, BpkChipGroupSingleSelectState, CHIP_GROUP_TYPES } from '../../packages/bpk-component-chip-group'; -import { useState } from 'react'; -import FilterIconSm from '../../packages/bpk-component-icon/sm/filter'; - - -const chips = [ - { - text: 'London', - }, - { - text: 'Berlin', - }, - { - text: 'Florence', - }, - { - text: 'Stockholm', - }, - { - text: 'Copenhagen', - }, - { - text: 'Salzburg', - }, - { - text: 'Graz', - }, - { - text: 'Lanzarote', - }, - { - text: 'Valencia', - }, - { - text: 'Reykjavik', - }, - { - text: 'Tallinn', - }, - { - text: 'Sofia', - }, -] - - -export const BpkChipGroupWrapping = () => { - - return ( -
- - -
- ); -}; - -export const BpkSingleChipGroupWrapping = () => { - return ( -
- - -
- ); -}; - - -export const BpkChipGroupRail = () => { - return ( -
- -
- ); -}; - - -export const BpkChipGroupSticky = () => { - - const stickyChip = { - text: 'Sort & Filter', - leadingAccessoryView: - } - - return ( -
- - {/*TODO*/} - {/**/} -
- ); -}; diff --git a/examples/bpk-component-chip-group/examples.module.css b/examples/bpk-component-chip-group/examples.module.css new file mode 100644 index 0000000000..7e080efccc --- /dev/null +++ b/examples/bpk-component-chip-group/examples.module.css @@ -0,0 +1,18 @@ +/* + * 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. +*/ +@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-examples__fixed-width{width:18.75rem}.bpk-chip-group-examples__contrast{padding:1rem;background-color:#eff1f2}.bpk-chip-group-examples__dark{padding:1rem;background-color:#05203c}.bpk-chip-group-examples__image{padding:1rem;background-image:url("https://content.skyscnr.com/96508dbac15a2895b0147dc7e7f9ad30/canadian-rockies-canada.jpg")}.bpk-chip-group-examples__mixed-container h2{margin-top:2rem;margin-bottom:0.5rem} diff --git a/examples/bpk-component-chip-group/examples.module.scss b/examples/bpk-component-chip-group/examples.module.scss new file mode 100644 index 0000000000..8e25f65437 --- /dev/null +++ b/examples/bpk-component-chip-group/examples.module.scss @@ -0,0 +1,31 @@ + +@import '../../packages/bpk-mixins/index.scss'; + +.bpk-chip-group-examples { + &__fixed-width { + width: 300 * $bpk-one-pixel-rem; + } + + &__contrast { + padding: bpk-spacing-base(); + background-color: $bpk-canvas-contrast-day; + } + + &__dark { + padding: bpk-spacing-base(); + background-color: $bpk-surface-contrast-day; + } + + &__image { + padding: bpk-spacing-base(); + background-image: url('https://content.skyscnr.com/96508dbac15a2895b0147dc7e7f9ad30/canadian-rockies-canada.jpg'); + } + + &__mixed-container { + h2 { + margin-top: bpk-spacing-xl(); + margin-bottom: bpk-spacing-md(); + } + } +} + diff --git a/examples/bpk-component-chip-group/examples.tsx b/examples/bpk-component-chip-group/examples.tsx new file mode 100644 index 0000000000..b2a33ed237 --- /dev/null +++ b/examples/bpk-component-chip-group/examples.tsx @@ -0,0 +1,250 @@ +/* + * 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 { BpkDismissibleChip, BpkDropdownChip, CHIP_TYPES } from '../../packages/bpk-component-chip'; +import { BpkChipGroupState, BpkChipGroupSingleSelectState, CHIP_GROUP_TYPES } from '../../packages/bpk-component-chip-group'; +import { useState } from 'react'; + +import STYLES from './examples.module.scss'; +import BpkText, { TEXT_STYLES } from '../../packages/bpk-component-text/index'; +import { cssModules } from '../../packages/bpk-react-utils/index'; + +const getClassName = cssModules(STYLES); + +const chips = [ + { + text: 'London', + }, + { + text: 'Berlin', + selected: true, + }, + { + text: 'Florence', + }, + { + text: 'Stockholm', + }, + { + text: 'Copenhagen', + }, + { + text: 'Salzburg', + }, + { + text: 'Graz', + }, + { + text: 'Lanzarote', + }, + { + text: 'Valencia', + }, + { + text: 'Reykjavik', + }, + { + text: 'Tallinn', + }, + { + text: 'Sofia', + }, +]; + + +export const BpkChipGroupWrapping = () => { + + return ( +
+ + +
+ ); +}; + +export const BpkSingleChipGroupWrapping = () => { + return ( +
+ + +
+ ); +}; + + +export const BpkChipGroupRail = () => { + return ( +
+ +
+ ); +}; + + +export const BpkChipGroupSticky = () => { + const stickyChip = { + text: 'Sort & Filter', + // component: BpkDropdownChip + } + + return ( +
+ +
+ ); +}; + +export const OnContrastChipGroup = () => { + const stickyChip = { + text: 'Sort & Filter', + } + + return ( +
+ +
+ ); +}; + + +export const OnDarkChipGroup = () => { + const stickyChip = { + text: 'Sort & Filter', + } + + return ( +
+ +
+ ); +}; + +export const OnImageChipGroup = () => { + const stickyChip = { + text: 'Sort & Filter', + } + + return ( +
+ +
+ ); +}; + + +export const AllChipTypesGroup = () => { + const [dismissed, setDismissed] = useState(false); + + const allChips = [ + { + text: 'Disabled', + disabled: true, + }, + !dismissed && { + text: 'Dismissable', + onClick: () => setDismissed(true), + component: BpkDismissibleChip, + }, + { + text: 'Dropdown', + component: BpkDropdownChip, + }, + { + text: 'Selectable', + }, + { + text: 'Initially selected', + selected: true, + }, + ]; + + return ( + + ); +}; + +export const MixedExample = () => ( +
+ + Rail + + + + Rail with sticky chip + + + + On Contrast + + + + On Dark + + + + On Image + + + + Wrapped + + + + All chip types + + + + Single Select Group + + +
+
+) diff --git a/examples/bpk-component-chip-group/stories.js b/examples/bpk-component-chip-group/stories.ts similarity index 67% rename from examples/bpk-component-chip-group/stories.js rename to examples/bpk-component-chip-group/stories.ts index ea6fa8a015..10418c2bd6 100644 --- a/examples/bpk-component-chip-group/stories.js +++ b/examples/bpk-component-chip-group/stories.ts @@ -16,12 +16,16 @@ * limitations under the License. */ -/* @flow strict */ - import { BpkChipGroupRail, BpkChipGroupWrapping, - BpkChipGroupSticky, BpkSingleChipGroupWrapping, + BpkChipGroupSticky, + BpkSingleChipGroupWrapping, + OnDarkChipGroup, + OnImageChipGroup, + MixedExample, + AllChipTypesGroup, + OnContrastChipGroup, } from './examples'; export default { @@ -32,3 +36,12 @@ export const WrappedChipGroup = BpkChipGroupWrapping; export const SingleSelectChipGroup = BpkSingleChipGroupWrapping; export const RailChipGroup = BpkChipGroupRail; export const StickyChipGroup = BpkChipGroupSticky; +export const OnContrast = OnContrastChipGroup; +export const OnDark = OnDarkChipGroup; +export const OnImage = OnImageChipGroup; +export const AllChipTypes = AllChipTypesGroup; +export const VisualTest = MixedExample; +export const VisualTestWithZoom = VisualTest.bind({}); +VisualTestWithZoom.args = { + zoomEnabled: true +}; diff --git a/packages/bpk-component-chip-group/README.md b/packages/bpk-component-chip-group/README.md index e38f612785..32466007c2 100644 --- a/packages/bpk-component-chip-group/README.md +++ b/packages/bpk-component-chip-group/README.md @@ -14,6 +14,13 @@ import BpkBoilerplate from '@skyscanner/backpack-web/bpk-component-code'; export default () => ; ``` +### TODO + +- Fix onImage with rail type shadows being cut off. +- Add IconChip the BpkChip? +- Tests +- Type files + ## Props | Property | PropType | Required | Default Value | diff --git a/packages/bpk-component-chip-group/index.d.ts b/packages/bpk-component-chip-group/index.d.ts index 6bcbf81b89..a1662e7760 100644 --- a/packages/bpk-component-chip-group/index.d.ts +++ b/packages/bpk-component-chip-group/index.d.ts @@ -1,24 +1,5 @@ -/* - * Backpack - Skyscanner's Design System - * - * Copyright 2022 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 BpkBoilerplate, { - type Props as BpkBoilerplateProps, -} from './src/BpkBoilerplate'; - -export type { BpkBoilerplateProps }; -export default BpkBoilerplate; +import BpkChipGroup, { type ChipGroupProps, BpkChipGroupState, CHIP_GROUP_TYPES, ChipItem, SingleSelectChipItem } from './src/BpkChipGroup'; +import BpkChipGroupSingleSelect, { type SingleSelectProps, BpkChipGroupSingleSelectState } from './src/BpkChipGroupSingleSelect'; +export type { ChipGroupProps, SingleSelectProps, ChipItem, SingleSelectChipItem }; +export { BpkChipGroupState, CHIP_GROUP_TYPES, BpkChipGroupSingleSelect, BpkChipGroupSingleSelectState }; +export default BpkChipGroup; diff --git a/packages/bpk-component-chip-group/index.ts b/packages/bpk-component-chip-group/index.ts index e77efd050b..c0a019cc86 100644 --- a/packages/bpk-component-chip-group/index.ts +++ b/packages/bpk-component-chip-group/index.ts @@ -20,14 +20,15 @@ import BpkChipGroup, { type ChipGroupProps, BpkChipGroupState, CHIP_GROUP_TYPES, + ChipItem, + SingleSelectChipItem, } from './src/BpkChipGroup'; -import { +import BpkChipGroupSingleSelect, { type SingleSelectProps, - BpkChipGroupSingleSelect, BpkChipGroupSingleSelectState, } from './src/BpkChipGroupSingleSelect'; -export type { ChipGroupProps, SingleSelectProps }; +export type { ChipGroupProps, SingleSelectProps, ChipItem, SingleSelectChipItem }; export { BpkChipGroupState, CHIP_GROUP_TYPES, BpkChipGroupSingleSelect , BpkChipGroupSingleSelectState}; export default BpkChipGroup; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx index 45dd18b5c7..f73b77dbd4 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx @@ -19,23 +19,58 @@ import { render } from '@testing-library/react'; -import BpkChipGroup from './BpkChipGroup'; +import BpkChipGroup, { CHIP_GROUP_TYPES } from './BpkChipGroup'; -describe('BpkBoilerplate', () => { - it('should render correctly', () => { - const { asFragment } = render(); +const chips = [ + { + text: 'London', + }, + { + text: 'Berlin', + selected: true, + }, + { + text: 'Florence', + }, + { + text: 'Stockholm', + } +]; + +describe('BpkChipGroup', () => { + it('should render correctly with type = rail', () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); + }); + + it('should render correctly with type = wrap', () => { + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); it('should support custom class names', () => { const { asFragment } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); }); - it('should support arbitrary props', () => { - const { asFragment } = render(); - expect(asFragment()).toMatchSnapshot(); - }); + // it('should support arbitrary props', () => { + // const { asFragment } = render( + // + // ); + // expect(asFragment()).toMatchSnapshot(); + // }); +}); + +describe('BpkChipGroupState', () => { + // TODO }); diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts b/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts index be5fe90b0e..9cbc95132d 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts @@ -1,29 +1,32 @@ -/* - * Backpack - Skyscanner's Design System - * - * Copyright 2022 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. - */ - -export type Props = { - className?: string | null; - [rest: string]: any; +import { ReactElement, ReactNode } from 'react'; +import { type BpkSelectableChipProps, CHIP_TYPES } from '../../bpk-component-chip'; +export declare const CHIP_GROUP_TYPES: { + rail: string; + wrap: string; }; -declare const BpkBoilerplate: { - ({ className, ...rest }: Props): JSX.Element; - defaultProps: { - className: null; - }; +export type ChipGroupType = (typeof CHIP_GROUP_TYPES)[keyof typeof CHIP_GROUP_TYPES]; +export type ChipStyleType = (typeof CHIP_TYPES)[keyof typeof CHIP_TYPES]; +export type SingleSelectChipItem = { + text: string; + accessibilityLabel?: string; + leadingAccessoryView?: ReactNode; + className?: string; + [rest: string]: any; }; -export default BpkBoilerplate; +export type ChipItem = { + component?: (props: BpkSelectableChipProps) => ReactElement; + onClick?: (selected: boolean, index: number) => void; + selected?: boolean; +} & SingleSelectChipItem; +export type CommonProps = { + type: ChipGroupType; + className?: string | null; + style?: ChipStyleType; +}; +export type ChipGroupProps = { + chips: ChipItem[]; + stickyChip?: ChipItem; +} & CommonProps; +declare const BpkChipGroup: ({ className, chips, type, style, stickyChip }: ChipGroupProps) => JSX.Element; +export declare const BpkChipGroupState: ({ chips, ...rest }: ChipGroupProps) => JSX.Element; +export default BpkChipGroup; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.css b/packages/bpk-component-chip-group/src/BpkChipGroup.module.css index be658e85d5..2b607ef3dd 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.css +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.css @@ -15,4 +15,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-container{display:flex;white-space:nowrap}.bpk-chip-group{display:flex;gap:0.5rem}.bpk-chip-group--rail{flex-wrap:nowrap}.bpk-chip-group--wrap{flex-wrap:wrap}.bpk-sticky-chip{padding-inline-end:0.5rem;margin-inline-end:0.5rem;box-shadow:-1px 0 0 0 #c2c9cd inset}html[dir='rtl'] .bpk-sticky-chip{box-shadow:1px 0 0 0 #c2c9cd inset}.bpk-sticky-chip--on-dark{box-shadow:-1px 0 0 0 rgba(255,255,255,0.5) inset}html[dir='rtl'] .bpk-sticky-chip--on-dark{box-shadow:1px 0 0 0 rgba(255,255,255,0.5) inset} +@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-container{display:flex;align-items:center;white-space:nowrap}.bpk-chip-group{display:flex;gap:0.5rem}.bpk-chip-group--rail{flex-wrap:nowrap}.bpk-chip-group--wrap{flex-wrap:wrap}.bpk-sticky-chip-container{padding-inline-end:0.5rem;margin-inline-end:0.5rem;box-shadow:-1px 0 0 0 #c2c9cd inset}html[dir='rtl'] .bpk-sticky-chip-container{box-shadow:1px 0 0 0 #c2c9cd inset}.bpk-sticky-chip-container--on-dark{box-shadow:-1px 0 0 0 rgba(255,255,255,0.5) inset}html[dir='rtl'] .bpk-sticky-chip-container--on-dark{box-shadow:1px 0 0 0 rgba(255,255,255,0.5) inset}@media (max-width: 64rem){.bpk-sticky-chip{padding-inline-start:0.5rem;padding-inline-end:0}.bpk-sticky-chip span:not(:nth-of-type(1)){display:none}}.bpk-chip-group-scroller-left-indicator--on-dark::before{position:absolute;top:0;bottom:0;content:' ';display:block;z-index:1;width:2rem;background-image:linear-gradient(90deg, #05203c, rgba(5,32,60,0));pointer-events:none;left:0}.bpk-chip-group-scroller-left-indicator--on-image::before{display:none}.bpk-chip-group-scroller-right-indicator--on-dark::after{position:absolute;top:0;bottom:0;content:' ';display:block;z-index:1;width:2rem;background-image:linear-gradient(270deg, #05203c, rgba(5,32,60,0));pointer-events:none;right:0}.bpk-chip-group-scroller-right-indicator--on-image::after{display:none} diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss index ca47ef088f..cf9ea21c09 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss @@ -37,7 +37,7 @@ } } -.bpk-sticky-chip { +.bpk-sticky-chip-container { padding-inline-end: bpk-spacing-md(); margin-inline-end: bpk-spacing-md(); @include bpk-border-right-sm($bpk-line-day); @@ -54,3 +54,36 @@ } } } + +@include bpk-breakpoint-tablet { + // TODO: Should this be a new chip component type without text? + .bpk-sticky-chip { + padding-inline-start: bpk-spacing-md(); + padding-inline-end: 0; + + // TODO: better way to do this? Hide all contents of the chip except the leading icon. + span:not(:nth-of-type(1)) { + display: none; + } + } +} + +.bpk-chip-group-scroller-left-indicator { + &--on-dark { + @include bpk-scroll-indicator-left($bpk-surface-contrast-day); + } + + &--on-image::before { + display: none; + } +} + +.bpk-chip-group-scroller-right-indicator { + &--on-dark { + @include bpk-scroll-indicator-right($bpk-surface-contrast-day); + } + + &--on-image::after { + display: none; + } +} diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx index 5938b87ef4..5ec7cf058e 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -16,13 +16,16 @@ * limitations under the License. */ -import { cssModules } from '../../bpk-react-utils'; - +import { cssModules, isRTL } from '../../bpk-react-utils'; import STYLES from './BpkChipGroup.module.scss'; -import { ReactElement, useRef, useState } from 'react'; -import BpkSelectableChip, { CHIP_TYPES } from '../../bpk-component-chip'; +import { ReactElement, ReactNode, useRef, useState } from 'react'; +import BpkSelectableChip, { type BpkSelectableChipProps, CHIP_TYPES } from '../../bpk-component-chip'; +// @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import BpkMobileScrollContainer from '../../bpk-component-mobile-scroll-container'; import Nudger from './Nudger'; +// @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. +import FilterIconSm from '../../../packages/bpk-component-icon/sm/filter'; +import BpkBreakpoint, { BREAKPOINTS } from '../../bpk-component-breakpoint'; const getClassName = cssModules(STYLES); @@ -36,19 +39,20 @@ export type ChipStyleType = (typeof CHIP_TYPES)[keyof typeof CHIP_TYPES]; export type SingleSelectChipItem = { text: string; accessibilityLabel?: string; - rest: [string: any]; + leadingAccessoryView?: ReactNode; + className?: string; + [rest: string]: any; // Inexact rest. See decisions/inexact-rest.md }; export type ChipItem = { - component?: () => ReactElement; // TODO Component + component?: (props: BpkSelectableChipProps) => ReactElement; onClick?: (selected: boolean, index: number) => void; selected?: boolean; } & SingleSelectChipItem; - export type CommonProps = { - className?: string | null; type: ChipGroupType; + className?: string | null; style?: ChipStyleType; }; @@ -58,7 +62,7 @@ export type ChipGroupProps = { } & CommonProps; const BpkChipGroup = ({ className = null, chips, type = CHIP_GROUP_TYPES.rail, style = CHIP_TYPES.default, stickyChip }: ChipGroupProps) => { - const scrollContainerRef = useRef(); + const scrollContainerRef = useRef(null); const containerClassnames = getClassName( className, @@ -70,9 +74,24 @@ const BpkChipGroup = ({ className = null, chips, type = CHIP_GROUP_TYPES.rail, s `bpk-chip-group--${type}`, ); + const stickyChipContainerClassnames = getClassName( + 'bpk-sticky-chip-container', + `bpk-sticky-chip-container--${style}`, + ); + const stickyChipClassnames = getClassName( 'bpk-sticky-chip', - `bpk-sticky-chip--${style}`, + stickyChip && stickyChip.className, + ); + + const scrollContainerLeftIndicator = getClassName( + 'bpk-chip-group-scroller-left-indicator', + `bpk-chip-group-scroller-left-indicator--${style}`, + ); + + const scrollContainerRightIndicator = getClassName( + 'bpk-chip-group-scroller-right-indicator', + `bpk-chip-group-scroller-right-indicator--${style}`, ); const renderChipItem = ({text, accessibilityLabel, selected, onClick, component: Component = BpkSelectableChip, ...rest}: ChipItem, index: number) => ( @@ -83,7 +102,7 @@ const BpkChipGroup = ({ className = null, chips, type = CHIP_GROUP_TYPES.rail, s accessibilityLabel={accessibilityLabel || text} onClick={() => { if (onClick) { - onClick(!selected, index) + onClick(!selected, index); } }} {...rest} @@ -91,24 +110,43 @@ const BpkChipGroup = ({ className = null, chips, type = CHIP_GROUP_TYPES.rail, s {text} ); - const wrapRailInScroll = (children: ReactElement) => - type === CHIP_GROUP_TYPES.rail ? {scrollContainerRef.current = el}}>{children} : children; + type === CHIP_GROUP_TYPES.rail ? ( + {scrollContainerRef.current = el}} + leadingIndicatorClassName={isRTL() ? scrollContainerRightIndicator : scrollContainerLeftIndicator} + trailingIndicatorClassName={isRTL() ? scrollContainerLeftIndicator : scrollContainerRightIndicator} + > + {children} + + ) : children; return (
- {type === CHIP_GROUP_TYPES.rail && } + {type === CHIP_GROUP_TYPES.rail && ( + + + + )} {type === CHIP_GROUP_TYPES.rail && stickyChip && -
- {renderChipItem(stickyChip)} +
+ {renderChipItem({ + ...stickyChip, + leadingAccessoryView: , + className: stickyChipClassnames, + }, -1)}
} {wrapRailInScroll(
- {chips.map(renderChipItem)} + {chips.map((chip, index) => chip && renderChipItem(chip, index))}
)} - {type === CHIP_GROUP_TYPES.rail && } + {type === CHIP_GROUP_TYPES.rail && ( + + + + )}
); }; @@ -116,7 +154,7 @@ const BpkChipGroup = ({ className = null, chips, type = CHIP_GROUP_TYPES.rail, s export const BpkChipGroupState = ({ chips, ...rest }: ChipGroupProps) => { const [selectedChips, setSelectedChips] = useState(chips.map(c => Boolean(c.selected))); - const statefulChips = chips.map((chip, index) => ({ + const statefulChips = chips.map((chip, index) => chip && ({ ...chip, selected: selectedChips[index], onClick: (selected: boolean, selectedIndex: number) => { @@ -130,9 +168,7 @@ export const BpkChipGroupState = ({ chips, ...rest }: ChipGroupProps) => { }, })) - return ( - - ); + return }; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx new file mode 100644 index 0000000000..3de2d560cd --- /dev/null +++ b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx @@ -0,0 +1,18 @@ +/* + * 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. + */ + diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.d.ts b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.d.ts new file mode 100644 index 0000000000..0437aad3a7 --- /dev/null +++ b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.d.ts @@ -0,0 +1,10 @@ +/// +import { SingleSelectChipItem, CommonProps } from './BpkChipGroup'; +export type SingleSelectProps = { + chips: SingleSelectChipItem[]; + selectedIndex?: number; + onItemClick?: (item: SingleSelectChipItem, selected: boolean, index: number) => void; +} & CommonProps; +declare const BpkChipGroupSingleSelect: ({ chips, onItemClick, selectedIndex, ...rest }: SingleSelectProps) => JSX.Element; +export declare const BpkChipGroupSingleSelectState: ({ selectedIndex: defaultSelectedIndex, onItemClick, ...rest }: SingleSelectProps) => JSX.Element; +export default BpkChipGroupSingleSelect; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx index b0f1dd62aa..f50e42289c 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx @@ -1,7 +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 { useState } from 'react'; import BpkChipGroup, { ChipItem, SingleSelectChipItem, CommonProps } from './BpkChipGroup'; - export type SingleSelectProps = { chips: SingleSelectChipItem[]; selectedIndex?: number; @@ -9,8 +26,8 @@ export type SingleSelectProps = { } & CommonProps; -export const BpkChipGroupSingleSelect = ({ chips, onItemClick, selectedIndex, ...rest }: SingleSelectProps) => { - const chipsWithSelection = chips.map((chip, index) => ({ +const BpkChipGroupSingleSelect = ({ chips, onItemClick, selectedIndex, ...rest }: SingleSelectProps) => { + const chipsWithSelection = chips.map((chip, index) => chip && ({ ...chip, selected: index === selectedIndex, onClick: (selected: boolean, clickedIndex: number) => { diff --git a/packages/bpk-component-chip-group/src/Nudger.d.ts b/packages/bpk-component-chip-group/src/Nudger.d.ts new file mode 100644 index 0000000000..b758012a3d --- /dev/null +++ b/packages/bpk-component-chip-group/src/Nudger.d.ts @@ -0,0 +1,9 @@ +import type { ChipStyleType } from './BpkChipGroup'; +import { MutableRefObject } from 'react'; +type Props = { + chipStyle: ChipStyleType; + scrollContainerRef: MutableRefObject; + leading?: boolean; +}; +declare const Nudger: ({ chipStyle, leading, scrollContainerRef }: Props) => JSX.Element; +export default Nudger; diff --git a/packages/bpk-component-chip-group/src/Nudger.module.css b/packages/bpk-component-chip-group/src/Nudger.module.css new file mode 100644 index 0000000000..7125761eb9 --- /dev/null +++ b/packages/bpk-component-chip-group/src/Nudger.module.css @@ -0,0 +1,18 @@ +/* + * 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. +*/ +@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-nudger--leading{margin-inline-end:0.5rem}.bpk-chip-group-nudger--trailing{margin-inline-start:0.5rem}.bpk-chip-group-nudger--hidden{display:none} diff --git a/packages/bpk-component-chip-group/src/Nudger.module.scss b/packages/bpk-component-chip-group/src/Nudger.module.scss index cb6011c481..efff66982a 100644 --- a/packages/bpk-component-chip-group/src/Nudger.module.scss +++ b/packages/bpk-component-chip-group/src/Nudger.module.scss @@ -19,18 +19,15 @@ @import '../../bpk-mixins/index.scss'; .bpk-chip-group-nudger { - display: none; - - @include bpk-breakpoint-desktop-only { - display: block; - } - &--leading { margin-inline-end: bpk-spacing-md(); - } &--trailing { margin-inline-start: bpk-spacing-md(); } + + &--hidden { + display: none; + } } diff --git a/packages/bpk-component-chip-group/src/Nudger.tsx b/packages/bpk-component-chip-group/src/Nudger.tsx index 0df9bcde91..04a9953700 100644 --- a/packages/bpk-component-chip-group/src/Nudger.tsx +++ b/packages/bpk-component-chip-group/src/Nudger.tsx @@ -19,13 +19,15 @@ import { BpkButtonV2, BUTTON_TYPES } from '../../bpk-component-button'; import type { ChipStyleType } from './BpkChipGroup'; import { CHIP_TYPES } from '../../bpk-component-chip'; +// @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import ArrowLeft from '../../bpk-component-icon/sm/long-arrow-left'; +// @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import ArrowRight from '../../bpk-component-icon/sm/long-arrow-right'; import { withButtonAlignment } from '../../bpk-component-icon/index'; import STYLES from './Nudger.module.scss'; import { cssModules, isRTL } from '../../bpk-react-utils/index'; -import { RefObject, useEffect, useState } from 'react'; +import { MutableRefObject, useEffect, useState } from 'react'; const getClassName = cssModules(STYLES); @@ -39,7 +41,7 @@ const CHIP_STYLE_TO_BUTTON_STYLE = { type Props = { chipStyle: ChipStyleType; - scrollContainerRef: RefObject; + scrollContainerRef: MutableRefObject; leading?: boolean; } @@ -50,11 +52,6 @@ const AlignedRightArrowIcon = withButtonAlignment(ArrowRight); const SCROLL_DISTANCE = 100; const Nudger = ({chipStyle = CHIP_TYPES.default, leading = false, scrollContainerRef}: Props) => { - const classNames = getClassName( - 'bpk-chip-group-nudger', - `bpk-chip-group-nudger--${leading ? "leading" : "trailing"}` - ) - const [show, setShow] = useState(false); const rtl = isRTL(); @@ -76,7 +73,13 @@ const Nudger = ({chipStyle = CHIP_TYPES.default, leading = false, scrollContaine return () => clearInterval(interval); }, [leading, rtl, scrollContainerRef]); - return show && ( + const classNames = getClassName( + 'bpk-chip-group-nudger', + `bpk-chip-group-nudger--${leading ? "leading" : "trailing"}`, + !show && `bpk-chip-group-nudger--hidden`, + ) + + return ( { - it('should not have programmatically-detectable accessibility issues', async () => { - const { container } = render(); +const chips = [ + { + text: 'London', + }, + { + text: 'Berlin', + selected: true, + }, + { + text: 'Florence', + }, + { + text: 'Stockholm', + disabled: true, + } +]; + +describe('BpkChipGroup accessibility tests', () => { + it('should not have programmatically-detectable accessibility issues when type = rail', async () => { + const { container } = render( + ); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('should not have programmatically-detectable accessibility issues when type = wrap', async () => { + const { container } = render( + ); const results = await axe(container); expect(results).toHaveNoViolations(); }); From 50ffcb48b20a7e4134d5cdbe84454fbcf95d5996 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Thu, 25 Jan 2024 16:18:31 +0000 Subject: [PATCH 05/79] More WIP, Update docs, add tests, add a11y roles, add TODOs --- .../examples.module.scss | 18 +- .../bpk-component-chip-group/examples.tsx | 34 +- examples/bpk-component-chip-group/stories.ts | 9 + packages/bpk-component-chip-group/README.md | 7 - packages/bpk-component-chip-group/index.ts | 5 +- .../src/BpkChipGroup-test.tsx | 89 +++- .../src/BpkChipGroup.module.scss | 5 +- .../src/BpkChipGroup.tsx | 34 +- .../src/BpkChipGroupSingleSelect-test.tsx | 96 ++++ .../src/BpkChipGroupSingleSelect.tsx | 7 +- .../src/Nudger-test.tsx | 26 ++ .../bpk-component-chip-group/src/Nudger.tsx | 10 +- .../BpkBoilerplate-test.tsx.snap | 32 -- .../__snapshots__/BpkChipGroup-test.tsx.snap | 414 ++++++++++++++++++ .../BpkChipGroupSingleSelect-test.tsx.snap | 218 +++++++++ .../src/accessibility-test.tsx | 29 ++ 16 files changed, 945 insertions(+), 88 deletions(-) create mode 100644 packages/bpk-component-chip-group/src/Nudger-test.tsx delete mode 100644 packages/bpk-component-chip-group/src/__snapshots__/BpkBoilerplate-test.tsx.snap create mode 100644 packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap create mode 100644 packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroupSingleSelect-test.tsx.snap diff --git a/examples/bpk-component-chip-group/examples.module.scss b/examples/bpk-component-chip-group/examples.module.scss index 8e25f65437..b7bdab4e41 100644 --- a/examples/bpk-component-chip-group/examples.module.scss +++ b/examples/bpk-component-chip-group/examples.module.scss @@ -1,3 +1,20 @@ +/* + * 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 '../../packages/bpk-mixins/index.scss'; @@ -28,4 +45,3 @@ } } } - diff --git a/examples/bpk-component-chip-group/examples.tsx b/examples/bpk-component-chip-group/examples.tsx index b2a33ed237..cafbc3d336 100644 --- a/examples/bpk-component-chip-group/examples.tsx +++ b/examples/bpk-component-chip-group/examples.tsx @@ -17,14 +17,15 @@ */ -import { BpkDismissibleChip, BpkDropdownChip, CHIP_TYPES } from '../../packages/bpk-component-chip'; -import { BpkChipGroupState, BpkChipGroupSingleSelectState, CHIP_GROUP_TYPES } from '../../packages/bpk-component-chip-group'; import { useState } from 'react'; -import STYLES from './examples.module.scss'; +import { BpkDismissibleChip, BpkDropdownChip, CHIP_TYPES } from '../../packages/bpk-component-chip'; +import { BpkChipGroupState, BpkChipGroupSingleSelectState, CHIP_GROUP_TYPES } from '../../packages/bpk-component-chip-group'; import BpkText, { TEXT_STYLES } from '../../packages/bpk-component-text/index'; import { cssModules } from '../../packages/bpk-react-utils/index'; +import STYLES from './examples.module.scss'; + const getClassName = cssModules(STYLES); const chips = [ @@ -68,43 +69,37 @@ const chips = [ ]; -export const BpkChipGroupWrapping = () => { - - return ( +export const BpkChipGroupWrapping = () => (
- + accessibilityLabel="Select cities" + />
); -}; -export const BpkSingleChipGroupWrapping = () => { - return ( +export const BpkSingleChipGroupWrapping = () => (
- + accessibilityLabel="Select a city" + />
); -}; -export const BpkChipGroupRail = () => { - return ( +export const BpkChipGroupRail = () => (
); -}; export const BpkChipGroupSticky = () => { @@ -119,6 +114,7 @@ export const BpkChipGroupSticky = () => { type={CHIP_GROUP_TYPES.rail} chips={chips} stickyChip={stickyChip} + accessibilityLabel="Select cities" />
); @@ -136,6 +132,7 @@ export const OnContrastChipGroup = () => { chips={chips} stickyChip={stickyChip} style={CHIP_TYPES.default} + accessibilityLabel="Select cities" />
); @@ -154,6 +151,7 @@ export const OnDarkChipGroup = () => { chips={chips} stickyChip={stickyChip} style={CHIP_TYPES.onDark} + accessibilityLabel="Select cities" /> ); @@ -171,6 +169,7 @@ export const OnImageChipGroup = () => { chips={chips} stickyChip={stickyChip} style={CHIP_TYPES.onImage} + accessibilityLabel="Select cities" /> ); @@ -207,6 +206,7 @@ export const AllChipTypesGroup = () => { ); }; diff --git a/examples/bpk-component-chip-group/stories.ts b/examples/bpk-component-chip-group/stories.ts index 10418c2bd6..3a9a922b68 100644 --- a/examples/bpk-component-chip-group/stories.ts +++ b/examples/bpk-component-chip-group/stories.ts @@ -16,6 +16,8 @@ * limitations under the License. */ +import BpkChipGroup, { BpkChipGroupSingleSelect, BpkChipGroupState, BpkChipGroupSingleSelectState } from '../../packages/bpk-component-chip-group'; + import { BpkChipGroupRail, BpkChipGroupWrapping, @@ -30,6 +32,13 @@ import { export default { title: 'bpk-component-chip-group', + component: BpkChipGroup, + subcomponents: { + 'BpkChipGroupSingleSelect': BpkChipGroupSingleSelect, + 'BpkChipGroupState': BpkChipGroupState, + 'BpkChipGroupSingleSelectState': BpkChipGroupSingleSelectState, + // TODO: can we show the shape of ChipItem here? + }, }; export const WrappedChipGroup = BpkChipGroupWrapping; diff --git a/packages/bpk-component-chip-group/README.md b/packages/bpk-component-chip-group/README.md index 32466007c2..e38f612785 100644 --- a/packages/bpk-component-chip-group/README.md +++ b/packages/bpk-component-chip-group/README.md @@ -14,13 +14,6 @@ import BpkBoilerplate from '@skyscanner/backpack-web/bpk-component-code'; export default () => ; ``` -### TODO - -- Fix onImage with rail type shadows being cut off. -- Add IconChip the BpkChip? -- Tests -- Type files - ## Props | Property | PropType | Required | Default Value | diff --git a/packages/bpk-component-chip-group/index.ts b/packages/bpk-component-chip-group/index.ts index c0a019cc86..1f88b81552 100644 --- a/packages/bpk-component-chip-group/index.ts +++ b/packages/bpk-component-chip-group/index.ts @@ -20,10 +20,9 @@ import BpkChipGroup, { type ChipGroupProps, BpkChipGroupState, CHIP_GROUP_TYPES, - ChipItem, - SingleSelectChipItem, + type ChipItem, + type SingleSelectChipItem, } from './src/BpkChipGroup'; - import BpkChipGroupSingleSelect, { type SingleSelectProps, BpkChipGroupSingleSelectState, diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx index f73b77dbd4..90c001e265 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx @@ -15,9 +15,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* @flow strict */ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import BpkSelectableChip, { BpkDismissibleChip, BpkDropdownChip } from '../../bpk-component-chip'; import BpkChipGroup, { CHIP_GROUP_TYPES } from './BpkChipGroup'; @@ -59,16 +61,79 @@ describe('BpkChipGroup', () => { expect(asFragment()).toMatchSnapshot(); }); - // it('should support arbitrary props', () => { - // const { asFragment } = render( - // - // ); - // expect(asFragment()).toMatchSnapshot(); - // }); + it('should render correctly with sticky chip', () => { + const { asFragment } = render( + , + ); + expect(asFragment()).toMatchSnapshot(); + }); + + it('should render correctly with all chip component types', () => { + const alternativeChips = [ + { + text: 'London', + component: BpkDismissibleChip, + }, + { + text: 'Berlin', + component: BpkDropdownChip, + }, + { + text: 'Florence', + component: BpkSelectableChip, + }, + ]; + + const { asFragment } = render( + , + ); + expect(asFragment()).toMatchSnapshot(); + }); + + it('should call onClick property of chip when clicked', async () => { + const user = userEvent.setup(); + + const onClick = jest.fn(); + + render( + , + ); + + await user.click(screen.getByText('Berlin')); + + expect(onClick).toHaveBeenCalledTimes(1); + expect(onClick).toHaveBeenCalledWith(true, 1); + }); + + it('should render nudger when on desktop', () => { + // TODO + }); + + it('should not render nudger when on mobile', () => { + // TODO + }); + }); describe('BpkChipGroupState', () => { diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss index cf9ea21c09..390a25b8e7 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss @@ -38,8 +38,9 @@ } .bpk-sticky-chip-container { - padding-inline-end: bpk-spacing-md(); margin-inline-end: bpk-spacing-md(); + padding-inline-end: bpk-spacing-md(); + @include bpk-border-right-sm($bpk-line-day); @include bpk-rtl { @@ -58,8 +59,8 @@ @include bpk-breakpoint-tablet { // TODO: Should this be a new chip component type without text? .bpk-sticky-chip { - padding-inline-start: bpk-spacing-md(); padding-inline-end: 0; + padding-inline-start: bpk-spacing-md(); // TODO: better way to do this? Hide all contents of the chip except the leading icon. span:not(:nth-of-type(1)) { diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx index 5ec7cf058e..8e018b6926 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -15,18 +15,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import type { ReactElement, ReactNode} from 'react'; +import { useRef, useState } from 'react'; import { cssModules, isRTL } from '../../bpk-react-utils'; -import STYLES from './BpkChipGroup.module.scss'; -import { ReactElement, ReactNode, useRef, useState } from 'react'; import BpkSelectableChip, { type BpkSelectableChipProps, CHIP_TYPES } from '../../bpk-component-chip'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import BpkMobileScrollContainer from '../../bpk-component-mobile-scroll-container'; -import Nudger from './Nudger'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. -import FilterIconSm from '../../../packages/bpk-component-icon/sm/filter'; +import FilterIconSm from '../../bpk-component-icon/sm/filter'; import BpkBreakpoint, { BREAKPOINTS } from '../../bpk-component-breakpoint'; +import Nudger from './Nudger'; +import STYLES from './BpkChipGroup.module.scss'; + const getClassName = cssModules(STYLES); export const CHIP_GROUP_TYPES = { @@ -51,6 +53,8 @@ export type ChipItem = { } & SingleSelectChipItem; export type CommonProps = { + ariaLabel?: string; + ariaLabelledBy?: string; type: ChipGroupType; className?: string | null; style?: ChipStyleType; @@ -59,11 +63,17 @@ export type CommonProps = { export type ChipGroupProps = { chips: ChipItem[]; stickyChip?: ChipItem; + _isSingleSelect?: boolean; // TODO: is there a better way for internal only props? } & CommonProps; -const BpkChipGroup = ({ className = null, chips, type = CHIP_GROUP_TYPES.rail, style = CHIP_TYPES.default, stickyChip }: ChipGroupProps) => { +const BpkChipGroup = ({ _isSingleSelect = false, ariaLabel, ariaLabelledBy, chips, className = null, stickyChip, style = CHIP_TYPES.default, type = CHIP_GROUP_TYPES.rail }: ChipGroupProps) => { const scrollContainerRef = useRef(null); + // TODO: is there a way in typescript to enforce this instead? + if ((!ariaLabel && !ariaLabelledBy) || (ariaLabel && ariaLabelledBy)) { + console.warn('BpkChipGroup: Exactly one of ariaLabel and ariaLabelledBy should be set') + } + const containerClassnames = getClassName( className, 'bpk-chip-group-container', @@ -94,7 +104,7 @@ const BpkChipGroup = ({ className = null, chips, type = CHIP_GROUP_TYPES.rail, s `bpk-chip-group-scroller-right-indicator--${style}`, ); - const renderChipItem = ({text, accessibilityLabel, selected, onClick, component: Component = BpkSelectableChip, ...rest}: ChipItem, index: number) => ( + const renderChipItem = ({ accessibilityLabel, component: Component = BpkSelectableChip, onClick, selected, text, ...rest }: ChipItem, index: number) => ( {text} ); + + // TODO: Fix/remove the scroll indicator when style=CHIP_TYPES.onImage + // TODO: Fix shadows + focus indicators being cutoff when type = rail const wrapRailInScroll = (children: ReactElement) => type === CHIP_GROUP_TYPES.rail ? ( } {wrapRailInScroll( -
+
{chips.map((chip, index) => chip && renderChipItem(chip, index))}
)} diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx index 3de2d560cd..1be7626fb7 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx @@ -16,3 +16,99 @@ * limitations under the License. */ +import '@testing-library/jest-dom'; +import userEvent from '@testing-library/user-event'; +import { render, screen } from '@testing-library/react'; + +import { CHIP_GROUP_TYPES } from './BpkChipGroup'; +import BpkChipGroupSingleSelect from './BpkChipGroupSingleSelect'; + +const chips = [ + { + text: 'London', + }, + { + text: 'Berlin', + }, + { + text: 'Florence', + }, + { + text: 'Stockholm', + } +]; + +describe('BpkChipGroupSingleSelect', () => { + it('should render correctly with type = rail', () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); + }); + + it('should render correctly with type = wrap', () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); + }); + + it('should call onItemClick when a chip is clicked', async () => { + const user = userEvent.setup(); + + const onItemClick = jest.fn(); + + render( + , + ); + + await user.click(screen.getByText('Berlin')); + + expect(onItemClick).toHaveBeenCalledTimes(1); + expect(onItemClick).toHaveBeenCalledWith({ text: 'Berlin' }, true, 1); + }); + + it('Should use selectedIndex prop to determine selected chip', async () => { + const chipsWithSelected = [ + { + text: 'London', + selected: true, + }, + { + text: 'Berlin', + selected: false, + }, + { + text: 'Florence', + selected: true, + }, + ]; + + render( + , + ); + + expect(screen.getByRole('option', { name: 'Berlin' })).toHaveClass('bpk-chip--default-selected') + expect(screen.getByRole('option', { name: 'London' })).not.toHaveClass('bpk-chip--default-selected') + expect(screen.getByRole('option', { name: 'Florence' })).not.toHaveClass('bpk-chip--default-selected') + }); + + it('should support custom class names', () => { + const { asFragment } = render( + , + ); + expect(asFragment()).toMatchSnapshot(); + }); +}); + +describe('BpkChipGroupSingleSelectState', () => { + // TODO +}); diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx index f50e42289c..57097403a5 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx @@ -17,7 +17,8 @@ */ import { useState } from 'react'; -import BpkChipGroup, { ChipItem, SingleSelectChipItem, CommonProps } from './BpkChipGroup'; + +import BpkChipGroup, { type ChipItem, type SingleSelectChipItem, type CommonProps } from './BpkChipGroup'; export type SingleSelectProps = { chips: SingleSelectChipItem[]; @@ -38,11 +39,11 @@ const BpkChipGroupSingleSelect = ({ chips, onItemClick, selectedIndex, ...rest } })); return ( - + ); }; -export const BpkChipGroupSingleSelectState = ({ selectedIndex: defaultSelectedIndex = -1, onItemClick, ...rest }: SingleSelectProps) => { +export const BpkChipGroupSingleSelectState = ({ onItemClick, selectedIndex: defaultSelectedIndex = -1, ...rest }: SingleSelectProps) => { const [selectedIndex, setSelectedIndex] = useState(defaultSelectedIndex); const onItemClickWithState = (item: ChipItem, selected: boolean, index: number) => { diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx new file mode 100644 index 0000000000..def6a0559f --- /dev/null +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -0,0 +1,26 @@ +/* + * 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. + */ + +describe('Nudger', () => { + + it('should render correctly', () => { + // TODO + }); + + // TODO +}); diff --git a/packages/bpk-component-chip-group/src/Nudger.tsx b/packages/bpk-component-chip-group/src/Nudger.tsx index 04a9953700..3c4d453a8e 100644 --- a/packages/bpk-component-chip-group/src/Nudger.tsx +++ b/packages/bpk-component-chip-group/src/Nudger.tsx @@ -16,18 +16,19 @@ * limitations under the License. */ +import { type MutableRefObject, useEffect, useState } from 'react'; + import { BpkButtonV2, BUTTON_TYPES } from '../../bpk-component-button'; -import type { ChipStyleType } from './BpkChipGroup'; import { CHIP_TYPES } from '../../bpk-component-chip'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import ArrowLeft from '../../bpk-component-icon/sm/long-arrow-left'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import ArrowRight from '../../bpk-component-icon/sm/long-arrow-right'; +import { cssModules, isRTL } from '../../bpk-react-utils/index'; import { withButtonAlignment } from '../../bpk-component-icon/index'; +import type { ChipStyleType } from './BpkChipGroup'; import STYLES from './Nudger.module.scss'; -import { cssModules, isRTL } from '../../bpk-react-utils/index'; -import { MutableRefObject, useEffect, useState } from 'react'; const getClassName = cssModules(STYLES); @@ -40,7 +41,7 @@ const CHIP_STYLE_TO_BUTTON_STYLE = { type Props = { - chipStyle: ChipStyleType; + chipStyle?: ChipStyleType; scrollContainerRef: MutableRefObject; leading?: boolean; } @@ -49,6 +50,7 @@ type Props = { const AlignedLeftArrowIcon = withButtonAlignment(ArrowLeft); const AlignedRightArrowIcon = withButtonAlignment(ArrowRight); +// TODO: how many px to scroll on click? const SCROLL_DISTANCE = 100; const Nudger = ({chipStyle = CHIP_TYPES.default, leading = false, scrollContainerRef}: Props) => { diff --git a/packages/bpk-component-chip-group/src/__snapshots__/BpkBoilerplate-test.tsx.snap b/packages/bpk-component-chip-group/src/__snapshots__/BpkBoilerplate-test.tsx.snap deleted file mode 100644 index 86a6aca630..0000000000 --- a/packages/bpk-component-chip-group/src/__snapshots__/BpkBoilerplate-test.tsx.snap +++ /dev/null @@ -1,32 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BpkBoilerplate should render correctly 1`] = ` - -
- I am an example component. -
-
-`; - -exports[`BpkBoilerplate should support arbitrary props 1`] = ` - -
- I am an example component. -
-
-`; - -exports[`BpkBoilerplate should support custom class names 1`] = ` - -
- I am an example component. -
-
-`; diff --git a/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap b/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap new file mode 100644 index 0000000000..9963f33a37 --- /dev/null +++ b/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap @@ -0,0 +1,414 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BpkChipGroup should render correctly with all chip component types 1`] = ` + +
+
+ + + +
+
+
+`; + +exports[`BpkChipGroup should render correctly with sticky chip 1`] = ` + +
+
+ +
+
+
+
+
+ + + + +
+
+
+
+
+
+`; + +exports[`BpkChipGroup should render correctly with type = rail 1`] = ` + +
+
+
+
+
+ + + + +
+
+
+
+
+
+`; + +exports[`BpkChipGroup should render correctly with type = wrap 1`] = ` + +
+
+ + + + +
+
+
+`; + +exports[`BpkChipGroup should support custom class names 1`] = ` + +
+
+ + + + +
+
+
+`; diff --git a/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroupSingleSelect-test.tsx.snap b/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroupSingleSelect-test.tsx.snap new file mode 100644 index 0000000000..aa684aae15 --- /dev/null +++ b/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroupSingleSelect-test.tsx.snap @@ -0,0 +1,218 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BpkChipGroupSingleSelect should render correctly with type = rail 1`] = ` + +
+
+
+
+
+ + + + +
+
+
+
+
+
+`; + +exports[`BpkChipGroupSingleSelect should render correctly with type = wrap 1`] = ` + +
+
+ + + + +
+
+
+`; + +exports[`BpkChipGroupSingleSelect should support custom class names 1`] = ` + +
+
+ + + + +
+
+
+`; diff --git a/packages/bpk-component-chip-group/src/accessibility-test.tsx b/packages/bpk-component-chip-group/src/accessibility-test.tsx index 041d6d66d2..c9b1cb5213 100644 --- a/packages/bpk-component-chip-group/src/accessibility-test.tsx +++ b/packages/bpk-component-chip-group/src/accessibility-test.tsx @@ -20,6 +20,7 @@ import { render } from '@testing-library/react'; import { axe } from 'jest-axe'; import BpkChipGroup, { CHIP_GROUP_TYPES } from './BpkChipGroup'; +import BpkChipGroupSingleSelect from './BpkChipGroupSingleSelect'; const chips = [ { @@ -44,6 +45,7 @@ describe('BpkChipGroup accessibility tests', () => { ); const results = await axe(container); expect(results).toHaveNoViolations(); @@ -54,6 +56,33 @@ describe('BpkChipGroup accessibility tests', () => { ); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); +}); + +describe('BpkChipGroupSingleSelect accessibility tests', () => { + it('should not have programmatically-detectable accessibility issues when type = rail', async () => { + const { container } = render( + ); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('should not have programmatically-detectable accessibility issues when type = wrap', async () => { + const { container } = render( + ); const results = await axe(container); expect(results).toHaveNoViolations(); From 2ba1a5a5cc6017f6480838bef47331e807efd6b9 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 9 Feb 2024 16:01:20 +0000 Subject: [PATCH 06/79] Fix shadow clipping bug, remove scroll indicators --- .../src/BpkChipGroup.module.css | 2 +- .../src/BpkChipGroup.module.scss | 20 ++++--------------- .../src/BpkChipGroup.tsx | 19 +++++------------- 3 files changed, 10 insertions(+), 31 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.css b/packages/bpk-component-chip-group/src/BpkChipGroup.module.css index 2b607ef3dd..1cf90a0726 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.css +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.css @@ -15,4 +15,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-container{display:flex;align-items:center;white-space:nowrap}.bpk-chip-group{display:flex;gap:0.5rem}.bpk-chip-group--rail{flex-wrap:nowrap}.bpk-chip-group--wrap{flex-wrap:wrap}.bpk-sticky-chip-container{padding-inline-end:0.5rem;margin-inline-end:0.5rem;box-shadow:-1px 0 0 0 #c2c9cd inset}html[dir='rtl'] .bpk-sticky-chip-container{box-shadow:1px 0 0 0 #c2c9cd inset}.bpk-sticky-chip-container--on-dark{box-shadow:-1px 0 0 0 rgba(255,255,255,0.5) inset}html[dir='rtl'] .bpk-sticky-chip-container--on-dark{box-shadow:1px 0 0 0 rgba(255,255,255,0.5) inset}@media (max-width: 64rem){.bpk-sticky-chip{padding-inline-start:0.5rem;padding-inline-end:0}.bpk-sticky-chip span:not(:nth-of-type(1)){display:none}}.bpk-chip-group-scroller-left-indicator--on-dark::before{position:absolute;top:0;bottom:0;content:' ';display:block;z-index:1;width:2rem;background-image:linear-gradient(90deg, #05203c, rgba(5,32,60,0));pointer-events:none;left:0}.bpk-chip-group-scroller-left-indicator--on-image::before{display:none}.bpk-chip-group-scroller-right-indicator--on-dark::after{position:absolute;top:0;bottom:0;content:' ';display:block;z-index:1;width:2rem;background-image:linear-gradient(270deg, #05203c, rgba(5,32,60,0));pointer-events:none;right:0}.bpk-chip-group-scroller-right-indicator--on-image::after{display:none} +@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-container{display:flex;align-items:center;white-space:nowrap}.bpk-chip-group{display:flex;gap:0.5rem}.bpk-chip-group--rail{flex-wrap:nowrap}.bpk-chip-group--wrap{flex-wrap:wrap}.bpk-sticky-chip-container{margin-inline-end:0.5rem;padding-inline-end:0.5rem;box-shadow:-1px 0 0 0 #c2c9cd inset}html[dir='rtl'] .bpk-sticky-chip-container{box-shadow:1px 0 0 0 #c2c9cd inset}.bpk-sticky-chip-container--on-dark{box-shadow:-1px 0 0 0 rgba(255,255,255,0.5) inset}html[dir='rtl'] .bpk-sticky-chip-container--on-dark{box-shadow:1px 0 0 0 rgba(255,255,255,0.5) inset}@media (max-width: 64rem){.bpk-sticky-chip{padding-inline-end:0;padding-inline-start:0.5rem}.bpk-sticky-chip span:not(:nth-of-type(1)){display:none}}.bpk-chip-group-scroller-indicator::before,.bpk-chip-group-scroller-indicator::after{display:none} diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss index 390a25b8e7..b551fd088e 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss @@ -29,6 +29,7 @@ gap: bpk-spacing-md(); &--rail { + padding: 2 * $bpk-one-pixel-rem 0; flex-wrap: nowrap; } @@ -69,22 +70,9 @@ } } -.bpk-chip-group-scroller-left-indicator { - &--on-dark { - @include bpk-scroll-indicator-left($bpk-surface-contrast-day); - } - - &--on-image::before { - display: none; - } -} - -.bpk-chip-group-scroller-right-indicator { - &--on-dark { - @include bpk-scroll-indicator-right($bpk-surface-contrast-day); - } - - &--on-image::after { +.bpk-chip-group-scroller-indicator { + &::before, + &::after { display: none; } } diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx index 8e018b6926..a4b83ae40f 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -18,7 +18,7 @@ import type { ReactElement, ReactNode} from 'react'; import { useRef, useState } from 'react'; -import { cssModules, isRTL } from '../../bpk-react-utils'; +import { cssModules } from '../../bpk-react-utils'; import BpkSelectableChip, { type BpkSelectableChipProps, CHIP_TYPES } from '../../bpk-component-chip'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import BpkMobileScrollContainer from '../../bpk-component-mobile-scroll-container'; @@ -94,15 +94,7 @@ const BpkChipGroup = ({ _isSingleSelect = false, ariaLabel, ariaLabelledBy, chip stickyChip && stickyChip.className, ); - const scrollContainerLeftIndicator = getClassName( - 'bpk-chip-group-scroller-left-indicator', - `bpk-chip-group-scroller-left-indicator--${style}`, - ); - - const scrollContainerRightIndicator = getClassName( - 'bpk-chip-group-scroller-right-indicator', - `bpk-chip-group-scroller-right-indicator--${style}`, - ); + const scrollContainerIndicator = getClassName('bpk-chip-group-scroller-indicator'); const renderChipItem = ({ accessibilityLabel, component: Component = BpkSelectableChip, onClick, selected, text, ...rest }: ChipItem, index: number) => ( ); - // TODO: Fix/remove the scroll indicator when style=CHIP_TYPES.onImage - // TODO: Fix shadows + focus indicators being cutoff when type = rail + // TODO: Fix focus indicators being cutoff when type = rail const wrapRailInScroll = (children: ReactElement) => type === CHIP_GROUP_TYPES.rail ? ( {scrollContainerRef.current = el}} - leadingIndicatorClassName={isRTL() ? scrollContainerRightIndicator : scrollContainerLeftIndicator} - trailingIndicatorClassName={isRTL() ? scrollContainerLeftIndicator : scrollContainerRightIndicator} + leadingIndicatorClassName={scrollContainerIndicator} + trailingIndicatorClassName={scrollContainerIndicator} > {children} From 6e24bf28d4ab881baeffa3bff0aa42e2f6582237 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 16 Feb 2024 13:10:09 +0000 Subject: [PATCH 07/79] Update prop names --- examples/bpk-component-chip-group/examples.tsx | 3 +-- packages/bpk-component-chip-group/index.ts | 3 ++- .../bpk-component-chip-group/src/BpkChipGroup.module.css | 2 +- packages/bpk-component-chip-group/src/BpkChipGroup.tsx | 6 +++--- .../src/BpkChipGroupSingleSelect.tsx | 8 ++++++-- packages/bpk-component-chip-group/src/Nudger.tsx | 6 +++--- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/examples/bpk-component-chip-group/examples.tsx b/examples/bpk-component-chip-group/examples.tsx index cafbc3d336..04023cc742 100644 --- a/examples/bpk-component-chip-group/examples.tsx +++ b/examples/bpk-component-chip-group/examples.tsx @@ -84,7 +84,7 @@ export const BpkSingleChipGroupWrapping = () => (
@@ -105,7 +105,6 @@ export const BpkChipGroupRail = () => ( export const BpkChipGroupSticky = () => { const stickyChip = { text: 'Sort & Filter', - // component: BpkDropdownChip } return ( diff --git a/packages/bpk-component-chip-group/index.ts b/packages/bpk-component-chip-group/index.ts index 1f88b81552..1672700874 100644 --- a/packages/bpk-component-chip-group/index.ts +++ b/packages/bpk-component-chip-group/index.ts @@ -25,9 +25,10 @@ import BpkChipGroup, { } from './src/BpkChipGroup'; import BpkChipGroupSingleSelect, { type SingleSelectProps, + type SingleSelectStateProps, BpkChipGroupSingleSelectState, } from './src/BpkChipGroupSingleSelect'; -export type { ChipGroupProps, SingleSelectProps, ChipItem, SingleSelectChipItem }; +export type { ChipGroupProps, SingleSelectProps, SingleSelectStateProps, ChipItem, SingleSelectChipItem }; export { BpkChipGroupState, CHIP_GROUP_TYPES, BpkChipGroupSingleSelect , BpkChipGroupSingleSelectState}; export default BpkChipGroup; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.css b/packages/bpk-component-chip-group/src/BpkChipGroup.module.css index 1cf90a0726..343c004f2a 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.css +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.css @@ -15,4 +15,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-container{display:flex;align-items:center;white-space:nowrap}.bpk-chip-group{display:flex;gap:0.5rem}.bpk-chip-group--rail{flex-wrap:nowrap}.bpk-chip-group--wrap{flex-wrap:wrap}.bpk-sticky-chip-container{margin-inline-end:0.5rem;padding-inline-end:0.5rem;box-shadow:-1px 0 0 0 #c2c9cd inset}html[dir='rtl'] .bpk-sticky-chip-container{box-shadow:1px 0 0 0 #c2c9cd inset}.bpk-sticky-chip-container--on-dark{box-shadow:-1px 0 0 0 rgba(255,255,255,0.5) inset}html[dir='rtl'] .bpk-sticky-chip-container--on-dark{box-shadow:1px 0 0 0 rgba(255,255,255,0.5) inset}@media (max-width: 64rem){.bpk-sticky-chip{padding-inline-end:0;padding-inline-start:0.5rem}.bpk-sticky-chip span:not(:nth-of-type(1)){display:none}}.bpk-chip-group-scroller-indicator::before,.bpk-chip-group-scroller-indicator::after{display:none} +@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-container{display:flex;align-items:center;white-space:nowrap}.bpk-chip-group{display:flex;gap:0.5rem}.bpk-chip-group--rail{padding:.125rem 0;flex-wrap:nowrap}.bpk-chip-group--wrap{flex-wrap:wrap}.bpk-sticky-chip-container{margin-inline-end:0.5rem;padding-inline-end:0.5rem;box-shadow:-1px 0 0 0 #c2c9cd inset}html[dir='rtl'] .bpk-sticky-chip-container{box-shadow:1px 0 0 0 #c2c9cd inset}.bpk-sticky-chip-container--on-dark{box-shadow:-1px 0 0 0 rgba(255,255,255,0.5) inset}html[dir='rtl'] .bpk-sticky-chip-container--on-dark{box-shadow:1px 0 0 0 rgba(255,255,255,0.5) inset}@media (max-width: 64rem){.bpk-sticky-chip{padding-inline-end:0;padding-inline-start:0.5rem}.bpk-sticky-chip span:not(:nth-of-type(1)){display:none}}.bpk-chip-group-scroller-indicator::before,.bpk-chip-group-scroller-indicator::after{display:none} diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx index a4b83ae40f..f869211ff6 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -63,10 +63,10 @@ export type CommonProps = { export type ChipGroupProps = { chips: ChipItem[]; stickyChip?: ChipItem; - _isSingleSelect?: boolean; // TODO: is there a better way for internal only props? + ariaMultiselectable?: boolean; } & CommonProps; -const BpkChipGroup = ({ _isSingleSelect = false, ariaLabel, ariaLabelledBy, chips, className = null, stickyChip, style = CHIP_TYPES.default, type = CHIP_GROUP_TYPES.rail }: ChipGroupProps) => { +const BpkChipGroup = ({ ariaLabel, ariaLabelledBy, ariaMultiselectable = true, chips, className = null, stickyChip, style = CHIP_TYPES.default, type = CHIP_GROUP_TYPES.rail }: ChipGroupProps) => { const scrollContainerRef = useRef(null); // TODO: is there a way in typescript to enforce this instead? @@ -146,7 +146,7 @@ const BpkChipGroup = ({ _isSingleSelect = false, ariaLabel, ariaLabelledBy, chip
diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx index 57097403a5..790a18367c 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx @@ -39,11 +39,15 @@ const BpkChipGroupSingleSelect = ({ chips, onItemClick, selectedIndex, ...rest } })); return ( - + ); }; -export const BpkChipGroupSingleSelectState = ({ onItemClick, selectedIndex: defaultSelectedIndex = -1, ...rest }: SingleSelectProps) => { +export type SingleSelectStateProps = { + initiallySelectedIndex?: number; +} & Omit + +export const BpkChipGroupSingleSelectState = ({ onItemClick, selectedIndex: defaultSelectedIndex = -1, ...rest }: SingleSelectStateProps) => { const [selectedIndex, setSelectedIndex] = useState(defaultSelectedIndex); const onItemClickWithState = (item: ChipItem, selected: boolean, index: number) => { diff --git a/packages/bpk-component-chip-group/src/Nudger.tsx b/packages/bpk-component-chip-group/src/Nudger.tsx index 3c4d453a8e..87fa485bde 100644 --- a/packages/bpk-component-chip-group/src/Nudger.tsx +++ b/packages/bpk-component-chip-group/src/Nudger.tsx @@ -67,10 +67,10 @@ const Nudger = ({chipStyle = CHIP_TYPES.default, leading = false, scrollContaine const { offsetWidth, scrollLeft, scrollWidth } = scrollContainerRef.current; const scrollValue = rtl ? -Math.floor(scrollLeft) : Math.ceil(scrollLeft); - const showLeadingIndicator = scrollValue > 0; - const showTrailingIndicator = scrollValue < scrollWidth - offsetWidth; + const showLeading = scrollValue > 0; + const showTrailing = scrollValue < scrollWidth - offsetWidth; - setShow((leading && showLeadingIndicator) || (!leading && showTrailingIndicator)) + setShow((leading && showLeading) || (!leading && showTrailing)) }, 100); return () => clearInterval(interval); }, [leading, rtl, scrollContainerRef]); From 73207a3b171a4e7fb703ccd40c236c9574d1cca6 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 16 Feb 2024 14:51:43 +0000 Subject: [PATCH 08/79] increase rail padding to 4px for focus indicator --- packages/bpk-component-chip-group/src/BpkChipGroup.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss index b551fd088e..ce5dc32831 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss @@ -29,7 +29,7 @@ gap: bpk-spacing-md(); &--rail { - padding: 2 * $bpk-one-pixel-rem 0; + padding: 4 * $bpk-one-pixel-rem 0; flex-wrap: nowrap; } From f9c1d78e5b50c82e9405896b243ef2b8630ee2aa Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 16 Feb 2024 16:32:29 +0000 Subject: [PATCH 09/79] Nudger tests, fix single select prop --- .../src/BpkChipGroupSingleSelect.tsx | 4 +- .../src/Nudger-test.tsx | 48 +++- .../src/__snapshots__/Nudger-test.tsx.snap | 235 ++++++++++++++++++ .../src/accessibility-test.tsx | 12 +- 4 files changed, 290 insertions(+), 9 deletions(-) create mode 100644 packages/bpk-component-chip-group/src/__snapshots__/Nudger-test.tsx.snap diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx index 790a18367c..7d38abcd4f 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx @@ -47,8 +47,8 @@ export type SingleSelectStateProps = { initiallySelectedIndex?: number; } & Omit -export const BpkChipGroupSingleSelectState = ({ onItemClick, selectedIndex: defaultSelectedIndex = -1, ...rest }: SingleSelectStateProps) => { - const [selectedIndex, setSelectedIndex] = useState(defaultSelectedIndex); +export const BpkChipGroupSingleSelectState = ({ initiallySelectedIndex = -1, onItemClick, ...rest }: SingleSelectStateProps) => { + const [selectedIndex, setSelectedIndex] = useState(initiallySelectedIndex); const onItemClickWithState = (item: ChipItem, selected: boolean, index: number) => { if (onItemClick) { diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index def6a0559f..b9c99b7346 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -16,11 +16,53 @@ * limitations under the License. */ +import { render, screen } from '@testing-library/react'; +import type { MutableRefObject } from 'react'; +import userEvent from '@testing-library/user-event'; + +import { isRTL } from '../../bpk-react-utils/index'; +import { CHIP_TYPES } from '../../bpk-component-chip'; + +import Nudger, { SCROLL_DISTANCE } from './Nudger'; + + +const mockIsRtl = jest.fn(() => false); + +jest.mock('../../bpk-react-utils/index', () => ({ + ...jest.requireActual('../../bpk-react-utils/index'), + isRTL: () => mockIsRtl(), +})); + +const mockScrollContainerRef = { + current: { + scrollBy: jest.fn(), + offsetWidth: 70, + scrollLeft: 10, + scrollWidth: 100, + }, +} as MutableRefObject; + describe('Nudger', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it.each([ + [false, false], + [true, false], + [false, true], + [true, true], + ])('should render correctly when leading=%s and isRtl=%s', (leading, isRtl) => { + mockIsRtl.mockReturnValue(isRtl); - it('should render correctly', () => { - // TODO + const { asFragment } = render(); + + expect(asFragment()).toMatchSnapshot(); }); - // TODO + it('should render correctly for onDark chip style', () => { + const { asFragment } = render(); + + expect(asFragment()).toMatchSnapshot(); + }); }); diff --git a/packages/bpk-component-chip-group/src/__snapshots__/Nudger-test.tsx.snap b/packages/bpk-component-chip-group/src/__snapshots__/Nudger-test.tsx.snap new file mode 100644 index 0000000000..836b6df2ce --- /dev/null +++ b/packages/bpk-component-chip-group/src/__snapshots__/Nudger-test.tsx.snap @@ -0,0 +1,235 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Nudger should render correctly 1`] = ` + + + +`; + +exports[`Nudger should render correctly for chip style: default 1`] = ` + + + +`; + +exports[`Nudger should render correctly for chip style: on-dark 1`] = ` + + + +`; + +exports[`Nudger should render correctly for chip style: on-image 1`] = ` + + + +`; + +exports[`Nudger should render correctly for onDark chip style 1`] = ` + + + +`; + +exports[`Nudger should render correctly when leading=false and isRtl=false 1`] = ` + + + +`; + +exports[`Nudger should render correctly when leading=false and isRtl=true 1`] = ` + + + +`; + +exports[`Nudger should render correctly when leading=true and isRtl=false 1`] = ` + + + +`; + +exports[`Nudger should render correctly when leading=true and isRtl=true 1`] = ` + + + +`; diff --git a/packages/bpk-component-chip-group/src/accessibility-test.tsx b/packages/bpk-component-chip-group/src/accessibility-test.tsx index c9b1cb5213..107a9d0688 100644 --- a/packages/bpk-component-chip-group/src/accessibility-test.tsx +++ b/packages/bpk-component-chip-group/src/accessibility-test.tsx @@ -46,7 +46,8 @@ describe('BpkChipGroup accessibility tests', () => { chips={chips} type={CHIP_GROUP_TYPES.rail} ariaLabel="Select cities" - />); + /> + ); const results = await axe(container); expect(results).toHaveNoViolations(); }); @@ -57,7 +58,8 @@ describe('BpkChipGroup accessibility tests', () => { chips={chips} type={CHIP_GROUP_TYPES.wrap} ariaLabel="Select cities" - />); + /> + ); const results = await axe(container); expect(results).toHaveNoViolations(); }); @@ -71,7 +73,8 @@ describe('BpkChipGroupSingleSelect accessibility tests', () => { type={CHIP_GROUP_TYPES.rail} selectedIndex={1} ariaLabel="Select a city" - />); + /> + ); const results = await axe(container); expect(results).toHaveNoViolations(); }); @@ -83,7 +86,8 @@ describe('BpkChipGroupSingleSelect accessibility tests', () => { type={CHIP_GROUP_TYPES.wrap} selectedIndex={1} ariaLabel="Select a city" - />); + /> + ); const results = await axe(container); expect(results).toHaveNoViolations(); }); From 9273120276e076579e15c5504ddfda0434c86fd4 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 1 Mar 2024 10:57:02 +0000 Subject: [PATCH 10/79] clean up imports --- .../bpk-component-chip-group/src/BpkChipGroup.module.css | 2 +- packages/bpk-component-chip-group/src/Nudger-test.tsx | 6 ++---- packages/bpk-component-chip-group/src/Nudger.module.css | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.css b/packages/bpk-component-chip-group/src/BpkChipGroup.module.css index 343c004f2a..3665ec2ea8 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.css +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.css @@ -15,4 +15,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-container{display:flex;align-items:center;white-space:nowrap}.bpk-chip-group{display:flex;gap:0.5rem}.bpk-chip-group--rail{padding:.125rem 0;flex-wrap:nowrap}.bpk-chip-group--wrap{flex-wrap:wrap}.bpk-sticky-chip-container{margin-inline-end:0.5rem;padding-inline-end:0.5rem;box-shadow:-1px 0 0 0 #c2c9cd inset}html[dir='rtl'] .bpk-sticky-chip-container{box-shadow:1px 0 0 0 #c2c9cd inset}.bpk-sticky-chip-container--on-dark{box-shadow:-1px 0 0 0 rgba(255,255,255,0.5) inset}html[dir='rtl'] .bpk-sticky-chip-container--on-dark{box-shadow:1px 0 0 0 rgba(255,255,255,0.5) inset}@media (max-width: 64rem){.bpk-sticky-chip{padding-inline-end:0;padding-inline-start:0.5rem}.bpk-sticky-chip span:not(:nth-of-type(1)){display:none}}.bpk-chip-group-scroller-indicator::before,.bpk-chip-group-scroller-indicator::after{display:none} +@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-container{display:flex;align-items:center;white-space:nowrap}.bpk-chip-group{display:flex;gap:0.5rem}.bpk-chip-group--rail{padding:.25rem 0;flex-wrap:nowrap}.bpk-chip-group--wrap{flex-wrap:wrap}.bpk-sticky-chip-container{margin-inline-end:0.5rem;padding-inline-end:0.5rem;box-shadow:-1px 0 0 0 #c2c9cd inset}html[dir='rtl'] .bpk-sticky-chip-container{box-shadow:1px 0 0 0 #c2c9cd inset}.bpk-sticky-chip-container--on-dark{box-shadow:-1px 0 0 0 rgba(255,255,255,0.5) inset}html[dir='rtl'] .bpk-sticky-chip-container--on-dark{box-shadow:1px 0 0 0 rgba(255,255,255,0.5) inset}@media (max-width: 64rem){.bpk-sticky-chip{padding-inline-end:0;padding-inline-start:0.5rem}.bpk-sticky-chip span:not(:nth-of-type(1)){display:none}}.bpk-chip-group-scroller-indicator::before,.bpk-chip-group-scroller-indicator::after{display:none} diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index b9c99b7346..7a2a9b1a51 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -16,14 +16,12 @@ * limitations under the License. */ -import { render, screen } from '@testing-library/react'; +import { render } from '@testing-library/react'; import type { MutableRefObject } from 'react'; -import userEvent from '@testing-library/user-event'; -import { isRTL } from '../../bpk-react-utils/index'; import { CHIP_TYPES } from '../../bpk-component-chip'; -import Nudger, { SCROLL_DISTANCE } from './Nudger'; +import Nudger from './Nudger'; const mockIsRtl = jest.fn(() => false); diff --git a/packages/bpk-component-chip-group/src/Nudger.module.css b/packages/bpk-component-chip-group/src/Nudger.module.css index 7125761eb9..88c240733b 100644 --- a/packages/bpk-component-chip-group/src/Nudger.module.css +++ b/packages/bpk-component-chip-group/src/Nudger.module.css @@ -15,4 +15,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-nudger--leading{margin-inline-end:0.5rem}.bpk-chip-group-nudger--trailing{margin-inline-start:0.5rem}.bpk-chip-group-nudger--hidden{display:none} +@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-nudger{position:absolute}.bpk-chip-group-nudger--leading{margin-inline-end:0.5rem}.bpk-chip-group-nudger--trailing{margin-inline-start:0.5rem}.bpk-chip-group-nudger--hidden{display:none} From 56a1e8b08204d115cfa4fd848a4d4ee0c61b74c8 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 1 Mar 2024 14:48:58 +0000 Subject: [PATCH 11/79] Add tests for stateful chip groups --- .../src/BpkChipGroup-test.tsx | 163 +++++++++++++++--- .../src/BpkChipGroupSingleSelect-test.tsx | 155 ++++++++++++++--- .../__snapshots__/BpkChipGroup-test.tsx.snap | 3 + .../BpkChipGroupSingleSelect-test.tsx.snap | 3 + scripts/jest/setup.js | 1 + 5 files changed, 275 insertions(+), 50 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx index 90c001e265..c8eba66095 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx @@ -18,28 +18,34 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import '@testing-library/jest-dom'; import BpkSelectableChip, { BpkDismissibleChip, BpkDropdownChip } from '../../bpk-component-chip'; -import BpkChipGroup, { CHIP_GROUP_TYPES } from './BpkChipGroup'; - -const chips = [ - { - text: 'London', - }, - { - text: 'Berlin', - selected: true, - }, - { - text: 'Florence', - }, - { - text: 'Stockholm', - } -]; +import BpkChipGroup, { BpkChipGroupState, CHIP_GROUP_TYPES } from './BpkChipGroup'; + +const defaultProps = { + type: CHIP_GROUP_TYPES.wrap, + ariaLabel: 'a11y label', +} describe('BpkChipGroup', () => { + const chips = [ + { + text: 'London', + }, + { + text: 'Berlin', + selected: true, + }, + { + text: 'Florence', + }, + { + text: 'Stockholm', + } + ]; + it('should render correctly with type = rail', () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); @@ -54,8 +60,8 @@ describe('BpkChipGroup', () => { const { asFragment } = render( , ); expect(asFragment()).toMatchSnapshot(); @@ -69,6 +75,7 @@ describe('BpkChipGroup', () => { }} chips={chips} type={CHIP_GROUP_TYPES.rail} + ariaLabel="Filter cities" />, ); expect(asFragment()).toMatchSnapshot(); @@ -93,7 +100,7 @@ describe('BpkChipGroup', () => { const { asFragment } = render( , ); expect(asFragment()).toMatchSnapshot(); @@ -116,7 +123,7 @@ describe('BpkChipGroup', () => { onClick, } ]} - type={CHIP_GROUP_TYPES.wrap} + {...defaultProps} />, ); @@ -125,17 +132,119 @@ describe('BpkChipGroup', () => { expect(onClick).toHaveBeenCalledTimes(1); expect(onClick).toHaveBeenCalledWith(true, 1); }); +}); - it('should render nudger when on desktop', () => { - // TODO +describe('BpkChipGroupState', () => { + const chips = [ + { + text: 'London', + onClick: jest.fn(), + }, + { + text: 'Berlin', + onClick: jest.fn(), + }, { + text: 'New York', + onClick: jest.fn(), + } + ]; + + beforeEach(() => { + jest.resetAllMocks(); }); - it('should not render nudger when on mobile', () => { - // TODO + it('should select a chip when clicked', async () => { + const user = userEvent.setup(); + + render( + , + ); + + const chip = screen.getByRole('option', { name: 'Berlin' }); + await user.click(chip); + + expect(chip).toHaveClass('bpk-chip--default-selected'); }); -}); + it('should allow multiple chips to be selected', async () => { + const user = userEvent.setup(); -describe('BpkChipGroupState', () => { - // TODO + render( + , + ); + + const berlinChip = screen.getByRole('option', { name: 'Berlin' }); + const londonChip = screen.getByRole('option', { name: 'London' }); + + await user.click(berlinChip); + await user.click(londonChip); + + expect(berlinChip).toHaveClass('bpk-chip--default-selected'); + expect(londonChip).toHaveClass('bpk-chip--default-selected'); + }); + + it('should unselect a chip when selected and clicked', async () => { + const user = userEvent.setup(); + + render( + , + ); + + const chip = screen.getByRole('option', { name: 'Berlin' }); + await user.click(chip); + + expect(chip).toHaveClass('bpk-chip--default-selected'); + + await user.click(chip); + + expect(chip).not.toHaveClass('bpk-chip--default-selected'); + }); + + it('should call onclick with the selected chip and index when clicked', async () => { + const user = userEvent.setup(); + + render( + , + ); + + await user.click(screen.getByRole('option', { name: 'Berlin' })); + + const { onClick } = chips[1]; + + expect(onClick).toHaveBeenCalledTimes(1); + expect(onClick).toHaveBeenCalledWith(true, 1); + }); + + it('should allow chips to be selected initially when passed in chips array', () => { + render( + , + ); + + const chip = screen.getByRole('option', { name: 'Berlin' }); + + expect(chip).toHaveClass('bpk-chip--default-selected'); + }); }); diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx index 1be7626fb7..758477aa61 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx @@ -16,36 +16,40 @@ * limitations under the License. */ -import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; import { render, screen } from '@testing-library/react'; import { CHIP_GROUP_TYPES } from './BpkChipGroup'; -import BpkChipGroupSingleSelect from './BpkChipGroupSingleSelect'; - -const chips = [ - { - text: 'London', - }, - { - text: 'Berlin', - }, - { - text: 'Florence', - }, - { - text: 'Stockholm', - } -]; +import BpkChipGroupSingleSelect, { BpkChipGroupSingleSelectState } from './BpkChipGroupSingleSelect'; + +const defaultProps = { + type: CHIP_GROUP_TYPES.wrap, + ariaLabel: 'a11y label', +} describe('BpkChipGroupSingleSelect', () => { + const chips = [ + { + text: 'London', + }, + { + text: 'Berlin', + }, + { + text: 'Florence', + }, + { + text: 'Stockholm', + } + ]; + it('should render correctly with type = rail', () => { - const { asFragment } = render(); + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); it('should render correctly with type = wrap', () => { - const { asFragment } = render(); + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); @@ -57,8 +61,8 @@ describe('BpkChipGroupSingleSelect', () => { render( , ); @@ -87,8 +91,8 @@ describe('BpkChipGroupSingleSelect', () => { render( , ); @@ -101,8 +105,8 @@ describe('BpkChipGroupSingleSelect', () => { const { asFragment } = render( , ); expect(asFragment()).toMatchSnapshot(); @@ -110,5 +114,110 @@ describe('BpkChipGroupSingleSelect', () => { }); describe('BpkChipGroupSingleSelectState', () => { - // TODO + const chips = [ + { + text: 'London', + }, + { + text: 'Berlin', + }, + { + text: 'Florence', + }, + ]; + + it('should select a chip when clicked', async () => { + const user = userEvent.setup(); + + render( + , + ); + + const chip = screen.getByRole('option', { name: 'Berlin' }); + + await user.click(chip); + + expect(chip).toHaveClass('bpk-chip--default-selected'); + }); + + it('should only allow one chip to be selected', async () => { + const user = userEvent.setup(); + + render( + , + ); + + const berlinChip = screen.getByRole('option', { name: 'Berlin' }); + + await user.click(berlinChip); + + expect(berlinChip).toHaveClass('bpk-chip--default-selected'); + + const londonChip = screen.getByRole('option', { name: 'London' }); + + await user.click(londonChip); + + expect(londonChip).toHaveClass('bpk-chip--default-selected'); + expect(berlinChip).not.toHaveClass('bpk-chip--default-selected'); + }); + + it('should deselect a chip when selected and clicked', async () => { + const user = userEvent.setup(); + + render( + , + ); + + const chip = screen.getByRole('option', { name: 'Berlin' }); + + await user.click(chip); + + expect(chip).toHaveClass('bpk-chip--default-selected'); + + await user.click(chip); + + expect(chip).not.toHaveClass('bpk-chip--default-selected'); + }); + + it('should call onItemClick with the correct params when clicked', async () => { + const user = userEvent.setup(); + + const onItemClick = jest.fn(); + + render( + , + ); + + await user.click(screen.getByRole('option', { name: 'Berlin' })); + + expect(onItemClick).toHaveBeenCalledTimes(1); + expect(onItemClick).toHaveBeenCalledWith(chips[1], true, 1); + }); + + it('should have initiallySelectedIndex selected before interaction', () => { + render( + , + ); + + const chip = screen.getByRole('option', { name: 'Berlin' }); + + expect(chip).toHaveClass('bpk-chip--default-selected'); + }); }); diff --git a/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap b/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap index 9963f33a37..89e4feff20 100644 --- a/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap +++ b/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap @@ -6,6 +6,7 @@ exports[`BpkChipGroup should render correctly with all chip component types 1`] class="bpk-chip-group-container" >
Date: Fri, 1 Mar 2024 14:53:30 +0000 Subject: [PATCH 12/79] Add sticky chip to a11y tests --- .../bpk-component-chip-group/src/accessibility-test.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/bpk-component-chip-group/src/accessibility-test.tsx b/packages/bpk-component-chip-group/src/accessibility-test.tsx index 107a9d0688..299ee0cdcb 100644 --- a/packages/bpk-component-chip-group/src/accessibility-test.tsx +++ b/packages/bpk-component-chip-group/src/accessibility-test.tsx @@ -44,6 +44,9 @@ describe('BpkChipGroup accessibility tests', () => { const { container } = render( @@ -70,6 +73,9 @@ describe('BpkChipGroupSingleSelect accessibility tests', () => { const { container } = render( Date: Thu, 7 Mar 2024 10:38:54 +0000 Subject: [PATCH 13/79] Start readme --- packages/bpk-component-chip-group/README.md | 33 ++++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/bpk-component-chip-group/README.md b/packages/bpk-component-chip-group/README.md index e38f612785..8dd182839d 100644 --- a/packages/bpk-component-chip-group/README.md +++ b/packages/bpk-component-chip-group/README.md @@ -1,6 +1,6 @@ # bpk-component-chip-group -> Backpack example component. +> Backpack chip group component. ## Installation @@ -8,14 +8,31 @@ Check the main [Readme](https://github.com/skyscanner/backpack#usage) for a comp ## Usage -```ts -import BpkBoilerplate from '@skyscanner/backpack-web/bpk-component-code'; - -export default () => ; +```tsx +import BpkChipGroup, { CHIP_GROUP_TYPES } from '@skyscanner/backpack-web/bpk-component-chip-group'; +import BpkSelectableChip, { CHIP_TYPES, BpkDropdownChip } from '@skyscanner/backpack-web/bpk-component-chip'; + +export default () => ( + console.log(`Open dropdown: ${selected}`), + }]} + style={CHIP_TYPES.onDark} + /> +); ``` ## Props -| Property | PropType | Required | Default Value | -| --------- | -------- | -------- | ------------- | -| className | string | false | null | +TODO: update to web tab +Check out the full list of props on Skyscanner's [design system documentation website](https://www.skyscanner.design/latest/components/chip-group/compose-LwGOKNct). From c89f9753e23928929f64ba6eec5b41bfa6b37e0d Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Tue, 26 Mar 2024 15:33:38 +0000 Subject: [PATCH 14/79] Use BpkIconChip, @import -> @use --- .../src/BpkChipGroup.module.scss | 37 +++++++------------ .../src/BpkChipGroup.tsx | 22 +++++------ .../src/Nudger.module.scss | 6 +-- 3 files changed, 27 insertions(+), 38 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss index ce5dc32831..9d5e6adee3 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss @@ -16,7 +16,9 @@ * limitations under the License. */ -@import '../../bpk-mixins/index.scss'; +@use '../../unstable__bpk-mixins/borders'; +@use '../../unstable__bpk-mixins/tokens'; +@use '../../unstable__bpk-mixins/utils'; .bpk-chip-group-container { display: flex; @@ -26,10 +28,10 @@ .bpk-chip-group { display: flex; - gap: bpk-spacing-md(); + gap: tokens.bpk-spacing-md(); &--rail { - padding: 4 * $bpk-one-pixel-rem 0; + padding: 4 * tokens.$bpk-one-pixel-rem 0; flex-wrap: nowrap; } @@ -39,33 +41,20 @@ } .bpk-sticky-chip-container { - margin-inline-end: bpk-spacing-md(); - padding-inline-end: bpk-spacing-md(); + margin-inline-end: tokens.bpk-spacing-md(); + padding-inline-end: tokens.bpk-spacing-md(); - @include bpk-border-right-sm($bpk-line-day); + @include borders.bpk-border-right-sm(tokens.$bpk-line-day); - @include bpk-rtl { - @include bpk-border-left-sm($bpk-line-day); + @include utils.bpk-rtl { + @include borders.bpk-border-left-sm(tokens.$bpk-line-day); } &--on-dark { - @include bpk-border-right-sm($bpk-line-on-dark-day); + @include borders.bpk-border-right-sm(tokens.$bpk-line-on-dark-day); - @include bpk-rtl { - @include bpk-border-left-sm($bpk-line-on-dark-day); - } - } -} - -@include bpk-breakpoint-tablet { - // TODO: Should this be a new chip component type without text? - .bpk-sticky-chip { - padding-inline-end: 0; - padding-inline-start: bpk-spacing-md(); - - // TODO: better way to do this? Hide all contents of the chip except the leading icon. - span:not(:nth-of-type(1)) { - display: none; + @include utils.bpk-rtl { + @include borders.bpk-border-left-sm(tokens.$bpk-line-on-dark-day); } } } diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx index f869211ff6..b0b817b006 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -19,7 +19,7 @@ import type { ReactElement, ReactNode} from 'react'; import { useRef, useState } from 'react'; import { cssModules } from '../../bpk-react-utils'; -import BpkSelectableChip, { type BpkSelectableChipProps, CHIP_TYPES } from '../../bpk-component-chip'; +import BpkSelectableChip, { BpkIconChip, type BpkSelectableChipProps, CHIP_TYPES } from '../../bpk-component-chip'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import BpkMobileScrollContainer from '../../bpk-component-mobile-scroll-container'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. @@ -27,6 +27,7 @@ import FilterIconSm from '../../bpk-component-icon/sm/filter'; import BpkBreakpoint, { BREAKPOINTS } from '../../bpk-component-breakpoint'; import Nudger from './Nudger'; + import STYLES from './BpkChipGroup.module.scss'; const getClassName = cssModules(STYLES); @@ -89,11 +90,6 @@ const BpkChipGroup = ({ ariaLabel, ariaLabelledBy, ariaMultiselectable = true, c `bpk-sticky-chip-container--${style}`, ); - const stickyChipClassnames = getClassName( - 'bpk-sticky-chip', - stickyChip && stickyChip.className, - ); - const scrollContainerIndicator = getClassName('bpk-chip-group-scroller-indicator'); const renderChipItem = ({ accessibilityLabel, component: Component = BpkSelectableChip, onClick, selected, text, ...rest }: ChipItem, index: number) => ( @@ -135,13 +131,17 @@ const BpkChipGroup = ({ ariaLabel, ariaLabelledBy, ariaMultiselectable = true, c )} {type === CHIP_GROUP_TYPES.rail && stickyChip &&
- {renderChipItem({ - ...stickyChip, - leadingAccessoryView: , - className: stickyChipClassnames, - }, -1)} + + {(isDesktop) => renderChipItem({ + ...stickyChip, + component: isDesktop ? BpkSelectableChip : BpkIconChip, + leadingAccessoryView: , + }, -1)} +
} + + {wrapRailInScroll(
Date: Wed, 27 Mar 2024 17:19:04 +0000 Subject: [PATCH 15/79] Fix nudgers jumpiness --- .../src/BpkChipGroup.tsx | 19 +++++++++---------- .../bpk-component-chip-group/src/Nudger.tsx | 9 ++++++--- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx index b0b817b006..649b716d75 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -53,13 +53,19 @@ export type ChipItem = { selected?: boolean; } & SingleSelectChipItem; +type AccessibilityLabels = { + ariaLabel: string; + ariaLabelledBy?: never; +} | { + ariaLabel?: never; + ariaLabelledBy: string; +}; + export type CommonProps = { - ariaLabel?: string; - ariaLabelledBy?: string; type: ChipGroupType; className?: string | null; style?: ChipStyleType; -}; +} & AccessibilityLabels; export type ChipGroupProps = { chips: ChipItem[]; @@ -70,11 +76,6 @@ export type ChipGroupProps = { const BpkChipGroup = ({ ariaLabel, ariaLabelledBy, ariaMultiselectable = true, chips, className = null, stickyChip, style = CHIP_TYPES.default, type = CHIP_GROUP_TYPES.rail }: ChipGroupProps) => { const scrollContainerRef = useRef(null); - // TODO: is there a way in typescript to enforce this instead? - if ((!ariaLabel && !ariaLabelledBy) || (ariaLabel && ariaLabelledBy)) { - console.warn('BpkChipGroup: Exactly one of ariaLabel and ariaLabelledBy should be set') - } - const containerClassnames = getClassName( className, 'bpk-chip-group-container', @@ -140,8 +141,6 @@ const BpkChipGroup = ({ ariaLabel, ariaLabelledBy, ariaMultiselectable = true, c
} - - {wrapRailInScroll(
{ const [show, setShow] = useState(false); + const [enabled, setEnabled] = useState(true); const rtl = isRTL(); const isLeft = (leading && !rtl) || (!leading && rtl); @@ -70,7 +72,8 @@ const Nudger = ({chipStyle = CHIP_TYPES.default, leading = false, scrollContaine const showLeading = scrollValue > 0; const showTrailing = scrollValue < scrollWidth - offsetWidth; - setShow((leading && showLeading) || (!leading && showTrailing)) + setEnabled((leading && showLeading) || (!leading && showTrailing)) + setShow(showLeading || showTrailing); }, 100); return () => clearInterval(interval); }, [leading, rtl, scrollContainerRef]); @@ -86,7 +89,7 @@ const Nudger = ({chipStyle = CHIP_TYPES.default, leading = false, scrollContaine className={classNames} type={CHIP_STYLE_TO_BUTTON_STYLE[chipStyle]} iconOnly - disabled={!show} + disabled={!show || !enabled} onClick={() => { if (scrollContainerRef.current) { scrollContainerRef.current.scrollBy({ From 9c90bd8d3b033d1718d55547cab67e2f365ba0e5 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Tue, 2 Apr 2024 10:54:40 +0100 Subject: [PATCH 16/79] update todo comment --- packages/bpk-component-chip-group/src/Nudger.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bpk-component-chip-group/src/Nudger.tsx b/packages/bpk-component-chip-group/src/Nudger.tsx index f6fea01e69..256416ec57 100644 --- a/packages/bpk-component-chip-group/src/Nudger.tsx +++ b/packages/bpk-component-chip-group/src/Nudger.tsx @@ -51,7 +51,7 @@ type Props = { const AlignedLeftArrowIcon = withButtonAlignment(ArrowLeft); const AlignedRightArrowIcon = withButtonAlignment(ArrowRight); -// TODO: how many px to scroll on click? +// Chosen based on feeling good with the example stories const SCROLL_DISTANCE = 150; const Nudger = ({chipStyle = CHIP_TYPES.default, leading = false, scrollContainerRef}: Props) => { From 63063f72b5c3ccae7d8086da838c1d78dee9073e Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 12 Apr 2024 10:22:02 +0100 Subject: [PATCH 17/79] Fix some type errors, write README --- .../examples.module.css | 4 +- .../bpk-component-chip-group/examples.tsx | 80 ++++++++--- examples/bpk-component-chip-group/stories.ts | 2 + packages/bpk-component-chip-group/README.md | 126 +++++++++++++++++- packages/bpk-component-chip-group/index.ts | 3 +- .../src/BpkChipGroup-test.tsx | 14 +- .../src/BpkChipGroup.module.css | 4 +- .../src/BpkChipGroup.module.scss | 2 +- .../src/BpkChipGroup.tsx | 113 +++++++++++----- .../src/Nudger-test.tsx | 5 +- .../src/Nudger.module.css | 4 +- .../src/accessibility-test.tsx | 3 - 12 files changed, 279 insertions(+), 81 deletions(-) diff --git a/examples/bpk-component-chip-group/examples.module.css b/examples/bpk-component-chip-group/examples.module.css index 7e080efccc..6682ae2774 100644 --- a/examples/bpk-component-chip-group/examples.module.css +++ b/examples/bpk-component-chip-group/examples.module.css @@ -14,5 +14,5 @@ * 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. -*/ -@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-examples__fixed-width{width:18.75rem}.bpk-chip-group-examples__contrast{padding:1rem;background-color:#eff1f2}.bpk-chip-group-examples__dark{padding:1rem;background-color:#05203c}.bpk-chip-group-examples__image{padding:1rem;background-image:url("https://content.skyscnr.com/96508dbac15a2895b0147dc7e7f9ad30/canadian-rockies-canada.jpg")}.bpk-chip-group-examples__mixed-container h2{margin-top:2rem;margin-bottom:0.5rem} + */ +@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-examples__fixed-width{width:18.75rem}.bpk-chip-group-examples__contrast{padding:1rem;background-color:#eff3f8}.bpk-chip-group-examples__dark{padding:1rem;background-color:#05203c}.bpk-chip-group-examples__image{padding:1rem;background-image:url("https://content.skyscnr.com/96508dbac15a2895b0147dc7e7f9ad30/canadian-rockies-canada.jpg")}.bpk-chip-group-examples__mixed-container h2{margin-top:2rem;margin-bottom:.5rem} \ No newline at end of file diff --git a/examples/bpk-component-chip-group/examples.tsx b/examples/bpk-component-chip-group/examples.tsx index 04023cc742..78b10eff0e 100644 --- a/examples/bpk-component-chip-group/examples.tsx +++ b/examples/bpk-component-chip-group/examples.tsx @@ -19,8 +19,13 @@ import { useState } from 'react'; -import { BpkDismissibleChip, BpkDropdownChip, CHIP_TYPES } from '../../packages/bpk-component-chip'; -import { BpkChipGroupState, BpkChipGroupSingleSelectState, CHIP_GROUP_TYPES } from '../../packages/bpk-component-chip-group'; +import { CHIP_TYPES } from '../../packages/bpk-component-chip'; +import BpkChipGroup, { + BpkChipGroupState, + BpkChipGroupSingleSelectState, + CHIP_GROUP_TYPES, + CHIP_COMPONENT, +} from '../../packages/bpk-component-chip-group'; import BpkText, { TEXT_STYLES } from '../../packages/bpk-component-text/index'; import { cssModules } from '../../packages/bpk-react-utils/index'; @@ -74,7 +79,7 @@ export const BpkChipGroupWrapping = () => (
); @@ -85,7 +90,7 @@ export const BpkSingleChipGroupWrapping = () => ( type={CHIP_GROUP_TYPES.wrap} chips={chips} initiallySelectedIndex={0} - accessibilityLabel="Select a city" + ariaLabel="Select a city" />
); @@ -96,7 +101,7 @@ export const BpkChipGroupRail = () => (
); @@ -113,7 +118,7 @@ export const BpkChipGroupSticky = () => { type={CHIP_GROUP_TYPES.rail} chips={chips} stickyChip={stickyChip} - accessibilityLabel="Select cities" + ariaLabel="Select cities" />
); @@ -130,8 +135,8 @@ export const OnContrastChipGroup = () => { type={CHIP_GROUP_TYPES.rail} chips={chips} stickyChip={stickyChip} - style={CHIP_TYPES.default} - accessibilityLabel="Select cities" + chipStyle={CHIP_TYPES.default} + ariaLabel="Select cities" />
); @@ -149,8 +154,8 @@ export const OnDarkChipGroup = () => { type={CHIP_GROUP_TYPES.rail} chips={chips} stickyChip={stickyChip} - style={CHIP_TYPES.onDark} - accessibilityLabel="Select cities" + chipStyle={CHIP_TYPES.onDark} + ariaLabel="Select cities" />
); @@ -167,8 +172,8 @@ export const OnImageChipGroup = () => { type={CHIP_GROUP_TYPES.rail} chips={chips} stickyChip={stickyChip} - style={CHIP_TYPES.onImage} - accessibilityLabel="Select cities" + chipStyle={CHIP_TYPES.onImage} + ariaLabel="Select cities" />
); @@ -183,14 +188,15 @@ export const AllChipTypesGroup = () => { text: 'Disabled', disabled: true, }, - !dismissed && { - text: 'Dismissable', + { + text: 'Dismissible', onClick: () => setDismissed(true), - component: BpkDismissibleChip, + component: CHIP_COMPONENT.dismissible, + hidden: dismissed, }, { text: 'Dropdown', - component: BpkDropdownChip, + component: CHIP_COMPONENT.dropdown, }, { text: 'Selectable', @@ -205,7 +211,43 @@ export const AllChipTypesGroup = () => { + ); +}; + + +export const StateManagement = () => { + const [route, setRoute] = useState('flights'); + + return ( + setRoute('flights'), + }, { + text: 'Car Hire', + selected: route === 'cars', + onClick: () => setRoute('cars'), + }, { + text: 'Hotels', + selected: route === 'hotels', + onClick: () => setRoute('hotels'), + }, { + text: 'Trains', + selected: route === 'trains', + onClick: () => setRoute('trains'), + }, { + component: CHIP_COMPONENT.dropdown, + text: 'More', + accessibilityLabel: 'Show more filter options', + // eslint-disable-next-line no-console + onClick: (selected) => console.log(`Open dropdown: ${selected}`), + }]} /> ); }; @@ -244,6 +286,10 @@ export const MixedExample = () => ( Single Select Group + + State example + +
) diff --git a/examples/bpk-component-chip-group/stories.ts b/examples/bpk-component-chip-group/stories.ts index 3a9a922b68..cc48f6dac8 100644 --- a/examples/bpk-component-chip-group/stories.ts +++ b/examples/bpk-component-chip-group/stories.ts @@ -28,6 +28,7 @@ import { MixedExample, AllChipTypesGroup, OnContrastChipGroup, + StateManagement, } from './examples'; export default { @@ -49,6 +50,7 @@ export const OnContrast = OnContrastChipGroup; export const OnDark = OnDarkChipGroup; export const OnImage = OnImageChipGroup; export const AllChipTypes = AllChipTypesGroup; +export const ExampleStateManagement = StateManagement; export const VisualTest = MixedExample; export const VisualTestWithZoom = VisualTest.bind({}); VisualTestWithZoom.args = { diff --git a/packages/bpk-component-chip-group/README.md b/packages/bpk-component-chip-group/README.md index 8dd182839d..cb3e191684 100644 --- a/packages/bpk-component-chip-group/README.md +++ b/packages/bpk-component-chip-group/README.md @@ -8,11 +8,21 @@ Check the main [Readme](https://github.com/skyscanner/backpack#usage) for a comp ## Usage +### BpkChipGroup + +This is a multiselectable chip group without any built in state management. State of chips must be managed by the consumer as passed in through the `chips` prop, using the `onClick` property of each chip to detect interaction. + ```tsx -import BpkChipGroup, { CHIP_GROUP_TYPES } from '@skyscanner/backpack-web/bpk-component-chip-group'; -import BpkSelectableChip, { CHIP_TYPES, BpkDropdownChip } from '@skyscanner/backpack-web/bpk-component-chip'; +import BpkChipGroup, { + BpkChipGroupState, + BpkChipGroupSingleSelectState, + CHIP_GROUP_TYPES, + CHIP_COMPONENT, +} from '@skyscanner/backpack-web/bpk-component-chip-group'; +import { CHIP_TYPES } from '@skyscanner/backpack-web/bpk-component-chip'; +import { useState } from 'react'; -export default () => ( +const MainExample = () => ( ( text: 'London', }, { text: 'Berlin', - selected: true, + selected: true, // must be managed by the consumer, see VerticalsExample }, { - component: BpkDropdownChip, + component: CHIP_COMPONENT.dropdown, text: 'More', accessibilityLabel: 'Show more filters', onClick: (selected) => console.log(`Open dropdown: ${selected}`), }]} - style={CHIP_TYPES.onDark} /> ); + +const VerticalsExample = () => { + const [route, setRoute] = useState('flights'); + + return ( + setRoute('flights'), + }, { + text: 'Car Hire', + selected: route === 'cars', + onClick: () => setRoute('cars'), + }, { + text: 'Hotels', + selected: route === 'hotels', + onClick: () => setRoute('hotels'), + }, { + text: 'Trains', + selected: route === 'trains', + onClick: () => setRoute('trains'), + }, { + component: CHIP_COMPONENT.dropdown, + text: 'More', + accessibilityLabel: 'Show more filter options', + onClick: (selected) => console.log(`Open dropdown: ${selected}`), + }]} + /> + ); +}; ``` +### BpkChipGroupState + +Like a `BpkChipGroup` (multi-selectable) but with basic state management similar to above built in. The `selected` property of each `ChipItem` will affect **only the first render** as the state is managed within the component afterwards. + +```tsx +const StatefulExample = () => ( + +); +``` + +### BpkChipGroupSingleSelect + +This is a wrapper around a `BpkChipGroup` that only allows a single chip to be `selected`, determined by the `selectedIndex` prop. If no chips should appear selected, this should be `undefined`. + +```tsx +const SingleSelectExample = () => ( + +); +``` + +### BpkChipGroupSingleSelectState + +A wrapper around `BpkChipGroupSingleSelect` that provides basic state management for selecting/deselecting a single chip in the group. The `initiallySelectedIndex` prop controls the chip that will be selected on **first render only**. + +```tsx +const SingleSelectStateExample = () => ( + +); +``` + + ## Props -TODO: update to web tab +[//]: # (TODO: this update to web tab) Check out the full list of props on Skyscanner's [design system documentation website](https://www.skyscanner.design/latest/components/chip-group/compose-LwGOKNct). diff --git a/packages/bpk-component-chip-group/index.ts b/packages/bpk-component-chip-group/index.ts index 1672700874..5a983f20fc 100644 --- a/packages/bpk-component-chip-group/index.ts +++ b/packages/bpk-component-chip-group/index.ts @@ -22,6 +22,7 @@ import BpkChipGroup, { CHIP_GROUP_TYPES, type ChipItem, type SingleSelectChipItem, + CHIP_COMPONENT, } from './src/BpkChipGroup'; import BpkChipGroupSingleSelect, { type SingleSelectProps, @@ -30,5 +31,5 @@ import BpkChipGroupSingleSelect, { } from './src/BpkChipGroupSingleSelect'; export type { ChipGroupProps, SingleSelectProps, SingleSelectStateProps, ChipItem, SingleSelectChipItem }; -export { BpkChipGroupState, CHIP_GROUP_TYPES, BpkChipGroupSingleSelect , BpkChipGroupSingleSelectState}; +export { BpkChipGroupState, CHIP_COMPONENT, CHIP_GROUP_TYPES, BpkChipGroupSingleSelect, BpkChipGroupSingleSelectState}; export default BpkChipGroup; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx index c8eba66095..cb53dbc878 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx @@ -20,9 +20,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import '@testing-library/jest-dom'; -import BpkSelectableChip, { BpkDismissibleChip, BpkDropdownChip } from '../../bpk-component-chip'; - -import BpkChipGroup, { BpkChipGroupState, CHIP_GROUP_TYPES } from './BpkChipGroup'; +import BpkChipGroup, { BpkChipGroupState, CHIP_COMPONENT, CHIP_GROUP_TYPES } from './BpkChipGroup'; const defaultProps = { type: CHIP_GROUP_TYPES.wrap, @@ -47,12 +45,12 @@ describe('BpkChipGroup', () => { ]; it('should render correctly with type = rail', () => { - const { asFragment } = render(); + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); it('should render correctly with type = wrap', () => { - const { asFragment } = render(); + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); @@ -85,15 +83,15 @@ describe('BpkChipGroup', () => { const alternativeChips = [ { text: 'London', - component: BpkDismissibleChip, + component: CHIP_COMPONENT.dismissible, }, { text: 'Berlin', - component: BpkDropdownChip, + component: CHIP_COMPONENT.dropdown, }, { text: 'Florence', - component: BpkSelectableChip, + component: CHIP_COMPONENT.selectable, }, ]; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.css b/packages/bpk-component-chip-group/src/BpkChipGroup.module.css index 3665ec2ea8..65ed3ee515 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.css +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.css @@ -14,5 +14,5 @@ * 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. -*/ -@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-container{display:flex;align-items:center;white-space:nowrap}.bpk-chip-group{display:flex;gap:0.5rem}.bpk-chip-group--rail{padding:.25rem 0;flex-wrap:nowrap}.bpk-chip-group--wrap{flex-wrap:wrap}.bpk-sticky-chip-container{margin-inline-end:0.5rem;padding-inline-end:0.5rem;box-shadow:-1px 0 0 0 #c2c9cd inset}html[dir='rtl'] .bpk-sticky-chip-container{box-shadow:1px 0 0 0 #c2c9cd inset}.bpk-sticky-chip-container--on-dark{box-shadow:-1px 0 0 0 rgba(255,255,255,0.5) inset}html[dir='rtl'] .bpk-sticky-chip-container--on-dark{box-shadow:1px 0 0 0 rgba(255,255,255,0.5) inset}@media (max-width: 64rem){.bpk-sticky-chip{padding-inline-end:0;padding-inline-start:0.5rem}.bpk-sticky-chip span:not(:nth-of-type(1)){display:none}}.bpk-chip-group-scroller-indicator::before,.bpk-chip-group-scroller-indicator::after{display:none} + */ +.bpk-chip-group-container{display:flex;align-items:center;white-space:nowrap}.bpk-chip-group{display:flex;gap:.5rem}.bpk-chip-group--rail{padding:.125rem 0;flex-wrap:nowrap}.bpk-chip-group--wrap{flex-wrap:wrap}.bpk-sticky-chip-container{margin-inline-end:.5rem;padding-inline-end:.5rem;box-shadow:-1px 0 0 0 #c1c7cf inset}html[dir=rtl] .bpk-sticky-chip-container{box-shadow:1px 0 0 0 #c1c7cf inset}.bpk-sticky-chip-container--on-dark{box-shadow:-1px 0 0 0 rgba(255,255,255,.5) inset}html[dir=rtl] .bpk-sticky-chip-container--on-dark{box-shadow:1px 0 0 0 rgba(255,255,255,.5) inset}.bpk-chip-group-scroller-indicator::before,.bpk-chip-group-scroller-indicator::after{display:none} \ No newline at end of file diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss index 9d5e6adee3..1d837b65d6 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss @@ -31,7 +31,7 @@ gap: tokens.bpk-spacing-md(); &--rail { - padding: 4 * tokens.$bpk-one-pixel-rem 0; + padding: 2 * tokens.$bpk-one-pixel-rem 0; flex-wrap: nowrap; } diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx index 649b716d75..95deb702c4 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -19,7 +19,7 @@ import type { ReactElement, ReactNode} from 'react'; import { useRef, useState } from 'react'; import { cssModules } from '../../bpk-react-utils'; -import BpkSelectableChip, { BpkIconChip, type BpkSelectableChipProps, CHIP_TYPES } from '../../bpk-component-chip'; +import BpkSelectableChip, { BpkDismissibleChip, BpkIconChip, BpkDropdownChip, CHIP_TYPES } from '../../bpk-component-chip'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import BpkMobileScrollContainer from '../../bpk-component-mobile-scroll-container'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. @@ -35,9 +35,27 @@ const getClassName = cssModules(STYLES); export const CHIP_GROUP_TYPES = { rail: 'rail', wrap: 'wrap', +}; + + +export const CHIP_COMPONENT = { + selectable: 'selectable', + dismissible: 'dismissible', + dropdown: 'dropdown', + icon: 'icon', +}; + +const CHIP_COMPONENT_MAP = { + [CHIP_COMPONENT.selectable]: BpkSelectableChip, + [CHIP_COMPONENT.dismissible]: BpkDismissibleChip, + [CHIP_COMPONENT.dropdown]: BpkDropdownChip, + [CHIP_COMPONENT.icon]: BpkIconChip, } + export type ChipGroupType = (typeof CHIP_GROUP_TYPES)[keyof typeof CHIP_GROUP_TYPES]; export type ChipStyleType = (typeof CHIP_TYPES)[keyof typeof CHIP_TYPES]; +export type ChipComponentType = (typeof CHIP_COMPONENT)[keyof typeof CHIP_COMPONENT]; + export type SingleSelectChipItem = { text: string; @@ -48,24 +66,19 @@ export type SingleSelectChipItem = { }; export type ChipItem = { - component?: (props: BpkSelectableChipProps) => ReactElement; + component?: ChipComponentType; onClick?: (selected: boolean, index: number) => void; selected?: boolean; + hidden?: boolean; } & SingleSelectChipItem; -type AccessibilityLabels = { - ariaLabel: string; - ariaLabelledBy?: never; -} | { - ariaLabel?: never; - ariaLabelledBy: string; -}; - export type CommonProps = { - type: ChipGroupType; + ariaLabel?: string; + ariaLabelledBy?: string; + type?: ChipGroupType; className?: string | null; - style?: ChipStyleType; -} & AccessibilityLabels; + chipStyle?: ChipStyleType; +}; export type ChipGroupProps = { chips: ChipItem[]; @@ -73,7 +86,22 @@ export type ChipGroupProps = { ariaMultiselectable?: boolean; } & CommonProps; -const BpkChipGroup = ({ ariaLabel, ariaLabelledBy, ariaMultiselectable = true, chips, className = null, stickyChip, style = CHIP_TYPES.default, type = CHIP_GROUP_TYPES.rail }: ChipGroupProps) => { + +const BpkChipGroup = ({ + ariaLabel, + ariaLabelledBy, + ariaMultiselectable = true, + chipStyle = CHIP_TYPES.default, + chips, + className, + stickyChip, + type = CHIP_GROUP_TYPES.rail, +}: ChipGroupProps) => { + if ((ariaLabel && ariaLabelledBy) || (!ariaLabel && !ariaLabelledBy)) { + // eslint-disable-next-line no-console + console.warn("BpkChipGroup: Bad combination of aria props. Exactly one of props ariaLabel or ariaLabelledBy should be used.") + } + const scrollContainerRef = useRef(null); const containerClassnames = getClassName( @@ -88,28 +116,41 @@ const BpkChipGroup = ({ ariaLabel, ariaLabelledBy, ariaMultiselectable = true, c const stickyChipContainerClassnames = getClassName( 'bpk-sticky-chip-container', - `bpk-sticky-chip-container--${style}`, + `bpk-sticky-chip-container--${chipStyle}`, ); const scrollContainerIndicator = getClassName('bpk-chip-group-scroller-indicator'); - const renderChipItem = ({ accessibilityLabel, component: Component = BpkSelectableChip, onClick, selected, text, ...rest }: ChipItem, index: number) => ( - { - if (onClick) { - onClick(!selected, index); - } - }} - role="option" - {...rest} - > - {text} - - ); + const renderChipItem = ({ + accessibilityLabel, + component = CHIP_COMPONENT.selectable, + hidden = false, + leadingAccessoryView = null, + onClick, + selected, + text, + ...rest + }: ChipItem, index: number) => { + const Component = CHIP_COMPONENT_MAP[component]; + return !hidden && ( + { + if (onClick) { + onClick(!selected, index); + } + }} + role="option" + leadingAccessoryView={leadingAccessoryView} + {...rest} + > + {text} + + ); + } // TODO: Fix focus indicators being cutoff when type = rail const wrapRailInScroll = (children: ReactElement) => @@ -127,7 +168,7 @@ const BpkChipGroup = ({ ariaLabel, ariaLabelledBy, ariaMultiselectable = true, c
{type === CHIP_GROUP_TYPES.rail && ( - + )} {type === CHIP_GROUP_TYPES.rail && stickyChip && @@ -135,7 +176,7 @@ const BpkChipGroup = ({ ariaLabel, ariaLabelledBy, ariaMultiselectable = true, c {(isDesktop) => renderChipItem({ ...stickyChip, - component: isDesktop ? BpkSelectableChip : BpkIconChip, + component: isDesktop ? CHIP_COMPONENT.selectable : CHIP_COMPONENT.icon, leadingAccessoryView: , }, -1)} @@ -149,12 +190,12 @@ const BpkChipGroup = ({ ariaLabel, ariaLabelledBy, ariaMultiselectable = true, c aria-label={ariaLabel} aria-labelledby={ariaLabelledBy} > - {chips.map((chip, index) => chip && renderChipItem(chip, index))} + {chips.map((chip, index) => renderChipItem(chip, index))}
)} {type === CHIP_GROUP_TYPES.rail && ( - + )} diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 7a2a9b1a51..03d2e98b79 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -16,9 +16,10 @@ * limitations under the License. */ -import { render } from '@testing-library/react'; import type { MutableRefObject } from 'react'; +import { render } from '@testing-library/react'; + import { CHIP_TYPES } from '../../bpk-component-chip'; import Nudger from './Nudger'; @@ -33,7 +34,7 @@ jest.mock('../../bpk-react-utils/index', () => ({ const mockScrollContainerRef = { current: { - scrollBy: jest.fn(), + scrollBy: jest.fn() as (options?: any) => void, offsetWidth: 70, scrollLeft: 10, scrollWidth: 100, diff --git a/packages/bpk-component-chip-group/src/Nudger.module.css b/packages/bpk-component-chip-group/src/Nudger.module.css index 88c240733b..05b130a0e9 100644 --- a/packages/bpk-component-chip-group/src/Nudger.module.css +++ b/packages/bpk-component-chip-group/src/Nudger.module.css @@ -14,5 +14,5 @@ * 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. -*/ -@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-nudger{position:absolute}.bpk-chip-group-nudger--leading{margin-inline-end:0.5rem}.bpk-chip-group-nudger--trailing{margin-inline-start:0.5rem}.bpk-chip-group-nudger--hidden{display:none} + */ +.bpk-chip-group-nudger--leading{margin-inline-end:.5rem}.bpk-chip-group-nudger--trailing{margin-inline-start:.5rem}.bpk-chip-group-nudger--hidden{display:none} \ No newline at end of file diff --git a/packages/bpk-component-chip-group/src/accessibility-test.tsx b/packages/bpk-component-chip-group/src/accessibility-test.tsx index 299ee0cdcb..efbce030a1 100644 --- a/packages/bpk-component-chip-group/src/accessibility-test.tsx +++ b/packages/bpk-component-chip-group/src/accessibility-test.tsx @@ -73,9 +73,6 @@ describe('BpkChipGroupSingleSelect accessibility tests', () => { const { container } = render( Date: Fri, 12 Apr 2024 12:39:14 +0100 Subject: [PATCH 18/79] a11y improvements --- .../src/BpkChipGroup.module.scss | 3 ++ .../src/BpkChipGroup.tsx | 30 ++++++------------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss index 1d837b65d6..f9ab2d6155 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss @@ -28,6 +28,9 @@ .bpk-chip-group { display: flex; + margin: 0; + padding: 0; + border: none; gap: tokens.bpk-spacing-md(); &--rail { diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx index 95deb702c4..8d4e35c2fd 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -37,7 +37,6 @@ export const CHIP_GROUP_TYPES = { wrap: 'wrap', }; - export const CHIP_COMPONENT = { selectable: 'selectable', dismissible: 'dismissible', @@ -56,7 +55,6 @@ export type ChipGroupType = (typeof CHIP_GROUP_TYPES)[keyof typeof CHIP_GROUP_TY export type ChipStyleType = (typeof CHIP_TYPES)[keyof typeof CHIP_TYPES]; export type ChipComponentType = (typeof CHIP_COMPONENT)[keyof typeof CHIP_COMPONENT]; - export type SingleSelectChipItem = { text: string; accessibilityLabel?: string; @@ -74,7 +72,6 @@ export type ChipItem = { export type CommonProps = { ariaLabel?: string; - ariaLabelledBy?: string; type?: ChipGroupType; className?: string | null; chipStyle?: ChipStyleType; @@ -89,7 +86,6 @@ export type ChipGroupProps = { const BpkChipGroup = ({ ariaLabel, - ariaLabelledBy, ariaMultiselectable = true, chipStyle = CHIP_TYPES.default, chips, @@ -97,11 +93,6 @@ const BpkChipGroup = ({ stickyChip, type = CHIP_GROUP_TYPES.rail, }: ChipGroupProps) => { - if ((ariaLabel && ariaLabelledBy) || (!ariaLabel && !ariaLabelledBy)) { - // eslint-disable-next-line no-console - console.warn("BpkChipGroup: Bad combination of aria props. Exactly one of props ariaLabel or ariaLabelledBy should be used.") - } - const scrollContainerRef = useRef(null); const containerClassnames = getClassName( @@ -143,7 +134,7 @@ const BpkChipGroup = ({ onClick(!selected, index); } }} - role="option" + role={ariaMultiselectable ? 'checkbox' : 'radio'} leadingAccessoryView={leadingAccessoryView} {...rest} > @@ -152,7 +143,6 @@ const BpkChipGroup = ({ ); } - // TODO: Fix focus indicators being cutoff when type = rail const wrapRailInScroll = (children: ReactElement) => type === CHIP_GROUP_TYPES.rail ? ( {(isDesktop) => renderChipItem({ - ...stickyChip, + role: 'button', component: isDesktop ? CHIP_COMPONENT.selectable : CHIP_COMPONENT.icon, leadingAccessoryView: , + ...stickyChip, }, -1)} } {wrapRailInScroll( -
+ {ariaLabel && {ariaLabel}} {chips.map((chip, index) => renderChipItem(chip, index))} -
+ , )} {type === CHIP_GROUP_TYPES.rail && ( @@ -217,10 +206,9 @@ export const BpkChipGroupState = ({ chips, ...rest }: ChipGroupProps) => { nextSelectedChips[selectedIndex] = selected; setSelectedChips(nextSelectedChips); }, - })) + })); return }; - export default BpkChipGroup; From adea07646858a6fa56c86baf055ac951dfefcb23 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 12 Apr 2024 12:43:48 +0100 Subject: [PATCH 19/79] generate type declaration files --- packages/bpk-component-chip-group/README.md | 1 + packages/bpk-component-chip-group/index.d.ts | 26 ++++++++++-- .../src/BpkChipGroup.d.ts | 40 ++++++++++++++++--- .../src/BpkChipGroupSingleSelect.d.ts | 25 +++++++++++- .../bpk-component-chip-group/src/Nudger.d.ts | 22 +++++++++- 5 files changed, 100 insertions(+), 14 deletions(-) diff --git a/packages/bpk-component-chip-group/README.md b/packages/bpk-component-chip-group/README.md index cb3e191684..3ac7649373 100644 --- a/packages/bpk-component-chip-group/README.md +++ b/packages/bpk-component-chip-group/README.md @@ -47,6 +47,7 @@ const VerticalsExample = () => { ReactElement; + component?: ChipComponentType; onClick?: (selected: boolean, index: number) => void; selected?: boolean; + hidden?: boolean; } & SingleSelectChipItem; export type CommonProps = { - type: ChipGroupType; + ariaLabel?: string; + type?: ChipGroupType; className?: string | null; - style?: ChipStyleType; + chipStyle?: ChipStyleType; }; export type ChipGroupProps = { chips: ChipItem[]; stickyChip?: ChipItem; + ariaMultiselectable?: boolean; } & CommonProps; -declare const BpkChipGroup: ({ className, chips, type, style, stickyChip }: ChipGroupProps) => JSX.Element; +declare const BpkChipGroup: ({ ariaLabel, ariaMultiselectable, chipStyle, chips, className, stickyChip, type, }: ChipGroupProps) => JSX.Element; export declare const BpkChipGroupState: ({ chips, ...rest }: ChipGroupProps) => JSX.Element; export default BpkChipGroup; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.d.ts b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.d.ts index 0437aad3a7..a2b4e6ea1a 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.d.ts +++ b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.d.ts @@ -1,10 +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. + */ + /// -import { SingleSelectChipItem, CommonProps } from './BpkChipGroup'; +import { type SingleSelectChipItem, type CommonProps } from './BpkChipGroup'; export type SingleSelectProps = { chips: SingleSelectChipItem[]; selectedIndex?: number; onItemClick?: (item: SingleSelectChipItem, selected: boolean, index: number) => void; } & CommonProps; declare const BpkChipGroupSingleSelect: ({ chips, onItemClick, selectedIndex, ...rest }: SingleSelectProps) => JSX.Element; -export declare const BpkChipGroupSingleSelectState: ({ selectedIndex: defaultSelectedIndex, onItemClick, ...rest }: SingleSelectProps) => JSX.Element; +export type SingleSelectStateProps = { + initiallySelectedIndex?: number; +} & Omit; +export declare const BpkChipGroupSingleSelectState: ({ initiallySelectedIndex, onItemClick, ...rest }: SingleSelectStateProps) => JSX.Element; export default BpkChipGroupSingleSelect; diff --git a/packages/bpk-component-chip-group/src/Nudger.d.ts b/packages/bpk-component-chip-group/src/Nudger.d.ts index b758012a3d..d351da1431 100644 --- a/packages/bpk-component-chip-group/src/Nudger.d.ts +++ b/packages/bpk-component-chip-group/src/Nudger.d.ts @@ -1,7 +1,25 @@ +/* + * 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 MutableRefObject } from 'react'; import type { ChipStyleType } from './BpkChipGroup'; -import { MutableRefObject } from 'react'; type Props = { - chipStyle: ChipStyleType; + chipStyle?: ChipStyleType; scrollContainerRef: MutableRefObject; leading?: boolean; }; From b3452ff12ed1c5d7ca6278b0d292c735de1a1233 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 12 Apr 2024 12:51:44 +0100 Subject: [PATCH 20/79] build css --- packages/bpk-component-chip-group/src/BpkChipGroup.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.css b/packages/bpk-component-chip-group/src/BpkChipGroup.module.css index 65ed3ee515..2ccd92e55e 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.css +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.css @@ -15,4 +15,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -.bpk-chip-group-container{display:flex;align-items:center;white-space:nowrap}.bpk-chip-group{display:flex;gap:.5rem}.bpk-chip-group--rail{padding:.125rem 0;flex-wrap:nowrap}.bpk-chip-group--wrap{flex-wrap:wrap}.bpk-sticky-chip-container{margin-inline-end:.5rem;padding-inline-end:.5rem;box-shadow:-1px 0 0 0 #c1c7cf inset}html[dir=rtl] .bpk-sticky-chip-container{box-shadow:1px 0 0 0 #c1c7cf inset}.bpk-sticky-chip-container--on-dark{box-shadow:-1px 0 0 0 rgba(255,255,255,.5) inset}html[dir=rtl] .bpk-sticky-chip-container--on-dark{box-shadow:1px 0 0 0 rgba(255,255,255,.5) inset}.bpk-chip-group-scroller-indicator::before,.bpk-chip-group-scroller-indicator::after{display:none} \ No newline at end of file +.bpk-chip-group-container{display:flex;align-items:center;white-space:nowrap}.bpk-chip-group{display:flex;margin:0;padding:0;border:none;gap:.5rem}.bpk-chip-group--rail{padding:.125rem 0;flex-wrap:nowrap}.bpk-chip-group--wrap{flex-wrap:wrap}.bpk-sticky-chip-container{margin-inline-end:.5rem;padding-inline-end:.5rem;box-shadow:-1px 0 0 0 #c1c7cf inset}html[dir=rtl] .bpk-sticky-chip-container{box-shadow:1px 0 0 0 #c1c7cf inset}.bpk-sticky-chip-container--on-dark{box-shadow:-1px 0 0 0 rgba(255,255,255,.5) inset}html[dir=rtl] .bpk-sticky-chip-container--on-dark{box-shadow:1px 0 0 0 rgba(255,255,255,.5) inset}.bpk-chip-group-scroller-indicator::before,.bpk-chip-group-scroller-indicator::after{display:none} \ No newline at end of file From e3d05f15c8fbc2112242ba7c90c145ccf7e15643 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 12 Apr 2024 13:01:46 +0100 Subject: [PATCH 21/79] remove custom classname support --- .../src/BpkChipGroup-test.tsx | 11 ----------- .../bpk-component-chip-group/src/BpkChipGroup.d.ts | 4 +--- .../bpk-component-chip-group/src/BpkChipGroup.tsx | 8 +------- .../src/BpkChipGroupSingleSelect-test.tsx | 11 ----------- 4 files changed, 2 insertions(+), 32 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx index cb53dbc878..348daff3e7 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx @@ -54,17 +54,6 @@ describe('BpkChipGroup', () => { expect(asFragment()).toMatchSnapshot(); }); - it('should support custom class names', () => { - const { asFragment } = render( - , - ); - expect(asFragment()).toMatchSnapshot(); - }); - it('should render correctly with sticky chip', () => { const { asFragment } = render( JSX.Element; +declare const BpkChipGroup: ({ ariaLabel, ariaMultiselectable, chipStyle, chips, stickyChip, type, }: ChipGroupProps) => JSX.Element; export declare const BpkChipGroupState: ({ chips, ...rest }: ChipGroupProps) => JSX.Element; export default BpkChipGroup; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx index 8d4e35c2fd..29445653a0 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -59,7 +59,6 @@ export type SingleSelectChipItem = { text: string; accessibilityLabel?: string; leadingAccessoryView?: ReactNode; - className?: string; [rest: string]: any; // Inexact rest. See decisions/inexact-rest.md }; @@ -73,7 +72,6 @@ export type ChipItem = { export type CommonProps = { ariaLabel?: string; type?: ChipGroupType; - className?: string | null; chipStyle?: ChipStyleType; }; @@ -89,16 +87,12 @@ const BpkChipGroup = ({ ariaMultiselectable = true, chipStyle = CHIP_TYPES.default, chips, - className, stickyChip, type = CHIP_GROUP_TYPES.rail, }: ChipGroupProps) => { const scrollContainerRef = useRef(null); - const containerClassnames = getClassName( - className, - 'bpk-chip-group-container', - ) + const containerClassnames = getClassName('bpk-chip-group-container') const chipGroupClassNames = getClassName( 'bpk-chip-group', diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx index 758477aa61..1b9ec3ded3 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx @@ -100,17 +100,6 @@ describe('BpkChipGroupSingleSelect', () => { expect(screen.getByRole('option', { name: 'London' })).not.toHaveClass('bpk-chip--default-selected') expect(screen.getByRole('option', { name: 'Florence' })).not.toHaveClass('bpk-chip--default-selected') }); - - it('should support custom class names', () => { - const { asFragment } = render( - , - ); - expect(asFragment()).toMatchSnapshot(); - }); }); describe('BpkChipGroupSingleSelectState', () => { From 708d7efb5030fa50ca76f77471f86383e1c6686d Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 12 Apr 2024 13:13:04 +0100 Subject: [PATCH 22/79] remove classname from nudger --- packages/bpk-component-chip-group/src/BpkChipGroup.tsx | 6 +++--- packages/bpk-component-chip-group/src/Nudger.tsx | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx index 29445653a0..4c0fa2ea4c 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -18,13 +18,13 @@ import type { ReactElement, ReactNode} from 'react'; import { useRef, useState } from 'react'; -import { cssModules } from '../../bpk-react-utils'; +import BpkBreakpoint, { BREAKPOINTS } from '../../bpk-component-breakpoint'; import BpkSelectableChip, { BpkDismissibleChip, BpkIconChip, BpkDropdownChip, CHIP_TYPES } from '../../bpk-component-chip'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. +import FilterIconSm from '../../bpk-component-icon/sm/filter'; import BpkMobileScrollContainer from '../../bpk-component-mobile-scroll-container'; +import { cssModules } from '../../bpk-react-utils'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. -import FilterIconSm from '../../bpk-component-icon/sm/filter'; -import BpkBreakpoint, { BREAKPOINTS } from '../../bpk-component-breakpoint'; import Nudger from './Nudger'; diff --git a/packages/bpk-component-chip-group/src/Nudger.tsx b/packages/bpk-component-chip-group/src/Nudger.tsx index 256416ec57..bc601e8e9d 100644 --- a/packages/bpk-component-chip-group/src/Nudger.tsx +++ b/packages/bpk-component-chip-group/src/Nudger.tsx @@ -21,11 +21,11 @@ import { type MutableRefObject, useEffect, useState } from 'react'; import { BpkButtonV2, BUTTON_TYPES } from '../../bpk-component-button'; import { CHIP_TYPES } from '../../bpk-component-chip'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. +import { withButtonAlignment } from '../../bpk-component-icon/index'; import ArrowLeft from '../../bpk-component-icon/sm/long-arrow-left'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import ArrowRight from '../../bpk-component-icon/sm/long-arrow-right'; import { cssModules, isRTL } from '../../bpk-react-utils/index'; -import { withButtonAlignment } from '../../bpk-component-icon/index'; import type { ChipStyleType } from './BpkChipGroup'; @@ -85,8 +85,8 @@ const Nudger = ({chipStyle = CHIP_TYPES.default, leading = false, scrollContaine ) return ( +
{isLeft ? : } +
); } From 6b4a6627e784eec2bf81cbcab7bdbd5b0311def7 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 12 Apr 2024 13:14:03 +0100 Subject: [PATCH 23/79] fix untyped imports --- packages/bpk-component-chip-group/src/BpkChipGroup.tsx | 2 +- packages/bpk-component-chip-group/src/Nudger.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx index 4c0fa2ea4c..76ff7fb40e 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -22,9 +22,9 @@ import BpkBreakpoint, { BREAKPOINTS } from '../../bpk-component-breakpoint'; import BpkSelectableChip, { BpkDismissibleChip, BpkIconChip, BpkDropdownChip, CHIP_TYPES } from '../../bpk-component-chip'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import FilterIconSm from '../../bpk-component-icon/sm/filter'; +// @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import BpkMobileScrollContainer from '../../bpk-component-mobile-scroll-container'; import { cssModules } from '../../bpk-react-utils'; -// @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import Nudger from './Nudger'; diff --git a/packages/bpk-component-chip-group/src/Nudger.tsx b/packages/bpk-component-chip-group/src/Nudger.tsx index bc601e8e9d..52c6fe885a 100644 --- a/packages/bpk-component-chip-group/src/Nudger.tsx +++ b/packages/bpk-component-chip-group/src/Nudger.tsx @@ -20,8 +20,8 @@ import { type MutableRefObject, useEffect, useState } from 'react'; import { BpkButtonV2, BUTTON_TYPES } from '../../bpk-component-button'; import { CHIP_TYPES } from '../../bpk-component-chip'; -// @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import { withButtonAlignment } from '../../bpk-component-icon/index'; +// @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import ArrowLeft from '../../bpk-component-icon/sm/long-arrow-left'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import ArrowRight from '../../bpk-component-icon/sm/long-arrow-right'; From 9c13146daa59b84eae18aff83106db3e9f1536c6 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 12 Apr 2024 13:39:40 +0100 Subject: [PATCH 24/79] update tests with a11y improvements --- .../src/BpkChipGroup-test.tsx | 12 +- .../src/BpkChipGroupSingleSelect-test.tsx | 20 +- .../__snapshots__/BpkChipGroup-test.tsx.snap | 106 ++---- .../BpkChipGroupSingleSelect-test.tsx.snap | 89 +---- .../src/__snapshots__/Nudger-test.tsx.snap | 304 +++++++----------- 5 files changed, 158 insertions(+), 373 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx index 348daff3e7..8522b66a95 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup-test.tsx @@ -150,7 +150,7 @@ describe('BpkChipGroupState', () => { />, ); - const chip = screen.getByRole('option', { name: 'Berlin' }); + const chip = screen.getByRole('checkbox', { name: 'Berlin' }); await user.click(chip); expect(chip).toHaveClass('bpk-chip--default-selected'); @@ -166,8 +166,8 @@ describe('BpkChipGroupState', () => { />, ); - const berlinChip = screen.getByRole('option', { name: 'Berlin' }); - const londonChip = screen.getByRole('option', { name: 'London' }); + const berlinChip = screen.getByRole('checkbox', { name: 'Berlin' }); + const londonChip = screen.getByRole('checkbox', { name: 'London' }); await user.click(berlinChip); await user.click(londonChip); @@ -186,7 +186,7 @@ describe('BpkChipGroupState', () => { />, ); - const chip = screen.getByRole('option', { name: 'Berlin' }); + const chip = screen.getByRole('checkbox', { name: 'Berlin' }); await user.click(chip); expect(chip).toHaveClass('bpk-chip--default-selected'); @@ -206,7 +206,7 @@ describe('BpkChipGroupState', () => { />, ); - await user.click(screen.getByRole('option', { name: 'Berlin' })); + await user.click(screen.getByRole('checkbox', { name: 'Berlin' })); const { onClick } = chips[1]; @@ -230,7 +230,7 @@ describe('BpkChipGroupState', () => { />, ); - const chip = screen.getByRole('option', { name: 'Berlin' }); + const chip = screen.getByRole('checkbox', { name: 'Berlin' }); expect(chip).toHaveClass('bpk-chip--default-selected'); }); diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx index 1b9ec3ded3..bbec0ca3ef 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx @@ -16,8 +16,8 @@ * limitations under the License. */ -import userEvent from '@testing-library/user-event'; import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { CHIP_GROUP_TYPES } from './BpkChipGroup'; import BpkChipGroupSingleSelect, { BpkChipGroupSingleSelectState } from './BpkChipGroupSingleSelect'; @@ -96,9 +96,9 @@ describe('BpkChipGroupSingleSelect', () => { />, ); - expect(screen.getByRole('option', { name: 'Berlin' })).toHaveClass('bpk-chip--default-selected') - expect(screen.getByRole('option', { name: 'London' })).not.toHaveClass('bpk-chip--default-selected') - expect(screen.getByRole('option', { name: 'Florence' })).not.toHaveClass('bpk-chip--default-selected') + expect(screen.getByRole('radio', { name: 'Berlin' })).toHaveClass('bpk-chip--default-selected') + expect(screen.getByRole('radio', { name: 'London' })).not.toHaveClass('bpk-chip--default-selected') + expect(screen.getByRole('radio', { name: 'Florence' })).not.toHaveClass('bpk-chip--default-selected') }); }); @@ -125,7 +125,7 @@ describe('BpkChipGroupSingleSelectState', () => { />, ); - const chip = screen.getByRole('option', { name: 'Berlin' }); + const chip = screen.getByRole('radio', { name: 'Berlin' }); await user.click(chip); @@ -142,13 +142,13 @@ describe('BpkChipGroupSingleSelectState', () => { />, ); - const berlinChip = screen.getByRole('option', { name: 'Berlin' }); + const berlinChip = screen.getByRole('radio', { name: 'Berlin' }); await user.click(berlinChip); expect(berlinChip).toHaveClass('bpk-chip--default-selected'); - const londonChip = screen.getByRole('option', { name: 'London' }); + const londonChip = screen.getByRole('radio', { name: 'London' }); await user.click(londonChip); @@ -166,7 +166,7 @@ describe('BpkChipGroupSingleSelectState', () => { />, ); - const chip = screen.getByRole('option', { name: 'Berlin' }); + const chip = screen.getByRole('radio', { name: 'Berlin' }); await user.click(chip); @@ -190,7 +190,7 @@ describe('BpkChipGroupSingleSelectState', () => { />, ); - await user.click(screen.getByRole('option', { name: 'Berlin' })); + await user.click(screen.getByRole('radio', { name: 'Berlin' })); expect(onItemClick).toHaveBeenCalledTimes(1); expect(onItemClick).toHaveBeenCalledWith(chips[1], true, 1); @@ -205,7 +205,7 @@ describe('BpkChipGroupSingleSelectState', () => { />, ); - const chip = screen.getByRole('option', { name: 'Berlin' }); + const chip = screen.getByRole('radio', { name: 'Berlin' }); expect(chip).toHaveClass('bpk-chip--default-selected'); }); diff --git a/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap b/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap index 89e4feff20..49e732368d 100644 --- a/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap +++ b/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap @@ -5,13 +5,15 @@ exports[`BpkChipGroup should render correctly with all chip component types 1`]
-
+ + a11y label + -
+
`; @@ -284,85 +286,19 @@ exports[`BpkChipGroup should render correctly with type = wrap 1`] = `
-
- - + a11y label + - -
-
- -`; - -exports[`BpkChipGroup should support custom class names 1`] = ` - -
-
- -
+
`; diff --git a/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroupSingleSelect-test.tsx.snap b/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroupSingleSelect-test.tsx.snap index 6b500176d8..aa086591f1 100644 --- a/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroupSingleSelect-test.tsx.snap +++ b/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroupSingleSelect-test.tsx.snap @@ -87,86 +87,19 @@ exports[`BpkChipGroupSingleSelect should render correctly with type = wrap 1`] =
-
- - - - -
-
- -`; - -exports[`BpkChipGroupSingleSelect should support custom class names 1`] = ` - -
-
+ Filter cities + -
+
`; diff --git a/packages/bpk-component-chip-group/src/__snapshots__/Nudger-test.tsx.snap b/packages/bpk-component-chip-group/src/__snapshots__/Nudger-test.tsx.snap index 836b6df2ce..2374bcd4c4 100644 --- a/packages/bpk-component-chip-group/src/__snapshots__/Nudger-test.tsx.snap +++ b/packages/bpk-component-chip-group/src/__snapshots__/Nudger-test.tsx.snap @@ -1,235 +1,151 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Nudger should render correctly 1`] = ` - - - -`; - -exports[`Nudger should render correctly for chip style: default 1`] = ` - - - -`; - -exports[`Nudger should render correctly for chip style: on-dark 1`] = ` - - - -`; - -exports[`Nudger should render correctly for chip style: on-image 1`] = ` - - - -`; - exports[`Nudger should render correctly for onDark chip style 1`] = ` - + + + + `; exports[`Nudger should render correctly when leading=false and isRtl=false 1`] = ` - + + + + `; exports[`Nudger should render correctly when leading=false and isRtl=true 1`] = ` - + + + + `; exports[`Nudger should render correctly when leading=true and isRtl=false 1`] = ` - + + + + `; exports[`Nudger should render correctly when leading=true and isRtl=true 1`] = ` - + + + + `; From 38e1791c325bab04033d5d93531a03db74ef2b89 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Tue, 16 Apr 2024 09:10:18 +0100 Subject: [PATCH 25/79] add nudger labels --- .../bpk-component-chip-group/examples.tsx | 12 ++ packages/bpk-component-chip-group/README.md | 4 + .../src/BpkChipGroup-test.tsx | 10 ++ .../src/BpkChipGroup.tsx | 8 +- .../src/BpkChipGroupSingleSelect-test.tsx | 18 ++- .../bpk-component-chip-group/src/Nudger.tsx | 9 +- .../__snapshots__/BpkChipGroup-test.tsx.snap | 150 +++++++++++++++--- .../BpkChipGroupSingleSelect-test.tsx.snap | 74 +++++++-- .../src/accessibility-test.tsx | 12 ++ 9 files changed, 261 insertions(+), 36 deletions(-) diff --git a/examples/bpk-component-chip-group/examples.tsx b/examples/bpk-component-chip-group/examples.tsx index 78b10eff0e..993f682389 100644 --- a/examples/bpk-component-chip-group/examples.tsx +++ b/examples/bpk-component-chip-group/examples.tsx @@ -102,6 +102,8 @@ export const BpkChipGroupRail = () => ( type={CHIP_GROUP_TYPES.rail} chips={chips} ariaLabel="Select cities" + leadingNudgerLabel="Scroll back" + trailingNudgerLabel="Scroll forward" /> ); @@ -119,6 +121,8 @@ export const BpkChipGroupSticky = () => { chips={chips} stickyChip={stickyChip} ariaLabel="Select cities" + leadingNudgerLabel="Scroll back" + trailingNudgerLabel="Scroll forward" /> ); @@ -137,6 +141,8 @@ export const OnContrastChipGroup = () => { stickyChip={stickyChip} chipStyle={CHIP_TYPES.default} ariaLabel="Select cities" + leadingNudgerLabel="Scroll back" + trailingNudgerLabel="Scroll forward" /> ); @@ -156,6 +162,8 @@ export const OnDarkChipGroup = () => { stickyChip={stickyChip} chipStyle={CHIP_TYPES.onDark} ariaLabel="Select cities" + leadingNudgerLabel="Scroll back" + trailingNudgerLabel="Scroll forward" /> ); @@ -174,6 +182,8 @@ export const OnImageChipGroup = () => { stickyChip={stickyChip} chipStyle={CHIP_TYPES.onImage} ariaLabel="Select cities" + leadingNudgerLabel="Scroll back" + trailingNudgerLabel="Scroll forward" /> ); @@ -225,6 +235,8 @@ export const StateManagement = () => { type={CHIP_GROUP_TYPES.rail} ariaLabel="Filter your search" ariaMultiselectable={false} + leadingNudgerLabel="Scroll back" + trailingNudgerLabel="Scroll forward" chips={[{ text: 'Flights', selected: route === 'flights', diff --git a/packages/bpk-component-chip-group/README.md b/packages/bpk-component-chip-group/README.md index 3ac7649373..0ad0c82f6c 100644 --- a/packages/bpk-component-chip-group/README.md +++ b/packages/bpk-component-chip-group/README.md @@ -48,6 +48,8 @@ const VerticalsExample = () => { type={CHIP_GROUP_TYPES.rail} ariaLabel="Filter your search" ariaMultiselectable={false} + leadingNudgerLabel="Scroll back" + trailingNudgerLabel="Scroll forward" chips={[{ text: 'Flights', selected: route === 'flights', @@ -84,6 +86,8 @@ const StatefulExample = () => ( { + beforeEach(() => { + window.matchMedia = jest.fn().mockImplementation(() => ({ + matches: true, + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + })); + }); + const chips = [ { text: 'London', @@ -63,6 +71,8 @@ describe('BpkChipGroup', () => { chips={chips} type={CHIP_GROUP_TYPES.rail} ariaLabel="Filter cities" + leadingNudgerLabel="Scroll back" + trailingNudgerLabel="Scroll forward" />, ); expect(asFragment()).toMatchSnapshot(); diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx index 76ff7fb40e..26fd49a6ac 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -73,6 +73,8 @@ export type CommonProps = { ariaLabel?: string; type?: ChipGroupType; chipStyle?: ChipStyleType; + leadingNudgerLabel?: string; + trailingNudgerLabel?: stirng; }; export type ChipGroupProps = { @@ -87,7 +89,9 @@ const BpkChipGroup = ({ ariaMultiselectable = true, chipStyle = CHIP_TYPES.default, chips, + leadingNudgerLabel, stickyChip, + trailingNudgerLabel, type = CHIP_GROUP_TYPES.rail, }: ChipGroupProps) => { const scrollContainerRef = useRef(null); @@ -152,7 +156,7 @@ const BpkChipGroup = ({
{type === CHIP_GROUP_TYPES.rail && ( - + )} {type === CHIP_GROUP_TYPES.rail && stickyChip && @@ -178,7 +182,7 @@ const BpkChipGroup = ({ )} {type === CHIP_GROUP_TYPES.rail && ( - + )}
diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx index bbec0ca3ef..1bb879afcc 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx @@ -28,6 +28,14 @@ const defaultProps = { } describe('BpkChipGroupSingleSelect', () => { + beforeEach(() => { + window.matchMedia = jest.fn().mockImplementation(() => ({ + matches: true, + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + })); + }); + const chips = [ { text: 'London', @@ -44,7 +52,15 @@ describe('BpkChipGroupSingleSelect', () => { ]; it('should render correctly with type = rail', () => { - const { asFragment } = render(); + const { asFragment } = render( + + ); expect(asFragment()).toMatchSnapshot(); }); diff --git a/packages/bpk-component-chip-group/src/Nudger.tsx b/packages/bpk-component-chip-group/src/Nudger.tsx index 52c6fe885a..e3b8a21643 100644 --- a/packages/bpk-component-chip-group/src/Nudger.tsx +++ b/packages/bpk-component-chip-group/src/Nudger.tsx @@ -42,6 +42,7 @@ const CHIP_STYLE_TO_BUTTON_STYLE = { type Props = { + ariaLabel: string; chipStyle?: ChipStyleType; scrollContainerRef: MutableRefObject; leading?: boolean; @@ -54,7 +55,12 @@ const AlignedRightArrowIcon = withButtonAlignment(ArrowRight); // Chosen based on feeling good with the example stories const SCROLL_DISTANCE = 150; -const Nudger = ({chipStyle = CHIP_TYPES.default, leading = false, scrollContainerRef}: Props) => { +const Nudger = ({ + ariaLabel, + chipStyle = CHIP_TYPES.default, + leading = false, + scrollContainerRef +}: Props) => { const [show, setShow] = useState(false); const [enabled, setEnabled] = useState(true); @@ -87,6 +93,7 @@ const Nudger = ({chipStyle = CHIP_TYPES.default, leading = false, scrollContaine return (
+
+ +
-
+
+
+ +
`; @@ -205,6 +258,31 @@ exports[`BpkChipGroup should render correctly with type = rail 1`] = `
+
+ +
-
+ + a11y label + -
+
+
+ +
`; diff --git a/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroupSingleSelect-test.tsx.snap b/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroupSingleSelect-test.tsx.snap index aa086591f1..0cbb3bcc27 100644 --- a/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroupSingleSelect-test.tsx.snap +++ b/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroupSingleSelect-test.tsx.snap @@ -5,6 +5,32 @@ exports[`BpkChipGroupSingleSelect should render correctly with type = rail 1`] =
+
+ +
-
+ + Filter cities + -
+
+
+ +
`; diff --git a/packages/bpk-component-chip-group/src/accessibility-test.tsx b/packages/bpk-component-chip-group/src/accessibility-test.tsx index efbce030a1..94b07c7d65 100644 --- a/packages/bpk-component-chip-group/src/accessibility-test.tsx +++ b/packages/bpk-component-chip-group/src/accessibility-test.tsx @@ -40,6 +40,14 @@ const chips = [ ]; describe('BpkChipGroup accessibility tests', () => { + beforeEach(() => { + window.matchMedia = jest.fn().mockImplementation(() => ({ + matches: true, + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + })); + }); + it('should not have programmatically-detectable accessibility issues when type = rail', async () => { const { container } = render( { }} type={CHIP_GROUP_TYPES.rail} ariaLabel="Select cities" + leadingNudgerLabel="Scroll back" + trailingNudgerLabel="Scroll forward" /> ); const results = await axe(container); @@ -76,6 +86,8 @@ describe('BpkChipGroupSingleSelect accessibility tests', () => { type={CHIP_GROUP_TYPES.rail} selectedIndex={1} ariaLabel="Select a city" + leadingNudgerLabel="Scroll back" + trailingNudgerLabel="Scroll forward" /> ); const results = await axe(container); From 80db707b27525f685ad7525ec768a01a2bdccf34 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Tue, 16 Apr 2024 09:21:23 +0100 Subject: [PATCH 26/79] add nudger labels to tests, snaps, type declarations --- packages/bpk-component-chip-group/index.d.ts | 18 ------------------ .../src/BpkChipGroup.d.ts | 4 +++- .../src/BpkChipGroup.tsx | 7 ++++--- .../src/BpkChipGroupSingleSelect.d.ts | 18 ------------------ .../src/Nudger-test.tsx | 4 ++-- .../bpk-component-chip-group/src/Nudger.d.ts | 3 ++- .../__snapshots__/BpkChipGroup-test.tsx.snap | 2 ++ .../src/__snapshots__/Nudger-test.tsx.snap | 5 +++++ 8 files changed, 18 insertions(+), 43 deletions(-) diff --git a/packages/bpk-component-chip-group/index.d.ts b/packages/bpk-component-chip-group/index.d.ts index 2f93d3bfb9..20be2e9ad2 100644 --- a/packages/bpk-component-chip-group/index.d.ts +++ b/packages/bpk-component-chip-group/index.d.ts @@ -1,21 +1,3 @@ -/* - * 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 BpkChipGroup, { type ChipGroupProps, BpkChipGroupState, CHIP_GROUP_TYPES, type ChipItem, type SingleSelectChipItem, CHIP_COMPONENT } from './src/BpkChipGroup'; import BpkChipGroupSingleSelect, { type SingleSelectProps, type SingleSelectStateProps, BpkChipGroupSingleSelectState } from './src/BpkChipGroupSingleSelect'; export type { ChipGroupProps, SingleSelectProps, SingleSelectStateProps, ChipItem, SingleSelectChipItem }; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts b/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts index b7fb640c26..4069592c54 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts @@ -47,12 +47,14 @@ export type CommonProps = { ariaLabel?: string; type?: ChipGroupType; chipStyle?: ChipStyleType; + leadingNudgerLabel?: string; + trailingNudgerLabel?: string; }; export type ChipGroupProps = { chips: ChipItem[]; stickyChip?: ChipItem; ariaMultiselectable?: boolean; } & CommonProps; -declare const BpkChipGroup: ({ ariaLabel, ariaMultiselectable, chipStyle, chips, stickyChip, type, }: ChipGroupProps) => JSX.Element; +declare const BpkChipGroup: ({ ariaLabel, ariaMultiselectable, chipStyle, chips, leadingNudgerLabel, stickyChip, trailingNudgerLabel, type, }: ChipGroupProps) => JSX.Element; export declare const BpkChipGroupState: ({ chips, ...rest }: ChipGroupProps) => JSX.Element; export default BpkChipGroup; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx index 26fd49a6ac..2723672384 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -73,8 +73,9 @@ export type CommonProps = { ariaLabel?: string; type?: ChipGroupType; chipStyle?: ChipStyleType; + // only required when type = rail leadingNudgerLabel?: string; - trailingNudgerLabel?: stirng; + trailingNudgerLabel?: string; }; export type ChipGroupProps = { @@ -89,9 +90,9 @@ const BpkChipGroup = ({ ariaMultiselectable = true, chipStyle = CHIP_TYPES.default, chips, - leadingNudgerLabel, + leadingNudgerLabel = '', stickyChip, - trailingNudgerLabel, + trailingNudgerLabel = '', type = CHIP_GROUP_TYPES.rail, }: ChipGroupProps) => { const scrollContainerRef = useRef(null); diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.d.ts b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.d.ts index a2b4e6ea1a..a52d08816c 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.d.ts +++ b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.d.ts @@ -1,21 +1,3 @@ -/* - * 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 SingleSelectChipItem, type CommonProps } from './BpkChipGroup'; export type SingleSelectProps = { diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 03d2e98b79..a6c911014d 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -54,13 +54,13 @@ describe('Nudger', () => { ])('should render correctly when leading=%s and isRtl=%s', (leading, isRtl) => { mockIsRtl.mockReturnValue(isRtl); - const { asFragment } = render(); + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); it('should render correctly for onDark chip style', () => { - const { asFragment } = render(); + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); diff --git a/packages/bpk-component-chip-group/src/Nudger.d.ts b/packages/bpk-component-chip-group/src/Nudger.d.ts index d351da1431..55cfeec351 100644 --- a/packages/bpk-component-chip-group/src/Nudger.d.ts +++ b/packages/bpk-component-chip-group/src/Nudger.d.ts @@ -19,9 +19,10 @@ import { type MutableRefObject } from 'react'; import type { ChipStyleType } from './BpkChipGroup'; type Props = { + ariaLabel: string; chipStyle?: ChipStyleType; scrollContainerRef: MutableRefObject; leading?: boolean; }; -declare const Nudger: ({ chipStyle, leading, scrollContainerRef }: Props) => JSX.Element; +declare const Nudger: ({ ariaLabel, chipStyle, leading, scrollContainerRef }: Props) => JSX.Element; export default Nudger; diff --git a/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap b/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap index 2e20e79526..6bde2b1aa6 100644 --- a/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap +++ b/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap @@ -264,6 +264,7 @@ exports[`BpkChipGroup should render correctly with type = rail 1`] = ` - - - - - -`; - exports[`BpkChipGroup should render correctly with sticky chip 1`] = `
`; - -exports[`BpkChipGroup should render correctly with type = rail 1`] = ` - -
-
- -
-
-
-
-
- - a11y label - - - - - -
-
-
-
-
- -
-
-
-`; - -exports[`BpkChipGroup should render correctly with type = wrap 1`] = ` - -
-
- - a11y label - - - - - -
-
-
-`; From 48d5cb078e5aacb50061aae719679dc2b0400ddc Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Wed, 1 May 2024 15:32:35 +0100 Subject: [PATCH 34/79] refactor SingleSelectChipGroup tests --- .../src/BpkChipGroupSingleSelect-test.tsx | 18 -- .../BpkChipGroupSingleSelect-test.tsx.snap | 208 ------------------ 2 files changed, 226 deletions(-) delete mode 100644 packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroupSingleSelect-test.tsx.snap diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx index 1bb879afcc..c94827407d 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx @@ -51,24 +51,6 @@ describe('BpkChipGroupSingleSelect', () => { } ]; - it('should render correctly with type = rail', () => { - const { asFragment } = render( - - ); - expect(asFragment()).toMatchSnapshot(); - }); - - it('should render correctly with type = wrap', () => { - const { asFragment } = render(); - expect(asFragment()).toMatchSnapshot(); - }); - it('should call onItemClick when a chip is clicked', async () => { const user = userEvent.setup(); diff --git a/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroupSingleSelect-test.tsx.snap b/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroupSingleSelect-test.tsx.snap deleted file mode 100644 index 0cbb3bcc27..0000000000 --- a/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroupSingleSelect-test.tsx.snap +++ /dev/null @@ -1,208 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BpkChipGroupSingleSelect should render correctly with type = rail 1`] = ` - -
-
- -
-
-
-
-
- - Filter cities - - - - - -
-
-
-
-
- -
-
-
-`; - -exports[`BpkChipGroupSingleSelect should render correctly with type = wrap 1`] = ` - -
-
- - Filter cities - - - - - -
-
-
-`; From c1b9601035dac6ff1b9a909c8806b719cf90af75 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Wed, 1 May 2024 16:12:54 +0100 Subject: [PATCH 35/79] refactor Nudger tests --- .../src/Nudger-test.tsx | 39 +++-- .../src/__snapshots__/Nudger-test.tsx.snap | 156 ------------------ 2 files changed, 27 insertions(+), 168 deletions(-) delete mode 100644 packages/bpk-component-chip-group/src/__snapshots__/Nudger-test.tsx.snap diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index a6c911014d..5abef2ab20 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -18,7 +18,8 @@ import type { MutableRefObject } from 'react'; -import { render } from '@testing-library/react'; +import { act, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { CHIP_TYPES } from '../../bpk-component-chip'; @@ -32,18 +33,19 @@ jest.mock('../../bpk-react-utils/index', () => ({ isRTL: () => mockIsRtl(), })); -const mockScrollContainerRef = { +const createMockScrollContainerRef = (isRtl: boolean): MutableRefObject => ({ current: { scrollBy: jest.fn() as (options?: any) => void, - offsetWidth: 70, - scrollLeft: 10, - scrollWidth: 100, + offsetWidth: 100, + scrollLeft: isRtl ? -150 : 150, + scrollWidth: 500, }, -} as MutableRefObject; +} as MutableRefObject); describe('Nudger', () => { beforeEach(() => { jest.resetAllMocks(); + jest.useFakeTimers(); }); it.each([ @@ -51,17 +53,30 @@ describe('Nudger', () => { [true, false], [false, true], [true, true], - ])('should render correctly when leading=%s and isRtl=%s', (leading, isRtl) => { + ])('should call scrollBy when leading=%s and isRtl=%s', async (leading, isRtl) => { + const user = userEvent.setup({ delay: null }); + const mockScrollContainerRef = createMockScrollContainerRef(isRtl); mockIsRtl.mockReturnValue(isRtl); - const { asFragment } = render(); + render(); - expect(asFragment()).toMatchSnapshot(); + act(() => { + jest.advanceTimersByTime(100); + }); + + await user.click(screen.getByRole('button')); + + const isLeft = (leading && !isRtl) || (!leading && isRtl); + expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledTimes(1); + expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledWith({ + left: isLeft ? -150 : 150, + behavior: 'smooth', + }); }); - it('should render correctly for onDark chip style', () => { - const { asFragment } = render(); + it('should render button style matching chips', () => { + render(); - expect(asFragment()).toMatchSnapshot(); + expect(screen.getByRole('button')).toHaveClass('bpk-button--secondary-on-dark'); }); }); diff --git a/packages/bpk-component-chip-group/src/__snapshots__/Nudger-test.tsx.snap b/packages/bpk-component-chip-group/src/__snapshots__/Nudger-test.tsx.snap deleted file mode 100644 index b17efe5fa6..0000000000 --- a/packages/bpk-component-chip-group/src/__snapshots__/Nudger-test.tsx.snap +++ /dev/null @@ -1,156 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Nudger should render correctly for onDark chip style 1`] = ` - -
- -
-
-`; - -exports[`Nudger should render correctly when leading=false and isRtl=false 1`] = ` - -
- -
-
-`; - -exports[`Nudger should render correctly when leading=false and isRtl=true 1`] = ` - -
- -
-
-`; - -exports[`Nudger should render correctly when leading=true and isRtl=false 1`] = ` - -
- -
-
-`; - -exports[`Nudger should render correctly when leading=true and isRtl=true 1`] = ` - -
- -
-
-`; From 3f3095e7f2d8bd2c26136a1eb9d9260e8db4595f Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Wed, 1 May 2024 16:28:51 +0100 Subject: [PATCH 36/79] fix type errors --- examples/bpk-component-chip-group/stories.ts | 4 ++-- packages/bpk-component-chip-group/src/BpkChipGroup.tsx | 1 - packages/bpk-component-chip-group/src/Nudger.tsx | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/bpk-component-chip-group/stories.ts b/examples/bpk-component-chip-group/stories.ts index af66427ccf..0eb54c71a3 100644 --- a/examples/bpk-component-chip-group/stories.ts +++ b/examples/bpk-component-chip-group/stories.ts @@ -58,7 +58,7 @@ export const WithLabel = BpkChipGroupWithLabel; export const AllChipTypes = AllChipTypesGroup; export const ExampleStateManagement = StateManagement; export const VisualTest = MixedExample; -export const VisualTestWithZoom = VisualTest.bind({}); -VisualTestWithZoom.args = { +export const VisualTestWithZoom = { + render: VisualTest, zoomEnabled: true, }; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx index 06ba01552b..e8b66c243b 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -20,7 +20,6 @@ import { useRef, useState } from 'react'; import BpkBreakpoint, { BREAKPOINTS } from '../../bpk-component-breakpoint'; import BpkSelectableChip, { BpkDismissibleChip, BpkIconChip, BpkDropdownChip, CHIP_TYPES } from '../../bpk-component-chip'; -// @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import FilterIconSm from '../../bpk-component-icon/sm/filter'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import BpkMobileScrollContainer from '../../bpk-component-mobile-scroll-container'; diff --git a/packages/bpk-component-chip-group/src/Nudger.tsx b/packages/bpk-component-chip-group/src/Nudger.tsx index e3b8a21643..00920607ab 100644 --- a/packages/bpk-component-chip-group/src/Nudger.tsx +++ b/packages/bpk-component-chip-group/src/Nudger.tsx @@ -21,9 +21,7 @@ import { type MutableRefObject, useEffect, useState } from 'react'; import { BpkButtonV2, BUTTON_TYPES } from '../../bpk-component-button'; import { CHIP_TYPES } from '../../bpk-component-chip'; import { withButtonAlignment } from '../../bpk-component-icon/index'; -// @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import ArrowLeft from '../../bpk-component-icon/sm/long-arrow-left'; -// @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import ArrowRight from '../../bpk-component-icon/sm/long-arrow-right'; import { cssModules, isRTL } from '../../bpk-react-utils/index'; From 1a814f06cefe0495ec19e6aca266735fab4b17a0 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Wed, 1 May 2024 16:47:01 +0100 Subject: [PATCH 37/79] remove snapshot --- .../__snapshots__/BpkChipGroup-test.tsx.snap | 168 ------------------ 1 file changed, 168 deletions(-) delete mode 100644 packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap diff --git a/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap b/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap deleted file mode 100644 index 3ed9d7f3a2..0000000000 --- a/packages/bpk-component-chip-group/src/__snapshots__/BpkChipGroup-test.tsx.snap +++ /dev/null @@ -1,168 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BpkChipGroup should render correctly with sticky chip 1`] = ` - -
-
- -
-
- -
-
-
-
-
- - Filter cities - - - - - -
-
-
-
-
- -
-
-
-`; From 56bfd19baf2a028eb22b51bf496504fed051fd21 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Thu, 2 May 2024 10:56:50 +0100 Subject: [PATCH 38/79] remove compiled css --- .../examples.module.css | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 examples/bpk-component-chip-group/examples.module.css diff --git a/examples/bpk-component-chip-group/examples.module.css b/examples/bpk-component-chip-group/examples.module.css deleted file mode 100644 index 6682ae2774..0000000000 --- a/examples/bpk-component-chip-group/examples.module.css +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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. - */ -@keyframes bpk-keyframe-spin{100%{transform:rotate(1turn)}}.bpk-chip-group-examples__fixed-width{width:18.75rem}.bpk-chip-group-examples__contrast{padding:1rem;background-color:#eff3f8}.bpk-chip-group-examples__dark{padding:1rem;background-color:#05203c}.bpk-chip-group-examples__image{padding:1rem;background-image:url("https://content.skyscnr.com/96508dbac15a2895b0147dc7e7f9ad30/canadian-rockies-canada.jpg")}.bpk-chip-group-examples__mixed-container h2{margin-top:2rem;margin-bottom:.5rem} \ No newline at end of file From 834dfcc70f297b5c48ba0785efd2e5e608427161 Mon Sep 17 00:00:00 2001 From: Stephen Hailey Date: Thu, 2 May 2024 11:05:53 +0100 Subject: [PATCH 39/79] ChipGroup - Remove label padding (#3407) * Remove label padding * Make nudger labels required --- .../bpk-component-chip-group/examples.tsx | 76 ++++++++++--------- .../src/BpkChipGroup.module.scss | 4 - .../src/BpkChipGroup.tsx | 10 +-- .../src/BpkChipGroupSingleSelect.tsx | 9 ++- 4 files changed, 50 insertions(+), 49 deletions(-) diff --git a/examples/bpk-component-chip-group/examples.tsx b/examples/bpk-component-chip-group/examples.tsx index 3ba8d21c69..dcb94da0fa 100644 --- a/examples/bpk-component-chip-group/examples.tsx +++ b/examples/bpk-component-chip-group/examples.tsx @@ -75,38 +75,38 @@ const chips = [ export const BpkChipGroupWrapping = () => ( -
- -
- ); +
+ +
+); export const BpkSingleChipGroupWrapping = () => ( -
- -
- ); +
+ +
+); export const BpkChipGroupRail = () => ( -
- -
- ); +
+ +
+); export const BpkChipGroupSticky = () => { @@ -190,16 +190,18 @@ export const OnImageChipGroup = () => { }; export const BpkChipGroupWithLabel = () => ( -
- -
- ); +
+ +
+); export const AllChipTypesGroup = () => { const [dismissed, setDismissed] = useState(false); diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss index cf7b7ee582..91a2bd9e11 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss @@ -42,10 +42,6 @@ &--wrap { flex-wrap: wrap; } - - &--with-label { - padding-inline-start: tokens.bpk-spacing-base(); - } } .bpk-sticky-chip-container { diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx index e8b66c243b..4be28c3c0c 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.tsx @@ -83,8 +83,8 @@ export type CommonProps = { export type RailChipGroupProps = { stickyChip?: ChipItem; - leadingNudgerLabel?: string; - trailingNudgerLabel?: string; + leadingNudgerLabel: string; + trailingNudgerLabel: string; } & CommonProps; export type WrapChipGroupProps = { @@ -218,16 +218,16 @@ const WrapChipGroup = ({ chips={chips} label={label} />; -const BpkChipGroup = ({ label, type, ...rest }: ChipGroupProps) => { +const BpkChipGroup = (props: ChipGroupProps) => { + const { type } = props; const containerClassnames = getClassName('bpk-chip-group-container') const chipGroupClassNames = getClassName( 'bpk-chip-group', `bpk-chip-group--${type}`, - label && 'bpk-chip-group--with-label' ); return
- {type === CHIP_GROUP_TYPES.rail ? : } + {props.type === CHIP_GROUP_TYPES.rail ? : }
} diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx index bb5f0b4098..632321e042 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx +++ b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.tsx @@ -20,12 +20,15 @@ import { useState } from 'react'; import BpkChipGroup, { type ChipItem, type SingleSelectChipItem, type ChipGroupProps } from './BpkChipGroup'; -export type SingleSelectProps = { +type CommonSingleSelectProps = { chips: SingleSelectChipItem[]; - selectedIndex?: number; onItemClick?: (item: SingleSelectChipItem, selected: boolean, index: number) => void, } & ChipGroupProps; +export type SingleSelectProps = { + selectedIndex?: number; +} & CommonSingleSelectProps; + const BpkChipGroupSingleSelect = ({ chips, onItemClick, selectedIndex, ...rest }: SingleSelectProps) => { const chipsWithSelection = chips.map((chip, index) => chip && ({ ...chip, @@ -44,7 +47,7 @@ const BpkChipGroupSingleSelect = ({ chips, onItemClick, selectedIndex, ...rest } export type SingleSelectStateProps = { initiallySelectedIndex?: number; -} & Omit +} & CommonSingleSelectProps export const BpkChipGroupSingleSelectState = ({ initiallySelectedIndex = -1, onItemClick, ...rest }: SingleSelectStateProps) => { const [selectedIndex, setSelectedIndex] = useState(initiallySelectedIndex); From 5693e07deb4eed52d5b50553c0185b80c6801255 Mon Sep 17 00:00:00 2001 From: Stephen Hailey Date: Thu, 2 May 2024 11:23:01 +0100 Subject: [PATCH 40/79] Set nudger labels as required in typedef (#3411) --- .../src/BpkChipGroup.d.ts | 66 +++++++++++-------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts b/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts index 4069592c54..cc2c152c55 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts @@ -19,42 +19,56 @@ import type { ReactNode } from 'react'; import { CHIP_TYPES } from '../../bpk-component-chip'; export declare const CHIP_GROUP_TYPES: { - rail: string; - wrap: string; + rail: string; + wrap: string; }; export declare const CHIP_COMPONENT: { - selectable: string; - dismissible: string; - dropdown: string; - icon: string; + selectable: string; + dismissible: string; + dropdown: string; + icon: string; }; -export type ChipGroupType = (typeof CHIP_GROUP_TYPES)[keyof typeof CHIP_GROUP_TYPES]; +export type ChipGroupType = + (typeof CHIP_GROUP_TYPES)[keyof typeof CHIP_GROUP_TYPES]; export type ChipStyleType = (typeof CHIP_TYPES)[keyof typeof CHIP_TYPES]; -export type ChipComponentType = (typeof CHIP_COMPONENT)[keyof typeof CHIP_COMPONENT]; +export type ChipComponentType = + (typeof CHIP_COMPONENT)[keyof typeof CHIP_COMPONENT]; export type SingleSelectChipItem = { - text: string; - accessibilityLabel?: string; - leadingAccessoryView?: ReactNode; - [rest: string]: any; + text: string; + accessibilityLabel?: string; + leadingAccessoryView?: ReactNode; + [rest: string]: any; }; export type ChipItem = { - component?: ChipComponentType; - onClick?: (selected: boolean, index: number) => void; - selected?: boolean; - hidden?: boolean; + component?: ChipComponentType; + onClick?: (selected: boolean, index: number) => void; + selected?: boolean; + hidden?: boolean; } & SingleSelectChipItem; export type CommonProps = { - ariaLabel?: string; - type?: ChipGroupType; - chipStyle?: ChipStyleType; - leadingNudgerLabel?: string; - trailingNudgerLabel?: string; + ariaLabel?: string; + type?: ChipGroupType; + chipStyle?: ChipStyleType; + leadingNudgerLabel: string; + trailingNudgerLabel: string; }; export type ChipGroupProps = { - chips: ChipItem[]; - stickyChip?: ChipItem; - ariaMultiselectable?: boolean; + chips: ChipItem[]; + stickyChip?: ChipItem; + ariaMultiselectable?: boolean; } & CommonProps; -declare const BpkChipGroup: ({ ariaLabel, ariaMultiselectable, chipStyle, chips, leadingNudgerLabel, stickyChip, trailingNudgerLabel, type, }: ChipGroupProps) => JSX.Element; -export declare const BpkChipGroupState: ({ chips, ...rest }: ChipGroupProps) => JSX.Element; +declare const BpkChipGroup: ({ + ariaLabel, + ariaMultiselectable, + chipStyle, + chips, + leadingNudgerLabel, + stickyChip, + trailingNudgerLabel, + type, +}: ChipGroupProps) => JSX.Element; +export declare const BpkChipGroupState: ({ + chips, + ...rest +}: ChipGroupProps) => JSX.Element; export default BpkChipGroup; From e6d51578f0658849277d3976a183906e0bbd5e42 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Thu, 2 May 2024 13:47:49 +0100 Subject: [PATCH 41/79] fix export style --- packages/bpk-component-chip-group/index.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/bpk-component-chip-group/index.ts b/packages/bpk-component-chip-group/index.ts index 5a983f20fc..f20d5baa49 100644 --- a/packages/bpk-component-chip-group/index.ts +++ b/packages/bpk-component-chip-group/index.ts @@ -30,6 +30,18 @@ import BpkChipGroupSingleSelect, { BpkChipGroupSingleSelectState, } from './src/BpkChipGroupSingleSelect'; -export type { ChipGroupProps, SingleSelectProps, SingleSelectStateProps, ChipItem, SingleSelectChipItem }; -export { BpkChipGroupState, CHIP_COMPONENT, CHIP_GROUP_TYPES, BpkChipGroupSingleSelect, BpkChipGroupSingleSelectState}; +export type { + ChipGroupProps, + SingleSelectProps, + SingleSelectStateProps, + ChipItem, + SingleSelectChipItem +}; +export { + BpkChipGroupState, + BpkChipGroupSingleSelect, + BpkChipGroupSingleSelectState, + CHIP_GROUP_TYPES, + CHIP_COMPONENT, +}; export default BpkChipGroup; From 4595b1767c8a00475ad9ff90caa4474a89afb149 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Thu, 23 May 2024 15:57:54 +0100 Subject: [PATCH 42/79] remove type declaration files --- packages/bpk-component-chip-group/index.d.ts | 23 ------ .../src/BpkChipGroup.d.ts | 74 ------------------- .../src/BpkChipGroupSingleSelect.d.ts | 31 -------- .../bpk-component-chip-group/src/Nudger.d.ts | 28 ------- 4 files changed, 156 deletions(-) delete mode 100644 packages/bpk-component-chip-group/index.d.ts delete mode 100644 packages/bpk-component-chip-group/src/BpkChipGroup.d.ts delete mode 100644 packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.d.ts delete mode 100644 packages/bpk-component-chip-group/src/Nudger.d.ts diff --git a/packages/bpk-component-chip-group/index.d.ts b/packages/bpk-component-chip-group/index.d.ts deleted file mode 100644 index 2f93d3bfb9..0000000000 --- a/packages/bpk-component-chip-group/index.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 BpkChipGroup, { type ChipGroupProps, BpkChipGroupState, CHIP_GROUP_TYPES, type ChipItem, type SingleSelectChipItem, CHIP_COMPONENT } from './src/BpkChipGroup'; -import BpkChipGroupSingleSelect, { type SingleSelectProps, type SingleSelectStateProps, BpkChipGroupSingleSelectState } from './src/BpkChipGroupSingleSelect'; -export type { ChipGroupProps, SingleSelectProps, SingleSelectStateProps, ChipItem, SingleSelectChipItem }; -export { BpkChipGroupState, CHIP_COMPONENT, CHIP_GROUP_TYPES, BpkChipGroupSingleSelect, BpkChipGroupSingleSelectState }; -export default BpkChipGroup; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts b/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts deleted file mode 100644 index cc2c152c55..0000000000 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.d.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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 { ReactNode } from 'react'; -import { CHIP_TYPES } from '../../bpk-component-chip'; -export declare const CHIP_GROUP_TYPES: { - rail: string; - wrap: string; -}; -export declare const CHIP_COMPONENT: { - selectable: string; - dismissible: string; - dropdown: string; - icon: string; -}; -export type ChipGroupType = - (typeof CHIP_GROUP_TYPES)[keyof typeof CHIP_GROUP_TYPES]; -export type ChipStyleType = (typeof CHIP_TYPES)[keyof typeof CHIP_TYPES]; -export type ChipComponentType = - (typeof CHIP_COMPONENT)[keyof typeof CHIP_COMPONENT]; -export type SingleSelectChipItem = { - text: string; - accessibilityLabel?: string; - leadingAccessoryView?: ReactNode; - [rest: string]: any; -}; -export type ChipItem = { - component?: ChipComponentType; - onClick?: (selected: boolean, index: number) => void; - selected?: boolean; - hidden?: boolean; -} & SingleSelectChipItem; -export type CommonProps = { - ariaLabel?: string; - type?: ChipGroupType; - chipStyle?: ChipStyleType; - leadingNudgerLabel: string; - trailingNudgerLabel: string; -}; -export type ChipGroupProps = { - chips: ChipItem[]; - stickyChip?: ChipItem; - ariaMultiselectable?: boolean; -} & CommonProps; -declare const BpkChipGroup: ({ - ariaLabel, - ariaMultiselectable, - chipStyle, - chips, - leadingNudgerLabel, - stickyChip, - trailingNudgerLabel, - type, -}: ChipGroupProps) => JSX.Element; -export declare const BpkChipGroupState: ({ - chips, - ...rest -}: ChipGroupProps) => JSX.Element; -export default BpkChipGroup; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.d.ts b/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.d.ts deleted file mode 100644 index a2b4e6ea1a..0000000000 --- a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect.d.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 SingleSelectChipItem, type CommonProps } from './BpkChipGroup'; -export type SingleSelectProps = { - chips: SingleSelectChipItem[]; - selectedIndex?: number; - onItemClick?: (item: SingleSelectChipItem, selected: boolean, index: number) => void; -} & CommonProps; -declare const BpkChipGroupSingleSelect: ({ chips, onItemClick, selectedIndex, ...rest }: SingleSelectProps) => JSX.Element; -export type SingleSelectStateProps = { - initiallySelectedIndex?: number; -} & Omit; -export declare const BpkChipGroupSingleSelectState: ({ initiallySelectedIndex, onItemClick, ...rest }: SingleSelectStateProps) => JSX.Element; -export default BpkChipGroupSingleSelect; diff --git a/packages/bpk-component-chip-group/src/Nudger.d.ts b/packages/bpk-component-chip-group/src/Nudger.d.ts deleted file mode 100644 index 55cfeec351..0000000000 --- a/packages/bpk-component-chip-group/src/Nudger.d.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 MutableRefObject } from 'react'; -import type { ChipStyleType } from './BpkChipGroup'; -type Props = { - ariaLabel: string; - chipStyle?: ChipStyleType; - scrollContainerRef: MutableRefObject; - leading?: boolean; -}; -declare const Nudger: ({ ariaLabel, chipStyle, leading, scrollContainerRef }: Props) => JSX.Element; -export default Nudger; From 315042e2f7b10702f7614dac9c94d75dbe6ee266 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Thu, 23 May 2024 16:28:24 +0100 Subject: [PATCH 43/79] Rename chip groups --- .../bpk-component-chip-group/examples.tsx | 26 +++++++++---------- examples/bpk-component-chip-group/stories.ts | 16 ++++++------ packages/bpk-component-chip-group/README.md | 22 ++++++++-------- packages/bpk-component-chip-group/index.ts | 20 +++++++------- ...t.tsx => BpkMultiSelectChipGroup-test.tsx} | 22 ++++++++-------- ...pGroup.tsx => BpkMultiSelectChipGroup.tsx} | 8 +++--- ....tsx => BpkSingleSelectChipGroup-test.tsx} | 22 ++++++++-------- ...elect.tsx => BpkSingleSelectChipGroup.tsx} | 12 ++++----- .../bpk-component-chip-group/src/Nudger.tsx | 2 +- .../src/accessibility-test.tsx | 12 ++++----- 10 files changed, 81 insertions(+), 81 deletions(-) rename packages/bpk-component-chip-group/src/{BpkChipGroup-test.tsx => BpkMultiSelectChipGroup-test.tsx} (89%) rename packages/bpk-component-chip-group/src/{BpkChipGroup.tsx => BpkMultiSelectChipGroup.tsx} (96%) rename packages/bpk-component-chip-group/src/{BpkChipGroupSingleSelect-test.tsx => BpkSingleSelectChipGroup-test.tsx} (90%) rename packages/bpk-component-chip-group/src/{BpkChipGroupSingleSelect.tsx => BpkSingleSelectChipGroup.tsx} (79%) diff --git a/examples/bpk-component-chip-group/examples.tsx b/examples/bpk-component-chip-group/examples.tsx index dcb94da0fa..4996b6d051 100644 --- a/examples/bpk-component-chip-group/examples.tsx +++ b/examples/bpk-component-chip-group/examples.tsx @@ -20,9 +20,9 @@ import { useState } from 'react'; import { CHIP_TYPES } from '../../packages/bpk-component-chip'; -import BpkChipGroup, { - BpkChipGroupState, - BpkChipGroupSingleSelectState, +import BpkMultiSelectChipGroup, { + BpkMultiSelectChipGroupState, + BpkSingleSelectChipGroupState, CHIP_GROUP_TYPES, CHIP_COMPONENT, } from '../../packages/bpk-component-chip-group'; @@ -76,7 +76,7 @@ const chips = [ export const BpkChipGroupWrapping = () => (
- ( export const BpkSingleChipGroupWrapping = () => (
- ( export const BpkChipGroupRail = () => (
- { return (
- { return (
- { return (
- { return (
- { export const BpkChipGroupWithLabel = () => (
- { ]; return ( - { const [route, setRoute] = useState('flights'); return ( - ( - { const [route, setRoute] = useState('flights'); return ( - { }; ``` -### BpkChipGroupState +### BpkMultiSelectChipGroupState Like a `BpkChipGroup` (multi-selectable) but with basic state management similar to above built in. The `selected` property of each `ChipItem` will affect **only the first render** as the state is managed within the component afterwards. ```tsx const StatefulExample = () => ( - ( ); ``` -### BpkChipGroupSingleSelect +### BpkSingleSelectChipGroup This is a wrapper around a `BpkChipGroup` that only allows a single chip to be `selected`, determined by the `selectedIndex` prop. If no chips should appear selected, this should be `undefined`. ```tsx const SingleSelectExample = () => ( - ( ); ``` -### BpkChipGroupSingleSelectState +### BpkSingleSelectChipGroupState -A wrapper around `BpkChipGroupSingleSelect` that provides basic state management for selecting/deselecting a single chip in the group. The `initiallySelectedIndex` prop controls the chip that will be selected on **first render only**. +A wrapper around `BpkSingleSelectChipGroup` that provides basic state management for selecting/deselecting a single chip in the group. The `initiallySelectedIndex` prop controls the chip that will be selected on **first render only**. ```tsx const SingleSelectStateExample = () => ( - { +describe('BpkMultiSelectChipGroup', () => { beforeEach(() => { window.matchMedia = jest.fn().mockImplementation(() => ({ matches: true, @@ -53,7 +53,7 @@ describe('BpkChipGroup', () => { ]; it('should render selected chip', () => { - render(); + render(); const chip = screen.getByRole('checkbox', { name: 'Berlin' }); @@ -62,7 +62,7 @@ describe('BpkChipGroup', () => { it('should render correctly with sticky chip', () => { render( - { const onClick = jest.fn(); render( - { }); }); -describe('BpkChipGroupState', () => { +describe('BpkMultiSelectChipGroupState', () => { const chips = [ { text: 'London', @@ -127,7 +127,7 @@ describe('BpkChipGroupState', () => { const user = userEvent.setup(); render( - , @@ -143,7 +143,7 @@ describe('BpkChipGroupState', () => { const user = userEvent.setup(); render( - , @@ -163,7 +163,7 @@ describe('BpkChipGroupState', () => { const user = userEvent.setup(); render( - , @@ -183,7 +183,7 @@ describe('BpkChipGroupState', () => { const user = userEvent.setup(); render( - , @@ -199,7 +199,7 @@ describe('BpkChipGroupState', () => { it('should allow chips to be selected initially when passed in chips array', () => { render( - ; -const BpkChipGroup = (props: ChipGroupProps) => { +const BpkMultiSelectChipGroup = (props: ChipGroupProps) => { const { type } = props; const containerClassnames = getClassName('bpk-chip-group-container') const chipGroupClassNames = getClassName( @@ -231,7 +231,7 @@ const BpkChipGroup = (props: ChipGroupProps) => {
} -export const BpkChipGroupState = ({ chips, ...rest }: ChipGroupProps) => { +export const BpkMultiSelectChipGroupState = ({ chips, ...rest }: ChipGroupProps) => { const [selectedChips, setSelectedChips] = useState(chips.map(c => Boolean(c.selected))); const statefulChips = chips.map((chip, index) => chip && ({ @@ -248,7 +248,7 @@ export const BpkChipGroupState = ({ chips, ...rest }: ChipGroupProps) => { }, })); - return + return }; -export default BpkChipGroup; +export default BpkMultiSelectChipGroup; diff --git a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx b/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup-test.tsx similarity index 90% rename from packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx rename to packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup-test.tsx index c94827407d..f9bde7e140 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroupSingleSelect-test.tsx +++ b/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup-test.tsx @@ -19,15 +19,15 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { CHIP_GROUP_TYPES } from './BpkChipGroup'; -import BpkChipGroupSingleSelect, { BpkChipGroupSingleSelectState } from './BpkChipGroupSingleSelect'; +import { CHIP_GROUP_TYPES } from './BpkMultiSelectChipGroup'; +import BpkSingleSelectChipGroup, { BpkSingleSelectChipGroupState } from './BpkSingleSelectChipGroup'; const defaultProps = { type: CHIP_GROUP_TYPES.wrap, ariaLabel: 'a11y label', } -describe('BpkChipGroupSingleSelect', () => { +describe('BpkSingleSelectChipGroup', () => { beforeEach(() => { window.matchMedia = jest.fn().mockImplementation(() => ({ matches: true, @@ -57,7 +57,7 @@ describe('BpkChipGroupSingleSelect', () => { const onItemClick = jest.fn(); render( - { ]; render( - { }); }); -describe('BpkChipGroupSingleSelectState', () => { +describe('BpkSingleSelectChipGroupState', () => { const chips = [ { text: 'London', @@ -117,7 +117,7 @@ describe('BpkChipGroupSingleSelectState', () => { const user = userEvent.setup(); render( - , @@ -134,7 +134,7 @@ describe('BpkChipGroupSingleSelectState', () => { const user = userEvent.setup(); render( - , @@ -158,7 +158,7 @@ describe('BpkChipGroupSingleSelectState', () => { const user = userEvent.setup(); render( - , @@ -181,7 +181,7 @@ describe('BpkChipGroupSingleSelectState', () => { const onItemClick = jest.fn(); render( - { it('should have initiallySelectedIndex selected before interaction', () => { render( - { +const BpkSingleSelectChipGroup = ({ chips, onItemClick, selectedIndex, ...rest }: SingleSelectProps) => { const chipsWithSelection = chips.map((chip, index) => chip && ({ ...chip, selected: index === selectedIndex, @@ -41,7 +41,7 @@ const BpkChipGroupSingleSelect = ({ chips, onItemClick, selectedIndex, ...rest } })); return ( - + ); }; @@ -49,7 +49,7 @@ export type SingleSelectStateProps = { initiallySelectedIndex?: number; } & CommonSingleSelectProps -export const BpkChipGroupSingleSelectState = ({ initiallySelectedIndex = -1, onItemClick, ...rest }: SingleSelectStateProps) => { +export const BpkSingleSelectChipGroupState = ({ initiallySelectedIndex = -1, onItemClick, ...rest }: SingleSelectStateProps) => { const [selectedIndex, setSelectedIndex] = useState(initiallySelectedIndex); const onItemClickWithState = (item: ChipItem, selected: boolean, index: number) => { @@ -59,8 +59,8 @@ export const BpkChipGroupSingleSelectState = ({ initiallySelectedIndex = -1, onI setSelectedIndex(selected ? index : -1); } - return + return }; -export default BpkChipGroupSingleSelect; +export default BpkSingleSelectChipGroup; diff --git a/packages/bpk-component-chip-group/src/Nudger.tsx b/packages/bpk-component-chip-group/src/Nudger.tsx index 00920607ab..1069531443 100644 --- a/packages/bpk-component-chip-group/src/Nudger.tsx +++ b/packages/bpk-component-chip-group/src/Nudger.tsx @@ -25,7 +25,7 @@ import ArrowLeft from '../../bpk-component-icon/sm/long-arrow-left'; import ArrowRight from '../../bpk-component-icon/sm/long-arrow-right'; import { cssModules, isRTL } from '../../bpk-react-utils/index'; -import type { ChipStyleType } from './BpkChipGroup'; +import type { ChipStyleType } from './BpkMultiSelectChipGroup'; import STYLES from './Nudger.module.scss'; diff --git a/packages/bpk-component-chip-group/src/accessibility-test.tsx b/packages/bpk-component-chip-group/src/accessibility-test.tsx index 94b07c7d65..9a0791b853 100644 --- a/packages/bpk-component-chip-group/src/accessibility-test.tsx +++ b/packages/bpk-component-chip-group/src/accessibility-test.tsx @@ -19,8 +19,8 @@ import { render } from '@testing-library/react'; import { axe } from 'jest-axe'; -import BpkChipGroup, { CHIP_GROUP_TYPES } from './BpkChipGroup'; -import BpkChipGroupSingleSelect from './BpkChipGroupSingleSelect'; +import BpkMultiSelectChipGroup, { CHIP_GROUP_TYPES } from './BpkMultiSelectChipGroup'; +import BpkSingleSelectChipGroup from './BpkSingleSelectChipGroup'; const chips = [ { @@ -50,7 +50,7 @@ describe('BpkChipGroup accessibility tests', () => { it('should not have programmatically-detectable accessibility issues when type = rail', async () => { const { container } = render( - { it('should not have programmatically-detectable accessibility issues when type = wrap', async () => { const { container } = render( - { describe('BpkChipGroupSingleSelect accessibility tests', () => { it('should not have programmatically-detectable accessibility issues when type = rail', async () => { const { container } = render( - { it('should not have programmatically-detectable accessibility issues when type = wrap', async () => { const { container } = render( - Date: Thu, 23 May 2024 16:29:51 +0100 Subject: [PATCH 44/79] Rename chip groups --- packages/bpk-component-chip-group/src/accessibility-test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bpk-component-chip-group/src/accessibility-test.tsx b/packages/bpk-component-chip-group/src/accessibility-test.tsx index 9a0791b853..1a1b06185a 100644 --- a/packages/bpk-component-chip-group/src/accessibility-test.tsx +++ b/packages/bpk-component-chip-group/src/accessibility-test.tsx @@ -39,7 +39,7 @@ const chips = [ } ]; -describe('BpkChipGroup accessibility tests', () => { +describe('BpkMultiSelectChipGroup accessibility tests', () => { beforeEach(() => { window.matchMedia = jest.fn().mockImplementation(() => ({ matches: true, @@ -78,7 +78,7 @@ describe('BpkChipGroup accessibility tests', () => { }); }); -describe('BpkChipGroupSingleSelect accessibility tests', () => { +describe('BpkSingleSelectChipGroup accessibility tests', () => { it('should not have programmatically-detectable accessibility issues when type = rail', async () => { const { container } = render( Date: Thu, 23 May 2024 16:39:41 +0100 Subject: [PATCH 45/79] fix zoom test --- examples/bpk-component-chip-group/stories.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/bpk-component-chip-group/stories.ts b/examples/bpk-component-chip-group/stories.ts index e53185a014..1c5ee6304b 100644 --- a/examples/bpk-component-chip-group/stories.ts +++ b/examples/bpk-component-chip-group/stories.ts @@ -58,7 +58,7 @@ export const WithLabel = BpkChipGroupWithLabel; export const AllChipTypes = AllChipTypesGroup; export const ExampleStateManagement = StateManagement; export const VisualTest = MixedExample; -export const VisualTestWithZoom = { - render: VisualTest, - zoomEnabled: true, +export const VisualTestWithZoom = VisualTest.bind({}); +VisualTestWithZoom.args = { + zoomEnabled: true }; From ab550bf85e7a4497bf13d06416a26ac539f3f25a Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Thu, 23 May 2024 16:51:55 +0100 Subject: [PATCH 46/79] remove default nudger labels --- .../bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx | 4 ++-- packages/bpk-component-chip-group/src/Nudger-test.tsx | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx index 3cd91e5462..653a56e551 100644 --- a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx @@ -156,9 +156,9 @@ const RailChipGroup = ({ chipStyle = CHIP_TYPES.default, chips, label, - leadingNudgerLabel = '', + leadingNudgerLabel, stickyChip, - trailingNudgerLabel = '', + trailingNudgerLabel, }: RailChipGroupProps & InternalProps) => { const scrollContainerRef = useRef(null); diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 5abef2ab20..cd82c8e8fe 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -60,12 +60,11 @@ describe('Nudger', () => { render(); - act(() => { + await act(async () => { jest.advanceTimersByTime(100); + await user.click(screen.getByRole('button')); }); - await user.click(screen.getByRole('button')); - const isLeft = (leading && !isRtl) || (!leading && isRtl); expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledTimes(1); expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledWith({ From 2f0d314c1fab7d3006ea21c0c639a3a278cf0ce4 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Thu, 23 May 2024 16:59:38 +0100 Subject: [PATCH 47/79] refactor nudger positions --- .../src/BpkMultiSelectChipGroup.tsx | 6 +++--- .../src/Nudger-test.tsx | 17 +++++++++-------- .../bpk-component-chip-group/src/Nudger.tsx | 10 +++++++--- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx index 653a56e551..8992df6f15 100644 --- a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx @@ -26,7 +26,7 @@ import BpkMobileScrollContainer from '../../bpk-component-mobile-scroll-containe import BpkText, { TEXT_STYLES } from '../../bpk-component-text/src/BpkText'; import { cssModules } from '../../bpk-react-utils'; -import Nudger from './Nudger'; +import Nudger, { POSITION } from './Nudger'; import STYLES from './BpkChipGroup.module.scss'; @@ -170,7 +170,7 @@ const RailChipGroup = ({ return ( <> - + {stickyChip &&
@@ -197,7 +197,7 @@ const RailChipGroup = ({ label={label} /> - + ); diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index cd82c8e8fe..724ce51583 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -23,7 +23,7 @@ import userEvent from '@testing-library/user-event'; import { CHIP_TYPES } from '../../bpk-component-chip'; -import Nudger from './Nudger'; +import Nudger, { POSITION } from './Nudger'; const mockIsRtl = jest.fn(() => false); @@ -49,22 +49,23 @@ describe('Nudger', () => { }); it.each([ - [false, false], - [true, false], - [false, true], - [true, true], - ])('should call scrollBy when leading=%s and isRtl=%s', async (leading, isRtl) => { + [POSITION.trailing, false], + [POSITION.leading, false], + [POSITION.trailing, true], + [POSITION.leading, true], + ])('should call scrollBy when leading=%s and isRtl=%s', async (position, isRtl) => { const user = userEvent.setup({ delay: null }); const mockScrollContainerRef = createMockScrollContainerRef(isRtl); mockIsRtl.mockReturnValue(isRtl); - render(); + render(); await act(async () => { jest.advanceTimersByTime(100); await user.click(screen.getByRole('button')); }); + const leading = position === POSITION.leading; const isLeft = (leading && !isRtl) || (!leading && isRtl); expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledTimes(1); expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledWith({ @@ -74,7 +75,7 @@ describe('Nudger', () => { }); it('should render button style matching chips', () => { - render(); + render(); expect(screen.getByRole('button')).toHaveClass('bpk-button--secondary-on-dark'); }); diff --git a/packages/bpk-component-chip-group/src/Nudger.tsx b/packages/bpk-component-chip-group/src/Nudger.tsx index 1069531443..3f2e47de72 100644 --- a/packages/bpk-component-chip-group/src/Nudger.tsx +++ b/packages/bpk-component-chip-group/src/Nudger.tsx @@ -38,15 +38,18 @@ const CHIP_STYLE_TO_BUTTON_STYLE = { [CHIP_TYPES.onImage]: BUTTON_TYPES.primaryOnDark, } +export const POSITION = { + leading: 'leading', + trailing: 'trailing', +} as const; type Props = { ariaLabel: string; chipStyle?: ChipStyleType; scrollContainerRef: MutableRefObject; - leading?: boolean; + position: (typeof POSITION)[keyof typeof POSITION]; } - const AlignedLeftArrowIcon = withButtonAlignment(ArrowLeft); const AlignedRightArrowIcon = withButtonAlignment(ArrowRight); @@ -56,12 +59,13 @@ const SCROLL_DISTANCE = 150; const Nudger = ({ ariaLabel, chipStyle = CHIP_TYPES.default, - leading = false, + position, scrollContainerRef }: Props) => { const [show, setShow] = useState(false); const [enabled, setEnabled] = useState(true); + const leading = position === POSITION.leading; const rtl = isRTL(); const isLeft = (leading && !rtl) || (!leading && rtl); From 812451ae486ebc4f15e7ee6c5b52a16d9d391994 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 24 May 2024 11:49:34 +0100 Subject: [PATCH 48/79] remove InternalProps classname --- .../src/BpkMultiSelectChipGroup.tsx | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx index 8992df6f15..2678dd5ce0 100644 --- a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx @@ -69,10 +69,6 @@ export type ChipItem = { hidden?: boolean; } & SingleSelectChipItem; -export type InternalProps = { - chipGroupClassNames: string -}; - export type CommonProps = { label?: string; ariaLabel?: string; @@ -152,20 +148,23 @@ const ChipGroupContent = ( const RailChipGroup = ({ ariaLabel, ariaMultiselectable = true, - chipGroupClassNames, chipStyle = CHIP_TYPES.default, chips, label, leadingNudgerLabel, stickyChip, trailingNudgerLabel, -}: RailChipGroupProps & InternalProps) => { +}: RailChipGroupProps) => { const scrollContainerRef = useRef(null); const stickyChipContainerClassnames = getClassName( 'bpk-sticky-chip-container', `bpk-sticky-chip-container--${chipStyle}`, ); + const chipGroupClassNames = getClassName( + 'bpk-chip-group', + 'bpk-chip-group--rail', + ); return ( <> @@ -206,28 +205,25 @@ const RailChipGroup = ({ const WrapChipGroup = ({ ariaLabel, ariaMultiselectable = true, - chipGroupClassNames, chipStyle = CHIP_TYPES.default, chips, label, -}: WrapChipGroupProps & InternalProps) => +}: WrapChipGroupProps) => ; const BpkMultiSelectChipGroup = (props: ChipGroupProps) => { - const { type } = props; const containerClassnames = getClassName('bpk-chip-group-container') - const chipGroupClassNames = getClassName( - 'bpk-chip-group', - `bpk-chip-group--${type}`, - ); return
- {props.type === CHIP_GROUP_TYPES.rail ? : } + {props.type === CHIP_GROUP_TYPES.rail ? : }
} From 3410878016ecf9f9dc15ae44042ae38b1f644b92 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 24 May 2024 14:30:58 +0100 Subject: [PATCH 49/79] refactoring --- .../src/BpkMultiSelectChipGroup.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx index 2678dd5ce0..4d7d5d9918 100644 --- a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx @@ -219,13 +219,12 @@ const WrapChipGroup = ({ chips={chips} label={label} />; -const BpkMultiSelectChipGroup = (props: ChipGroupProps) => { - const containerClassnames = getClassName('bpk-chip-group-container') - - return
+const BpkMultiSelectChipGroup = (props: ChipGroupProps) => ( +
{props.type === CHIP_GROUP_TYPES.rail ? : }
-} +); + export const BpkMultiSelectChipGroupState = ({ chips, ...rest }: ChipGroupProps) => { const [selectedChips, setSelectedChips] = useState(chips.map(c => Boolean(c.selected))); @@ -238,7 +237,7 @@ export const BpkMultiSelectChipGroupState = ({ chips, ...rest }: ChipGroupProps) chip.onClick(selected, selectedIndex); } - const nextSelectedChips = [...selectedChips] + const nextSelectedChips = [...selectedChips]; nextSelectedChips[selectedIndex] = selected; setSelectedChips(nextSelectedChips); }, From 1b072ce4ceebcd2052f410cac7303df0e9593ceb Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 24 May 2024 16:01:27 +0100 Subject: [PATCH 50/79] styling/refactoring --- packages/bpk-component-chip-group/index.ts | 4 +-- .../src/BpkMultiSelectChipGroup.tsx | 26 +++++++++++++------ .../src/BpkSingleSelectChipGroup.tsx | 12 ++++----- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/packages/bpk-component-chip-group/index.ts b/packages/bpk-component-chip-group/index.ts index 097b9d971d..79bdc4bff1 100644 --- a/packages/bpk-component-chip-group/index.ts +++ b/packages/bpk-component-chip-group/index.ts @@ -17,7 +17,7 @@ */ import BpkMultiSelectChipGroup, { - type ChipGroupProps, + type MultiSelectChipGroupProps, BpkMultiSelectChipGroupState, CHIP_GROUP_TYPES, type ChipItem, @@ -31,7 +31,7 @@ import BpkSingleSelectChipGroup, { } from './src/BpkSingleSelectChipGroup'; export type { - ChipGroupProps, + MultiSelectChipGroupProps, SingleSelectProps, SingleSelectStateProps, ChipItem, diff --git a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx index 4d7d5d9918..50b4f9a655 100644 --- a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx @@ -69,7 +69,7 @@ export type ChipItem = { hidden?: boolean; } & SingleSelectChipItem; -export type CommonProps = { +type CommonProps = { label?: string; ariaLabel?: string; chipStyle?: ChipStyleType; @@ -77,18 +77,28 @@ export type CommonProps = { ariaMultiselectable?: boolean; }; -export type RailChipGroupProps = { +type RailChipGroupProps = { stickyChip?: ChipItem; leadingNudgerLabel: string; trailingNudgerLabel: string; } & CommonProps; -export type WrapChipGroupProps = { +type WrapChipGroupProps = { } & CommonProps; -export type ChipGroupProps = (RailChipGroupProps & { type: typeof CHIP_GROUP_TYPES.rail } | WrapChipGroupProps & { type: typeof CHIP_GROUP_TYPES.wrap }); +export type MultiSelectChipGroupProps = (RailChipGroupProps & { type: typeof CHIP_GROUP_TYPES.rail } | WrapChipGroupProps & { type: typeof CHIP_GROUP_TYPES.wrap }); -const Chip = ({ ariaMultiselectable, chipIndex, chipItem, chipStyle }: { chipIndex: number, chipItem: ChipItem, chipStyle: ChipStyleType, ariaMultiselectable: boolean }) => { +const Chip = ( + { ariaMultiselectable, + chipIndex, + chipItem, + chipStyle }: + { + chipIndex: number, + chipItem: ChipItem, + chipStyle: ChipStyleType, + ariaMultiselectable: boolean, + }) => { const { accessibilityLabel, component = CHIP_COMPONENT.selectable, @@ -133,7 +143,7 @@ const ChipGroupContent = ( ariaLabel?: string, label?: string, chips: ChipItem[], - chipStyle: ChipStyleType + chipStyle: ChipStyleType, }) => (
; -const BpkMultiSelectChipGroup = (props: ChipGroupProps) => ( +const BpkMultiSelectChipGroup = (props: MultiSelectChipGroupProps) => (
{props.type === CHIP_GROUP_TYPES.rail ? : }
); -export const BpkMultiSelectChipGroupState = ({ chips, ...rest }: ChipGroupProps) => { +export const BpkMultiSelectChipGroupState = ({ chips, ...rest }: MultiSelectChipGroupProps) => { const [selectedChips, setSelectedChips] = useState(chips.map(c => Boolean(c.selected))); const statefulChips = chips.map((chip, index) => chip && ({ diff --git a/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup.tsx index 82999df4cc..93440e1d12 100644 --- a/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup.tsx @@ -18,12 +18,12 @@ import { useState } from 'react'; -import BpkMultiSelectChipGroup, { type ChipItem, type SingleSelectChipItem, type ChipGroupProps } from './BpkMultiSelectChipGroup'; +import BpkMultiSelectChipGroup, { type ChipItem, type SingleSelectChipItem, type MultiSelectChipGroupProps } from './BpkMultiSelectChipGroup'; type CommonSingleSelectProps = { chips: SingleSelectChipItem[]; onItemClick?: (item: SingleSelectChipItem, selected: boolean, index: number) => void, -} & ChipGroupProps; +} & MultiSelectChipGroupProps; export type SingleSelectProps = { selectedIndex?: number; @@ -40,14 +40,12 @@ const BpkSingleSelectChipGroup = ({ chips, onItemClick, selectedIndex, ...rest } }, })); - return ( - - ); + return ; }; export type SingleSelectStateProps = { initiallySelectedIndex?: number; -} & CommonSingleSelectProps +} & CommonSingleSelectProps; export const BpkSingleSelectChipGroupState = ({ initiallySelectedIndex = -1, onItemClick, ...rest }: SingleSelectStateProps) => { const [selectedIndex, setSelectedIndex] = useState(initiallySelectedIndex); @@ -59,7 +57,7 @@ export const BpkSingleSelectChipGroupState = ({ initiallySelectedIndex = -1, onI setSelectedIndex(selected ? index : -1); } - return + return ; }; From f8729eaab473b93ef0ff0f47b9d130d365cf514c Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 24 May 2024 16:42:05 +0100 Subject: [PATCH 51/79] Move stateful ChipGroups to storybook only --- .../bpk-component-chip-group/examples.tsx | 119 ++++++++++++------ packages/bpk-component-chip-group/README.md | 90 ++++--------- packages/bpk-component-chip-group/index.ts | 16 +-- .../src/BpkMultiSelectChipGroup-test.tsx | 115 ----------------- .../src/BpkMultiSelectChipGroup.tsx | 27 +--- .../src/BpkSingleSelectChipGroup-test.tsx | 109 ---------------- .../src/BpkSingleSelectChipGroup.tsx | 29 +---- 7 files changed, 115 insertions(+), 390 deletions(-) diff --git a/examples/bpk-component-chip-group/examples.tsx b/examples/bpk-component-chip-group/examples.tsx index 4996b6d051..4aad03317c 100644 --- a/examples/bpk-component-chip-group/examples.tsx +++ b/examples/bpk-component-chip-group/examples.tsx @@ -21,18 +21,59 @@ import { useState } from 'react'; import { CHIP_TYPES } from '../../packages/bpk-component-chip'; import BpkMultiSelectChipGroup, { - BpkMultiSelectChipGroupState, - BpkSingleSelectChipGroupState, + BpkSingleSelectChipGroup, CHIP_GROUP_TYPES, CHIP_COMPONENT, } from '../../packages/bpk-component-chip-group'; import BpkText, { TEXT_STYLES } from '../../packages/bpk-component-text/index'; import { cssModules } from '../../packages/bpk-react-utils/index'; +import type { + MultiSelectProps, + ChipItem, +} from '../../packages/bpk-component-chip-group'; + import STYLES from './examples.module.scss'; const getClassName = cssModules(STYLES); +const BpkMultiSelectChipGroupState = ({ chips, ...rest }: MultiSelectProps) => { + const [selectedChips, setSelectedChips] = useState(chips.map(c => Boolean(c.selected))); + + const statefulChips = chips.map((chip, index) => chip && ({ + ...chip, + selected: selectedChips[index], + onClick: (selected: boolean, selectedIndex: number) => { + if (chip.onClick) { + chip.onClick(selected, selectedIndex); + } + + const nextSelectedChips = [...selectedChips]; + nextSelectedChips[selectedIndex] = selected; + setSelectedChips(nextSelectedChips); + }, + })); + + return ; +}; + +const BpkSingleSelectChipGroupState = ({ + onItemClick, + selectedIndex: initiallySelectedIndex = -1, + ...rest + }: SingleSelectStateProps) => { + const [selectedIndex, setSelectedIndex] = useState(initiallySelectedIndex); + + const onItemClickWithState = (item: ChipItem, selected: boolean, index: number) => { + if (onItemClick) { + onItemClick(item, selected, index); + } + setSelectedIndex(selected ? index : -1); + }; + + return ; +}; + const chips = [ { text: 'London', @@ -79,7 +120,7 @@ export const BpkChipGroupWrapping = () => (
); @@ -89,8 +130,8 @@ export const BpkSingleChipGroupWrapping = () => (
); @@ -101,9 +142,9 @@ export const BpkChipGroupRail = () => (
); @@ -112,7 +153,7 @@ export const BpkChipGroupRail = () => ( export const BpkChipGroupSticky = () => { const stickyChip = { text: 'Sort & Filter', - } + }; return (
@@ -120,9 +161,9 @@ export const BpkChipGroupSticky = () => { type={CHIP_GROUP_TYPES.rail} chips={chips} stickyChip={stickyChip} - ariaLabel="Select cities" - leadingNudgerLabel="Scroll back" - trailingNudgerLabel="Scroll forward" + ariaLabel='Select cities' + leadingNudgerLabel='Scroll back' + trailingNudgerLabel='Scroll forward' />
); @@ -131,7 +172,7 @@ export const BpkChipGroupSticky = () => { export const OnContrastChipGroup = () => { const stickyChip = { text: 'Sort & Filter', - } + }; return (
@@ -140,9 +181,9 @@ export const OnContrastChipGroup = () => { chips={chips} stickyChip={stickyChip} chipStyle={CHIP_TYPES.default} - ariaLabel="Select cities" - leadingNudgerLabel="Scroll back" - trailingNudgerLabel="Scroll forward" + ariaLabel='Select cities' + leadingNudgerLabel='Scroll back' + trailingNudgerLabel='Scroll forward' />
); @@ -152,7 +193,7 @@ export const OnContrastChipGroup = () => { export const OnDarkChipGroup = () => { const stickyChip = { text: 'Sort & Filter', - } + }; return (
@@ -161,9 +202,9 @@ export const OnDarkChipGroup = () => { chips={chips} stickyChip={stickyChip} chipStyle={CHIP_TYPES.onDark} - ariaLabel="Select cities" - leadingNudgerLabel="Scroll back" - trailingNudgerLabel="Scroll forward" + ariaLabel='Select cities' + leadingNudgerLabel='Scroll back' + trailingNudgerLabel='Scroll forward' />
); @@ -172,7 +213,7 @@ export const OnDarkChipGroup = () => { export const OnImageChipGroup = () => { const stickyChip = { text: 'Sort & Filter', - } + }; return (
@@ -181,9 +222,9 @@ export const OnImageChipGroup = () => { chips={chips} stickyChip={stickyChip} chipStyle={CHIP_TYPES.onImage} - ariaLabel="Select cities" - leadingNudgerLabel="Scroll back" - trailingNudgerLabel="Scroll forward" + ariaLabel='Select cities' + leadingNudgerLabel='Scroll back' + trailingNudgerLabel='Scroll forward' />
); @@ -196,9 +237,9 @@ export const BpkChipGroupWithLabel = () => ( chips={chips} chipStyle={CHIP_TYPES.default} label='Filter' - ariaLabel="Select cities to filter by" - leadingNudgerLabel="Scroll back" - trailingNudgerLabel="Scroll forward" + ariaLabel='Select cities to filter by' + leadingNudgerLabel='Scroll back' + trailingNudgerLabel='Scroll forward' />
); @@ -234,7 +275,7 @@ export const AllChipTypesGroup = () => { ); }; @@ -279,46 +320,46 @@ export const StateManagement = () => { export const MixedExample = () => (
- + Rail - + Rail with sticky chip - + On Contrast - + On Dark - + On Image - + With Label - + Wrapped - + All chip types - + Single Select Group - + State example
-) +); diff --git a/packages/bpk-component-chip-group/README.md b/packages/bpk-component-chip-group/README.md index 9d36dad5f2..72d84d3a19 100644 --- a/packages/bpk-component-chip-group/README.md +++ b/packages/bpk-component-chip-group/README.md @@ -10,7 +10,7 @@ Check the main [Readme](https://github.com/skyscanner/backpack#usage) for a comp ### BpkMultiSelectChipGroup -This is a multiselectable chip group without any built in state management. State of chips must be managed by the consumer as passed in through the `chips` prop, using the `onClick` property of each chip to detect interaction. +This is a multiselectable chip group without any built in state management. State of chips must be managed by the consumer as passed in through the `chips` prop, using the `onClick` property of each chip to detect interaction. See [stories.tsx](/examples/bpk-component-chip-group/examples.tsx) for an example of how to manage state of chips. ```tsx import BpkMultiSelectChipGroup, { @@ -77,78 +77,36 @@ const VerticalsExample = () => { }; ``` -### BpkMultiSelectChipGroupState - -Like a `BpkChipGroup` (multi-selectable) but with basic state management similar to above built in. The `selected` property of each `ChipItem` will affect **only the first render** as the state is managed within the component afterwards. - -```tsx -const StatefulExample = () => ( - -); -``` - ### BpkSingleSelectChipGroup -This is a wrapper around a `BpkChipGroup` that only allows a single chip to be `selected`, determined by the `selectedIndex` prop. If no chips should appear selected, this should be `undefined`. +This is a wrapper around a `BpkChipGroup` that only allows a single chip to be `selected`, determined by the `selectedIndex` prop. If no chips should appear selected, this should be `undefined`. State of selected chips should be managed using the `onItemClick` prop. ```tsx -const SingleSelectExample = () => ( - -); -``` - -### BpkSingleSelectChipGroupState +const SingleSelectExample = () => { + const [selectedIndex, setSelectedIndex] = useState(2); -A wrapper around `BpkSingleSelectChipGroup` that provides basic state management for selecting/deselecting a single chip in the group. The `initiallySelectedIndex` prop controls the chip that will be selected on **first render only**. - -```tsx -const SingleSelectStateExample = () => ( - -); + return ( + { setSelectedIndex(selected ? index : undefined) }} + /> + ); +}; ``` - ## Props Check out the full list of props on Skyscanner's [design system documentation website](https://www.skyscanner.design/latest/components/chip-group/web-4eQsMvYv). diff --git a/packages/bpk-component-chip-group/index.ts b/packages/bpk-component-chip-group/index.ts index 79bdc4bff1..65522e9cd2 100644 --- a/packages/bpk-component-chip-group/index.ts +++ b/packages/bpk-component-chip-group/index.ts @@ -17,30 +17,24 @@ */ import BpkMultiSelectChipGroup, { - type MultiSelectChipGroupProps, - BpkMultiSelectChipGroupState, - CHIP_GROUP_TYPES, + type MultiSelectProps, type ChipItem, type SingleSelectChipItem, CHIP_COMPONENT, + CHIP_GROUP_TYPES, } from './src/BpkMultiSelectChipGroup'; import BpkSingleSelectChipGroup, { type SingleSelectProps, - type SingleSelectStateProps, - BpkSingleSelectChipGroupState, } from './src/BpkSingleSelectChipGroup'; export type { - MultiSelectChipGroupProps, - SingleSelectProps, - SingleSelectStateProps, ChipItem, - SingleSelectChipItem + MultiSelectProps, + SingleSelectProps, + SingleSelectChipItem, }; export { - BpkMultiSelectChipGroupState, BpkSingleSelectChipGroup, - BpkSingleSelectChipGroupState, CHIP_GROUP_TYPES, CHIP_COMPONENT, }; diff --git a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup-test.tsx b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup-test.tsx index fb621cf34e..b6192103f7 100644 --- a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup-test.tsx +++ b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup-test.tsx @@ -103,118 +103,3 @@ describe('BpkMultiSelectChipGroup', () => { expect(onClick).toHaveBeenCalledWith(true, 1); }); }); - -describe('BpkMultiSelectChipGroupState', () => { - const chips = [ - { - text: 'London', - onClick: jest.fn(), - }, - { - text: 'Berlin', - onClick: jest.fn(), - }, { - text: 'New York', - onClick: jest.fn(), - } - ]; - - beforeEach(() => { - jest.resetAllMocks(); - }); - - it('should select a chip when clicked', async () => { - const user = userEvent.setup(); - - render( - , - ); - - const chip = screen.getByRole('checkbox', { name: 'Berlin' }); - await user.click(chip); - - expect(chip).toHaveClass('bpk-chip--default-selected'); - }); - - it('should allow multiple chips to be selected', async () => { - const user = userEvent.setup(); - - render( - , - ); - - const berlinChip = screen.getByRole('checkbox', { name: 'Berlin' }); - const londonChip = screen.getByRole('checkbox', { name: 'London' }); - - await user.click(berlinChip); - await user.click(londonChip); - - expect(berlinChip).toHaveClass('bpk-chip--default-selected'); - expect(londonChip).toHaveClass('bpk-chip--default-selected'); - }); - - it('should unselect a chip when selected and clicked', async () => { - const user = userEvent.setup(); - - render( - , - ); - - const chip = screen.getByRole('checkbox', { name: 'Berlin' }); - await user.click(chip); - - expect(chip).toHaveClass('bpk-chip--default-selected'); - - await user.click(chip); - - expect(chip).not.toHaveClass('bpk-chip--default-selected'); - }); - - it('should call onclick with the selected chip and index when clicked', async () => { - const user = userEvent.setup(); - - render( - , - ); - - await user.click(screen.getByRole('checkbox', { name: 'Berlin' })); - - const { onClick } = chips[1]; - - expect(onClick).toHaveBeenCalledTimes(1); - expect(onClick).toHaveBeenCalledWith(true, 1); - }); - - it('should allow chips to be selected initially when passed in chips array', () => { - render( - , - ); - - const chip = screen.getByRole('checkbox', { name: 'Berlin' }); - - expect(chip).toHaveClass('bpk-chip--default-selected'); - }); -}); diff --git a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx index 50b4f9a655..ee4d316351 100644 --- a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ import type { ReactNode } from 'react'; -import { useRef, useState } from 'react'; +import { useRef } from 'react'; import BpkBreakpoint, { BREAKPOINTS } from '../../bpk-component-breakpoint'; import BpkSelectableChip, { BpkDismissibleChip, BpkIconChip, BpkDropdownChip, CHIP_TYPES } from '../../bpk-component-chip'; @@ -86,7 +86,7 @@ type RailChipGroupProps = { type WrapChipGroupProps = { } & CommonProps; -export type MultiSelectChipGroupProps = (RailChipGroupProps & { type: typeof CHIP_GROUP_TYPES.rail } | WrapChipGroupProps & { type: typeof CHIP_GROUP_TYPES.wrap }); +export type MultiSelectProps = (RailChipGroupProps & { type: typeof CHIP_GROUP_TYPES.rail } | WrapChipGroupProps & { type: typeof CHIP_GROUP_TYPES.wrap }); const Chip = ( { ariaMultiselectable, @@ -229,31 +229,10 @@ const WrapChipGroup = ({ chips={chips} label={label} />; -const BpkMultiSelectChipGroup = (props: MultiSelectChipGroupProps) => ( +const BpkMultiSelectChipGroup = (props: MultiSelectProps) => (
{props.type === CHIP_GROUP_TYPES.rail ? : }
); - -export const BpkMultiSelectChipGroupState = ({ chips, ...rest }: MultiSelectChipGroupProps) => { - const [selectedChips, setSelectedChips] = useState(chips.map(c => Boolean(c.selected))); - - const statefulChips = chips.map((chip, index) => chip && ({ - ...chip, - selected: selectedChips[index], - onClick: (selected: boolean, selectedIndex: number) => { - if (chip.onClick) { - chip.onClick(selected, selectedIndex); - } - - const nextSelectedChips = [...selectedChips]; - nextSelectedChips[selectedIndex] = selected; - setSelectedChips(nextSelectedChips); - }, - })); - - return -}; - export default BpkMultiSelectChipGroup; diff --git a/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup-test.tsx b/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup-test.tsx index f9bde7e140..4ee945e096 100644 --- a/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup-test.tsx +++ b/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup-test.tsx @@ -99,112 +99,3 @@ describe('BpkSingleSelectChipGroup', () => { expect(screen.getByRole('radio', { name: 'Florence' })).not.toHaveClass('bpk-chip--default-selected') }); }); - -describe('BpkSingleSelectChipGroupState', () => { - const chips = [ - { - text: 'London', - }, - { - text: 'Berlin', - }, - { - text: 'Florence', - }, - ]; - - it('should select a chip when clicked', async () => { - const user = userEvent.setup(); - - render( - , - ); - - const chip = screen.getByRole('radio', { name: 'Berlin' }); - - await user.click(chip); - - expect(chip).toHaveClass('bpk-chip--default-selected'); - }); - - it('should only allow one chip to be selected', async () => { - const user = userEvent.setup(); - - render( - , - ); - - const berlinChip = screen.getByRole('radio', { name: 'Berlin' }); - - await user.click(berlinChip); - - expect(berlinChip).toHaveClass('bpk-chip--default-selected'); - - const londonChip = screen.getByRole('radio', { name: 'London' }); - - await user.click(londonChip); - - expect(londonChip).toHaveClass('bpk-chip--default-selected'); - expect(berlinChip).not.toHaveClass('bpk-chip--default-selected'); - }); - - it('should deselect a chip when selected and clicked', async () => { - const user = userEvent.setup(); - - render( - , - ); - - const chip = screen.getByRole('radio', { name: 'Berlin' }); - - await user.click(chip); - - expect(chip).toHaveClass('bpk-chip--default-selected'); - - await user.click(chip); - - expect(chip).not.toHaveClass('bpk-chip--default-selected'); - }); - - it('should call onItemClick with the correct params when clicked', async () => { - const user = userEvent.setup(); - - const onItemClick = jest.fn(); - - render( - , - ); - - await user.click(screen.getByRole('radio', { name: 'Berlin' })); - - expect(onItemClick).toHaveBeenCalledTimes(1); - expect(onItemClick).toHaveBeenCalledWith(chips[1], true, 1); - }); - - it('should have initiallySelectedIndex selected before interaction', () => { - render( - , - ); - - const chip = screen.getByRole('radio', { name: 'Berlin' }); - - expect(chip).toHaveClass('bpk-chip--default-selected'); - }); -}); diff --git a/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup.tsx index 93440e1d12..8a10b91d12 100644 --- a/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup.tsx @@ -16,18 +16,13 @@ * limitations under the License. */ -import { useState } from 'react'; +import BpkMultiSelectChipGroup, { type SingleSelectChipItem, type MultiSelectProps } from './BpkMultiSelectChipGroup'; -import BpkMultiSelectChipGroup, { type ChipItem, type SingleSelectChipItem, type MultiSelectChipGroupProps } from './BpkMultiSelectChipGroup'; - -type CommonSingleSelectProps = { +export type SingleSelectProps = { chips: SingleSelectChipItem[]; onItemClick?: (item: SingleSelectChipItem, selected: boolean, index: number) => void, -} & MultiSelectChipGroupProps; - -export type SingleSelectProps = { selectedIndex?: number; -} & CommonSingleSelectProps; +} & MultiSelectProps; const BpkSingleSelectChipGroup = ({ chips, onItemClick, selectedIndex, ...rest }: SingleSelectProps) => { const chipsWithSelection = chips.map((chip, index) => chip && ({ @@ -43,22 +38,4 @@ const BpkSingleSelectChipGroup = ({ chips, onItemClick, selectedIndex, ...rest } return ; }; -export type SingleSelectStateProps = { - initiallySelectedIndex?: number; -} & CommonSingleSelectProps; - -export const BpkSingleSelectChipGroupState = ({ initiallySelectedIndex = -1, onItemClick, ...rest }: SingleSelectStateProps) => { - const [selectedIndex, setSelectedIndex] = useState(initiallySelectedIndex); - - const onItemClickWithState = (item: ChipItem, selected: boolean, index: number) => { - if (onItemClick) { - onItemClick(item, selected, index); - } - setSelectedIndex(selected ? index : -1); - } - - return ; -}; - - export default BpkSingleSelectChipGroup; From 50785cb55234d347ca7515b3f4417022d5b0c9bc Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 24 May 2024 16:47:23 +0100 Subject: [PATCH 52/79] fix type errors --- examples/bpk-component-chip-group/examples.tsx | 6 +++--- examples/bpk-component-chip-group/stories.ts | 12 +++++------- .../src/BpkMultiSelectChipGroup-test.tsx | 2 +- .../src/BpkSingleSelectChipGroup-test.tsx | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/examples/bpk-component-chip-group/examples.tsx b/examples/bpk-component-chip-group/examples.tsx index 4aad03317c..9e84156b5b 100644 --- a/examples/bpk-component-chip-group/examples.tsx +++ b/examples/bpk-component-chip-group/examples.tsx @@ -23,7 +23,7 @@ import { CHIP_TYPES } from '../../packages/bpk-component-chip'; import BpkMultiSelectChipGroup, { BpkSingleSelectChipGroup, CHIP_GROUP_TYPES, - CHIP_COMPONENT, + CHIP_COMPONENT } from '../../packages/bpk-component-chip-group'; import BpkText, { TEXT_STYLES } from '../../packages/bpk-component-text/index'; import { cssModules } from '../../packages/bpk-react-utils/index'; @@ -31,7 +31,7 @@ import { cssModules } from '../../packages/bpk-react-utils/index'; import type { MultiSelectProps, ChipItem, -} from '../../packages/bpk-component-chip-group'; + SingleSelectProps} from '../../packages/bpk-component-chip-group'; import STYLES from './examples.module.scss'; @@ -61,7 +61,7 @@ const BpkSingleSelectChipGroupState = ({ onItemClick, selectedIndex: initiallySelectedIndex = -1, ...rest - }: SingleSelectStateProps) => { + }: SingleSelectProps) => { const [selectedIndex, setSelectedIndex] = useState(initiallySelectedIndex); const onItemClickWithState = (item: ChipItem, selected: boolean, index: number) => { diff --git a/examples/bpk-component-chip-group/stories.ts b/examples/bpk-component-chip-group/stories.ts index 1c5ee6304b..5680cd566c 100644 --- a/examples/bpk-component-chip-group/stories.ts +++ b/examples/bpk-component-chip-group/stories.ts @@ -18,8 +18,6 @@ import BpkMultiSelectChipGroup, { BpkSingleSelectChipGroup, - BpkMultiSelectChipGroupState, - BpkSingleSelectChipGroupState, } from '../../packages/bpk-component-chip-group'; import { @@ -41,8 +39,6 @@ export default { component: BpkMultiSelectChipGroup, subcomponents: { BpkChipGroupSingleSelect: BpkSingleSelectChipGroup, - BpkChipGroupState: BpkMultiSelectChipGroupState, - BpkChipGroupSingleSelectState: BpkSingleSelectChipGroupState, // TODO: can we show the shape of ChipItem here? }, }; @@ -58,7 +54,9 @@ export const WithLabel = BpkChipGroupWithLabel; export const AllChipTypes = AllChipTypesGroup; export const ExampleStateManagement = StateManagement; export const VisualTest = MixedExample; -export const VisualTestWithZoom = VisualTest.bind({}); -VisualTestWithZoom.args = { - zoomEnabled: true +export const VisualTestWithZoom = { + render: VisualTest, + args: { + zoomEnabled: true + }, }; diff --git a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup-test.tsx b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup-test.tsx index b6192103f7..74ebf86969 100644 --- a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup-test.tsx +++ b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup-test.tsx @@ -20,7 +20,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import '@testing-library/jest-dom'; -import BpkMultiSelectChipGroup, { BpkMultiSelectChipGroupState, CHIP_GROUP_TYPES } from './BpkMultiSelectChipGroup'; +import BpkMultiSelectChipGroup, { CHIP_GROUP_TYPES } from './BpkMultiSelectChipGroup'; const defaultProps = { type: CHIP_GROUP_TYPES.wrap, diff --git a/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup-test.tsx b/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup-test.tsx index 4ee945e096..c276dd56ec 100644 --- a/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup-test.tsx +++ b/packages/bpk-component-chip-group/src/BpkSingleSelectChipGroup-test.tsx @@ -20,7 +20,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { CHIP_GROUP_TYPES } from './BpkMultiSelectChipGroup'; -import BpkSingleSelectChipGroup, { BpkSingleSelectChipGroupState } from './BpkSingleSelectChipGroup'; +import BpkSingleSelectChipGroup from './BpkSingleSelectChipGroup'; const defaultProps = { type: CHIP_GROUP_TYPES.wrap, From 2664de9a456776fe37b42c53f756ed5549cac6dd Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 24 May 2024 17:10:43 +0100 Subject: [PATCH 53/79] fix list keys --- .../bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx index ee4d316351..9a19f475fe 100644 --- a/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx +++ b/packages/bpk-component-chip-group/src/BpkMultiSelectChipGroup.tsx @@ -112,7 +112,6 @@ const Chip = ( const Component = CHIP_COMPONENT_MAP[component]; return hidden ? null : ( {ariaLabel && {ariaLabel}} {label && {label}} - {chips.map((chip, index) => )} + {chips.map((chip, index) => )} ); From 0bec6e23a4deb524cead6296ee9af1d2ea14385d Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Tue, 28 May 2024 10:51:39 +0100 Subject: [PATCH 54/79] debug nudger test --- packages/bpk-component-chip-group/src/Nudger-test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 724ce51583..4af2423d81 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -63,6 +63,7 @@ describe('Nudger', () => { await act(async () => { jest.advanceTimersByTime(100); await user.click(screen.getByRole('button')); + jest.runAllTimers(); }); const leading = position === POSITION.leading; From 366ff34ea1b00df7b64e91e6a5c89026bdbcaff0 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Tue, 28 May 2024 11:07:44 +0100 Subject: [PATCH 55/79] sync act --- packages/bpk-component-chip-group/src/Nudger-test.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 4af2423d81..715a0421ad 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -60,11 +60,10 @@ describe('Nudger', () => { render(); - await act(async () => { + act(() => { jest.advanceTimersByTime(100); - await user.click(screen.getByRole('button')); - jest.runAllTimers(); }); + await user.click(screen.getByRole('button')); const leading = position === POSITION.leading; const isLeft = (leading && !isRtl) || (!leading && isRtl); From 1bc4373ecbc03284494e32412eda0dfff10e15ba Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 31 May 2024 14:48:27 +0100 Subject: [PATCH 56/79] use waitFor --- packages/bpk-component-chip-group/src/Nudger-test.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 715a0421ad..268eff2df0 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -18,7 +18,7 @@ import type { MutableRefObject } from 'react'; -import { act, render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { CHIP_TYPES } from '../../bpk-component-chip'; @@ -60,9 +60,10 @@ describe('Nudger', () => { render(); - act(() => { - jest.advanceTimersByTime(100); + await waitFor(() => { + expect(screen.getByRole('button')).not.toHaveAttribute('disabled'); }); + await user.click(screen.getByRole('button')); const leading = position === POSITION.leading; From 983840f6feaf59de5c495b05d9aa8e1854efb89e Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 31 May 2024 15:08:53 +0100 Subject: [PATCH 57/79] no waiting --- packages/bpk-component-chip-group/src/Nudger-test.tsx | 7 +------ packages/bpk-component-chip-group/src/Nudger.tsx | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 268eff2df0..e63585945b 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -18,7 +18,7 @@ import type { MutableRefObject } from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; +import { act, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { CHIP_TYPES } from '../../bpk-component-chip'; @@ -57,13 +57,8 @@ describe('Nudger', () => { const user = userEvent.setup({ delay: null }); const mockScrollContainerRef = createMockScrollContainerRef(isRtl); mockIsRtl.mockReturnValue(isRtl); - render(); - await waitFor(() => { - expect(screen.getByRole('button')).not.toHaveAttribute('disabled'); - }); - await user.click(screen.getByRole('button')); const leading = position === POSITION.leading; diff --git a/packages/bpk-component-chip-group/src/Nudger.tsx b/packages/bpk-component-chip-group/src/Nudger.tsx index 3f2e47de72..c8f1f76ceb 100644 --- a/packages/bpk-component-chip-group/src/Nudger.tsx +++ b/packages/bpk-component-chip-group/src/Nudger.tsx @@ -98,7 +98,7 @@ const Nudger = ({ title={ariaLabel} type={CHIP_STYLE_TO_BUTTON_STYLE[chipStyle]} iconOnly - disabled={!show || !enabled} + disabled={!enabled} onClick={() => { if (scrollContainerRef.current) { scrollContainerRef.current.scrollBy({ From 15528f0a80c93ad681bf02c719af092c808d22d4 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 31 May 2024 15:16:35 +0100 Subject: [PATCH 58/79] act only --- packages/bpk-component-chip-group/src/Nudger-test.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index e63585945b..861d8aeb89 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -59,7 +59,9 @@ describe('Nudger', () => { mockIsRtl.mockReturnValue(isRtl); render(); - await user.click(screen.getByRole('button')); + await act(async () => { + await user.click(screen.getByRole('button')); + }); const leading = position === POSITION.leading; const isLeft = (leading && !isRtl) || (!leading && isRtl); From 625a5883e15aaa6c036a622630c27a6114a7c0d1 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 31 May 2024 16:08:58 +0100 Subject: [PATCH 59/79] no fake timers --- packages/bpk-component-chip-group/src/Nudger-test.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 861d8aeb89..08a32b4c31 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -18,7 +18,7 @@ import type { MutableRefObject } from 'react'; -import { act, render, screen, waitFor } from '@testing-library/react'; +import { act, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { CHIP_TYPES } from '../../bpk-component-chip'; @@ -45,7 +45,6 @@ const createMockScrollContainerRef = (isRtl: boolean): MutableRefObject { beforeEach(() => { jest.resetAllMocks(); - jest.useFakeTimers(); }); it.each([ @@ -54,7 +53,7 @@ describe('Nudger', () => { [POSITION.trailing, true], [POSITION.leading, true], ])('should call scrollBy when leading=%s and isRtl=%s', async (position, isRtl) => { - const user = userEvent.setup({ delay: null }); + const user = userEvent.setup(); const mockScrollContainerRef = createMockScrollContainerRef(isRtl); mockIsRtl.mockReturnValue(isRtl); render(); From dc23d8d061c60a01dc3cca5b74b931a7748ede08 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 31 May 2024 16:19:55 +0100 Subject: [PATCH 60/79] debug act --- .../src/Nudger-test.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 08a32b4c31..23df7e7e70 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -53,22 +53,23 @@ describe('Nudger', () => { [POSITION.trailing, true], [POSITION.leading, true], ])('should call scrollBy when leading=%s and isRtl=%s', async (position, isRtl) => { - const user = userEvent.setup(); + // const user = userEvent.setup(); const mockScrollContainerRef = createMockScrollContainerRef(isRtl); mockIsRtl.mockReturnValue(isRtl); render(); - await act(async () => { - await user.click(screen.getByRole('button')); - }); + // await act(async () => { + // await user.click(screen.getByRole('button')); + // }); const leading = position === POSITION.leading; const isLeft = (leading && !isRtl) || (!leading && isRtl); - expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledTimes(1); - expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledWith({ - left: isLeft ? -150 : 150, - behavior: 'smooth', - }); + expect(true).toBeTruthy(); + // expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledTimes(1); + // expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledWith({ + // left: isLeft ? -150 : 150, + // behavior: 'smooth', + // }); }); it('should render button style matching chips', () => { From cdefa6bccd8bc1906b49525e7f8194ec8dbcd9c0 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 31 May 2024 16:27:23 +0100 Subject: [PATCH 61/79] no async --- packages/bpk-component-chip-group/src/Nudger-test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 23df7e7e70..ea51d0e6d1 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -52,7 +52,7 @@ describe('Nudger', () => { [POSITION.leading, false], [POSITION.trailing, true], [POSITION.leading, true], - ])('should call scrollBy when leading=%s and isRtl=%s', async (position, isRtl) => { + ])('should call scrollBy when leading=%s and isRtl=%s', (position, isRtl) => { // const user = userEvent.setup(); const mockScrollContainerRef = createMockScrollContainerRef(isRtl); mockIsRtl.mockReturnValue(isRtl); From ec6a519ded462f4e2e6f41429b0028a7d3c60cf3 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Fri, 31 May 2024 16:40:30 +0100 Subject: [PATCH 62/79] render in act --- .../src/Nudger-test.tsx | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index ea51d0e6d1..2215e9c2e6 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -45,6 +45,7 @@ const createMockScrollContainerRef = (isRtl: boolean): MutableRefObject { beforeEach(() => { jest.resetAllMocks(); + jest.useFakeTimers(); }); it.each([ @@ -52,24 +53,22 @@ describe('Nudger', () => { [POSITION.leading, false], [POSITION.trailing, true], [POSITION.leading, true], - ])('should call scrollBy when leading=%s and isRtl=%s', (position, isRtl) => { - // const user = userEvent.setup(); + ])('should call scrollBy when leading=%s and isRtl=%s', async (position, isRtl) => { + const user = userEvent.setup({ delay: null }); const mockScrollContainerRef = createMockScrollContainerRef(isRtl); mockIsRtl.mockReturnValue(isRtl); - render(); - - // await act(async () => { - // await user.click(screen.getByRole('button')); - // }); + await act(async () => { + render(); + await user.click(screen.getByRole('button')); + }) const leading = position === POSITION.leading; const isLeft = (leading && !isRtl) || (!leading && isRtl); - expect(true).toBeTruthy(); - // expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledTimes(1); - // expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledWith({ - // left: isLeft ? -150 : 150, - // behavior: 'smooth', - // }); + expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledTimes(1); + expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledWith({ + left: isLeft ? -150 : 150, + behavior: 'smooth', + }); }); it('should render button style matching chips', () => { From 9817f8e5f0845e02e707d8cb9cf730e9925906c8 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Tue, 4 Jun 2024 09:15:17 +0100 Subject: [PATCH 63/79] Increase rail padding for focus indicators --- packages/bpk-component-chip-group/src/BpkChipGroup.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss index 91a2bd9e11..fdf83d308f 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss @@ -35,7 +35,7 @@ gap: tokens.bpk-spacing-md(); &--rail { - padding: 2 * tokens.$bpk-one-pixel-rem 0; + padding: 4 * tokens.$bpk-one-pixel-rem 0; flex-wrap: nowrap; } From 8782ed7dac806a8683c393e58e74fc8260dbc84d Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Tue, 4 Jun 2024 09:45:59 +0100 Subject: [PATCH 64/79] clear timers --- packages/bpk-component-chip-group/src/Nudger-test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 2215e9c2e6..4a6944cf5e 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -69,6 +69,7 @@ describe('Nudger', () => { left: isLeft ? -150 : 150, behavior: 'smooth', }); + jest.clearAllTimers(); }); it('should render button style matching chips', () => { From a1365bc2606676c06828f25e1e6ff666256452fc Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Tue, 4 Jun 2024 10:06:13 +0100 Subject: [PATCH 65/79] dont clear timers --- packages/bpk-component-chip-group/src/Nudger-test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 4a6944cf5e..2215e9c2e6 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -69,7 +69,6 @@ describe('Nudger', () => { left: isLeft ? -150 : 150, behavior: 'smooth', }); - jest.clearAllTimers(); }); it('should render button style matching chips', () => { From ffafa99aea1a4ff1fec458c283b944bbc35cf097 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Tue, 4 Jun 2024 10:48:46 +0100 Subject: [PATCH 66/79] advanceTimers userEvent --- packages/bpk-component-chip-group/src/Nudger-test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 2215e9c2e6..0aa318e4ea 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -54,13 +54,13 @@ describe('Nudger', () => { [POSITION.trailing, true], [POSITION.leading, true], ])('should call scrollBy when leading=%s and isRtl=%s', async (position, isRtl) => { - const user = userEvent.setup({ delay: null }); + const user = userEvent.setup({advanceTimers: jest.advanceTimersByTime}); const mockScrollContainerRef = createMockScrollContainerRef(isRtl); mockIsRtl.mockReturnValue(isRtl); await act(async () => { render(); await user.click(screen.getByRole('button')); - }) + }); const leading = position === POSITION.leading; const isLeft = (leading && !isRtl) || (!leading && isRtl); From c3f9a2990b153f41aaafa40cc26e1cccc5cb08ae Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Tue, 4 Jun 2024 11:07:54 +0100 Subject: [PATCH 67/79] update mock values --- packages/bpk-component-chip-group/src/Nudger-test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 0aa318e4ea..0bbbc77602 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -38,7 +38,7 @@ const createMockScrollContainerRef = (isRtl: boolean): MutableRefObject void, offsetWidth: 100, scrollLeft: isRtl ? -150 : 150, - scrollWidth: 500, + scrollWidth: 150, }, } as MutableRefObject); From a5baac3e64a5f108637d0a5fb648a1a8a1a7df6c Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Tue, 4 Jun 2024 11:18:11 +0100 Subject: [PATCH 68/79] no mock values --- packages/bpk-component-chip-group/src/Nudger-test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 0bbbc77602..0ab699e6e6 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -36,9 +36,9 @@ jest.mock('../../bpk-react-utils/index', () => ({ const createMockScrollContainerRef = (isRtl: boolean): MutableRefObject => ({ current: { scrollBy: jest.fn() as (options?: any) => void, - offsetWidth: 100, - scrollLeft: isRtl ? -150 : 150, - scrollWidth: 150, + offsetWidth: 0, + scrollLeft: 0, + scrollWidth: 0, }, } as MutableRefObject); From 5ef8753566d0e8b107610809ca4ec539f2d7eb59 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Tue, 4 Jun 2024 11:29:40 +0100 Subject: [PATCH 69/79] tweak mock values --- packages/bpk-component-chip-group/src/Nudger-test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 0ab699e6e6..8370db2902 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -36,9 +36,9 @@ jest.mock('../../bpk-react-utils/index', () => ({ const createMockScrollContainerRef = (isRtl: boolean): MutableRefObject => ({ current: { scrollBy: jest.fn() as (options?: any) => void, - offsetWidth: 0, - scrollLeft: 0, - scrollWidth: 0, + offsetWidth: 2, + scrollLeft: isRtl ? -1 : 1, + scrollWidth: 2, }, } as MutableRefObject); From 46703b2032da5197e33a26a11ace532110f12e2b Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Tue, 4 Jun 2024 14:32:42 +0100 Subject: [PATCH 70/79] debug --- packages/bpk-component-chip-group/src/Nudger-test.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 8370db2902..01d337d9ce 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -36,9 +36,9 @@ jest.mock('../../bpk-react-utils/index', () => ({ const createMockScrollContainerRef = (isRtl: boolean): MutableRefObject => ({ current: { scrollBy: jest.fn() as (options?: any) => void, - offsetWidth: 2, - scrollLeft: isRtl ? -1 : 1, - scrollWidth: 2, + offsetWidth: 100, + scrollLeft: isRtl ? -150 : 150, + scrollWidth: 500, }, } as MutableRefObject); @@ -59,7 +59,7 @@ describe('Nudger', () => { mockIsRtl.mockReturnValue(isRtl); await act(async () => { render(); - await user.click(screen.getByRole('button')); + // await user.click(screen.getByRole('button')); }); const leading = position === POSITION.leading; From bc96802831944a2a639189b980dd4bbc30ff5812 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Tue, 4 Jun 2024 14:38:57 +0100 Subject: [PATCH 71/79] debug --- packages/bpk-component-chip-group/src/Nudger-test.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 01d337d9ce..3e9706fc21 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -64,11 +64,11 @@ describe('Nudger', () => { const leading = position === POSITION.leading; const isLeft = (leading && !isRtl) || (!leading && isRtl); - expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledTimes(1); - expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledWith({ - left: isLeft ? -150 : 150, - behavior: 'smooth', - }); + // expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledTimes(1); + // expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledWith({ + // left: isLeft ? -150 : 150, + // behavior: 'smooth', + // }); }); it('should render button style matching chips', () => { From 9dbba837d45e569757a704418cce88953629fbd3 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Tue, 4 Jun 2024 15:43:32 +0100 Subject: [PATCH 72/79] use waitFor again --- .../src/Nudger-test.tsx | 20 ++++++++++--------- .../bpk-component-chip-group/src/Nudger.tsx | 4 ++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 3e9706fc21..96e40d5aed 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -18,7 +18,7 @@ import type { MutableRefObject } from 'react'; -import { act, render, screen } from '@testing-library/react'; +import { act, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { CHIP_TYPES } from '../../bpk-component-chip'; @@ -57,18 +57,20 @@ describe('Nudger', () => { const user = userEvent.setup({advanceTimers: jest.advanceTimersByTime}); const mockScrollContainerRef = createMockScrollContainerRef(isRtl); mockIsRtl.mockReturnValue(isRtl); - await act(async () => { - render(); - // await user.click(screen.getByRole('button')); + render(); + await waitFor(() => { + expect(screen.getByRole('button')).not.toHaveAttribute('disabled', ''); }); + await user.click(screen.getByRole('button')); + const leading = position === POSITION.leading; const isLeft = (leading && !isRtl) || (!leading && isRtl); - // expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledTimes(1); - // expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledWith({ - // left: isLeft ? -150 : 150, - // behavior: 'smooth', - // }); + expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledTimes(1); + expect(mockScrollContainerRef.current.scrollBy).toHaveBeenCalledWith({ + left: isLeft ? -150 : 150, + behavior: 'smooth', + }); }); it('should render button style matching chips', () => { diff --git a/packages/bpk-component-chip-group/src/Nudger.tsx b/packages/bpk-component-chip-group/src/Nudger.tsx index c8f1f76ceb..a8deffe40c 100644 --- a/packages/bpk-component-chip-group/src/Nudger.tsx +++ b/packages/bpk-component-chip-group/src/Nudger.tsx @@ -63,7 +63,7 @@ const Nudger = ({ scrollContainerRef }: Props) => { const [show, setShow] = useState(false); - const [enabled, setEnabled] = useState(true); + const [enabled, setEnabled] = useState(false); const leading = position === POSITION.leading; const rtl = isRTL(); @@ -80,8 +80,8 @@ const Nudger = ({ const showLeading = scrollValue > 0; const showTrailing = scrollValue < scrollWidth - offsetWidth; - setEnabled((leading && showLeading) || (!leading && showTrailing)) setShow(showLeading || showTrailing); + setEnabled((leading && showLeading) || (!leading && showTrailing)) }, 100); return () => clearInterval(interval); }, [leading, rtl, scrollContainerRef]); From 9db1c3a3ef8947a4b517891c6de90a7667c1b273 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Tue, 4 Jun 2024 15:52:38 +0100 Subject: [PATCH 73/79] less specific attribute testing --- packages/bpk-component-chip-group/src/Nudger-test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index 96e40d5aed..d0438ae174 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -59,7 +59,7 @@ describe('Nudger', () => { mockIsRtl.mockReturnValue(isRtl); render(); await waitFor(() => { - expect(screen.getByRole('button')).not.toHaveAttribute('disabled', ''); + expect(screen.getByRole('button')).not.toHaveAttribute('disabled'); }); await user.click(screen.getByRole('button')); From 24a63d2f5641cff943f7aaaf1f11ab9e9580047d Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Wed, 5 Jun 2024 11:34:30 +0100 Subject: [PATCH 74/79] padding for focus indicators at the end --- .../bpk-component-chip-group/src/BpkChipGroup.module.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss index fdf83d308f..3457c1136d 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss @@ -35,8 +35,10 @@ gap: tokens.bpk-spacing-md(); &--rail { - padding: 4 * tokens.$bpk-one-pixel-rem 0; + padding-top: 4 * tokens.$bpk-one-pixel-rem; + padding-bottom: 4 * tokens.$bpk-one-pixel-rem; flex-wrap: nowrap; + padding-inline-end: 4 * tokens.$bpk-one-pixel-rem; } &--wrap { From 4d09246ef1d6908ed36c7ad0ba99634ffb4484f4 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Wed, 5 Jun 2024 13:50:01 +0100 Subject: [PATCH 75/79] negative margins for focus indicators --- .../src/BpkChipGroup.module.scss | 18 +++++++++--------- .../src/Nudger.module.scss | 2 ++ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss index 3457c1136d..02aaa8de6d 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss @@ -22,6 +22,7 @@ .bpk-chip-group-container { display: flex; + margin: 0 (-4 * tokens.$bpk-one-pixel-rem); align-items: center; white-space: nowrap; } @@ -29,25 +30,24 @@ .bpk-chip-group { display: flex; margin: 0; - padding: 0; + padding: 4 * tokens.$bpk-one-pixel-rem; align-items: baseline; border: none; gap: tokens.bpk-spacing-md(); - &--rail { - padding-top: 4 * tokens.$bpk-one-pixel-rem; - padding-bottom: 4 * tokens.$bpk-one-pixel-rem; - flex-wrap: nowrap; - padding-inline-end: 4 * tokens.$bpk-one-pixel-rem; - } - &--wrap { + padding-top: 0; + padding-bottom: 0; flex-wrap: wrap; } } +.bpk-sticky-chip-container:first-child { + margin-inline-start: 4 * tokens.$bpk-one-pixel-rem; +} + .bpk-sticky-chip-container { - margin-inline-end: tokens.bpk-spacing-md(); + margin-inline-end: tokens.bpk-spacing-sm(); padding-inline-end: tokens.bpk-spacing-md(); @include borders.bpk-border-right-sm(tokens.$bpk-line-day); diff --git a/packages/bpk-component-chip-group/src/Nudger.module.scss b/packages/bpk-component-chip-group/src/Nudger.module.scss index 639911664a..b8bf1e4751 100644 --- a/packages/bpk-component-chip-group/src/Nudger.module.scss +++ b/packages/bpk-component-chip-group/src/Nudger.module.scss @@ -21,9 +21,11 @@ .bpk-chip-group-nudger { &--leading { margin-inline-end: tokens.bpk-spacing-md(); + margin-inline-start: 4 * tokens.$bpk-one-pixel-rem; } &--trailing { + margin-inline-end: 4 * tokens.$bpk-one-pixel-rem; margin-inline-start: tokens.bpk-spacing-md(); } From 14fd3fefe12cbae779f4903888ad002127607182 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Wed, 5 Jun 2024 14:18:22 +0100 Subject: [PATCH 76/79] only render nudgers when visible --- packages/bpk-component-chip-group/src/Nudger-test.tsx | 7 +++++-- packages/bpk-component-chip-group/src/Nudger.module.scss | 4 ---- packages/bpk-component-chip-group/src/Nudger.tsx | 5 ++--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/bpk-component-chip-group/src/Nudger-test.tsx b/packages/bpk-component-chip-group/src/Nudger-test.tsx index d0438ae174..157f61d0fd 100644 --- a/packages/bpk-component-chip-group/src/Nudger-test.tsx +++ b/packages/bpk-component-chip-group/src/Nudger-test.tsx @@ -59,7 +59,7 @@ describe('Nudger', () => { mockIsRtl.mockReturnValue(isRtl); render(); await waitFor(() => { - expect(screen.getByRole('button')).not.toHaveAttribute('disabled'); + expect(screen.queryByRole('button')).not.toHaveAttribute('disabled'); }); await user.click(screen.getByRole('button')); @@ -73,8 +73,11 @@ describe('Nudger', () => { }); }); - it('should render button style matching chips', () => { + it('should render button style matching chips', async () => { render(); + await waitFor(() => { + expect(screen.queryByRole('button')).toBeVisible(); + }); expect(screen.getByRole('button')).toHaveClass('bpk-button--secondary-on-dark'); }); diff --git a/packages/bpk-component-chip-group/src/Nudger.module.scss b/packages/bpk-component-chip-group/src/Nudger.module.scss index b8bf1e4751..d99280c1eb 100644 --- a/packages/bpk-component-chip-group/src/Nudger.module.scss +++ b/packages/bpk-component-chip-group/src/Nudger.module.scss @@ -28,8 +28,4 @@ margin-inline-end: 4 * tokens.$bpk-one-pixel-rem; margin-inline-start: tokens.bpk-spacing-md(); } - - &--hidden { - display: none; - } } diff --git a/packages/bpk-component-chip-group/src/Nudger.tsx b/packages/bpk-component-chip-group/src/Nudger.tsx index a8deffe40c..969768e31f 100644 --- a/packages/bpk-component-chip-group/src/Nudger.tsx +++ b/packages/bpk-component-chip-group/src/Nudger.tsx @@ -89,10 +89,9 @@ const Nudger = ({ const classNames = getClassName( 'bpk-chip-group-nudger', `bpk-chip-group-nudger--${leading ? "leading" : "trailing"}`, - !show && `bpk-chip-group-nudger--hidden`, ) - return ( + return show ? (
: }
- ); + ) : null; } export default Nudger; From 8ea37d8c25878e0de266d1c3d8f9ba7ea43a5647 Mon Sep 17 00:00:00 2001 From: Iain Cattermole <22656572+Iain530@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:25:21 +0100 Subject: [PATCH 77/79] Use bpk tokens for 4px Co-authored-by: Ollie Curtis <8831547+olliecurtis@users.noreply.github.com> --- .../bpk-component-chip-group/src/BpkChipGroup.module.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss index 02aaa8de6d..059cab7edd 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss @@ -22,7 +22,7 @@ .bpk-chip-group-container { display: flex; - margin: 0 (-4 * tokens.$bpk-one-pixel-rem); + margin: 0 -(tokens.bpk-spacing-sm()); align-items: center; white-space: nowrap; } @@ -30,7 +30,7 @@ .bpk-chip-group { display: flex; margin: 0; - padding: 4 * tokens.$bpk-one-pixel-rem; + padding: tokens.bpk-spacing-sm(); align-items: baseline; border: none; gap: tokens.bpk-spacing-md(); @@ -43,7 +43,7 @@ } .bpk-sticky-chip-container:first-child { - margin-inline-start: 4 * tokens.$bpk-one-pixel-rem; + margin-inline-start: tokens.bpk-spacing-sm(); } .bpk-sticky-chip-container { From 9ffdff89889cdfc9f3130355408cd6776db875ed Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Wed, 5 Jun 2024 16:26:22 +0100 Subject: [PATCH 78/79] use bpk tokens for nudger --- packages/bpk-component-chip-group/src/Nudger.module.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bpk-component-chip-group/src/Nudger.module.scss b/packages/bpk-component-chip-group/src/Nudger.module.scss index d99280c1eb..8c6058191e 100644 --- a/packages/bpk-component-chip-group/src/Nudger.module.scss +++ b/packages/bpk-component-chip-group/src/Nudger.module.scss @@ -21,11 +21,11 @@ .bpk-chip-group-nudger { &--leading { margin-inline-end: tokens.bpk-spacing-md(); - margin-inline-start: 4 * tokens.$bpk-one-pixel-rem; + margin-inline-start: tokens.bpk-spacing-sm(); } &--trailing { - margin-inline-end: 4 * tokens.$bpk-one-pixel-rem; + margin-inline-end: tokens.bpk-spacing-sm(); margin-inline-start: tokens.bpk-spacing-md(); } } From 11089552dbeaf367a7974c5b7e664a28f3aa08a7 Mon Sep 17 00:00:00 2001 From: Iain Cattermole Date: Thu, 6 Jun 2024 10:54:34 +0100 Subject: [PATCH 79/79] fix margin brackets --- packages/bpk-component-chip-group/src/BpkChipGroup.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss index 059cab7edd..48a50611d3 100644 --- a/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss +++ b/packages/bpk-component-chip-group/src/BpkChipGroup.module.scss @@ -22,7 +22,7 @@ .bpk-chip-group-container { display: flex; - margin: 0 -(tokens.bpk-spacing-sm()); + margin: 0 (- tokens.bpk-spacing-sm()); align-items: center; white-space: nowrap; }