@svgrid/create 1.0.2 → 1.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.
- package/package.json +1 -1
- package/templates/admin-dashboard/_package.json +1 -1
- package/templates/admin-dashboard/src/app.css +166 -8
- package/templates/admin-dashboard/src/app.html +9 -1
- package/templates/admin-dashboard/src/lib/StatCard.svelte +2 -2
- package/templates/admin-dashboard/src/lib/theme.ts +20 -0
- package/templates/admin-dashboard/src/routes/+layout.svelte +47 -11
- package/templates/admin-dashboard/src/routes/+page.svelte +1 -1
- package/templates/admin-dashboard/src/routes/customers/+page.svelte +1 -1
- package/templates/admin-dashboard/src/routes/orders/+page.svelte +1 -1
package/package.json
CHANGED
|
@@ -1,18 +1,176 @@
|
|
|
1
1
|
@import 'tailwindcss';
|
|
2
2
|
|
|
3
|
-
/*
|
|
4
|
-
|
|
3
|
+
/* Tailwind's `dark:` modifier follows the `data-theme` attribute the layout
|
|
4
|
+
* writes onto <html>, so utility classes and the grid's own --sg-* tokens
|
|
5
|
+
* switch together. */
|
|
6
|
+
@custom-variant dark (&:where(html[data-theme='dark'], html[data-theme='dark'] *));
|
|
7
|
+
|
|
8
|
+
/* ---------------------------------------------------------------------------
|
|
9
|
+
* SvGrid theme tokens. These are the SAME tokens the svgrid.com demos use, so
|
|
10
|
+
* the grid looks identical here out of the box. The dashboard shell reads them
|
|
11
|
+
* through the --app-* aliases below, so the whole app themes as one. Edit any
|
|
12
|
+
* value to rebrand.
|
|
13
|
+
* ------------------------------------------------------------------------- */
|
|
5
14
|
:root {
|
|
6
|
-
--
|
|
7
|
-
--
|
|
8
|
-
--
|
|
9
|
-
--
|
|
10
|
-
--
|
|
11
|
-
--
|
|
15
|
+
--sg-bg: #ffffff;
|
|
16
|
+
--sg-fg: #0f172a;
|
|
17
|
+
--sg-muted: #64748b;
|
|
18
|
+
--sg-border: #e2e8f0;
|
|
19
|
+
--sg-header-bg: #f1f5f9;
|
|
20
|
+
--sg-header-fg: #0f172a;
|
|
21
|
+
--sg-row-alt-bg: #f8fafc;
|
|
22
|
+
--sg-row-hover-bg: #eef2ff;
|
|
23
|
+
--sg-row-height: 36px;
|
|
24
|
+
--sg-focus-ring: 0 0 0 2px #2563eb;
|
|
25
|
+
--sg-selection-bg: #dbeafe;
|
|
26
|
+
--sg-accent: #2563eb;
|
|
27
|
+
--sg-pill-active: #dcfce7;
|
|
28
|
+
--sg-pill-active-fg: #166534;
|
|
29
|
+
--sg-pill-pending: #fef9c3;
|
|
30
|
+
--sg-pill-pending-fg: #854d0e;
|
|
31
|
+
--sg-pill-inactive: #fee2e2;
|
|
32
|
+
--sg-pill-inactive-fg: #991b1b;
|
|
33
|
+
--sg-input-bg: #ffffff;
|
|
34
|
+
--sg-input-border: #cbd5e1;
|
|
35
|
+
|
|
36
|
+
/* Dashboard shell aliases - map the chrome to the grid tokens. */
|
|
37
|
+
--app-bg: var(--sg-row-alt-bg);
|
|
38
|
+
--app-panel: var(--sg-bg);
|
|
39
|
+
--app-border: var(--sg-border);
|
|
40
|
+
--app-fg: var(--sg-fg);
|
|
41
|
+
--app-muted: var(--sg-muted);
|
|
42
|
+
--app-accent: var(--sg-accent);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
html[data-theme='dark'] {
|
|
46
|
+
--sg-bg: #181d27;
|
|
47
|
+
--sg-fg: #e2e8f0;
|
|
48
|
+
--sg-muted: #94a3b8;
|
|
49
|
+
--sg-border: #374151;
|
|
50
|
+
--sg-header-bg: #1e2433;
|
|
51
|
+
--sg-header-fg: #e2e8f0;
|
|
52
|
+
--sg-row-alt-bg: #1b2230;
|
|
53
|
+
--sg-row-hover-bg: #232b3c;
|
|
54
|
+
--sg-selection-bg: #1d3a73;
|
|
55
|
+
--sg-pill-active: #064e3b;
|
|
56
|
+
--sg-pill-active-fg: #6ee7b7;
|
|
57
|
+
--sg-pill-pending: #713f12;
|
|
58
|
+
--sg-pill-pending-fg: #fde68a;
|
|
59
|
+
--sg-pill-inactive: #7f1d1d;
|
|
60
|
+
--sg-pill-inactive-fg: #fecaca;
|
|
61
|
+
--sg-input-bg: #1a2130;
|
|
62
|
+
--sg-input-border: #2c3548;
|
|
63
|
+
--sg-accent: #3b82f6;
|
|
64
|
+
--sg-scrollbar-bg: #1a2130;
|
|
65
|
+
--sg-scrollbar-border: rgba(148, 163, 184, 0.08);
|
|
66
|
+
--sg-scrollbar-arrow: #64748b;
|
|
67
|
+
--sg-scrollbar-arrow-hover: #e2e8f0;
|
|
68
|
+
--sg-scrollbar-arrow-hover-bg: #2a3142;
|
|
69
|
+
--sg-scrollbar-arrow-active: #f1f5f9;
|
|
70
|
+
--sg-scrollbar-arrow-active-bg: #374151;
|
|
71
|
+
--sg-scrollbar-arrow-disabled: #475569;
|
|
72
|
+
--sg-scrollbar-thumb: #475569;
|
|
73
|
+
--sg-scrollbar-thumb-hover: #64748b;
|
|
74
|
+
--sg-scrollbar-thumb-active: #94a3b8;
|
|
75
|
+
/* The shell panels sit a touch above the page in dark mode. */
|
|
76
|
+
--app-panel: #1e2433;
|
|
77
|
+
color-scheme: dark;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
html[data-theme='light'] {
|
|
81
|
+
color-scheme: light;
|
|
12
82
|
}
|
|
13
83
|
|
|
14
84
|
html,
|
|
15
85
|
body {
|
|
16
86
|
background: var(--app-bg);
|
|
17
87
|
color: var(--app-fg);
|
|
88
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial,
|
|
89
|
+
sans-serif;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ---------------------------------------------------------------------------
|
|
93
|
+
* Optional polish. The grid themes itself from the --sg-* tokens above, so it
|
|
94
|
+
* already follows light / dark with no extra CSS. These rules only add table
|
|
95
|
+
* niceties the grid leaves to the app: cell gridlines, zebra striping, row
|
|
96
|
+
* hover, and a styled pagination bar. Delete any you don't want.
|
|
97
|
+
* ------------------------------------------------------------------------- */
|
|
98
|
+
|
|
99
|
+
/* Cell gridlines (the grid draws none by default). */
|
|
100
|
+
.sv-grid-column,
|
|
101
|
+
.sv-grid-cell {
|
|
102
|
+
border-right: 1px solid var(--sg-border) !important;
|
|
103
|
+
border-bottom: 1px solid var(--sg-border) !important;
|
|
104
|
+
}
|
|
105
|
+
.sv-grid-table tr > :last-child.sv-grid-column,
|
|
106
|
+
.sv-grid-table tr > :last-child.sv-grid-cell {
|
|
107
|
+
border-right: 0 !important;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Zebra striping + row hover. */
|
|
111
|
+
.sv-grid-table tbody tr:nth-child(even) .sv-grid-cell {
|
|
112
|
+
background: var(--sg-row-alt-bg) !important;
|
|
113
|
+
}
|
|
114
|
+
.sv-grid-table tbody tr:hover .sv-grid-cell {
|
|
115
|
+
background: var(--sg-row-hover-bg) !important;
|
|
116
|
+
}
|
|
117
|
+
/* Selection wins over zebra (declared after, same specificity). */
|
|
118
|
+
.sv-grid-table tbody tr.sv-grid-row-selected .sv-grid-cell,
|
|
119
|
+
.sv-grid-table tbody tr .sv-grid-cell[data-selected-range='true'] {
|
|
120
|
+
background: var(--sg-selection-bg) !important;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Pagination bar: layout + theme (the grid ships the markup, not the styling). */
|
|
124
|
+
.sv-grid-pagination {
|
|
125
|
+
display: flex !important;
|
|
126
|
+
align-items: center !important;
|
|
127
|
+
justify-content: flex-end !important;
|
|
128
|
+
gap: 24px !important;
|
|
129
|
+
padding: 12px 16px !important;
|
|
130
|
+
border: 1px solid var(--sg-border) !important;
|
|
131
|
+
border-top: 0 !important;
|
|
132
|
+
border-radius: 0 0 6px 6px !important;
|
|
133
|
+
background: var(--sg-header-bg) !important;
|
|
134
|
+
color: var(--sg-fg) !important;
|
|
135
|
+
font-size: 13px !important;
|
|
136
|
+
}
|
|
137
|
+
.sv-grid-pagination-pagesize {
|
|
138
|
+
display: inline-flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
gap: 8px;
|
|
141
|
+
color: var(--sg-muted);
|
|
142
|
+
}
|
|
143
|
+
.sv-grid-pagination-pagesize select,
|
|
144
|
+
.sv-grid-pagination-btn {
|
|
145
|
+
border: 1px solid var(--sg-input-border);
|
|
146
|
+
background: var(--sg-input-bg);
|
|
147
|
+
color: var(--sg-fg);
|
|
148
|
+
border-radius: 5px;
|
|
149
|
+
height: 28px;
|
|
150
|
+
font: inherit;
|
|
151
|
+
font-size: 13px;
|
|
152
|
+
cursor: pointer;
|
|
153
|
+
}
|
|
154
|
+
.sv-grid-pagination-btn {
|
|
155
|
+
display: inline-flex !important;
|
|
156
|
+
align-items: center !important;
|
|
157
|
+
justify-content: center !important;
|
|
158
|
+
width: 28px !important;
|
|
159
|
+
border: 0 !important;
|
|
160
|
+
background: transparent !important;
|
|
161
|
+
font-size: 16px !important;
|
|
162
|
+
}
|
|
163
|
+
.sv-grid-pagination-btn:hover:not(:disabled) {
|
|
164
|
+
background: var(--sg-input-bg) !important;
|
|
165
|
+
color: var(--sg-accent) !important;
|
|
166
|
+
}
|
|
167
|
+
.sv-grid-pagination-btn:disabled {
|
|
168
|
+
color: var(--sg-muted) !important;
|
|
169
|
+
opacity: 0.4 !important;
|
|
170
|
+
cursor: default !important;
|
|
171
|
+
}
|
|
172
|
+
.sv-grid-pagination-nav {
|
|
173
|
+
display: inline-flex;
|
|
174
|
+
align-items: center;
|
|
175
|
+
gap: 4px;
|
|
18
176
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html lang="en" class="h-full">
|
|
2
|
+
<html lang="en" class="h-full" data-theme="dark">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<link rel="icon" href="%sveltekit.assets%/favicon.svg" />
|
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
name="description"
|
|
9
9
|
content="Admin dashboard starter built with SvelteKit and SvGrid - the Svelte 5 data grid."
|
|
10
10
|
/>
|
|
11
|
+
<!-- Apply the saved theme before first paint (defaults to dark) so there's
|
|
12
|
+
no light-mode flash. The layout keeps this in sync afterwards. -->
|
|
13
|
+
<script>
|
|
14
|
+
try {
|
|
15
|
+
var t = localStorage.getItem('theme')
|
|
16
|
+
document.documentElement.setAttribute('data-theme', t === 'light' ? 'light' : 'dark')
|
|
17
|
+
} catch (e) {}
|
|
18
|
+
</script>
|
|
11
19
|
%sveltekit.head%
|
|
12
20
|
</head>
|
|
13
21
|
<body data-sveltekit-preload-data="hover" class="h-full">
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
9
|
<div
|
|
10
|
-
class="rounded-xl border
|
|
11
|
-
style="border-color: var(--app-border);"
|
|
10
|
+
class="rounded-xl border p-4 shadow-sm"
|
|
11
|
+
style="border-color: var(--app-border); background: var(--app-panel);"
|
|
12
12
|
>
|
|
13
13
|
<p class="text-xs font-medium uppercase tracking-wide" style="color: var(--app-muted);">
|
|
14
14
|
{label}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { browser } from '$app/environment'
|
|
2
|
+
|
|
3
|
+
export type Theme = 'light' | 'dark'
|
|
4
|
+
|
|
5
|
+
/** Read the persisted theme. Defaults to dark (matches app.html). */
|
|
6
|
+
export function readTheme(): Theme {
|
|
7
|
+
if (!browser) return 'dark'
|
|
8
|
+
return localStorage.getItem('theme') === 'light' ? 'light' : 'dark'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Write the theme to <html data-theme> and persist it. */
|
|
12
|
+
export function applyTheme(theme: Theme): void {
|
|
13
|
+
if (!browser) return
|
|
14
|
+
document.documentElement.setAttribute('data-theme', theme)
|
|
15
|
+
try {
|
|
16
|
+
localStorage.setItem('theme', theme)
|
|
17
|
+
} catch {
|
|
18
|
+
/* storage may be unavailable (private mode) - ignore */
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import '../app.css'
|
|
3
3
|
import { page } from '$app/state'
|
|
4
|
+
import { onMount } from 'svelte'
|
|
5
|
+
import { readTheme, applyTheme, type Theme } from '$lib/theme'
|
|
4
6
|
|
|
5
7
|
let { children } = $props()
|
|
6
8
|
|
|
9
|
+
// Theme: dark by default (see app.html), toggleable + persisted.
|
|
10
|
+
let theme = $state<Theme>('dark')
|
|
11
|
+
onMount(() => {
|
|
12
|
+
theme = readTheme()
|
|
13
|
+
applyTheme(theme)
|
|
14
|
+
})
|
|
15
|
+
function toggleTheme() {
|
|
16
|
+
theme = theme === 'dark' ? 'light' : 'dark'
|
|
17
|
+
applyTheme(theme)
|
|
18
|
+
}
|
|
19
|
+
|
|
7
20
|
const nav = [
|
|
8
21
|
{ href: '/', label: 'Overview', icon: '▦' },
|
|
9
22
|
{ href: '/orders', label: 'Orders', icon: '🧾' },
|
|
@@ -18,8 +31,8 @@
|
|
|
18
31
|
<div class="flex h-full min-h-screen">
|
|
19
32
|
<!-- Sidebar -->
|
|
20
33
|
<aside
|
|
21
|
-
class="hidden w-60 shrink-0 flex-col border-r
|
|
22
|
-
style="border-color: var(--app-border);"
|
|
34
|
+
class="hidden w-60 shrink-0 flex-col border-r md:flex"
|
|
35
|
+
style="border-color: var(--app-border); background: var(--app-panel);"
|
|
23
36
|
>
|
|
24
37
|
<div class="flex items-center gap-2 px-5 py-4">
|
|
25
38
|
<span
|
|
@@ -57,19 +70,42 @@
|
|
|
57
70
|
<!-- Main -->
|
|
58
71
|
<div class="flex min-w-0 flex-1 flex-col">
|
|
59
72
|
<header
|
|
60
|
-
class="flex items-center justify-between border-b
|
|
61
|
-
style="border-color: var(--app-border);"
|
|
73
|
+
class="flex items-center justify-between border-b px-6 py-3"
|
|
74
|
+
style="border-color: var(--app-border); background: var(--app-panel);"
|
|
62
75
|
>
|
|
63
76
|
<h1 class="text-base font-semibold" style="color: var(--app-fg);">
|
|
64
77
|
{nav.find((n) => isActive(n.href))?.label ?? 'Dashboard'}
|
|
65
78
|
</h1>
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
<div class="flex items-center gap-2">
|
|
80
|
+
<button
|
|
81
|
+
type="button"
|
|
82
|
+
onclick={toggleTheme}
|
|
83
|
+
title={theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
|
|
84
|
+
aria-label="Toggle theme"
|
|
85
|
+
class="grid h-9 w-9 place-items-center rounded-lg border"
|
|
86
|
+
style="border-color: var(--app-border); color: var(--app-fg);"
|
|
87
|
+
>
|
|
88
|
+
{#if theme === 'dark'}
|
|
89
|
+
<!-- sun -->
|
|
90
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
91
|
+
<circle cx="12" cy="12" r="4" />
|
|
92
|
+
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41" />
|
|
93
|
+
</svg>
|
|
94
|
+
{:else}
|
|
95
|
+
<!-- moon -->
|
|
96
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
97
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
|
98
|
+
</svg>
|
|
99
|
+
{/if}
|
|
100
|
+
</button>
|
|
101
|
+
<a
|
|
102
|
+
href="https://www.svgrid.com/docs"
|
|
103
|
+
target="_blank"
|
|
104
|
+
rel="noopener"
|
|
105
|
+
class="rounded-lg border px-3 py-1.5 text-sm font-medium"
|
|
106
|
+
style="border-color: var(--app-border); color: var(--app-fg);">Docs</a
|
|
107
|
+
>
|
|
108
|
+
</div>
|
|
73
109
|
</header>
|
|
74
110
|
|
|
75
111
|
<main class="min-w-0 flex-1 p-6">
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
<StatCard label="Active customers" value={kpis.customers.toString()} />
|
|
54
54
|
</div>
|
|
55
55
|
|
|
56
|
-
<div class="rounded-xl border
|
|
56
|
+
<div class="rounded-xl border p-4 shadow-sm" style="border-color: var(--app-border); background: var(--app-panel);">
|
|
57
57
|
<div class="mb-3 flex items-center justify-between">
|
|
58
58
|
<h2 class="text-sm font-semibold" style="color: var(--app-fg);">Recent orders</h2>
|
|
59
59
|
<a class="text-sm font-medium" style="color: var(--app-accent);" href="/orders">View all →</a>
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
{rows.length} customers · drag the <strong>Plan</strong> column into the group bar to group by plan.
|
|
53
53
|
</p>
|
|
54
54
|
|
|
55
|
-
<div class="min-h-0 flex-1 rounded-xl border
|
|
55
|
+
<div class="min-h-0 flex-1 rounded-xl border p-2 shadow-sm" style="border-color: var(--app-border); background: var(--app-panel);">
|
|
56
56
|
{#if browser}
|
|
57
57
|
<SvGrid
|
|
58
58
|
data={rows}
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
</p>
|
|
63
63
|
</div>
|
|
64
64
|
|
|
65
|
-
<div class="min-h-0 flex-1 rounded-xl border
|
|
65
|
+
<div class="min-h-0 flex-1 rounded-xl border p-2 shadow-sm" style="border-color: var(--app-border); background: var(--app-panel);">
|
|
66
66
|
{#if browser}
|
|
67
67
|
<SvGrid
|
|
68
68
|
data={rows}
|