Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
## [0.19.1] - 2026-07-14

**Branch**: `ui-v0.19.1-레이아웃및토큰체계정비`
### 추가
- feat: 기능색상램프토큰추가
- feat: spacing토큰및레이아웃컴포넌트추가
- feat: Card컴포넌트variant추가

### 변경
- refactor: Tabs디자인개편및구분선토큰계층정비
- refactor: Overview쇼케이스및파운데이션타이포토큰정비
- chore: v0.19.1버전범프

---
## [0.18.1] - 2026-07-13

**Branch**: `ui-v0.18.1-달력및숫자입력컴포넌트추가`
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@youngduck/yd-ui",
"version": "0.18.1",
"version": "0.19.1",
"type": "module",
"main": "./dist/index.cjs.js",
"module": "./dist/index.esm.js",
Expand Down
370 changes: 203 additions & 167 deletions src/Overview.stories.tsx

Large diffs are not rendered by default.

177 changes: 74 additions & 103 deletions src/components/Card/Card.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { Meta, StoryObj } from '@storybook/react'
import { Card } from './Card'
import { copyCodeToClipboard } from '../../storybook/utils'

const copyCode = (className?: string) => {
const classNameProp = className ? ` className="${className}"` : ''
const code = `import { Card } from '@youngduck/yd-ui';\n\nfunction Example() {\n return (\n <Card${classNameProp}>\n 카드 내용\n </Card>\n );\n}`

copyCodeToClipboard(code)
}
import { Card, type CardVariant } from './Card'
import { Stack } from '../Layouts/Stack/Stack'
import { Inline } from '../Layouts/Inline/Inline'

const meta: Meta<typeof Card> = {
title: 'Components/Card',
Expand All @@ -19,22 +13,28 @@ const meta: Meta<typeof Card> = {
component: `
YD-UI 디자인 시스템의 카드 컴포넌트입니다.

콘텐츠를 담는 면(surface)으로, 테두리로 구분하는 outlined 와 배경색으로 구분하는 filled 두 가지 variant 를 지원합니다.

## 주요 특징
- card-navy-50 스타일 고정 (디자인 토큰 기반)
- border, border-radius, padding은 디자인 토큰에서 일괄 적용
- \`className\`으로 width, height 등 Tailwind 클래스를 자유롭게 지정 가능
- children으로 자유롭게 구성 가능
- outlined(테두리, 기본) / filled(배경) 두 가지 variant
- padding, border-radius, 색상은 디자인 토큰에서 일괄 적용
- \`className\` 으로 width, height 등 Tailwind 클래스를 자유롭게 지정 가능
- children 으로 자유롭게 구성 가능

## 사용 가이드
- Card는 항상 card-navy-50 스타일을 사용합니다.
- \`className\`에 Tailwind 클래스를 전달하여 크기를 조정합니다 (예: "w-64", "h-40", "max-w-md", "min-h-[200px]").
- \`children\`에 제목, 본문 등 원하는 내용을 넣어 사용합니다.
- 어두운 페이지 배경 위에 놓이는 대시보드 타일·패널·폼 컨테이너에는 \`variant="filled"\` 를 사용합니다.
- 배경과 같은 면 위에서 영역만 구분할 때는 \`variant="outlined"\` 를 사용합니다.
`,
},
},
},
tags: ['autodocs'],
argTypes: {
variant: {
options: ['outlined', 'filled'] as CardVariant[],
control: { type: 'select' },
description: 'outlined: 테두리로 구분 / filled: 배경색으로 구분',
},
children: {
control: 'text',
description: '카드 내용',
Expand All @@ -44,10 +44,7 @@ YD-UI 디자인 시스템의 카드 컴포넌트입니다.
},
className: {
control: 'text',
description: '추가 CSS 클래스',
table: {
type: { summary: 'string' },
},
description: '추가 CSS 클래스 (크기 지정 등)',
},
},
}
Expand All @@ -58,98 +55,72 @@ type Story = StoryObj<typeof Card>

export const Default: Story = {
render: args => (
<Card className={args.className}>{args.children}</Card>
<Card variant={args.variant} className="w-64">
<span className="text-yds-b2 text-white">{args.children}</span>
</Card>
),
args: {
variant: 'outlined',
children: '카드 내용',
className: '',
},
}

export const Examples = {
export const AllVariants: Story = {
render: () => (
<div className="bg-background-primary rounded-lg p-8">
<Inline gap="lg" align="start">
<Card variant="outlined" className="w-64">
<Stack gap="sm">
<span className="text-yds-b1 text-white">Outlined</span>
<span className="text-yds-c1m text-gray-300">테두리로 영역을 구분합니다.</span>
</Stack>
</Card>
<Card variant="filled" className="w-64">
<Stack gap="sm">
<span className="text-yds-b1 text-white">Filled</span>
<span className="text-yds-c1m text-gray-300">배경색으로 영역을 구분합니다.</span>
</Stack>
</Card>
</Inline>
</div>
),
}

export const DashboardTiles: Story = {
parameters: {
layout: 'fullscreen',
docs: {
disable: true,
description: {
story: '대시보드 요약 타일처럼 filled 카드를 그리드로 배치하는 예시입니다.',
},
},
},
render: () => {
return (
<div className="bg-background-primary min-h-screen p-8">
<div className="mx-auto max-w-6xl">
<h1 className="text-yds-h1 mb-2 text-white">Card Examples</h1>
<p className="text-yds-b2 mb-8 text-gray-300">
각 카드를 클릭하면 사용 코드가 클립보드에 복사됩니다.
</p>

<div className="bg-background-secondary mb-8 rounded-lg p-8">
<h2 className="text-yds-s1 mb-4 text-white">Width & Height (크기 조정)</h2>
<p className="text-yds-c1m mb-6 text-gray-300">
className에 Tailwind 클래스를 전달하여 자유롭게 크기를 조정할 수 있습니다.
</p>
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{[
{ className: 'w-64', label: 'className: w-64' },
{ className: 'w-full', label: 'className: w-full' },
{ className: 'max-w-md', label: 'className: max-w-md' },
{ className: 'w-64 h-40', label: 'className: w-64 h-40' },
{ className: 'w-full min-h-[200px]', label: 'className: w-full min-h-[200px]' },
{ className: 'max-w-lg h-64', label: 'className: max-w-lg h-64' },
].map(({ className, label }) => (
<div
key={label}
className="hover:bg-background-tertiary cursor-pointer rounded-lg p-2 transition-colors"
onClick={() => copyCode(className)}
onKeyDown={e => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
copyCode(className)
}
}}
role="button"
tabIndex={0}
aria-label={`${label} 카드 - 클릭 시 코드 복사`}
>
<Card className={className}>
<div className="text-yds-b2 text-white">
<strong>{label}</strong>
<p className="mt-2 text-yds-c1m">클릭하면 코드가 복사됩니다.</p>
</div>
</Card>
</div>
))}
</div>
</div>

<div className="bg-background-secondary mt-8 rounded-lg p-8">
<h2 className="text-yds-s1 mb-4 text-white">커스텀 내용 예시</h2>
<div
className="hover:bg-background-tertiary cursor-pointer max-w-md rounded-lg p-4 transition-colors"
onClick={() => {
const code = `import { Card } from '@youngduck/yd-ui';\n\nfunction Example() {\n return (\n <Card>\n <h3 className="text-yds-s2 text-white mb-2">카드 제목</h3>\n <p className="text-yds-c1m text-white">자유롭게 children을 넣어 사용할 수 있습니다.</p>\n </Card>\n );\n}`

copyCodeToClipboard(code)
}}
onKeyDown={e => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
const code = `import { Card } from '@youngduck/yd-ui';\n\nfunction Example() {\n return (\n <Card>\n <h3 className="text-yds-s2 text-white mb-2">카드 제목</h3>\n <p className="text-yds-c1m text-white">자유롭게 children을 넣어 사용할 수 있습니다.</p>\n </Card>\n );\n}`

copyCodeToClipboard(code)
}
}}
role="button"
tabIndex={0}
aria-label="커스텀 내용 예시 - 클릭 시 코드 복사"
>
<Card>
<h3 className="text-yds-s2 mb-2 text-white">카드 제목</h3>
<p className="text-yds-c1m text-white">자유롭게 children을 넣어 사용할 수 있습니다.</p>
</Card>
</div>
</div>
</div>
render: () => (
<div className="bg-background-primary rounded-lg p-8">
<div className="grid w-[640px] grid-cols-3 gap-4">
{[
{ label: '총 수입', value: '3,200,000원' },
{ label: '총 지출', value: '82,000원' },
{ label: '잔액', value: '3,118,000원' },
].map(item => (
<Card key={item.label} variant="filled">
<Stack gap="sm">
<span className="text-yds-c1m text-primary-100">{item.label}</span>
<span className="text-yds-s2 text-white">{item.value}</span>
</Stack>
</Card>
))}
</div>
)
},
</div>
),
}

export const WithCustomContent: Story = {
render: () => (
<Card variant="filled" className="max-w-md">
<Stack gap="sm">
<h3 className="text-yds-s2 text-white">카드 제목</h3>
<p className="text-yds-c1m text-white">자유롭게 children 을 넣어 사용할 수 있습니다.</p>
</Stack>
</Card>
),
}
33 changes: 28 additions & 5 deletions src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,36 @@
import { clsx } from 'clsx'
/**
* 작성자: KYD
* 기능: 콘텐츠를 담는 면(surface) 컴포넌트
* 프로세스 설명: outlined(테두리) / filled(배경) 두 가지 variant 로 대시보드 타일, 패널, 폼 컨테이너 등에 사용
*/

import { cva, type VariantProps } from 'class-variance-authority'

const cardVariants = cva('yds-card', {
variants: {
variant: {
/** 테두리로 영역을 구분 (기본) */
outlined: 'yds-card-outlined',
/** 배경색으로 영역을 구분 (대시보드 타일, 패널 등) */
filled: 'yds-card-filled',
},
},
defaultVariants: {
variant: 'outlined',
},
})

// 타입 export
export type CardVariant = VariantProps<typeof cardVariants>['variant']

type CardProps = {
children: React.ReactNode
className?: string
} & React.ComponentPropsWithoutRef<'div'>
} & React.ComponentPropsWithoutRef<'div'> &
VariantProps<typeof cardVariants>

export function Card({ children, className = '', ...props }: CardProps) {
export function Card({ variant, children, className = '', ...props }: CardProps) {
return (
<div className={clsx('yds-card', 'card-navy-50', className)} {...props}>
<div className={`${cardVariants({ variant })} ${className}`} {...props}>
{children}
</div>
)
Expand Down
Loading
Loading