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 폴더 제거.
5.9 KiB
5.9 KiB
id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
| id | title | category | status | source_trust_level | verification_status | created_at | updated_at | tags | tech_stack | applied_in | aliases | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| react-dnd-kit-patterns | dnd-kit — Drag & Drop / Sortable / 키보드 | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
dnd-kit
Modern React drag & drop. A11y (키보드) 일급, 가벼움, 모바일 OK. Sortable list / Kanban / Tree. react-dnd 보다 가볍고 modern.
📖 핵심 개념
- DndContext: provider.
- useDraggable / useDroppable: 기본 hook.
- SortableContext + useSortable: sortable list 자동.
- Sensor: pointer / keyboard / touch.
💻 코드 패턴
단순 drag/drop
import { DndContext, useDraggable, useDroppable } from '@dnd-kit/core';
function Draggable() {
const { attributes, listeners, setNodeRef, transform } = useDraggable({ id: 'a' });
const style = transform ? { transform: `translate3d(${transform.x}px, ${transform.y}px, 0)` } : undefined;
return <div ref={setNodeRef} style={style} {...attributes} {...listeners}>Drag me</div>;
}
function Droppable({ children }: ...) {
const { setNodeRef, isOver } = useDroppable({ id: 'zone' });
return <div ref={setNodeRef} style={{ background: isOver ? '#eef' : '#fff' }}>{children}</div>;
}
function App() {
return (
<DndContext onDragEnd={(e) => console.log(e.active.id, '→', e.over?.id)}>
<Draggable />
<Droppable>Drop here</Droppable>
</DndContext>
);
}
Sortable list
import { DndContext, closestCenter } from '@dnd-kit/core';
import { SortableContext, useSortable, arrayMove, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
function SortableItem({ id, children }: ...) {
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
const style = { transform: CSS.Transform.toString(transform), transition };
return <li ref={setNodeRef} style={style} {...attributes} {...listeners}>{children}</li>;
}
function List() {
const [items, setItems] = useState(['a', 'b', 'c']);
return (
<DndContext
collisionDetection={closestCenter}
onDragEnd={(e) => {
if (e.over && e.active.id !== e.over.id) {
setItems((it) => arrayMove(it, it.indexOf(e.active.id as string), it.indexOf(e.over!.id as string)));
}
}}
>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
<ul>{items.map(id => <SortableItem key={id} id={id}>{id}</SortableItem>)}</ul>
</SortableContext>
</DndContext>
);
}
Kanban (다중 column)
const [columns, setColumns] = useState({
todo: ['Task 1', 'Task 2'],
doing: ['Task 3'],
done: [],
});
function onDragEnd(e: DragEndEvent) {
const { active, over } = e;
if (!over) return;
const activeCol = findCol(columns, active.id);
const overCol = findCol(columns, over.id) ?? over.id; // column 자체에 drop 가능
if (activeCol === overCol) {
setColumns(c => ({ ...c, [activeCol]: arrayMove(c[activeCol],
c[activeCol].indexOf(active.id as string),
c[overCol].indexOf(over.id as string)) }));
} else {
setColumns(c => {
const fromIdx = c[activeCol].indexOf(active.id as string);
const toIdx = over.id === overCol ? c[overCol].length : c[overCol].indexOf(over.id as string);
const item = c[activeCol][fromIdx];
return {
...c,
[activeCol]: c[activeCol].filter((_, i) => i !== fromIdx),
[overCol]: [...c[overCol].slice(0, toIdx), item, ...c[overCol].slice(toIdx)],
};
});
}
}
Drag overlay (drag 중 ghost)
import { DragOverlay } from '@dnd-kit/core';
const [activeId, setActiveId] = useState<string | null>(null);
<DndContext
onDragStart={(e) => setActiveId(e.active.id as string)}
onDragEnd={(e) => { setActiveId(null); /* ... */ }}
>
<SortableContext items={items}>...</SortableContext>
<DragOverlay>
{activeId ? <ItemPreview id={activeId} /> : null}
</DragOverlay>
</DndContext>
Sensor (touch + keyboard)
import { useSensors, useSensor, PointerSensor, KeyboardSensor } from '@dnd-kit/core';
import { sortableKeyboardCoordinates } from '@dnd-kit/sortable';
const sensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
);
<DndContext sensors={sensors} ...>
→ Tab + Space + Arrow keys 자동.
Restrict modifier
import { restrictToVerticalAxis, restrictToParentElement } from '@dnd-kit/modifiers';
<DndContext modifiers={[restrictToVerticalAxis, restrictToParentElement]} ...>
Server sync (mutation)
function onDragEnd(e: DragEndEvent) {
setItems(...); // optimistic
api.reorder.mutate({ ids: newOrder })
.catch(() => setItems(prev)); // 실패 시 복원
}
🤔 의사결정 기준
| 상황 | 추천 |
|---|---|
| Sortable list | dnd-kit + SortableContext |
| Kanban | dnd-kit multi-container |
| Tree | dnd-kit tree (커뮤니티) |
| Native HTML5 DnD only | 직접 (간단 기능) |
| 큰 list (수천) | virtualized + dnd-kit |
| Native mobile | RN reanimated + gesture handler |
❌ 안티패턴
- Sensor 없이 mobile: touch 안 작동.
- A11y 무시 (키보드): 사용자 일부 못 씀.
- DragOverlay 없음 — sortable 시 lag: jumpy. overlay 권장.
- State update 동기 X: 즉시 update + 서버 비동기.
- Server reorder 후 refetch — 깜빡임: optimistic + rollback.
- id 가 unstable: 매 render 새 id — drag 깨짐.
🤖 LLM 활용 힌트
- DndContext + SortableContext + useSortable 3종.
- Sensor 항상 포함 (Pointer + Keyboard).
- DragOverlay 로 부드러움.