ux-toolkit 0.1.0 → 0.4.1

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,476 @@
1
+ ---
2
+ name: navigation-patterns
3
+ description: Sidebar navigation, mobile drawers, breadcrumbs, and app shell patterns for React/Next.js applications
4
+ license: MIT
5
+ ---
6
+
7
+ # Navigation Design Patterns
8
+
9
+ ## Collapsible Sidebar
10
+
11
+ ### Desktop Sidebar with Collapse Toggle
12
+ ```tsx
13
+ interface SidebarProps {
14
+ isCollapsed: boolean;
15
+ setIsCollapsed: (collapsed: boolean) => void;
16
+ }
17
+
18
+ function Sidebar({ isCollapsed, setIsCollapsed }: SidebarProps) {
19
+ const router = useRouter();
20
+
21
+ const isPathActive = (href: string) => {
22
+ if (href === '/') return router.pathname === '/';
23
+ return router.pathname.startsWith(href);
24
+ };
25
+
26
+ return (
27
+ <aside
28
+ className={`
29
+ bg-surface-deep border-r border-border
30
+ fixed inset-y-0 left-0 z-30
31
+ flex flex-col
32
+ ${isCollapsed ? 'w-16' : 'w-56'}
33
+ transition-[width] duration-300 ease-in-out
34
+ `}
35
+ >
36
+ {/* Header / Brand */}
37
+ <div className={`
38
+ flex items-center h-14 border-b border-border
39
+ ${isCollapsed ? 'justify-center px-2' : 'px-4 gap-3'}
40
+ `}>
41
+ <Logo className="w-8 h-8 flex-shrink-0" />
42
+ {!isCollapsed && (
43
+ <div className="flex-1 min-w-0">
44
+ <span className="text-base font-bold">AppName</span>
45
+ <span className="block text-xs text-text-muted">Subtitle</span>
46
+ </div>
47
+ )}
48
+
49
+ {/* Collapse toggle */}
50
+ <button
51
+ onClick={() => setIsCollapsed(!isCollapsed)}
52
+ className="p-1.5 rounded-lg text-text-secondary hover:text-white hover:bg-surface-raised"
53
+ aria-label={isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
54
+ >
55
+ {isCollapsed ? <ChevronRightIcon /> : <ChevronLeftIcon />}
56
+ </button>
57
+ </div>
58
+
59
+ {/* Navigation */}
60
+ <nav className="flex-1 overflow-y-auto py-4">
61
+ <NavSection title="Browse" isCollapsed={isCollapsed}>
62
+ <NavItem href="/items" icon={<ItemsIcon />} label="Items" isCollapsed={isCollapsed} isActive={isPathActive('/items')} />
63
+ <NavItem href="/catalog" icon={<CatalogIcon />} label="Catalog" isCollapsed={isCollapsed} isActive={isPathActive('/catalog')} />
64
+ </NavSection>
65
+
66
+ <NavSection title="Tools" isCollapsed={isCollapsed}>
67
+ <NavItem href="/editor" icon={<EditorIcon />} label="Editor" isCollapsed={isCollapsed} isActive={isPathActive('/editor')} />
68
+ </NavSection>
69
+ </nav>
70
+
71
+ {/* Footer */}
72
+ <div className={`border-t border-border py-3 ${isCollapsed ? 'px-2 text-center' : 'px-4'}`}>
73
+ {isCollapsed ? (
74
+ <span className="text-xs text-text-muted">v1.0</span>
75
+ ) : (
76
+ <div className="flex items-center justify-between text-xs text-text-muted">
77
+ <span>v1.0.0</span>
78
+ <a href="https://github.com" className="hover:text-accent">
79
+ <GithubIcon />
80
+ </a>
81
+ </div>
82
+ )}
83
+ </div>
84
+ </aside>
85
+ );
86
+ }
87
+ ```
88
+
89
+ ### Navigation Item Component
90
+ ```tsx
91
+ interface NavItemProps {
92
+ href: string;
93
+ icon: ReactElement;
94
+ label: string;
95
+ isCollapsed: boolean;
96
+ isActive: boolean;
97
+ onClick?: () => void;
98
+ }
99
+
100
+ function NavItem({ href, icon, label, isCollapsed, isActive, onClick }: NavItemProps) {
101
+ return (
102
+ <Link href={href}>
103
+ <a
104
+ onClick={onClick}
105
+ className={`
106
+ relative flex items-center px-3 py-2.5 mx-2 rounded-lg
107
+ transition-all duration-150 group
108
+ ${isCollapsed ? 'justify-center' : 'gap-3'}
109
+ ${isActive
110
+ ? 'bg-accent/15 text-accent'
111
+ : 'text-text-secondary hover:text-white hover:bg-surface-raised/50'
112
+ }
113
+ `}
114
+ >
115
+ {/* Active indicator - left border */}
116
+ {isActive && (
117
+ <div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-6 bg-accent rounded-r-full" />
118
+ )}
119
+
120
+ {/* Icon */}
121
+ <span className={`flex-shrink-0 ${isActive ? 'text-accent' : ''}`}>
122
+ {icon}
123
+ </span>
124
+
125
+ {/* Label */}
126
+ {!isCollapsed && (
127
+ <span className="text-sm font-medium truncate">{label}</span>
128
+ )}
129
+
130
+ {/* Tooltip for collapsed state */}
131
+ {isCollapsed && (
132
+ <div className="absolute left-full ml-2 px-2 py-1 bg-surface-base text-white text-sm rounded shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all z-50 whitespace-nowrap border border-border">
133
+ {label}
134
+ </div>
135
+ )}
136
+ </a>
137
+ </Link>
138
+ );
139
+ }
140
+ ```
141
+
142
+ ### Navigation Section Component
143
+ ```tsx
144
+ function NavSection({ title, children, isCollapsed }: { title?: string; children: React.ReactNode; isCollapsed: boolean }) {
145
+ return (
146
+ <div className="mb-2">
147
+ {title && !isCollapsed && (
148
+ <div className="px-3 py-2">
149
+ <span className="text-xs font-semibold uppercase tracking-wider text-text-muted">
150
+ {title}
151
+ </span>
152
+ </div>
153
+ )}
154
+ {!isCollapsed && title && <div className="mx-4 border-t border-border mb-4" />}
155
+ <div className="space-y-0.5">
156
+ {children}
157
+ </div>
158
+ </div>
159
+ );
160
+ }
161
+ ```
162
+
163
+ ## Mobile Sidebar Drawer
164
+
165
+ ### Mobile Sidebar with Overlay
166
+ ```tsx
167
+ // Store for mobile sidebar state (Zustand example)
168
+ const useMobileSidebarStore = create((set) => ({
169
+ isOpen: false,
170
+ open: () => set({ isOpen: true }),
171
+ close: () => set({ isOpen: false }),
172
+ }));
173
+
174
+ function MobileSidebar({ children }) {
175
+ const { isOpen, close } = useMobileSidebarStore();
176
+
177
+ return (
178
+ <>
179
+ {/* Backdrop overlay */}
180
+ {isOpen && (
181
+ <div
182
+ className="fixed inset-0 bg-black/50 z-40 lg:hidden transition-opacity duration-300"
183
+ onClick={close}
184
+ aria-hidden="true"
185
+ />
186
+ )}
187
+
188
+ {/* Sidebar drawer */}
189
+ <aside
190
+ className={`
191
+ fixed inset-y-0 left-0 z-50 w-64
192
+ bg-surface-deep border-r border-border
193
+ transform transition-transform duration-300 ease-in-out
194
+ ${isOpen ? 'translate-x-0' : '-translate-x-full'}
195
+ lg:hidden
196
+ `}
197
+ >
198
+ {children}
199
+ </aside>
200
+ </>
201
+ );
202
+ }
203
+ ```
204
+
205
+ ### Mobile Header with Menu Button
206
+ ```tsx
207
+ function MobileHeader({ title }) {
208
+ const openMobileSidebar = useMobileSidebarStore((s) => s.open);
209
+
210
+ return (
211
+ <header className="lg:hidden flex items-center justify-between h-12 px-3 bg-surface-base border-b border-border">
212
+ <span className="text-sm font-semibold text-white truncate">
213
+ {title}
214
+ </span>
215
+ <button
216
+ onClick={openMobileSidebar}
217
+ className="p-2 -mr-2 rounded-lg text-text-secondary hover:text-white hover:bg-surface-raised/50"
218
+ aria-label="Open navigation menu"
219
+ >
220
+ <HamburgerIcon />
221
+ </button>
222
+ </header>
223
+ );
224
+ }
225
+ ```
226
+
227
+ ### Auto-Close on Navigation
228
+ ```tsx
229
+ // In _app.tsx or layout
230
+ function App({ Component, pageProps }) {
231
+ const router = useRouter();
232
+ const closeMobileSidebar = useMobileSidebarStore((s) => s.close);
233
+
234
+ // Close mobile drawer on route change
235
+ useEffect(() => {
236
+ const handleRouteChange = () => {
237
+ if (window.innerWidth < 1024) {
238
+ closeMobileSidebar();
239
+ }
240
+ };
241
+
242
+ router.events.on('routeChangeComplete', handleRouteChange);
243
+ return () => router.events.off('routeChangeComplete', handleRouteChange);
244
+ }, [router.events, closeMobileSidebar]);
245
+
246
+ return <Component {...pageProps} />;
247
+ }
248
+ ```
249
+
250
+ ## App Shell Layout
251
+
252
+ ### Responsive Layout with Sidebar
253
+ ```tsx
254
+ interface LayoutProps {
255
+ children: ReactNode;
256
+ title?: string;
257
+ sidebarComponent?: ReactNode;
258
+ isSidebarCollapsed?: boolean;
259
+ }
260
+
261
+ function Layout({ children, title, sidebarComponent, isSidebarCollapsed }: LayoutProps) {
262
+ // Calculate main content margin based on sidebar state
263
+ const hasSidebar = !!sidebarComponent;
264
+ const contentMargin = hasSidebar
265
+ ? (isSidebarCollapsed ? 'lg:ml-16' : 'lg:ml-56')
266
+ : '';
267
+
268
+ return (
269
+ <>
270
+ <Head>
271
+ <title>{title}</title>
272
+ </Head>
273
+
274
+ <div className="flex flex-col h-screen bg-surface-deep overflow-hidden">
275
+ {/* Mobile header - only on mobile */}
276
+ {hasSidebar && <MobileHeader title={title} />}
277
+
278
+ <div className="flex flex-1 overflow-hidden">
279
+ {/* Sidebar - fixed position */}
280
+ {sidebarComponent}
281
+
282
+ {/* Main content */}
283
+ <main className={`flex-1 overflow-auto ${contentMargin} transition-all duration-300`}>
284
+ {children}
285
+ </main>
286
+ </div>
287
+ </div>
288
+ </>
289
+ );
290
+ }
291
+ ```
292
+
293
+ ## Breadcrumbs
294
+
295
+ ### Breadcrumb Component
296
+ ```tsx
297
+ interface BreadcrumbItem {
298
+ label: string;
299
+ href?: string;
300
+ }
301
+
302
+ interface BreadcrumbsProps {
303
+ items: BreadcrumbItem[];
304
+ }
305
+
306
+ function Breadcrumbs({ items }: BreadcrumbsProps) {
307
+ return (
308
+ <nav aria-label="Breadcrumb" className="mb-4">
309
+ <ol className="flex items-center gap-2 text-sm">
310
+ {items.map((item, index) => {
311
+ const isLast = index === items.length - 1;
312
+
313
+ return (
314
+ <li key={index} className="flex items-center gap-2">
315
+ {index > 0 && (
316
+ <ChevronRightIcon className="w-4 h-4 text-text-muted" />
317
+ )}
318
+
319
+ {isLast || !item.href ? (
320
+ <span className={isLast ? 'text-white font-medium' : 'text-text-secondary'}>
321
+ {item.label}
322
+ </span>
323
+ ) : (
324
+ <Link href={item.href}>
325
+ <a className="text-text-secondary hover:text-accent transition-colors">
326
+ {item.label}
327
+ </a>
328
+ </Link>
329
+ )}
330
+ </li>
331
+ );
332
+ })}
333
+ </ol>
334
+ </nav>
335
+ );
336
+ }
337
+
338
+ // Usage
339
+ <Breadcrumbs items={[
340
+ { label: 'Home', href: '/' },
341
+ { label: 'Campaigns', href: '/campaigns' },
342
+ { label: 'Operation Serpent' } // Current page, no href
343
+ ]} />
344
+ ```
345
+
346
+ ## Quick Navigation Tags
347
+
348
+ ### Settings-Style Quick Nav
349
+ ```tsx
350
+ interface QuickNavProps {
351
+ sections: { id: string; title: string; icon: ReactNode }[];
352
+ activeSection: string;
353
+ onNavigate: (sectionId: string) => void;
354
+ }
355
+
356
+ function QuickNavigation({ sections, activeSection, onNavigate }: QuickNavProps) {
357
+ return (
358
+ <div className="sticky top-0 z-10 bg-surface-deep/95 backdrop-blur-sm border-b border-border -mx-4 px-4 py-3 mb-4">
359
+ <div className="flex items-center gap-2 flex-wrap">
360
+ <span className="text-xs text-text-muted mr-1">Jump to:</span>
361
+ {sections.map((section) => {
362
+ const isActive = activeSection === section.id;
363
+ return (
364
+ <button
365
+ key={section.id}
366
+ onClick={() => onNavigate(section.id)}
367
+ className={`
368
+ inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-md
369
+ transition-all duration-150
370
+ ${isActive
371
+ ? 'bg-accent/20 text-accent border border-accent/30'
372
+ : 'bg-surface-raised/50 text-text-secondary border border-border hover:bg-surface-raised hover:text-white'
373
+ }
374
+ `}
375
+ >
376
+ {section.icon}
377
+ {section.title}
378
+ </button>
379
+ );
380
+ })}
381
+ </div>
382
+ </div>
383
+ );
384
+ }
385
+ ```
386
+
387
+ ### Scrolling to Section with URL Hash
388
+ ```tsx
389
+ function useHashNavigation(sections: string[]) {
390
+ const [activeSection, setActiveSection] = useState(sections[0]);
391
+ const sectionRefs = useRef<Record<string, HTMLElement | null>>({});
392
+
393
+ // Parse hash on mount
394
+ useEffect(() => {
395
+ const hash = window.location.hash.replace('#', '');
396
+ if (hash && sections.includes(hash)) {
397
+ setActiveSection(hash);
398
+ setTimeout(() => {
399
+ sectionRefs.current[hash]?.scrollIntoView({ behavior: 'smooth', block: 'start' });
400
+ }, 100);
401
+ }
402
+
403
+ const onHashChange = () => {
404
+ const newHash = window.location.hash.replace('#', '');
405
+ if (newHash && sections.includes(newHash)) {
406
+ setActiveSection(newHash);
407
+ sectionRefs.current[newHash]?.scrollIntoView({ behavior: 'smooth', block: 'start' });
408
+ }
409
+ };
410
+
411
+ window.addEventListener('hashchange', onHashChange);
412
+ return () => window.removeEventListener('hashchange', onHashChange);
413
+ }, [sections]);
414
+
415
+ const navigateToSection = useCallback((sectionId: string) => {
416
+ window.history.pushState(null, '', `#${sectionId}`);
417
+ setActiveSection(sectionId);
418
+ setTimeout(() => {
419
+ sectionRefs.current[sectionId]?.scrollIntoView({ behavior: 'smooth', block: 'start' });
420
+ }, 50);
421
+ }, []);
422
+
423
+ const createSectionRef = (id: string) => (el: HTMLElement | null) => {
424
+ sectionRefs.current[id] = el;
425
+ };
426
+
427
+ return { activeSection, navigateToSection, createSectionRef };
428
+ }
429
+ ```
430
+
431
+ ## Sidebar Navigation Best Practices
432
+
433
+ ### Do's
434
+ - Use clear, recognizable icons
435
+ - Show tooltips when collapsed
436
+ - Indicate active state clearly (color + left border)
437
+ - Auto-close mobile drawer on navigation
438
+ - Persist collapsed state to localStorage
439
+ - Use section dividers for grouping
440
+
441
+ ### Don'ts
442
+ - Don't nest more than 2 levels deep
443
+ - Don't hide important navigation in submenus
444
+ - Don't forget mobile experience
445
+ - Don't animate width changes too slowly (>300ms feels sluggish)
446
+ - Don't use horizontal scrolling in sidebar
447
+
448
+ ### Accessibility
449
+ - Use `aria-label` on collapse/expand buttons
450
+ - Ensure focus is visible in all states
451
+ - Support keyboard navigation (Tab, Enter, Escape)
452
+ - Announce state changes to screen readers
453
+ - Use semantic HTML (nav, ul, li)
454
+
455
+ ## Audit Checklist for Navigation
456
+
457
+ ### Critical (Must Fix)
458
+ - [ ] All main sections have nav items - users can't find features
459
+ - [ ] Active state is clear - users don't know where they are
460
+ - [ ] Mobile navigation accessible - mobile users blocked
461
+ - [ ] Keyboard navigable (Tab, Enter) - accessibility violation
462
+ - [ ] Uses semantic HTML (nav element) - accessibility violation
463
+
464
+ ### Major (Should Fix)
465
+ - [ ] Icons are recognizable - confusion about meaning
466
+ - [ ] Tooltips when collapsed - can't identify items
467
+ - [ ] Mobile drawer closes on navigation - users get stuck
468
+ - [ ] Breadcrumbs on nested pages - users lose context
469
+ - [ ] URL updates reflect navigation state - can't share deep links
470
+ - [ ] Active detection works for nested routes - wrong item highlighted
471
+
472
+ ### Minor (Nice to Have)
473
+ - [ ] Collapse state persisted to localStorage - convenience
474
+ - [ ] Section dividers group related items - visual organization
475
+ - [ ] Animations are smooth (<300ms) - polish
476
+ - [ ] Footer shows version/links - helpful info