-
Notifications
You must be signed in to change notification settings - Fork 178
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(components): listButton and children creation
closes AUTH-657
- Loading branch information
Showing
9 changed files
with
485 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,132 @@ | ||
import * as React from 'react' | ||
|
||
import { ListButton as ListButtonComponent } from './index' | ||
import { | ||
ListButtonAccordion, | ||
ListButtonAccordionContainer, | ||
ListButtonRadioButton, | ||
} from './ListButtonChildren/index' | ||
import { StyledText } from '../..' | ||
import type { Meta, StoryObj } from '@storybook/react' | ||
|
||
const meta: Meta<typeof ListButtonComponent> = { | ||
title: 'Library/Atoms/ListButton', | ||
component: ListButtonComponent, | ||
argTypes: { | ||
type: { | ||
control: { | ||
type: 'select', | ||
options: ['noActive', 'success', 'warning'], | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
export default meta | ||
|
||
type Story = StoryObj<typeof ListButtonComponent> | ||
|
||
const Template = (args: any): JSX.Element => { | ||
const [containerExpand, setContainerExpand] = React.useState<boolean>(false) | ||
const [buttonValue, setButtonValue] = React.useState<string | null>(null) | ||
const [nestedButtonValue, setNestedButtonValue] = React.useState< | ||
string | null | ||
>(null) | ||
|
||
return ( | ||
<ListButtonComponent | ||
{...args} | ||
onClick={() => { | ||
setContainerExpand(!containerExpand) | ||
}} | ||
> | ||
<ListButtonAccordion | ||
key="main" | ||
mainHeadline="Main heading" | ||
headline="accordion heading" | ||
isExpanded={containerExpand} | ||
> | ||
<ListButtonAccordionContainer id="mainAccordionContainer"> | ||
<> | ||
<ListButtonRadioButton | ||
key="buttonNested" | ||
isSelected={buttonValue === 'radio button nested'} | ||
buttonValue="radio button nested" | ||
buttonText="Radio button with nested" | ||
onChange={e => { | ||
e.stopPropagation() | ||
setButtonValue('radio button nested') | ||
}} | ||
/> | ||
|
||
{buttonValue === 'radio button nested' ? ( | ||
<ListButtonAccordionContainer id="nestedAccordionContainer"> | ||
<ListButtonAccordion | ||
key="child" | ||
isNested | ||
headline="nested accordion heading" | ||
isExpanded={buttonValue === 'radio button nested'} | ||
> | ||
<> | ||
<ListButtonRadioButton | ||
isSelected={nestedButtonValue === 'radio button1'} | ||
buttonValue="radio button1" | ||
buttonText="nested button" | ||
onChange={() => { | ||
setNestedButtonValue('radio button1') | ||
}} | ||
/> | ||
{nestedButtonValue === 'radio button1' ? ( | ||
<StyledText desktop="bodyDefaultRegular"> | ||
Nested button option | ||
</StyledText> | ||
) : null} | ||
</> | ||
<ListButtonRadioButton | ||
isSelected={nestedButtonValue === 'radio button2'} | ||
buttonValue="radio button2" | ||
buttonText="nested button 2" | ||
onChange={() => { | ||
setNestedButtonValue('radio button2') | ||
}} | ||
/> | ||
<ListButtonRadioButton | ||
isSelected={nestedButtonValue === 'radio button3'} | ||
buttonValue="radio button3" | ||
buttonText="nested button 3" | ||
onChange={() => { | ||
setNestedButtonValue('radio button3') | ||
}} | ||
/> | ||
</ListButtonAccordion> | ||
</ListButtonAccordionContainer> | ||
) : null} | ||
</> | ||
<> | ||
<ListButtonRadioButton | ||
key="buttonNonNested" | ||
isSelected={buttonValue === 'radio button non nest'} | ||
buttonValue="radio button non nest" | ||
buttonText="Radio button without nested" | ||
onChange={() => { | ||
setButtonValue('radio button non nest') | ||
}} | ||
/> | ||
|
||
{buttonValue === 'radio button non nest' ? ( | ||
<StyledText desktop="bodyDefaultRegular"> | ||
Non nested button option | ||
</StyledText> | ||
) : null} | ||
</> | ||
</ListButtonAccordionContainer> | ||
</ListButtonAccordion> | ||
</ListButtonComponent> | ||
) | ||
} | ||
export const ListButton: Story = { | ||
render: Template, | ||
args: { | ||
type: 'noActive', | ||
}, | ||
} |
59 changes: 59 additions & 0 deletions
59
components/src/atoms/ListButton/ListButtonChildren/ListButtonAccordion.tsx
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,59 @@ | ||
import * as React from 'react' | ||
import { Flex } from '../../../primitives' | ||
import { DIRECTION_COLUMN } from '../../../styles' | ||
import { SPACING } from '../../../ui-style-constants' | ||
import { StyledText } from '../../StyledText' | ||
|
||
interface ListButtonAccordionProps { | ||
headline: string | ||
children: React.ReactNode | ||
// determines if the accordion is expanded or not | ||
isExpanded?: boolean | ||
// is it nested into another accordion? | ||
isNested?: boolean | ||
// optional main headline for the top level accordion | ||
mainHeadline?: string | ||
} | ||
|
||
/* | ||
To be used with ListButton, ListButtonAccordion and ListButtonRadioButton | ||
This is the accordion component to use both as just an accordion or nested accordion | ||
**/ | ||
export function ListButtonAccordion( | ||
props: ListButtonAccordionProps | ||
): JSX.Element { | ||
const { | ||
headline, | ||
children, | ||
mainHeadline, | ||
isExpanded = false, | ||
isNested = false, | ||
} = props | ||
|
||
return ( | ||
<Flex flexDirection={DIRECTION_COLUMN} width="100%"> | ||
{mainHeadline != null ? ( | ||
<Flex marginBottom={isExpanded ? SPACING.spacing40 : '0'}> | ||
<StyledText desktopStyle="bodyDefaultSemiBold"> | ||
{mainHeadline} | ||
</StyledText> | ||
</Flex> | ||
) : null} | ||
{isExpanded ? ( | ||
<Flex | ||
marginTop={isNested ? SPACING.spacing4 : '0'} | ||
flexDirection={DIRECTION_COLUMN} | ||
gridGap={SPACING.spacing4} | ||
marginLeft={isNested ? SPACING.spacing40 : '0'} | ||
> | ||
<Flex marginBottom={SPACING.spacing4}> | ||
<StyledText desktopStyle="bodyDefaultSemiBold"> | ||
{headline} | ||
</StyledText> | ||
</Flex> | ||
<Flex flexDirection={DIRECTION_COLUMN}>{children}</Flex> | ||
</Flex> | ||
) : null} | ||
</Flex> | ||
) | ||
} |
29 changes: 29 additions & 0 deletions
29
components/src/atoms/ListButton/ListButtonChildren/ListButtonAccordionContainer.tsx
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,29 @@ | ||
import * as React from 'react' | ||
import { Flex } from '../../../primitives' | ||
import { DIRECTION_COLUMN } from '../../../styles' | ||
|
||
interface ListButtonAccordionContainerProps { | ||
children: React.ReactNode | ||
id: string | ||
} | ||
/* | ||
To be used with ListButtonAccordion to stop propagation since multiple | ||
layers have a CTA | ||
**/ | ||
export function ListButtonAccordionContainer( | ||
props: ListButtonAccordionContainerProps | ||
): JSX.Element { | ||
const { id, children } = props | ||
|
||
return ( | ||
<Flex | ||
key={id} | ||
flexDirection={DIRECTION_COLUMN} | ||
onClick={(e: React.MouseEvent) => { | ||
e.stopPropagation() | ||
}} | ||
> | ||
{children} | ||
</Flex> | ||
) | ||
} |
84 changes: 84 additions & 0 deletions
84
components/src/atoms/ListButton/ListButtonChildren/ListButtonRadioButton.tsx
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,84 @@ | ||
import * as React from 'react' | ||
import styled, { css } from 'styled-components' | ||
import { SPACING } from '../../../ui-style-constants' | ||
import { BORDERS, COLORS } from '../../../helix-design-system' | ||
import { Flex } from '../../../primitives' | ||
import { StyledText } from '../../StyledText' | ||
|
||
import type { StyleProps } from '../../../primitives' | ||
|
||
interface ListButtonRadioButtonProps extends StyleProps { | ||
buttonText: string | ||
buttonValue: string | number | ||
onChange: React.ChangeEventHandler<HTMLInputElement> | ||
disabled?: boolean | ||
isSelected?: boolean | ||
id?: string | ||
} | ||
|
||
// used for helix and as a child button to ListButtonAccordion | ||
export function ListButtonRadioButton( | ||
props: ListButtonRadioButtonProps | ||
): JSX.Element { | ||
const { | ||
buttonText, | ||
buttonValue, | ||
isSelected = false, | ||
onChange, | ||
disabled = false, | ||
id = buttonText, | ||
} = props | ||
|
||
const SettingButton = styled.input` | ||
display: none; | ||
` | ||
|
||
const AVAILABLE_BUTTON_STYLE = css` | ||
background: ${COLORS.white}; | ||
color: ${COLORS.black90}; | ||
&:hover { | ||
background-color: ${COLORS.grey10}; | ||
} | ||
` | ||
|
||
const SELECTED_BUTTON_STYLE = css` | ||
background: ${COLORS.blue50}; | ||
color: ${COLORS.white}; | ||
&:active { | ||
background-color: ${COLORS.blue60}; | ||
} | ||
` | ||
|
||
const DISABLED_STYLE = css` | ||
color: ${COLORS.grey40}; | ||
background-color: ${COLORS.grey10}; | ||
` | ||
|
||
const SettingButtonLabel = styled.label` | ||
border-radius: ${BORDERS.borderRadius8}; | ||
cursor: pointer; | ||
padding: 14px ${SPACING.spacing12}; | ||
width: 100%; | ||
${isSelected ? SELECTED_BUTTON_STYLE : AVAILABLE_BUTTON_STYLE} | ||
${disabled && DISABLED_STYLE} | ||
` | ||
|
||
return ( | ||
<Flex width="100%" margin={SPACING.spacing4}> | ||
<SettingButton | ||
checked={isSelected} | ||
id={id} | ||
disabled={disabled} | ||
onChange={onChange} | ||
type="radio" | ||
value={buttonValue} | ||
/> | ||
<SettingButtonLabel role="label" htmlFor={id}> | ||
<StyledText desktopStyle="bodyDefaultRegular">{buttonText}</StyledText> | ||
</SettingButtonLabel> | ||
</Flex> | ||
) | ||
} |
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 * from './ListButtonAccordion' | ||
export * from './ListButtonAccordionContainer' | ||
export * from './ListButtonRadioButton' |
46 changes: 46 additions & 0 deletions
46
components/src/atoms/ListButton/__tests__/ListButton.test.tsx
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,46 @@ | ||
import * as React from 'react' | ||
import { vi, describe, it, expect, beforeEach } from 'vitest' | ||
import '@testing-library/jest-dom/vitest' | ||
import { fireEvent, screen } from '@testing-library/react' | ||
import { renderWithProviders } from '../../../testing/utils' | ||
import { COLORS } from '../../../helix-design-system' | ||
|
||
import { ListButton } from '..' | ||
|
||
const render = (props: React.ComponentProps<typeof ListButton>) => | ||
renderWithProviders(<ListButton {...props} />) | ||
|
||
describe('ListButton', () => { | ||
let props: React.ComponentProps<typeof ListButton> | ||
|
||
beforeEach(() => { | ||
props = { | ||
type: 'noActive', | ||
children: <div>mock ListButton content</div>, | ||
onClick: vi.fn(), | ||
} | ||
}) | ||
|
||
it('should render correct style - noActive', () => { | ||
render(props) | ||
const listButton = screen.getByTestId('ListButton_noActive') | ||
expect(listButton).toHaveStyle(`backgroundColor: ${COLORS.grey35}`) | ||
}) | ||
it('should render correct style - connected', () => { | ||
props.type = 'connected' | ||
render(props) | ||
const listButton = screen.getByTestId('ListButton_connected') | ||
expect(listButton).toHaveStyle(`backgroundColor: ${COLORS.green35}`) | ||
}) | ||
it('should render correct style - notConnected', () => { | ||
props.type = 'notConnected' | ||
render(props) | ||
const listButton = screen.getByTestId('ListButton_notConnected') | ||
expect(listButton).toHaveStyle(`backgroundColor: ${COLORS.yellow35}`) | ||
}) | ||
it('should call on click when pressed', () => { | ||
render(props) | ||
fireEvent.click(screen.getByText('mock ListButton content')) | ||
expect(props.onClick).toHaveBeenCalled() | ||
}) | ||
}) |
35 changes: 35 additions & 0 deletions
35
components/src/atoms/ListButton/__tests__/ListButtonAccordion.test.tsx
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,35 @@ | ||
import * as React from 'react' | ||
import { describe, it, beforeEach } from 'vitest' | ||
import { screen } from '@testing-library/react' | ||
import { renderWithProviders } from '../../../testing/utils' | ||
|
||
import { ListButtonAccordion } from '..' | ||
|
||
const render = (props: React.ComponentProps<typeof ListButtonAccordion>) => | ||
renderWithProviders(<ListButtonAccordion {...props} />) | ||
|
||
describe('ListButtonAccordion', () => { | ||
let props: React.ComponentProps<typeof ListButtonAccordion> | ||
|
||
beforeEach(() => { | ||
props = { | ||
children: <div>mock ListButtonAccordion content</div>, | ||
headline: 'mock headline', | ||
isExpanded: true, | ||
} | ||
}) | ||
|
||
it('should render non nested accordion', () => { | ||
render(props) | ||
screen.getByText('mock headline') | ||
screen.getByText('mock ListButtonAccordion content') | ||
}) | ||
it('should render non nested accordion with main headline', () => { | ||
props.isNested = true | ||
props.mainHeadline = 'mock main headline' | ||
render(props) | ||
screen.getByText('mock main headline') | ||
screen.getByText('mock headline') | ||
screen.getByText('mock ListButtonAccordion content') | ||
}) | ||
}) |
Oops, something went wrong.