@svgrid/create 1.0.2 → 1.0.3
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 +538 -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,548 @@
|
|
|
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
|
+
/* ---------------------------------------------------------------------------
|
|
94
|
+
* Grid theming overrides. SvGrid ships hard-coded light-mode colors on its
|
|
95
|
+
* internals (.sv-grid-cell, .sv-grid-head, menus, pager, ...). These rules -
|
|
96
|
+
* the same ones the svgrid.com demos use - route them through the --sg-* tokens
|
|
97
|
+
* above so the grid themes (and goes dark) with the rest of the app.
|
|
98
|
+
* ------------------------------------------------------------------------- */
|
|
99
|
+
/*
|
|
100
|
+
* The SvGrid component ships with hard-coded light-mode colors on
|
|
101
|
+
* .sv-grid-cell / .sv-grid-head / .sv-grid-foot / .sv-grid-shell. We
|
|
102
|
+
* override them to use our CSS custom properties so dark mode works.
|
|
103
|
+
*/
|
|
104
|
+
.sv-grid-shell {
|
|
105
|
+
border-bottom: 1px solid var(--sg-border) !important;
|
|
106
|
+
}
|
|
107
|
+
.sv-grid-column,
|
|
108
|
+
.sv-grid-cell {
|
|
109
|
+
background: var(--sg-bg) !important;
|
|
110
|
+
color: var(--sg-fg);
|
|
111
|
+
/* Visible row + column borders. The table uses border-collapse: separate
|
|
112
|
+
* so we draw right + bottom only (no doubles between adjacent cells). */
|
|
113
|
+
border-right: 1px solid var(--sg-border) !important;
|
|
114
|
+
border-bottom: 1px solid var(--sg-border) !important;
|
|
115
|
+
}
|
|
116
|
+
/* Last column has no right border so it doesn't double up against the grid edge */
|
|
117
|
+
.sv-grid-table tr > :last-child.sv-grid-column,
|
|
118
|
+
.sv-grid-table tr > :last-child.sv-grid-cell {
|
|
119
|
+
border-right: 0 !important;
|
|
120
|
+
}
|
|
121
|
+
.sv-grid-head {
|
|
122
|
+
background: var(--sg-header-bg) !important;
|
|
123
|
+
color: var(--sg-header-fg);
|
|
124
|
+
}
|
|
125
|
+
.sv-grid-foot {
|
|
126
|
+
background: var(--sg-header-bg) !important;
|
|
127
|
+
color: var(--sg-header-fg);
|
|
128
|
+
}
|
|
129
|
+
.sv-grid-scrollbar-corner,
|
|
130
|
+
.sv-grid-scrollbar-corner-br {
|
|
131
|
+
background: var(--sg-header-bg) !important;
|
|
132
|
+
border-bottom-color: var(--sg-border) !important;
|
|
133
|
+
}
|
|
134
|
+
/* Hide the corner when its accompanying scrollbar is absent. */
|
|
135
|
+
.sv-grid-no-scroll .sv-grid-scrollbar-corner,
|
|
136
|
+
.sv-grid-no-scroll .sv-grid-scrollbar {
|
|
137
|
+
display: none !important;
|
|
138
|
+
}
|
|
139
|
+
/* Tone down the active-cell outline; SvGrid's default is 2px solid #0b63f3 which
|
|
140
|
+
reads as a hard rectangle on otherwise calm rows. */
|
|
141
|
+
.sv-grid-cell-active {
|
|
142
|
+
outline-color: var(--sg-accent) !important;
|
|
143
|
+
outline-offset: -1px !important;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Filter row inputs + cell editor inputs + column menu inputs */
|
|
147
|
+
.sv-grid-column-filter,
|
|
148
|
+
.sv-grid-filter-value,
|
|
149
|
+
.sv-grid-cell-editor,
|
|
150
|
+
.sv-grid-menu-search,
|
|
151
|
+
.sv-grid-menu-condition-value,
|
|
152
|
+
.sv-grid-global-filter input {
|
|
153
|
+
background: var(--sg-input-bg) !important;
|
|
154
|
+
color: var(--sg-fg) !important;
|
|
155
|
+
border: 1px solid var(--sg-input-border) !important;
|
|
156
|
+
border-radius: 4px !important;
|
|
157
|
+
padding: 0 8px !important;
|
|
158
|
+
box-sizing: border-box !important;
|
|
159
|
+
}
|
|
160
|
+
/*
|
|
161
|
+
* SvGrid's `.sv-grid-menu-condition-value` is styled `flex: 1 1 0`, which is
|
|
162
|
+
* only meaningful inside a flex parent. Since the redesigned menu uses block
|
|
163
|
+
* layout, force the input to span the full menu width.
|
|
164
|
+
*/
|
|
165
|
+
.sv-grid-menu-condition-value,
|
|
166
|
+
.sv-grid-menu-search {
|
|
167
|
+
width: 100% !important;
|
|
168
|
+
}
|
|
169
|
+
.sv-grid-column-filter::placeholder,
|
|
170
|
+
.sv-grid-filter-value::placeholder,
|
|
171
|
+
.sv-grid-cell-editor::placeholder,
|
|
172
|
+
.sv-grid-menu-search::placeholder {
|
|
173
|
+
color: var(--sg-muted);
|
|
174
|
+
opacity: 0.7;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* Filter row sizing - make the row read as a single calm bar */
|
|
178
|
+
.sv-grid-column-filter {
|
|
179
|
+
height: 28px !important;
|
|
180
|
+
width: 100% !important;
|
|
181
|
+
font-size: 0.8125rem !important;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/*
|
|
185
|
+
* Override SvGrid's hard-coded #0b63f3 focus outline on filter inputs and the
|
|
186
|
+
* inline cell editor. The library sets:
|
|
187
|
+
* outline: 2px solid #0b63f3; outline-offset: -2px; border-color: #0b63f3
|
|
188
|
+
* which is harsh on a dark background. Replace with a calm accent-tinted
|
|
189
|
+
* box-shadow that respects the active theme.
|
|
190
|
+
*/
|
|
191
|
+
.sv-grid-column-filter:focus,
|
|
192
|
+
.sv-grid-filter-value:focus,
|
|
193
|
+
.sv-grid-menu-search:focus,
|
|
194
|
+
.sv-grid-menu-condition-value:focus,
|
|
195
|
+
.sv-grid-global-filter input:focus,
|
|
196
|
+
.sv-grid-cell-editor:focus {
|
|
197
|
+
outline: none !important;
|
|
198
|
+
outline-offset: 0 !important;
|
|
199
|
+
border-color: var(--sg-accent) !important;
|
|
200
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-accent) 30%, transparent) !important;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* Column menu popover */
|
|
204
|
+
.sv-grid-menu {
|
|
205
|
+
background: var(--sg-bg) !important;
|
|
206
|
+
color: var(--sg-fg) !important;
|
|
207
|
+
border-color: var(--sg-border) !important;
|
|
208
|
+
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.4) !important;
|
|
209
|
+
}
|
|
210
|
+
.sv-grid-menu-item:hover {
|
|
211
|
+
background: var(--sg-row-hover-bg) !important;
|
|
212
|
+
}
|
|
213
|
+
.sv-grid-menu-sep {
|
|
214
|
+
background: var(--sg-border) !important;
|
|
215
|
+
}
|
|
216
|
+
.sv-grid-menu-filter-head {
|
|
217
|
+
color: var(--sg-muted) !important;
|
|
218
|
+
}
|
|
219
|
+
.sv-grid-menu-operator-btn {
|
|
220
|
+
background: var(--sg-input-bg) !important;
|
|
221
|
+
color: var(--sg-fg) !important;
|
|
222
|
+
border-color: var(--sg-input-border) !important;
|
|
223
|
+
}
|
|
224
|
+
.sv-grid-menu-operator-btn.is-active {
|
|
225
|
+
background: var(--sg-accent) !important;
|
|
226
|
+
color: #fff !important;
|
|
227
|
+
border-color: var(--sg-accent) !important;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/* New <select> for filter operator (replaces the radio-button group) */
|
|
231
|
+
.sv-grid-menu-operator-select {
|
|
232
|
+
width: 100%;
|
|
233
|
+
height: 28px;
|
|
234
|
+
padding: 0 22px 0 8px;
|
|
235
|
+
margin-bottom: 6px;
|
|
236
|
+
border: 1px solid var(--sg-input-border);
|
|
237
|
+
border-radius: 5px;
|
|
238
|
+
background: var(--sg-input-bg);
|
|
239
|
+
color: var(--sg-fg);
|
|
240
|
+
font: inherit;
|
|
241
|
+
font-size: 13px;
|
|
242
|
+
cursor: pointer;
|
|
243
|
+
appearance: none;
|
|
244
|
+
-webkit-appearance: none;
|
|
245
|
+
background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
|
|
246
|
+
linear-gradient(135deg, currentColor 50%, transparent 50%);
|
|
247
|
+
background-position:
|
|
248
|
+
calc(100% - 12px) 12px,
|
|
249
|
+
calc(100% - 8px) 12px;
|
|
250
|
+
background-size:
|
|
251
|
+
4px 4px,
|
|
252
|
+
4px 4px;
|
|
253
|
+
background-repeat: no-repeat;
|
|
254
|
+
}
|
|
255
|
+
.sv-grid-menu-operator-select:focus {
|
|
256
|
+
outline: none;
|
|
257
|
+
border-color: var(--sg-accent);
|
|
258
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-accent) 40%, transparent);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* Facet list (the "Select all" + value checklist inside the menu) */
|
|
262
|
+
.sv-grid-facet:hover {
|
|
263
|
+
background: var(--sg-row-hover-bg) !important;
|
|
264
|
+
}
|
|
265
|
+
.sv-grid-facet-all {
|
|
266
|
+
border-bottom-color: var(--sg-border) !important;
|
|
267
|
+
}
|
|
268
|
+
.sv-grid-facet-list {
|
|
269
|
+
border-color: var(--sg-border) !important;
|
|
270
|
+
}
|
|
271
|
+
.sv-grid-facet-empty,
|
|
272
|
+
.sv-grid-facet-note {
|
|
273
|
+
color: var(--sg-muted) !important;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Menu action buttons */
|
|
277
|
+
.sv-grid-menu-btn {
|
|
278
|
+
background: var(--sg-input-bg) !important;
|
|
279
|
+
color: var(--sg-fg) !important;
|
|
280
|
+
border-color: var(--sg-input-border) !important;
|
|
281
|
+
}
|
|
282
|
+
.sv-grid-menu-btn-primary {
|
|
283
|
+
background: var(--sg-accent) !important;
|
|
284
|
+
color: #fff !important;
|
|
285
|
+
border-color: var(--sg-accent) !important;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/* New filter-only popover (split out from the column menu) */
|
|
289
|
+
.sv-grid-filter-menu {
|
|
290
|
+
width: 260px;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/* Choose Columns submenu */
|
|
294
|
+
.sv-grid-choose-columns-menu {
|
|
295
|
+
width: 240px;
|
|
296
|
+
}
|
|
297
|
+
.sv-grid-choose-columns-menu .sv-grid-facet-list {
|
|
298
|
+
max-height: 280px;
|
|
299
|
+
}
|
|
300
|
+
/* Native checkboxes inside the facet list pick up the theme accent */
|
|
301
|
+
.sv-grid-facet input[type='checkbox'] {
|
|
302
|
+
accent-color: var(--sg-accent);
|
|
303
|
+
width: 14px;
|
|
304
|
+
height: 14px;
|
|
305
|
+
cursor: pointer;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* Right-chevron on the "Choose columns" item to indicate a submenu */
|
|
309
|
+
.sv-grid-menu-item-chevron {
|
|
310
|
+
margin-left: auto;
|
|
311
|
+
transform: rotate(-90deg);
|
|
312
|
+
opacity: 0.55;
|
|
313
|
+
}
|
|
314
|
+
.sv-grid-menu-item.is-open .sv-grid-menu-item-chevron {
|
|
315
|
+
opacity: 1;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* Filter button on the column header (funnel icon, next to the 3-line menu)
|
|
319
|
+
* Hidden when the column is idle. Becomes visible when:
|
|
320
|
+
* - the column is hovered, OR
|
|
321
|
+
* - the menu is open on this column, OR
|
|
322
|
+
* - the column has an active filter (.is-active - also tinted in accent). */
|
|
323
|
+
.sv-grid-col-filter-btn {
|
|
324
|
+
opacity: 0 !important;
|
|
325
|
+
}
|
|
326
|
+
.sv-grid-column:hover .sv-grid-col-filter-btn,
|
|
327
|
+
.sv-grid-col-filter-btn:hover,
|
|
328
|
+
.sv-grid-col-filter-btn.is-open {
|
|
329
|
+
opacity: 1 !important;
|
|
330
|
+
}
|
|
331
|
+
.sv-grid-col-filter-btn.is-active {
|
|
332
|
+
color: var(--sg-accent) !important;
|
|
333
|
+
opacity: 1 !important;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/* Magnifying-glass prefix on the filter menu's search input */
|
|
337
|
+
.sv-grid-menu-search {
|
|
338
|
+
padding-left: 28px !important;
|
|
339
|
+
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='6'/%3E%3Cpath d='M20 20l-4.5-4.5'/%3E%3C/svg%3E");
|
|
340
|
+
background-position: 8px center;
|
|
341
|
+
background-repeat: no-repeat;
|
|
342
|
+
background-size: 14px 14px;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/* Filter menu spacing rhythm */
|
|
346
|
+
.sv-grid-menu-filter > * + * {
|
|
347
|
+
margin-top: 6px;
|
|
348
|
+
}
|
|
349
|
+
.sv-grid-facet-all {
|
|
350
|
+
font-weight: 600;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/* Row-number column: themed muted text and matched background tokens. */
|
|
354
|
+
.sv-grid-row-number-column,
|
|
355
|
+
.sv-grid-row-number-cell {
|
|
356
|
+
color: var(--sg-muted) !important;
|
|
357
|
+
background: var(--sg-header-bg) !important;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/* Group row background (selection rules live below the zebra rule so they
|
|
361
|
+
* win on source order; see below). */
|
|
362
|
+
.sv-grid-group-row > .sv-grid-cell {
|
|
363
|
+
background: var(--sg-header-bg) !important;
|
|
364
|
+
color: var(--sg-header-fg) !important;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/* Checkbox (used for row selection + the read-only checkbox cells) */
|
|
368
|
+
.sv-grid-checkbox {
|
|
369
|
+
background: var(--sg-input-bg) !important;
|
|
370
|
+
border-color: var(--sg-input-border) !important;
|
|
371
|
+
}
|
|
372
|
+
.sv-grid-checkbox[aria-checked='true'],
|
|
373
|
+
.sv-grid-checkbox[aria-checked='mixed'] {
|
|
374
|
+
background: var(--sg-accent) !important;
|
|
375
|
+
border-color: var(--sg-accent) !important;
|
|
376
|
+
color: #fff !important;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/* Filter-row operator button: SvGrid's scoped styles hard-code white bg +
|
|
380
|
+
* dark text which is too bright in dark mode. Route through theme tokens. */
|
|
381
|
+
.sv-grid-filter-operator-btn {
|
|
382
|
+
background: var(--sg-input-bg) !important;
|
|
383
|
+
color: var(--sg-fg) !important;
|
|
384
|
+
border-color: var(--sg-input-border) !important;
|
|
385
|
+
opacity: 0.85;
|
|
386
|
+
}
|
|
387
|
+
.sv-grid-filter-operator-btn:hover {
|
|
388
|
+
opacity: 1;
|
|
389
|
+
border-color: var(--sg-accent) !important;
|
|
390
|
+
}
|
|
391
|
+
.sv-grid-filter-operator-btn.is-open {
|
|
392
|
+
border-color: var(--sg-accent) !important;
|
|
393
|
+
opacity: 1;
|
|
394
|
+
}
|
|
395
|
+
.sv-grid-filter-operator-btn .sv-grid-header-icon {
|
|
396
|
+
color: var(--sg-muted);
|
|
397
|
+
}
|
|
398
|
+
.sv-grid-filter-operator-btn .sv-grid-caret {
|
|
399
|
+
color: var(--sg-muted);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/* Pagination footer below the grid. Layout: PageSize · Range · Nav buttons.
|
|
403
|
+
* Matches the AG-Grid-style pager the user shipped as a reference. */
|
|
404
|
+
.sv-grid-pagination {
|
|
405
|
+
display: flex !important;
|
|
406
|
+
align-items: center !important;
|
|
407
|
+
justify-content: flex-end !important;
|
|
408
|
+
gap: 24px !important;
|
|
409
|
+
padding: 12px 16px !important;
|
|
410
|
+
margin-top: 0 !important;
|
|
411
|
+
border: 1px solid var(--sg-border) !important;
|
|
412
|
+
border-top: 0 !important;
|
|
413
|
+
border-radius: 0 0 6px 6px !important;
|
|
414
|
+
background: var(--sg-header-bg) !important;
|
|
415
|
+
color: var(--sg-fg) !important;
|
|
416
|
+
font-size: 13px !important;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.sv-grid-pagination-pagesize {
|
|
420
|
+
display: inline-flex;
|
|
421
|
+
align-items: center;
|
|
422
|
+
gap: 8px;
|
|
423
|
+
color: var(--sg-muted);
|
|
424
|
+
}
|
|
425
|
+
.sv-grid-pagination-pagesize select {
|
|
426
|
+
height: 28px;
|
|
427
|
+
padding: 0 22px 0 8px;
|
|
428
|
+
border: 1px solid var(--sg-input-border);
|
|
429
|
+
border-radius: 5px;
|
|
430
|
+
background: var(--sg-input-bg);
|
|
431
|
+
color: var(--sg-fg);
|
|
432
|
+
font: inherit;
|
|
433
|
+
font-size: 13px;
|
|
434
|
+
cursor: pointer;
|
|
435
|
+
appearance: none;
|
|
436
|
+
-webkit-appearance: none;
|
|
437
|
+
background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
|
|
438
|
+
linear-gradient(135deg, currentColor 50%, transparent 50%);
|
|
439
|
+
background-position:
|
|
440
|
+
calc(100% - 12px) 12px,
|
|
441
|
+
calc(100% - 8px) 12px;
|
|
442
|
+
background-size:
|
|
443
|
+
4px 4px,
|
|
444
|
+
4px 4px;
|
|
445
|
+
background-repeat: no-repeat;
|
|
446
|
+
}
|
|
447
|
+
.sv-grid-pagination-pagesize select:focus {
|
|
448
|
+
outline: none;
|
|
449
|
+
border-color: var(--sg-accent);
|
|
450
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-accent) 30%, transparent);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.sv-grid-pagination-range {
|
|
454
|
+
color: var(--sg-fg);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.sv-grid-pagination-nav {
|
|
458
|
+
display: inline-flex;
|
|
459
|
+
align-items: center;
|
|
460
|
+
gap: 4px;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.sv-grid-pagination-btn {
|
|
464
|
+
display: inline-flex !important;
|
|
465
|
+
align-items: center !important;
|
|
466
|
+
justify-content: center !important;
|
|
467
|
+
width: 28px !important;
|
|
468
|
+
height: 28px !important;
|
|
469
|
+
padding: 0 !important;
|
|
470
|
+
border: 0 !important;
|
|
471
|
+
border-radius: 5px !important;
|
|
472
|
+
background: transparent !important;
|
|
473
|
+
color: var(--sg-fg) !important;
|
|
474
|
+
font: inherit !important;
|
|
475
|
+
font-size: 16px !important;
|
|
476
|
+
cursor: pointer !important;
|
|
477
|
+
transition: background-color 100ms ease, color 100ms ease;
|
|
478
|
+
}
|
|
479
|
+
.sv-grid-pagination-btn:hover:not(:disabled) {
|
|
480
|
+
background: var(--sg-input-bg) !important;
|
|
481
|
+
color: var(--sg-accent) !important;
|
|
482
|
+
}
|
|
483
|
+
.sv-grid-pagination-btn:disabled {
|
|
484
|
+
color: var(--sg-muted) !important;
|
|
485
|
+
opacity: 0.4 !important;
|
|
486
|
+
cursor: default !important;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.sv-grid-pagination-label {
|
|
490
|
+
margin: 0 8px;
|
|
491
|
+
color: var(--sg-fg);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/* Resize handle: an invisible 6px hit-zone on the right edge of every
|
|
495
|
+
* header. The pill ::after only appears while the column is hovered, the
|
|
496
|
+
* handle is hovered, or a drag is in progress. Standard AG-Grid behavior. */
|
|
497
|
+
.sv-grid-resize-handle {
|
|
498
|
+
position: absolute !important;
|
|
499
|
+
top: 0 !important;
|
|
500
|
+
right: 0 !important;
|
|
501
|
+
width: 6px !important;
|
|
502
|
+
height: 100% !important;
|
|
503
|
+
z-index: 100 !important;
|
|
504
|
+
cursor: col-resize !important;
|
|
505
|
+
background: transparent !important;
|
|
506
|
+
pointer-events: auto !important;
|
|
507
|
+
}
|
|
508
|
+
.sv-grid-resize-handle::after {
|
|
509
|
+
content: '';
|
|
510
|
+
position: absolute;
|
|
511
|
+
inset: 0;
|
|
512
|
+
margin: auto 2px;
|
|
513
|
+
width: 2px;
|
|
514
|
+
background: var(--sg-fg);
|
|
515
|
+
opacity: 0;
|
|
516
|
+
border-radius: 1px;
|
|
517
|
+
transition: background-color 100ms ease, opacity 100ms ease;
|
|
518
|
+
}
|
|
519
|
+
/* faint pill while the parent column is hovered */
|
|
520
|
+
.sv-grid-column:hover .sv-grid-resize-handle::after {
|
|
521
|
+
opacity: 0.3;
|
|
522
|
+
}
|
|
523
|
+
/* bright accent pill when the handle itself is hovered or being dragged.
|
|
524
|
+
* The selector includes ".sv-grid-column:hover" to match the specificity
|
|
525
|
+
* of the rule above and rank higher (extra :hover) so it wins on cascade. */
|
|
526
|
+
.sv-grid-column:hover .sv-grid-resize-handle:hover::after,
|
|
527
|
+
.sv-grid-resize-handle.is-resizing::after {
|
|
528
|
+
background: var(--sg-accent);
|
|
529
|
+
opacity: 1;
|
|
530
|
+
}
|
|
531
|
+
/* zebra striping by row */
|
|
532
|
+
.sv-grid-table tbody tr:nth-child(even) .sv-grid-cell {
|
|
533
|
+
background: var(--sg-row-alt-bg) !important;
|
|
534
|
+
}
|
|
535
|
+
.sv-grid-table tbody tr:hover .sv-grid-cell {
|
|
536
|
+
background: var(--sg-row-hover-bg) !important;
|
|
537
|
+
}
|
|
538
|
+
/* Selected row and cell-range backgrounds. We re-write them here, AFTER
|
|
539
|
+
* the zebra rule, with the same `.sv-grid-table tbody tr` prefix so they
|
|
540
|
+
* match zebra's specificity (0,3,2) and beat it on source order. Without
|
|
541
|
+
* this, even-numbered selected rows show the zebra bg instead of the
|
|
542
|
+
* selection bg. */
|
|
543
|
+
.sv-grid-table tbody tr.sv-grid-row-selected .sv-grid-cell {
|
|
544
|
+
background: var(--sg-selection-bg) !important;
|
|
545
|
+
}
|
|
546
|
+
.sv-grid-table tbody tr .sv-grid-cell[data-selected-range='true'] {
|
|
547
|
+
background: var(--sg-selection-bg) !important;
|
|
18
548
|
}
|
|
@@ -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}
|