@geenius/tools 0.1.0 → 0.3.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 (177) hide show
  1. package/package.json +62 -3
  2. package/packages/convex/shared/README.md +1 -1
  3. package/packages/devtools/dist/index.d.ts +204 -0
  4. package/packages/devtools/dist/index.js +186 -0
  5. package/packages/devtools/dist/index.js.map +1 -0
  6. package/packages/devtools/react/README.md +1 -1
  7. package/packages/devtools/solidjs/README.md +1 -1
  8. package/packages/devtools/solidjs/dist/index.js +1830 -0
  9. package/packages/devtools/solidjs/dist/index.js.map +1 -0
  10. package/packages/env/dist/index.d.ts +151 -0
  11. package/packages/env/dist/index.js +93 -0
  12. package/packages/env/dist/index.js.map +1 -0
  13. package/packages/errors/dist/index.d.ts +177 -0
  14. package/packages/errors/dist/index.js +187 -0
  15. package/packages/errors/dist/index.js.map +1 -0
  16. package/packages/errors/react/README.md +1 -1
  17. package/packages/errors/solidjs/README.md +1 -1
  18. package/packages/logger/dist/index.d.ts +171 -0
  19. package/packages/logger/dist/index.js +216 -0
  20. package/packages/logger/dist/index.js.map +1 -0
  21. package/packages/logger/react/README.md +1 -1
  22. package/packages/logger/solidjs/README.md +1 -1
  23. package/packages/perf/dist/index.d.ts +168 -0
  24. package/packages/perf/dist/index.js +265 -0
  25. package/packages/perf/dist/index.js.map +1 -0
  26. package/packages/perf/react/README.md +1 -1
  27. package/packages/perf/solidjs/README.md +1 -1
  28. package/packages/shared/dist/index.d.ts +253 -0
  29. package/packages/shared/dist/index.js +278 -0
  30. package/packages/shared/dist/index.js.map +1 -0
  31. package/.changeset/config.json +0 -11
  32. package/.env.example +0 -2
  33. package/.github/CODEOWNERS +0 -1
  34. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -16
  35. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -11
  36. package/.github/PULL_REQUEST_TEMPLATE.md +0 -10
  37. package/.github/dependabot.yml +0 -11
  38. package/.github/workflows/ci.yml +0 -23
  39. package/.github/workflows/release.yml +0 -29
  40. package/.node-version +0 -1
  41. package/.nvmrc +0 -1
  42. package/.prettierrc +0 -7
  43. package/.project/ACCOUNT.yaml +0 -4
  44. package/.project/IDEAS.yaml +0 -7
  45. package/.project/PROJECT.yaml +0 -11
  46. package/.project/ROADMAP.yaml +0 -15
  47. package/CODE_OF_CONDUCT.md +0 -26
  48. package/CONTRIBUTING.md +0 -69
  49. package/SECURITY.md +0 -18
  50. package/SUPPORT.md +0 -14
  51. package/packages/convex/shared/package.json +0 -42
  52. package/packages/convex/shared/src/audit/index.ts +0 -5
  53. package/packages/convex/shared/src/audit/presets.ts +0 -165
  54. package/packages/convex/shared/src/audit/schema.ts +0 -85
  55. package/packages/convex/shared/src/audit/write.ts +0 -102
  56. package/packages/convex/shared/src/extract.ts +0 -75
  57. package/packages/convex/shared/src/index.ts +0 -41
  58. package/packages/convex/shared/src/messages.ts +0 -45
  59. package/packages/convex/shared/src/security.ts +0 -112
  60. package/packages/convex/shared/src/throw.ts +0 -184
  61. package/packages/convex/shared/src/types.ts +0 -57
  62. package/packages/convex/shared/src/utils.ts +0 -58
  63. package/packages/convex/shared/tsconfig.json +0 -28
  64. package/packages/convex/shared/tsup.config.ts +0 -12
  65. package/packages/devtools/package.json +0 -27
  66. package/packages/devtools/react/package.json +0 -53
  67. package/packages/devtools/react/src/components/DesignPreview.tsx +0 -59
  68. package/packages/devtools/react/src/components/DesignSwitcherDropdown.tsx +0 -99
  69. package/packages/devtools/react/src/components/DevSidebar.tsx +0 -247
  70. package/packages/devtools/react/src/components/DevToolbar.tsx +0 -242
  71. package/packages/devtools/react/src/components/GitHubIssueDialog.tsx +0 -402
  72. package/packages/devtools/react/src/components/InspectorOverlay.tsx +0 -312
  73. package/packages/devtools/react/src/components/PageLoadWaterfall.tsx +0 -144
  74. package/packages/devtools/react/src/components/PerformancePanel.tsx +0 -330
  75. package/packages/devtools/react/src/context/DevModeContext.tsx +0 -226
  76. package/packages/devtools/react/src/context/PerformanceContext.tsx +0 -143
  77. package/packages/devtools/react/src/data/designs.ts +0 -13
  78. package/packages/devtools/react/src/hooks/useGitHubLabels.ts +0 -47
  79. package/packages/devtools/react/src/hooks/useVirtualList.ts +0 -124
  80. package/packages/devtools/react/src/index.ts +0 -77
  81. package/packages/devtools/react/src/panels/ConvexSpy.tsx +0 -130
  82. package/packages/devtools/react/src/panels/DatabaseSeeder.tsx +0 -116
  83. package/packages/devtools/react/src/panels/DevModePhase2.tsx +0 -191
  84. package/packages/devtools/react/src/panels/DevModePhase3.tsx +0 -234
  85. package/packages/devtools/react/src/panels/FeatureFlagsToggle.tsx +0 -104
  86. package/packages/devtools/react/src/panels/QuickRouteJump.tsx +0 -152
  87. package/packages/devtools/react/src/services/github-service.ts +0 -247
  88. package/packages/devtools/react/tsconfig.json +0 -31
  89. package/packages/devtools/react/tsup.config.ts +0 -18
  90. package/packages/devtools/solidjs/package.json +0 -49
  91. package/packages/devtools/solidjs/src/components/DesignPreview.tsx +0 -51
  92. package/packages/devtools/solidjs/src/components/DesignSwitcherDropdown.tsx +0 -95
  93. package/packages/devtools/solidjs/src/components/DevSidebar.tsx +0 -247
  94. package/packages/devtools/solidjs/src/components/DevToolbar.tsx +0 -242
  95. package/packages/devtools/solidjs/src/components/GitHubIssueDialog.tsx +0 -400
  96. package/packages/devtools/solidjs/src/components/InspectorOverlay.tsx +0 -311
  97. package/packages/devtools/solidjs/src/components/PageLoadWaterfall.tsx +0 -144
  98. package/packages/devtools/solidjs/src/components/PerformancePanel.tsx +0 -330
  99. package/packages/devtools/solidjs/src/context/DevModeContext.tsx +0 -216
  100. package/packages/devtools/solidjs/src/context/PerformanceContext.tsx +0 -135
  101. package/packages/devtools/solidjs/src/data/designs.ts +0 -13
  102. package/packages/devtools/solidjs/src/hooks/createGitHubLabels.ts +0 -47
  103. package/packages/devtools/solidjs/src/index.ts +0 -64
  104. package/packages/devtools/solidjs/src/services/github-service.ts +0 -247
  105. package/packages/devtools/solidjs/tsconfig.json +0 -21
  106. package/packages/devtools/src/index.ts +0 -377
  107. package/packages/devtools/tsup.config.ts +0 -12
  108. package/packages/env/package.json +0 -30
  109. package/packages/env/src/index.ts +0 -264
  110. package/packages/env/tsup.config.ts +0 -12
  111. package/packages/errors/package.json +0 -27
  112. package/packages/errors/react/package.json +0 -72
  113. package/packages/errors/react/src/analytics.ts +0 -16
  114. package/packages/errors/react/src/components/ErrorBoundary.tsx +0 -248
  115. package/packages/errors/react/src/components/ErrorDisplay.tsx +0 -328
  116. package/packages/errors/react/src/components/ValidationErrors.tsx +0 -102
  117. package/packages/errors/react/src/config.ts +0 -199
  118. package/packages/errors/react/src/constants.ts +0 -74
  119. package/packages/errors/react/src/hooks/useErrorBoundary.ts +0 -92
  120. package/packages/errors/react/src/hooks/useErrorHandler.ts +0 -87
  121. package/packages/errors/react/src/index.ts +0 -96
  122. package/packages/errors/react/src/types.ts +0 -102
  123. package/packages/errors/react/src/utils/errorMessages.ts +0 -35
  124. package/packages/errors/react/src/utils/errorPolicy.ts +0 -139
  125. package/packages/errors/react/src/utils/extractAppError.ts +0 -174
  126. package/packages/errors/react/src/utils/formatError.ts +0 -112
  127. package/packages/errors/react/tsconfig.json +0 -25
  128. package/packages/errors/react/tsup.config.ts +0 -24
  129. package/packages/errors/solidjs/package.json +0 -46
  130. package/packages/errors/solidjs/src/components/ErrorDisplay.tsx +0 -179
  131. package/packages/errors/solidjs/src/config.ts +0 -98
  132. package/packages/errors/solidjs/src/hooks/createErrorHandler.ts +0 -107
  133. package/packages/errors/solidjs/src/index.ts +0 -61
  134. package/packages/errors/solidjs/src/types.ts +0 -34
  135. package/packages/errors/solidjs/src/utils/errorPolicy.ts +0 -56
  136. package/packages/errors/solidjs/src/utils/extractAppError.ts +0 -94
  137. package/packages/errors/solidjs/src/utils/formatError.ts +0 -33
  138. package/packages/errors/solidjs/tsconfig.json +0 -26
  139. package/packages/errors/solidjs/tsup.config.ts +0 -21
  140. package/packages/errors/src/index.ts +0 -320
  141. package/packages/errors/tsup.config.ts +0 -12
  142. package/packages/logger/package.json +0 -27
  143. package/packages/logger/react/package.json +0 -46
  144. package/packages/logger/react/src/index.ts +0 -4
  145. package/packages/logger/react/src/useMetrics.ts +0 -42
  146. package/packages/logger/react/src/usePerformanceLog.ts +0 -61
  147. package/packages/logger/react/tsconfig.json +0 -31
  148. package/packages/logger/react/tsup.config.ts +0 -12
  149. package/packages/logger/solidjs/package.json +0 -45
  150. package/packages/logger/solidjs/src/createMetrics.ts +0 -37
  151. package/packages/logger/solidjs/src/createPerformanceLog.ts +0 -58
  152. package/packages/logger/solidjs/src/index.ts +0 -4
  153. package/packages/logger/solidjs/tsconfig.json +0 -32
  154. package/packages/logger/solidjs/tsup.config.ts +0 -12
  155. package/packages/logger/src/index.ts +0 -363
  156. package/packages/logger/tsup.config.ts +0 -12
  157. package/packages/perf/package.json +0 -27
  158. package/packages/perf/react/package.json +0 -59
  159. package/packages/perf/react/src/components/PerformanceDashboard.tsx +0 -257
  160. package/packages/perf/react/src/hooks/useMonitoredQuery.ts +0 -89
  161. package/packages/perf/react/src/hooks/usePerformanceMetrics.ts +0 -78
  162. package/packages/perf/react/src/index.ts +0 -33
  163. package/packages/perf/react/src/services/PerformanceMonitor.ts +0 -313
  164. package/packages/perf/react/src/types.ts +0 -77
  165. package/packages/perf/react/tsconfig.json +0 -25
  166. package/packages/perf/react/tsup.config.ts +0 -19
  167. package/packages/perf/solidjs/package.json +0 -41
  168. package/packages/perf/solidjs/src/components/PerformanceDashboard.tsx +0 -207
  169. package/packages/perf/solidjs/src/hooks/createPerformanceMetrics.ts +0 -73
  170. package/packages/perf/solidjs/src/index.ts +0 -31
  171. package/packages/perf/solidjs/src/services/PerformanceMonitor.ts +0 -134
  172. package/packages/perf/solidjs/src/types.ts +0 -78
  173. package/packages/perf/solidjs/tsconfig.json +0 -26
  174. package/packages/perf/solidjs/tsup.config.ts +0 -14
  175. package/packages/perf/src/index.ts +0 -410
  176. package/packages/perf/tsup.config.ts +0 -12
  177. package/pnpm-workspace.yaml +0 -2
@@ -1,234 +0,0 @@
1
- import { useState, useEffect, useCallback, useMemo } from 'react'
2
-
3
- // ─── Accessibility Auditor ───────────────────────────────────
4
-
5
- interface A11yIssue {
6
- id: string
7
- impact: 'critical' | 'serious' | 'moderate' | 'minor'
8
- description: string
9
- element: string
10
- help: string
11
- helpUrl?: string
12
- }
13
-
14
- /**
15
- * Accessibility Auditor Panel — Runtime a11y checking.
16
- * Uses DOM inspection to detect common accessibility issues.
17
- */
18
- export function AccessibilityAuditor() {
19
- const [issues, setIssues] = useState<A11yIssue[]>([])
20
- const [isScanning, setIsScanning] = useState(false)
21
- const [filter, setFilter] = useState<string>('all')
22
-
23
- const runAudit = useCallback(() => {
24
- setIsScanning(true)
25
- const found: A11yIssue[] = []
26
- let id = 0
27
-
28
- // Check images without alt
29
- document.querySelectorAll('img:not([alt])').forEach(el => {
30
- found.push({
31
- id: `a11y_${id++}`, impact: 'critical',
32
- description: 'Image missing alt attribute',
33
- element: `<img src="${el.getAttribute('src')?.slice(0, 40)}">`,
34
- help: 'Add descriptive alt text to all images',
35
- })
36
- })
37
-
38
- // Check buttons without accessible names
39
- document.querySelectorAll('button').forEach(el => {
40
- if (!el.textContent?.trim() && !el.getAttribute('aria-label') && !el.getAttribute('title')) {
41
- found.push({
42
- id: `a11y_${id++}`, impact: 'critical',
43
- description: 'Button without accessible name',
44
- element: el.outerHTML.slice(0, 80),
45
- help: 'Add text content, aria-label, or title to buttons',
46
- })
47
- }
48
- })
49
-
50
- // Check form inputs without labels
51
- document.querySelectorAll('input, select, textarea').forEach(el => {
52
- const input = el as HTMLInputElement
53
- const hasLabel = input.id && document.querySelector(`label[for="${input.id}"]`)
54
- const hasAria = input.getAttribute('aria-label') || input.getAttribute('aria-labelledby')
55
- if (!hasLabel && !hasAria && input.type !== 'hidden') {
56
- found.push({
57
- id: `a11y_${id++}`, impact: 'serious',
58
- description: 'Form input without label',
59
- element: `<${el.tagName.toLowerCase()} type="${input.type}">`,
60
- help: 'Associate a <label> element or add aria-label',
61
- })
62
- }
63
- })
64
-
65
- // Check color contrast (simplified — check for very light text)
66
- document.querySelectorAll('*').forEach(el => {
67
- const style = getComputedStyle(el)
68
- const color = style.color
69
- const bg = style.backgroundColor
70
- if (color === 'rgb(255, 255, 255)' && bg === 'rgb(255, 255, 255)') {
71
- found.push({
72
- id: `a11y_${id++}`, impact: 'serious',
73
- description: 'Possible invisible text (white on white)',
74
- element: el.tagName.toLowerCase(),
75
- help: 'Ensure sufficient color contrast ratio (4.5:1 for normal text)',
76
- })
77
- }
78
- })
79
-
80
- // Check heading hierarchy
81
- const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'))
82
- let prevLevel = 0
83
- for (const h of headings) {
84
- const level = parseInt(h.tagName[1])
85
- if (level > prevLevel + 1 && prevLevel > 0) {
86
- found.push({
87
- id: `a11y_${id++}`, impact: 'moderate',
88
- description: `Heading level skipped (h${prevLevel} → h${level})`,
89
- element: `<${h.tagName.toLowerCase()}>${h.textContent?.slice(0, 30)}</${h.tagName.toLowerCase()}>`,
90
- help: 'Maintain proper heading hierarchy without skipping levels',
91
- })
92
- }
93
- prevLevel = level
94
- }
95
-
96
- // Check for multiple h1
97
- const h1s = document.querySelectorAll('h1')
98
- if (h1s.length > 1) {
99
- found.push({
100
- id: `a11y_${id++}`, impact: 'moderate',
101
- description: `Multiple h1 elements found (${h1s.length})`,
102
- element: '<h1> × ' + h1s.length,
103
- help: 'Use only one h1 per page',
104
- })
105
- }
106
-
107
- // Check links without href
108
- document.querySelectorAll('a:not([href])').forEach(el => {
109
- found.push({
110
- id: `a11y_${id++}`, impact: 'minor',
111
- description: 'Link without href attribute',
112
- element: el.outerHTML.slice(0, 60),
113
- help: 'Add href or use a <button> instead',
114
- })
115
- })
116
-
117
- setIssues(found)
118
- setIsScanning(false)
119
- }, [])
120
-
121
- const filtered = useMemo(() =>
122
- filter === 'all' ? issues : issues.filter(i => i.impact === filter)
123
- , [issues, filter])
124
-
125
- const impactColors: Record<string, string> = {
126
- critical: '#ef4444', serious: '#f59e0b', moderate: '#3b82f6', minor: '#64748b',
127
- }
128
-
129
- return (
130
- <div style={{ fontFamily: 'system-ui', fontSize: 13, color: '#e2e8f0', background: '#0f172a', borderRadius: 8, overflow: 'hidden' }}>
131
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 12px', background: '#1e293b', borderBottom: '1px solid #334155' }}>
132
- <span style={{ fontWeight: 700, fontSize: 13 }}>♿ Accessibility Auditor</span>
133
- <div style={{ display: 'flex', gap: 6 }}>
134
- <select value={filter} onChange={e => setFilter(e.target.value)} style={{ padding: '4px 6px', borderRadius: 4, border: '1px solid #334155', background: '#0f172a', color: '#e2e8f0', fontSize: 11 }}>
135
- <option value="all">All</option>
136
- <option value="critical">Critical</option>
137
- <option value="serious">Serious</option>
138
- <option value="moderate">Moderate</option>
139
- <option value="minor">Minor</option>
140
- </select>
141
- <button onClick={runAudit} disabled={isScanning} style={{ padding: '4px 10px', borderRadius: 4, border: '1px solid #334155', background: isScanning ? '#334155' : '#0f172a', color: '#e2e8f0', cursor: 'pointer', fontSize: 11, fontWeight: 600 }}>
142
- {isScanning ? 'Scanning...' : '🔍 Scan'}
143
- </button>
144
- </div>
145
- </div>
146
- <div style={{ maxHeight: 350, overflow: 'auto', padding: 4 }}>
147
- {issues.length === 0 ? (
148
- <div style={{ padding: 20, textAlign: 'center', color: '#64748b' }}>
149
- {isScanning ? 'Scanning DOM...' : 'Click Scan to run accessibility audit'}
150
- </div>
151
- ) : filtered.map(issue => (
152
- <div key={issue.id} style={{ padding: '6px 10px', borderBottom: '1px solid #1e293b' }}>
153
- <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
154
- <span style={{ padding: '1px 6px', borderRadius: 4, fontSize: 9, fontWeight: 700, background: impactColors[issue.impact], color: 'white', textTransform: 'uppercase' }}>{issue.impact}</span>
155
- <span style={{ fontWeight: 600 }}>{issue.description}</span>
156
- </div>
157
- <div style={{ fontSize: 11, color: '#64748b', marginTop: 2 }}>
158
- <code style={{ color: '#94a3b8' }}>{issue.element}</code>
159
- </div>
160
- <div style={{ fontSize: 11, color: '#94a3b8', marginTop: 2 }}>💡 {issue.help}</div>
161
- </div>
162
- ))}
163
- </div>
164
- <div style={{ padding: '6px 12px', borderTop: '1px solid #334155', color: '#64748b', fontSize: 10 }}>
165
- {issues.length} issues: {issues.filter(i => i.impact === 'critical').length} critical, {issues.filter(i => i.impact === 'serious').length} serious
166
- </div>
167
- </div>
168
- )
169
- }
170
-
171
- // ─── Responsive Layout Tester ────────────────────────────────
172
-
173
- interface Viewport { name: string; width: number; height: number; icon: string }
174
-
175
- const VIEWPORTS: Viewport[] = [
176
- { name: 'Mobile S', width: 320, height: 568, icon: '📱' },
177
- { name: 'Mobile M', width: 375, height: 812, icon: '📱' },
178
- { name: 'Mobile L', width: 425, height: 896, icon: '📱' },
179
- { name: 'Tablet', width: 768, height: 1024, icon: '📋' },
180
- { name: 'Laptop', width: 1024, height: 768, icon: '💻' },
181
- { name: 'Desktop', width: 1440, height: 900, icon: '🖥' },
182
- { name: '4K', width: 2560, height: 1440, icon: '🖥' },
183
- ]
184
-
185
- /**
186
- * Responsive Layout Tester — Multi-viewport preview.
187
- */
188
- export function ResponsiveTester() {
189
- const [selectedViewport, setSelectedViewport] = useState<Viewport | null>(null)
190
- const [originalSize, setOriginalSize] = useState<{ width: number; height: number } | null>(null)
191
-
192
- const applyViewport = useCallback((vp: Viewport | null) => {
193
- if (vp) {
194
- if (!originalSize) {
195
- setOriginalSize({ width: window.innerWidth, height: window.innerHeight })
196
- }
197
- // Can't resize window from JS, but we can add visual indicators
198
- document.documentElement.style.setProperty('--viewport-width', `${vp.width}px`)
199
- setSelectedViewport(vp)
200
- } else {
201
- document.documentElement.style.removeProperty('--viewport-width')
202
- setSelectedViewport(null)
203
- }
204
- }, [originalSize])
205
-
206
- return (
207
- <div style={{ fontFamily: 'system-ui', fontSize: 13, color: '#e2e8f0', background: '#0f172a', borderRadius: 8, overflow: 'hidden' }}>
208
- <div style={{ padding: '8px 12px', background: '#1e293b', borderBottom: '1px solid #334155', fontWeight: 700, fontSize: 13 }}>
209
- 📐 Responsive Tester
210
- </div>
211
- <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', padding: 10 }}>
212
- {VIEWPORTS.map(vp => (
213
- <button
214
- key={vp.name}
215
- onClick={() => applyViewport(selectedViewport?.name === vp.name ? null : vp)}
216
- style={{
217
- padding: '6px 10px', borderRadius: 6, fontSize: 11, cursor: 'pointer', fontWeight: 600,
218
- border: selectedViewport?.name === vp.name ? '2px solid #6366f1' : '1px solid #334155',
219
- background: selectedViewport?.name === vp.name ? '#1e1b4b' : '#1e293b',
220
- color: '#e2e8f0',
221
- }}
222
- >
223
- {vp.icon} {vp.name} <span style={{ fontSize: 9, color: '#64748b' }}>{vp.width}×{vp.height}</span>
224
- </button>
225
- ))}
226
- </div>
227
- {selectedViewport && (
228
- <div style={{ padding: '6px 12px', borderTop: '1px solid #334155', color: '#64748b', fontSize: 10 }}>
229
- Active: {selectedViewport.name} ({selectedViewport.width}×{selectedViewport.height})
230
- </div>
231
- )}
232
- </div>
233
- )
234
- }
@@ -1,104 +0,0 @@
1
- import { useState, useCallback} from 'react'
2
-
3
- // ─── Types ────────────────────────────────────────────────────
4
-
5
- interface FeatureFlag {
6
- key: string
7
- label: string
8
- description?: string
9
- enabled: boolean
10
- category?: string
11
- }
12
-
13
- interface FeatureFlagsToggleProps {
14
- flags: FeatureFlag[]
15
- onToggle: (key: string, enabled: boolean) => void | Promise<void>
16
- }
17
-
18
- // ─── Component ────────────────────────────────────────────────
19
-
20
- /**
21
- * Feature Flags Toggle Panel — Instant flag switching for dev mode.
22
- * Shows all flags with category grouping and instant toggle.
23
- */
24
- export function FeatureFlagsToggle({ flags, onToggle }: FeatureFlagsToggleProps) {
25
- const [filter, setFilter] = useState('')
26
- const [togglingKey, setTogglingKey] = useState<string | null>(null)
27
-
28
- const handleToggle = useCallback(async (flag: FeatureFlag) => {
29
- setTogglingKey(flag.key)
30
- try {
31
- await onToggle(flag.key, !flag.enabled)
32
- } finally {
33
- setTogglingKey(null)
34
- }
35
- }, [onToggle])
36
-
37
- const filtered = filter
38
- ? flags.filter(f => f.key.toLowerCase().includes(filter.toLowerCase()) || f.label.toLowerCase().includes(filter.toLowerCase()))
39
- : flags
40
-
41
- // Group by category
42
- const groups = new Map<string, FeatureFlag[]>()
43
- for (const f of filtered) {
44
- const cat = f.category ?? 'General'
45
- if (!groups.has(cat)) groups.set(cat, [])
46
- groups.get(cat)!.push(f)
47
- }
48
-
49
- return (
50
- <div style={{ fontFamily: 'system-ui', fontSize: 13, color: '#e2e8f0', background: '#0f172a', borderRadius: 8, overflow: 'hidden' }}>
51
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 12px', background: '#1e293b', borderBottom: '1px solid #334155' }}>
52
- <span style={{ fontWeight: 700, fontSize: 13 }}>🚩 Feature Flags</span>
53
- <input
54
- placeholder="Filter flags..."
55
- value={filter}
56
- onChange={e => setFilter(e.target.value)}
57
- style={{ padding: '4px 8px', borderRadius: 4, border: '1px solid #334155', background: '#0f172a', color: '#e2e8f0', fontSize: 11, width: 140 }}
58
- />
59
- </div>
60
- <div style={{ maxHeight: 350, overflow: 'auto', padding: 8 }}>
61
- {[...groups.entries()].map(([category, categoryFlags]) => (
62
- <div key={category}>
63
- <div style={{ padding: '6px 8px', color: '#64748b', fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 1 }}>
64
- {category}
65
- </div>
66
- {categoryFlags.map(flag => (
67
- <div
68
- key={flag.key}
69
- style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '6px 8px', borderBottom: '1px solid #1e293b' }}
70
- >
71
- <div>
72
- <div style={{ fontWeight: 600 }}>{flag.label}</div>
73
- <div style={{ fontSize: 11, color: '#64748b' }}>
74
- <code style={{ color: '#94a3b8' }}>{flag.key}</code>
75
- {flag.description && ` — ${flag.description}`}
76
- </div>
77
- </div>
78
- <button
79
- onClick={() => handleToggle(flag)}
80
- disabled={togglingKey === flag.key}
81
- style={{
82
- width: 44, height: 24, borderRadius: 12, border: 'none', cursor: 'pointer',
83
- background: flag.enabled ? '#22c55e' : '#334155',
84
- position: 'relative', transition: 'background 0.2s',
85
- }}
86
- >
87
- <div style={{
88
- width: 18, height: 18, borderRadius: '50%', background: 'white',
89
- position: 'absolute', top: 3,
90
- left: flag.enabled ? 23 : 3,
91
- transition: 'left 0.2s',
92
- }} />
93
- </button>
94
- </div>
95
- ))}
96
- </div>
97
- ))}
98
- </div>
99
- <div style={{ padding: '6px 12px', borderTop: '1px solid #334155', color: '#64748b', fontSize: 10 }}>
100
- {flags.filter(f => f.enabled).length}/{flags.length} enabled
101
- </div>
102
- </div>
103
- )
104
- }
@@ -1,152 +0,0 @@
1
- import { useState, useEffect, useCallback, useRef } from 'react'
2
-
3
- // ─── Types ────────────────────────────────────────────────────
4
-
5
- interface Route {
6
- path: string
7
- label: string
8
- icon?: string
9
- group?: string
10
- }
11
-
12
- interface QuickRouteJumpProps {
13
- routes: Route[]
14
- onNavigate: (path: string) => void
15
- hotkey?: string // Default: 'Mod+K'
16
- }
17
-
18
- // ─── Component ────────────────────────────────────────────────
19
-
20
- /**
21
- * Quick Route Jump — Command palette for instant navigation.
22
- * Opens with Cmd/Ctrl+K, search routes, arrow key navigation.
23
- */
24
- export function QuickRouteJump({ routes, onNavigate, hotkey = 'Mod+K' }: QuickRouteJumpProps) {
25
- const [isOpen, setIsOpen] = useState(false)
26
- const [query, setQuery] = useState('')
27
- const [selectedIndex, setSelectedIndex] = useState(0)
28
- const inputRef = useRef<HTMLInputElement>(null)
29
-
30
- // Keyboard shortcut to open
31
- useEffect(() => {
32
- const handler = (e: KeyboardEvent) => {
33
- const isMod = e.metaKey || e.ctrlKey
34
- if (isMod && e.key.toLowerCase() === 'k') {
35
- e.preventDefault()
36
- setIsOpen(prev => !prev)
37
- setQuery('')
38
- setSelectedIndex(0)
39
- }
40
- if (e.key === 'Escape') setIsOpen(false)
41
- }
42
- window.addEventListener('keydown', handler)
43
- return () => window.removeEventListener('keydown', handler)
44
- }, [])
45
-
46
- // Focus input when opened
47
- useEffect(() => {
48
- if (isOpen) setTimeout(() => inputRef.current?.focus(), 50)
49
- }, [isOpen])
50
-
51
- const filtered = query
52
- ? routes.filter(r =>
53
- r.path.toLowerCase().includes(query.toLowerCase()) ||
54
- r.label.toLowerCase().includes(query.toLowerCase())
55
- )
56
- : routes
57
-
58
- const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
59
- if (e.key === 'ArrowDown') {
60
- e.preventDefault()
61
- setSelectedIndex(i => Math.min(i + 1, filtered.length - 1))
62
- } else if (e.key === 'ArrowUp') {
63
- e.preventDefault()
64
- setSelectedIndex(i => Math.max(i - 1, 0))
65
- } else if (e.key === 'Enter' && filtered[selectedIndex]) {
66
- e.preventDefault()
67
- onNavigate(filtered[selectedIndex].path)
68
- setIsOpen(false)
69
- }
70
- }, [filtered, selectedIndex, onNavigate])
71
-
72
- if (!isOpen) return null
73
-
74
- // Group routes
75
- const groups = new Map<string, Route[]>()
76
- for (const r of filtered) {
77
- const g = r.group ?? 'Routes'
78
- if (!groups.has(g)) groups.set(g, [])
79
- groups.get(g)!.push(r)
80
- }
81
-
82
- let globalIdx = -1
83
-
84
- return (
85
- <>
86
- {/* Backdrop */}
87
- <div onClick={() => setIsOpen(false)} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.6)', zIndex: 9998 }} />
88
- {/* Panel */}
89
- <div style={{
90
- position: 'fixed', top: '20%', left: '50%', transform: 'translateX(-50%)',
91
- width: '100%', maxWidth: 520, background: '#0f172a', border: '1px solid #334155',
92
- borderRadius: 12, overflow: 'hidden', zIndex: 9999,
93
- boxShadow: '0 25px 50px rgba(0,0,0,0.4)',
94
- }}>
95
- <div style={{ padding: 12, borderBottom: '1px solid #334155' }}>
96
- <input
97
- ref={inputRef}
98
- value={query}
99
- onChange={e => { setQuery(e.target.value); setSelectedIndex(0) }}
100
- onKeyDown={handleKeyDown}
101
- placeholder="Jump to route..."
102
- style={{
103
- width: '100%', padding: '10px 12px', borderRadius: 8, border: '1px solid #334155',
104
- background: '#1e293b', color: '#e2e8f0', fontSize: 14, outline: 'none',
105
- }}
106
- />
107
- </div>
108
- <div style={{ maxHeight: 350, overflow: 'auto', padding: 4 }}>
109
- {filtered.length === 0 ? (
110
- <div style={{ padding: 24, textAlign: 'center', color: '#64748b' }}>No routes match "{query}"</div>
111
- ) : [...groups.entries()].map(([group, groupRoutes]) => (
112
- <div key={group}>
113
- <div style={{ padding: '8px 12px', color: '#64748b', fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 1 }}>
114
- {group}
115
- </div>
116
- {groupRoutes.map(route => {
117
- globalIdx++
118
- const idx = globalIdx
119
- return (
120
- <div
121
- key={route.path}
122
- onClick={() => { onNavigate(route.path); setIsOpen(false) }}
123
- style={{
124
- display: 'flex', alignItems: 'center', gap: 10, padding: '8px 12px',
125
- borderRadius: 6, cursor: 'pointer',
126
- background: idx === selectedIndex ? '#1e293b' : 'transparent',
127
- }}
128
- onMouseEnter={() => setSelectedIndex(idx)}
129
- >
130
- <span style={{ fontSize: 16 }}>{route.icon ?? '📄'}</span>
131
- <div style={{ flex: 1 }}>
132
- <div style={{ fontWeight: 600, color: '#e2e8f0', fontSize: 13 }}>{route.label}</div>
133
- <div style={{ fontSize: 11, color: '#64748b' }}>{route.path}</div>
134
- </div>
135
- {idx === selectedIndex && (
136
- <span style={{ fontSize: 10, color: '#64748b', border: '1px solid #334155', padding: '2px 6px', borderRadius: 4 }}>↵</span>
137
- )}
138
- </div>
139
- )
140
- })}
141
- </div>
142
- ))}
143
- </div>
144
- <div style={{ padding: '8px 12px', borderTop: '1px solid #334155', display: 'flex', gap: 12, color: '#64748b', fontSize: 10 }}>
145
- <span>↑↓ Navigate</span>
146
- <span>↵ Open</span>
147
- <span>Esc Close</span>
148
- </div>
149
- </div>
150
- </>
151
- )
152
- }