@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.
- package/package.json +62 -3
- package/packages/convex/shared/README.md +1 -1
- package/packages/devtools/dist/index.d.ts +204 -0
- package/packages/devtools/dist/index.js +186 -0
- package/packages/devtools/dist/index.js.map +1 -0
- package/packages/devtools/react/README.md +1 -1
- package/packages/devtools/solidjs/README.md +1 -1
- package/packages/devtools/solidjs/dist/index.js +1830 -0
- package/packages/devtools/solidjs/dist/index.js.map +1 -0
- package/packages/env/dist/index.d.ts +151 -0
- package/packages/env/dist/index.js +93 -0
- package/packages/env/dist/index.js.map +1 -0
- package/packages/errors/dist/index.d.ts +177 -0
- package/packages/errors/dist/index.js +187 -0
- package/packages/errors/dist/index.js.map +1 -0
- package/packages/errors/react/README.md +1 -1
- package/packages/errors/solidjs/README.md +1 -1
- package/packages/logger/dist/index.d.ts +171 -0
- package/packages/logger/dist/index.js +216 -0
- package/packages/logger/dist/index.js.map +1 -0
- package/packages/logger/react/README.md +1 -1
- package/packages/logger/solidjs/README.md +1 -1
- package/packages/perf/dist/index.d.ts +168 -0
- package/packages/perf/dist/index.js +265 -0
- package/packages/perf/dist/index.js.map +1 -0
- package/packages/perf/react/README.md +1 -1
- package/packages/perf/solidjs/README.md +1 -1
- package/packages/shared/dist/index.d.ts +253 -0
- package/packages/shared/dist/index.js +278 -0
- package/packages/shared/dist/index.js.map +1 -0
- package/.changeset/config.json +0 -11
- package/.env.example +0 -2
- package/.github/CODEOWNERS +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -16
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -11
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -10
- package/.github/dependabot.yml +0 -11
- package/.github/workflows/ci.yml +0 -23
- package/.github/workflows/release.yml +0 -29
- package/.node-version +0 -1
- package/.nvmrc +0 -1
- package/.prettierrc +0 -7
- package/.project/ACCOUNT.yaml +0 -4
- package/.project/IDEAS.yaml +0 -7
- package/.project/PROJECT.yaml +0 -11
- package/.project/ROADMAP.yaml +0 -15
- package/CODE_OF_CONDUCT.md +0 -26
- package/CONTRIBUTING.md +0 -69
- package/SECURITY.md +0 -18
- package/SUPPORT.md +0 -14
- package/packages/convex/shared/package.json +0 -42
- package/packages/convex/shared/src/audit/index.ts +0 -5
- package/packages/convex/shared/src/audit/presets.ts +0 -165
- package/packages/convex/shared/src/audit/schema.ts +0 -85
- package/packages/convex/shared/src/audit/write.ts +0 -102
- package/packages/convex/shared/src/extract.ts +0 -75
- package/packages/convex/shared/src/index.ts +0 -41
- package/packages/convex/shared/src/messages.ts +0 -45
- package/packages/convex/shared/src/security.ts +0 -112
- package/packages/convex/shared/src/throw.ts +0 -184
- package/packages/convex/shared/src/types.ts +0 -57
- package/packages/convex/shared/src/utils.ts +0 -58
- package/packages/convex/shared/tsconfig.json +0 -28
- package/packages/convex/shared/tsup.config.ts +0 -12
- package/packages/devtools/package.json +0 -27
- package/packages/devtools/react/package.json +0 -53
- package/packages/devtools/react/src/components/DesignPreview.tsx +0 -59
- package/packages/devtools/react/src/components/DesignSwitcherDropdown.tsx +0 -99
- package/packages/devtools/react/src/components/DevSidebar.tsx +0 -247
- package/packages/devtools/react/src/components/DevToolbar.tsx +0 -242
- package/packages/devtools/react/src/components/GitHubIssueDialog.tsx +0 -402
- package/packages/devtools/react/src/components/InspectorOverlay.tsx +0 -312
- package/packages/devtools/react/src/components/PageLoadWaterfall.tsx +0 -144
- package/packages/devtools/react/src/components/PerformancePanel.tsx +0 -330
- package/packages/devtools/react/src/context/DevModeContext.tsx +0 -226
- package/packages/devtools/react/src/context/PerformanceContext.tsx +0 -143
- package/packages/devtools/react/src/data/designs.ts +0 -13
- package/packages/devtools/react/src/hooks/useGitHubLabels.ts +0 -47
- package/packages/devtools/react/src/hooks/useVirtualList.ts +0 -124
- package/packages/devtools/react/src/index.ts +0 -77
- package/packages/devtools/react/src/panels/ConvexSpy.tsx +0 -130
- package/packages/devtools/react/src/panels/DatabaseSeeder.tsx +0 -116
- package/packages/devtools/react/src/panels/DevModePhase2.tsx +0 -191
- package/packages/devtools/react/src/panels/DevModePhase3.tsx +0 -234
- package/packages/devtools/react/src/panels/FeatureFlagsToggle.tsx +0 -104
- package/packages/devtools/react/src/panels/QuickRouteJump.tsx +0 -152
- package/packages/devtools/react/src/services/github-service.ts +0 -247
- package/packages/devtools/react/tsconfig.json +0 -31
- package/packages/devtools/react/tsup.config.ts +0 -18
- package/packages/devtools/solidjs/package.json +0 -49
- package/packages/devtools/solidjs/src/components/DesignPreview.tsx +0 -51
- package/packages/devtools/solidjs/src/components/DesignSwitcherDropdown.tsx +0 -95
- package/packages/devtools/solidjs/src/components/DevSidebar.tsx +0 -247
- package/packages/devtools/solidjs/src/components/DevToolbar.tsx +0 -242
- package/packages/devtools/solidjs/src/components/GitHubIssueDialog.tsx +0 -400
- package/packages/devtools/solidjs/src/components/InspectorOverlay.tsx +0 -311
- package/packages/devtools/solidjs/src/components/PageLoadWaterfall.tsx +0 -144
- package/packages/devtools/solidjs/src/components/PerformancePanel.tsx +0 -330
- package/packages/devtools/solidjs/src/context/DevModeContext.tsx +0 -216
- package/packages/devtools/solidjs/src/context/PerformanceContext.tsx +0 -135
- package/packages/devtools/solidjs/src/data/designs.ts +0 -13
- package/packages/devtools/solidjs/src/hooks/createGitHubLabels.ts +0 -47
- package/packages/devtools/solidjs/src/index.ts +0 -64
- package/packages/devtools/solidjs/src/services/github-service.ts +0 -247
- package/packages/devtools/solidjs/tsconfig.json +0 -21
- package/packages/devtools/src/index.ts +0 -377
- package/packages/devtools/tsup.config.ts +0 -12
- package/packages/env/package.json +0 -30
- package/packages/env/src/index.ts +0 -264
- package/packages/env/tsup.config.ts +0 -12
- package/packages/errors/package.json +0 -27
- package/packages/errors/react/package.json +0 -72
- package/packages/errors/react/src/analytics.ts +0 -16
- package/packages/errors/react/src/components/ErrorBoundary.tsx +0 -248
- package/packages/errors/react/src/components/ErrorDisplay.tsx +0 -328
- package/packages/errors/react/src/components/ValidationErrors.tsx +0 -102
- package/packages/errors/react/src/config.ts +0 -199
- package/packages/errors/react/src/constants.ts +0 -74
- package/packages/errors/react/src/hooks/useErrorBoundary.ts +0 -92
- package/packages/errors/react/src/hooks/useErrorHandler.ts +0 -87
- package/packages/errors/react/src/index.ts +0 -96
- package/packages/errors/react/src/types.ts +0 -102
- package/packages/errors/react/src/utils/errorMessages.ts +0 -35
- package/packages/errors/react/src/utils/errorPolicy.ts +0 -139
- package/packages/errors/react/src/utils/extractAppError.ts +0 -174
- package/packages/errors/react/src/utils/formatError.ts +0 -112
- package/packages/errors/react/tsconfig.json +0 -25
- package/packages/errors/react/tsup.config.ts +0 -24
- package/packages/errors/solidjs/package.json +0 -46
- package/packages/errors/solidjs/src/components/ErrorDisplay.tsx +0 -179
- package/packages/errors/solidjs/src/config.ts +0 -98
- package/packages/errors/solidjs/src/hooks/createErrorHandler.ts +0 -107
- package/packages/errors/solidjs/src/index.ts +0 -61
- package/packages/errors/solidjs/src/types.ts +0 -34
- package/packages/errors/solidjs/src/utils/errorPolicy.ts +0 -56
- package/packages/errors/solidjs/src/utils/extractAppError.ts +0 -94
- package/packages/errors/solidjs/src/utils/formatError.ts +0 -33
- package/packages/errors/solidjs/tsconfig.json +0 -26
- package/packages/errors/solidjs/tsup.config.ts +0 -21
- package/packages/errors/src/index.ts +0 -320
- package/packages/errors/tsup.config.ts +0 -12
- package/packages/logger/package.json +0 -27
- package/packages/logger/react/package.json +0 -46
- package/packages/logger/react/src/index.ts +0 -4
- package/packages/logger/react/src/useMetrics.ts +0 -42
- package/packages/logger/react/src/usePerformanceLog.ts +0 -61
- package/packages/logger/react/tsconfig.json +0 -31
- package/packages/logger/react/tsup.config.ts +0 -12
- package/packages/logger/solidjs/package.json +0 -45
- package/packages/logger/solidjs/src/createMetrics.ts +0 -37
- package/packages/logger/solidjs/src/createPerformanceLog.ts +0 -58
- package/packages/logger/solidjs/src/index.ts +0 -4
- package/packages/logger/solidjs/tsconfig.json +0 -32
- package/packages/logger/solidjs/tsup.config.ts +0 -12
- package/packages/logger/src/index.ts +0 -363
- package/packages/logger/tsup.config.ts +0 -12
- package/packages/perf/package.json +0 -27
- package/packages/perf/react/package.json +0 -59
- package/packages/perf/react/src/components/PerformanceDashboard.tsx +0 -257
- package/packages/perf/react/src/hooks/useMonitoredQuery.ts +0 -89
- package/packages/perf/react/src/hooks/usePerformanceMetrics.ts +0 -78
- package/packages/perf/react/src/index.ts +0 -33
- package/packages/perf/react/src/services/PerformanceMonitor.ts +0 -313
- package/packages/perf/react/src/types.ts +0 -77
- package/packages/perf/react/tsconfig.json +0 -25
- package/packages/perf/react/tsup.config.ts +0 -19
- package/packages/perf/solidjs/package.json +0 -41
- package/packages/perf/solidjs/src/components/PerformanceDashboard.tsx +0 -207
- package/packages/perf/solidjs/src/hooks/createPerformanceMetrics.ts +0 -73
- package/packages/perf/solidjs/src/index.ts +0 -31
- package/packages/perf/solidjs/src/services/PerformanceMonitor.ts +0 -134
- package/packages/perf/solidjs/src/types.ts +0 -78
- package/packages/perf/solidjs/tsconfig.json +0 -26
- package/packages/perf/solidjs/tsup.config.ts +0 -14
- package/packages/perf/src/index.ts +0 -410
- package/packages/perf/tsup.config.ts +0 -12
- 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
|
-
}
|