@geenius/tools 0.1.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 (160) hide show
  1. package/.changeset/config.json +11 -0
  2. package/.env.example +2 -0
  3. package/.github/CODEOWNERS +1 -0
  4. package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.md +11 -0
  6. package/.github/PULL_REQUEST_TEMPLATE.md +10 -0
  7. package/.github/dependabot.yml +11 -0
  8. package/.github/workflows/ci.yml +23 -0
  9. package/.github/workflows/release.yml +29 -0
  10. package/.node-version +1 -0
  11. package/.nvmrc +1 -0
  12. package/.prettierrc +7 -0
  13. package/.project/ACCOUNT.yaml +4 -0
  14. package/.project/IDEAS.yaml +7 -0
  15. package/.project/PROJECT.yaml +11 -0
  16. package/.project/ROADMAP.yaml +15 -0
  17. package/CHANGELOG.md +16 -0
  18. package/CODE_OF_CONDUCT.md +26 -0
  19. package/CONTRIBUTING.md +69 -0
  20. package/LICENSE +21 -0
  21. package/README.md +1 -0
  22. package/SECURITY.md +18 -0
  23. package/SUPPORT.md +14 -0
  24. package/package.json +75 -0
  25. package/packages/convex/shared/README.md +1 -0
  26. package/packages/convex/shared/package.json +42 -0
  27. package/packages/convex/shared/src/audit/index.ts +5 -0
  28. package/packages/convex/shared/src/audit/presets.ts +165 -0
  29. package/packages/convex/shared/src/audit/schema.ts +85 -0
  30. package/packages/convex/shared/src/audit/write.ts +102 -0
  31. package/packages/convex/shared/src/extract.ts +75 -0
  32. package/packages/convex/shared/src/index.ts +41 -0
  33. package/packages/convex/shared/src/messages.ts +45 -0
  34. package/packages/convex/shared/src/security.ts +112 -0
  35. package/packages/convex/shared/src/throw.ts +184 -0
  36. package/packages/convex/shared/src/types.ts +57 -0
  37. package/packages/convex/shared/src/utils.ts +58 -0
  38. package/packages/convex/shared/tsconfig.json +28 -0
  39. package/packages/convex/shared/tsup.config.ts +12 -0
  40. package/packages/devtools/package.json +27 -0
  41. package/packages/devtools/react/README.md +1 -0
  42. package/packages/devtools/react/package.json +53 -0
  43. package/packages/devtools/react/src/components/DesignPreview.tsx +59 -0
  44. package/packages/devtools/react/src/components/DesignSwitcherDropdown.tsx +99 -0
  45. package/packages/devtools/react/src/components/DevSidebar.tsx +247 -0
  46. package/packages/devtools/react/src/components/DevToolbar.tsx +242 -0
  47. package/packages/devtools/react/src/components/GitHubIssueDialog.tsx +402 -0
  48. package/packages/devtools/react/src/components/InspectorOverlay.tsx +312 -0
  49. package/packages/devtools/react/src/components/PageLoadWaterfall.tsx +144 -0
  50. package/packages/devtools/react/src/components/PerformancePanel.tsx +330 -0
  51. package/packages/devtools/react/src/context/DevModeContext.tsx +226 -0
  52. package/packages/devtools/react/src/context/PerformanceContext.tsx +143 -0
  53. package/packages/devtools/react/src/data/designs.ts +13 -0
  54. package/packages/devtools/react/src/hooks/useGitHubLabels.ts +47 -0
  55. package/packages/devtools/react/src/hooks/useVirtualList.ts +124 -0
  56. package/packages/devtools/react/src/index.ts +77 -0
  57. package/packages/devtools/react/src/panels/ConvexSpy.tsx +130 -0
  58. package/packages/devtools/react/src/panels/DatabaseSeeder.tsx +116 -0
  59. package/packages/devtools/react/src/panels/DevModePhase2.tsx +191 -0
  60. package/packages/devtools/react/src/panels/DevModePhase3.tsx +234 -0
  61. package/packages/devtools/react/src/panels/FeatureFlagsToggle.tsx +104 -0
  62. package/packages/devtools/react/src/panels/QuickRouteJump.tsx +152 -0
  63. package/packages/devtools/react/src/services/github-service.ts +247 -0
  64. package/packages/devtools/react/tsconfig.json +31 -0
  65. package/packages/devtools/react/tsup.config.ts +18 -0
  66. package/packages/devtools/solidjs/README.md +1 -0
  67. package/packages/devtools/solidjs/package.json +49 -0
  68. package/packages/devtools/solidjs/src/components/DesignPreview.tsx +51 -0
  69. package/packages/devtools/solidjs/src/components/DesignSwitcherDropdown.tsx +95 -0
  70. package/packages/devtools/solidjs/src/components/DevSidebar.tsx +247 -0
  71. package/packages/devtools/solidjs/src/components/DevToolbar.tsx +242 -0
  72. package/packages/devtools/solidjs/src/components/GitHubIssueDialog.tsx +400 -0
  73. package/packages/devtools/solidjs/src/components/InspectorOverlay.tsx +311 -0
  74. package/packages/devtools/solidjs/src/components/PageLoadWaterfall.tsx +144 -0
  75. package/packages/devtools/solidjs/src/components/PerformancePanel.tsx +330 -0
  76. package/packages/devtools/solidjs/src/context/DevModeContext.tsx +216 -0
  77. package/packages/devtools/solidjs/src/context/PerformanceContext.tsx +135 -0
  78. package/packages/devtools/solidjs/src/data/designs.ts +13 -0
  79. package/packages/devtools/solidjs/src/hooks/createGitHubLabels.ts +47 -0
  80. package/packages/devtools/solidjs/src/index.ts +64 -0
  81. package/packages/devtools/solidjs/src/services/github-service.ts +247 -0
  82. package/packages/devtools/solidjs/tsconfig.json +21 -0
  83. package/packages/devtools/src/index.ts +377 -0
  84. package/packages/devtools/tsup.config.ts +12 -0
  85. package/packages/env/package.json +30 -0
  86. package/packages/env/src/index.ts +264 -0
  87. package/packages/env/tsup.config.ts +12 -0
  88. package/packages/errors/package.json +27 -0
  89. package/packages/errors/react/README.md +1 -0
  90. package/packages/errors/react/package.json +72 -0
  91. package/packages/errors/react/src/analytics.ts +16 -0
  92. package/packages/errors/react/src/components/ErrorBoundary.tsx +248 -0
  93. package/packages/errors/react/src/components/ErrorDisplay.tsx +328 -0
  94. package/packages/errors/react/src/components/ValidationErrors.tsx +102 -0
  95. package/packages/errors/react/src/config.ts +199 -0
  96. package/packages/errors/react/src/constants.ts +74 -0
  97. package/packages/errors/react/src/hooks/useErrorBoundary.ts +92 -0
  98. package/packages/errors/react/src/hooks/useErrorHandler.ts +87 -0
  99. package/packages/errors/react/src/index.ts +96 -0
  100. package/packages/errors/react/src/types.ts +102 -0
  101. package/packages/errors/react/src/utils/errorMessages.ts +35 -0
  102. package/packages/errors/react/src/utils/errorPolicy.ts +139 -0
  103. package/packages/errors/react/src/utils/extractAppError.ts +174 -0
  104. package/packages/errors/react/src/utils/formatError.ts +112 -0
  105. package/packages/errors/react/tsconfig.json +25 -0
  106. package/packages/errors/react/tsup.config.ts +24 -0
  107. package/packages/errors/solidjs/README.md +1 -0
  108. package/packages/errors/solidjs/package.json +46 -0
  109. package/packages/errors/solidjs/src/components/ErrorDisplay.tsx +179 -0
  110. package/packages/errors/solidjs/src/config.ts +98 -0
  111. package/packages/errors/solidjs/src/hooks/createErrorHandler.ts +107 -0
  112. package/packages/errors/solidjs/src/index.ts +61 -0
  113. package/packages/errors/solidjs/src/types.ts +34 -0
  114. package/packages/errors/solidjs/src/utils/errorPolicy.ts +56 -0
  115. package/packages/errors/solidjs/src/utils/extractAppError.ts +94 -0
  116. package/packages/errors/solidjs/src/utils/formatError.ts +33 -0
  117. package/packages/errors/solidjs/tsconfig.json +26 -0
  118. package/packages/errors/solidjs/tsup.config.ts +21 -0
  119. package/packages/errors/src/index.ts +320 -0
  120. package/packages/errors/tsup.config.ts +12 -0
  121. package/packages/logger/package.json +27 -0
  122. package/packages/logger/react/README.md +1 -0
  123. package/packages/logger/react/package.json +46 -0
  124. package/packages/logger/react/src/index.ts +4 -0
  125. package/packages/logger/react/src/useMetrics.ts +42 -0
  126. package/packages/logger/react/src/usePerformanceLog.ts +61 -0
  127. package/packages/logger/react/tsconfig.json +31 -0
  128. package/packages/logger/react/tsup.config.ts +12 -0
  129. package/packages/logger/solidjs/README.md +1 -0
  130. package/packages/logger/solidjs/package.json +45 -0
  131. package/packages/logger/solidjs/src/createMetrics.ts +37 -0
  132. package/packages/logger/solidjs/src/createPerformanceLog.ts +58 -0
  133. package/packages/logger/solidjs/src/index.ts +4 -0
  134. package/packages/logger/solidjs/tsconfig.json +32 -0
  135. package/packages/logger/solidjs/tsup.config.ts +12 -0
  136. package/packages/logger/src/index.ts +363 -0
  137. package/packages/logger/tsup.config.ts +12 -0
  138. package/packages/perf/package.json +27 -0
  139. package/packages/perf/react/README.md +1 -0
  140. package/packages/perf/react/package.json +59 -0
  141. package/packages/perf/react/src/components/PerformanceDashboard.tsx +257 -0
  142. package/packages/perf/react/src/hooks/useMonitoredQuery.ts +89 -0
  143. package/packages/perf/react/src/hooks/usePerformanceMetrics.ts +78 -0
  144. package/packages/perf/react/src/index.ts +33 -0
  145. package/packages/perf/react/src/services/PerformanceMonitor.ts +313 -0
  146. package/packages/perf/react/src/types.ts +77 -0
  147. package/packages/perf/react/tsconfig.json +25 -0
  148. package/packages/perf/react/tsup.config.ts +19 -0
  149. package/packages/perf/solidjs/README.md +1 -0
  150. package/packages/perf/solidjs/package.json +41 -0
  151. package/packages/perf/solidjs/src/components/PerformanceDashboard.tsx +207 -0
  152. package/packages/perf/solidjs/src/hooks/createPerformanceMetrics.ts +73 -0
  153. package/packages/perf/solidjs/src/index.ts +31 -0
  154. package/packages/perf/solidjs/src/services/PerformanceMonitor.ts +134 -0
  155. package/packages/perf/solidjs/src/types.ts +78 -0
  156. package/packages/perf/solidjs/tsconfig.json +26 -0
  157. package/packages/perf/solidjs/tsup.config.ts +14 -0
  158. package/packages/perf/src/index.ts +410 -0
  159. package/packages/perf/tsup.config.ts +12 -0
  160. package/pnpm-workspace.yaml +2 -0
@@ -0,0 +1,247 @@
1
+ // @geenius-tools/devtools-react — src/services/github-service.ts
2
+ // GitHub API service for creating issues — adapted from geenius-react
3
+
4
+ export interface GitHubIssue {
5
+ title: string
6
+ body: string
7
+ labels?: string[]
8
+ }
9
+
10
+ export interface GitHubLabel {
11
+ id: number
12
+ name: string
13
+ color: string
14
+ description: string | null
15
+ }
16
+
17
+ export interface CreateIssueResponse {
18
+ id: number
19
+ number: number
20
+ html_url: string
21
+ title: string
22
+ }
23
+
24
+ export interface GitHubIssueListItem {
25
+ id: number
26
+ number: number
27
+ title: string
28
+ body: string | null
29
+ html_url: string
30
+ state: 'open' | 'closed'
31
+ created_at: string
32
+ updated_at: string
33
+ labels: GitHubLabel[]
34
+ user: {
35
+ login: string
36
+ avatar_url: string
37
+ } | null
38
+ }
39
+
40
+ export type IssueFilter = 'all' | 'bug' | 'enhancement'
41
+
42
+ /**
43
+ * GitHub configuration. Provide via `configureGitHub()` or env vars.
44
+ */
45
+ export interface GitHubConfig {
46
+ owner: string
47
+ repo: string
48
+ branch?: string
49
+ token: string
50
+ }
51
+
52
+ let _config: GitHubConfig | null = null
53
+
54
+ /**
55
+ * Configure GitHub integration.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * configureGitHub({
60
+ * owner: 'my-org',
61
+ * repo: 'my-app',
62
+ * token: import.meta.env.VITE_GITHUB_TOKEN,
63
+ * })
64
+ * ```
65
+ */
66
+ export function configureGitHub(config: GitHubConfig): void {
67
+ _config = config
68
+ }
69
+
70
+ export function getGitHubConfig(): GitHubConfig {
71
+ if (_config) return _config
72
+
73
+ // Fallback: try env vars (Vite convention)
74
+ let owner = ''
75
+ let repo = ''
76
+ let branch = 'main'
77
+ let token = ''
78
+ try {
79
+ // @ts-expect-error — Vite env
80
+ owner = import.meta?.env?.VITE_GITHUB_OWNER ?? ''
81
+ // @ts-expect-error — Vite env
82
+ repo = import.meta?.env?.VITE_GITHUB_REPO ?? ''
83
+ // @ts-expect-error — Vite env
84
+ branch = import.meta?.env?.VITE_GITHUB_BRANCH ?? 'main'
85
+ // @ts-expect-error — Vite env
86
+ token = import.meta?.env?.VITE_GITHUB_TOKEN ?? ''
87
+ } catch {
88
+ // Not in Vite
89
+ }
90
+
91
+ return { owner, repo, branch, token }
92
+ }
93
+
94
+ export function isGitHubConfigured(): boolean {
95
+ const { owner, repo, token } = getGitHubConfig()
96
+ const isPlaceholder =
97
+ !token || token === 'ghp_your_personal_access_token_here'
98
+ return Boolean(owner && repo && token && !isPlaceholder)
99
+ }
100
+
101
+ export async function createGitHubIssue(
102
+ issue: GitHubIssue,
103
+ ): Promise<CreateIssueResponse> {
104
+ const { owner, repo, token } = getGitHubConfig()
105
+
106
+ if (!owner || !repo || !token) {
107
+ throw new Error(
108
+ 'GitHub configuration is incomplete. Call configureGitHub() or set VITE_GITHUB_* env vars.',
109
+ )
110
+ }
111
+
112
+ const response = await fetch(
113
+ `https://api.github.com/repos/${owner}/${repo}/issues`,
114
+ {
115
+ method: 'POST',
116
+ headers: {
117
+ Accept: 'application/vnd.github+json',
118
+ Authorization: `Bearer ${token}`,
119
+ 'X-GitHub-Api-Version': '2022-11-28',
120
+ 'Content-Type': 'application/json',
121
+ },
122
+ body: JSON.stringify({
123
+ title: issue.title,
124
+ body: issue.body,
125
+ labels: issue.labels || [],
126
+ }),
127
+ },
128
+ )
129
+
130
+ if (!response.ok) {
131
+ const error = await response
132
+ .json()
133
+ .catch(() => ({ message: 'Unknown error' }))
134
+ throw new Error(
135
+ `Failed to create issue: ${error.message || response.statusText}`,
136
+ )
137
+ }
138
+
139
+ return response.json()
140
+ }
141
+
142
+ export async function getRepositoryLabels(): Promise<GitHubLabel[]> {
143
+ const { owner, repo, token } = getGitHubConfig()
144
+
145
+ if (!owner || !repo || !token) return []
146
+
147
+ try {
148
+ const response = await fetch(
149
+ `https://api.github.com/repos/${owner}/${repo}/labels`,
150
+ {
151
+ headers: {
152
+ Accept: 'application/vnd.github+json',
153
+ Authorization: `Bearer ${token}`,
154
+ 'X-GitHub-Api-Version': '2022-11-28',
155
+ },
156
+ },
157
+ )
158
+
159
+ if (!response.ok) return []
160
+ return response.json()
161
+ } catch {
162
+ return []
163
+ }
164
+ }
165
+
166
+ export async function getGitHubIssues(
167
+ filter: IssueFilter = 'all',
168
+ state: 'open' | 'closed' | 'all' = 'open',
169
+ ): Promise<GitHubIssueListItem[]> {
170
+ const { owner, repo, token } = getGitHubConfig()
171
+
172
+ if (!owner || !repo || !token) return []
173
+
174
+ try {
175
+ const params = new URLSearchParams({
176
+ state,
177
+ per_page: '50',
178
+ sort: 'updated',
179
+ direction: 'desc',
180
+ })
181
+
182
+ if (filter !== 'all') {
183
+ params.set('labels', filter)
184
+ }
185
+
186
+ const response = await fetch(
187
+ `https://api.github.com/repos/${owner}/${repo}/issues?${params.toString()}`,
188
+ {
189
+ headers: {
190
+ Accept: 'application/vnd.github+json',
191
+ Authorization: `Bearer ${token}`,
192
+ 'X-GitHub-Api-Version': '2022-11-28',
193
+ },
194
+ },
195
+ )
196
+
197
+ if (!response.ok) return []
198
+
199
+ const issues: GitHubIssueListItem[] = await response.json()
200
+ return issues.filter((issue) => !('pull_request' in issue))
201
+ } catch {
202
+ return []
203
+ }
204
+ }
205
+
206
+ export function generateContextInfo(): string {
207
+ const lines: string[] = [
208
+ '---',
209
+ '### Context',
210
+ '',
211
+ `**Page URL:** ${window.location.href}`,
212
+ `**Timestamp:** ${new Date().toISOString()}`,
213
+ `**User Agent:** ${navigator.userAgent}`,
214
+ `**Branch:** ${getGitHubConfig().branch ?? 'main'}`,
215
+ ]
216
+
217
+ if (typeof window !== 'undefined') {
218
+ lines.push(`**Screen:** ${window.innerWidth}x${window.innerHeight}`)
219
+ }
220
+
221
+ return lines.join('\n')
222
+ }
223
+
224
+ export function getIssueType(
225
+ issue: GitHubIssueListItem,
226
+ ): 'bug' | 'feature' | 'other' {
227
+ const labelNames = issue.labels.map((l) => l.name.toLowerCase())
228
+ if (labelNames.includes('bug')) return 'bug'
229
+ if (labelNames.includes('enhancement') || labelNames.includes('feature'))
230
+ return 'feature'
231
+ return 'other'
232
+ }
233
+
234
+ export function formatRelativeTime(dateString: string): string {
235
+ const date = new Date(dateString)
236
+ const now = new Date()
237
+ const diffMs = now.getTime() - date.getTime()
238
+ const diffMins = Math.floor(diffMs / 60000)
239
+ const diffHours = Math.floor(diffMs / 3600000)
240
+ const diffDays = Math.floor(diffMs / 86400000)
241
+
242
+ if (diffMins < 1) return 'just now'
243
+ if (diffMins < 60) return `${diffMins}m ago`
244
+ if (diffHours < 24) return `${diffHours}h ago`
245
+ if (diffDays < 7) return `${diffDays}d ago`
246
+ return date.toLocaleDateString()
247
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "jsx": "react-jsx",
7
+ "lib": [
8
+ "ES2022",
9
+ "DOM",
10
+ "DOM.Iterable"
11
+ ],
12
+ "strict": true,
13
+ "esModuleInterop": true,
14
+ "skipLibCheck": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "resolveJsonModule": true,
17
+ "isolatedModules": true,
18
+ "declaration": true,
19
+ "declarationMap": true,
20
+ "sourceMap": true,
21
+ "outDir": "./dist",
22
+ "rootDir": "./src"
23
+ },
24
+ "include": [
25
+ "src"
26
+ ],
27
+ "exclude": [
28
+ "node_modules",
29
+ "dist"
30
+ ]
31
+ }
@@ -0,0 +1,18 @@
1
+ import { defineConfig } from 'tsup'
2
+
3
+ export default defineConfig({
4
+ entry: { index: 'src/index.ts' },
5
+ outDir: 'dist',
6
+ format: ['esm'],
7
+ dts: true,
8
+ sourcemap: true,
9
+ clean: true,
10
+ treeshake: true,
11
+ external: [
12
+ 'react',
13
+ 'react-dom',
14
+ '@geenius-ui/react',
15
+ '@geenius-tools/logger',
16
+ 'lucide-react',
17
+ ],
18
+ })
@@ -0,0 +1 @@
1
+ # ✦ @geenius-tools/devtools-solidjs\n\n> SolidJS dev-tools UI — floating toolbar, performance panel, inspector, GitHub issues\n\n---\n\n## Overview\nBuilt with Steve Jobs-level minimalism and Jony Ive-level craftsmanship, this package is designed to deliver unparalleled developer experience (DX) and rock-solid performance.\n\n## Installation\n\n```bash\npnpm add @geenius-tools/devtools-solidjs\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius-tools/devtools-solidjs';\n\n// Initialize the module with absolute precision\ninit({\n mode: 'premium',\n});\n```\n\n## Architecture\n- **Zero-config**: It just works.\n- **Strictly Typed**: Fully written in TypeScript for flawless IntelliSense.\n- **Framework Agnostic**: seamlessly integrates into the Geenius ecosystem.\n\n---\n\n*Designed by Antigravity HQ*\n
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@geenius-tools/devtools-solidjs",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "SolidJS dev-tools UI — floating toolbar, performance panel, inspector, GitHub issues",
7
+ "license": "MIT",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "main": "./dist/index.js",
12
+ "module": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "src"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "clean": "rm -rf dist",
27
+ "type-check": "tsc --noEmit",
28
+ "prepublishOnly": "pnpm clean && pnpm build"
29
+ },
30
+ "dependencies": {
31
+ "@geenius-tools/logger": "workspace:*"
32
+ },
33
+ "devDependencies": {
34
+ "@geenius-ui/solid": "workspace:*",
35
+ "lucide-solid": "^0.577.0",
36
+ "solid-js": "^1.9.0",
37
+ "tsup": "^8.5.1",
38
+ "typescript": "~5.9.3"
39
+ },
40
+ "peerDependencies": {
41
+ "@geenius-ui/solid": "workspace:*",
42
+ "lucide-solid": ">=0.300.0",
43
+ "solid-js": "^1.8.0 || ^1.9.0"
44
+ },
45
+ "author": "Antigravity HQ",
46
+ "engines": {
47
+ "node": ">=20.0.0"
48
+ }
49
+ }
@@ -0,0 +1,51 @@
1
+ // @geenius-tools/devtools-solidjs — components/DesignPreview.tsx
2
+ import { createEffect, onMount } from 'solid-js'
3
+ import type { Design } from '../data/designs'
4
+
5
+ export type DesignPreviewProps = {
6
+ designNumber: number
7
+ showGrid?: boolean
8
+ theme?: 'light' | 'dark'
9
+ }
10
+
11
+ /**
12
+ * DesignPreview — renders an HTML design file in an iframe with grid overlay + theme control.
13
+ *
14
+ * Uses Tailwind CSS utility classes.
15
+ */
16
+ export function DesignPreview(props: DesignPreviewProps) {
17
+ let iframeRef: HTMLIFrameElement | undefined
18
+
19
+ const applyTheme = () => {
20
+ try {
21
+ if (iframeRef?.contentDocument) {
22
+ const action = (props.theme ?? 'light') === 'dark' ? 'add' : 'remove'
23
+ iframeRef.contentDocument.documentElement.classList[action]('dark')
24
+ iframeRef.contentDocument.body?.classList[action]('dark')
25
+ }
26
+ } catch {
27
+ // Cross-origin restrictions
28
+ }
29
+ }
30
+
31
+ createEffect(applyTheme)
32
+
33
+ return (
34
+ <div class="relative w-full h-screen">
35
+ {(props.showGrid ?? false) && (
36
+ <div class="absolute inset-0 pointer-events-none z-[100] flex justify-between px-4 max-w-[1400px] mx-auto w-full">
37
+ {Array.from({ length: 12 }).map((_, i) => (
38
+ <div class="h-full w-[calc(100%/12)] border-x border-red-500/20 bg-red-500/5 mx-2" />
39
+ ))}
40
+ </div>
41
+ )}
42
+ <iframe
43
+ ref={iframeRef}
44
+ src={`/designs/design-${props.designNumber}.html`}
45
+ class="w-full h-full border-0"
46
+ title={`Design ${props.designNumber} Preview`}
47
+ onLoad={applyTheme}
48
+ />
49
+ </div>
50
+ )
51
+ }
@@ -0,0 +1,95 @@
1
+ // @geenius-tools/devtools-solidjs — components/DesignSwitcherDropdown.tsx
2
+ import { createSignal, createEffect, onCleanup } from 'solid-js'
3
+ import { For, Show } from 'solid-js/web'
4
+
5
+ export type DesignSwitcherDropdownProps = {
6
+ /** Current active path — used to highlight the active design */
7
+ currentPath: string
8
+ /** Called when user selects a design */
9
+ onNavigate: (path: string) => void
10
+ /** Total number of designs */
11
+ designCount?: number
12
+ }
13
+
14
+ /**
15
+ * DesignSwitcherDropdown — dropdown menu for switching between design previews.
16
+ *
17
+ * Router-agnostic: caller provides currentPath and onNavigate.
18
+ * Uses Tailwind CSS utility classes.
19
+ */
20
+ export function DesignSwitcherDropdown(props: DesignSwitcherDropdownProps) {
21
+ const [isOpen, setIsOpen] = createSignal(false)
22
+ let dropdownRef: HTMLDivElement | undefined
23
+
24
+ const getCurrentDesign = () => {
25
+ const match = props.currentPath.match(/\/dev\/design\/(\d+)/)
26
+ return match ? `Design ${match[1]}` : 'Current Design'
27
+ }
28
+
29
+ createEffect(() => {
30
+ const handleClickOutside = (event: MouseEvent) => {
31
+ if (dropdownRef && !dropdownRef.contains(event.target as Node)) {
32
+ setIsOpen(false)
33
+ }
34
+ }
35
+ document.addEventListener('mousedown', handleClickOutside)
36
+ onCleanup(() => document.removeEventListener('mousedown', handleClickOutside))
37
+ })
38
+
39
+ const handleDesignChange = (path: string) => {
40
+ props.onNavigate(path)
41
+ setIsOpen(false)
42
+ }
43
+
44
+ const designs = () => [
45
+ { label: 'Current Design', path: '/' },
46
+ ...Array.from({ length: props.designCount ?? 21 }, (_, i) => ({
47
+ label: `Design ${i + 1}`,
48
+ path: `/dev/design/${i + 1}`,
49
+ })),
50
+ ]
51
+
52
+ return (
53
+ <div ref={dropdownRef} class="relative">
54
+ <button
55
+ type="button"
56
+ onClick={() => setIsOpen(!isOpen())}
57
+ aria-label="Switch Design"
58
+ class="flex items-center gap-2 px-3 py-2 rounded-lg text-foreground hover:bg-bg-muted transition-colors text-sm font-medium"
59
+ >
60
+ <svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width={2}><circle cx="13.5" cy="6.5" r="0.5" /><circle cx="17.5" cy="10.5" r="0.5" /><circle cx="8.5" cy="7.5" r="0.5" /><circle cx="6.5" cy="12" r="0.5" /><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z" /></svg>
61
+ <span class="hidden sm:inline">{getCurrentDesign()}</span>
62
+ <svg
63
+ class={`w-4 h-4 transition-transform duration-200 ${isOpen() ? 'rotate-180' : ''}`}
64
+ fill="none"
65
+ stroke="currentColor"
66
+ viewBox="0 0 24 24"
67
+ >
68
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M19 9l-7 7-7-7" />
69
+ </svg>
70
+ </button>
71
+
72
+ <Show when={isOpen()}>
73
+ <div class="absolute right-0 mt-2 py-2 bg-card border border-border rounded-lg shadow-xl overflow-hidden min-w-[180px] max-h-[400px] overflow-y-auto z-50">
74
+ <For each={designs()}>
75
+ {(design) => (
76
+ <button
77
+ type="button"
78
+ onClick={() => handleDesignChange(design.path)}
79
+ class={`w-full px-4 py-2.5 text-left text-sm transition-colors ${getCurrentDesign() === design.label
80
+ ? 'bg-primary/10 text-primary font-medium'
81
+ : 'hover:bg-bg-muted text-foreground'
82
+ }`}
83
+ >
84
+ {design.label}
85
+ <Show when={getCurrentDesign() === design.label}>
86
+ <span class="ml-2 text-primary">✓</span>
87
+ </Show>
88
+ </button>
89
+ )}
90
+ </For>
91
+ </div>
92
+ </Show>
93
+ </div>
94
+ )
95
+ }