stitch-react
3
总安装量
3
周安装量
#60537
全站排名
安装命令
npx skills add https://github.com/jh941213/my-claude-code-asset --skill stitch-react
Agent 安装分布
cline
3
github-copilot
3
codex
3
kimi-cli
3
gemini-cli
3
cursor
3
Skill 文档
Stitch to React Components
Stitchìì ìì±ë HTML ì¤í¬ë¦°ì ì¬ì¬ì© ê°ë¥í React ì»´í¬ëí¸ ìì¤í ì¼ë¡ ë³íí©ëë¤. ëìì¸ í í° ì¶ì¶, ì»´í¬ëí¸ ë¶í´, ìë ê²ì¦ì í¬í¨í©ëë¤.
ê°ì
ì´ ì¤í¬ì Stitchì ì ì HTML ì¶ë ¥ì íë¡ëì ë ë React ì»´í¬ëí¸ë¡ ë³íí©ëë¤:
- ëìì¸ í í° ì¶ì¶: ìì, íì´í¬ê·¸ëí¼, ê°ê²©ì í í°ì¼ë¡ ì¶ì¶
- ì»´í¬ëí¸ ë¶í´: HTMLì ì¬ì¬ì© ê°ë¥í ì»´í¬ëí¸ë¡ ë¶ë¦¬
- TypeScript ì§ì: Props íì ì ì ìë ìì±
- ê²ì¦: ìì±ë ì»´í¬ëí¸ì ë¬¸ë² ë° íì ê²ì¦
ì¬ì ì구ì¬í
- Stitch MCP ìë² ì ê·¼ ê¶í
- Stitch íë¡ì í¸ì ìì±ë ì¤í¬ë¦°
- Node.js íê²½ (React íë¡ì í¸)
DESIGN.mdíì¼ (ì í, í í° ì¼ê´ì± í¥ì)
ë³í ìí¬íë¡ì°
Step 1: Stitch ì¤í¬ë¦° ê°ì ¸ì¤ê¸°
# Stitch MCPë¡ ì¤í¬ë¦° HTML ë¤ì´ë¡ë
[prefix]:get_screen í¸ì¶
â htmlCode.downloadUrlìì HTML ë¤ì´ë¡ë
â source.htmlë¡ ì ì¥
Step 2: ëìì¸ í í° ì¶ì¶
Stitch HTMLìì Tailwind í´ëì¤ì ì¸ë¼ì¸ ì¤íì¼ì ë¶ìíì¬ ëìì¸ í í°ì ì¶ì¶í©ëë¤.
ìì í í°
// tokens/colors.ts
export const colors = {
// Primary
primary: {
DEFAULT: '#0066FF',
hover: '#0052CC',
light: '#E6F0FF',
},
// Neutral
neutral: {
50: '#F9FAFB',
100: '#F3F4F6',
200: '#E5E7EB',
// ...
900: '#111827',
},
// Semantic
success: '#10B981',
error: '#EF4444',
warning: '#F59E0B',
} as const;
íì´í¬ê·¸ëí¼ í í°
// tokens/typography.ts
export const typography = {
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.25rem' }],
base: ['1rem', { lineHeight: '1.5rem' }],
lg: ['1.125rem', { lineHeight: '1.75rem' }],
xl: ['1.25rem', { lineHeight: '1.75rem' }],
'2xl': ['1.5rem', { lineHeight: '2rem' }],
'3xl': ['1.875rem', { lineHeight: '2.25rem' }],
},
fontWeight: {
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
},
} as const;
ê°ê²© í í°
// tokens/spacing.ts
export const spacing = {
px: '1px',
0: '0',
0.5: '0.125rem',
1: '0.25rem',
2: '0.5rem',
3: '0.75rem',
4: '1rem',
5: '1.25rem',
6: '1.5rem',
8: '2rem',
10: '2.5rem',
12: '3rem',
16: '4rem',
20: '5rem',
24: '6rem',
} as const;
Step 3: ì»´í¬ëí¸ ë¶í´
HTML 구조를 ë¶ìíì¬ ì¬ì¬ì© ê°ë¥í ì»´í¬ëí¸ë¡ ë¶í´í©ëë¤.
ë¶í´ ìì¹
| ìì¹ | ì¤ëª |
|---|---|
| ë¨ì¼ ì± ì | ê° ì»´í¬ëí¸ë íëì ìí ë§ |
| ì¬ì¬ì©ì± | ì¬ë¬ ê³³ìì ì¬ì©ë í¨í´ ìë³ |
| êµ¬ì± ê°ë¥ì± | ìì ì»´í¬ëí¸ë¡ í° ì»´í¬ëí¸ êµ¬ì± |
| Props ê¸°ë° | íëì½ë© ëì Propsë¡ ì»¤ì¤í°ë§ì´ì§ |
ì»´í¬ëí¸ ê³ì¸µ 구조
components/
âââ primitives/ # 기본 ìì
â âââ Button.tsx
â âââ Input.tsx
â âââ Text.tsx
â âââ Icon.tsx
âââ patterns/ # ì¬ì¬ì© í¨í´
â âââ Card.tsx
â âââ Badge.tsx
â âââ Avatar.tsx
â âââ Tooltip.tsx
âââ blocks/ # ì¹ì
ë¸ë¡
â âââ Header.tsx
â âââ Footer.tsx
â âââ Hero.tsx
â âââ Features.tsx
âââ layouts/ # ë ì´ìì
âââ PageLayout.tsx
âââ GridLayout.tsx
Step 4: ì»´í¬ëí¸ ìì±
Button ì»´í¬ëí¸ ìì
// components/primitives/Button.tsx
import { forwardRef, type ButtonHTMLAttributes } from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
// Base styles
'inline-flex items-center justify-center rounded-lg font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
primary: 'bg-primary text-white hover:bg-primary-hover',
secondary: 'bg-neutral-100 text-neutral-900 hover:bg-neutral-200',
outline: 'border border-neutral-200 bg-white hover:bg-neutral-50',
ghost: 'hover:bg-neutral-100',
destructive: 'bg-error text-white hover:bg-error/90',
},
size: {
sm: 'h-8 px-3 text-sm',
md: 'h-10 px-4 text-sm',
lg: 'h-12 px-6 text-base',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
}
);
export interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
isLoading?: boolean;
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, isLoading, children, disabled, ...props }, ref) => {
return (
<button
ref={ref}
className={cn(buttonVariants({ variant, size }), className)}
disabled={disabled || isLoading}
{...props}
>
{isLoading ? (
<span className="mr-2 h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
) : null}
{children}
</button>
);
}
);
Button.displayName = 'Button';
Card ì»´í¬ëí¸ ìì
// components/patterns/Card.tsx
import { forwardRef, type HTMLAttributes } from 'react';
import { cn } from '@/lib/utils';
export interface CardProps extends HTMLAttributes<HTMLDivElement> {
variant?: 'default' | 'outlined' | 'elevated';
}
export const Card = forwardRef<HTMLDivElement, CardProps>(
({ className, variant = 'default', ...props }, ref) => {
const variants = {
default: 'bg-white rounded-xl',
outlined: 'bg-white rounded-xl border border-neutral-200',
elevated: 'bg-white rounded-xl shadow-lg',
};
return (
<div
ref={ref}
className={cn(variants[variant], className)}
{...props}
/>
);
}
);
Card.displayName = 'Card';
// Sub-components
export const CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pb-0', className)} {...props} />
)
);
export const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6', className)} {...props} />
)
);
export const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pt-0 flex items-center gap-4', className)} {...props} />
)
);
Step 5: ê²ì¦
ìì±ë ì»´í¬ëí¸ë¥¼ ê²ì¦í©ëë¤.
TypeScript ê²ì¦
# íì
ì²´í¬
npx tsc --noEmit
# ë¦°í¸ ê²ì¬
npx eslint components/
ì¤í ë¦¬ë¶ ê²ì¦ (ì í)
// components/primitives/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'Primitives/Button',
component: Button,
tags: ['autodocs'],
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = {
args: {
children: 'Button',
variant: 'primary',
},
};
export const Secondary: Story = {
args: {
children: 'Button',
variant: 'secondary',
},
};
export const Loading: Story = {
args: {
children: 'Loading',
isLoading: true,
},
};
ì¶ë ¥ íì¼ êµ¬ì¡°
src/
âââ tokens/
â âââ index.ts
â âââ colors.ts
â âââ typography.ts
â âââ spacing.ts
â âââ shadows.ts
âââ components/
â âââ primitives/
â â âââ Button.tsx
â â âââ Input.tsx
â â âââ ...
â âââ patterns/
â â âââ Card.tsx
â â âââ ...
â âââ blocks/
â â âââ Header.tsx
â â âââ ...
â âââ index.ts
âââ lib/
â âââ utils.ts
âââ pages/
âââ [StitchPage].tsx # ë³íë ì ì²´ íì´ì§
DESIGN.md ì°ë
DESIGN.mdê° ìì¼ë©´ í í° ì¶ì¶ ì 참조íì¬ ì¼ê´ì±ì ë³´ì¥í©ëë¤:
// DESIGN.mdì ìì ì¹ì
ê³¼ 매í
const designMdColors = {
'Deep Ocean Blue': '#0066FF', // â colors.primary.DEFAULT
'Whisper Gray': '#F5F5F5', // â colors.neutral.100
'Midnight Text': '#1A1A1A', // â colors.neutral.900
};
ìëí ì¤í¬ë¦½í¸
ë³í ì¤í¬ë¦½í¸
// scripts/convert-stitch.ts
import { parseHTML } from './parsers/html';
import { extractTokens } from './extractors/tokens';
import { generateComponents } from './generators/components';
import { validateOutput } from './validators';
async function convertStitchScreen(htmlPath: string, outputDir: string) {
// 1. HTML íì±
const dom = await parseHTML(htmlPath);
// 2. í í° ì¶ì¶
const tokens = extractTokens(dom);
// 3. ì»´í¬ëí¸ ìì±
const components = generateComponents(dom, tokens);
// 4. íì¼ ì¶ë ¥
await writeTokens(outputDir, tokens);
await writeComponents(outputDir, components);
// 5. ê²ì¦
const valid = await validateOutput(outputDir);
if (!valid) {
throw new Error('Validation failed');
}
console.log('Conversion complete!');
}
ì¼ë°ì ì¸ ë³í í¨í´
Tailwind â CSS-in-JS
// Stitch HTML
<button class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg">
// React ì»´í¬ëí¸
<Button variant="primary" size="md">
ì ì ì½í ì¸ â Props
// Stitch HTML
<h1 class="text-3xl font-bold">Welcome to Our App</h1>
// React ì»´í¬ëí¸
interface HeadingProps {
children: React.ReactNode;
size?: 'sm' | 'md' | 'lg' | 'xl';
}
<Heading size="xl">{title}</Heading>
ë°ë³µ ìì â 매í
// Stitch HTML (ë°ë³µë ì¹´ë)
<div class="card">...</div>
<div class="card">...</div>
<div class="card">...</div>
// React ì»´í¬ëí¸
{items.map((item) => (
<Card key={item.id} {...item} />
))}
í¼í´ì¼ í í¨ì
| 문ì | í´ê²°ì± |
|---|---|
| â 모ë ì¤íì¼ì ì¸ë¼ì¸ì¼ë¡ | í í°ê³¼ variants ì¬ì© |
| â íëì½ë©ë í ì¤í¸ | Propsë¡ ì ë¬ |
| â ë¨ì¼ ê±°ë ì»´í¬ëí¸ | ìì ì»´í¬ëí¸ë¡ ë¶í´ |
| â íì ì ì ëë½ | 모ë Propsì TypeScript íì |
| â ì ê·¼ì± ë¬´ì | ARIA ìì± ë° ìë§¨í± HTML |
리ìì¤
- Stitch 문ì: https://stitch.withgoogle.com/docs/
- class-variance-authority: https://cva.style/docs
- Tailwind CSS: https://tailwindcss.com/docs
- Radix UI: https://www.radix-ui.com/ (ì ê·¼ì± ìë primitives)