Skip to content

Commit

Permalink
feat: APP-3056 - Implement Collapsible and CardCollapsible core compo…
Browse files Browse the repository at this point in the history
…nents (#165)
  • Loading branch information
thekidnamedkd authored May 8, 2024
1 parent c412e92 commit a704fd2
Show file tree
Hide file tree
Showing 12 changed files with 568 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Added

- Implement `Collapsible`, and `CardCollapsible` core components

## [1.0.26] - 2024-05-06

### Added
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { Meta, StoryObj } from '@storybook/react';
import { CardCollapsible } from './cardCollapsible';

/**
* CardCollapsible component that can wrap any content and visually collapse it for space-saving purposes.
*/
const meta: Meta<typeof CardCollapsible> = {
title: 'Core/Components/Cards/CardCollapsible',
component: CardCollapsible,
tags: ['autodocs'],
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/jfKRr1V9evJUp1uBeyP3Zz/v1.0.0?node-id=10157-27011&t=RVJHJFTrLMnhgYnJ-4',
},
},
};

type Story = StoryObj<typeof CardCollapsible>;

/**
* Default usage example of the CardCollapsible component.
*/
export const Default: Story = {
args: { buttonLabelClosed: 'Read more', buttonLabelOpened: 'Read less' },
render: (args) => (
<CardCollapsible {...args}>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac nulla nec nunc consectetur tincidunt.
Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque. Nulla facilisi. Nullam nec sapien
nec turpis tincidunt scelerisque. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac nulla
nec nunc consectetur tincidunt. Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque.
Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque. Lorem ipsum dolor sit amet,
consectetur adipiscing elit. Sed ac nulla nec nunc consectetur tincidunt. Nulla facilisi. Nullam nec
sapien nec turpis tincidunt scelerisque. Nulla facilisi. Nullam nec sapien nec turpis tincidunt
scelerisque.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac nulla nec nunc consectetur tincidunt.
Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque. Nulla facilisi. Nullam nec sapien
nec turpis tincidunt scelerisque. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac nulla
nec nunc consectetur tincidunt. Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque.
Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac nulla nec nunc consectetur tincidunt.
Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque. Nulla facilisi. Nullam nec sapien
nec turpis tincidunt scelerisque.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac nulla
nec nunc consectetur tincidunt. Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque.
Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque.Lorem ipsum dolor sit amet,
consectetur adipiscing elit. Sed ac nulla nec nunc consectetur tincidunt. Nulla facilisi. Nullam nec
sapien nec turpis tincidunt scelerisque. Nulla facilisi. Nullam nec sapien nec turpis tincidunt
scelerisque.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac nulla nec nunc consectetur
tincidunt. Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque. Nulla facilisi. Nullam
nec sapien nec turpis tincidunt scelerisque.
</p>
</CardCollapsible>
),
};

/**
* CardCollapsible component with an image as the content.
*/
export const WithImage: Story = {
args: {
buttonLabelClosed: 'See more',
buttonLabelOpened: 'See less',
},
render: (args) => (
<CardCollapsible {...args}>
<img src="https://source.unsplash.com/800x600/?landscape" alt="A beautiful landscape" />
</CardCollapsible>
),
};

export default meta;
33 changes: 33 additions & 0 deletions src/core/components/cards/cardCollapsible/cardCollapsible.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { render, screen } from '@testing-library/react';
import { CardCollapsible, type ICardCollapsibleProps } from './cardCollapsible';

global.ResizeObserver = class {
observe() {}
unobserve() {}
disconnect() {}
};

describe('<CardCollapsible /> component', () => {
const createTestComponent = (props?: Partial<ICardCollapsibleProps>) => {
const completeProps = { ...props };

return <CardCollapsible {...completeProps} />;
};

it('renders without crashing', () => {
const children = 'Content of the card';
render(createTestComponent({ children }));
expect(screen.getByText('Content of the card')).toBeInTheDocument();
});

it('forwards props to the Collapsible component', () => {
const defaultOpen = true;
const buttonLabelOpened = 'Close';
const buttonLabelClosed = 'Open';
jest.spyOn(HTMLElement.prototype, 'scrollHeight', 'get').mockReturnValue(500);

render(createTestComponent({ buttonLabelOpened, defaultOpen }));
expect(screen.queryByText(buttonLabelClosed)).not.toBeInTheDocument();
expect(screen.getByText(buttonLabelOpened)).toBeInTheDocument();
});
});
22 changes: 22 additions & 0 deletions src/core/components/cards/cardCollapsible/cardCollapsible.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import classNames from 'classnames';
import { Collapsible, type ICollapsibleProps } from '../../collapsible';
import { Card } from '../card';

export interface ICardCollapsibleProps extends Omit<ICollapsibleProps, 'buttonVariant' | 'className'> {
/**
* Additional class names to apply to the card.
*/
className?: string;
}

export const CardCollapsible: React.FC<ICardCollapsibleProps> = (props) => {
const { children, className, ...otherProps } = props;

return (
<Card className={classNames('p-4 md:p-6', className)}>
<Collapsible showOverlay={true} {...otherProps}>
{children}
</Collapsible>
</Card>
);
};
1 change: 1 addition & 0 deletions src/core/components/cards/cardCollapsible/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { CardCollapsible, type ICardCollapsibleProps } from './cardCollapsible';
1 change: 1 addition & 0 deletions src/core/components/cards/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './card';
export * from './cardCollapsible';
export * from './cardEmptyState';
export * from './cardSummary';
38 changes: 38 additions & 0 deletions src/core/components/collapsible/collapsible.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { type ComponentProps } from 'react';

export type CollapsedSize = 'sm' | 'md' | 'lg';

export interface ICollapsibleProps extends ComponentProps<'div'> {
/**
* The initial height of the collapsible container while closed. @default md
*/
collapsedSize?: CollapsedSize;
/**
* Custom pixel height for the collapsible container that will override collapsedSize prop if defined.
*/
customCollapsedHeight?: number;
/**
* Controlled state of the collapsible container. @default false
*/
isOpen?: boolean;
/**
* Default state of the collapsible container. @default false
*/
defaultOpen?: boolean;
/**
* The label to display on the trigger button when the collapsible container is closed.
*/
buttonLabelClosed?: string;
/**
* The label to display on the trigger button when the collapsible container is open.
*/
buttonLabelOpened?: string;
/**
* Show overlay when the collapsible container is open. @default false
*/
showOverlay?: boolean;
/**
* Callback function that is called when the collapsible container is toggled.
*/
onToggle?: (isOpen: boolean) => void;
}
142 changes: 142 additions & 0 deletions src/core/components/collapsible/collapsible.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import type { Meta, StoryObj } from '@storybook/react';
import { useState } from 'react';
import { Collapsible } from './collapsible';

/**
* Collapsible component that can wrap any content and visually collapse it for space-saving purposes.
*/
const meta: Meta<typeof Collapsible> = {
title: 'Core/Components/Collapsible',
component: Collapsible,
tags: ['autodocs'],
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/jfKRr1V9evJUp1uBeyP3Zz/v1.0.0?node-id=10157-27011&t=RVJHJFTrLMnhgYnJ-4',
},
},
};

type Story = StoryObj<typeof Collapsible>;

/**
* Default usage example of the Collapsible component.
*/
export const Default: Story = {
args: { buttonLabelClosed: 'Read more', buttonLabelOpened: 'Read less' },
render: (args) => (
<Collapsible {...args}>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac nulla nec nunc consectetur tincidunt.
Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque. Nulla facilisi. Nullam nec sapien
nec turpis tincidunt scelerisque. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac nulla
nec nunc consectetur tincidunt. Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque.
Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque. Lorem ipsum dolor sit amet,
consectetur adipiscing elit. Sed ac nulla nec nunc consectetur tincidunt. Nulla facilisi. Nullam nec
sapien nec turpis tincidunt scelerisque. Nulla facilisi. Nullam nec sapien nec turpis tincidunt
scelerisque.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac nulla nec nunc consectetur tincidunt.
Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque. Nulla facilisi. Nullam nec sapien
nec turpis tincidunt scelerisque. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac nulla
nec nunc consectetur tincidunt. Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque.
Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac nulla nec nunc consectetur tincidunt.
Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque. Nulla facilisi. Nullam nec sapien
nec turpis tincidunt scelerisque.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac nulla
nec nunc consectetur tincidunt. Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque.
Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque.Lorem ipsum dolor sit amet,
consectetur adipiscing elit. Sed ac nulla nec nunc consectetur tincidunt. Nulla facilisi. Nullam nec
sapien nec turpis tincidunt scelerisque. Nulla facilisi. Nullam nec sapien nec turpis tincidunt
scelerisque.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac nulla nec nunc consectetur
tincidunt. Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque. Nulla facilisi. Nullam
nec sapien nec turpis tincidunt scelerisque.
</p>
</Collapsible>
),
};

/**
* Collapsible component with a short text as the content to show overflow detection.
*/
export const ShortContent: Story = {
args: { buttonLabelClosed: 'Read more', buttonLabelOpened: 'Read less' },
render: (args) => (
<Collapsible {...args}>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac nulla nec nunc consectetur tincidunt.
Nulla facilisi.
</p>
</Collapsible>
),
};

/**
* Collapsible component with an image as the content with defaultOpen true.
*/
export const WithImage: Story = {
args: {
buttonLabelClosed: 'See more',
buttonLabelOpened: 'See less',
defaultOpen: true,
},
render: (args) => (
<Collapsible {...args}>
<img
src="https://source.unsplash.com/800x600/?landscape"
alt="A beautiful landscape"
className="h-auto w-full"
/>
</Collapsible>
),
};

/**
* Controlled usage example of the Collapsible component.
*/
export const Controlled: Story = {
args: {
buttonLabelOpened: 'Collapse content',
buttonLabelClosed: 'Expand content',
collapsedSize: 'sm',
},
render: (args) => {
const [isOpen, setIsOpen] = useState(false);

const handleToggle = (toggle: boolean) => {
setIsOpen(toggle);
};

return (
<Collapsible {...args} isOpen={isOpen} onToggle={handleToggle}>
<p>
This is some example content within the Collapsible component. When expanded, the content will be
fully visible.
</p>
<br />
<p>
Controlled usage ensures that the parent controls the open and closed states and updates the
component accordingly.
</p>
<br />
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac nulla nec nunc consectetur
tincidunt. Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque. Nulla facilisi.
Nullam nec sapien nec turpis tincidunt scelerisque.Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Sed ac nulla nec nunc consectetur tincidunt. Nulla facilisi. Nullam nec sapien nec
turpis tincidunt scelerisque. Nulla facilisi. Nullam nec sapien nec turpis tincidunt
scelerisque.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac nulla nec nunc
consectetur tincidunt. Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque. Nulla
facilisi. Nullam nec sapien nec turpis tincidunt scelerisque.Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Sed ac nulla nec nunc consectetur tincidunt. Nulla facilisi. Nullam nec sapien nec
turpis tincidunt scelerisque. Nulla facilisi. Nullam nec sapien nec turpis tincidunt scelerisque.
</p>
</Collapsible>
);
},
};

export default meta;
Loading

0 comments on commit a704fd2

Please sign in to comment.