Skip to content

Commit 35b1d2d

Browse files
committed
feat: add card component
1 parent 410850c commit 35b1d2d

File tree

13 files changed

+487
-0
lines changed

13 files changed

+487
-0
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { tv } from 'tailwind-variants';
2+
3+
export const cardVariants = tv({
4+
defaultVariants: {
5+
flexHeight: false,
6+
size: 'md'
7+
},
8+
slots: {
9+
content: 'flex-grow',
10+
footer: 'flex items-center justify-between',
11+
header: 'flex items-center justify-between',
12+
root: 'flex flex-col items-stretch rounded-md border bg-card text-card-foreground shadow-sm',
13+
title: 'font-semibold tracking-tight',
14+
titleRoot: 'flex items-center'
15+
},
16+
variants: {
17+
flexHeight: {
18+
true: {
19+
content: 'overflow-hidden'
20+
}
21+
},
22+
size: {
23+
'2xl': {
24+
content: 'px-7 py-6',
25+
footer: 'px-7 py-6',
26+
header: 'px-7 py-6',
27+
root: 'text-lg',
28+
title: 'text-xl',
29+
titleRoot: 'gap-3.5'
30+
},
31+
lg: {
32+
content: 'px-5 py-4',
33+
footer: 'px-5 py-5',
34+
header: 'px-5 py-4',
35+
root: 'text-base',
36+
title: 'text-lg',
37+
titleRoot: 'gap-2.5'
38+
},
39+
md: {
40+
content: 'px-4 py-3',
41+
footer: 'px-4 py-3',
42+
header: 'px-4 py-3',
43+
root: 'text-sm',
44+
title: 'text-base',
45+
titleRoot: 'gap-2'
46+
},
47+
sm: {
48+
content: 'px-3 py-2',
49+
footer: 'px-3 py-2',
50+
header: 'px-3 py-2',
51+
root: 'text-xs',
52+
title: 'text-sm',
53+
titleRoot: 'gap-1.75'
54+
},
55+
xl: {
56+
content: 'px-6 py-5',
57+
footer: 'px-6 py-6',
58+
header: 'px-6 py-5',
59+
root: 'text-base',
60+
title: 'text-lg',
61+
titleRoot: 'gap-3'
62+
},
63+
xs: {
64+
content: 'px-2 py-1.5',
65+
footer: 'px-2 py-1.5',
66+
header: 'px-2 py-1.5',
67+
root: 'text-2xs',
68+
title: 'text-xs',
69+
titleRoot: 'gap-1.5'
70+
}
71+
},
72+
split: {
73+
true: {
74+
root: 'divide-y divide-border'
75+
}
76+
}
77+
}
78+
});
79+
80+
export type CardSlots = keyof typeof cardVariants.slots;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export * from './source/Card';
2+
export * from './source/CardContent';
3+
export * from './source/CardFooter';
4+
export * from './source/CardHeader';
5+
export * from './source/CardRoot';
6+
export * from './source/CardTitle';
7+
export * from './source/CardTitleRoot';
8+
9+
export * from './type';
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { cardVariants, cn } from '@skyroc-ui/variants';
2+
import React, { isValidElement } from 'react';
3+
4+
import { If } from '../../if';
5+
import type { CardProps } from '../type';
6+
7+
import { CardContent } from './CardContent';
8+
import { CardFooter } from './CardFooter';
9+
import { CardHeader } from './CardHeader';
10+
import { CardRoot } from './CardRoot';
11+
import { CardTitle } from './CardTitle';
12+
import { CardTitleRoot } from './CardTitleRoot';
13+
14+
export const Card = React.forwardRef<HTMLDivElement, CardProps>((props, ref) => {
15+
const {
16+
children,
17+
className,
18+
classNames,
19+
extra,
20+
flexHeight,
21+
footer,
22+
header,
23+
size,
24+
split,
25+
title,
26+
titleLeading,
27+
titleRoot,
28+
titleTrailing,
29+
...rest
30+
} = props;
31+
32+
const showHeader = Boolean(header || title || extra);
33+
34+
const { root } = cardVariants({ size, split });
35+
36+
const mergedCls = cn(root(), className);
37+
38+
return (
39+
<CardRoot
40+
className={mergedCls}
41+
{...rest}
42+
ref={ref}
43+
>
44+
<If condition={showHeader}>
45+
<CardHeader
46+
className={classNames?.header}
47+
size={size}
48+
>
49+
<If
50+
condition={!header}
51+
fallback={header}
52+
>
53+
<If
54+
condition={!titleRoot}
55+
fallback={titleRoot}
56+
>
57+
<CardTitleRoot
58+
className={classNames?.titleRoot}
59+
size={size}
60+
>
61+
{titleLeading}
62+
<If
63+
condition={!isValidElement(title)}
64+
fallback={title}
65+
>
66+
<CardTitle
67+
className={classNames?.title}
68+
size={size}
69+
>
70+
{title}
71+
</CardTitle>
72+
</If>
73+
{titleTrailing}
74+
</CardTitleRoot>
75+
</If>
76+
77+
{extra}
78+
</If>
79+
</CardHeader>
80+
</If>
81+
<CardContent
82+
className={classNames?.content}
83+
flexHeight={flexHeight}
84+
size={size}
85+
>
86+
{children}
87+
</CardContent>
88+
89+
<If
90+
condition={!isValidElement(footer) && Boolean(footer)}
91+
fallback={footer}
92+
>
93+
<CardFooter
94+
className={classNames?.footer}
95+
size={size}
96+
>
97+
{footer}
98+
</CardFooter>
99+
</If>
100+
</CardRoot>
101+
);
102+
});
103+
104+
Card.displayName = 'Card';
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { cardVariants, cn } from '@skyroc-ui/variants';
2+
import React from 'react';
3+
4+
import type { CardContentProps } from '../type';
5+
6+
export const CardContent = React.forwardRef<HTMLDivElement, CardContentProps>((props, ref) => {
7+
const { className, flexHeight, size, ...rest } = props;
8+
9+
const { content } = cardVariants({ flexHeight, size });
10+
11+
const mergedCls = cn(content(), className);
12+
13+
return (
14+
<div
15+
className={mergedCls}
16+
{...rest}
17+
ref={ref}
18+
/>
19+
);
20+
});
21+
22+
CardContent.displayName = 'CardContent';
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { cardVariants, cn } from '@skyroc-ui/variants';
2+
import React from 'react';
3+
4+
import type { CardFooterProps } from '../type';
5+
6+
export const CardFooter = React.forwardRef<HTMLDivElement, CardFooterProps>((props, ref) => {
7+
const { className, size, ...rest } = props;
8+
9+
const { footer } = cardVariants({ size });
10+
11+
const mergedCls = cn(footer(), className);
12+
13+
return (
14+
<div
15+
className={mergedCls}
16+
{...rest}
17+
ref={ref}
18+
/>
19+
);
20+
});
21+
22+
CardFooter.displayName = 'CardFooter';
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { cardVariants, cn } from '@skyroc-ui/variants';
2+
import React, { useMemo } from 'react';
3+
4+
import type { CardHeaderProps } from '../type';
5+
6+
export const CardHeader = React.forwardRef<HTMLDivElement, CardHeaderProps>((props, ref) => {
7+
const { className, size, ...rest } = props;
8+
9+
const { header } = cardVariants({ size });
10+
11+
const mergedCls = cn(header(), className);
12+
13+
return (
14+
<div
15+
className={mergedCls}
16+
{...rest}
17+
ref={ref}
18+
/>
19+
);
20+
});
21+
22+
CardHeader.displayName = 'CardHeader';
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { cardVariants, cn } from '@skyroc-ui/variants';
2+
import React, { useMemo } from 'react';
3+
4+
import type { CardRootProps } from '../type';
5+
6+
export const CardRoot = React.forwardRef<HTMLDivElement, CardRootProps>((props, ref) => {
7+
const { className, size, split, ...rest } = props;
8+
9+
const { root } = cardVariants({ size, split });
10+
11+
const mergedCls = cn(root(), className);
12+
13+
return (
14+
<div
15+
className={mergedCls}
16+
{...rest}
17+
ref={ref}
18+
/>
19+
);
20+
});
21+
22+
CardRoot.displayName = 'CardRoot';
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { cardVariants, cn } from '@skyroc-ui/variants';
2+
import React from 'react';
3+
4+
import type { CardTitleProps } from '../type';
5+
6+
export const CardTitle = React.forwardRef<HTMLDivElement, CardTitleProps>((props, ref) => {
7+
const { className, size, ...rest } = props;
8+
9+
const { title } = cardVariants({ size });
10+
11+
const mergedCls = cn(title(), className);
12+
13+
return (
14+
<div
15+
className={mergedCls}
16+
{...rest}
17+
ref={ref}
18+
/>
19+
);
20+
});
21+
22+
CardTitle.displayName = 'CardTitle';
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { cardVariants, cn } from '@skyroc-ui/variants';
2+
import React from 'react';
3+
4+
import type { CardTitleRootProps } from '../type';
5+
6+
export const CardTitleRoot = React.forwardRef<HTMLDivElement, CardTitleRootProps>((props, ref) => {
7+
const { children, className, leading, size, trailing, ...rest } = props;
8+
9+
const { titleRoot } = cardVariants({ size });
10+
11+
const mergedCls = cn(titleRoot(), className);
12+
13+
return (
14+
<div
15+
className={mergedCls}
16+
{...rest}
17+
ref={ref}
18+
>
19+
{leading}
20+
{children}
21+
{trailing}
22+
</div>
23+
);
24+
});
25+
26+
CardTitleRoot.displayName = 'CardTitleRoot';

0 commit comments

Comments
 (0)