home / skills / pluxity / pf-frontend / pf-component

pf-component skill

/.claude/skills/pf-component

This skill helps generate PF frontend React UI components following PF conventions for atoms, molecules, organisms, and app components.

npx playbooks add skill pluxity/pf-frontend --skill pf-component

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
5.8 KB
---
name: pf-component
description: pf-frontend 프로젝트 컨벤션에 맞는 React 컴포넌트 생성. "컴포넌트 만들어줘", "새 컴포넌트", "UI 컴포넌트 생성" 요청 시 사용.
allowed-tools: Write, Read, Bash
---

# PF 컴포넌트 생성기

$ARGUMENTS 이름으로 컴포넌트를 생성합니다.

## 컴포넌트 유형 판단

1. **packages/ui 컴포넌트** - 재사용 가능한 공통 UI
   - atoms: 기본 컴포넌트 (Button, Input, Select 등)
   - molecules: 복합 컴포넌트 (Carousel, Widget 등)
   - organisms: 복잡한 컴포넌트 (Sidebar, DataTable 등)

2. **앱 전용 컴포넌트** - 특정 앱에서만 사용
   - `apps/앱이름/src/components/`

---

## packages/ui 컴포넌트 구조

```
packages/ui/src/atoms/ComponentName/
├── ComponentName.tsx        # 메인 컴포넌트
├── index.ts                 # export
├── types.ts                 # Props 타입
├── variants.ts              # CVA 스타일 (필요시)
└── ComponentName.stories.tsx # Storybook (선택)
```

### types.ts 템플릿

```tsx
import type { VariantProps } from "class-variance-authority";
import { componentVariants } from "./variants";

export interface ComponentNameProps
  extends React.HTMLAttributes<HTMLDivElement>,
    VariantProps<typeof componentVariants> {
  /** 컴포넌트 설명 */
  children?: React.ReactNode;
  /** ref 전달 (React 19) */
  ref?: React.Ref<HTMLDivElement>;
}
```

### variants.ts 템플릿 (CVA)

```tsx
import { cva } from "class-variance-authority";

export const componentVariants = cva(
  // Base styles
  "inline-flex items-center justify-center",
  {
    variants: {
      variant: {
        default: "bg-white text-gray-900",
        primary: "bg-brand text-white",
        secondary: "bg-neutral-100 text-gray-700",
      },
      size: {
        sm: "h-8 px-3 text-sm",
        md: "h-10 px-4 text-base",
        lg: "h-12 px-6 text-lg",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "md",
    },
  }
);
```

### ComponentName.tsx 템플릿

```tsx
import { cn } from "../../utils";
import { componentVariants } from "./variants";
import type { ComponentNameProps } from "./types";

/**
 * ComponentName 컴포넌트
 *
 * @example
 * <ComponentName variant="primary" size="md">
 *   Content
 * </ComponentName>
 */
function ComponentName({
  className,
  variant,
  size,
  children,
  ref,
  ...props
}: ComponentNameProps) {
  return (
    <div
      ref={ref}
      className={cn(componentVariants({ variant, size, className }))}
      {...props}
    >
      {children}
    </div>
  );
}

export { ComponentName };
```

### index.ts 템플릿

```tsx
export { ComponentName } from "./ComponentName";
export type { ComponentNameProps } from "./types";
```

---

## Composition Pattern (Organism 컴포넌트)

복잡한 컴포넌트는 Context + 서브 컴포넌트 패턴 사용:

```tsx
// Context 생성
import { createContext, useContext, useState } from "react";

interface ComponentContextValue {
  expanded: boolean;
  setExpanded: (value: boolean) => void;
}

const ComponentContext = createContext<ComponentContextValue | null>(null);

function useComponentContext() {
  const context = useContext(ComponentContext);
  if (!context) {
    throw new Error("Component 컨텍스트 내에서 사용해야 합니다");
  }
  return context;
}

// 메인 컴포넌트
interface ComponentNameProps {
  children: React.ReactNode;
  defaultExpanded?: boolean;
}

function ComponentName({ children, defaultExpanded = false }: ComponentNameProps) {
  const [expanded, setExpanded] = useState(defaultExpanded);

  return (
    <ComponentContext.Provider value={{ expanded, setExpanded }}>
      <div className="component-wrapper">{children}</div>
    </ComponentContext.Provider>
  );
}

// 서브 컴포넌트
function ComponentHeader({ children }: { children: React.ReactNode }) {
  const { expanded, setExpanded } = useComponentContext();
  return (
    <div className="component-header" onClick={() => setExpanded(!expanded)}>
      {children}
    </div>
  );
}

function ComponentContent({ children }: { children: React.ReactNode }) {
  const { expanded } = useComponentContext();
  if (!expanded) return null;
  return <div className="component-content">{children}</div>;
}

// 서브 컴포넌트 연결
ComponentName.Header = ComponentHeader;
ComponentName.Content = ComponentContent;

export { ComponentName, useComponentContext };
```

**사용:**

```tsx
<ComponentName defaultExpanded>
  <ComponentName.Header>제목</ComponentName.Header>
  <ComponentName.Content>내용</ComponentName.Content>
</ComponentName>
```

---

## 앱 전용 컴포넌트

앱 전용 컴포넌트는 더 간단하게:

```tsx
// apps/앱이름/src/components/FeatureCard.tsx

interface FeatureCardProps {
  title: string;
  description: string;
  icon: React.ReactNode;
  onClick?: () => void;
}

export function FeatureCard({ title, description, icon, onClick }: FeatureCardProps) {
  return (
    <div
      className="rounded-lg border p-4 hover:shadow-md transition-shadow cursor-pointer"
      onClick={onClick}
    >
      <div className="flex items-center gap-3">
        {icon}
        <h3 className="font-semibold">{title}</h3>
      </div>
      <p className="mt-2 text-sm text-gray-600">{description}</p>
    </div>
  );
}
```

---

## 중요 규칙

1. **React 19 패턴**
   - `forwardRef` 사용하지 않음
   - ref를 props로 직접 받음
   - 불필요한 memo/useMemo/useCallback 피함

2. **TypeScript**
   - Props 인터페이스 필수
   - `any` 사용 금지

3. **스타일링**
   - Tailwind CSS 사용
   - cn() 유틸리티로 클래스 병합
   - CVA로 variant 관리

4. **Export**
   - named export만 사용 (default export 금지)
   - 타입도 함께 export

5. **문서화**
   - JSDoc 주석 추가
   - @example 포함

Overview

This skill generates React components that follow the pf-frontend project conventions for the PF DEV monorepo. It creates either reusable packages/ui components (atoms, molecules, organisms) or app-specific components under apps/<app>/src/components, with TypeScript types, CVA-based variants, Tailwind styling, and Storybook scaffolding when appropriate.

How this skill works

Given a component name argument, the skill decides whether to scaffold a packages/ui component or an app-specific component based on context or an explicit flag. It emits a directory with files: a typed main component, index exports, types.ts, optional variants.ts using class-variance-authority, and an optional .stories.tsx. For complex organisms it scaffolds a Context + subcomponent composition pattern.

When to use it

  • Create a new shared UI element that should live in packages/ui (atom, molecule, organism).
  • Add a simple, app-scoped component inside apps/<app>/src/components.
  • Scaffold a component with consistent TypeScript props, Tailwind classes, and CVA variants.
  • Generate composition-ready organisms using Context and subcomponent pattern.
  • Get storybook-ready component files for documentation and QA.

Best practices

  • Choose packages/ui for reusable UI (atoms → molecules → organisms), apps/ for app-specific pieces.
  • Always declare a Props interface in types.ts; avoid any and export the type.
  • Use CVA in variants.ts to centralize variant and size styles; merge classes with cn().
  • Follow React 19 pattern: accept ref via props (do not forwardRef) and avoid unnecessary memoization.
  • Use named exports only and include JSDoc with @example for each component.

Example use cases

  • Generate an atom Button with variants.ts, types.ts, ComponentName.tsx, and index.ts for packages/ui.
  • Create a molecule like Card that composes atoms and exposes subcomponents (Header, Body, Footer).
  • Scaffold an organism Accordion that provides context, Header toggle, and Content panels.
  • Add an app-only FeatureCard under apps/dashboard/src/components for a single-app UI.
  • Produce a Storybook story file to preview variants and sizes.

FAQ

Where should I put a component used by multiple apps?

If it will be reused across apps, place it in packages/ui under the appropriate atom/molecule/organism folder.

How should refs be handled?

Accept ref as a prop on the component (React 19 pattern). Do not use forwardRef.