Skip to content

Commit

Permalink
feat: card 컴포넌트 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
dmswl98 committed Jan 15, 2024
1 parent e394f55 commit b819607
Show file tree
Hide file tree
Showing 3 changed files with 325 additions and 0 deletions.
161 changes: 161 additions & 0 deletions src/components/Card/Card.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import Badge from '@components/Badge';
import Button from '@components/Button';
import Icon from '@components/Icon';
import { CATEGORY_COLOR } from '@constants/config';
import type { Meta, StoryObj } from '@storybook/react';
import { COLORS } from '@styles/tokens';

import Card from '.';

const CARD_CONTENT = `
안녕하세요. 리더님!
어제 줌 회의로 가볍게 인사드렸는데
메신저로는 처음 인사 드립니다 :)
다름이 아니라 업무 진행을 위해 JIRA
권한을 받고자 하는데 시간 괜찮으실 때 권한 추가해주실 수 있을까요?
`;

const COMPONENT_DESCRIPTION = `
- \`<Card />\`: 컴포넌트 내 모든 요소들의 레이아웃을 제공합니다.
- \`<Card.Header />\`: Card 컴포넌트 내 상위 내용을 표시합니다.
- \`<Card.Body />\`: Card 컴포넌트 내 내용을 표시합니다.
- \`<Card.Footer />\`: Card 컴포넌트 내 하위 내용을 표시합니다.
`;

const meta: Meta<typeof Card> = {
title: 'Components/Card',
component: Card,
parameters: {
componentSubtitle: '여러 요소들을 그룹화하는 컴포넌트',
docs: {
description: {
component: COMPONENT_DESCRIPTION,
},
},
},
};

export default meta;

type Story = StoryObj<typeof Card>;

export const Basic: Story = {
args: {
color: 'blue',
},
render: (args) => (
<Card color={args.color}>
<Card.Header>Card.Header</Card.Header>
<Card.Body>Card.Body</Card.Body>
<Card.Footer>Card.Footer</Card.Footer>
</Card>
),
};

export const WriteCard: Story = {
render: () => (
<Card color="grey">
<Card.Header>오후 8:23</Card.Header>
<Card.Body>{CARD_CONTENT}</Card.Body>
</Card>
),
};

export const TemplateCard: Story = {
render: () => {
return (
<Card>
<Card.Menu>
<Button>
<Icon icon="copy" />
</Button>
<Button>
<Icon icon="menu" />
</Button>
</Card.Menu>
<Card.Header>
<Badge color={CATEGORY_COLOR['부탁하기']}>부탁하기</Badge>
<Badge color="grey">퇴사/이직</Badge>
</Card.Header>
<Card.Body>{CARD_CONTENT}</Card.Body>
<Card.Footer>
복사{' '}
<span
style={{
fontWeight: 500,
fontSize: '14px',
color: COLORS['Grey/500'],
}}
>
1.2k
</span>{' '}
• 저장{' '}
<span
style={{
fontWeight: 500,
fontSize: '14px',
color: COLORS['Grey/500'],
}}
>
200
</span>
</Card.Footer>
</Card>
);
},
};

export const ArchivedWriteCard: Story = {
render: () => (
<Card color="grey">
<Card.Header>
<Badge color="black">끄적이는</Badge>
</Card.Header>
<Card.Body>{CARD_CONTENT}</Card.Body>
</Card>
),
};

export const ArchivedTemplateCard: Story = {
render: () => {
return (
<Card color={CATEGORY_COLOR['부탁하기']}>
<Card.Menu>
<Button>
<Icon icon="copy" />
</Button>
<Button>
<Icon icon="menu" />
</Button>
</Card.Menu>
<Card.Header>
<Badge color="black">참고하는</Badge>
<Badge color={CATEGORY_COLOR['부탁하기']}>부탁하기</Badge>
</Card.Header>
<Card.Body>{CARD_CONTENT}</Card.Body>
<Card.Footer>
복사{' '}
<span
style={{
fontWeight: 500,
fontSize: '14px',
color: COLORS['Grey/500'],
}}
>
1.2k
</span>{' '}
• 저장{' '}
<span
style={{
fontWeight: 500,
fontSize: '14px',
color: COLORS['Grey/500'],
}}
>
200
</span>
</Card.Footer>
</Card>
);
},
};
77 changes: 77 additions & 0 deletions src/components/Card/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {
createContext,
type PropsWithChildren,
useContext,
useState,
} from 'react';

import * as styles from './style.css';

interface CardProps {
color?:
| 'blue'
| 'green'
| 'yellow'
| 'red'
| 'orange'
| 'purple'
| 'grey'
| 'white';
}

interface CardContextProps {
isVisibleMenu: boolean;
}

export const CardContext = createContext<CardContextProps>({
isVisibleMenu: false,
});

const Card = ({ children, color = 'white' }: PropsWithChildren<CardProps>) => {
const [isVisible, setIsVisible] = useState(false);

const handleMenuShow = () => {
setIsVisible(true);
};

const handleMenuHide = () => {
setIsVisible(false);
};

return (
<CardContext.Provider value={{ isVisibleMenu: isVisible }}>
<div
className={styles.wrapper({ color })}
onMouseEnter={handleMenuShow}
onMouseLeave={handleMenuHide}
>
{children}
</div>
</CardContext.Provider>
);
};

const CardMenu = ({ children }: PropsWithChildren) => {
const { isVisibleMenu } = useContext(CardContext);

return <>{isVisibleMenu && <div className={styles.menu}>{children}</div>}</>;
};

const CardHeader = ({ children }: PropsWithChildren) => {
return <div className={styles.header}>{children}</div>;
};

const CardBody = ({ children }: PropsWithChildren) => {
return <div className={styles.body}>{children}</div>;
};

const CardFooter = ({ children }: PropsWithChildren) => {
return <div className={styles.footer}>{children}</div>;
};

Card.Menu = CardMenu;
Card.Header = CardHeader;
Card.Body = CardBody;
Card.Footer = CardFooter;

export default Card;
87 changes: 87 additions & 0 deletions src/components/Card/style.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { style } from '@vanilla-extract/css';
import { recipe } from '@vanilla-extract/recipes';

import { sprinkles } from '@styles/sprinkles.css';
import { COLORS } from '@styles/tokens';

export const wrapper = recipe({
base: {
position: 'relative',
display: 'flex',
flexDirection: 'column',
gap: '16px',
padding: '28px',
borderRadius: '16px',
},
variants: {
color: {
blue: {
backgroundColor: `${COLORS['Blue/Default']}20`,
},
green: {
backgroundColor: `${COLORS['Green']}20`,
},
yellow: {
backgroundColor: `${COLORS['Yellow']}20`,
},
red: {
backgroundColor: `${COLORS['LightRed']}20`,
},
orange: {
backgroundColor: `${COLORS['Orange']}20`,
},
purple: {
backgroundColor: `${COLORS['Purple']}20`,
},
grey: {
backgroundColor: `${COLORS['Grey/600']}10`,
},
white: {
backgroundColor: `${COLORS['Grey/White']}20`,
},
},
},
});

export const menu = style({
position: 'absolute',
display: 'flex',
gap: '14px',
top: '16px',
right: '16px',
padding: '8px',
});

export const header = style([
sprinkles({ typography: '15/Title/Medium' }),
{
display: 'flex',
gap: '8px',
color: COLORS['Grey/400'],
wordBreak: 'keep-all',
},
]);

export const body = style([
sprinkles({ typography: '15/Body/Regular' }),
{
wordBreak: 'keep-all',
},
]);

export const footer = style([
sprinkles({ typography: '14/Body/Regular' }),
{
marginBottom: '-8px',
paddingTop: '16px',
borderTop: `1px solid ${COLORS['Dim/6']}`,
color: COLORS['Grey/400'],
},
]);

export const count = style([
sprinkles({ typography: '14/Caption/Medium' }),
{
color: COLORS['Grey/500'],
},
]);

0 comments on commit b819607

Please sign in to comment.