9148c358d0
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를 Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류. - 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로 자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거, 동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거. - 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming, Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business, Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로, 나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는 title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백). 원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지. - 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서. - 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는 지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지. - Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경. - 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
7.9 KiB
7.9 KiB
id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
| id | title | category | status | canonical_id | aliases | duplicate_of | source_trust_level | confidence_score | verification_status | tags | raw_sources | last_reinforced | github_commit | tech_stack | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| wiki-2026-0508-compound-components | Compound Components | 10_Wiki/Topics | verified | self |
|
none | A | 0.95 | applied |
|
2026-05-10 | pending |
|
Compound Components
매 한 줄
"매 parent 가 매 child 들과 implicit state share — 매 expressive composable API.". Compound Component Pattern은 React (그리고 Vue/Solid) 에서
<Tabs><Tab/><Tab.Panel/></Tabs>류의 declarative API 를 만드는 표준. Radix UI, Headless UI, Ariakit, shadcn/ui 의 dominant pattern. 2026년 React 19 + Server Components 시대에도 그대로 valid.
매 핵심
매 problem solved
- 단일 monolithic component 의 props explosion (
<Tabs items={...} renderTab={...} ...>). - 매 caller 가 매 children 의 layout/order 를 control 의 X.
- Slot composition 의 부재.
매 solution
- Parent 가 Context 로 shared state 의 publish.
- Children 이 Context 를 consume — explicit prop drilling 의 X.
- Children 의 component 가 namespace pattern (
Tabs.Tab) 또는 separate exports.
매 patterns
- React Context based (most common 2026).
React.Childrencloning (legacy, fragile with refs/server components).- State reducer (advanced — caller customizes reducer).
asChildslot (Radix — wraps user element).
매 응용
- Tabs, Accordion, Disclosure.
- Menu, Dropdown, Combobox.
- Dialog, Drawer, Popover.
- Form fields with label/error/description.
- DataTable column children.
💻 패턴
Context-based Tabs
import { createContext, useContext, useId, useState } from 'react';
type TabsContextValue = {
activeId: string;
setActiveId: (id: string) => void;
baseId: string;
};
const TabsContext = createContext<TabsContextValue | null>(null);
function useTabs() {
const ctx = useContext(TabsContext);
if (!ctx) throw new Error('Tabs.* must be used inside <Tabs>');
return ctx;
}
export function Tabs({
defaultValue,
children,
}: { defaultValue: string; children: React.ReactNode }) {
const [activeId, setActiveId] = useState(defaultValue);
const baseId = useId();
return (
<TabsContext.Provider value={{ activeId, setActiveId, baseId }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
function List({ children }: { children: React.ReactNode }) {
return <div role="tablist" className="flex gap-2">{children}</div>;
}
function Trigger({ value, children }: { value: string; children: React.ReactNode }) {
const { activeId, setActiveId, baseId } = useTabs();
const selected = activeId === value;
return (
<button
role="tab"
id={`${baseId}-trigger-${value}`}
aria-selected={selected}
aria-controls={`${baseId}-panel-${value}`}
tabIndex={selected ? 0 : -1}
onClick={() => setActiveId(value)}
className={selected ? 'tab tab-active' : 'tab'}
>
{children}
</button>
);
}
function Panel({ value, children }: { value: string; children: React.ReactNode }) {
const { activeId, baseId } = useTabs();
if (activeId !== value) return null;
return (
<div
role="tabpanel"
id={`${baseId}-panel-${value}`}
aria-labelledby={`${baseId}-trigger-${value}`}
>
{children}
</div>
);
}
Tabs.List = List;
Tabs.Trigger = Trigger;
Tabs.Panel = Panel;
Usage
<Tabs defaultValue="overview">
<Tabs.List>
<Tabs.Trigger value="overview">Overview</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
</Tabs.List>
<Tabs.Panel value="overview"><OverviewView /></Tabs.Panel>
<Tabs.Panel value="settings"><SettingsView /></Tabs.Panel>
</Tabs>
Controlled variant
type Props = {
value?: string;
defaultValue?: string;
onValueChange?: (value: string) => void;
children: React.ReactNode;
};
export function Tabs({ value, defaultValue, onValueChange, children }: Props) {
const [internal, setInternal] = useState(defaultValue ?? '');
const isControlled = value !== undefined;
const activeId = isControlled ? value : internal;
const setActiveId = (next: string) => {
if (!isControlled) setInternal(next);
onValueChange?.(next);
};
// ...rest
}
asChild slot pattern (Radix)
import { Slot } from '@radix-ui/react-slot';
function Trigger({ asChild, children, value }: {
asChild?: boolean;
value: string;
children: React.ReactNode;
}) {
const { setActiveId } = useTabs();
const Comp = asChild ? Slot : 'button';
return (
<Comp onClick={() => setActiveId(value)} role="tab">
{children}
</Comp>
);
}
// Usage: customize button as anchor
<Tabs.Trigger value="x" asChild>
<a href="#x">Section X</a>
</Tabs.Trigger>
Form field compound
const FieldContext = createContext<{ id: string; errorId: string } | null>(null);
export function Field({ children }: { children: React.ReactNode }) {
const id = useId();
return (
<FieldContext.Provider value={{ id, errorId: `${id}-error` }}>
<div className="field">{children}</div>
</FieldContext.Provider>
);
}
function Label({ children }: { children: React.ReactNode }) {
const { id } = useContext(FieldContext)!;
return <label htmlFor={id}>{children}</label>;
}
function Input(props: React.InputHTMLAttributes<HTMLInputElement>) {
const { id, errorId } = useContext(FieldContext)!;
return <input id={id} aria-describedby={errorId} {...props} />;
}
function Error({ children }: { children: React.ReactNode }) {
const { errorId } = useContext(FieldContext)!;
return <span id={errorId} role="alert">{children}</span>;
}
Field.Label = Label;
Field.Input = Input;
Field.Error = Error;
Server Components note
// Compound components 의 Context — Client Component only.
// 매 Server Component layout 에서:
// <Tabs> ← Client Component (provides context)
// <Tabs.Panel value="x"> ← Client (consumes context)
// <ServerOnlyData /> ← can be Server Component child
// </Tabs.Panel>
// </Tabs>
'use client';
매 결정 기준
| 상황 | Approach |
|---|---|
| Standard widgets (tabs, menu) | Context-based compound |
| Wrap user element | asChild + Slot |
| Caller controls state | Controlled variant |
| Behavior-only library | Headless compound (Radix style) |
| Server Components mixed | 'use client' on the parent + child triggers |
기본값: Context-based, namespace exports (Tabs.Trigger), controlled+uncontrolled support, useId for a11y, useContext 로 misuse 방지.
🔗 Graph
- 부모: Component-Based Architecture (CBA) · React Patterns
- 변형: Headless UI · Render_Props
- 응용: Component Library Architecture · Radix UI · shadcn-ui
- Adjacent: A11y · Modern_Web_Rendering_and_Optimization
🤖 LLM 활용
언제: building reusable widgets (tabs/menu/dialog), design system primitives, replacing prop-explosion components, headless library. 언제 X: 1-shot static UI, simple presentational components, server-only components (Context 의 X).
❌ 안티패턴
React.Childrencloning: brittle with refs, breaks with fragments — Context 가 표준.- Missing context throw: child used standalone gives undefined error — explicit throw with helpful message.
- Over-namespacing: 5+ children types — split into multiple components.
- Forgetting a11y: tabs without role/aria-* — Radix-style attribute mapping 필수.
🧪 검증 / 중복
- Verified (Kent C. Dodds compound components / Radix UI source / React 19 docs / WAI-ARIA 1.2).
- 신뢰도 A.
🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — Context-based Tabs + asChild + form field 패턴 |