Skip to content

Commit

Permalink
Refactor(web): Collapse API prop changed
Browse files Browse the repository at this point in the history
- prop `hideOnCollapse`` changed to `isDisposable`

- Solves DS-832
  • Loading branch information
pavelklibani committed Dec 2, 2024
1 parent eefd2c9 commit 914210f
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 34 deletions.
8 changes: 7 additions & 1 deletion packages/web-react/DEPRECATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ This document lists all deprecations that will be removed in the next major vers
## Deprecations

Nothing here right now! 🎉
### UncontrolledCollapse `isDisposable`

The `hideOnCollapse` prop was removed, please use `isDisposable` instead.

#### Migration Guide

- `<UncontrolledCollapse id="collapse" renderTrigger={…} hideOnCollapse … />``<UncontrolledCollapse id="collapse" renderTrigger={…} isDisposable … />`

👉 [What are deprecations?][readme-deprecations]

Expand Down
26 changes: 18 additions & 8 deletions packages/web-react/src/components/Collapse/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,19 +127,28 @@ import { Button, UncontrolledCollapse } from '@lmc-eu/spirit-web-react/component

## API

| Name | Type | Default | Required | Description |
| ------------------------- | -------------------------------------------- | ------- | -------- | ------------------------------------------- |
| `collapsibleToBreakpoint` | [`mobile` \| `tablet` \| `desktop`] ||| Handle for responsive breakpoint |
| `elementType` | [`span` \| `div`] | `div` || Type of element used as wrapper and content |
| `id` | `string` ||| Component id |
| `isOpen` | `bool` ||| Is open on initialization |
| `hideOnCollapse` | `bool` ||| Hides button when content is displayed |
| `renderTrigger` | `(render: CollapseRenderProps) => ReactNode` ||| Properties for trigger render |
| Name | Type | Default | Required | Description |
| ------------------------- | -------------------------------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------- |
| `collapsibleToBreakpoint` | [`mobile` \| `tablet` \| `desktop`] ||| Handle for responsive breakpoint |
| `elementType` | [`span` \| `div`] | `div` || Type of element used as wrapper and content |
| `hideOnCollapse` | `bool` | `false` || [**DEPRECATED**][readme-deprecations] in favor of `isDisposable`; Hides button when content is displayed |
| `id` | `string` ||| Component id |
| `isDisposable` | `bool` | `false` || Hides button when content is displayed |
| `isOpen` | `bool` | `false` || Is open on initialization |
| `renderTrigger` | `(render: CollapseRenderProps) => ReactNode` ||| Properties for trigger render |

On top of the API options, the components accept [additional attributes][readme-additional-attributes].
If you need more control over the styling of a component, you can use [style props][readme-style-props]
and [escape hatches][readme-escape-hatches].

#### ⚠️ DEPRECATION NOTICE

`hideOnCollapse` property will be replaced in the next major version. Please use `isDisposable` instead.

We are providing a [codemod](https://github.com/lmc-eu/spirit-design-system/blob/main/packages/codemods/src/transforms/v3/web-react/README.md#v4web-reactcollapse-isdisposable-prop--uncontrolledcollapse-hideoncollapse-to-isdisposable-prop-change) to assist with this change.

[What are deprecations?][readme-deprecations]

## Render Toggle API

| Name | Type | Default | Required | Description |
Expand All @@ -150,5 +159,6 @@ and [escape hatches][readme-escape-hatches].
| `aria-controls` | `string` ||| Trigger aria controls |

[readme-additional-attributes]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#additional-attributes
[readme-deprecations]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#deprecations
[readme-escape-hatches]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#escape-hatches
[readme-style-props]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#style-props
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,21 @@ const defaultProps = {

const UncontrolledCollapse = (props: SpiritUncontrolledCollapseProps) => {
const propsWithDefaults = { ...defaultProps, ...props };
const { children, hideOnCollapse, renderTrigger, ...restProps } = propsWithDefaults;
const {
children,
/** @deprecated "hideOnCollapse" property will be replaced in the next major version. Please use "isDisposable" instead. */
hideOnCollapse,
isDisposable,
renderTrigger,
...restProps
} = propsWithDefaults;
const { isOpen, toggleHandler } = useCollapse(restProps.isOpen);
const { ariaProps } = useCollapseAriaProps({ ...restProps, isOpen });

const isDisposed = hideOnCollapse || isDisposable;

const triggerRenderHandler = () => {
const showTrigger = hideOnCollapse ? !(hideOnCollapse && isOpen) : true;
const showTrigger = isDisposed ? !(isDisposed && isOpen) : true;

return renderTrigger && showTrigger
? renderTrigger({
Expand All @@ -31,7 +40,7 @@ const UncontrolledCollapse = (props: SpiritUncontrolledCollapseProps) => {
return (
<>
{triggerRenderHandler()}
{hideOnCollapse && isOpen ? (
{isDisposed && isOpen ? (
children
) : (
<Collapse {...restProps} isOpen={isOpen}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import '@testing-library/jest-dom';
import { fireEvent, render, screen } from '@testing-library/react';
import React, { useState } from 'react';
import { classNamePrefixProviderTest } from '../../../../tests/providerTests/classNamePrefixProviderTest';
import { restPropsTest } from '../../../../tests/providerTests/restPropsTest';
import { stylePropsTest } from '../../../../tests/providerTests/stylePropsTest';
import { Button } from '../../Button';
import Collapse from '../Collapse';

describe('UncontrolledCollapse', () => {
classNamePrefixProviderTest(Collapse, 'Collapse');

stylePropsTest(Collapse);

restPropsTest(Collapse, 'div');

it('should render text children', () => {
render(
<Collapse id="collapse" isOpen data-testId="test">
Hello World
</Collapse>,
);

const element = screen.getByTestId('test');
expect(element).toHaveTextContent('Hello World');
expect(element).toHaveClass('is-open');
});

it('should open collapse', () => {
const RenderCollapse = () => {
const [isOpen, setIsOpen] = useState(false);

return (
<>
<Button type="button" data-testId="test-button" onClick={() => setIsOpen(!isOpen)}>
Toggle Collapse
</Button>
<Collapse id="collapse" isOpen={isOpen} data-testId="test">
Hello World
</Collapse>
</>
);
};

render(<RenderCollapse />);

const toggleButton = screen.getByTestId('test-button');
const collapseElement = screen.getByTestId('test');

expect(collapseElement).not.toHaveClass('is-open');

fireEvent.click(toggleButton);

expect(collapseElement).toHaveClass('is-open');
});

it('should have correct html element', () => {
render(
<Collapse id="collapse" elementType="section" isOpen data-testId="test">
Hello World
</Collapse>,
);

const element = screen.getByTestId('test');

expect(element.tagName).toBe('SECTION');
});

// @TODO: Missing tests for props - collapsibleToBreakpoint, transitionDuration
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React from 'react';
import { classNamePrefixProviderTest } from '../../../../tests/providerTests/classNamePrefixProviderTest';
import { restPropsTest } from '../../../../tests/providerTests/restPropsTest';
import { stylePropsTest } from '../../../../tests/providerTests/stylePropsTest';
import { Button } from '../../Button';
import UncontrolledCollapse from '../UncontrolledCollapse';

describe('UncontrolledCollapse', () => {
Expand Down Expand Up @@ -56,3 +57,30 @@ describe('UncontrolledCollapse', () => {
expect(element).toHaveAttribute('id', 'example-id');
});
});

describe('UncontrolledCollapse with disposable trigger', () => {
const content = 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, similique.';

it('should hide trigger after collapse open', () => {
render(
<div>
{content}
<UncontrolledCollapse
id="uncontrolled-collapse-id"
// Normally we want to display state change, not in this test, prop is passed anyway
// eslint-disable-next-line @typescript-eslint/no-unused-vars
renderTrigger={({ isOpen, ...rest }) => <Button {...rest}>… more</Button>}
isDisposable
>
{content}
</UncontrolledCollapse>
</div>,
);

const trigger = screen.getByRole('button') as HTMLElement;

fireEvent.click(trigger);

expect(trigger).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
// Because there is no `dist` directory during the CI run
/* eslint-disable import/no-extraneous-dependencies, import/extensions, import/no-unresolved */
import { StoryFn } from '@storybook/react';
import React from 'react';
import { Button } from '../../Button';
import UncontrolledCollapse from '../UncontrolledCollapse';
import { CollapseTrigger, content } from './Collapse';

const Story: StoryFn<typeof UncontrolledCollapse> = () => {
const UncontrolledCollapseDemo = () => {
const content = (
<p>
Condimentum odio, pulvinar et sollicitudin accumsan ac hendrerit vestibulum commodo, molestie laoreet dui sit
amet. Molestie consectetur, sed ac felis scelerisque lectus accumsan purus id dolor sed vitae, praesent aliquam
dolor quis ornare. Nulla sit amet, rhoncus at quis odio et iaculis lacinia suscipit vivamus sodales, nunc id
condimentum felis. Consectetur nec commodo, praesent et elit magna purus molestie cursus molestie, libero ut
venenatis erat id et nisi. Quam posuere, aliquam quam leo vitae tellus semper eget nunc, ultricies adipiscing sit
amet accumsan. Lorem rutrum, porttitor ante mauris suspendisse ultricies consequat purus, congue a commodo magna
et.
</p>
);

return (
<div>
{content}
<UncontrolledCollapse id="uncontrolled-collapse-id" renderTrigger={CollapseTrigger}>
<UncontrolledCollapse
id="uncontrolled-collapse-id"
renderTrigger={(props) => <Button {...props}>… more</Button>}
isDisposable
>
{content}
</UncontrolledCollapse>
</div>
);
};

export default Story;
export default UncontrolledCollapseDemo;
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';
import { SpiritUncontrolledCollapseProps } from '../../../types';
import { Button } from '../../Button';
import { UncontrolledCollapse } from '..';
import content from './content';

const meta: Meta<typeof UncontrolledCollapse> = {
type UncontrolledCollapsePlaygroundType = {
content: string;
} & SpiritUncontrolledCollapseProps;

const meta: Meta<UncontrolledCollapsePlaygroundType> = {
title: 'Components/Collapse',
component: UncontrolledCollapse,
argTypes: {
collapsibleToBreakpoint: {
control: 'select',
options: ['mobile', 'tablet', 'desktop', undefined],
description: 'Handle for responsive breakpoint',
table: {
defaultValue: { summary: undefined },
},
Expand All @@ -23,41 +28,74 @@ const meta: Meta<typeof UncontrolledCollapse> = {
},
hideOnCollapse: {
control: 'boolean',
description: '**DEPRECATED** in favor of `isDisposable`; Hides button when content is displayed',
table: {
defaultValue: { summary: 'false' },
},
},
isDisposable: {
control: 'boolean',
description: 'Hides button when content is displayed',
table: {
defaultValue: { summary: 'false' },
},
},
id: {
control: 'text',
},
isOpen: {
control: 'boolean',
description: 'Initial state of the collapse',
table: {
defaultValue: { summary: 'false' },
},
},
transitionDuration: {
control: 'number',
description: 'Duration of the transition',
table: {
defaultValue: { summary: '250' },
defaultValue: { summary: '250', detail: ' in milliseconds' },
},
},
content: {
control: 'text',
description: 'Content to be displayed in the collapse',
},
},
args: {
elementType: 'div',
hideOnCollapse: false,
id: 'collapse-example',
isDisposable: false,
isOpen: false,
transitionDuration: 250,
content:
'Condimentum odio, pulvinar et sollicitudin accumsan ac hendrerit vestibulum commodo, molestie laoreet dui sit amet. Molestie consectetur, sed ac felis scelerisque lectus accumsan purus id dolor sed vitae, praesent aliquam dolor quis ornare. Nulla sit amet, rhoncus at quis odio et iaculis lacinia suscipit vivamus sodales, nunc id condimentum felis. Consectetur nec commodo, praesent et elit magna purus molestie cursus molestie, libero ut venenatis erat id et nisi. Quam posuere, aliquam quam leo vitae tellus semper eget nunc, ultricies adipiscing sit amet accumsan. Lorem rutrum, porttitor ante mauris suspendisse ultricies consequat purus, congue a commodo magna et.',
},
};

export default meta;
type Story = StoryObj<typeof UncontrolledCollapse>;
type Story = StoryObj<UncontrolledCollapsePlaygroundType>;

export const UncontrolledCollapsePlayground: Story = {
name: 'UncontrolledCollapse',
render: (args) => (
<UncontrolledCollapse
{...args}
renderTrigger={({ isOpen, ...restProps }) => (
<Button {...restProps}>Collapse Trigger ({isOpen ? 'Open' : 'Closed'})</Button>
)}
>
{content}
</UncontrolledCollapse>
),
render: (args) => {
const { content } = args;

return (
<>
<p>{content}</p>
<UncontrolledCollapse
{...args}
renderTrigger={({ isOpen, ...restProps }) => (
<Button {...restProps} marginBottom="space-700">
Collapse Trigger ({isOpen ? 'Open' : 'Closed'})
</Button>
)}
>
<p>{content}</p>
</UncontrolledCollapse>
</>
);
},
};
4 changes: 3 additions & 1 deletion packages/web-react/src/types/collapse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ export interface TransitionCollapseProps {
export interface SpiritCollapseProps extends CollapseProps, TransitionCollapseProps {}

export interface SpiritUncontrolledCollapseProps extends Omit<SpiritCollapseProps, 'isOpen'> {
isOpen?: boolean;
/** @deprecated "hideOnCollapse" property will be replaced in the next major version. Please use "isDisposable" instead. */
hideOnCollapse?: boolean;
isDisposable?: boolean;
isOpen?: boolean;
renderTrigger?: (render: CollapseRenderProps) => ReactNode;
}

0 comments on commit 914210f

Please sign in to comment.