From b8196070fc29a1d95ba36801c2ccb0c819059fc9 Mon Sep 17 00:00:00 2001 From: cje Date: Mon, 15 Jan 2024 23:47:45 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20card=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Card/Card.stories.tsx | 161 +++++++++++++++++++++++++++ src/components/Card/index.tsx | 77 +++++++++++++ src/components/Card/style.css.ts | 87 +++++++++++++++ 3 files changed, 325 insertions(+) create mode 100644 src/components/Card/Card.stories.tsx create mode 100644 src/components/Card/index.tsx create mode 100644 src/components/Card/style.css.ts diff --git a/src/components/Card/Card.stories.tsx b/src/components/Card/Card.stories.tsx new file mode 100644 index 00000000..bdc33387 --- /dev/null +++ b/src/components/Card/Card.stories.tsx @@ -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 컴포넌트 내 내용을 표시합니다. + - \`\`: Card 컴포넌트 내 하위 내용을 표시합니다. +`; + +const meta: Meta = { + title: 'Components/Card', + component: Card, + parameters: { + componentSubtitle: '여러 요소들을 그룹화하는 컴포넌트', + docs: { + description: { + component: COMPONENT_DESCRIPTION, + }, + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + color: 'blue', + }, + render: (args) => ( + + Card.Header + Card.Body + Card.Footer + + ), +}; + +export const WriteCard: Story = { + render: () => ( + + 오후 8:23 + {CARD_CONTENT} + + ), +}; + +export const TemplateCard: Story = { + render: () => { + return ( + + + + + + + 부탁하기 + 퇴사/이직 + + {CARD_CONTENT} + + 복사{' '} + + 1.2k + {' '} + • 저장{' '} + + 200 + + + + ); + }, +}; + +export const ArchivedWriteCard: Story = { + render: () => ( + + + 끄적이는 + + {CARD_CONTENT} + + ), +}; + +export const ArchivedTemplateCard: Story = { + render: () => { + return ( + + + + + + + 참고하는 + 부탁하기 + + {CARD_CONTENT} + + 복사{' '} + + 1.2k + {' '} + • 저장{' '} + + 200 + + + + ); + }, +}; diff --git a/src/components/Card/index.tsx b/src/components/Card/index.tsx new file mode 100644 index 00000000..797fd5cb --- /dev/null +++ b/src/components/Card/index.tsx @@ -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({ + isVisibleMenu: false, +}); + +const Card = ({ children, color = 'white' }: PropsWithChildren) => { + const [isVisible, setIsVisible] = useState(false); + + const handleMenuShow = () => { + setIsVisible(true); + }; + + const handleMenuHide = () => { + setIsVisible(false); + }; + + return ( + +
+ {children} +
+
+ ); +}; + +const CardMenu = ({ children }: PropsWithChildren) => { + const { isVisibleMenu } = useContext(CardContext); + + return <>{isVisibleMenu &&
{children}
}; +}; + +const CardHeader = ({ children }: PropsWithChildren) => { + return
{children}
; +}; + +const CardBody = ({ children }: PropsWithChildren) => { + return
{children}
; +}; + +const CardFooter = ({ children }: PropsWithChildren) => { + return
{children}
; +}; + +Card.Menu = CardMenu; +Card.Header = CardHeader; +Card.Body = CardBody; +Card.Footer = CardFooter; + +export default Card; diff --git a/src/components/Card/style.css.ts b/src/components/Card/style.css.ts new file mode 100644 index 00000000..2285cec9 --- /dev/null +++ b/src/components/Card/style.css.ts @@ -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'], + }, +]);