Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

## [0.17.1](https://github.com/frontapp/front-ui-kit/compare/v0.17.0...v0.17.1) (2025-10-22)


### Bug Fixes

* dropdown border color ([#293](https://github.com/frontapp/front-ui-kit/issues/293)) ([eb7a37c](https://github.com/frontapp/front-ui-kit/commit/eb7a37c8a5cb898eab020828ba9e03fee19c1879))
- dropdown border color ([#293](https://github.com/frontapp/front-ui-kit/issues/293)) ([eb7a37c](https://github.com/frontapp/front-ui-kit/commit/eb7a37c8a5cb898eab020828ba9e03fee19c1879))

## [0.17.0](https://github.com/frontapp/front-ui-kit/compare/v0.16.1...v0.17.0) (2025-10-22)

Expand Down
88 changes: 88 additions & 0 deletions src/components/card/__docs__/docs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {Meta} from '@storybook/blocks';
import {Card} from '../card';

<Meta title="Components/Card" component={Card} />

# Card

A flexible container component that can be used to group related content together. Cards are commonly used to display information in a structured and visually appealing way.

## Basic Usage

```tsx
<Card>
<Card.Header>Card Title</Card.Header>
<Card.Body>This is the main content of the card.</Card.Body>
<Card.Footer>Card footer content</Card.Footer>
</Card>
```

## Features

- **Flexible Layout**: Cards can contain header, body, and footer sections
- **Multiple Sizes**: Support for small, medium, and large sizes
- **Customizable Styling**: Options for shadows, borders, and click interactions
- **Accessible**: Proper semantic structure and keyboard navigation support

## Props

### Card Props

| Prop | Type | Default | Description |
| ------------- | ----------------- | -------- | --------------------------------- |
| `children` | `ReactNode` | - | Content to render inside the card |
| `size` | `VisualSizesEnum` | `MEDIUM` | The size of the card |
| `hasShadow` | `boolean` | `true` | Whether the card has a shadow |
| `hasBorder` | `boolean` | `false` | Whether the card has a border |
| `className` | `string` | - | Class name for custom styling |
| `isClickable` | `boolean` | `false` | Whether the card is clickable |
| `onClick` | `() => void` | - | Called when the card is clicked |

### CardHeader Props

| Prop | Type | Default | Description |
| ----------- | ----------------- | -------- | -------------------------------------- |
| `children` | `ReactNode` | - | Content to render inside the header |
| `size` | `VisualSizesEnum` | `MEDIUM` | The size of the header |
| `className` | `string` | - | Class name for custom styling |
| `hasBorder` | `boolean` | `false` | Whether the header has a bottom border |

### CardBody Props

| Prop | Type | Default | Description |
| ------------ | ----------------- | -------- | --------------------------------- |
| `children` | `ReactNode` | - | Content to render inside the body |
| `size` | `VisualSizesEnum` | `MEDIUM` | The size of the body |
| `className` | `string` | - | Class name for custom styling |
| `hasPadding` | `boolean` | `false` | Whether the body has padding |

### CardFooter Props

| Prop | Type | Default | Description |
| ----------- | ----------------- | -------- | ----------------------------------- |
| `children` | `ReactNode` | - | Content to render inside the footer |
| `size` | `VisualSizesEnum` | `MEDIUM` | The size of the footer |
| `className` | `string` | - | Class name for custom styling |
| `hasBorder` | `boolean` | `false` | Whether the footer has a top border |

## Examples

### Basic Card

A simple card with all three sections.

### Card with Border

A card with a visible border instead of a shadow.

### Clickable Card

A card that responds to click events with hover effects.

### Different Sizes

Cards in small, medium, and large sizes.

### Card with Custom Content

Examples of cards with various content types like images, buttons, and text.
264 changes: 264 additions & 0 deletions src/components/card/__docs__/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
import type {Meta, StoryObj} from '@storybook/react';
import React, {useState} from 'react';

import {greys, palette} from '../../../helpers/colorHelpers';
import {fontSizes, fontWeights, VisualSizesEnum} from '../../../helpers/fontHelpers';
import {DefaultStyleProvider} from '../../../utils/defaultStyleProvider';
import {Button} from '../../button/button';
import {Checkbox} from '../../checkbox/checkbox';
import {Card} from '../card';

const meta: Meta<typeof Card> = {
title: 'Components/Card',
component: Card,
parameters: {
layout: 'padded'
},
argTypes: {
size: {
control: {type: 'select'},
options: ['SMALL', 'MEDIUM', 'LARGE']
}
}
};

export default meta;
type Story = StoryObj<typeof Card>;

export const WithButtons: Story = {
render: (args) => (
<DefaultStyleProvider>
<Card {...args}>
<Card.Header>Card with Buttons</Card.Header>
<Card.Body>This card contains buttons in the footer.</Card.Body>
<Card.Footer>
<div style={{display: 'flex', gap: '8px', justifyContent: 'flex-end'}}>
<Button type="secondary" size={VisualSizesEnum.SMALL}>
Cancel
</Button>
<Button type="primary" size={VisualSizesEnum.SMALL}>
Save
</Button>
</div>
</Card.Footer>
</Card>
</DefaultStyleProvider>
),
decorators: [
(Story) => (
<div style={{width: '400px'}}>
<Story />
</div>
)
]
};

const WithCheckboxAndBodyComponent = () => {
const [isChecked, setIsChecked] = useState(false);

return (
<DefaultStyleProvider>
<Card>
<Card.Header>
<Checkbox isChecked={isChecked} onChange={setIsChecked}>
<span
style={{
color: palette.blue.shade60,
fontSize: fontSizes.large,
fontWeight: fontWeights.medium
}}>
Customer Feedback
</span>
</Checkbox>
</Card.Header>
<Card.Body>
<div
style={{
paddingLeft: '24px',
color: greys.shade60,
fontSize: fontSizes.medium,
fontWeight: fontWeights.normal
}}>
Problem - 30 Sep, 2025
</div>
</Card.Body>
</Card>
</DefaultStyleProvider>
);
};

export const WithCheckboxAndBody: Story = {
render: () => <WithCheckboxAndBodyComponent />,
decorators: [
(Story) => (
<div style={{width: '400px'}}>
<Story />
</div>
)
]
};

const WithCheckboxBodyAndFooterComponent = () => {
const [isChecked, setIsChecked] = useState(false);

return (
<DefaultStyleProvider>
<Card
groupActions
actions={[
{
label: 'Edit',
icon: 'Edit',
tooltip: 'Edit this card',
onClick: () => console.log('Edit clicked!')
},
{
label: 'Log content',
icon: 'AttachmentGeneric',
tooltip: 'Log content',
onClick: () => console.log('Log content clicked!')
}
]}>
<Card.Header>
<Checkbox isChecked={isChecked} onChange={setIsChecked}>
<span
style={{
color: palette.blue.shade60,
fontSize: fontSizes.large,
fontWeight: fontWeights.medium
}}>
Customer Feedback
</span>
</Checkbox>
</Card.Header>
<Card.Body>
<div
style={{
paddingLeft: '24px',
color: greys.shade80,
fontSize: fontSizes.medium,
fontWeight: fontWeights.normal
}}>
Speak with Lance about pricing proposal
</div>
</Card.Body>
<Card.Footer>
<div
style={{
paddingLeft: '24px',
color: greys.shade60,
fontSize: fontSizes.small,
fontWeight: fontWeights.normal
}}>
Call - 30 Sep, 2025
</div>
</Card.Footer>
</Card>
</DefaultStyleProvider>
);
};

export const WithCheckboxBodyAndFooter: Story = {
args: {
size: VisualSizesEnum.MEDIUM
},
render: () => <WithCheckboxBodyAndFooterComponent />,

decorators: [
(Story) => (
<div style={{width: '400px'}}>
<Story />
</div>
)
]
};

export const CardWithActions: Story = {
render: (args) => (
<DefaultStyleProvider>
<div style={{display: 'flex', flexDirection: 'column', gap: '20px'}}>
<Card
{...args}
showActionsOnHover
actions={[
{
label: 'Edit',
icon: 'Edit',
tooltip: 'Edit this card',
onClick: () => console.log('Edit clicked!')
}
]}>
<Card.Header>Card with Single Action</Card.Header>
<Card.Body>Hover over this card to see the edit action in the top right corner.</Card.Body>
<Card.Footer>Card footer content</Card.Footer>
</Card>
<Card
{...args}
showActionsOnHover
actions={[
{
label: 'Edit',
icon: 'Edit',
tooltip: 'Edit this card',
onClick: () => console.log('Edit clicked!')
},
{
label: 'Duplicate',
icon: 'Copy',
tooltip: 'Duplicate this card',
onClick: () => console.log('Duplicate clicked!')
},
{
label: 'Delete',
icon: 'Trash',
tooltip: 'Delete this card',
onClick: () => console.log('Delete clicked!')
}
]}>
<Card.Header>Actions Show on Hover</Card.Header>
<Card.Body>
Hover over this card to see the action menu (three dots) in the top right corner.
</Card.Body>
<Card.Footer>Card footer content</Card.Footer>
</Card>
<Card
{...args}
groupActions
actions={[
{
label: 'Edit',
icon: 'Edit',
tooltip: 'Edit this card',
onClick: () => console.log('Edit clicked!')
},
{
label: 'Copy',
icon: 'Copy',
tooltip: 'Copy this card',
onClick: () => console.log('Copy clicked!')
},
{
label: 'Share',
icon: 'ExternalLink',
tooltip: 'Share this card',
onClick: () => console.log('Share clicked!')
}
]}>
<Card.Header>Grouped Actions (groupActions=true)</Card.Header>
<Card.Body>
When groupActions=true, all actions are grouped into a single dropdown menu. This keeps the
interface clean and compact, especially useful when you have many actions or limited space.
</Card.Body>
<Card.Footer>Card footer content</Card.Footer>
</Card>
</div>
</DefaultStyleProvider>
),
decorators: [
(Story) => (
<div style={{width: '400px'}}>
<Story />
</div>
)
]
};
Loading
Loading