@svgrid/create 2.0.0 → 2.2.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/LICENSE +46 -0
- package/README.md +58 -49
- package/index.mjs +360 -228
- package/package.json +43 -37
- package/templates/admin-dashboard/_package.json +1 -1
- package/templates/admin-dashboard/src/app.css +180 -176
- package/templates/minimal/_package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
This repository is a multi-package workspace. Different packages ship
|
|
2
|
+
under different licenses. The authoritative license for each package is
|
|
3
|
+
the LICENSE file inside that package directory.
|
|
4
|
+
|
|
5
|
+
------------------------------------------------------------------------
|
|
6
|
+
Per-package licensing
|
|
7
|
+
------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
packages/grid/ MIT (terms below, and packages/grid/LICENSE)
|
|
10
|
+
packages/enterprise/ Commercial - see packages/enterprise/LICENSE
|
|
11
|
+
packages/mcp/ Commercial - see packages/mcp/LICENSE
|
|
12
|
+
website/ Proprietary - see website/LICENSE
|
|
13
|
+
|
|
14
|
+
Only `@svgrid/grid` is open source. Everything else in this
|
|
15
|
+
repository (the Pro feature pack, the MCP server, the marketing +
|
|
16
|
+
docs website, and any other package added later that does not ship
|
|
17
|
+
its own MIT LICENSE) is proprietary and may not be copied, modified,
|
|
18
|
+
or redistributed without a paid license. Source is visible in this
|
|
19
|
+
repository for transparency and for paying customers; visibility does
|
|
20
|
+
not grant a license.
|
|
21
|
+
|
|
22
|
+
------------------------------------------------------------------------
|
|
23
|
+
MIT License (applies ONLY to packages/grid)
|
|
24
|
+
------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
MIT License
|
|
27
|
+
|
|
28
|
+
Copyright (c) 2026 jQWidgets Ltd
|
|
29
|
+
|
|
30
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
31
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
32
|
+
in the Software without restriction, including without limitation the rights
|
|
33
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
34
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
35
|
+
furnished to do so, subject to the following conditions:
|
|
36
|
+
|
|
37
|
+
The above copyright notice and this permission notice shall be included in all
|
|
38
|
+
copies or substantial portions of the Software.
|
|
39
|
+
|
|
40
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
41
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
42
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
43
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
44
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
45
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
46
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,49 +1,58 @@
|
|
|
1
|
-
# @svgrid/create
|
|
2
|
-
|
|
3
|
-
Scaffold a [Svelte](https://svelte.dev) app powered by
|
|
4
|
-
[SvGrid](https://www.svgrid.com) - the modern Svelte 5 data grid - in one
|
|
5
|
-
command.
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm create @svgrid@latest
|
|
9
|
-
# or
|
|
10
|
-
pnpm create @svgrid
|
|
11
|
-
# or
|
|
12
|
-
yarn create @svgrid
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Interactive by default. Or pass a directory and template directly:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm create @svgrid@latest my-app -- --template admin-dashboard
|
|
19
|
-
pnpm create @svgrid my-app -t minimal
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Templates
|
|
23
|
-
|
|
24
|
-
| Template | Stack | Best for |
|
|
25
|
-
| --- | --- | --- |
|
|
26
|
-
| `minimal` | Vite + Svelte 5 + SvGrid | Dropping a grid into something quickly |
|
|
27
|
-
| `admin-dashboard` | SvelteKit + Tailwind + SvGrid, deploy to Vercel | A real dashboard / internal tool |
|
|
28
|
-
|
|
29
|
-
## Options
|
|
30
|
-
|
|
31
|
-
| Flag | Description |
|
|
32
|
-
| --- | --- |
|
|
33
|
-
| `--template`, `-t` | `minimal` or `admin-dashboard` |
|
|
34
|
-
| `--
|
|
35
|
-
| `--
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
1
|
+
# @svgrid/create
|
|
2
|
+
|
|
3
|
+
Scaffold a [Svelte](https://svelte.dev) app powered by
|
|
4
|
+
[SvGrid](https://www.svgrid.com) - the modern Svelte 5 data grid - in one
|
|
5
|
+
command.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm create @svgrid@latest
|
|
9
|
+
# or
|
|
10
|
+
pnpm create @svgrid
|
|
11
|
+
# or
|
|
12
|
+
yarn create @svgrid
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Interactive by default. Or pass a directory and template directly:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm create @svgrid@latest my-app -- --template admin-dashboard
|
|
19
|
+
pnpm create @svgrid my-app -t minimal
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Templates
|
|
23
|
+
|
|
24
|
+
| Template | Stack | Best for |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `minimal` | Vite + Svelte 5 + SvGrid | Dropping a grid into something quickly |
|
|
27
|
+
| `admin-dashboard` | SvelteKit + Tailwind + SvGrid, deploy to Vercel | A real dashboard / internal tool |
|
|
28
|
+
|
|
29
|
+
## Options
|
|
30
|
+
|
|
31
|
+
| Flag | Description |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `--template`, `-t` | `minimal` or `admin-dashboard` |
|
|
34
|
+
| `--theme <id>` | `admin-dashboard` only. One of `@svgrid/grid`'s 19 built-in presets - shadcn, Tailwind, Material, Excel, Fluent, and more (default: `tailwind`) |
|
|
35
|
+
| `--dark` / `--light` | `admin-dashboard` only. Start in dark or light mode (default: `dark`) |
|
|
36
|
+
| `--force`, `-f` | Scaffold into a non-empty directory |
|
|
37
|
+
| `--help`, `-h` | Show usage |
|
|
38
|
+
|
|
39
|
+
Choosing `admin-dashboard` interactively also prompts for a theme and light/dark
|
|
40
|
+
mode - or skip the prompts with flags:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm create @svgrid@latest my-app -- -t admin-dashboard --theme material --light
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cd my-app
|
|
50
|
+
npm install
|
|
51
|
+
npm run dev
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
All templates use the free MIT `@svgrid/grid` core. Add
|
|
55
|
+
[`@svgrid/enterprise`](https://www.svgrid.com/pricing) for Excel/PDF export, import,
|
|
56
|
+
print, pivot, and AI helpers.
|
|
57
|
+
|
|
58
|
+
SvGrid(TM) is a trademark of jQWidgets Ltd. This package is MIT-licensed.
|
package/index.mjs
CHANGED
|
@@ -1,228 +1,360 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// @svgrid/create - scaffold a Svelte app powered by SvGrid.
|
|
3
|
-
//
|
|
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
|
-
//
|
|
9
|
-
// Zero runtime dependencies - Node built-ins only. Copies a bundled template,
|
|
10
|
-
// renames `_`-prefixed dotfiles, and rewrites the project name.
|
|
11
|
-
|
|
12
|
-
import { cp, mkdir, readdir, rename, stat, readFile, writeFile } from 'node:fs/promises'
|
|
13
|
-
import { existsSync } from 'node:fs'
|
|
14
|
-
import { dirname, join, basename, resolve, isAbsolute } from 'node:path'
|
|
15
|
-
import { fileURLToPath } from 'node:url'
|
|
16
|
-
import { createInterface } from 'node:readline/promises'
|
|
17
|
-
import { stdin, stdout } from 'node:process'
|
|
18
|
-
|
|
19
|
-
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
20
|
-
|
|
21
|
-
const TEMPLATES = {
|
|
22
|
-
minimal: {
|
|
23
|
-
label: 'Minimal - Vite + Svelte 5 + SvGrid, one page',
|
|
24
|
-
bundled: join(__dirname, 'templates', 'minimal'),
|
|
25
|
-
},
|
|
26
|
-
'admin-dashboard': {
|
|
27
|
-
label: 'Admin dashboard - SvelteKit shell, multiple grids, deploy to Vercel',
|
|
28
|
-
bundled: join(__dirname, 'templates', 'admin-dashboard'),
|
|
29
|
-
// When running from the monorepo before `prepack` has synced the bundled
|
|
30
|
-
// copy, fall back to the canonical source.
|
|
31
|
-
fallback: join(__dirname, '..', '..', 'templates', 'sveltekit-admin-dashboard'),
|
|
32
|
-
},
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const RENAME_BACK = new Map([
|
|
36
|
-
['_gitignore', '.gitignore'],
|
|
37
|
-
['_npmrc', '.npmrc'],
|
|
38
|
-
['_env.example', '.env.example'],
|
|
39
|
-
['_package.json', 'package.json'],
|
|
40
|
-
])
|
|
41
|
-
|
|
42
|
-
const c = {
|
|
43
|
-
reset: '\x1b[0m',
|
|
44
|
-
bold: '\x1b[1m',
|
|
45
|
-
dim: '\x1b[2m',
|
|
46
|
-
green: '\x1b[32m',
|
|
47
|
-
cyan: '\x1b[36m',
|
|
48
|
-
red: '\x1b[31m',
|
|
49
|
-
yellow: '\x1b[33m',
|
|
50
|
-
}
|
|
51
|
-
const color = stdout.isTTY ? (k, s) => `${c[k]}${s}${c.reset}` : (_k, s) => s
|
|
52
|
-
|
|
53
|
-
function parseArgs(argv) {
|
|
54
|
-
const args = { _: [], template: null, force: false, help: false }
|
|
55
|
-
for (let i = 0; i < argv.length; i++) {
|
|
56
|
-
const a = argv[i]
|
|
57
|
-
if (a === '--help' || a === '-h') args.help = true
|
|
58
|
-
else if (a === '--force' || a === '-f') args.force = true
|
|
59
|
-
else if (a === '--template' || a === '-t') args.template = argv[++i]
|
|
60
|
-
else if (a.startsWith('--template=')) args.template = a.slice('--template='.length)
|
|
61
|
-
else if (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
${
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
${
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
await
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (!
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @svgrid/create - scaffold a Svelte app powered by SvGrid.
|
|
3
|
+
//
|
|
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
|
+
//
|
|
9
|
+
// Zero runtime dependencies - Node built-ins only. Copies a bundled template,
|
|
10
|
+
// renames `_`-prefixed dotfiles, and rewrites the project name.
|
|
11
|
+
|
|
12
|
+
import { cp, mkdir, readdir, rename, stat, readFile, writeFile } from 'node:fs/promises'
|
|
13
|
+
import { existsSync } from 'node:fs'
|
|
14
|
+
import { dirname, join, basename, resolve, isAbsolute } from 'node:path'
|
|
15
|
+
import { fileURLToPath } from 'node:url'
|
|
16
|
+
import { createInterface } from 'node:readline/promises'
|
|
17
|
+
import { stdin, stdout } from 'node:process'
|
|
18
|
+
|
|
19
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
20
|
+
|
|
21
|
+
const TEMPLATES = {
|
|
22
|
+
minimal: {
|
|
23
|
+
label: 'Minimal - Vite + Svelte 5 + SvGrid, one page',
|
|
24
|
+
bundled: join(__dirname, 'templates', 'minimal'),
|
|
25
|
+
},
|
|
26
|
+
'admin-dashboard': {
|
|
27
|
+
label: 'Admin dashboard - SvelteKit shell, multiple grids, deploy to Vercel',
|
|
28
|
+
bundled: join(__dirname, 'templates', 'admin-dashboard'),
|
|
29
|
+
// When running from the monorepo before `prepack` has synced the bundled
|
|
30
|
+
// copy, fall back to the canonical source.
|
|
31
|
+
fallback: join(__dirname, '..', '..', 'templates', 'sveltekit-admin-dashboard'),
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const RENAME_BACK = new Map([
|
|
36
|
+
['_gitignore', '.gitignore'],
|
|
37
|
+
['_npmrc', '.npmrc'],
|
|
38
|
+
['_env.example', '.env.example'],
|
|
39
|
+
['_package.json', 'package.json'],
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
const c = {
|
|
43
|
+
reset: '\x1b[0m',
|
|
44
|
+
bold: '\x1b[1m',
|
|
45
|
+
dim: '\x1b[2m',
|
|
46
|
+
green: '\x1b[32m',
|
|
47
|
+
cyan: '\x1b[36m',
|
|
48
|
+
red: '\x1b[31m',
|
|
49
|
+
yellow: '\x1b[33m',
|
|
50
|
+
}
|
|
51
|
+
const color = stdout.isTTY ? (k, s) => `${c[k]}${s}${c.reset}` : (_k, s) => s
|
|
52
|
+
|
|
53
|
+
function parseArgs(argv) {
|
|
54
|
+
const args = { _: [], template: null, force: false, help: false, theme: null, mode: null }
|
|
55
|
+
for (let i = 0; i < argv.length; i++) {
|
|
56
|
+
const a = argv[i]
|
|
57
|
+
if (a === '--help' || a === '-h') args.help = true
|
|
58
|
+
else if (a === '--force' || a === '-f') args.force = true
|
|
59
|
+
else if (a === '--template' || a === '-t') args.template = argv[++i]
|
|
60
|
+
else if (a.startsWith('--template=')) args.template = a.slice('--template='.length)
|
|
61
|
+
else if (a === '--theme') args.theme = argv[++i]
|
|
62
|
+
else if (a.startsWith('--theme=')) args.theme = a.slice('--theme='.length)
|
|
63
|
+
else if (a === '--dark') args.mode = 'dark'
|
|
64
|
+
else if (a === '--light') args.mode = 'light'
|
|
65
|
+
else if (!a.startsWith('-')) args._.push(a)
|
|
66
|
+
}
|
|
67
|
+
return args
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function printHelp() {
|
|
71
|
+
stdout.write(`
|
|
72
|
+
${color('bold', '@svgrid/create')} - scaffold a Svelte app powered by SvGrid
|
|
73
|
+
|
|
74
|
+
${color('bold', 'Usage')}
|
|
75
|
+
npm create @svgrid@latest [dir] -- [--template <name>] [--force]
|
|
76
|
+
|
|
77
|
+
${color('bold', 'Templates')}
|
|
78
|
+
${Object.entries(TEMPLATES)
|
|
79
|
+
.map(([k, t]) => ` ${color('cyan', k.padEnd(16))} ${t.label.replace(/^\S+\s+-\s+/, '')}`)
|
|
80
|
+
.join('\n')}
|
|
81
|
+
|
|
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).
|
|
85
|
+
--force Scaffold into a non-empty directory.
|
|
86
|
+
|
|
87
|
+
${color('bold', 'Examples')}
|
|
88
|
+
npm create @svgrid@latest
|
|
89
|
+
npm create @svgrid@latest my-app -- --template admin-dashboard
|
|
90
|
+
npm create @svgrid@latest my-app -- -t admin-dashboard --theme material --light
|
|
91
|
+
pnpm create @svgrid my-app -t minimal
|
|
92
|
+
`)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const THEME_IDS = [
|
|
96
|
+
'shadcn', 'tailwind', 'material', 'excel', 'fluent', 'carbon', 'sap',
|
|
97
|
+
'salesforce', 'atlassian', 'github', 'antd', 'ag-alpine', 'bootstrap',
|
|
98
|
+
'vercel', 'linear', 'notion', 'nord', 'dracula', 'catppuccin',
|
|
99
|
+
]
|
|
100
|
+
|
|
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.
|
|
107
|
+
*/
|
|
108
|
+
async function promptTheme(args, ask, interactive) {
|
|
109
|
+
let themes
|
|
110
|
+
try {
|
|
111
|
+
themes = await import('@svgrid/grid/themes')
|
|
112
|
+
} catch {
|
|
113
|
+
return null // theming is best-effort; the template's own default stands in.
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let themeId = args.theme ? args.theme.trim().toLowerCase() : null
|
|
117
|
+
if (themeId && !themes.getThemePreset(themeId)) {
|
|
118
|
+
stdout.write(`${color('yellow', '!')} Unknown theme "${themeId}" - using tailwind. (${THEME_IDS.join(', ')})\n`)
|
|
119
|
+
themeId = null
|
|
120
|
+
}
|
|
121
|
+
if (!themeId && interactive) {
|
|
122
|
+
const list = themes.themePresets.map((t, i) => ` ${i + 1}. ${t.name} ${color('dim', `(${t.id})`)}`).join('\n')
|
|
123
|
+
stdout.write(`\n${color('bold', 'Theme')}\n${list}\n`)
|
|
124
|
+
const choice = await ask('Pick a number or id:', 'tailwind')
|
|
125
|
+
const byIndex = themes.themePresets[Number(choice) - 1]
|
|
126
|
+
themeId = byIndex ? byIndex.id : (themes.getThemePreset(choice.trim().toLowerCase())?.id ?? 'tailwind')
|
|
127
|
+
}
|
|
128
|
+
themeId ??= 'tailwind'
|
|
129
|
+
|
|
130
|
+
let mode = args.mode
|
|
131
|
+
if (!mode && interactive) {
|
|
132
|
+
const a = await ask('Light or dark mode? (light/dark)', 'dark')
|
|
133
|
+
mode = /^l(ight)?$/i.test(a) ? 'light' : 'dark'
|
|
134
|
+
}
|
|
135
|
+
mode ??= 'dark' // matches the template's existing default - only patched when 'light' is chosen.
|
|
136
|
+
|
|
137
|
+
return { themeId, mode, name: themes.getThemePreset(themeId)?.name ?? themeId, themes }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Rewrite `app.css` (between the `svgrid-theme` markers) with the resolved
|
|
141
|
+
* `--sg-*` tokens for the chosen theme. */
|
|
142
|
+
async function applyTheme(destDir, choice) {
|
|
143
|
+
if (!choice) return
|
|
144
|
+
const { themeId, themes } = choice
|
|
145
|
+
const preset = themes.getThemePreset(themeId) ?? themes.defaultThemePreset
|
|
146
|
+
const light = themes.resolveThemeTokens(preset, 'light')
|
|
147
|
+
const dark = themes.resolveThemeTokens(preset, 'dark')
|
|
148
|
+
const block = (selector, tokens, scheme) => {
|
|
149
|
+
const lines = Object.entries(tokens).map(([k, v]) => ` ${k}: ${v};`).join('\n')
|
|
150
|
+
return `${selector} {\n${lines}\n color-scheme: ${scheme};\n}`
|
|
151
|
+
}
|
|
152
|
+
const css = `${block(':root', light, 'light')}\n${block(":root[data-theme='dark']", dark, 'dark')}`
|
|
153
|
+
|
|
154
|
+
const cssPath = join(destDir, 'src', 'app.css')
|
|
155
|
+
const cssText = await readFile(cssPath, 'utf8').catch(() => null)
|
|
156
|
+
if (cssText == null) return
|
|
157
|
+
const marked = /\/\* svgrid-theme:start \*\/[\s\S]*?\/\* svgrid-theme:end \*\//
|
|
158
|
+
if (marked.test(cssText)) {
|
|
159
|
+
await writeFile(cssPath, cssText.replace(marked, `/* svgrid-theme:start */\n${css}\n/* svgrid-theme:end */`))
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** admin-dashboard defaults to dark (see app.html / src/lib/theme.ts /
|
|
164
|
+
* +layout.svelte). Flip all three to light-by-default when 'light' is chosen
|
|
165
|
+
* - a no-op when 'dark' is chosen, since that already matches the template. */
|
|
166
|
+
async function applyMode(destDir, choice) {
|
|
167
|
+
if (!choice || choice.mode !== 'light') return
|
|
168
|
+
|
|
169
|
+
const htmlPath = join(destDir, 'src', 'app.html')
|
|
170
|
+
const html = await readFile(htmlPath, 'utf8').catch(() => null)
|
|
171
|
+
if (html != null) {
|
|
172
|
+
await writeFile(
|
|
173
|
+
htmlPath,
|
|
174
|
+
html
|
|
175
|
+
.replace(`data-theme="dark"`, `data-theme="light"`)
|
|
176
|
+
.replace(`t === 'light' ? 'light' : 'dark'`, `t === 'dark' ? 'dark' : 'light'`),
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const themeTsPath = join(destDir, 'src', 'lib', 'theme.ts')
|
|
181
|
+
const themeTs = await readFile(themeTsPath, 'utf8').catch(() => null)
|
|
182
|
+
if (themeTs != null) {
|
|
183
|
+
await writeFile(
|
|
184
|
+
themeTsPath,
|
|
185
|
+
themeTs
|
|
186
|
+
.replace('Defaults to dark (matches app.html).', 'Defaults to light (matches app.html).')
|
|
187
|
+
.replace(`if (!browser) return 'dark'`, `if (!browser) return 'light'`)
|
|
188
|
+
.replace(
|
|
189
|
+
`return localStorage.getItem('theme') === 'light' ? 'light' : 'dark'`,
|
|
190
|
+
`return localStorage.getItem('theme') === 'dark' ? 'dark' : 'light'`,
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const layoutPath = join(destDir, 'src', 'routes', '+layout.svelte')
|
|
196
|
+
const layout = await readFile(layoutPath, 'utf8').catch(() => null)
|
|
197
|
+
if (layout != null) {
|
|
198
|
+
await writeFile(
|
|
199
|
+
layoutPath,
|
|
200
|
+
layout
|
|
201
|
+
.replace('// Theme: dark by default (see app.html), toggleable + persisted.', '// Theme: light by default (see app.html), toggleable + persisted.')
|
|
202
|
+
.replace(`let theme = $state<Theme>('dark')`, `let theme = $state<Theme>('light')`),
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function sanitizeName(name) {
|
|
208
|
+
return (
|
|
209
|
+
name
|
|
210
|
+
.trim()
|
|
211
|
+
.toLowerCase()
|
|
212
|
+
.replace(/[^a-z0-9._-]+/g, '-')
|
|
213
|
+
.replace(/^[-_.]+|[-_.]+$/g, '') || 'sv-grid-app'
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function resolveTemplateDir(key) {
|
|
218
|
+
const t = TEMPLATES[key]
|
|
219
|
+
if (existsSync(t.bundled)) return t.bundled
|
|
220
|
+
if (t.fallback && existsSync(t.fallback)) return t.fallback
|
|
221
|
+
return null
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function copyTemplate(srcDir, destDir) {
|
|
225
|
+
// Match only on the path RELATIVE to the template root. The absolute `src`
|
|
226
|
+
// includes the install location (e.g. `.../node_modules/@svgrid/create/...`),
|
|
227
|
+
// so testing it directly would match `node_modules` and skip the whole copy.
|
|
228
|
+
const skip = /(^|[\\/])(node_modules|\.svelte-kit|\.vercel|build|dist)([\\/]|$)/
|
|
229
|
+
await cp(srcDir, destDir, {
|
|
230
|
+
recursive: true,
|
|
231
|
+
filter: (src) => !skip.test(src.slice(srcDir.length)),
|
|
232
|
+
})
|
|
233
|
+
// Rename `_`-prefixed files back to their real dotfile names.
|
|
234
|
+
await renameBack(destDir)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function renameBack(dir) {
|
|
238
|
+
for (const entry of await readdir(dir)) {
|
|
239
|
+
const full = join(dir, entry)
|
|
240
|
+
if ((await stat(full)).isDirectory()) {
|
|
241
|
+
await renameBack(full)
|
|
242
|
+
} else if (RENAME_BACK.has(entry)) {
|
|
243
|
+
await rename(full, join(dir, RENAME_BACK.get(entry)))
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function setProjectName(destDir, name) {
|
|
249
|
+
const pkgPath = join(destDir, 'package.json')
|
|
250
|
+
if (!existsSync(pkgPath)) return
|
|
251
|
+
try {
|
|
252
|
+
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
|
|
253
|
+
pkg.name = name
|
|
254
|
+
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
|
|
255
|
+
} catch {
|
|
256
|
+
// leave the template's name if it isn't valid JSON for some reason
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function isEmptyDir(dir) {
|
|
261
|
+
if (!existsSync(dir)) return true
|
|
262
|
+
const entries = await readdir(dir)
|
|
263
|
+
return entries.filter((e) => e !== '.git').length === 0
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function main() {
|
|
267
|
+
const args = parseArgs(process.argv.slice(2))
|
|
268
|
+
if (args.help) return printHelp()
|
|
269
|
+
|
|
270
|
+
const interactive = stdin.isTTY && stdout.isTTY
|
|
271
|
+
let rl = null
|
|
272
|
+
const ask = async (q, def) => {
|
|
273
|
+
if (!interactive) return def
|
|
274
|
+
rl ??= createInterface({ input: stdin, output: stdout })
|
|
275
|
+
const a = (await rl.question(`${q} ${color('dim', `(${def})`)} `)).trim()
|
|
276
|
+
return a || def
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
stdout.write(`\n${color('bold', '◆ @svgrid/create')} ${color('dim', 'Svelte + SvGrid')}\n\n`)
|
|
280
|
+
|
|
281
|
+
// 1. Target directory.
|
|
282
|
+
let target = args._[0]
|
|
283
|
+
if (!target) target = await ask('Project directory:', 'my-sv-grid-app')
|
|
284
|
+
const destDir = resolve(process.cwd(), target)
|
|
285
|
+
const projectName = sanitizeName(basename(destDir))
|
|
286
|
+
|
|
287
|
+
// 2. Template.
|
|
288
|
+
let template = args.template
|
|
289
|
+
if (!template) {
|
|
290
|
+
if (interactive) {
|
|
291
|
+
stdout.write(`\n Templates:\n`)
|
|
292
|
+
Object.entries(TEMPLATES).forEach(([k, t], i) => {
|
|
293
|
+
stdout.write(` ${color('cyan', String(i + 1))}. ${t.label}\n`)
|
|
294
|
+
})
|
|
295
|
+
const pick = await ask('\nChoose a template (1-2):', '1')
|
|
296
|
+
template = Object.keys(TEMPLATES)[Number(pick) - 1] ?? 'minimal'
|
|
297
|
+
} else {
|
|
298
|
+
template = 'minimal'
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (!TEMPLATES[template]) {
|
|
302
|
+
if (rl) rl.close()
|
|
303
|
+
stdout.write(
|
|
304
|
+
`\n${color('red', '✖')} Unknown template "${template}". Choose: ${Object.keys(TEMPLATES).join(', ')}\n`,
|
|
305
|
+
)
|
|
306
|
+
process.exit(1)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 3. Safety: don't clobber a non-empty directory.
|
|
310
|
+
if (!(await isEmptyDir(destDir)) && !args.force) {
|
|
311
|
+
const ok = await ask(
|
|
312
|
+
`\n${color('yellow', '!')} ${target} is not empty. Continue and overwrite files? (y/N)`,
|
|
313
|
+
'N',
|
|
314
|
+
)
|
|
315
|
+
if (!/^y(es)?$/i.test(ok)) {
|
|
316
|
+
if (rl) rl.close()
|
|
317
|
+
stdout.write(`${color('red', '✖')} Aborted.\n`)
|
|
318
|
+
process.exit(1)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const srcDir = resolveTemplateDir(template)
|
|
323
|
+
if (!srcDir) {
|
|
324
|
+
if (rl) rl.close()
|
|
325
|
+
stdout.write(
|
|
326
|
+
`\n${color('red', '✖')} Template "${template}" is not available in this build.\n`,
|
|
327
|
+
)
|
|
328
|
+
process.exit(1)
|
|
329
|
+
}
|
|
330
|
+
|
|
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
|
|
334
|
+
if (rl) rl.close()
|
|
335
|
+
|
|
336
|
+
// 4. Scaffold.
|
|
337
|
+
await mkdir(destDir, { recursive: true })
|
|
338
|
+
await copyTemplate(srcDir, destDir)
|
|
339
|
+
await setProjectName(destDir, projectName)
|
|
340
|
+
await applyTheme(destDir, themeChoice)
|
|
341
|
+
await applyMode(destDir, themeChoice)
|
|
342
|
+
|
|
343
|
+
// 5. Next steps.
|
|
344
|
+
const rel = isAbsolute(target) || target.startsWith('.') ? target : `./${target}`
|
|
345
|
+
stdout.write(`\n${color('green', '✔')} Scaffolded ${color('bold', projectName)} (${template}) into ${rel}\n`)
|
|
346
|
+
if (themeChoice) {
|
|
347
|
+
stdout.write(` ${color('dim', 'theme')} ${themeChoice.name} (${themeChoice.mode})\n`)
|
|
348
|
+
}
|
|
349
|
+
stdout.write(`\n`)
|
|
350
|
+
stdout.write(`${color('bold', 'Next steps')}\n`)
|
|
351
|
+
stdout.write(` cd ${target}\n`)
|
|
352
|
+
stdout.write(` npm install\n`)
|
|
353
|
+
stdout.write(` npm run dev\n\n`)
|
|
354
|
+
stdout.write(`${color('dim', 'Docs:')} https://www.svgrid.com/docs ${color('dim', 'Pro:')} https://www.svgrid.com/pricing\n\n`)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
main().catch((err) => {
|
|
358
|
+
console.error(err)
|
|
359
|
+
process.exit(1)
|
|
360
|
+
})
|
package/package.json
CHANGED
|
@@ -1,38 +1,44 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@svgrid/create",
|
|
3
|
-
"
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@svgrid/create",
|
|
3
|
+
"funding": {
|
|
4
|
+
"type": "commercial",
|
|
5
|
+
"url": "https://svgrid.com/pricing"
|
|
6
|
+
},
|
|
7
|
+
"version": "2.2.0",
|
|
8
|
+
"description": "Scaffold a Svelte app powered by SvGrid in one command: npm create @svgrid@latest",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"author": "jQWidgets Ltd",
|
|
12
|
+
"homepage": "https://www.svgrid.com",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/sv-grid/sv-grid.git",
|
|
16
|
+
"directory": "packages/create-sv-grid"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"svelte",
|
|
20
|
+
"sveltekit",
|
|
21
|
+
"data-grid",
|
|
22
|
+
"datagrid",
|
|
23
|
+
"table",
|
|
24
|
+
"sv-grid",
|
|
25
|
+
"create",
|
|
26
|
+
"scaffold",
|
|
27
|
+
"starter",
|
|
28
|
+
"template"
|
|
29
|
+
],
|
|
30
|
+
"bin": "index.mjs",
|
|
31
|
+
"files": [
|
|
32
|
+
"index.mjs",
|
|
33
|
+
"templates"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@svgrid/grid": "^2.2.0"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"sync-templates": "node sync-templates.mjs"
|
|
43
|
+
}
|
|
38
44
|
}
|
|
@@ -1,176 +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
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* ------------------------------------------------------------------------- */
|
|
14
|
-
:
|
|
15
|
-
|
|
16
|
-
--sg-
|
|
17
|
-
--sg-
|
|
18
|
-
--sg-
|
|
19
|
-
--sg-
|
|
20
|
-
--sg-header-
|
|
21
|
-
--sg-
|
|
22
|
-
--sg-
|
|
23
|
-
--sg-row-
|
|
24
|
-
--sg-
|
|
25
|
-
--sg-selection-bg: #
|
|
26
|
-
--sg-
|
|
27
|
-
--sg-
|
|
28
|
-
--sg-
|
|
29
|
-
--sg-
|
|
30
|
-
--sg-
|
|
31
|
-
--sg-
|
|
32
|
-
--sg-
|
|
33
|
-
--sg-
|
|
34
|
-
--sg-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
--
|
|
38
|
-
--
|
|
39
|
-
--
|
|
40
|
-
--
|
|
41
|
-
--
|
|
42
|
-
--
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
--sg-bg: #
|
|
47
|
-
--sg-fg: #
|
|
48
|
-
--sg-muted: #94a3b8;
|
|
49
|
-
--sg-border: #
|
|
50
|
-
--sg-header-bg: #
|
|
51
|
-
--sg-header-fg: #
|
|
52
|
-
--sg-
|
|
53
|
-
--sg-row-
|
|
54
|
-
--sg-
|
|
55
|
-
--sg-
|
|
56
|
-
--sg-
|
|
57
|
-
--sg-
|
|
58
|
-
--sg-
|
|
59
|
-
--sg-
|
|
60
|
-
--sg-
|
|
61
|
-
--sg-
|
|
62
|
-
--sg-
|
|
63
|
-
--sg-
|
|
64
|
-
--sg-scrollbar-
|
|
65
|
-
--sg-scrollbar-
|
|
66
|
-
--sg-scrollbar-
|
|
67
|
-
--sg-scrollbar-arrow
|
|
68
|
-
--sg-scrollbar-arrow-hover
|
|
69
|
-
--sg-scrollbar-arrow-
|
|
70
|
-
--sg-scrollbar-arrow-active
|
|
71
|
-
--sg-scrollbar-arrow-
|
|
72
|
-
--sg-scrollbar-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
*
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
.sv-grid-
|
|
106
|
-
|
|
107
|
-
border-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
.sv-grid-table tbody tr
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
background:
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
.sv-grid-pagination-btn:disabled {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
+
}
|