@svgrid/create 1.0.0 → 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/README.md CHANGED
@@ -5,18 +5,18 @@ Scaffold a [Svelte](https://svelte.dev) app powered by
5
5
  command.
6
6
 
7
7
  ```bash
8
- npm create sv-grid@latest
8
+ npm create @svgrid@latest
9
9
  # or
10
- pnpm create sv-grid
10
+ pnpm create @svgrid
11
11
  # or
12
- yarn create sv-grid
12
+ yarn create @svgrid
13
13
  ```
14
14
 
15
15
  Interactive by default. Or pass a directory and template directly:
16
16
 
17
17
  ```bash
18
- npm create sv-grid@latest my-app -- --template admin-dashboard
19
- pnpm create sv-grid my-app -t minimal
18
+ npm create @svgrid@latest my-app -- --template admin-dashboard
19
+ pnpm create @svgrid my-app -t minimal
20
20
  ```
21
21
 
22
22
  ## Templates
package/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  // @svgrid/create - scaffold a Svelte app powered by SvGrid.
3
3
  //
4
- // npm create sv-grid@latest # interactive
5
- // pnpm create sv-grid # interactive
6
- // npm create sv-grid@latest my-app -- --template admin-dashboard
7
- // npm create sv-grid@latest my-app -- -t minimal
4
+ // npm create @svgrid@latest # interactive
5
+ // pnpm create @svgrid # interactive
6
+ // npm create @svgrid@latest my-app -- --template admin-dashboard
7
+ // npm create @svgrid@latest my-app -- -t minimal
8
8
  //
9
9
  // Zero runtime dependencies - Node built-ins only. Copies a bundled template,
10
10
  // renames `_`-prefixed dotfiles, and rewrites the project name.
@@ -68,7 +68,7 @@ function printHelp() {
68
68
  ${color('bold', '@svgrid/create')} - scaffold a Svelte app powered by SvGrid
69
69
 
70
70
  ${color('bold', 'Usage')}
71
- npm create sv-grid@latest [dir] -- [--template <name>] [--force]
71
+ npm create @svgrid@latest [dir] -- [--template <name>] [--force]
72
72
 
73
73
  ${color('bold', 'Templates')}
74
74
  ${Object.entries(TEMPLATES)
@@ -76,9 +76,9 @@ ${Object.entries(TEMPLATES)
76
76
  .join('\n')}
77
77
 
78
78
  ${color('bold', 'Examples')}
79
- npm create sv-grid@latest
80
- npm create sv-grid@latest my-app -- --template admin-dashboard
81
- pnpm create sv-grid my-app -t minimal
79
+ npm create @svgrid@latest
80
+ npm create @svgrid@latest my-app -- --template admin-dashboard
81
+ pnpm create @svgrid my-app -t minimal
82
82
  `)
83
83
  }
84
84
 
@@ -100,10 +100,13 @@ function resolveTemplateDir(key) {
100
100
  }
101
101
 
102
102
  async function copyTemplate(srcDir, destDir) {
103
+ // Match only on the path RELATIVE to the template root. The absolute `src`
104
+ // includes the install location (e.g. `.../node_modules/@svgrid/create/...`),
105
+ // so testing it directly would match `node_modules` and skip the whole copy.
106
+ const skip = /(^|[\\/])(node_modules|\.svelte-kit|\.vercel|build|dist)([\\/]|$)/
103
107
  await cp(srcDir, destDir, {
104
108
  recursive: true,
105
- filter: (src) =>
106
- !/[\\/](node_modules|\.svelte-kit|\.vercel|build|dist)([\\/]|$)/.test(src),
109
+ filter: (src) => !skip.test(src.slice(srcDir.length)),
107
110
  })
108
111
  // Rename `_`-prefixed files back to their real dotfile names.
109
112
  await renameBack(destDir)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@svgrid/create",
3
- "version": "1.0.0",
4
- "description": "Scaffold a Svelte app powered by SvGrid in one command: npm create sv-grid@latest",
3
+ "version": "1.0.3",
4
+ "description": "Scaffold a Svelte app powered by SvGrid in one command: npm create @svgrid@latest",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "jQWidgets Ltd",
@@ -23,9 +23,7 @@
23
23
  "starter",
24
24
  "template"
25
25
  ],
26
- "bin": {
27
- "@svgrid/create": "index.mjs"
28
- },
26
+ "bin": "index.mjs",
29
27
  "files": [
30
28
  "index.mjs",
31
29
  "templates"
@@ -22,6 +22,6 @@
22
22
  "vite": "^8.0.10"
23
23
  },
24
24
  "dependencies": {
25
- "@svgrid/grid": "^1.0.0"
25
+ "@svgrid/grid": "^1.0.1"
26
26
  }
27
27
  }
@@ -1,18 +1,548 @@
1
1
  @import 'tailwindcss';
2
2
 
3
- /* SvGrid ships its own scoped styles. These custom properties let you theme
4
- the grid to match the dashboard shell. Tweak freely. */
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
- --app-bg: #f8fafc;
7
- --app-panel: #ffffff;
8
- --app-border: #e2e8f0;
9
- --app-fg: #0f172a;
10
- --app-muted: #64748b;
11
- --app-accent: #4f46e5;
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 bg-white p-4 shadow-sm"
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 bg-white md:flex"
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 bg-white px-6 py-3"
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
- <a
67
- href="https://www.svgrid.com/docs"
68
- target="_blank"
69
- rel="noopener"
70
- class="rounded-lg border px-3 py-1.5 text-sm font-medium"
71
- style="border-color: var(--app-border); color: var(--app-fg);">Docs</a
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 bg-white p-4 shadow-sm" style="border-color: var(--app-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 bg-white p-2 shadow-sm" style="border-color: var(--app-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 bg-white p-2 shadow-sm" style="border-color: var(--app-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}