From 4bd651b9e32c568d86b30463858c20ef290d8c07 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Thu, 7 Apr 2022 22:54:27 +0200 Subject: chore: add a HeadingButton component --- .../molecules/buttons/heading-button.module.scss | 41 ++++++++++++ .../molecules/buttons/heading-button.stories.tsx | 75 ++++++++++++++++++++++ .../molecules/buttons/heading-button.test.tsx | 32 +++++++++ .../molecules/buttons/heading-button.tsx | 66 +++++++++++++++++++ 4 files changed, 214 insertions(+) create mode 100644 src/components/molecules/buttons/heading-button.module.scss create mode 100644 src/components/molecules/buttons/heading-button.stories.tsx create mode 100644 src/components/molecules/buttons/heading-button.test.tsx create mode 100644 src/components/molecules/buttons/heading-button.tsx (limited to 'src') diff --git a/src/components/molecules/buttons/heading-button.module.scss b/src/components/molecules/buttons/heading-button.module.scss new file mode 100644 index 0000000..d068001 --- /dev/null +++ b/src/components/molecules/buttons/heading-button.module.scss @@ -0,0 +1,41 @@ +@use "@styles/abstracts/functions" as fun; + +.icon { + transition: all 0.25s ease-in-out 0s; +} + +.wrapper { + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: space-between; + gap: var(--spacing-md); + width: 100%; + padding: 0; + position: sticky; + top: 0; + background: inherit; + border: none; + border-top: fun.convert-px(2) solid var(--color-primary-dark); + border-bottom: fun.convert-px(2) solid var(--color-primary-dark); + cursor: pointer; + + &:hover, + &:focus { + .icon { + background: var(--color-primary-light); + color: var(--color-fg-inverted); + transform: scale(1.25); + + &::before, + &::after { + background: var(--color-bg); + } + } + } +} + +.heading { + background: none; + padding: var(--spacing-2xs) 0; +} diff --git a/src/components/molecules/buttons/heading-button.stories.tsx b/src/components/molecules/buttons/heading-button.stories.tsx new file mode 100644 index 0000000..0a23b08 --- /dev/null +++ b/src/components/molecules/buttons/heading-button.stories.tsx @@ -0,0 +1,75 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { useState } from 'react'; +import { IntlProvider } from 'react-intl'; +import HeadingButtonComponent from './heading-button'; + +export default { + title: 'Molecules/Buttons', + component: HeadingButtonComponent, + argTypes: { + expanded: { + control: { + type: null, + }, + description: 'Heading button state (plus or minus).', + type: { + name: 'boolean', + required: true, + }, + }, + level: { + control: { + type: 'number', + }, + description: 'Heading level.', + type: { + name: 'number', + required: true, + }, + }, + setExpanded: { + control: { + type: null, + }, + description: 'Callback function to set heading button state.', + type: { + name: 'function', + required: true, + }, + }, + title: { + control: { + type: 'text', + }, + description: 'Heading title.', + type: { + name: 'string', + required: true, + }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = ({ + expanded, + setExpanded: _setExpanded, + ...args +}) => { + const [isExpanded, setIsExpanded] = useState(expanded); + + return ( + + + + ); +}; + +export const HeadingButton = Template.bind({}); +HeadingButton.args = { + level: 2, + title: 'Your title', +}; diff --git a/src/components/molecules/buttons/heading-button.test.tsx b/src/components/molecules/buttons/heading-button.test.tsx new file mode 100644 index 0000000..be3865a --- /dev/null +++ b/src/components/molecules/buttons/heading-button.test.tsx @@ -0,0 +1,32 @@ +import { render, screen } from '@test-utils'; +import HeadingButton from './heading-button'; + +describe('HeadingButton', () => { + it('renders a button to collapse.', () => { + render( + null} + /> + ); + expect( + screen.getByRole('button', { name: 'Collapse The accordion title' }) + ).toBeInTheDocument(); + }); + + it('renders a button to expand.', () => { + render( + null} + /> + ); + expect( + screen.getByRole('button', { name: 'Expand The accordion title' }) + ).toBeInTheDocument(); + }); +}); diff --git a/src/components/molecules/buttons/heading-button.tsx b/src/components/molecules/buttons/heading-button.tsx new file mode 100644 index 0000000..700b3e1 --- /dev/null +++ b/src/components/molecules/buttons/heading-button.tsx @@ -0,0 +1,66 @@ +import Heading, { type HeadingProps } from '@components/atoms/headings/heading'; +import PlusMinus from '@components/atoms/icons/plus-minus'; +import { FC, SetStateAction } from 'react'; +import { useIntl } from 'react-intl'; +import styles from './heading-button.module.scss'; + +export type HeadingButtonProps = Pick & { + /** + * Accordion state. + */ + expanded: boolean; + /** + * Callback function to set accordion state on click. + */ + setExpanded: (value: SetStateAction) => void; + /** + * Accordion title. + */ + title: string; +}; + +/** + * HeadingButton component + * + * Render a button as accordion title to toggle body. + */ +const HeadingButton: FC = ({ + expanded, + level, + setExpanded, + title, +}) => { + const intl = useIntl(); + const iconState = expanded ? 'minus' : 'plus'; + const titlePrefix = expanded + ? intl.formatMessage({ + defaultMessage: 'Collapse', + description: 'HeadingButton: title prefix (expanded state)', + id: 'UX9Bu8', + }) + : intl.formatMessage({ + defaultMessage: 'Expand', + description: 'HeadingButton: title prefix (collapsed state)', + id: 'bcyOgC', + }); + + return ( + + ); +}; + +export default HeadingButton; -- cgit v1.2.3