| name | create-mds-component |
| description | Create a new @sopt-mds/ui component following this design system's conventions. Use when the user asks to create, add, or scaffold a new UI component in packages/ui. |
Create MDS Component
์ด ์คํฌ์ @sopt-mds/ui์ ์ ์ปดํฌ๋ํธ๋ฅผ ์ถ๊ฐํ ๋ ๋์์ธ ์์คํ
์ด ๊ฐ์ ํ๋ ๊ตฌ์กฐยทํจํด์ ์งํค๋๋ก ์๋ดํฉ๋๋ค. ๋ชฉํ๋ ์ฌ์ฉ์๊ฐ ์ ๋ฌํ ์๊ตฌ์ฌํญ(ํฌ๊ธฐ/์ํ/props ๋ฑ)์ ์ด ๋ ํฌ์ ๊ด๋ก์ ๋ง๊ฒ ๊ตฌํํ๋ ๊ฒ์
๋๋ค.
When to Use
- ์ฌ์ฉ์๊ฐ
@sopt-mds/ui(packages/ui)์ ์ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค๊ฑฐ๋ ์ถ๊ฐยท์ค์บํด๋ฉํด ๋ฌ๋ผ๊ณ ํ ๋.
- ์์ ๋ฐํ: "Badge ์ปดํฌ๋ํธ ๋ง๋ค์ด์ค", "add a Tooltip component", "์ ์ปดํฌ๋ํธ ์ถ๊ฐ".
- ๊ธฐ์กด ์ปดํฌ๋ํธ์ ๋จ์ ์์ ยท๋ฒ๊ทธ ํฝ์ค์๋ ์ฌ์ฉํ์ง ์์ต๋๋ค (์ ๊ท ์ปดํฌ๋ํธ ์์ฑ ์ ์ฉ).
๋ ํผ๋ฐ์ค
๊ฐ์ฅ ์ ํํ ๋ ํผ๋ฐ์ค๋ ์ค์ ์ฝ๋์
๋๋ค. ์์
์ ๋ค์ ๋ ํ์ผ์ ์ด์ด ํจํด์ ํ์ธํ์ธ์. ์ ์ปดํฌ๋ํธ๋ ์ด ๋์ ๊ทธ๋๋ก ๋ฐ๋ฆ
๋๋ค.
packages/ui/src/components/Chip/ โ ๋ฒํผ/ํ ๊ธ ์ฑ๊ฒฉ, state + size variant
packages/ui/src/components/Toggle/ โ native input ๊ธฐ๋ฐ, size variant
์์ ์ ๋ฐ๋์ ๋ฌผ์ด๋ณผ ๊ฒ
์คํฌ์ด ํธ๋ฆฌ๊ฑฐ๋๋ฉด ์ฝ๋๋ฅผ ์ฐ๊ธฐ ์ ์ AskQuestion ํด๋ก **"์ด ์ปดํฌ๋ํธ๋ ๋ฌด์์ธ๊ฐ์?"**๋ฅผ ๋ฌผ์ด๋ณด์ธ์. ๋ต๋ณ์ ์คํ ๋ฆฌ์ COMPONENT_DESCRIPTION(ํ๊ตญ์ด ์ค๋ช
)์ ๊ทธ๋๋ก ๋ฐ์๋ฉ๋๋ค. ์ด๋ฏธ ์ฌ์ฉ์๊ฐ ๋ํ์์ ์ปดํฌ๋ํธ ์ค๋ช
์ ์ถฉ๋ถํ ์ค ๊ฒฝ์ฐ์ ๊ทธ ๋ฌธ์ฅ์ ํ์ธ๋ง ๋ฐ๊ณ ์งํํด๋ ๋ฉ๋๋ค.
์ง๋ฌธ์ ์ ๊ณตํ ๋๋ ์ด ๋ต๋ณ์ด ์คํ ๋ฆฌ ๋ฌธ์(Storybook)์ ์ปดํฌ๋ํธ ์ค๋ช
์ ๊ทธ๋๋ก ๋ฐ์๋๋ค๋ ์ ์ ํ๋กฌํํธ์ ํจ๊ป ์๋ดํ์ธ์. ์ฌ์ฉ์๊ฐ ์ค๋ช
์ ์ฉ๋์ ํค(๋ฌธ์์ ๋
ธ์ถ๋๋ ํ๊ตญ์ด ์ค๋ช
)์ ์๊ณ ๋ตํ๋๋ก ํ๊ธฐ ์ํจ์
๋๋ค. ์: "์ด ์ปดํฌ๋ํธ๋ ๋ฌด์์ธ๊ฐ์? (์
๋ ฅํ์ ์ค๋ช
์ Storybook ์คํ ๋ฆฌ์ ์ปดํฌ๋ํธ ์ค๋ช
๋์ ๊ทธ๋๋ก ํ์๋ฉ๋๋ค.)"
์ปดํฌ๋ํธ ์ด๋ฆ(PascalCase)์ด ๋ถ๋ช
ํํ๋ฉด ํจ๊ป ํ์ธํ์ธ์.
prop ๋จ๊ณ๋ณ ์ธํฐ๋ทฐ (ํต์ฌ)
ํ์ผ์ ๋ง๋ค๊ธฐ ์ ์, ์ปดํฌ๋ํธ์ prop์ ํ ๋ฒ์ ํ๋์ฉ ์ฌ์ฉ์์๊ฒ ์ง์ํ๋ฉฐ ๊ทธ prop์ด ์ด๋ค ์คํ์ผ ๋ถ๊ธฐ๋ฅผ ๋ง๋๋์ง ํ์
ํฉ๋๋ค. ๋ชฉ์ ์ ์ฌ์ฉ์์ ์ค๋ช
์ ๊ทธ๋๋ก CSS variant๋ก ์ฐ์ถํ๋ ๊ฒ์
๋๋ค.
๊ฐ prop๋ง๋ค ๋ค์์ ํ์ธํ์ธ์.
- prop ์ด๋ฆ๊ณผ ํ์
โ ๋ฆฌํฐ๋ด ์ ๋์จ์ธ์ง, boolean์ธ์ง, ์คํ์ผ๊ณผ ๋ฌด๊ดํ ๊ฐ์ธ์ง.
- ์คํ์ผ ๋ถ๊ธฐ ์ฌ๋ถ โ ์ด prop์ ๊ฐ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋ CSS๊ฐ ์๋์ง.
- ๊ฐ๋ณ ์คํ์ผ โ ์๋ค๋ฉด ๊ฐ ๊ฐ์ด ์ด๋ค ์์ฑ(๋ฐฐ๊ฒฝ/์/ํ
๋๋ฆฌ/์น์ ๋ฑ)์ ๊ฐ๋์ง.
ํ prop ํ์ธ์ด ๋๋๋ฉด ๋ค์ prop์ผ๋ก ๋์ด๊ฐ๊ณ , ์ฌ์ฉ์๊ฐ "๋"์ด๋ผ๊ณ ํ๊ฑฐ๋ ๋ ์์ ๋๊น์ง ๋ฐ๋ณตํฉ๋๋ค.
์์
- prop:
variant, ํ์
: "active" | "filled" | "default"
active: ๋ฐฐ๊ฒฝ secondary.default, ๊ธ์ neutral.bold
filled: ๋ฐฐ๊ฒฝ neutral.subtle, ๊ธ์ neutral.bold
default: ๋ฐฐ๊ฒฝ neutral.ghost, ํ
๋๋ฆฌ 1px neutral.subtle, ๊ธ์ neutral.subtle
์ด ๋ต๋ณ์ ๋ฐ์ผ๋ฉด ๊ณง๋ฐ๋ก ์ฐ์ถํฉ๋๋ค.
types.ts: export type BadgeVariant = "active" | "filled" | "default";
constant.ts: BADGE_VARIANT_TOKENS: Record<BadgeVariant, ...> ํ ํฐ ๋งต
<Name>.css.ts: variant๊ฐ ์คํ์ผ์ ๋ถ๊ธฐํ๋ฏ๋ก ๊ฐ๋ณ { vars: {...} }๋ฅผ ๋ง๋ค๊ณ styleVariants๋ก ๋
ธ์ถ (references/css-variables.md ์ฐธ๊ณ )
์คํ์ผ ๋ถ๊ธฐ๊ฐ ์๋ prop(์: onClick, children, id)์ CSS variant๋ฅผ ๋ง๋ค์ง ์๊ณ ์ธํฐํ์ด์ค์๋ง JSDoc๊ณผ ํจ๊ป ๋ก๋๋ค. ์ด๋ค ์์ฑ์ด ๊ฐ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋์ง๊ฐ ๊ณง --mds-* CSS ๋ณ์ ๋ชฉ๋ก์ด ๋๋ฏ๋ก, ์ธํฐ๋ทฐ ๊ฒฐ๊ณผ์์ ๋ณ์ ๋ชฉ๋ก์ ๋์ถํ์ธ์.
์ํฌํ๋ก์ฐ
- [ ] 1. ์ปดํฌ๋ํธ ์ค๋ช
์ AskQuestion์ผ๋ก ํ๋ณด (์คํ ๋ฆฌ description์ฉ)
- [ ] 2. prop์ ํ step์ฉ ์ธํฐ๋ทฐํ์ฌ ๊ฐ prop์ ์คํ์ผ ๋ถ๊ธฐยท๊ฐ๋ณ ์คํ์ผ ํ์
- [ ] 3. ์ธํฐ๋ทฐ ๊ฒฐ๊ณผ๋ก variant ํ์
ยทํ ํฐ ๋งตยทCSS ๋ณ์ ๋ชฉ๋ก ์ฐ์ถ
- [ ] 4. 6๊ฐ ํ์ผ ์์ฑ (types โ constant โ css โ tsx โ stories โ index ์ ๊ถ์ฅ)
- [ ] 5. components ๋ฐฐ๋ด export + Introduction.mdx ๋งํฌ ์ถ๊ฐ
- [ ] 6. ๊ฒ์ฆ (typecheck / build)
๋๋ ํฐ๋ฆฌ: packages/ui/src/components/<Name>/
| ํ์ผ | ์ญํ |
|---|
types.ts | Size ๋ฑ์์ ํ์ํ variant ์ ๋์จ ํ์
์ ์ |
constant.ts | size/state๋ณ ๋์์ธ ํ ํฐ ๋งต (@sopt-mds/design-tokens ์ฌ์ฉ, ํ๋์ฝ๋ฉ ์ง์) |
<Name>.css.ts | vanilla-extract ์คํ์ผ + --mds-* CSS ๋ณ์ |
<Name>.tsx | forwardRef ์ปดํฌ๋ํธ, prop๋ง๋ค JSDoc |
<Name>.stories.tsx | COMPONENT_DESCRIPTION + autodocs |
index.ts | export * from "./<Name>"; export type * from "./types"; |
ํ์ฅ์๋ ๋ฐ๋์ <Name>.stories.tsx์
๋๋ค (JSX ์ฌ์ฉ). .stories.ts๊ฐ ์๋๋๋ค.
ํต์ฌ ๊ท์น
1. CSS ๋ณ์ (์ค๋ฒ๋ผ์ด๋ ๊ฐ๋ฅ)
์ฌ์ฉ์ฒ์์ ์คํ์ผ์ ์ค๋ฒ๋ผ์ด๋ํ ์ ์๋๋ก ์ปดํฌ๋ํธ์ ์กฐ์ ๊ฐ๋ฅํ ์์ฑ์ --mds-<component>-<property> CSS ๋ณ์๋ก ๋
ธ์ถํฉ๋๋ค. variant(size/state)๋ณ ๊ฐ์ ์ด ๋ณ์๋ค์ ํ ํฐ์ ๋งคํํด ์ง์ ํฉ๋๋ค.
ํจํด์ด ์ ๊ตํ๋ฏ๋ก ์ ์ฒด ํ
ํ๋ฆฟ๊ณผ ๊ท์น์ references/css-variables.md๋ฅผ ๋ฐ๋์ ์ฝ๊ณ ๋ฐ๋ฅด์ธ์. ์์ฝํ๋ฉด: ๋ณ์๋ช
๋ฌธ์์ด ์์ โ ์ ๋์จ ํ์
โ createGlobalVar๋ฅผ ๊ฐ์ผ create<Name>Var โ <name>Vars ๊ฐ์ฒด โ constant.ts ํ ํฐ์ { vars: {...} }๋ก ๋งคํ โ styleVariants๋ก ๋
ธ์ถ.
์๊ตฌ์ฌํญ์์ "๋ฌด์์ด ํฌ๊ธฐ/์ํ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋๊ฐ", "์ฌ์ฉ์ฒ๊ฐ ๋ฌด์์ ์ปค์คํฐ๋ง์ด์ฆํ ์ ์์ด์ผ ํ๋๊ฐ"๋ฅผ ๋ฝ์๋ด ๋ณ์ ๋ชฉ๋ก์ ์ ํ์ธ์.
2. ์ปดํฌ๋ํธ ์ธํฐํ์ด์ค (JSDoc ํ์)
react-docgen-typescript๊ฐ ๊ฐ prop์ JSDoc์ Storybook ๋ฌธ์ ์ค๋ช
์ผ๋ก ๋ณํํฉ๋๋ค. ๋ฐ๋ผ์ ๋ชจ๋ public prop์ ์๋ฏธ๋ฅผ ์ค๋ช
ํ๋ JSDoc ์ฃผ์์ ๋ต๋๋ค.
export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
size?: BadgeSize;
leftAddon?: React.ReactElement;
}
React.forwardRef๋ก ๊ตฌํํ๊ณ displayName์ ์ค์ ํฉ๋๋ค. className/style์ ์ธ๋ถ์์ ๋ณํฉ ๊ฐ๋ฅํ๋๋ก ๋๊ณ , clsx(base, sizeVariants[size], className) ํํ๋ก ๊ฒฐํฉํฉ๋๋ค.
3. ์คํ ๋ฆฌ
์๋จ์ COMPONENT_DESCRIPTION ์์(์ฌ์ฉ์ ๋ต๋ณ)๋ฅผ ๋๊ณ parameters.docs.description.component์ ์ฐ๊ฒฐํฉ๋๋ค. tags: ["autodocs"]๋ฅผ ํฌํจํฉ๋๋ค.
const COMPONENT_DESCRIPTION = `
\`Badge\`๋ ... ์ปดํฌ๋ํธ์
๋๋ค.
`;
const meta: Meta<typeof Badge> = {
title: "Badge",
component: Badge,
tags: ["autodocs"],
parameters: {
docs: { description: { component: COMPONENT_DESCRIPTION } },
},
};
๊ธฐ๋ณธ ์คํ ๋ฆฌ์ name์ ์ปดํฌ๋ํธ ํ์๋ช
(์: "Badge")์ผ๋ก ์ง์ ํฉ๋๋ค (Chip ์ฐธ๊ณ ).
4. ๋ฐฐ์ (barrel + Introduction)
์ปดํฌ๋ํธ ์์ฑ ํ ๋ ๊ณณ์ ๊ฐฑ์ ํฉ๋๋ค.
packages/ui/src/components/index.ts ์ ์ํ๋ฒณ ์์๋ก export * from "./<Name>"; ์ถ๊ฐ. (์ด ๋ฐฐ๋ด์ด packages/ui/src/index.ts๋ฅผ ํตํด ์ฌ๋
ธ์ถ๋ฉ๋๋ค.)
apps/storybook/stories/Introduction.mdx ์ ## ์ปดํฌ๋ํธ ๋ชฉ๋ก ์๋์ ๋งํฌ ์ถ๊ฐ:
[<Name>](?path=/docs/components-<slug>--์๊ฐ)
<slug>๋ ์ปดํฌ๋ํธ๋ช
์๋ฌธ์์
๋๋ค (์: Badge โ badge).
5. ๊ฒ์ฆ
์์ฑ ํ ํด๋น ํจํค์ง๋ฅผ ๊ฒ์ฆํฉ๋๋ค.
pnpm --filter @sopt-mds/ui typecheck
pnpm --filter @sopt-mds/ui build
์ํฐํจํด ํํผ
- ์์/์น์ ํ๋์ฝ๋ฉ ์ฐ์ฌ ๊ธ์ง โ
constant.ts ํ ํฐ ๋งต์ ๋จ์ผ ์ถ์ฒ๋ก ์ฌ์ฉ.
state prop ์ด์ค๊ด๋ฆฌ ๋์ ๊ฐ๋ฅํ ๊ฒฝ์ฐ native pseudo-class(:checked/:disabled/:focus-visible)์ ์ธ์ ํ์ /:has() ์ ํ์๋ก ์ํ๋ฅผ ๊ตฌ๋ (Chip/Toggle ๊ด๋ก).
- native ์์๋ก ํด๊ฒฐ๋๋ ์ํธ์์ฉ(์ฒดํฌ/๋ฒํผ ๋ฑ)์
div + onClick์ผ๋ก ๋์ฒด ๊ธ์ง (์ ๊ทผ์ฑ).
- prop JSDoc ๋๋ฝ ๊ธ์ง โ autodocs ํ์ง๊ณผ ์ง๊ฒฐ.