Skip to content

Commit 0342a0f

Browse files
authored
feat(ItemCard): add component (#1063)
1 parent f07f11a commit 0342a0f

8 files changed

Lines changed: 387 additions & 1 deletion

File tree

.changeset/add-item-card.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cube-dev/ui-kit': minor
3+
---
4+
5+
Add `ItemCard` component — a convenience wrapper around `Item type="card"` that maps `title` to the card heading and `children` to the card body. Includes `ItemCard.Action` sub-component for inline actions.

.storybook/preview.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ configure({ testIdAttribute: 'data-qa', asyncUtilTimeout: 10000 });
88

99
// Load tasty debug utilities in local Storybook only (exclude Chromatic)
1010
if (!isChromatic() && import.meta.env.DEV) {
11-
import('../src/tasty/debug').then(({ tastyDebug }) => {
11+
import('@tenphi/tasty').then(({ tastyDebug }) => {
1212
try {
1313
tastyDebug.install();
1414
} catch (e) {
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { Meta, Story, Controls } from '@storybook/addon-docs/blocks';
2+
import * as ItemCardStories from './ItemCard.stories';
3+
4+
<Meta of={ItemCardStories} />
5+
6+
# ItemCard
7+
8+
A convenience wrapper around `Item type="card"` that provides a more natural card API by mapping `title` to the card heading and `children` to the card body.
9+
10+
## When to Use
11+
12+
- When displaying notification-style messages (success, error, warning, info)
13+
- For status banners or alert cards
14+
- When you need a card with a heading and body content
15+
16+
## Component
17+
18+
<Story of={ItemCardStories.Default} />
19+
20+
---
21+
22+
### Properties
23+
24+
<Controls of={ItemCardStories.Default} />
25+
26+
### Base Properties
27+
28+
Supports [Base properties](https://github.com/tenphi/tasty/blob/main/docs/tasty.md)
29+
30+
### Styling Properties
31+
32+
#### styles
33+
34+
Inherits all styling from `Item`. See [Item documentation](/docs/content-item--docs) for sub-elements and modifiers.
35+
36+
## Variants
37+
38+
### Sizes
39+
40+
<Story of={ItemCardStories.Sizes} />
41+
42+
- `medium` — Default size suitable for most use cases
43+
- `large` — Larger size for prominent notifications
44+
- `xlarge` — Extra large size for emphasized alerts
45+
46+
### Themes
47+
48+
Use the `theme` prop to apply a semantic color scheme:
49+
50+
- `default` — Standard card appearance
51+
- `success` — Green theme for positive outcomes
52+
- `danger` — Red theme for errors or destructive states
53+
- `warning` — Amber theme for caution
54+
- `note` — Violet theme for informational context
55+
56+
## Examples
57+
58+
### Basic Usage
59+
60+
```jsx
61+
<ItemCard title="Info" icon={<IconInfoCircle />}>
62+
This is the card body content.
63+
</ItemCard>
64+
```
65+
66+
### Themed Cards
67+
68+
```jsx
69+
<ItemCard title="Success" icon={<IconCheck />} theme="success">
70+
Operation completed successfully.
71+
</ItemCard>
72+
73+
<ItemCard title="Error" icon={<IconAlertTriangle />} theme="danger">
74+
Something went wrong.
75+
</ItemCard>
76+
```
77+
78+
### With Actions
79+
80+
Use `ItemCard.Action` to add inline action buttons such as dismiss or close.
81+
82+
```jsx
83+
<ItemCard
84+
title="Deployment Complete"
85+
icon={<IconCheck />}
86+
theme="success"
87+
actions={<ItemCard.Action icon={<IconX />} aria-label="Dismiss" />}
88+
>
89+
Your changes are now live in production.
90+
</ItemCard>
91+
```
92+
93+
### Custom Heading Level
94+
95+
The `level` prop controls the semantic HTML heading tag (h1-h6) for the title. Default is `level={3}` (h3).
96+
97+
```jsx
98+
<ItemCard title="Important Section" icon={<IconInfoCircle />} level={2}>
99+
This card's title renders as an h2 element.
100+
</ItemCard>
101+
```
102+
103+
## Accessibility
104+
105+
### Keyboard Navigation
106+
107+
- `Tab` - Moves focus to the component when focusable
108+
- `Space/Enter` - Activates the component when used as a button
109+
110+
### Screen Reader Support
111+
112+
- Title renders as a semantic heading (h1-h6) via the `level` prop for proper document structure
113+
- Inherits all accessibility features from `Item`
114+
115+
### ARIA Properties
116+
117+
- `aria-label` - Provides accessible label when no visible label exists
118+
119+
## Best Practices
120+
121+
1. **Do**: Use meaningful titles and body content
122+
```jsx
123+
<ItemCard title="Deployment Complete" icon={<IconCheck />} theme="success">
124+
Your changes are now live in production.
125+
</ItemCard>
126+
```
127+
128+
2. **Don't**: Use without a title
129+
```jsx
130+
{/* Prefer using Item type="card" directly if you only need a description */}
131+
<ItemCard>Body only without a title</ItemCard>
132+
```
133+
134+
## Related Components
135+
136+
- [Item](/docs/content-item--docs) - The underlying component with full control over `type`, `children`, and `description`
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import {
2+
IconAlertTriangle,
3+
IconCheck,
4+
IconInfoCircle,
5+
IconNote,
6+
IconX,
7+
} from '@tabler/icons-react';
8+
9+
import { baseProps } from '../../../stories/lists/baseProps';
10+
import { Space } from '../../layout/Space';
11+
12+
import { CubeItemCardProps, ItemCard } from './ItemCard';
13+
14+
import type { Meta, StoryObj } from '@storybook/react-vite';
15+
16+
const meta = {
17+
title: 'Content/ItemCard',
18+
component: ItemCard,
19+
parameters: {
20+
controls: {
21+
exclude: baseProps,
22+
},
23+
},
24+
argTypes: {
25+
/* Content */
26+
title: {
27+
control: { type: 'text' },
28+
description: 'Card heading',
29+
},
30+
children: {
31+
control: { type: 'text' },
32+
description: 'Card body content',
33+
},
34+
icon: {
35+
control: { type: null },
36+
description: 'Icon rendered before the content',
37+
},
38+
39+
/* Presentation */
40+
theme: {
41+
options: ['default', 'success', 'danger', 'warning', 'note'],
42+
control: { type: 'radio' },
43+
description: 'Card theme',
44+
table: { defaultValue: { summary: 'default' } },
45+
},
46+
level: {
47+
options: [1, 2, 3, 4, 5, 6],
48+
control: { type: 'select' },
49+
description: 'Heading level for the title (h1-h6)',
50+
table: { defaultValue: { summary: 3 } },
51+
},
52+
},
53+
} satisfies Meta<typeof ItemCard>;
54+
55+
export default meta;
56+
type Story = StoryObj<typeof meta>;
57+
58+
export const Default: Story = {
59+
args: {
60+
title: 'Card Title',
61+
children: 'This is the card body content with additional details.',
62+
icon: <IconInfoCircle />,
63+
},
64+
};
65+
66+
export const Sizes = (args: CubeItemCardProps) => (
67+
<Space gap="1x" flow="column" width="max 400px">
68+
<ItemCard
69+
{...args}
70+
size="medium"
71+
title="Medium Card"
72+
icon={<IconInfoCircle />}
73+
actions={<ItemCard.Action icon={<IconX />} aria-label="Dismiss" />}
74+
>
75+
Default size suitable for most use cases.
76+
</ItemCard>
77+
<ItemCard
78+
{...args}
79+
size="large"
80+
title="Large Card"
81+
icon={<IconCheck />}
82+
actions={<ItemCard.Action icon={<IconX />} aria-label="Dismiss" />}
83+
>
84+
Larger size for prominent notifications.
85+
</ItemCard>
86+
<ItemCard
87+
{...args}
88+
size="xlarge"
89+
title="Extra Large Card"
90+
icon={<IconAlertTriangle />}
91+
actions={<ItemCard.Action icon={<IconX />} aria-label="Dismiss" />}
92+
>
93+
Extra large size for emphasized alerts.
94+
</ItemCard>
95+
</Space>
96+
);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { ForwardedRef, forwardRef, ReactNode } from 'react';
2+
3+
import { ItemAction } from '../../actions/ItemAction';
4+
import { CubeItemProps, Item } from '../Item/Item';
5+
6+
export interface CubeItemCardProps
7+
extends Omit<CubeItemProps, 'type' | 'children' | 'description'> {
8+
/** Card heading, mapped to Item's `children`. */
9+
title?: ReactNode;
10+
/** Card body content, mapped to Item's `description`. */
11+
children?: ReactNode;
12+
}
13+
14+
const _ItemCard = forwardRef(function ItemCard(
15+
{ title, children, ...props }: CubeItemCardProps,
16+
ref: ForwardedRef<HTMLElement>,
17+
) {
18+
return (
19+
<Item ref={ref} {...props} type="card" description={children}>
20+
{title}
21+
</Item>
22+
);
23+
});
24+
25+
const ItemCard = Object.assign(_ItemCard, {
26+
Action: ItemAction,
27+
});
28+
29+
export { ItemCard };
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './ItemCard';

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export type { CubeItemProps } from './components/content/Item/Item';
2020
export { ItemBase } from './components/content/Item/Item';
2121
// @deprecated Use `CubeItemProps` instead
2222
export type { CubeItemBaseProps } from './components/content/Item/Item';
23+
export { ItemCard } from './components/content/ItemCard/ItemCard';
24+
export type { CubeItemCardProps } from './components/content/ItemCard/ItemCard';
2325
export { ItemBadge } from './components/content/ItemBadge/ItemBadge';
2426
export type { CubeItemBadgeProps } from './components/content/ItemBadge/ItemBadge';
2527
export { ActiveZone } from './components/content/ActiveZone/ActiveZone';

0 commit comments

Comments
 (0)