-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
325 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'], | ||
}, | ||
]); |