@svgrid/create 2.2.1 → 2.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/README.md CHANGED
@@ -1,77 +1,81 @@
1
- <p align="center">
2
- <img src="https://svgrid.com/brand/svgrid-logo-icon-1200.png" alt="SvGrid" width="100" height="100" />
3
- </p>
4
-
5
- <h1 align="center">@svgrid/create</h1>
6
-
7
- <p align="center"><strong>Scaffold a Svelte 5 app with a data grid already wired up.</strong></p>
8
-
9
- <p align="center">
10
- <a href="https://www.npmjs.com/package/@svgrid/create"><img src="https://img.shields.io/npm/v/%40svgrid%2Fcreate.svg?label=%40svgrid%2Fcreate" alt="npm version" /></a>
11
- <a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-brightgreen.svg" alt="MIT License" /></a>
12
- </p>
13
-
14
- <p align="center">
15
- <a href="https://svgrid.com">Website</a> ·
16
- <a href="https://svgrid.com/docs/">Docs</a> ·
17
- <a href="https://svgrid.com/demos/">Demos</a>
18
- </p>
19
-
20
- ---
21
-
22
- Scaffold a [Svelte](https://svelte.dev) app powered by
23
- [SvGrid](https://svgrid.com) - the Svelte 5 data grid and data table - in one
24
- command.
25
-
26
- ```bash
27
- npm create @svgrid@latest
28
- # or
29
- pnpm create @svgrid
30
- # or
31
- yarn create @svgrid
32
- ```
33
-
34
- Interactive by default. Or pass a directory and template directly:
35
-
36
- ```bash
37
- npm create @svgrid@latest my-app -- --template admin-dashboard
38
- pnpm create @svgrid my-app -t minimal
39
- ```
40
-
41
- ## Templates
42
-
43
- | Template | Stack | Best for |
44
- | --- | --- | --- |
45
- | `minimal` | Vite + Svelte 5 + SvGrid | Dropping a grid into something quickly |
46
- | `admin-dashboard` | SvelteKit + Tailwind + SvGrid, deploy to Vercel | A real dashboard / internal tool |
47
-
48
- ## Options
49
-
50
- | Flag | Description |
51
- | --- | --- |
52
- | `--template`, `-t` | `minimal` or `admin-dashboard` |
53
- | `--theme <id>` | `admin-dashboard` only. One of `@svgrid/grid`'s 19 built-in presets - shadcn, Tailwind, Material, Excel, Fluent, and more (default: `tailwind`) |
54
- | `--dark` / `--light` | `admin-dashboard` only. Start in dark or light mode (default: `dark`) |
55
- | `--force`, `-f` | Scaffold into a non-empty directory |
56
- | `--help`, `-h` | Show usage |
57
-
58
- Choosing `admin-dashboard` interactively also prompts for a theme and light/dark
59
- mode - or skip the prompts with flags:
60
-
61
- ```bash
62
- npm create @svgrid@latest my-app -- -t admin-dashboard --theme material --light
63
- ```
64
-
65
- Then:
66
-
67
- ```bash
68
- cd my-app
69
- npm install
70
- npm run dev
71
- ```
72
-
73
- All templates use the free MIT `@svgrid/grid` core. Add
74
- [`@svgrid/enterprise`](https://svgrid.com/pricing/) for Excel/PDF export, import,
75
- print, pivot, and AI helpers.
76
-
77
- SvGrid(TM) is a trademark of jQWidgets Ltd. This package is MIT-licensed.
1
+ <p align="center">
2
+ <img src="https://svgrid.com/brand/svgrid-logo-icon-1200.png" alt="SvGrid" width="100" height="100" />
3
+ </p>
4
+
5
+ <h1 align="center">@svgrid/create</h1>
6
+
7
+ <p align="center"><strong>Scaffold a Svelte 5 app with a data grid already wired up.</strong></p>
8
+
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/@svgrid/create"><img src="https://img.shields.io/npm/v/%40svgrid%2Fcreate.svg?label=%40svgrid%2Fcreate" alt="npm version" /></a>
11
+ <a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-brightgreen.svg" alt="MIT License" /></a>
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="https://svgrid.com">Website</a> ·
16
+ <a href="https://svgrid.com/docs/">Docs</a> ·
17
+ <a href="https://svgrid.com/demos/">Demos</a>
18
+ </p>
19
+
20
+ ---
21
+
22
+ Scaffold a [Svelte](https://svelte.dev) app powered by
23
+ [SvGrid](https://svgrid.com) - the Svelte 5 data grid and data table - in one
24
+ command.
25
+
26
+ ```bash
27
+ npm create @svgrid@latest
28
+ # or
29
+ pnpm create @svgrid
30
+ # or
31
+ yarn create @svgrid
32
+ ```
33
+
34
+ Interactive by default. Or pass a directory and template directly:
35
+
36
+ ```bash
37
+ npm create @svgrid@latest my-app -- --template admin-dashboard
38
+ pnpm create @svgrid my-app -t minimal
39
+ ```
40
+
41
+ ## Templates
42
+
43
+ | Template | Stack | Best for |
44
+ | --- | --- | --- |
45
+ | `minimal` | Vite + Svelte 5 + SvGrid | Dropping a grid into something quickly |
46
+ | `admin-dashboard` | SvelteKit + Tailwind + SvGrid, deploy to Vercel | A real dashboard / internal tool |
47
+
48
+ ## Options
49
+
50
+ | Flag | Description |
51
+ | --- | --- |
52
+ | `--template`, `-t` | `minimal` or `admin-dashboard` |
53
+ | `--theme <id>` | One of `@svgrid/grid`'s 20 built-in presets - shadcn, Tailwind, Material, Excel, Fluent, and more (default: `tailwind`) |
54
+ | `--dark` / `--light` | Start in dark or light mode. `minimal` follows the OS when neither is given |
55
+ | `--force`, `-f` | Scaffold into a non-empty directory |
56
+ | `--help`, `-h` | Show usage |
57
+
58
+ Either template prompts for a theme and a light/dark mode when run
59
+ interactively, or takes them as flags:
60
+
61
+ ```bash
62
+ npm create @svgrid@latest my-app -- -t admin-dashboard --theme material --light
63
+ npm create @svgrid@latest my-app -- -t minimal --theme nord --dark
64
+ ```
65
+
66
+ Both ship a working toggle, so the choice is a starting point rather than
67
+ something you are stuck with.
68
+
69
+ Then:
70
+
71
+ ```bash
72
+ cd my-app
73
+ npm install
74
+ npm run dev
75
+ ```
76
+
77
+ All templates use the free MIT `@svgrid/grid` core. Add
78
+ [`@svgrid/enterprise`](https://svgrid.com/pricing/) for Excel/PDF export, import,
79
+ print, pivot, and AI helpers.
80
+
81
+ SvGrid(TM) is a trademark of jQWidgets Ltd. This package is MIT-licensed.
package/index.mjs CHANGED
@@ -80,8 +80,8 @@ ${Object.entries(TEMPLATES)
80
80
  .join('\n')}
81
81
 
82
82
  ${color('bold', 'Options')}
83
- --theme <id> admin-dashboard only. One of: ${THEME_IDS.join(', ')} (default: tailwind).
84
- --dark / --light admin-dashboard only. Start in dark or light mode (default: dark).
83
+ --theme <id> One of: ${THEME_IDS.join(', ')} (default: tailwind).
84
+ --dark / --light Start in dark or light mode (default: dark).
85
85
  --force Scaffold into a non-empty directory.
86
86
 
87
87
  ${color('bold', 'Examples')}
@@ -93,17 +93,16 @@ ${color('bold', 'Examples')}
93
93
  }
94
94
 
95
95
  const THEME_IDS = [
96
- 'shadcn', 'tailwind', 'material', 'excel', 'fluent', 'carbon', 'sap',
96
+ 'ember', 'shadcn', 'tailwind', 'material', 'excel', 'fluent', 'carbon', 'sap',
97
97
  'salesforce', 'atlassian', 'github', 'antd', 'ag-alpine', 'bootstrap',
98
98
  'vercel', 'linear', 'notion', 'nord', 'dracula', 'catppuccin',
99
99
  ]
100
100
 
101
101
  /**
102
- * Resolve the theme + mode to apply (admin-dashboard only): from `--theme` /
103
- * `--dark` / `--light` flags, or (in a TTY) an interactive prompt; otherwise
104
- * left untouched (the template's own dark-by-default look stands). Reuses
105
- * `@svgrid/grid/themes`'s own preset list, so the picker can never drift from
106
- * the canonical set.
102
+ * Resolve the theme + mode to apply: from `--theme` / `--dark` / `--light`
103
+ * flags, or (in a TTY) an interactive prompt; otherwise left untouched (the
104
+ * template's own default look stands). Reuses `@svgrid/grid/themes`'s own
105
+ * preset list, so the picker can never drift from the canonical set.
107
106
  */
108
107
  async function promptTheme(args, ask, interactive) {
109
108
  let themes
@@ -132,13 +131,24 @@ async function promptTheme(args, ask, interactive) {
132
131
  const a = await ask('Light or dark mode? (light/dark)', 'dark')
133
132
  mode = /^l(ight)?$/i.test(a) ? 'light' : 'dark'
134
133
  }
134
+ // Whether the mode was actually chosen, as opposed to falling through to the
135
+ // default below. minimal follows the OS when nobody said, which is only
136
+ // distinguishable from an explicit --dark by keeping this flag.
137
+ const explicitMode = mode != null
135
138
  mode ??= 'dark' // matches the template's existing default - only patched when 'light' is chosen.
136
139
 
137
- return { themeId, mode, name: themes.getThemePreset(themeId)?.name ?? themeId, themes }
140
+ return { themeId, mode, explicitMode, name: themes.getThemePreset(themeId)?.name ?? themeId, themes }
138
141
  }
139
142
 
140
- /** Rewrite `app.css` (between the `svgrid-theme` markers) with the resolved
141
- * `--sg-*` tokens for the chosen theme. */
143
+ /** Rewrite `app.css` between the `svgrid-theme` markers.
144
+ *
145
+ * The two templates want different things there. admin-dashboard inlines the
146
+ * resolved `--sg-*` tokens, because its Tailwind layer aliases them to
147
+ * `--app-*` and having the values on the page makes them editable. minimal
148
+ * just points at the preset stylesheet the package already ships, which keeps
149
+ * its `app.css` short enough to read in one go. Which one we are looking at
150
+ * is decided by what the marked block currently holds, not by the template
151
+ * name, so a hand-edited project still round-trips. */
142
152
  async function applyTheme(destDir, choice) {
143
153
  if (!choice) return
144
154
  const { themeId, themes } = choice
@@ -149,12 +159,19 @@ async function applyTheme(destDir, choice) {
149
159
  const lines = Object.entries(tokens).map(([k, v]) => ` ${k}: ${v};`).join('\n')
150
160
  return `${selector} {\n${lines}\n color-scheme: ${scheme};\n}`
151
161
  }
152
- const css = `${block(':root', light, 'light')}\n${block(":root[data-theme='dark']", dark, 'dark')}`
162
+ const tokenBlocks = `${block(':root', light, 'light')}\n${block(":root[data-theme='dark']", dark, 'dark')}`
153
163
 
154
164
  const cssPath = join(destDir, 'src', 'app.css')
155
165
  const cssText = await readFile(cssPath, 'utf8').catch(() => null)
156
166
  if (cssText == null) return
157
- const marked = /\/\* svgrid-theme:start \*\/[\s\S]*?\/\* svgrid-theme:end \*\//
167
+ const marked = /\/\* svgrid-theme:start \*\/([\s\S]*?)\/\* svgrid-theme:end \*\//
168
+ // A CSS @import has to stay at the top of the file, so where the block holds
169
+ // one we swap the preset id in place instead of replacing it with token
170
+ // declarations that would then sit above the import.
171
+ const held = cssText.match(marked)
172
+ const css = held && /@import\s/.test(held[1])
173
+ ? `@import '@svgrid/grid/themes/${preset.id}.css';`
174
+ : tokenBlocks
158
175
  if (marked.test(cssText)) {
159
176
  await writeFile(cssPath, cssText.replace(marked, `/* svgrid-theme:start */\n${css}\n/* svgrid-theme:end */`))
160
177
  }
@@ -204,6 +221,27 @@ async function applyMode(destDir, choice) {
204
221
  }
205
222
  }
206
223
 
224
+ /** minimal picks its start mode in an inline script in `index.html`, falling
225
+ * back to the OS preference. Pin it only when light or dark was actually asked
226
+ * for - following the OS is the better default for anyone who did not say. */
227
+ async function applyModeMinimal(destDir, choice) {
228
+ if (!choice || !choice.explicitMode) return
229
+
230
+ const htmlPath = join(destDir, 'index.html')
231
+ const html = await readFile(htmlPath, 'utf8').catch(() => null)
232
+ if (html == null) return
233
+
234
+ await writeFile(
235
+ htmlPath,
236
+ html
237
+ .replace(`<html lang="en" data-theme="light">`, `<html lang="en" data-theme="${choice.mode}">`)
238
+ .replace(
239
+ `var fallback = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'`,
240
+ `var fallback = '${choice.mode}'`,
241
+ ),
242
+ )
243
+ }
244
+
207
245
  function sanitizeName(name) {
208
246
  return (
209
247
  name
@@ -289,7 +327,7 @@ async function main() {
289
327
  if (!template) {
290
328
  if (interactive) {
291
329
  stdout.write(`\n Templates:\n`)
292
- Object.entries(TEMPLATES).forEach(([k, t], i) => {
330
+ Object.entries(TEMPLATES).forEach(([, t], i) => {
293
331
  stdout.write(` ${color('cyan', String(i + 1))}. ${t.label}\n`)
294
332
  })
295
333
  const pick = await ask('\nChoose a template (1-2):', '1')
@@ -328,9 +366,9 @@ async function main() {
328
366
  process.exit(1)
329
367
  }
330
368
 
331
- // 3b. Theme + light/dark mode - admin-dashboard only (minimal has no theme
332
- // system to pick from).
333
- const themeChoice = template === 'admin-dashboard' ? await promptTheme(args, ask, interactive) : null
369
+ // 3b. Theme + light/dark mode. Both templates carry the full --sg-* palette
370
+ // and a toggle, so both get asked.
371
+ const themeChoice = await promptTheme(args, ask, interactive)
334
372
  if (rl) rl.close()
335
373
 
336
374
  // 4. Scaffold.
@@ -339,12 +377,17 @@ async function main() {
339
377
  await setProjectName(destDir, projectName)
340
378
  await applyTheme(destDir, themeChoice)
341
379
  await applyMode(destDir, themeChoice)
380
+ await applyModeMinimal(destDir, themeChoice)
342
381
 
343
382
  // 5. Next steps.
344
383
  const rel = isAbsolute(target) || target.startsWith('.') ? target : `./${target}`
345
384
  stdout.write(`\n${color('green', '✔')} Scaffolded ${color('bold', projectName)} (${template}) into ${rel}\n`)
346
385
  if (themeChoice) {
347
- stdout.write(` ${color('dim', 'theme')} ${themeChoice.name} (${themeChoice.mode})\n`)
386
+ // minimal only pins a mode when one was asked for; otherwise it reads the
387
+ // OS preference at load, so reporting "dark" here would be a lie.
388
+ const pinned = themeChoice.explicitMode || template === 'admin-dashboard'
389
+ const mode = pinned ? themeChoice.mode : 'follows your OS'
390
+ stdout.write(` ${color('dim', 'theme')} ${themeChoice.name} (${mode})\n`)
348
391
  }
349
392
  stdout.write(`\n`)
350
393
  stdout.write(`${color('bold', 'Next steps')}\n`)
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "commercial",
5
5
  "url": "https://svgrid.com/pricing"
6
6
  },
7
- "version": "2.2.1",
7
+ "version": "2.4.0",
8
8
  "description": "Scaffold a Svelte app powered by SvGrid in one command: npm create @svgrid@latest",
9
9
  "type": "module",
10
10
  "license": "MIT",
@@ -36,7 +36,7 @@
36
36
  "node": ">=18"
37
37
  },
38
38
  "dependencies": {
39
- "@svgrid/grid": "^2.2.27"
39
+ "@svgrid/grid": "^2.4.0"
40
40
  },
41
41
  "scripts": {
42
42
  "sync-templates": "node sync-templates.mjs"
@@ -11,7 +11,7 @@
11
11
  "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json"
12
12
  },
13
13
  "devDependencies": {
14
- "@sveltejs/adapter-vercel": "^5.5.0",
14
+ "@sveltejs/adapter-vercel": "^6.3.2",
15
15
  "@sveltejs/kit": "^2.15.0",
16
16
  "@sveltejs/vite-plugin-svelte": "^7.0.0",
17
17
  "@tailwindcss/vite": "^4.2.4",
@@ -1,180 +1,180 @@
1
- @import 'tailwindcss';
2
-
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 - one of @svgrid/grid's 19 built-in presets (pick one
10
- * with `npm create @svgrid@latest -- --theme <id> [--dark]`, or edit the
11
- * values below by hand). The dashboard shell reads them through the --app-*
12
- * aliases further down, so the whole app themes as one, not just the grid.
13
- * ------------------------------------------------------------------------- */
14
- /* svgrid-theme:start */
15
- :root {
16
- --sg-bg: #ffffff;
17
- --sg-fg: #0f172a;
18
- --sg-muted: #64748b;
19
- --sg-border: #e2e8f0;
20
- --sg-header-bg: #f8fafc;
21
- --sg-header-fg: #0f172a;
22
- --sg-bg-subtle: #f8fafc;
23
- --sg-row-alt-bg: #ffffff;
24
- --sg-row-hover-bg: #f1f5f9;
25
- --sg-selection-bg: #e0e7ff;
26
- --sg-input-bg: #ffffff;
27
- --sg-input-border: #e2e8f0;
28
- --sg-accent: #4f46e5;
29
- --sg-on-accent: #ffffff;
30
- --sg-radius: 6px;
31
- --sg-font: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
32
- --sg-scrollbar-bg: #f8fafc;
33
- --sg-scrollbar-border: #e2e8f0;
34
- --sg-scrollbar-thumb: #64748b;
35
- --sg-scrollbar-thumb-hover: #0f172a;
36
- --sg-scrollbar-thumb-active: #0f172a;
37
- --sg-scrollbar-arrow: #64748b;
38
- --sg-scrollbar-arrow-hover: #0f172a;
39
- --sg-scrollbar-arrow-hover-bg: #f1f5f9;
40
- --sg-scrollbar-arrow-active: #0f172a;
41
- --sg-scrollbar-arrow-active-bg: #e0e7ff;
42
- --sg-scrollbar-arrow-disabled: #e2e8f0;
43
- color-scheme: light;
44
- }
45
- :root[data-theme='dark'] {
46
- --sg-bg: #0f172a;
47
- --sg-fg: #f8fafc;
48
- --sg-muted: #94a3b8;
49
- --sg-border: #334155;
50
- --sg-header-bg: #1e293b;
51
- --sg-header-fg: #f8fafc;
52
- --sg-bg-subtle: #1e293b;
53
- --sg-row-alt-bg: #0f172a;
54
- --sg-row-hover-bg: #1e293b;
55
- --sg-selection-bg: #312e81;
56
- --sg-input-bg: #0f172a;
57
- --sg-input-border: #334155;
58
- --sg-accent: #818cf8;
59
- --sg-on-accent: #18181b;
60
- --sg-radius: 6px;
61
- --sg-font: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
62
- --sg-scrollbar-bg: #1e293b;
63
- --sg-scrollbar-border: #334155;
64
- --sg-scrollbar-thumb: #94a3b8;
65
- --sg-scrollbar-thumb-hover: #f8fafc;
66
- --sg-scrollbar-thumb-active: #f8fafc;
67
- --sg-scrollbar-arrow: #94a3b8;
68
- --sg-scrollbar-arrow-hover: #f8fafc;
69
- --sg-scrollbar-arrow-hover-bg: #1e293b;
70
- --sg-scrollbar-arrow-active: #f8fafc;
71
- --sg-scrollbar-arrow-active-bg: #312e81;
72
- --sg-scrollbar-arrow-disabled: #334155;
73
- color-scheme: dark;
74
- }
75
- /* svgrid-theme:end */
76
-
77
- /* Dashboard shell aliases - map the chrome to the grid tokens above, so any
78
- * theme (light or dark, whichever preset) reskins the whole page as one. */
79
- :root {
80
- --app-bg: var(--sg-row-alt-bg);
81
- --app-panel: var(--sg-bg);
82
- --app-border: var(--sg-border);
83
- --app-fg: var(--sg-fg);
84
- --app-muted: var(--sg-muted);
85
- --app-accent: var(--sg-accent);
86
- }
87
-
88
- html,
89
- body {
90
- background: var(--app-bg);
91
- color: var(--app-fg);
92
- font-family: var(--sg-font, Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial,
93
- sans-serif);
94
- }
95
-
96
- /* ---------------------------------------------------------------------------
97
- * Optional polish. The grid themes itself from the --sg-* tokens above, so it
98
- * already follows light / dark with no extra CSS. These rules only add table
99
- * niceties the grid leaves to the app: cell gridlines, zebra striping, row
100
- * hover, and a styled pagination bar. Delete any you don't want.
101
- * ------------------------------------------------------------------------- */
102
-
103
- /* Cell gridlines (the grid draws none by default). */
104
- .sv-grid-column,
105
- .sv-grid-cell {
106
- border-right: 1px solid var(--sg-border) !important;
107
- border-bottom: 1px solid var(--sg-border) !important;
108
- }
109
- .sv-grid-table tr > :last-child.sv-grid-column,
110
- .sv-grid-table tr > :last-child.sv-grid-cell {
111
- border-right: 0 !important;
112
- }
113
-
114
- /* Zebra striping + row hover. */
115
- .sv-grid-table tbody tr:nth-child(even) .sv-grid-cell {
116
- background: var(--sg-row-alt-bg) !important;
117
- }
118
- .sv-grid-table tbody tr:hover .sv-grid-cell {
119
- background: var(--sg-row-hover-bg) !important;
120
- }
121
- /* Selection wins over zebra (declared after, same specificity). */
122
- .sv-grid-table tbody tr.sv-grid-row-selected .sv-grid-cell,
123
- .sv-grid-table tbody tr .sv-grid-cell[data-selected-range='true'] {
124
- background: var(--sg-selection-bg) !important;
125
- }
126
-
127
- /* Pagination bar: layout + theme (the grid ships the markup, not the styling). */
128
- .sv-grid-pagination {
129
- display: flex !important;
130
- align-items: center !important;
131
- justify-content: flex-end !important;
132
- gap: 24px !important;
133
- padding: 12px 16px !important;
134
- border: 1px solid var(--sg-border) !important;
135
- border-top: 0 !important;
136
- border-radius: 0 0 6px 6px !important;
137
- background: var(--sg-header-bg) !important;
138
- color: var(--sg-fg) !important;
139
- font-size: 13px !important;
140
- }
141
- .sv-grid-pagination-pagesize {
142
- display: inline-flex;
143
- align-items: center;
144
- gap: 8px;
145
- color: var(--sg-muted);
146
- }
147
- .sv-grid-pagination-pagesize select,
148
- .sv-grid-pagination-btn {
149
- border: 1px solid var(--sg-input-border);
150
- background: var(--sg-input-bg);
151
- color: var(--sg-fg);
152
- border-radius: 5px;
153
- height: 28px;
154
- font: inherit;
155
- font-size: 13px;
156
- cursor: pointer;
157
- }
158
- .sv-grid-pagination-btn {
159
- display: inline-flex !important;
160
- align-items: center !important;
161
- justify-content: center !important;
162
- width: 28px !important;
163
- border: 0 !important;
164
- background: transparent !important;
165
- font-size: 16px !important;
166
- }
167
- .sv-grid-pagination-btn:hover:not(:disabled) {
168
- background: var(--sg-input-bg) !important;
169
- color: var(--sg-accent) !important;
170
- }
171
- .sv-grid-pagination-btn:disabled {
172
- color: var(--sg-muted) !important;
173
- opacity: 0.4 !important;
174
- cursor: default !important;
175
- }
176
- .sv-grid-pagination-nav {
177
- display: inline-flex;
178
- align-items: center;
179
- gap: 4px;
180
- }
1
+ @import 'tailwindcss';
2
+
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 - one of @svgrid/grid's 20 built-in presets (pick one
10
+ * with `npm create @svgrid@latest -- --theme <id> [--dark]`, or edit the
11
+ * values below by hand). The dashboard shell reads them through the --app-*
12
+ * aliases further down, so the whole app themes as one, not just the grid.
13
+ * ------------------------------------------------------------------------- */
14
+ /* svgrid-theme:start */
15
+ :root {
16
+ --sg-bg: #ffffff;
17
+ --sg-fg: #0f172a;
18
+ --sg-muted: #64748b;
19
+ --sg-border: #e2e8f0;
20
+ --sg-header-bg: #f8fafc;
21
+ --sg-header-fg: #0f172a;
22
+ --sg-bg-subtle: #f8fafc;
23
+ --sg-row-alt-bg: #ffffff;
24
+ --sg-row-hover-bg: #f1f5f9;
25
+ --sg-selection-bg: #e0e7ff;
26
+ --sg-input-bg: #ffffff;
27
+ --sg-input-border: #e2e8f0;
28
+ --sg-accent: #4f46e5;
29
+ --sg-on-accent: #ffffff;
30
+ --sg-radius: 6px;
31
+ --sg-font: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
32
+ --sg-scrollbar-bg: #f8fafc;
33
+ --sg-scrollbar-border: #e2e8f0;
34
+ --sg-scrollbar-thumb: #64748b;
35
+ --sg-scrollbar-thumb-hover: #0f172a;
36
+ --sg-scrollbar-thumb-active: #0f172a;
37
+ --sg-scrollbar-arrow: #64748b;
38
+ --sg-scrollbar-arrow-hover: #0f172a;
39
+ --sg-scrollbar-arrow-hover-bg: #f1f5f9;
40
+ --sg-scrollbar-arrow-active: #0f172a;
41
+ --sg-scrollbar-arrow-active-bg: #e0e7ff;
42
+ --sg-scrollbar-arrow-disabled: #e2e8f0;
43
+ color-scheme: light;
44
+ }
45
+ :root[data-theme='dark'] {
46
+ --sg-bg: #0f172a;
47
+ --sg-fg: #f8fafc;
48
+ --sg-muted: #94a3b8;
49
+ --sg-border: #334155;
50
+ --sg-header-bg: #1e293b;
51
+ --sg-header-fg: #f8fafc;
52
+ --sg-bg-subtle: #1e293b;
53
+ --sg-row-alt-bg: #0f172a;
54
+ --sg-row-hover-bg: #1e293b;
55
+ --sg-selection-bg: #312e81;
56
+ --sg-input-bg: #0f172a;
57
+ --sg-input-border: #334155;
58
+ --sg-accent: #818cf8;
59
+ --sg-on-accent: #18181b;
60
+ --sg-radius: 6px;
61
+ --sg-font: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
62
+ --sg-scrollbar-bg: #1e293b;
63
+ --sg-scrollbar-border: #334155;
64
+ --sg-scrollbar-thumb: #94a3b8;
65
+ --sg-scrollbar-thumb-hover: #f8fafc;
66
+ --sg-scrollbar-thumb-active: #f8fafc;
67
+ --sg-scrollbar-arrow: #94a3b8;
68
+ --sg-scrollbar-arrow-hover: #f8fafc;
69
+ --sg-scrollbar-arrow-hover-bg: #1e293b;
70
+ --sg-scrollbar-arrow-active: #f8fafc;
71
+ --sg-scrollbar-arrow-active-bg: #312e81;
72
+ --sg-scrollbar-arrow-disabled: #334155;
73
+ color-scheme: dark;
74
+ }
75
+ /* svgrid-theme:end */
76
+
77
+ /* Dashboard shell aliases - map the chrome to the grid tokens above, so any
78
+ * theme (light or dark, whichever preset) reskins the whole page as one. */
79
+ :root {
80
+ --app-bg: var(--sg-row-alt-bg);
81
+ --app-panel: var(--sg-bg);
82
+ --app-border: var(--sg-border);
83
+ --app-fg: var(--sg-fg);
84
+ --app-muted: var(--sg-muted);
85
+ --app-accent: var(--sg-accent);
86
+ }
87
+
88
+ html,
89
+ body {
90
+ background: var(--app-bg);
91
+ color: var(--app-fg);
92
+ font-family: var(--sg-font, Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial,
93
+ sans-serif);
94
+ }
95
+
96
+ /* ---------------------------------------------------------------------------
97
+ * Optional polish. The grid themes itself from the --sg-* tokens above, so it
98
+ * already follows light / dark with no extra CSS. These rules only add table
99
+ * niceties the grid leaves to the app: cell gridlines, zebra striping, row
100
+ * hover, and a styled pagination bar. Delete any you don't want.
101
+ * ------------------------------------------------------------------------- */
102
+
103
+ /* Cell gridlines (the grid draws none by default). */
104
+ .sv-grid-column,
105
+ .sv-grid-cell {
106
+ border-right: 1px solid var(--sg-border) !important;
107
+ border-bottom: 1px solid var(--sg-border) !important;
108
+ }
109
+ .sv-grid-table tr > :last-child.sv-grid-column,
110
+ .sv-grid-table tr > :last-child.sv-grid-cell {
111
+ border-right: 0 !important;
112
+ }
113
+
114
+ /* Zebra striping + row hover. */
115
+ .sv-grid-table tbody tr:nth-child(even) .sv-grid-cell {
116
+ background: var(--sg-row-alt-bg) !important;
117
+ }
118
+ .sv-grid-table tbody tr:hover .sv-grid-cell {
119
+ background: var(--sg-row-hover-bg) !important;
120
+ }
121
+ /* Selection wins over zebra (declared after, same specificity). */
122
+ .sv-grid-table tbody tr.sv-grid-row-selected .sv-grid-cell,
123
+ .sv-grid-table tbody tr .sv-grid-cell[data-selected-range='true'] {
124
+ background: var(--sg-selection-bg) !important;
125
+ }
126
+
127
+ /* Pagination bar: layout + theme (the grid ships the markup, not the styling). */
128
+ .sv-grid-pagination {
129
+ display: flex !important;
130
+ align-items: center !important;
131
+ justify-content: flex-end !important;
132
+ gap: 24px !important;
133
+ padding: 12px 16px !important;
134
+ border: 1px solid var(--sg-border) !important;
135
+ border-top: 0 !important;
136
+ border-radius: 0 0 6px 6px !important;
137
+ background: var(--sg-header-bg) !important;
138
+ color: var(--sg-fg) !important;
139
+ font-size: 13px !important;
140
+ }
141
+ .sv-grid-pagination-pagesize {
142
+ display: inline-flex;
143
+ align-items: center;
144
+ gap: 8px;
145
+ color: var(--sg-muted);
146
+ }
147
+ .sv-grid-pagination-pagesize select,
148
+ .sv-grid-pagination-btn {
149
+ border: 1px solid var(--sg-input-border);
150
+ background: var(--sg-input-bg);
151
+ color: var(--sg-fg);
152
+ border-radius: 5px;
153
+ height: 28px;
154
+ font: inherit;
155
+ font-size: 13px;
156
+ cursor: pointer;
157
+ }
158
+ .sv-grid-pagination-btn {
159
+ display: inline-flex !important;
160
+ align-items: center !important;
161
+ justify-content: center !important;
162
+ width: 28px !important;
163
+ border: 0 !important;
164
+ background: transparent !important;
165
+ font-size: 16px !important;
166
+ }
167
+ .sv-grid-pagination-btn:hover:not(:disabled) {
168
+ background: var(--sg-input-bg) !important;
169
+ color: var(--sg-accent) !important;
170
+ }
171
+ .sv-grid-pagination-btn:disabled {
172
+ color: var(--sg-muted) !important;
173
+ opacity: 0.4 !important;
174
+ cursor: default !important;
175
+ }
176
+ .sv-grid-pagination-nav {
177
+ display: inline-flex;
178
+ align-items: center;
179
+ gap: 4px;
180
+ }
@@ -11,6 +11,25 @@ npm run build
11
11
 
12
12
  Open `src/App.svelte` and edit the `rows` / `columns` to use your own data.
13
13
 
14
+ ## Theme
15
+
16
+ `src/app.css` imports one of the 20 presets that ship with `@svgrid/grid`.
17
+ Swap the id to re-theme the grid and the page around it together:
18
+
19
+ ```css
20
+ @import '@svgrid/grid/themes/nord.css';
21
+ ```
22
+
23
+ Pick from `ember`, `shadcn`, `tailwind`, `material`, `fluent`, `carbon`,
24
+ `antd`, `bootstrap`, `atlassian`, `salesforce`, `sap`, `github`, `linear`,
25
+ `notion`, `vercel`, `excel`, `nord`, `dracula`, `catppuccin`, `ag-alpine`.
26
+
27
+ Every preset carries a light and a dark palette. Dark hangs off
28
+ `<html data-theme="dark">`, which the toggle in the header writes and
29
+ `index.html` restores before the first paint. To drop the presets and write
30
+ your own `--sg-*` values instead, see
31
+ [Theme and density](https://svgrid.com/docs/getting-started/5-theme-and-density).
32
+
14
33
  ## Next steps
15
34
 
16
35
  - Add more features: `rowExpandingFeature`, `columnGroupingFeature`, pagination, pinned rows. See the [docs](https://svgrid.com/docs/).
@@ -1,9 +1,19 @@
1
1
  <!doctype html>
2
- <html lang="en">
2
+ <html lang="en" data-theme="light">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>SvGrid app</title>
7
+ <script>
8
+ // Settle the theme before the first paint, otherwise the page renders in
9
+ // one palette and App.svelte's toggle swaps it a frame later. With nothing
10
+ // saved yet we follow the OS.
11
+ try {
12
+ var saved = localStorage.getItem('theme')
13
+ var fallback = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
14
+ document.documentElement.dataset.theme = saved || fallback
15
+ } catch (e) {}
16
+ </script>
7
17
  </head>
8
18
  <body>
9
19
  <div id="app"></div>
@@ -1,16 +1,23 @@
1
1
  <script>
2
- import {
3
- SvGrid,
4
- tableFeatures,
5
- rowSortingFeature,
6
- columnFilteringFeature,
7
- rowSelectionFeature,
8
- } from '@svgrid/grid'
9
-
10
- const features = tableFeatures({
11
- rowSortingFeature,
12
- columnFilteringFeature,
13
- rowSelectionFeature,
2
+ import { SvGrid } from '@svgrid/grid'
3
+
4
+ // Capabilities are boolean props - `sortable`, `filterable`, `editable`,
5
+ // `groupable`, `pageable` - and each injects the feature it needs. For finer
6
+ // control you can register features explicitly with `tableFeatures({ ... })`
7
+ // and pass them as `features`; see docs/getting-started/4-features.md.
8
+
9
+ // Light/dark. The preset imported in src/app.css declares both palettes, so
10
+ // writing `data-theme` on <html> is all it takes to swap them. index.html has
11
+ // already restored the saved choice by the time this runs.
12
+ let theme = $state(document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light')
13
+
14
+ $effect(() => {
15
+ document.documentElement.dataset.theme = theme
16
+ try {
17
+ localStorage.setItem('theme', theme)
18
+ } catch (e) {
19
+ // Private mode / storage disabled. The toggle still works for this tab.
20
+ }
14
21
  })
15
22
 
16
23
  // Your data. Swap for a fetch() in onMount, a store, or props.
@@ -36,26 +43,101 @@
36
43
  ]
37
44
  </script>
38
45
 
39
- <main style="max-width: 720px; margin: 3rem auto; font-family: system-ui, sans-serif;">
40
- <h1 style="font-size: 1.4rem;">SvGrid</h1>
41
- <p style="color:#64748b;">
42
- Sort, filter, select, and double-click a cell to edit. Edit
43
- <code>src/App.svelte</code> to make it yours.
44
- </p>
46
+ <main>
47
+ <header>
48
+ <div>
49
+ <h1>SvGrid</h1>
50
+ <p>
51
+ Sort, filter, select, and double-click a cell to edit. Edit
52
+ <code>src/App.svelte</code> to make it yours.
53
+ </p>
54
+ </div>
55
+ <button
56
+ type="button"
57
+ onclick={() => (theme = theme === 'dark' ? 'light' : 'dark')}
58
+ aria-label="Switch to {theme === 'dark' ? 'light' : 'dark'} mode"
59
+ >
60
+ {theme === 'dark' ? 'Light' : 'Dark'}
61
+ </button>
62
+ </header>
45
63
 
46
- <div style="height: 320px;">
64
+ <div class="grid-shell">
47
65
  <SvGrid
48
66
  data={rows}
49
67
  {columns}
50
- {features}
51
- filterMode="menu"
68
+ sortable
69
+ filterable
70
+ editable
52
71
  selectionMode="row"
53
72
  showRowSelection={true}
54
73
  showRowNumbers={true}
55
- enableInlineEditing={true}
56
74
  rowHeight={38}
57
75
  containerHeight="100%"
58
76
  fitColumns={true}
59
77
  />
60
78
  </div>
79
+
80
+ <p class="hint">
81
+ Theme: <code>src/app.css</code> imports one of 20 presets. Swap the id for
82
+ <code>material</code>, <code>nord</code>, <code>dracula</code>, and so on.
83
+ </p>
61
84
  </main>
85
+
86
+ <style>
87
+ /* Chrome reads the same --sg-* tokens as the grid, so it re-themes with it. */
88
+ main {
89
+ max-width: 720px;
90
+ margin: 3rem auto;
91
+ padding: 0 1rem;
92
+ }
93
+
94
+ header {
95
+ display: flex;
96
+ align-items: flex-start;
97
+ justify-content: space-between;
98
+ gap: 1rem;
99
+ }
100
+
101
+ h1 {
102
+ margin: 0;
103
+ font-size: 1.4rem;
104
+ }
105
+
106
+ p {
107
+ color: var(--sg-muted);
108
+ }
109
+
110
+ code {
111
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
112
+ font-size: 0.9em;
113
+ }
114
+
115
+ button {
116
+ flex: none;
117
+ padding: 0.4rem 0.8rem;
118
+ border: 1px solid var(--sg-border);
119
+ border-radius: var(--sg-radius, 6px);
120
+ background: var(--sg-bg-subtle, transparent);
121
+ color: var(--sg-fg);
122
+ font: inherit;
123
+ font-size: 0.85rem;
124
+ cursor: pointer;
125
+ }
126
+
127
+ button:hover {
128
+ background: var(--sg-row-hover-bg);
129
+ }
130
+
131
+ button:focus-visible {
132
+ outline: 2px solid var(--sg-accent);
133
+ outline-offset: 2px;
134
+ }
135
+
136
+ .grid-shell {
137
+ height: 320px;
138
+ }
139
+
140
+ .hint {
141
+ font-size: 0.85rem;
142
+ }
143
+ </style>
@@ -0,0 +1,36 @@
1
+ /* ---------------------------------------------------------------------------
2
+ * Theme.
3
+ *
4
+ * @svgrid/grid ships 20 presets, each one a full light AND dark palette on the
5
+ * --sg-* custom properties: ember, shadcn, tailwind, material, fluent, carbon,
6
+ * antd, bootstrap, atlassian, salesforce, sap, github, linear, notion, vercel,
7
+ * excel, nord, dracula, catppuccin, ag-alpine.
8
+ *
9
+ * Swap the id below to re-theme the grid and this page together, or delete the
10
+ * import and declare the tokens yourself. The dark half of every preset hangs
11
+ * off <html data-theme="dark">, which the toggle in App.svelte writes.
12
+ *
13
+ * Token reference: https://svgrid.com/docs/getting-started/5-theme-and-density
14
+ * ------------------------------------------------------------------------- */
15
+ /* svgrid-theme:start */
16
+ @import '@svgrid/grid/themes/shadcn.css';
17
+ /* svgrid-theme:end */
18
+
19
+ /* Which way the page leans, so scrollbars, form controls and the canvas behind
20
+ the app follow the theme instead of staying stubbornly light. */
21
+ :root {
22
+ color-scheme: light;
23
+ }
24
+ :root[data-theme='dark'] {
25
+ color-scheme: dark;
26
+ }
27
+
28
+ /* The page chrome reads the same tokens the grid does, so changing the preset
29
+ above moves the whole app, not just the table. */
30
+ body {
31
+ margin: 0;
32
+ min-height: 100vh;
33
+ background: var(--sg-bg);
34
+ color: var(--sg-fg);
35
+ font-family: var(--sg-font, system-ui, sans-serif);
36
+ }
@@ -1,4 +1,5 @@
1
1
  import { mount } from 'svelte'
2
+ import './app.css'
2
3
  import App from './App.svelte'
3
4
 
4
5
  const app = mount(App, { target: document.getElementById('app') })