Loading...
Loading...
React/Next.js 프론트엔드 프로젝트의 컴포넌트, 페이지, API 라우트, 훅, 스토어 등 보일러플레이트를 자동 생성하는 스캐폴딩 스킬. "컴포넌트 만들어", "페이지 생성", "scaffold", "boilerplate" 등의 요청 시 사용.
npx skill4agent add ingpdw/pdw-fe-dev-tool fe-scaffold$ARGUMENTS| 타입 | 설명 | 예시 |
|---|---|---|
| React 컴포넌트 + 테스트 | |
| Next.js App Router 페이지 | |
| Route Handler (API) | |
| 커스텀 훅 + 테스트 | |
| Zustand 스토어 | |
| 기능 모듈 (컴포넌트 + 훅 + 타입 + 테스트) | |
| React Hook Form + Zod 스키마 폼 | |
src/components/[이름]/[이름].tsx[이름].test.tsx// src/components/[Name]/[Name].tsx
import { cn } from "@/lib/utils";
interface [Name]Props {
className?: string;
children?: React.ReactNode;
}
function [Name]({ className, children }: [Name]Props) {
return (
<div className={cn("", className)}>
{children}
</div>
);
}
export { [Name] };
export type { [Name]Props };// src/components/[Name]/[Name].test.tsx
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { [Name] } from "./[Name]";
describe("[Name]", () => {
it("renders children", () => {
render(<[Name]>test</[Name]>);
expect(screen.getByText("test")).toBeInTheDocument();
});
});src/app/[이름]/page.tsxlayout.tsxloading.tsx// src/app/[name]/page.tsx
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "[Name]",
description: "[Name] page",
};
export default function [Name]Page() {
return (
<main>
<h1>[Name]</h1>
</main>
);
}// src/app/[name]/loading.tsx
import { Skeleton } from "@/components/ui/skeleton";
export default function [Name]Loading() {
return <Skeleton className="h-screen w-full" />;
}src/app/api/[이름]/route.ts// src/app/api/[name]/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest) {
try {
// TODO: implement
return NextResponse.json({ data: [] });
} catch (error) {
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 }
);
}
}
export async function POST(request: NextRequest) {
try {
const body = await request.json();
// TODO: implement
return NextResponse.json({ data: body }, { status: 201 });
} catch (error) {
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 }
);
}
}src/hooks/[이름].ts[이름].test.ts// src/hooks/[useName].ts
import { useCallback, useState } from "react";
function [useName]() {
// TODO: implement
return {};
}
export { [useName] };// src/hooks/[useName].test.ts
import { renderHook, act } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { [useName] } from "./[useName]";
describe("[useName]", () => {
it("should work", () => {
const { result } = renderHook(() => [useName]());
expect(result.current).toBeDefined();
});
});src/stores/[이름]Store.ts// src/stores/[name]Store.ts
import { create } from "zustand";
import { devtools } from "zustand/middleware";
interface [Name]State {
// TODO: define state
}
interface [Name]Actions {
// TODO: define actions
reset: () => void;
}
const initial[Name]State: [Name]State = {
// TODO: initial values
};
const use[Name]Store = create<[Name]State & [Name]Actions>()(
devtools(
(set) => ({
...initial[Name]State,
reset: () => set(initial[Name]State),
}),
{ name: "[name]-store" }
)
);
export { use[Name]Store };
export type { [Name]State, [Name]Actions };src/components/[feature]/src/components/[feature]/
├── [Feature].tsx # 메인 컴포넌트
├── [Feature].test.tsx # 테스트
├── use[Feature].ts # 기능 전용 훅
├── [feature].types.ts # 타입 정의
└── index.ts # barrel export// src/components/[Name]/[Name].tsx
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
const [name]Schema = z.object({
// TODO: define schema
email: z.string().email(),
});
type [Name]Values = z.infer<typeof [name]Schema>;
interface [Name]Props {
onSubmit: (values: [Name]Values) => void;
}
function [Name]({ onSubmit }: [Name]Props) {
const form = useForm<[Name]Values>({
resolver: zodResolver([name]Schema),
defaultValues: {
email: "",
},
});
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="email@example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
);
}
export { [Name] };
export type { [Name]Values };$ARGUMENTS