ux-toolkit 0.1.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +113 -7
  2. package/agents/card-reviewer.md +173 -0
  3. package/agents/comparison-reviewer.md +143 -0
  4. package/agents/density-reviewer.md +207 -0
  5. package/agents/detail-page-reviewer.md +143 -0
  6. package/agents/editor-reviewer.md +165 -0
  7. package/agents/form-reviewer.md +156 -0
  8. package/agents/game-ui-reviewer.md +181 -0
  9. package/agents/list-page-reviewer.md +132 -0
  10. package/agents/navigation-reviewer.md +145 -0
  11. package/agents/panel-reviewer.md +182 -0
  12. package/agents/replay-reviewer.md +174 -0
  13. package/agents/settings-reviewer.md +166 -0
  14. package/agents/ux-auditor.md +145 -45
  15. package/agents/ux-engineer.md +211 -38
  16. package/dist/cli.js +172 -5
  17. package/dist/cli.js.map +1 -1
  18. package/dist/index.cjs +172 -5
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +128 -4
  21. package/dist/index.d.ts +128 -4
  22. package/dist/index.js +172 -5
  23. package/dist/index.js.map +1 -1
  24. package/package.json +6 -4
  25. package/skills/canvas-grid-patterns/SKILL.md +367 -0
  26. package/skills/comparison-patterns/SKILL.md +354 -0
  27. package/skills/data-density-patterns/SKILL.md +493 -0
  28. package/skills/detail-page-patterns/SKILL.md +522 -0
  29. package/skills/drag-drop-patterns/SKILL.md +406 -0
  30. package/skills/editor-workspace-patterns/SKILL.md +552 -0
  31. package/skills/event-timeline-patterns/SKILL.md +542 -0
  32. package/skills/form-patterns/SKILL.md +608 -0
  33. package/skills/info-card-patterns/SKILL.md +531 -0
  34. package/skills/keyboard-shortcuts-patterns/SKILL.md +365 -0
  35. package/skills/list-page-patterns/SKILL.md +351 -0
  36. package/skills/modal-patterns/SKILL.md +750 -0
  37. package/skills/navigation-patterns/SKILL.md +476 -0
  38. package/skills/page-structure-patterns/SKILL.md +271 -0
  39. package/skills/playback-replay-patterns/SKILL.md +695 -0
  40. package/skills/react-ux-patterns/SKILL.md +434 -0
  41. package/skills/split-panel-patterns/SKILL.md +609 -0
  42. package/skills/status-visualization-patterns/SKILL.md +635 -0
  43. package/skills/toast-notification-patterns/SKILL.md +207 -0
  44. package/skills/turn-based-ui-patterns/SKILL.md +506 -0
@@ -0,0 +1,406 @@
1
+ ---
2
+ name: drag-drop-patterns
3
+ description: Drag and drop interactions, visual feedback, drop zones, and reordering patterns
4
+ license: MIT
5
+ ---
6
+
7
+ # Drag & Drop Patterns
8
+
9
+ Modern drag-and-drop UI patterns for complex interactions like equipment assignment, panel management, and list reordering. Based on dnd-kit, react-dnd, and WCAG 2.1 accessibility guidelines.
10
+
11
+ ## 1. Drag Initiation
12
+
13
+ ### Drag Handle Pattern
14
+ ```tsx
15
+ interface DraggableItemProps {
16
+ id: string;
17
+ children: React.ReactNode;
18
+ }
19
+
20
+ function DraggableItem({ id, children }: DraggableItemProps) {
21
+ const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ id });
22
+
23
+ return (
24
+ <div ref={setNodeRef} className={isDragging ? 'opacity-50' : ''}>
25
+ <button {...attributes} {...listeners} className="drag-handle cursor-grab active:cursor-grabbing p-2">
26
+ <GripVertical className="w-4 h-4 text-gray-400" /> {/* ⋮⋮ icon */}
27
+ </button>
28
+ <div>{children}</div>
29
+ </div>
30
+ );
31
+ }
32
+ ```
33
+
34
+ ### Activation Constraints
35
+ ```tsx
36
+ const sensors = useSensors(
37
+ useSensor(MouseSensor, {
38
+ activationConstraint: { distance: 10 } // 10px threshold prevents accidental drags
39
+ }),
40
+ useSensor(TouchSensor, {
41
+ activationConstraint: { delay: 250, tolerance: 5 } // Long press for touch
42
+ })
43
+ );
44
+ ```
45
+
46
+ ## 2. Drag Preview
47
+
48
+ ### Ghost Element with Custom Preview
49
+ ```tsx
50
+ function DragOverlayPreview({ item }: { item: Item }) {
51
+ return (
52
+ <DragOverlay>
53
+ {item ? (
54
+ <div className="bg-white border-2 border-blue-500 rounded-lg shadow-lg p-4 opacity-90">
55
+ {item.name}
56
+ {item.count > 1 && <Badge className="ml-2">{item.count}</Badge>}
57
+ </div>
58
+ ) : null}
59
+ </DragOverlay>
60
+ );
61
+ }
62
+ ```
63
+
64
+ ### Cursor & Visual States
65
+ ```css
66
+ .dragging { opacity: 0.5; transform: scale(0.95); }
67
+ .drag-handle { cursor: grab; }
68
+ .drag-handle:active { cursor: grabbing; }
69
+ ```
70
+
71
+ ## 3. Drop Zone Feedback
72
+
73
+ ### Valid/Invalid Drop Zones
74
+ ```tsx
75
+ function DropZone({ id, accepts }: DropZoneProps) {
76
+ const { isOver, setNodeRef } = useDroppable({ id, data: { accepts } });
77
+ const canDrop = useCanDrop(accepts); // Check if active item matches
78
+
79
+ return (
80
+ <div
81
+ ref={setNodeRef}
82
+ className={cn(
83
+ 'min-h-32 rounded-lg border-2 border-dashed transition-colors',
84
+ isOver && canDrop && 'border-green-500 bg-green-50',
85
+ isOver && !canDrop && 'border-red-500 bg-red-50',
86
+ !isOver && 'border-gray-300'
87
+ )}
88
+ >
89
+ {children}
90
+ </div>
91
+ );
92
+ }
93
+ ```
94
+
95
+ ### Cursor Feedback
96
+ ```tsx
97
+ const cursorClass = isOver
98
+ ? (canDrop ? 'cursor-copy' : 'cursor-not-allowed')
99
+ : 'cursor-default';
100
+ ```
101
+
102
+ ## 4. Drop Indicators
103
+
104
+ ### Insertion Line Between Items
105
+ ```tsx
106
+ function DropIndicator({ active }: { active: boolean }) {
107
+ if (!active) return null;
108
+
109
+ return (
110
+ <div className="relative h-0.5 my-2">
111
+ <div className="absolute inset-0 bg-blue-500" />
112
+ <div className="absolute -left-1 -top-1 w-2 h-2 rounded-full bg-blue-500" />
113
+ </div>
114
+ );
115
+ }
116
+
117
+ function SortableList({ items }: { items: Item[] }) {
118
+ const { over } = useDndContext();
119
+
120
+ return items.map((item, index) => (
121
+ <>
122
+ <DropIndicator active={over?.id === `gap-${index}`} />
123
+ <SortableItem key={item.id} id={item.id} />
124
+ </>
125
+ ));
126
+ }
127
+ ```
128
+
129
+ ### Placeholder for Empty Zones
130
+ ```tsx
131
+ {items.length === 0 && (
132
+ <div className="text-center py-8 text-gray-400">
133
+ Drop items here
134
+ </div>
135
+ )}
136
+ ```
137
+
138
+ ## 5. Drag Constraints
139
+
140
+ ### Axis Locking
141
+ ```tsx
142
+ const restrictToVerticalAxis: Modifier = ({ transform }) => ({
143
+ ...transform,
144
+ x: 0
145
+ });
146
+
147
+ <DndContext modifiers={[restrictToVerticalAxis]}>
148
+ ```
149
+
150
+ ### Containment & Boundaries
151
+ ```tsx
152
+ import { restrictToWindowEdges, restrictToParentElement } from '@dnd-kit/modifiers';
153
+
154
+ <DndContext modifiers={[restrictToWindowEdges]}>
155
+ ```
156
+
157
+ ### Snap to Grid
158
+ ```tsx
159
+ const snapToGrid: Modifier = ({ transform }) => ({
160
+ x: Math.round(transform.x / 20) * 20,
161
+ y: Math.round(transform.y / 20) * 20
162
+ });
163
+ ```
164
+
165
+ ## 6. Multi-Select Drag
166
+
167
+ ### Selected Items State
168
+ ```tsx
169
+ function MultiDraggable({ id, selected }: { id: string; selected: string[] }) {
170
+ const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
171
+ id,
172
+ data: { selectedIds: selected.includes(id) ? selected : [id] }
173
+ });
174
+
175
+ const count = selected.length > 1 ? selected.length : null;
176
+
177
+ return (
178
+ <div ref={setNodeRef} className={selected.includes(id) ? 'ring-2 ring-blue-500' : ''}>
179
+ {count && <Badge className="absolute -top-2 -right-2">{count}</Badge>}
180
+ <button {...attributes} {...listeners}>Drag</button>
181
+ </div>
182
+ );
183
+ }
184
+ ```
185
+
186
+ ## 7. Keyboard Alternative [CRITICAL]
187
+
188
+ ### Accessible Reordering
189
+ ```tsx
190
+ const keyboardSensor = useSensor(KeyboardSensor, {
191
+ coordinateGetter: sortableKeyboardCoordinates,
192
+ keyboardCodes: {
193
+ start: ['Space', 'Enter'],
194
+ cancel: ['Escape'],
195
+ end: ['Space', 'Enter'],
196
+ up: ['ArrowUp'],
197
+ down: ['ArrowDown']
198
+ }
199
+ });
200
+ ```
201
+
202
+ ### Screen Reader Instructions
203
+ ```tsx
204
+ <div
205
+ role="button"
206
+ tabIndex={0}
207
+ aria-describedby="drag-instructions"
208
+ {...attributes}
209
+ {...listeners}
210
+ >
211
+ <span id="drag-instructions" className="sr-only">
212
+ Press Space to pick up, Arrow keys to move, Space to drop, Escape to cancel
213
+ </span>
214
+ </div>
215
+ ```
216
+
217
+ ## 8. Cancel & Undo
218
+
219
+ ### Escape to Cancel
220
+ ```tsx
221
+ function DragContext() {
222
+ const [items, setItems] = useState(initialItems);
223
+ const [snapshot, setSnapshot] = useState<Item[]>([]);
224
+
225
+ const handleDragStart = () => setSnapshot([...items]);
226
+
227
+ const handleDragCancel = () => setItems(snapshot);
228
+
229
+ return (
230
+ <DndContext onDragStart={handleDragStart} onDragCancel={handleDragCancel}>
231
+ );
232
+ }
233
+ ```
234
+
235
+ ### Undo after Drop
236
+ ```tsx
237
+ const [history, setHistory] = useState<Item[][]>([]);
238
+
239
+ const handleDragEnd = (event: DragEndEvent) => {
240
+ setHistory(prev => [...prev, items]);
241
+ // Apply changes
242
+ };
243
+
244
+ const undo = () => {
245
+ if (history.length > 0) {
246
+ setItems(history[history.length - 1]);
247
+ setHistory(prev => prev.slice(0, -1));
248
+ }
249
+ };
250
+ ```
251
+
252
+ ## 9. Cross-Container Drag
253
+
254
+ ### Multi-Container Setup
255
+ ```tsx
256
+ function MultiContainer() {
257
+ const containers = ['panel-1', 'panel-2', 'panel-3'];
258
+
259
+ const handleDragEnd = (event: DragEndEvent) => {
260
+ const { active, over } = event;
261
+ if (!over) return;
262
+
263
+ const activeContainer = findContainer(active.id);
264
+ const overContainer = over.id;
265
+
266
+ if (activeContainer !== overContainer) {
267
+ moveItemBetweenContainers(active.id, activeContainer, overContainer);
268
+ }
269
+ };
270
+
271
+ return (
272
+ <DndContext onDragEnd={handleDragEnd}>
273
+ {containers.map(id => (
274
+ <DropZone key={id} id={id} />
275
+ ))}
276
+ </DndContext>
277
+ );
278
+ }
279
+ ```
280
+
281
+ ## 10. Touch Support
282
+
283
+ ### Touch Configuration
284
+ ```tsx
285
+ const touchSensor = useSensor(TouchSensor, {
286
+ activationConstraint: {
287
+ delay: 250, // Long press 250ms
288
+ tolerance: 5 // Allow 5px movement during press
289
+ }
290
+ });
291
+ ```
292
+
293
+ ### Touch Feedback
294
+ ```tsx
295
+ // Add haptic feedback on touch devices
296
+ const handleDragStart = () => {
297
+ if ('vibrate' in navigator) {
298
+ navigator.vibrate(50);
299
+ }
300
+ };
301
+ ```
302
+
303
+ ## 11. Code Examples
304
+
305
+ ### Complete useDragDrop Hook
306
+ ```tsx
307
+ interface UseDragDropOptions {
308
+ items: Item[];
309
+ onReorder: (items: Item[]) => void;
310
+ }
311
+
312
+ function useDragDrop({ items, onReorder }: UseDragDropOptions) {
313
+ const sensors = useSensors(
314
+ useSensor(MouseSensor, { activationConstraint: { distance: 10 } }),
315
+ useSensor(TouchSensor, { activationConstraint: { delay: 250, tolerance: 5 } }),
316
+ useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
317
+ );
318
+
319
+ const [activeId, setActiveId] = useState<string | null>(null);
320
+
321
+ const handleDragStart = (event: DragStartEvent) => setActiveId(event.active.id);
322
+
323
+ const handleDragEnd = (event: DragEndEvent) => {
324
+ const { active, over } = event;
325
+ if (over && active.id !== over.id) {
326
+ const oldIndex = items.findIndex(item => item.id === active.id);
327
+ const newIndex = items.findIndex(item => item.id === over.id);
328
+ onReorder(arrayMove(items, oldIndex, newIndex));
329
+ }
330
+ setActiveId(null);
331
+ };
332
+
333
+ return { sensors, activeId, handleDragStart, handleDragEnd };
334
+ }
335
+ ```
336
+
337
+ ### TypeScript Interfaces
338
+ ```tsx
339
+ interface DragItem {
340
+ id: string;
341
+ type: 'equipment' | 'weapon' | 'module';
342
+ data: unknown;
343
+ }
344
+
345
+ interface DropZoneProps {
346
+ id: string;
347
+ accepts: DragItem['type'][];
348
+ children: React.ReactNode;
349
+ onDrop?: (item: DragItem) => void;
350
+ }
351
+
352
+ interface DragEndEvent {
353
+ active: { id: string; data: { current: DragItem } };
354
+ over: { id: string; data: { current: { accepts: string[] } } } | null;
355
+ }
356
+ ```
357
+
358
+ ## 12. Audit Checklist
359
+
360
+ ### Drag Initiation
361
+ - [ ] [MAJOR] Drag handle is clearly visible (icon present)
362
+ - [ ] [MAJOR] Activation threshold prevents accidental drags (10px minimum)
363
+ - [ ] [MINOR] Cursor changes to `grab` on hover, `grabbing` on drag
364
+ - [ ] [MAJOR] Touch requires long-press (250ms minimum)
365
+
366
+ ### Visual Feedback
367
+ - [ ] [CRITICAL] Drag preview shows what's being dragged
368
+ - [ ] [MAJOR] Original item has reduced opacity during drag (0.5 or less)
369
+ - [ ] [MAJOR] Multi-select shows count badge
370
+ - [ ] [MINOR] Smooth transitions on drag start/end
371
+
372
+ ### Drop Zones
373
+ - [ ] [CRITICAL] Valid drop zones highlighted (border/background change)
374
+ - [ ] [CRITICAL] Invalid drop zones show "not-allowed" cursor
375
+ - [ ] [MAJOR] Drop indicator shows insertion point (horizontal line)
376
+ - [ ] [MINOR] Empty zones show placeholder text
377
+
378
+ ### Keyboard Accessibility
379
+ - [ ] [CRITICAL] Space/Enter to pick up and drop
380
+ - [ ] [CRITICAL] Arrow keys to move
381
+ - [ ] [CRITICAL] Escape to cancel
382
+ - [ ] [CRITICAL] Screen reader instructions provided (aria-describedby)
383
+ - [ ] [MAJOR] Focus visible during keyboard drag
384
+
385
+ ### Cross-Container
386
+ - [ ] [MAJOR] Items can move between containers
387
+ - [ ] [MAJOR] Invalid containers reject drops
388
+ - [ ] [MINOR] Visual feedback when hovering over target container
389
+
390
+ ### Touch Support
391
+ - [ ] [MAJOR] Long-press activates drag on touch (250ms)
392
+ - [ ] [MINOR] Haptic feedback on drag start (if supported)
393
+ - [ ] [MAJOR] Touch tolerance prevents scroll conflict (5px)
394
+
395
+ ### Cancel & Error Recovery
396
+ - [ ] [MAJOR] Escape cancels drag and restores position
397
+ - [ ] [MINOR] Undo available after drop
398
+ - [ ] [MAJOR] Invalid drops don't corrupt state
399
+
400
+ ### Performance
401
+ - [ ] [MINOR] Smooth 60fps drag animation
402
+ - [ ] [MINOR] Large lists use virtualization if >100 items
403
+ - [ ] [MINOR] Preview uses lightweight component
404
+
405
+ **Critical Path**: Keyboard accessibility + valid/invalid drop zones + drag preview
406
+ **Total Severity Score**: 9 Critical, 11 Major, 8 Minor