Skip to content

Commit

Permalink
[LUNA-1231][BpkSegmentedControl] Add segmented control component (#3482)
Browse files Browse the repository at this point in the history
[LUNA-1231][BpkSegmentedControl] Add segmented control component  (#3482)

---------

Co-authored-by: Maggie Pentcheva <[email protected]>
Co-authored-by: Jenna Mclelland <[email protected]>
  • Loading branch information
3 people authored Jun 21, 2024
1 parent e2a4335 commit 2a11238
Show file tree
Hide file tree
Showing 8 changed files with 595 additions and 0 deletions.
159 changes: 159 additions & 0 deletions examples/bpk-component-segmented-control/examples.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* 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 {
privateSegmentedControlCanvasDefaultDay,
privateSegmentedControlSurfaceContrastDay,
} from '@skyscanner/bpk-foundations-web/tokens/base.es6';

import BpkSegmentedControl from '../../packages/bpk-component-segmented-control';
import { SEGMENT_TYPES } from '../../packages/bpk-component-segmented-control/src/BpkSegmentedControl';
// @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`.
import { BpkDarkExampleWrapper } from '../bpk-storybook-utils';

const canvasDefaultWrapperStyle = {
display: 'flex',
maxWidth: '500px',
backgroundColor: privateSegmentedControlCanvasDefaultDay,
padding: '2rem',
};

const surfaceContrastWrapperStyle = {
display: 'flex',
maxWidth: '500px',
backgroundColor: privateSegmentedControlSurfaceContrastDay,
padding: '2rem',
};

// Simple Segmented Control
const SimpleDefault = () => (
<BpkSegmentedControl
buttonContents={['Value', 'Value']}
onItemClick={() => {}}
selectedIndex={0}
type={SEGMENT_TYPES.CanvasDefault}
/>
);

const SimpleCanvasContrast = () => (
<div style={canvasDefaultWrapperStyle}>
<BpkSegmentedControl
buttonContents={['Value', 'Value', 'Value']}
onItemClick={() => {}}
selectedIndex={2}
type={SEGMENT_TYPES.CanvasContrast}
/>
</div>
);

const SimpleSurfaceDefault = () => (
<div style={surfaceContrastWrapperStyle}>
<BpkSegmentedControl
buttonContents={['Value', 'Value', 'Value', 'Value']}
onItemClick={() => {}}
selectedIndex={2}
type={SEGMENT_TYPES.SurfaceDefault}
/>
</div>
);

const SimpleSurfaceContrast = () => (
<BpkDarkExampleWrapper padded>
<BpkSegmentedControl
buttonContents={[
'Very Long Value1',
'Very Long Value2',
'Very Long Value3',
'Very Long Value4',
]}
onItemClick={() => {}}
selectedIndex={2}
type={SEGMENT_TYPES.SurfaceContrast}
/>
</BpkDarkExampleWrapper>
);

// // Complex Segmented Control
const complexButtonContentBest = [
<>
<div>Best</div>
<div>£84</div>
<div>2h average</div>
</>,
];
const complexButtonContentCheapest = [
<>
<div>Cheapest</div>
<div>£34</div>
<div>9h average</div>
</>,
];
const complexButtonContentFastest = [
<>
<div>Fastest</div>
<div>£90</div>
<div>1h average</div>
</>,
];

const allButtonContent = [
complexButtonContentBest,
complexButtonContentCheapest,
complexButtonContentFastest,
];

const ComplexSurfaceContrast = () => (
<BpkDarkExampleWrapper padded style={{ display: 'flex' }}>
<BpkSegmentedControl
buttonContents={allButtonContent}
onItemClick={() => {}}
selectedIndex={1}
type={SEGMENT_TYPES.SurfaceContrast}
shadow
/>
</BpkDarkExampleWrapper>
);

const ComplexSurfaceDefault = () => (
<BpkSegmentedControl
buttonContents={allButtonContent}
onItemClick={() => {}}
selectedIndex={1}
type={SEGMENT_TYPES.SurfaceDefault}
shadow
/>
);

const ComplexSurfaceDefaultNoShadow = () => (
<BpkSegmentedControl
buttonContents={allButtonContent}
onItemClick={() => {}}
selectedIndex={1}
type={SEGMENT_TYPES.SurfaceDefault}
/>
);

export {
SimpleDefault,
SimpleCanvasContrast,
SimpleSurfaceDefault,
SimpleSurfaceContrast,
ComplexSurfaceContrast,
ComplexSurfaceDefault,
ComplexSurfaceDefaultNoShadow,
};
50 changes: 50 additions & 0 deletions examples/bpk-component-segmented-control/stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 BpkSegmentedControl from '../../packages/bpk-component-segmented-control';

import {
SimpleDefault,
SimpleCanvasContrast,
SimpleSurfaceDefault,
SimpleSurfaceContrast,
ComplexSurfaceContrast,
ComplexSurfaceDefault,
ComplexSurfaceDefaultNoShadow,
} from './examples';

export default {
title: 'bpk-component-segmented-control',
component: BpkSegmentedControl,
};

export const SimpleTwoSegmentsCanvasDefault = SimpleDefault;
export const SimpleThreeSegmentsCanvasContrast = SimpleCanvasContrast;
export const SimpleFourSegmentsSurfaceDefault = SimpleSurfaceDefault;
export const SimpleFourSegmentsSurfaceContrast = SimpleSurfaceContrast;
export const ComplexThreeSegmentsSurfaceContrast = ComplexSurfaceContrast;
export const ComplexThreeSegmentsSurfaceDefault = ComplexSurfaceDefault;
export const ComplexThreeSegmentsSurfaceDefaultNoShadow =
ComplexSurfaceDefaultNoShadow;
export const VisualTest = ComplexSurfaceDefault;
export const VisualTestWithZoom = {
render: VisualTest,
args: {
zoomEnabled: true,
},
};
23 changes: 23 additions & 0 deletions packages/bpk-component-segmented-control/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# bpk-segmented-control

## Installation
Check the main [Readme](https://github.com/skyscanner/backpack#usage) for a complete installation guide.

## Usage
```js
import BpkSegmentedControl from '@skyscanner/backpack-web/bpk-component-segmented-control';

export default () => (
<BpkSegmentedControl
buttonContents={buttonContent}
onItemClick={() => {}}
selectedIndex={1} // button selected on load
type={SEGMENT_TYPES.SurfaceContrast}
shadow
/>
)
```


## Props
Check out the full list of props on Skyscanner's [design system documentation website]( https://github.com/Skyscanner/backpack/blob/main/packages/bpk-component-segmented-control/README.md).
24 changes: 24 additions & 0 deletions packages/bpk-component-segmented-control/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Backpack - Skyscanner's Design System
*
* Copyright 2016 Skyscanner Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import BpkSegmentedControl, {
type Props as BpkSegmentControlProps,
} from './src/BpkSegmentedControl';

export type { BpkSegmentControlProps };
export default BpkSegmentedControl;
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Backpack - Skyscanner's Design System
*
* Copyright 2016 Skyscanner Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { render, fireEvent, screen } from '@testing-library/react';
import '@testing-library/jest-dom';

import BpkSegmentedControl, { SEGMENT_TYPES } from './BpkSegmentedControl';

const mockOnItemClick = jest.fn();

const defaultProps = {
buttonContents: ['one', 'two'],
onItemClick: mockOnItemClick,
selectedIndex: 1,
shadow: false,
type: SEGMENT_TYPES.CanvasContrast,
};

describe('BpkSegmentedControl', () => {
beforeEach(() => {
mockOnItemClick.mockClear();
});

it('should render ReactNode contents correctly', () => {
const propsWithReactNodes = {
...defaultProps,
buttonContents: [<div>one</div>, <div>two</div>, <div>three</div>],
};
const { getByText } = render(
<BpkSegmentedControl {...propsWithReactNodes} />,
);

expect(getByText('one')).toBeInTheDocument();
expect(getByText('two')).toBeInTheDocument();
expect(getByText('three')).toBeInTheDocument();
});

it('should render button contents correctly', () => {
const { getByText } = render(<BpkSegmentedControl {...defaultProps} />);

expect(getByText('one')).toBeInTheDocument();
expect(getByText('two')).toBeInTheDocument();
});

it('should call onItemClick with the correct index when a button is clicked', () => {
const { getByText } = render(<BpkSegmentedControl {...defaultProps} />);
const firstButton = getByText('one');
fireEvent.click(firstButton);

expect(mockOnItemClick).toHaveBeenCalledWith(0);
});

it('should update the selected button when a button is clicked', () => {
const { getByText } = render(<BpkSegmentedControl {...defaultProps} />);
const buttonOne = getByText('one');
fireEvent.click(buttonOne);

expect(screen.getByText('one')).toHaveAttribute('aria-pressed', 'true');
expect(screen.getByText('two')).toHaveAttribute('aria-pressed', 'false');
});

it('should render with the correct type class', () => {
const { container } = render(<BpkSegmentedControl {...defaultProps} />);
const button = container.querySelector(
'.bpk-segmented-control--canvas-contrast',
);

expect(button).toBeInTheDocument();
});

it('should apply shadow class when shadow prop is true', () => {
const props = { ...defaultProps, shadow: true };
const { container } = render(<BpkSegmentedControl {...props} />);

expect(container.firstChild).toHaveClass(
'bpk-segmented-control-group-shadow',
);
});

it('should apply the correct class when button is selected and shadow is true', () => {
const props = { ...defaultProps, shadow: true };
const { container } = render(<BpkSegmentedControl {...props} />);
const selectedButton = container.querySelector(
'.bpk-segmented-control--canvas-contrast-selected-shadow',
);

expect(selectedButton).toBeInTheDocument();
});
});
Loading

0 comments on commit 2a11238

Please sign in to comment.