diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-10-07 18:44:14 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-11 18:14:41 +0100 |
| commit | d75b9a1e150ab211c1052fb49bede9bd16320aca (patch) | |
| tree | e5bb221d2b8dc83151697fe646e9194f921b5807 /src/components/molecules/forms/flipping-label | |
| parent | 12a03a9a72f7895d571dbaeeb245d92aa277a610 (diff) | |
feat(components): add a generic Flip component
The flipping animation is used at several places so it makes sense to
use a single component to handle the animation. It will avoid styles
duplication.
Diffstat (limited to 'src/components/molecules/forms/flipping-label')
4 files changed, 54 insertions, 76 deletions
diff --git a/src/components/molecules/forms/flipping-label/flipping-label.module.scss b/src/components/molecules/forms/flipping-label/flipping-label.module.scss index 4e7947f..169bde3 100644 --- a/src/components/molecules/forms/flipping-label/flipping-label.module.scss +++ b/src/components/molecules/forms/flipping-label/flipping-label.module.scss @@ -1,61 +1,17 @@ @use "../../../../styles/abstracts/functions" as fun; -.label { - display: block; - width: var(--btn-size, #{fun.convert-px(60)}); - height: var(--btn-size, #{fun.convert-px(60)}); +.wrapper { + --size: var(--btn-size, #{fun.convert-px(60)}); + --flipper-speed: 0.5s; + + width: var(--size); + height: var(--size); } +.wrapper, .front, .back { display: flex; place-content: center; - width: 100%; - height: 100%; - position: absolute; - top: 0; - right: 0; - backface-visibility: hidden; - transition: all 0.6s ease-in 0s; -} - -.front { - z-index: 20; -} - -.back { - z-index: 10; -} - -.wrapper { - display: flex; - place-content: center; place-items: center; - width: 100%; - height: 100%; - position: relative; - transition: all 0.5s ease-in-out 0s; - transform-style: preserve-3d; - - &--active { - transform: rotateY(180deg); - - .front { - transform: scale(0.2); - } - - .back { - transform: scale(1) rotateY(180deg); - } - } - - &--inactive { - .front { - transform: scale(1); - } - - .back { - transform: scale(0.2) rotateY(180deg); - } - } } diff --git a/src/components/molecules/forms/flipping-label/flipping-label.stories.tsx b/src/components/molecules/forms/flipping-label/flipping-label.stories.tsx index bf5724e..c3c4f9a 100644 --- a/src/components/molecules/forms/flipping-label/flipping-label.stories.tsx +++ b/src/components/molecules/forms/flipping-label/flipping-label.stories.tsx @@ -1,6 +1,6 @@ import type { ComponentMeta, ComponentStory } from '@storybook/react'; import { useCallback, useState } from 'react'; -import { Icon } from '../../../atoms'; +import { Button, Icon } from '../../../atoms'; import { FlippingLabel } from './flipping-label'; export default { @@ -78,20 +78,22 @@ const Template: ComponentStory<typeof FlippingLabel> = ({ const updateState = useCallback(() => setActive((prev) => !prev), []); return ( - <button onClick={updateState} type="button"> + <Button kind="neutral" onClick={updateState} shape="initial" type="button"> <FlippingLabel {...args} isActive={active} /> - </button> + </Button> ); }; export const Active = Template.bind({}); Active.args = { - children: <Icon shape="magnifying-glass" />, + icon: <Icon shape="magnifying-glass" />, isActive: true, + label: 'Close the search', }; export const Inactive = Template.bind({}); Inactive.args = { - children: <Icon shape="magnifying-glass" />, + icon: <Icon shape="magnifying-glass" />, isActive: false, + label: 'Open the search', }; diff --git a/src/components/molecules/forms/flipping-label/flipping-label.test.tsx b/src/components/molecules/forms/flipping-label/flipping-label.test.tsx index 71ea2ba..d59c5f3 100644 --- a/src/components/molecules/forms/flipping-label/flipping-label.test.tsx +++ b/src/components/molecules/forms/flipping-label/flipping-label.test.tsx @@ -1,15 +1,18 @@ import { describe, expect, it } from '@jest/globals'; -import { render, screen } from '../../../../../tests/utils'; +import { render, screen as rtlScreen } from '@testing-library/react'; +import { Icon } from '../../../atoms'; import { FlippingLabel } from './flipping-label'; describe('FlippingLabel', () => { it('renders a label', () => { - const ariaLabel = 'vero quo inventore'; + const label = 'vero quo inventore'; render( - <FlippingLabel aria-label={ariaLabel} isActive={false}> - <>Test</> - </FlippingLabel> + <FlippingLabel + icon={<Icon shape="arrow" />} + isActive={false} + label={label} + /> ); - expect(screen.getByLabelText(ariaLabel)).toBeInTheDocument(); + expect(rtlScreen.getByText(label)).toBeInTheDocument(); }); }); diff --git a/src/components/molecules/forms/flipping-label/flipping-label.tsx b/src/components/molecules/forms/flipping-label/flipping-label.tsx index e9d6a10..586301f 100644 --- a/src/components/molecules/forms/flipping-label/flipping-label.tsx +++ b/src/components/molecules/forms/flipping-label/flipping-label.tsx @@ -1,37 +1,54 @@ -import type { FC } from 'react'; -import { Icon, Label, type LabelProps } from '../../../atoms'; +import type { FC, ReactNode } from 'react'; +import { + Icon, + Label, + VisuallyHidden, + type LabelProps, + Flip, + FlipSide, +} from '../../../atoms'; import styles from './flipping-label.module.scss'; -export type FlippingLabelProps = Pick< +export type FlippingLabelProps = Omit< LabelProps, - 'aria-label' | 'className' | 'htmlFor' + 'children' | 'isHidden' | 'isRequired' > & { /** * The front icon. */ - children: JSX.Element; + icon: ReactNode; /** * Which side of the label should be displayed? True for the close icon. */ isActive: boolean; + /** + * An accessible name for the label. + */ + label: string; }; export const FlippingLabel: FC<FlippingLabelProps> = ({ - children, className = '', + icon, isActive, + label, ...props }) => { - const wrapperModifier = isActive ? 'wrapper--active' : 'wrapper--inactive'; + const wrapperClass = `${styles.wrapper} ${className}`; return ( - <Label {...props} className={`${styles.label} ${className}`}> - <span className={`${styles.wrapper} ${styles[wrapperModifier]}`}> - <span className={styles.front}>{children}</span> - <span className={styles.back}> - <Icon aria-hidden={true} shape="cross" /> - </span> - </span> + <Label {...props} className={wrapperClass}> + <VisuallyHidden>{label}</VisuallyHidden> + <Flip + aria-hidden + // eslint-disable-next-line react/jsx-no-literals -- Shape allowed + showBack={isActive} + > + <FlipSide className={styles.front}>{icon}</FlipSide> + <FlipSide className={styles.back} isBack> + <Icon aria-hidden shape="cross" /> + </FlipSide> + </Flip> </Label> ); }; |
