-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
299 additions
and
0 deletions.
There are no files selected for viewing
159 changes: 159 additions & 0 deletions
159
packages/react/src/components/disclosure/disclosure.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,159 @@ | ||
import { shallow } from 'enzyme'; | ||
import { useId } from '../../hooks/use-id'; | ||
import { mountWithProviders } from '../../test-utils/renderer'; | ||
import { Button } from '../buttons/button'; | ||
import { IconButton } from '../buttons/icon-button'; | ||
import { ButtonPropsWithoutOnClick, Container, Disclosure } from './disclosure'; | ||
|
||
jest.mock('../../hooks/use-id'); | ||
|
||
describe('Disclosure', () => { | ||
const content = 'content'; | ||
const idContent = 'idContent'; | ||
const buttonTypes = [ | ||
{ | ||
describeName: 'Button', | ||
component: Button, | ||
props: { | ||
buttonType: 'primary', | ||
label: 'disclose content', | ||
} as ButtonPropsWithoutOnClick, | ||
}, | ||
{ | ||
describeName: 'IconButton', | ||
component: IconButton, | ||
props: { | ||
iconName: 'home', | ||
buttonType: 'primary', | ||
label: 'disclose content', | ||
} as ButtonPropsWithoutOnClick, | ||
}, | ||
]; | ||
|
||
beforeEach(() => { | ||
jest.mocked(useId).mockReturnValue(idContent); | ||
}); | ||
|
||
buttonTypes.forEach(({ | ||
describeName, | ||
props: buttonProps, | ||
component: ButtonComponent, | ||
}) => { | ||
describe(describeName, () => { | ||
it('renders a button and a div', () => { | ||
const wrapper = mountWithProviders( | ||
<Disclosure buttonProps={buttonProps}>{content}</Disclosure>, | ||
); | ||
|
||
expect(wrapper.find(ButtonComponent).exists()).toBe(true); | ||
expect(wrapper.find(Container).exists()).toBe(true); | ||
}); | ||
|
||
it('calls useId to generated content id', () => { | ||
const someId = 'someId'; | ||
|
||
shallow( | ||
<Disclosure buttonProps={buttonProps} idContent={someId}>{content}</Disclosure>, | ||
); | ||
|
||
expect(useId).toHaveBeenCalledWith(someId); | ||
}); | ||
|
||
it('sets div content id to id generated by useId', () => { | ||
const wrapper = mountWithProviders( | ||
<Disclosure buttonProps={buttonProps}>{content}</Disclosure>, | ||
); | ||
|
||
expect(wrapper.find(Container).prop('id')).toBe(idContent); | ||
}); | ||
|
||
it('sets button aria-controls to id generated by useId', () => { | ||
const wrapper = shallow( | ||
<Disclosure buttonProps={buttonProps}>{content}</Disclosure>, | ||
); | ||
|
||
expect(wrapper.find(ButtonComponent).prop('aria-controls')).toBe(idContent); | ||
}); | ||
|
||
it('toggles expanded state when button is clicked', () => { | ||
const wrapper = mountWithProviders( | ||
<Disclosure buttonProps={buttonProps}>{content}</Disclosure>, | ||
); | ||
const button = wrapper.find(ButtonComponent); | ||
|
||
expect(button.prop('aria-expanded')).toBe(false); | ||
button.simulate('click'); | ||
|
||
expect(wrapper.find(ButtonComponent).prop('aria-expanded')).toBe(true); | ||
button.simulate('click'); | ||
expect(wrapper.find(ButtonComponent).prop('aria-expanded')).toBe(false); | ||
}); | ||
|
||
it('renders content container when button has not been clicked yet', () => { | ||
const childrenContent = <p>Test Content</p>; | ||
|
||
const wrapper = shallow( | ||
<Disclosure buttonProps={buttonProps}>{childrenContent}</Disclosure>, | ||
); | ||
|
||
expect(wrapper.find(Container).exists()).toBe(true); | ||
}); | ||
|
||
it('renders children content when button has not been clicked yet', () => { | ||
const childrenContent = <p>Test Content</p>; | ||
|
||
const wrapper = shallow( | ||
<Disclosure buttonProps={buttonProps}>{childrenContent}</Disclosure>, | ||
); | ||
|
||
expect(wrapper.find(Container).contains(childrenContent)).toBe(true); | ||
}); | ||
|
||
it('sets content container expanded to false when button has not been clicked yet', () => { | ||
const childrenContent = <p>Test Content</p>; | ||
|
||
const wrapper = shallow( | ||
<Disclosure buttonProps={buttonProps}>{childrenContent}</Disclosure>, | ||
); | ||
|
||
expect(wrapper.find(Container).prop('$expanded')).toBe(false); | ||
}); | ||
|
||
it('sets container expanded to true after button is clicked', () => { | ||
const childrenContent = <p>Test Content</p>; | ||
const wrapper = mountWithProviders( | ||
<Disclosure buttonProps={buttonProps}>{childrenContent}</Disclosure>, | ||
); | ||
const button = wrapper.find(ButtonComponent); | ||
|
||
button.simulate('click'); | ||
|
||
expect(wrapper.find(Container).prop('$expanded')).toBe(true); | ||
}); | ||
|
||
it('renders content container after button is clicked', () => { | ||
const childrenContent = <p>Test Content</p>; | ||
const wrapper = mountWithProviders( | ||
<Disclosure buttonProps={buttonProps}>{childrenContent}</Disclosure>, | ||
); | ||
const button = wrapper.find(ButtonComponent); | ||
|
||
button.simulate('click'); | ||
|
||
expect(wrapper.find(Container).exists()).toBe(true); | ||
}); | ||
|
||
it('renders children content after button is clicked', () => { | ||
const childrenContent = <p>Test Content</p>; | ||
const wrapper = mountWithProviders( | ||
<Disclosure buttonProps={buttonProps}>{childrenContent}</Disclosure>, | ||
); | ||
const button = wrapper.find(ButtonComponent); | ||
|
||
button.simulate('click'); | ||
|
||
expect(wrapper.find(Container).contains(childrenContent)).toBe(true); | ||
}); | ||
}); | ||
}); | ||
}); |
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,73 @@ | ||
import { FunctionComponent, PropsWithChildren, useState } from 'react'; | ||
import styled from 'styled-components'; | ||
import { useId } from '../../hooks/use-id'; | ||
import { Button, ButtonProps, Size } from '../buttons/button'; | ||
import { IconButton } from '../buttons/icon-button'; | ||
import { IconName } from '../icon/icon'; | ||
|
||
export type ButtonPropsWithoutOnClick = Omit<ButtonProps, 'onClick'> & { | ||
iconName?: IconName; | ||
size?: Size; | ||
} | ||
|
||
interface DisclosureWidgetProps { | ||
idContent?: string; | ||
buttonProps: ButtonPropsWithoutOnClick; | ||
} | ||
|
||
export const Container = styled.div<{ $expanded: boolean; }>` | ||
background-color: ${({ theme }) => theme.component['disclosure-background-color']}; | ||
border: | ||
${({ | ||
$expanded, | ||
theme, | ||
}) => ( | ||
$expanded ? `1px solid ${theme.component['disclosure-border-color']}` : 0 | ||
)}; | ||
border-radius: var(--border-radius); | ||
box-shadow: 0 10px 20px 0 ${({ theme }) => theme.component['disclosure-box-shadow-color']}; | ||
color: ${({ theme }) => theme.component['disclosure-text-color']}; | ||
list-style-type: none; | ||
position: absolute; | ||
visibility: ${({ $expanded }) => ($expanded ? 'visible' : 'hidden')}; | ||
width: 100%; | ||
z-index: 700; | ||
`; | ||
|
||
const DisclosureContainer = styled.div` | ||
position: relative; | ||
`; | ||
|
||
export const Disclosure: FunctionComponent<PropsWithChildren<DisclosureWidgetProps>> = ({ | ||
idContent: providedIdContent, | ||
buttonProps, | ||
children, | ||
}) => { | ||
const [expanded, setExpanded] = useState<boolean>(false); | ||
const idContent = useId(providedIdContent); | ||
|
||
return ( | ||
<DisclosureContainer> | ||
{!buttonProps.iconName && ( | ||
<Button | ||
{...buttonProps /* eslint-disable-line react/jsx-props-no-spreading */} | ||
onClick={() => setExpanded(!expanded)} | ||
aria-expanded={expanded} | ||
aria-controls={idContent} | ||
/> | ||
)} | ||
{buttonProps.iconName && ( | ||
<IconButton | ||
{...buttonProps /* eslint-disable-line react/jsx-props-no-spreading */} | ||
iconName={buttonProps.iconName} | ||
onClick={() => setExpanded(!expanded)} | ||
aria-expanded={expanded} | ||
aria-controls={idContent} | ||
/> | ||
)} | ||
<Container $expanded={expanded} id={idContent}> | ||
{children} | ||
</Container> | ||
</DisclosureContainer> | ||
); | ||
}; |
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
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
21 changes: 21 additions & 0 deletions
21
packages/react/src/themes/tokens/component/disclosure-tokens.ts
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,21 @@ | ||
import { AliasTokens } from '../alias-tokens'; | ||
import { RefTokens } from '../ref-tokens'; | ||
|
||
export type DisclosureTokens = | ||
| 'disclosure-background-color' | ||
| 'disclosure-border-color' | ||
| 'disclosure-box-shadow-color' | ||
| 'disclosure-text-color'; | ||
|
||
export type DisclosureTokenValue = AliasTokens | RefTokens; | ||
|
||
export type DisclosureTokenMap = { | ||
[Token in DisclosureTokens]: DisclosureTokenValue; | ||
}; | ||
|
||
export const defaultDisclosureTokens: DisclosureTokenMap = { | ||
'disclosure-background-color': 'color-menu-background', | ||
'disclosure-border-color': 'color-menu-border', | ||
'disclosure-box-shadow-color': 'color-box-shadow', | ||
'disclosure-text-color': 'color-menu-item-content', | ||
}; |
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 { Meta, StoryObj } from '@storybook/react'; | ||
import { Disclosure } from '@equisoft/design-elements-react'; | ||
import styled from 'styled-components'; | ||
import { decorateWith } from './utils/decorator'; | ||
import { rawCodeParameters } from './utils/parameters'; | ||
|
||
const Container = styled.div` | ||
height: 240px; | ||
`; | ||
|
||
const disclosureMeta: Meta<typeof Disclosure> = { | ||
title: 'Components/Disclosure', | ||
component: Disclosure, | ||
decorators: [decorateWith(Container)], | ||
parameters: rawCodeParameters, | ||
}; | ||
|
||
export default disclosureMeta; | ||
|
||
type Story = StoryObj<typeof Disclosure>; | ||
|
||
export const Default: Story = { | ||
args: { | ||
children: 'content to display', | ||
buttonProps: { | ||
label: 'Display content', | ||
buttonType: 'tertiary', | ||
}, | ||
idContent: 'someContentId', | ||
}, | ||
}; | ||
|
||
export const IconButton: Story = { | ||
args: { | ||
children: 'content to display', | ||
buttonProps: { | ||
iconName: 'home', | ||
buttonType: 'primary', | ||
}, | ||
idContent: 'someContentId', | ||
}, | ||
}; |