create-mantiq 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/kits/react.ts +45 -18
- package/src/kits/svelte.ts +50 -40
- package/src/kits/vue.ts +51 -36
- package/src/ui/shadcn.ts +25 -0
package/package.json
CHANGED
package/src/kits/react.ts
CHANGED
|
@@ -275,6 +275,8 @@ export default function Dashboard({ appName = '${ctx.name}', currentUser, users:
|
|
|
275
275
|
const [users, setUsers] = useState<User[]>(initialUsers ?? [])
|
|
276
276
|
const [loading, setLoading] = useState(!initialUsers?.length)
|
|
277
277
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
278
|
+
const [collapsed, setCollapsed] = useState(false)
|
|
279
|
+
const [accountOpen, setAccountOpen] = useState(false)
|
|
278
280
|
const [isDark, setIsDark] = useState(() =>
|
|
279
281
|
typeof document !== 'undefined' ? document.documentElement.classList.contains('dark') : true
|
|
280
282
|
)
|
|
@@ -307,34 +309,39 @@ export default function Dashboard({ appName = '${ctx.name}', currentUser, users:
|
|
|
307
309
|
{sidebarOpen && <div className="fixed inset-0 bg-black/50 z-30 lg:hidden" onClick={() => setSidebarOpen(false)} />}
|
|
308
310
|
|
|
309
311
|
{/* Sidebar */}
|
|
310
|
-
<aside className={\`fixed inset-y-0 left-0 w-60 bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800 flex flex-col z-40 transition-
|
|
312
|
+
<aside className={\`fixed inset-y-0 left-0 \${sidebarOpen ? 'w-60 translate-x-0' : '-translate-x-full'} \${collapsed ? 'lg:w-16' : 'lg:w-60'} bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800 flex flex-col z-40 transition-all duration-200 lg:translate-x-0\`}>
|
|
311
313
|
<div className="h-14 flex items-center px-5 border-b border-gray-200 dark:border-gray-800">
|
|
312
|
-
<span className=
|
|
314
|
+
<span className={\`text-sm font-semibold text-gray-900 dark:text-white \${collapsed ? 'lg:hidden' : ''}\`}>{appName}</span>
|
|
313
315
|
</div>
|
|
314
316
|
<nav className="flex-1 px-3 py-3 space-y-0.5">
|
|
315
317
|
<a href="/dashboard" onClick={() => setSidebarOpen(false)} className="flex items-center gap-2.5 px-2.5 py-2 rounded-lg text-sm font-medium bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white">
|
|
316
|
-
<svg className="w-4 h-4 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" /></svg>
|
|
317
|
-
Dashboard
|
|
318
|
+
<svg className="w-4 h-4 text-gray-500 dark:text-gray-400 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" /></svg>
|
|
319
|
+
<span className={\`\${collapsed ? 'lg:hidden' : ''}\`}>Dashboard</span>
|
|
318
320
|
</a>
|
|
319
321
|
<a href="#users-section" onClick={(e) => { e.preventDefault(); setSidebarOpen(false); document.getElementById('users-section')?.scrollIntoView({ behavior: 'smooth' }) }} className="flex items-center gap-2.5 px-2.5 py-2 rounded-lg text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
|
|
320
|
-
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" /></svg>
|
|
321
|
-
Users
|
|
322
|
+
<svg className="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" /></svg>
|
|
323
|
+
<span className={\`\${collapsed ? 'lg:hidden' : ''}\`}>Users</span>
|
|
322
324
|
</a>
|
|
323
325
|
</nav>
|
|
326
|
+
|
|
327
|
+
{/* Collapse toggle */}
|
|
328
|
+
<div className="px-3 py-2 hidden lg:block">
|
|
329
|
+
<button onClick={() => setCollapsed(!collapsed)} className="flex items-center justify-center w-full px-2.5 py-2 rounded-lg text-sm text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
|
|
330
|
+
<span className="text-xs font-mono">{collapsed ? '>>' : '<<'}</span>
|
|
331
|
+
</button>
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
{/* Bottom links */}
|
|
324
335
|
<div className="px-3 py-3 mt-auto border-t border-gray-200 dark:border-gray-800 space-y-0.5">
|
|
325
|
-
<a href="/
|
|
326
|
-
<svg className="w-4 h-4" fill="
|
|
327
|
-
|
|
328
|
-
</a>
|
|
329
|
-
<a href="/api/ping" onClick={() => setSidebarOpen(false)} className="flex items-center gap-2.5 px-2.5 py-2 rounded-lg text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
|
|
330
|
-
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
|
|
331
|
-
API Ping
|
|
336
|
+
<a href="https://github.com/mantiqjs/mantiq" target="_blank" rel="noopener" className="flex items-center gap-2.5 px-2.5 py-2 rounded-lg text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
|
|
337
|
+
<svg className="w-4 h-4 shrink-0" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" /></svg>
|
|
338
|
+
<span className={\`\${collapsed ? 'lg:hidden' : ''}\`}>Documentation</span>
|
|
332
339
|
</a>
|
|
333
340
|
</div>
|
|
334
341
|
</aside>
|
|
335
342
|
|
|
336
343
|
{/* Main */}
|
|
337
|
-
<div className=
|
|
344
|
+
<div className={\`flex-1 \${collapsed ? 'lg:ml-16' : 'lg:ml-60'} transition-all duration-200\`}>
|
|
338
345
|
{/* Top bar */}
|
|
339
346
|
<header className="h-14 border-b border-gray-200 dark:border-gray-800 bg-white/90 dark:bg-gray-950/90 backdrop-blur-md sticky top-0 z-20 flex items-center justify-between px-6">
|
|
340
347
|
<div className="flex items-center">
|
|
@@ -355,10 +362,30 @@ export default function Dashboard({ appName = '${ctx.name}', currentUser, users:
|
|
|
355
362
|
</svg>
|
|
356
363
|
)}
|
|
357
364
|
</button>
|
|
358
|
-
|
|
359
|
-
<
|
|
360
|
-
|
|
361
|
-
|
|
365
|
+
{/* Account dropdown */}
|
|
366
|
+
<div className="relative">
|
|
367
|
+
<button onClick={() => setAccountOpen(!accountOpen)} className="flex items-center gap-2 rounded-lg px-2 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
|
|
368
|
+
<div className="w-7 h-7 rounded-full bg-emerald-100 dark:bg-emerald-900/30 flex items-center justify-center text-xs font-medium text-emerald-700 dark:text-emerald-300">
|
|
369
|
+
{currentUser?.name?.split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2)}
|
|
370
|
+
</div>
|
|
371
|
+
<span className="text-sm text-gray-700 dark:text-gray-300 hidden sm:block">{currentUser?.name}</span>
|
|
372
|
+
<svg className="w-3.5 h-3.5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
|
|
373
|
+
</button>
|
|
374
|
+
{accountOpen && (
|
|
375
|
+
<>
|
|
376
|
+
<div className="fixed inset-0 z-40" onClick={() => setAccountOpen(false)} />
|
|
377
|
+
<div className="absolute right-0 top-full mt-1 w-48 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-lg shadow-lg py-1 z-50">
|
|
378
|
+
<div className="px-3 py-2 border-b border-gray-100 dark:border-gray-800">
|
|
379
|
+
<div className="text-sm font-medium text-gray-900 dark:text-white">{currentUser?.name}</div>
|
|
380
|
+
<div className="text-xs text-gray-500 dark:text-gray-400">{currentUser?.email}</div>
|
|
381
|
+
</div>
|
|
382
|
+
<button onClick={handleLogout} className="w-full text-left px-3 py-2 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white transition-colors">
|
|
383
|
+
Sign out
|
|
384
|
+
</button>
|
|
385
|
+
</div>
|
|
386
|
+
</>
|
|
387
|
+
)}
|
|
388
|
+
</div>
|
|
362
389
|
</div>
|
|
363
390
|
</header>
|
|
364
391
|
|
package/src/kits/svelte.ts
CHANGED
|
@@ -297,15 +297,10 @@ export function render(_url: string, data?: Record<string, any>) {
|
|
|
297
297
|
let loading = !users?.length
|
|
298
298
|
let isDark = true
|
|
299
299
|
let sidebarOpen = false
|
|
300
|
+
let collapsed = false
|
|
301
|
+
let accountOpen = false
|
|
300
302
|
|
|
301
|
-
|
|
302
|
-
{ label: 'Dashboard', icon: 'grid', href: '/dashboard', active: true },
|
|
303
|
-
]
|
|
304
|
-
|
|
305
|
-
const bottomLinks = [
|
|
306
|
-
{ label: 'Heartbeat', icon: 'heart', href: '/heartbeat' },
|
|
307
|
-
{ label: 'API Ping', icon: 'zap', href: '/api/ping' },
|
|
308
|
-
]
|
|
303
|
+
$: userInitials = currentUser?.name?.split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2) ?? ''
|
|
309
304
|
|
|
310
305
|
async function fetchUsers() {
|
|
311
306
|
loading = true
|
|
@@ -340,53 +335,48 @@ export function render(_url: string, data?: Record<string, any>) {
|
|
|
340
335
|
{/if}
|
|
341
336
|
|
|
342
337
|
<!-- Sidebar -->
|
|
343
|
-
<aside class="fixed inset-y-0 left-0 w-60 bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800 flex flex-col z-40 transition-
|
|
338
|
+
<aside class="fixed inset-y-0 left-0 {sidebarOpen ? 'w-60 translate-x-0' : '-translate-x-full'} {collapsed ? 'lg:w-16' : 'lg:w-60'} bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800 flex flex-col z-40 transition-all duration-200 lg:translate-x-0">
|
|
344
339
|
<!-- App name -->
|
|
345
340
|
<div class="h-14 flex items-center px-5 border-b border-gray-200 dark:border-gray-800">
|
|
346
341
|
<div class="flex items-center gap-2.5">
|
|
347
|
-
<div class="w-7 h-7 rounded-lg bg-emerald-600 flex items-center justify-center">
|
|
342
|
+
<div class="w-7 h-7 rounded-lg bg-emerald-600 flex items-center justify-center shrink-0">
|
|
348
343
|
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
|
|
349
344
|
</div>
|
|
350
|
-
<span class="text-sm font-bold text-gray-900 dark:text-white">{appName}</span>
|
|
345
|
+
<span class="text-sm font-bold text-gray-900 dark:text-white {collapsed ? 'lg:hidden' : ''}">{appName}</span>
|
|
351
346
|
</div>
|
|
352
347
|
</div>
|
|
353
348
|
|
|
354
349
|
<!-- Nav items -->
|
|
355
350
|
<nav class="flex-1 px-3 py-4 space-y-1 overflow-y-auto">
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800'}">
|
|
363
|
-
{#if item.icon === 'grid'}
|
|
364
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" /></svg>
|
|
365
|
-
{/if}
|
|
366
|
-
{item.label}
|
|
367
|
-
</a>
|
|
368
|
-
{/each}
|
|
351
|
+
<a href="/dashboard"
|
|
352
|
+
on:click={() => sidebarOpen = false}
|
|
353
|
+
class="flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors bg-emerald-50 dark:bg-emerald-500/10 text-emerald-700 dark:text-emerald-300">
|
|
354
|
+
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" /></svg>
|
|
355
|
+
<span class="{collapsed ? 'lg:hidden' : ''}">Dashboard</span>
|
|
356
|
+
</a>
|
|
369
357
|
</nav>
|
|
370
358
|
|
|
359
|
+
<!-- Collapse toggle -->
|
|
360
|
+
<div class="px-3 py-2 hidden lg:block">
|
|
361
|
+
<button on:click={() => collapsed = !collapsed} class="flex items-center justify-center w-full px-2.5 py-2 rounded-lg text-sm text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
|
|
362
|
+
<span class="text-xs font-mono">{collapsed ? '>>' : '<<'}</span>
|
|
363
|
+
</button>
|
|
364
|
+
</div>
|
|
365
|
+
|
|
371
366
|
<!-- Bottom links -->
|
|
372
|
-
<div class="px-3 py-4 border-t border-gray-200 dark:border-gray-800 space-y-1">
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
|
|
381
|
-
{/if}
|
|
382
|
-
{link.label}
|
|
383
|
-
</a>
|
|
384
|
-
{/each}
|
|
367
|
+
<div class="px-3 py-4 mt-auto border-t border-gray-200 dark:border-gray-800 space-y-1">
|
|
368
|
+
<a href="https://github.com/mantiqjs/mantiq"
|
|
369
|
+
target="_blank"
|
|
370
|
+
rel="noopener"
|
|
371
|
+
class="flex items-center gap-3 px-3 py-2 rounded-lg text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
|
|
372
|
+
<svg class="w-4 h-4 shrink-0" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" /></svg>
|
|
373
|
+
<span class="{collapsed ? 'lg:hidden' : ''}">Documentation</span>
|
|
374
|
+
</a>
|
|
385
375
|
</div>
|
|
386
376
|
</aside>
|
|
387
377
|
|
|
388
378
|
<!-- Main content -->
|
|
389
|
-
<div class="flex-1 lg:ml-60 flex flex-col min-h-screen">
|
|
379
|
+
<div class="flex-1 {collapsed ? 'lg:ml-16' : 'lg:ml-60'} flex flex-col min-h-screen transition-all duration-200">
|
|
390
380
|
<!-- Header -->
|
|
391
381
|
<header class="h-14 flex items-center justify-between px-6 border-b border-gray-200 dark:border-gray-800 bg-white/80 dark:bg-gray-950/90 backdrop-blur-md sticky top-0 z-20">
|
|
392
382
|
<div class="flex items-center">
|
|
@@ -403,8 +393,28 @@ export function render(_url: string, data?: Record<string, any>) {
|
|
|
403
393
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg>
|
|
404
394
|
{/if}
|
|
405
395
|
</button>
|
|
406
|
-
|
|
407
|
-
<
|
|
396
|
+
<!-- Account dropdown -->
|
|
397
|
+
<div class="relative">
|
|
398
|
+
<button on:click={() => accountOpen = !accountOpen} class="flex items-center gap-2 rounded-lg px-2 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
|
|
399
|
+
<div class="w-7 h-7 rounded-full bg-emerald-100 dark:bg-emerald-900/30 flex items-center justify-center text-xs font-medium text-emerald-700 dark:text-emerald-300">
|
|
400
|
+
{userInitials}
|
|
401
|
+
</div>
|
|
402
|
+
<span class="text-sm text-gray-700 dark:text-gray-300 hidden sm:block">{currentUser?.name}</span>
|
|
403
|
+
<svg class="w-3.5 h-3.5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /></svg>
|
|
404
|
+
</button>
|
|
405
|
+
{#if accountOpen}
|
|
406
|
+
<div class="fixed inset-0 z-40" on:click={() => accountOpen = false}></div>
|
|
407
|
+
<div class="absolute right-0 top-full mt-1 w-48 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-lg shadow-lg py-1 z-50">
|
|
408
|
+
<div class="px-3 py-2 border-b border-gray-100 dark:border-gray-800">
|
|
409
|
+
<div class="text-sm font-medium text-gray-900 dark:text-white">{currentUser?.name}</div>
|
|
410
|
+
<div class="text-xs text-gray-500 dark:text-gray-400">{currentUser?.email}</div>
|
|
411
|
+
</div>
|
|
412
|
+
<button on:click={handleLogout} class="w-full text-left px-3 py-2 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white transition-colors">
|
|
413
|
+
Sign out
|
|
414
|
+
</button>
|
|
415
|
+
</div>
|
|
416
|
+
{/if}
|
|
417
|
+
</div>
|
|
408
418
|
</div>
|
|
409
419
|
</header>
|
|
410
420
|
|
package/src/kits/vue.ts
CHANGED
|
@@ -331,7 +331,7 @@ async function handleSubmit() {
|
|
|
331
331
|
`,
|
|
332
332
|
|
|
333
333
|
'src/pages/Dashboard.vue': `<script setup lang="ts">
|
|
334
|
-
import { ref, onMounted, inject } from 'vue'
|
|
334
|
+
import { ref, computed, onMounted, inject } from 'vue'
|
|
335
335
|
import { api, post } from '../lib/api.ts'
|
|
336
336
|
|
|
337
337
|
const props = defineProps<{
|
|
@@ -346,8 +346,14 @@ const users = ref(props.users ?? [])
|
|
|
346
346
|
const loading = ref(!props.users?.length)
|
|
347
347
|
const isDark = ref(true)
|
|
348
348
|
const sidebarOpen = ref(false)
|
|
349
|
+
const collapsed = ref(false)
|
|
350
|
+
const accountOpen = ref(false)
|
|
349
351
|
const nav = inject<(href: string) => void>('navigate', props.navigate)
|
|
350
352
|
|
|
353
|
+
const userInitials = computed(() => {
|
|
354
|
+
return props.currentUser?.name?.split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2) ?? ''
|
|
355
|
+
})
|
|
356
|
+
|
|
351
357
|
function toggleTheme() {
|
|
352
358
|
const dark = document.documentElement.classList.toggle('dark')
|
|
353
359
|
isDark.value = dark
|
|
@@ -379,12 +385,15 @@ onMounted(() => {
|
|
|
379
385
|
|
|
380
386
|
<!-- Sidebar -->
|
|
381
387
|
<aside
|
|
382
|
-
:class="
|
|
383
|
-
|
|
388
|
+
:class="[
|
|
389
|
+
sidebarOpen ? 'w-60 translate-x-0' : '-translate-x-full',
|
|
390
|
+
collapsed ? 'lg:w-16' : 'lg:w-60'
|
|
391
|
+
]"
|
|
392
|
+
class="fixed inset-y-0 left-0 bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800 flex flex-col z-40 transition-all duration-200 lg:translate-x-0"
|
|
384
393
|
>
|
|
385
394
|
<!-- App name -->
|
|
386
395
|
<div class="h-14 flex items-center px-5 border-b border-gray-200 dark:border-gray-800">
|
|
387
|
-
<span class="text-sm font-bold text-gray-900 dark:text-white">{{ appName }}</span>
|
|
396
|
+
<span :class="collapsed ? 'lg:hidden' : ''" class="text-sm font-bold text-gray-900 dark:text-white">{{ appName }}</span>
|
|
388
397
|
</div>
|
|
389
398
|
|
|
390
399
|
<!-- Navigation -->
|
|
@@ -394,43 +403,36 @@ onMounted(() => {
|
|
|
394
403
|
@click="sidebarOpen = false"
|
|
395
404
|
class="flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium bg-emerald-50 dark:bg-emerald-500/10 text-emerald-700 dark:text-emerald-300"
|
|
396
405
|
>
|
|
397
|
-
|
|
398
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
406
|
+
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
399
407
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
|
|
400
408
|
</svg>
|
|
401
|
-
Dashboard
|
|
409
|
+
<span :class="collapsed ? 'lg:hidden' : ''">Dashboard</span>
|
|
402
410
|
</a>
|
|
403
411
|
</nav>
|
|
404
412
|
|
|
413
|
+
<!-- Collapse toggle -->
|
|
414
|
+
<div class="px-3 py-2 hidden lg:block">
|
|
415
|
+
<button @click="collapsed = !collapsed" class="flex items-center justify-center w-full px-2.5 py-2 rounded-lg text-sm text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
|
|
416
|
+
<span class="text-xs font-mono">{{ collapsed ? '>>' : '<<' }}</span>
|
|
417
|
+
</button>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
405
420
|
<!-- Bottom links -->
|
|
406
|
-
<div class="px-3 py-4 border-t border-gray-200 dark:border-gray-800 space-y-1">
|
|
407
|
-
<a
|
|
408
|
-
href="/heartbeat"
|
|
409
|
-
@click="sidebarOpen = false"
|
|
410
|
-
class="flex items-center gap-3 px-3 py-2 rounded-lg text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
|
411
|
-
>
|
|
412
|
-
<!-- Heart/pulse icon -->
|
|
413
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
414
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
|
|
415
|
-
</svg>
|
|
416
|
-
Heartbeat
|
|
417
|
-
</a>
|
|
421
|
+
<div class="px-3 py-4 mt-auto border-t border-gray-200 dark:border-gray-800 space-y-1">
|
|
418
422
|
<a
|
|
419
|
-
href="/
|
|
420
|
-
|
|
423
|
+
href="https://github.com/mantiqjs/mantiq"
|
|
424
|
+
target="_blank"
|
|
425
|
+
rel="noopener"
|
|
421
426
|
class="flex items-center gap-3 px-3 py-2 rounded-lg text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
|
422
427
|
>
|
|
423
|
-
|
|
424
|
-
<
|
|
425
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.858 15.355-5.858 21.213 0" />
|
|
426
|
-
</svg>
|
|
427
|
-
API Ping
|
|
428
|
+
<svg class="w-4 h-4 shrink-0" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" /></svg>
|
|
429
|
+
<span :class="collapsed ? 'lg:hidden' : ''">Documentation</span>
|
|
428
430
|
</a>
|
|
429
431
|
</div>
|
|
430
432
|
</aside>
|
|
431
433
|
|
|
432
434
|
<!-- Main area -->
|
|
433
|
-
<div class="
|
|
435
|
+
<div :class="collapsed ? 'lg:ml-16' : 'lg:ml-60'" class="flex-1 flex flex-col min-h-screen transition-all duration-200">
|
|
434
436
|
<!-- Header -->
|
|
435
437
|
<header class="h-14 flex items-center justify-between px-6 border-b border-gray-200 dark:border-gray-800 bg-white/80 dark:bg-gray-950/90 backdrop-blur-md sticky top-0 z-20">
|
|
436
438
|
<div class="flex items-center">
|
|
@@ -446,22 +448,35 @@ onMounted(() => {
|
|
|
446
448
|
class="p-2 rounded-lg text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
|
447
449
|
:title="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
|
|
448
450
|
>
|
|
449
|
-
<!-- Sun icon (shown in dark mode) -->
|
|
450
451
|
<svg v-if="isDark" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
451
452
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
|
452
453
|
</svg>
|
|
453
|
-
<!-- Moon icon (shown in light mode) -->
|
|
454
454
|
<svg v-else class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
455
455
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
|
456
456
|
</svg>
|
|
457
457
|
</button>
|
|
458
|
-
|
|
459
|
-
<
|
|
460
|
-
@click="
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
458
|
+
<!-- Account dropdown -->
|
|
459
|
+
<div class="relative">
|
|
460
|
+
<button @click="accountOpen = !accountOpen" class="flex items-center gap-2 rounded-lg px-2 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
|
|
461
|
+
<div class="w-7 h-7 rounded-full bg-emerald-100 dark:bg-emerald-900/30 flex items-center justify-center text-xs font-medium text-emerald-700 dark:text-emerald-300">
|
|
462
|
+
{{ userInitials }}
|
|
463
|
+
</div>
|
|
464
|
+
<span class="text-sm text-gray-700 dark:text-gray-300 hidden sm:block">{{ currentUser?.name }}</span>
|
|
465
|
+
<svg class="w-3.5 h-3.5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /></svg>
|
|
466
|
+
</button>
|
|
467
|
+
<template v-if="accountOpen">
|
|
468
|
+
<div class="fixed inset-0 z-40" @click="accountOpen = false" />
|
|
469
|
+
<div class="absolute right-0 top-full mt-1 w-48 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-lg shadow-lg py-1 z-50">
|
|
470
|
+
<div class="px-3 py-2 border-b border-gray-100 dark:border-gray-800">
|
|
471
|
+
<div class="text-sm font-medium text-gray-900 dark:text-white">{{ currentUser?.name }}</div>
|
|
472
|
+
<div class="text-xs text-gray-500 dark:text-gray-400">{{ currentUser?.email }}</div>
|
|
473
|
+
</div>
|
|
474
|
+
<button @click="handleLogout" class="w-full text-left px-3 py-2 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white transition-colors">
|
|
475
|
+
Sign out
|
|
476
|
+
</button>
|
|
477
|
+
</div>
|
|
478
|
+
</template>
|
|
479
|
+
</div>
|
|
465
480
|
</div>
|
|
466
481
|
</header>
|
|
467
482
|
|
package/src/ui/shadcn.ts
CHANGED
|
@@ -6,6 +6,31 @@ export function getShadcnTemplates(ctx: TemplateContext): {
|
|
|
6
6
|
} {
|
|
7
7
|
return {
|
|
8
8
|
files: {
|
|
9
|
+
// ── vite config override with @/ resolve alias ────────────────────────
|
|
10
|
+
'vite.config.ts': `import { defineConfig } from 'vite'
|
|
11
|
+
import react from '@vitejs/plugin-react'
|
|
12
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
13
|
+
import path from 'path'
|
|
14
|
+
|
|
15
|
+
export default defineConfig({
|
|
16
|
+
plugins: [react(), tailwindcss()],
|
|
17
|
+
publicDir: false,
|
|
18
|
+
resolve: {
|
|
19
|
+
alias: {
|
|
20
|
+
'@': path.resolve(__dirname, './src'),
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
build: {
|
|
24
|
+
outDir: 'public/build',
|
|
25
|
+
manifest: true,
|
|
26
|
+
emptyOutDir: true,
|
|
27
|
+
rollupOptions: {
|
|
28
|
+
input: ['src/main.tsx', 'src/style.css'],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
`,
|
|
33
|
+
|
|
9
34
|
// ── tsconfig override with @/ path alias ──────────────────────────────
|
|
10
35
|
'tsconfig.json': JSON.stringify({
|
|
11
36
|
compilerOptions: {
|