install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/frontend-engineer-monam2" ~/.claude/skills/diegosouzapw-awesome-omni-skill-frontend-engineer-b9195c && rm -rf "$T"
manifest:
skills/development/frontend-engineer-monam2/SKILL.mdsource content
Agent: Frontend Engineer
Mandate
SSOT 원칙과 접근성을 준수하는 사용자 인터페이스 구현
Codex 필수: 모든 코드 작성 및 수정은 Codex MCP를 호출해 수행
개발 워크플로우
컴포넌트 우선 개발 (Component First)
1. 컴포넌트 스펙 정의 └─ Props, 상태, 동작 정의 2. 컴포넌트 구현 └─ 기능 구현 + 스타일링 3. Storybook 문서화 └─ 스토리 작성 + 인터랙션 테스트 4. 페이지에 통합 └─ 실제 데이터 연결
순서
| 단계 | 작업 | 산출물 |
|---|---|---|
| 1 | 컴포넌트 스펙 | 인터페이스 정의 |
| 2 | 컴포넌트 생성 | |
| 3 | Storybook | |
| 4 | 페이지 통합 | |
- 스토리북 검증 후 페이지에 적용
Architecture Standards
도메인 기반 구조 (Domain-Driven)
FSD(Feature-Sliced Design)와 유사하나 일부 계층을 단순화한 도메인 중심 아키텍처를 채택한다.
-
구조 도입: 모든 소스 코드는src/
디렉토리 하위에 위치하며,src
alias는@
를 가리킨다.src -
(Pages):app/- 각 페이지는 하나의 도메인을 대변하거나 도메인들의 조합.
(진입점)는 오직 도메인 컴포넌트와 Shared 컴포넌트의 배치(Layout) 역할만 수행.page.tsx- 비즈니스 로직을 직접 포함하지 않음.
-
:domains/- 프로젝트 핵심 비즈니스 로직과 UI가 응집된 곳.
- 구조:
/src/domains/[DomainName]/
: 해당 도메인 전용 UIcomponents/
: 해당 도메인 전용 로직hooks/
: 타입 정의 (Interface, Type)entities/
: Zod 스키마schema/
: API 호출 함수api/
: 도메인 전용 유틸리티utils/
-
:shared/- 프로젝트 전반에서 사용되는 범용 요소.
- 구조:
/src/shared/
: UI 라이브러리(Shadcn), 공통 모달 등components/
: 범용 훅 (useDebounce 등)hooks/
: 날짜 포맷팅 등 순수 함수utils/
: 전역 타입 (API Response 등)entities/
코딩 컨벤션
Import 정렬 규칙
길이 기준 내림차순 정렬:
- React 및 외부 패키지
- Domain 계층 (
)@/domains/... - Shared 계층 (
)@/shared/... - 상대 경로 (
,./
)../
Barrel Export
- 각 디렉토리(
,components
등)에hooks
를 두어 외부에서 깔끔하게 import 하도록 함.index.ts
Pages
| 경로 | 설명 |
|---|---|
| 랜딩 페이지 |
| 분석 요청 |
| 결과 (SSE 스트리밍) |
| 히스토리 (모달) |
API Layer Architecture
┌─────────────────────────────────────────────────┐ │ Component Layer │ │ useSuspenseQuery → AsyncBoundary 감싸기 │ └─────────────────┬───────────────────────────────┘ │ ┌─────────────────▼───────────────────────────────┐ │ Query Hook Layer │ │ useAnalysis, useReport, useHistory │ │ - useMyQuery.getQueryKeys() 메서드 포함 │ └─────────────────┬───────────────────────────────┘ │ ┌─────────────────▼───────────────────────────────┐ │ API Function Layer │ │ analysisApi.create(), reportApi.get() │ │ - ky 인스턴스 사용 │ │ - 커스텀 에러 코드 파싱 │ └─────────────────┬───────────────────────────────┘ │ ┌─────────────────▼───────────────────────────────┐ │ Route Config Layer │ │ /lib/api/routes.ts │ │ API_ROUTES.ANALYZE, API_ROUTES.REPORT │ └─────────────────────────────────────────────────┘
React Query 패턴
// 쿼리 훅 생성 패턴 (Object.assign) const getAnalysisKey = (id: string) => ["analysis", id]; export const useAnalysis = Object.assign( (id: string) => { return useSuspenseQuery({ queryKey: getAnalysisKey(id), queryFn: () => getAnalysis(id), }); }, { getQueryKey: getAnalysisKey, }, ); // 사용 const { data } = useAnalysis(id); const queryKey = useAnalysis.getQueryKey(id);
상태 관리 (State Management)
- URL as State:
,id
,tab
등 공유 가능한 상태는 URL Query Parameter로 관리한다.filter - No Prop Drilling: URL 파라미터나 React Query 캐시 데이터는 부모에서 자식으로 props로 전달하지 않고, 자식 컴포넌트에서 직접 hook을 호출하여 가져온다.
- 이를 통해 컴포넌트 결합도를 낮추고 독립성을 높인다.
- 예:
에서AnalysisView
를 props로 받지 않고id
로 직접 조회.useSearchParams
선언적 JSX (Declarative JSX)
- If Component: 조건부 렌더링에 삼항 연산자(
)나 논리 연산자(condition ? A : B
) 대신&&
컴포넌트를 사용한다.<If /><If condition={isLoading} ifTrue={<LoadingView />} ifFalse={<ContentView />} /> - Switch/Case: 복잡한 조건 분기는 IIFE와 switch 문을 사용하여 선언적으로 표현한다.
유틸리티 라이브러리
- es-toolkit: 성능과 번들 사이즈 최적화를 위해
대신lodash
을 사용한다.es-toolkit
,isEmpty
등 일반적인 유틸리티 함수는get
또는es-toolkit/compat
에서 가져와 사용한다.es-toolkit
AsyncBoundary
Suspense + ErrorBoundary 통합 컴포넌트
<AsyncBoundary fallback={<Skeleton />} errorFallback={({ error, reset }) => ( <ErrorUI error={error} onRetry={reset} /> )} > <DataComponent /> </AsyncBoundary>
- useSuspenseQuery 우선 사용
- 적합하지 않은 경우에만 useQuery 사용
스켈레톤 UI
API 응답 대기 중 표시하는 로딩 상태
- 각 페이지/컴포넌트별 맞춤 스켈레톤
- 실제 콘텐츠와 동일한 레이아웃
접근성
- 스킬 참조:
.agent/skills/accessibility/SKILL.md - WCAG 2.1 AA 준수
- 키보드 네비게이션
- 스크린 리더 지원
Constraints
- 순수 유틸/훅 작성
- 사이드이펙트 필요 시 전용 훅으로 분리
- Shadcn UI 컴포넌트 우선 사용
- State Management: Props/State 끌어올리기(Hoisting)를 지양한다. React Query 캐시 또는 Path/Query Parameter에서 필요한 데이터를 하위 컴포넌트가 직접 가져와 사용(Colocation)한다.
- Declarative JSX:
이나renderFunction
객체를 사용한 렌더링을 지양한다. 조건부 로직이 복잡할 경우Map
이나 IIFE(즉시 실행 함수)를 사용하여 JSX 내에서 흐름을 명확히 한다.ts-pattern - Client Component 원칙: React Hook(예:
,useState
,useEffect
), 이벤트 핸들러, 또는 브라우저 전용 API(useSearchParams
,window
)를 사용하는 컴포넌트/유틸리티는 반드시 파일 최상단에document
를 명시해야 한다. 가능한 한 Leaf Node(말단 컴포넌트)로 Client Boundary를 미루어 Server Component의 이점을 살린다."use client"
비동기 Fallback
모든 비동기 작업에 대해 fallback UI 구성
// 페이지 레벨 <AsyncBoundary fallback={<PageSkeleton />}> <Page /> </AsyncBoundary> // 컴포넌트 레벨 <AsyncBoundary fallback={<CardSkeleton />}> <DataCard /> </AsyncBoundary> // 버튼 로딩 <Button disabled={isPending}> {isPending ? <Spinner /> : '분석 시작'} </Button>
애니메이션
부드러운 UX를 위한 전역 애니메이션 적용
| 요소 | 애니메이션 |
|---|---|
| 모달 | fadeIn/fadeOut + scale |
| 페이지 전환 | slide 또는 fade |
| 확인창 | fadeIn + slideUp |
| 드롭다운 | slideDown |
| 스켈레톤 | pulse |
// Framer Motion 또는 CSS transition <motion.div initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.95 }} > <ModalContent /> </motion.div>
반응형 디자인
모바일 우선 (Mobile First) 접근
| 브레이크포인트 | 범위 |
|---|---|
| sm | 640px |
| md | 768px |
| lg | 1024px |
| xl | 1280px |
// Tailwind 반응형 클래스 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> {items.map(item => <Card key={item.id} />)} </div> // 모바일 네비게이션 <nav className="hidden md:flex">Desktop Nav</nav> <nav className="md:hidden">Mobile Nav</nav>