Skip to content

Commit

Permalink
Merge d5d33af into 6bf5766
Browse files Browse the repository at this point in the history
  • Loading branch information
alexbrillant committed Jul 15, 2024
2 parents 6bf5766 + d5d33af commit d6cd706
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 0 deletions.
159 changes: 159 additions & 0 deletions packages/react/src/components/disclosure/disclosure.test.tsx
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);
});
});
});
});
73 changes: 73 additions & 0 deletions packages/react/src/components/disclosure/disclosure.tsx
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>
);
};
1 change: 1 addition & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export { ExternalLink } from './components/external-link/external-link';
export { Heading } from './components/heading/heading';
export { GlobalHeader } from './components/global-header/global-header';
export { DropdownMenuButton } from './components/dropdown-menu-button/dropdown-menu-button';
export { Disclosure } from './components/disclosure/disclosure';
export { ExternalItemProps } from './components/dropdown-menu/list-items/external-item';
export { NavItemProps } from './components/dropdown-menu/list-items/nav-item';
export { Icon } from './components/icon/icon';
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/themes/tokens/component-tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CheckboxTokens, defaultCheckboxTokens } from './component/checkbox-toke
import { ChooserTokens, defaultChooserTokens } from './component/chooser-tokens';
import { ComboboxTokens, defaultComboboxTokens } from './component/combobox-tokens';
import { DatepickerTokens, defaultDatepickerTokens } from './component/datepicker-tokens';
import { defaultDisclosureTokens, DisclosureTokens } from './component/disclosure-tokens';
import { defaultDropdownListTokens, DropdownListTokens } from './component/dropdown-list-tokens';
import { CardTokens, defaultCardTokens } from './component/card-tokens';
import { defaultDropdownMenuTokens, DropdownMenuTokens } from './component/dropdown-menu-tokens';
Expand Down Expand Up @@ -57,6 +58,7 @@ export type ComponentTokens =
| BentoMenuButtonTokens
| ButtonTokens
| BreadcrumbTokens
| DisclosureTokens
| FocusTokens
| HeadingTokens
| LabelTokens
Expand Down Expand Up @@ -115,6 +117,7 @@ export const defaultComponentTokens: ComponentTokenMap = {
...defaultAvatarTokens,
...defaultBentoMenuButtonTokens,
...defaultButtonTokens,
...defaultDisclosureTokens,
...defaultFocusTokens,
...defaultHeadingTokens,
...defaultLabelTokens,
Expand Down
21 changes: 21 additions & 0 deletions packages/react/src/themes/tokens/component/disclosure-tokens.ts
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',
};
42 changes: 42 additions & 0 deletions packages/storybook/stories/disclosure.stories.tsx
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',
},
};

0 comments on commit d6cd706

Please sign in to comment.