-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* #282 - Add EbaySegmentedButtons component * Add tests, update readme * remove unused var * update types * update readme * use className for the button component, update readme * remove redundant code
- Loading branch information
1 parent
4b48329
commit bc8829d
Showing
7 changed files
with
240 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# EbaySegmentedButtons | ||
|
||
## Demo | ||
[Storybook](https://opensource.ebay.com/ebayui-core-react/main/?path=/story/buttons-ebay-segmented-buttons--default) | ||
|
||
## Install | ||
``` | ||
yarn add @ebay/ui-core-react @ebay/skin | ||
``` | ||
|
||
## Usage | ||
``` | ||
import React from 'react' | ||
import { EbaySegmentedButtons, EbaySegmentedButton as Button } from '@ebay/ui-core-react/ebay-segmented-buttons' | ||
import { EbayIcon } from '@ebay/ui-core-react/ebay-icon' | ||
import '@ebay/skin/segmented-buttons' | ||
export const Example = () => ( | ||
<EbaySegmentedButtons | ||
size="large" | ||
onChange={(e, { i, value }) => console.log('Selected:', i, value)} | ||
> | ||
<Button value="1" selected>Button 1</Button> | ||
<Button value="2">Button 2</Button> | ||
<Button value="3"><EbayIcon name="settings24" /> Button 3</Button> | ||
/> | ||
); | ||
``` | ||
|
||
## EbaySegmentedButtons Props | ||
|
||
Name | Type | Required | Description | ||
--- |----------| --- | --- | ||
`size` | enum | No | Can be `regular` (default) or `large` | ||
`onChange` | Function | No | props: (e: event, { index: number, value: string), triggered on selected button change | ||
|
||
## EbaySegmentedButton Props | ||
|
||
Name | Type | Required | Description | ||
--- | --- |---------------------------------| --- | ||
`value` | String | No | the value to use with `onChange` callback | ||
`selected` | Boolean | No | Whether or not the button is selected |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import React from 'react' | ||
import { fireEvent, render, screen } from '@testing-library/react' | ||
import { composeStories } from '@storybook/react' | ||
import * as stories from './index.stories' | ||
|
||
const { Default, WithIcons } = composeStories(stories) | ||
|
||
describe('<EbaySegmentedButtons>', () => { | ||
it('should render the default', () => { | ||
render(<Default />) | ||
|
||
expect(screen.getByRole('list')).toBeInTheDocument() | ||
expect(screen.getAllByRole('listitem')).toHaveLength(4) | ||
expect(screen.getAllByRole('button')).toHaveLength(4) | ||
|
||
const firstButton = screen.getByRole('button', { name: 'Q1' }) | ||
expect(firstButton).toBeInTheDocument() | ||
expect(firstButton).toHaveAttribute('aria-current', 'true') | ||
}) | ||
|
||
it('should handle button clicks', () => { | ||
const spy = jest.fn() | ||
render(<Default onChange={spy} />) | ||
|
||
const firstButton = screen.getByRole('button', { name: 'Q1' }) | ||
const secondButton = screen.getByRole('button', { name: 'Q2' }) | ||
expect(secondButton).toBeInTheDocument() | ||
expect(secondButton).not.toHaveAttribute('aria-current') | ||
|
||
fireEvent.click(secondButton) | ||
|
||
expect(spy).toHaveBeenCalledTimes(1) | ||
expect(firstButton).not.toHaveAttribute('aria-current') | ||
expect(secondButton).toHaveAttribute('aria-current', 'true') | ||
}) | ||
|
||
it('should render buttons with icons', () => { | ||
render(<WithIcons />) | ||
expect(screen.getByRole('list')).toBeInTheDocument() | ||
expect(screen.getAllByRole('listitem')).toHaveLength(2) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import React from 'react' | ||
import { Meta, StoryObj } from '@storybook/react' | ||
import { action } from '@storybook/addon-actions' | ||
import { EbaySegmentedButtons, EbaySegmentedButton as Button } from '..' | ||
import { EbayIcon } from '../../ebay-icon' | ||
|
||
export default { | ||
title: 'Buttons/ebay-segmented-buttons', | ||
component: EbaySegmentedButtons, | ||
argTypes: { | ||
size: { | ||
options: ['large', 'regular'], | ||
control: { | ||
type: 'select' | ||
}, | ||
table: { | ||
defaultValue: { | ||
summary: "regular", | ||
}, | ||
}, | ||
}, | ||
onChange: { | ||
action: 'changed', | ||
table: { | ||
category: 'Events', | ||
defaultValue: { | ||
summary: 'originalEvent, { index, value }' | ||
} | ||
} | ||
} | ||
} | ||
} as Meta<typeof EbaySegmentedButtons> | ||
|
||
export const Default: StoryObj<typeof EbaySegmentedButtons> = { | ||
render: args => ( | ||
<EbaySegmentedButtons onChange={action('change')} {...args}> | ||
<Button selected value="quarter1">Q1</Button> | ||
<Button value="quarter2">Q2</Button> | ||
<Button value="quarter3">Q3</Button> | ||
<Button value="quarter4">Q4</Button> | ||
</EbaySegmentedButtons> | ||
) | ||
} | ||
|
||
export const WithIcons: StoryObj<typeof EbaySegmentedButtons> = { | ||
render: args => ( | ||
<EbaySegmentedButtons onChange={action('change')} {...args}> | ||
<Button selected><EbayIcon name="fullView24"/> Desktop</Button> | ||
<Button><EbayIcon name="mobile24"/> Mobile</Button> | ||
</EbaySegmentedButtons> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import React, { FC } from 'react' | ||
import classNames from 'classnames' | ||
import { excludeComponent, findComponent } from '../common/component-utils' | ||
import { EbayIcon } from '../ebay-icon' | ||
import { SegmentedButtonProps } from './types' | ||
|
||
const SegmentedButton: FC<SegmentedButtonProps> = ({ | ||
selected, | ||
children, | ||
className, | ||
...rest | ||
}) => { | ||
const icon = findComponent(children, EbayIcon) | ||
|
||
const iconWithText = () => { | ||
const text = excludeComponent(children, EbayIcon) | ||
|
||
return ( | ||
<span className="segmented-buttons__button-cell"> | ||
{icon} | ||
<span>{text}</span> | ||
</span> | ||
) | ||
} | ||
|
||
return ( | ||
<li> | ||
<button | ||
className={classNames('segmented-buttons__button', className)} | ||
aria-current={selected || undefined} | ||
{...rest} | ||
> | ||
{icon ? iconWithText() : children} | ||
</button> | ||
</li> | ||
) | ||
} | ||
|
||
export default SegmentedButton |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { default as EbaySegmentedButtons } from './segmented-buttons' | ||
export { default as EbaySegmentedButton } from './button' | ||
export type { SegmentedButtonsProps, SegmentedButtonProps, SegmentedButtonSize } from './types' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import React, { cloneElement, FC, ReactElement, useState } from 'react' | ||
import classNames from 'classnames' | ||
import { SegmentedButtonProps, SegmentedButtonsProps } from './types' | ||
import { filterByType } from '../common/component-utils' | ||
import SegmentedButton from './button' | ||
|
||
const EbaySegmentedButtons: FC<SegmentedButtonsProps> = ({ | ||
size, | ||
className, | ||
onChange = () => {}, | ||
children, | ||
...rest | ||
}) => { | ||
const buttons = filterByType(children, SegmentedButton) | ||
const [selectedIndex, setSelectedIndex] = useState( | ||
buttons.findIndex(button => button.props.selected) || 0 | ||
) | ||
|
||
const handleClick = (e, index: number, value: string) => { | ||
setSelectedIndex(index) | ||
onChange(e, { index, value }) | ||
} | ||
|
||
return ( | ||
<div | ||
className={classNames('segmented-buttons', size && `segmented-buttons--${size}`, className)} | ||
{...rest} | ||
> | ||
<ul> | ||
{buttons.map((button: ReactElement, i) => { | ||
const { | ||
value, | ||
...buttonRest | ||
}: SegmentedButtonProps = button.props | ||
|
||
return cloneElement(button, { | ||
...buttonRest, | ||
onClick: e => handleClick(e, i, value), | ||
selected: i === selectedIndex | ||
} as SegmentedButtonProps) | ||
})} | ||
</ul> | ||
</div> | ||
) | ||
} | ||
|
||
export default EbaySegmentedButtons |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { ComponentProps } from 'react' | ||
import { EbayChangeEventHandler, EbayMouseEventHandler } from '../common/event-utils/types' | ||
|
||
export type SegmentedButtonProps = Omit<ComponentProps<'button'>, 'onClick'> & { | ||
value?: string; | ||
selected?: boolean; | ||
onClick?: EbayMouseEventHandler<HTMLButtonElement>; | ||
} | ||
|
||
export type SegmentedButtonSize = 'large' | 'regular' | ||
|
||
export type SegmentedButtonsProps = Omit<ComponentProps<'div'>, 'onChange'> & { | ||
size?: SegmentedButtonSize; | ||
onChange?: EbayChangeEventHandler<HTMLButtonElement, { index: number, value?: string }>; | ||
} |