@svgrid/ui 0.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 +53 -0
- package/index.mjs +303 -0
- package/package.json +49 -0
- package/recipes/buttons/button-group.svelte +18 -0
- package/recipes/buttons/button.svelte +17 -0
- package/recipes/buttons/check-box.svelte +10 -0
- package/recipes/buttons/radio-group.svelte +18 -0
- package/recipes/buttons/rating.svelte +8 -0
- package/recipes/buttons/repeat-button.svelte +10 -0
- package/recipes/buttons/switch-button.svelte +12 -0
- package/recipes/buttons/toggle-button.svelte +8 -0
- package/recipes/date-time/calendar.svelte +15 -0
- package/recipes/date-time/date-range-input.svelte +15 -0
- package/recipes/date-time/date-time-picker.svelte +16 -0
- package/recipes/date-time/time-picker.svelte +15 -0
- package/recipes/feedback/alert.svelte +11 -0
- package/recipes/feedback/avatar-group.svelte +11 -0
- package/recipes/feedback/avatar.svelte +8 -0
- package/recipes/feedback/badge.svelte +8 -0
- package/recipes/feedback/carousel.svelte +10 -0
- package/recipes/feedback/chip.svelte +10 -0
- package/recipes/feedback/empty-state.svelte +8 -0
- package/recipes/feedback/skeleton.svelte +8 -0
- package/recipes/feedback/timeline.svelte +10 -0
- package/recipes/inputs/color-input.svelte +8 -0
- package/recipes/inputs/duration-input.svelte +9 -0
- package/recipes/inputs/masked-input.svelte +14 -0
- package/recipes/inputs/number-input.svelte +17 -0
- package/recipes/inputs/otp-input.svelte +13 -0
- package/recipes/inputs/password-input.svelte +14 -0
- package/recipes/inputs/phone-input.svelte +8 -0
- package/recipes/inputs/tags-input.svelte +8 -0
- package/recipes/inputs/text-area.svelte +15 -0
- package/recipes/inputs/text-input.svelte +14 -0
- package/recipes/layout/accordion.svelte +17 -0
- package/recipes/layout/card.svelte +9 -0
- package/recipes/layout/divider.svelte +9 -0
- package/recipes/layout/dock-layout.svelte +22 -0
- package/recipes/layout/dock-manager.svelte +24 -0
- package/recipes/layout/field.svelte +15 -0
- package/recipes/layout/file-upload.svelte +16 -0
- package/recipes/layout/form.svelte +14 -0
- package/recipes/layout/grid-chart.svelte +16 -0
- package/recipes/layout/scroll-area.svelte +10 -0
- package/recipes/layout/splitter.svelte +13 -0
- package/recipes/layout/tabs.svelte +17 -0
- package/recipes/navigation/breadcrumb.svelte +12 -0
- package/recipes/navigation/command.svelte +12 -0
- package/recipes/navigation/nav-pane.svelte +14 -0
- package/recipes/navigation/pagination.svelte +13 -0
- package/recipes/navigation/rich-text.svelte +8 -0
- package/recipes/navigation/stepper.svelte +13 -0
- package/recipes/navigation/tour.svelte +14 -0
- package/recipes/navigation/tree.svelte +14 -0
- package/recipes/overlays/context-menu.svelte +15 -0
- package/recipes/overlays/drawer.svelte +13 -0
- package/recipes/overlays/menu.svelte +15 -0
- package/recipes/overlays/modal.svelte +13 -0
- package/recipes/overlays/popover.svelte +9 -0
- package/recipes/overlays/toaster.svelte +9 -0
- package/recipes/overlays/tooltip.svelte +8 -0
- package/recipes/range/circular-progress.svelte +9 -0
- package/recipes/range/gauge.svelte +16 -0
- package/recipes/range/progress.svelte +10 -0
- package/recipes/range/slider.svelte +19 -0
- package/recipes/range/sparkline.svelte +9 -0
- package/recipes/range/stat.svelte +9 -0
- package/recipes/registry.json +1462 -0
- package/recipes/selection/auto-complete.svelte +10 -0
- package/recipes/selection/combo-box.svelte +13 -0
- package/recipes/selection/country-input.svelte +8 -0
- package/recipes/selection/drop-down-list.svelte +13 -0
- package/recipes/selection/grid-select.svelte +17 -0
- package/recipes/selection/list-box.svelte +13 -0
- package/recipes/selection/multi-select.svelte +13 -0
- package/recipes/selection/tree-select.svelte +15 -0
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
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# @svgrid/ui
|
|
2
|
+
|
|
3
|
+
Add [SvGrid UI](https://www.svgrid.com/docs/help/ui-components) components to your
|
|
4
|
+
app in one command:
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npx @svgrid/ui add calendar
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## How it works
|
|
11
|
+
|
|
12
|
+
`@svgrid/ui` is a **recipe scaffolder**, not a component library. `add` writes a
|
|
13
|
+
minimal, ready-to-edit `.svelte` starter into your project that imports from
|
|
14
|
+
[`@svgrid/grid`](https://www.npmjs.com/package/@svgrid/grid), then makes sure the
|
|
15
|
+
package is a dependency and prints the install command. The components themselves
|
|
16
|
+
live in `@svgrid/grid` - you get bug fixes and new features by bumping the package,
|
|
17
|
+
while the file `add` drops in is yours to style and wire however you like.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
# add one component
|
|
23
|
+
npx @svgrid/ui add calendar
|
|
24
|
+
|
|
25
|
+
# add several, into a custom folder
|
|
26
|
+
npx @svgrid/ui add calendar time-picker --dir src/lib/ui
|
|
27
|
+
|
|
28
|
+
# add a whole family
|
|
29
|
+
npx @svgrid/ui add date-time
|
|
30
|
+
|
|
31
|
+
# install the dependency automatically (default: just prints the command)
|
|
32
|
+
npx @svgrid/ui add calendar --install
|
|
33
|
+
|
|
34
|
+
# see what you can add
|
|
35
|
+
npx @svgrid/ui list
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Options
|
|
39
|
+
|
|
40
|
+
| Flag | Description |
|
|
41
|
+
| ---------------- | ------------------------------------------------------------------------ |
|
|
42
|
+
| `--dir <path>` | Where to write files. Default: `src/lib/components/ui` (or `componentsDir` in a project `svgrid.json`). |
|
|
43
|
+
| `--force` | Overwrite files that already exist. |
|
|
44
|
+
| `--install` | Run your package manager to install deps instead of just printing it. |
|
|
45
|
+
|
|
46
|
+
## Components
|
|
47
|
+
|
|
48
|
+
The whole SvGrid UI catalogue is available - 70+ components across `date-time`,
|
|
49
|
+
`buttons`, `inputs`, `selection`, `range`, `overlays`, `layout`, `feedback` and
|
|
50
|
+
`navigation`. Add one by id, several at once, or a whole family by its group id
|
|
51
|
+
(e.g. `npx @svgrid/ui add inputs`). Run `npx @svgrid/ui list` for the full set.
|
|
52
|
+
|
|
53
|
+
MIT © jQWidgets Ltd
|
package/index.mjs
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @svgrid/ui - add SvGrid UI components to your app, one command at a time.
|
|
3
|
+
//
|
|
4
|
+
// npx @svgrid/ui add calendar
|
|
5
|
+
// npx @svgrid/ui add calendar time-picker --dir src/lib/ui
|
|
6
|
+
// npx @svgrid/ui add date-time # the whole date/time family
|
|
7
|
+
// npx @svgrid/ui list
|
|
8
|
+
//
|
|
9
|
+
// Recipe-scaffolder model: `add` writes a minimal, ready-to-EDIT .svelte starter
|
|
10
|
+
// that imports from `@svgrid/grid` (which you own and can change), then makes
|
|
11
|
+
// sure the package is a dependency and prints the install command. It does NOT
|
|
12
|
+
// vendor library source - the components live in `@svgrid/grid`.
|
|
13
|
+
//
|
|
14
|
+
// Zero runtime dependencies - Node built-ins only.
|
|
15
|
+
|
|
16
|
+
import { cp, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
17
|
+
import { existsSync } from 'node:fs'
|
|
18
|
+
import { dirname, join, resolve } from 'node:path'
|
|
19
|
+
import { fileURLToPath } from 'node:url'
|
|
20
|
+
import { stdin, stdout } from 'node:process'
|
|
21
|
+
import { spawnSync } from 'node:child_process'
|
|
22
|
+
|
|
23
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
24
|
+
const RECIPES_DIR = join(__dirname, 'recipes')
|
|
25
|
+
|
|
26
|
+
const c = {
|
|
27
|
+
reset: '\x1b[0m',
|
|
28
|
+
bold: '\x1b[1m',
|
|
29
|
+
dim: '\x1b[2m',
|
|
30
|
+
green: '\x1b[32m',
|
|
31
|
+
cyan: '\x1b[36m',
|
|
32
|
+
red: '\x1b[31m',
|
|
33
|
+
yellow: '\x1b[33m',
|
|
34
|
+
}
|
|
35
|
+
const color = stdout.isTTY ? (k, s) => `${c[k]}${s}${c.reset}` : (_k, s) => s
|
|
36
|
+
|
|
37
|
+
// Package managers: lockfile -> add command. Order matters (first match wins).
|
|
38
|
+
const PMS = [
|
|
39
|
+
{ id: 'pnpm', lock: 'pnpm-lock.yaml', add: 'pnpm add' },
|
|
40
|
+
{ id: 'bun', lock: 'bun.lockb', add: 'bun add' },
|
|
41
|
+
{ id: 'yarn', lock: 'yarn.lock', add: 'yarn add' },
|
|
42
|
+
{ id: 'npm', lock: 'package-lock.json', add: 'npm install' },
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
function parseArgs(argv) {
|
|
46
|
+
const args = { _: [], dir: null, force: false, install: false, help: false, js: false }
|
|
47
|
+
for (let i = 0; i < argv.length; i++) {
|
|
48
|
+
const a = argv[i]
|
|
49
|
+
if (a === '--help' || a === '-h') args.help = true
|
|
50
|
+
else if (a === '--force' || a === '-f') args.force = true
|
|
51
|
+
else if (a === '--install') args.install = true
|
|
52
|
+
else if (a === '--js') args.js = true
|
|
53
|
+
else if (a === '--ts') args.js = false
|
|
54
|
+
else if (a === '--dir' || a === '-d') args.dir = argv[++i]
|
|
55
|
+
else if (a.startsWith('--dir=')) args.dir = a.slice('--dir='.length)
|
|
56
|
+
else if (!a.startsWith('-')) args._.push(a)
|
|
57
|
+
}
|
|
58
|
+
return args
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function loadRegistry() {
|
|
62
|
+
const raw = await readFile(join(RECIPES_DIR, 'registry.json'), 'utf8')
|
|
63
|
+
return JSON.parse(raw)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Match a user-typed id against the registry: exact id, alias, or lowercase. */
|
|
67
|
+
function resolveItem(registry, token) {
|
|
68
|
+
const key = token.trim().toLowerCase()
|
|
69
|
+
return (
|
|
70
|
+
registry.items.find((it) => it.id === key) ??
|
|
71
|
+
registry.items.find((it) => (it.aliases ?? []).includes(key)) ??
|
|
72
|
+
null
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Expand a token into one or more recipe items (a group alias -> its members). */
|
|
77
|
+
function expand(registry, token) {
|
|
78
|
+
const key = token.trim().toLowerCase()
|
|
79
|
+
const group = registry.groups?.[key]
|
|
80
|
+
if (group) return group.items.map((id) => resolveItem(registry, id)).filter(Boolean)
|
|
81
|
+
const item = resolveItem(registry, key)
|
|
82
|
+
return item ? [item] : []
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Walk up from `start` to the nearest directory containing a package.json. */
|
|
86
|
+
function findProjectRoot(start) {
|
|
87
|
+
let dir = start
|
|
88
|
+
while (true) {
|
|
89
|
+
if (existsSync(join(dir, 'package.json'))) return dir
|
|
90
|
+
const parent = dirname(dir)
|
|
91
|
+
if (parent === dir) return null
|
|
92
|
+
dir = parent
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function detectPm(root) {
|
|
97
|
+
if (!root) return PMS.find((p) => p.id === 'npm')
|
|
98
|
+
return PMS.find((p) => existsSync(join(root, p.lock))) ?? PMS.find((p) => p.id === 'npm')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function targetDir(args, projectRoot, cwd) {
|
|
102
|
+
if (args.dir) return resolve(cwd, args.dir)
|
|
103
|
+
// Optional project config: svgrid.json { componentsDir }
|
|
104
|
+
if (projectRoot) {
|
|
105
|
+
const cfgPath = join(projectRoot, 'svgrid.json')
|
|
106
|
+
if (existsSync(cfgPath)) {
|
|
107
|
+
try {
|
|
108
|
+
const cfg = JSON.parse(await readFile(cfgPath, 'utf8'))
|
|
109
|
+
if (cfg.componentsDir) return resolve(projectRoot, cfg.componentsDir)
|
|
110
|
+
} catch {
|
|
111
|
+
/* fall through to the default */
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return join(projectRoot, 'src', 'lib', 'components', 'ui')
|
|
115
|
+
}
|
|
116
|
+
return join(cwd, 'src', 'lib', 'components', 'ui')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Add any missing deps to the nearest package.json (dependencies). Returns the
|
|
120
|
+
* list actually added so the caller can report / install them. */
|
|
121
|
+
async function ensureDeps(projectRoot, deps) {
|
|
122
|
+
if (!projectRoot) return deps
|
|
123
|
+
const pkgPath = join(projectRoot, 'package.json')
|
|
124
|
+
let pkg
|
|
125
|
+
try {
|
|
126
|
+
pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
|
|
127
|
+
} catch {
|
|
128
|
+
return deps
|
|
129
|
+
}
|
|
130
|
+
pkg.dependencies ??= {}
|
|
131
|
+
const added = []
|
|
132
|
+
for (const dep of deps) {
|
|
133
|
+
const known = pkg.dependencies[dep] || pkg.devDependencies?.[dep]
|
|
134
|
+
if (!known) {
|
|
135
|
+
pkg.dependencies[dep] = 'latest'
|
|
136
|
+
added.push(dep)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (added.length) await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
|
|
140
|
+
return added
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function printHelp() {
|
|
144
|
+
stdout.write(`
|
|
145
|
+
${color('bold', '@svgrid/ui')} - add SvGrid UI components to your app
|
|
146
|
+
|
|
147
|
+
${color('bold', 'Usage')}
|
|
148
|
+
npx @svgrid/ui add <component...> [--dir <path>] [--force] [--install]
|
|
149
|
+
npx @svgrid/ui list
|
|
150
|
+
|
|
151
|
+
${color('bold', 'Commands')}
|
|
152
|
+
${color('cyan', 'add')} Write a ready-to-edit recipe for each component into your project.
|
|
153
|
+
${color('cyan', 'list')} Show the components you can add.
|
|
154
|
+
|
|
155
|
+
${color('bold', 'Options')}
|
|
156
|
+
--dir <path> Where to write files (default: src/lib/components/ui, or the
|
|
157
|
+
"componentsDir" in a project svgrid.json).
|
|
158
|
+
--force Overwrite files that already exist.
|
|
159
|
+
--install Run the package manager to install deps (default: just print it).
|
|
160
|
+
|
|
161
|
+
${color('bold', 'Examples')}
|
|
162
|
+
npx @svgrid/ui add calendar
|
|
163
|
+
npx @svgrid/ui add calendar time-picker --dir src/lib/ui
|
|
164
|
+
npx @svgrid/ui add date-time --install
|
|
165
|
+
`)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function cmdList(registry) {
|
|
169
|
+
stdout.write(`\n${color('bold', 'Available components')}\n`)
|
|
170
|
+
for (const it of registry.items) {
|
|
171
|
+
stdout.write(` ${color('cyan', it.id.padEnd(18))} ${color('dim', it.description)}\n`)
|
|
172
|
+
}
|
|
173
|
+
const groups = Object.entries(registry.groups ?? {})
|
|
174
|
+
if (groups.length) {
|
|
175
|
+
stdout.write(`\n${color('bold', 'Groups')}\n`)
|
|
176
|
+
for (const [id, g] of groups) {
|
|
177
|
+
stdout.write(` ${color('cyan', id.padEnd(18))} ${color('dim', g.items.join(', '))}\n`)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
stdout.write(`\n${color('dim', 'Add one with:')} npx @svgrid/ui add ${registry.items[0]?.id ?? 'calendar'}\n\n`)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function cmdAdd(registry, tokens, args) {
|
|
184
|
+
if (!tokens.length) {
|
|
185
|
+
stdout.write(`${color('red', '✖')} Nothing to add. Try: ${color('cyan', 'npx @svgrid/ui list')}\n`)
|
|
186
|
+
process.exit(1)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Resolve tokens -> unique items.
|
|
190
|
+
const items = new Map()
|
|
191
|
+
const unknown = []
|
|
192
|
+
for (const tok of tokens) {
|
|
193
|
+
const matched = expand(registry, tok)
|
|
194
|
+
if (!matched.length) unknown.push(tok)
|
|
195
|
+
for (const it of matched) items.set(it.id, it)
|
|
196
|
+
}
|
|
197
|
+
if (unknown.length) {
|
|
198
|
+
stdout.write(
|
|
199
|
+
`${color('red', '✖')} Unknown component(s): ${unknown.join(', ')}\n` +
|
|
200
|
+
` See ${color('cyan', 'npx @svgrid/ui list')} for the available set.\n`,
|
|
201
|
+
)
|
|
202
|
+
process.exit(1)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const cwd = process.cwd()
|
|
206
|
+
const projectRoot = findProjectRoot(cwd)
|
|
207
|
+
const dest = await targetDir(args, projectRoot, cwd)
|
|
208
|
+
await mkdir(dest, { recursive: true })
|
|
209
|
+
|
|
210
|
+
const written = []
|
|
211
|
+
const skipped = []
|
|
212
|
+
const deps = new Set()
|
|
213
|
+
for (const it of items.values()) {
|
|
214
|
+
for (const d of it.deps ?? []) deps.add(d)
|
|
215
|
+
for (const file of it.files ?? []) {
|
|
216
|
+
const outPath = join(dest, file.write)
|
|
217
|
+
if (existsSync(outPath) && !args.force) {
|
|
218
|
+
skipped.push(file.write)
|
|
219
|
+
continue
|
|
220
|
+
}
|
|
221
|
+
await cp(join(RECIPES_DIR, file.from), outPath)
|
|
222
|
+
written.push(file.write)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Report writes.
|
|
227
|
+
stdout.write(`\n`)
|
|
228
|
+
for (const f of written) stdout.write(` ${color('green', '+')} ${relFromCwd(cwd, join(dest, f))}\n`)
|
|
229
|
+
for (const f of skipped)
|
|
230
|
+
stdout.write(` ${color('yellow', '•')} ${f} ${color('dim', 'already exists (use --force to overwrite)')}\n`)
|
|
231
|
+
if (!written.length) {
|
|
232
|
+
stdout.write(`\n${color('yellow', '!')} No files written.\n\n`)
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Ensure deps + install.
|
|
237
|
+
const added = await ensureDeps(projectRoot, [...deps])
|
|
238
|
+
const pm = detectPm(projectRoot)
|
|
239
|
+
if (added.length) {
|
|
240
|
+
if (args.install && projectRoot) {
|
|
241
|
+
stdout.write(`\n${color('dim', `Installing with ${pm.id}...`)}\n`)
|
|
242
|
+
const [bin, ...rest] = pm.add.split(' ')
|
|
243
|
+
const res = spawnSync(bin, [...rest, ...added], { cwd: projectRoot, stdio: 'inherit', shell: true })
|
|
244
|
+
if (res.status !== 0) {
|
|
245
|
+
stdout.write(`${color('yellow', '!')} Install failed - run it yourself: ${color('cyan', `${pm.add} ${added.join(' ')}`)}\n`)
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
stdout.write(`\n${color('bold', 'Install the dependency')}\n ${color('cyan', `${pm.add} ${added.join(' ')}`)}\n`)
|
|
249
|
+
}
|
|
250
|
+
} else if ([...deps].length) {
|
|
251
|
+
stdout.write(`\n${color('dim', `${[...deps].join(', ')} already in package.json.`)}\n`)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Usage hint.
|
|
255
|
+
const first = [...items.values()][0]
|
|
256
|
+
stdout.write(`\n${color('green', '✔')} Added ${written.length} file(s). They're yours - edit away.\n`)
|
|
257
|
+
if (first) {
|
|
258
|
+
stdout.write(` ${color('dim', 'Use it:')} import { ${exportName(first.id)} } from '@svgrid/grid'\n`)
|
|
259
|
+
}
|
|
260
|
+
stdout.write(`\n${color('dim', 'Docs:')} https://www.svgrid.com/docs/help/ui-components\n\n`)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Component export name from its id (calendar -> SvCalendar, time-picker ->
|
|
264
|
+
* SvTimePicker). */
|
|
265
|
+
function exportName(id) {
|
|
266
|
+
return 'Sv' + id.split('-').map((s) => s[0].toUpperCase() + s.slice(1)).join('')
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function relFromCwd(cwd, p) {
|
|
270
|
+
const r = p.startsWith(cwd) ? p.slice(cwd.length).replace(/^[\\/]/, '') : p
|
|
271
|
+
return r.split('\\').join('/')
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function main() {
|
|
275
|
+
const argv = process.argv.slice(2)
|
|
276
|
+
const args = parseArgs(argv)
|
|
277
|
+
const [command, ...rest] = args._
|
|
278
|
+
|
|
279
|
+
if (args.help || !command) return printHelp()
|
|
280
|
+
|
|
281
|
+
const registry = await loadRegistry()
|
|
282
|
+
stdout.write(`${color('bold', '◆ @svgrid/ui')}\n`)
|
|
283
|
+
|
|
284
|
+
switch (command) {
|
|
285
|
+
case 'list':
|
|
286
|
+
case 'ls':
|
|
287
|
+
return cmdList(registry)
|
|
288
|
+
case 'add':
|
|
289
|
+
return cmdAdd(registry, rest, args)
|
|
290
|
+
default:
|
|
291
|
+
// Treat a bare component id as `add <id>` for convenience.
|
|
292
|
+
if (resolveItem(registry, command) || registry.groups?.[command]) {
|
|
293
|
+
return cmdAdd(registry, [command, ...rest], args)
|
|
294
|
+
}
|
|
295
|
+
stdout.write(`${color('red', '✖')} Unknown command "${command}". Try ${color('cyan', 'add')} or ${color('cyan', 'list')}.\n`)
|
|
296
|
+
process.exit(1)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
main().catch((err) => {
|
|
301
|
+
console.error(err)
|
|
302
|
+
process.exit(1)
|
|
303
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@svgrid/ui",
|
|
3
|
+
"funding": {
|
|
4
|
+
"type": "commercial",
|
|
5
|
+
"url": "https://svgrid.com/pricing"
|
|
6
|
+
},
|
|
7
|
+
"version": "0.2.0",
|
|
8
|
+
"description": "Add SvGrid UI components to your app in one command: npx @svgrid/ui add calendar",
|
|
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/svgrid-ui"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"svelte",
|
|
20
|
+
"sveltekit",
|
|
21
|
+
"svgrid",
|
|
22
|
+
"sv-grid",
|
|
23
|
+
"ui",
|
|
24
|
+
"components",
|
|
25
|
+
"cli",
|
|
26
|
+
"scaffold",
|
|
27
|
+
"add",
|
|
28
|
+
"recipe"
|
|
29
|
+
],
|
|
30
|
+
"bin": "index.mjs",
|
|
31
|
+
"files": [
|
|
32
|
+
"index.mjs",
|
|
33
|
+
"recipes"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
40
|
+
"@tsconfig/svelte": "^5.0.8",
|
|
41
|
+
"svelte": "^5.55.5",
|
|
42
|
+
"svelte-check": "^4.4.6",
|
|
43
|
+
"typescript": "6.0.3",
|
|
44
|
+
"@svgrid/grid": "2.2.0"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"test:types": "svelte-check --tsconfig ./tsconfig.json"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SvButtonGroup - a segmented single/multi-select button bar. Your copy, edit freely. Docs: https://www.svgrid.com/docs/help/ui-components/sv-button-group
|
|
3
|
+
import { SvButtonGroup, type ButtonGroupItem } from '@svgrid/grid'
|
|
4
|
+
|
|
5
|
+
const views: ButtonGroupItem[] = [
|
|
6
|
+
{ value: 'day', label: 'Day' },
|
|
7
|
+
{ value: 'week', label: 'Week' },
|
|
8
|
+
{ value: 'month', label: 'Month' },
|
|
9
|
+
]
|
|
10
|
+
let view = $state<string | number>('week')
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<SvButtonGroup
|
|
14
|
+
items={views}
|
|
15
|
+
value={view}
|
|
16
|
+
mode="single"
|
|
17
|
+
onChange={(v) => (view = v as string)}
|
|
18
|
+
/>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SvButton - the themeable press primitive with variants and loading. Your copy, edit freely. Docs: https://www.svgrid.com/docs/help/ui-components/sv-button
|
|
3
|
+
import { SvButton } from '@svgrid/grid'
|
|
4
|
+
|
|
5
|
+
let saving = $state(false)
|
|
6
|
+
async function save() {
|
|
7
|
+
saving = true
|
|
8
|
+
try {
|
|
9
|
+
await new Promise((r) => setTimeout(r, 800))
|
|
10
|
+
} finally {
|
|
11
|
+
saving = false
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<SvButton variant="primary" loading={saving} onclick={save}>Save</SvButton>
|
|
17
|
+
<SvButton variant="outline">Cancel</SvButton>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SvCheckBox - a themed checkbox with a true indeterminate state. Your copy, edit freely. Docs: https://www.svgrid.com/docs/help/ui-components/sv-check-box
|
|
3
|
+
import { SvCheckBox } from '@svgrid/grid'
|
|
4
|
+
|
|
5
|
+
let agree = $state(false)
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<SvCheckBox checked={agree} onChange={(v) => (agree = v)}>
|
|
9
|
+
I accept the terms
|
|
10
|
+
</SvCheckBox>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SvRadioGroup - an accessible single-select radio group. Your copy, edit freely. Docs: https://www.svgrid.com/docs/help/ui-components/sv-radio-group
|
|
3
|
+
import { SvRadioGroup, type RadioOption } from '@svgrid/grid'
|
|
4
|
+
|
|
5
|
+
const plans: RadioOption[] = [
|
|
6
|
+
{ value: 'free', label: 'Free' },
|
|
7
|
+
{ value: 'pro', label: 'Pro' },
|
|
8
|
+
{ value: 'team', label: 'Team' },
|
|
9
|
+
]
|
|
10
|
+
let plan = $state<string | number | null>('pro')
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<SvRadioGroup
|
|
14
|
+
label="Plan"
|
|
15
|
+
options={plans}
|
|
16
|
+
value={plan}
|
|
17
|
+
onChange={(v) => (plan = v)}
|
|
18
|
+
/>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SvRating - a star rating control on the ARIA slider pattern. Your copy, edit freely. Docs: https://www.svgrid.com/docs/help/ui-components/sv-rating
|
|
3
|
+
import { SvRating } from '@svgrid/grid'
|
|
4
|
+
|
|
5
|
+
let score = $state(4)
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<SvRating label="Your rating" value={score} onChange={(v) => (score = v)} />
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SvRepeatButton - a button that keeps firing while held. Your copy, edit freely. Docs: https://www.svgrid.com/docs/help/ui-components/sv-repeat-button
|
|
3
|
+
import { SvRepeatButton } from '@svgrid/grid'
|
|
4
|
+
|
|
5
|
+
let qty = $state(1)
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<SvRepeatButton ariaLabel="Decrease" onclick={() => (qty = Math.max(0, qty - 1))}>-</SvRepeatButton>
|
|
9
|
+
<span>{qty}</span>
|
|
10
|
+
<SvRepeatButton ariaLabel="Increase" onclick={() => (qty += 1)}>+</SvRepeatButton>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SvSwitchButton - an on/off sliding switch for immediate settings. Your copy, edit freely. Docs: https://www.svgrid.com/docs/help/ui-components/sv-switch-button
|
|
3
|
+
import { SvSwitchButton } from '@svgrid/grid'
|
|
4
|
+
|
|
5
|
+
let notify = $state(true)
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<SvSwitchButton
|
|
9
|
+
label="Email notifications"
|
|
10
|
+
checked={notify}
|
|
11
|
+
onChange={(v) => (notify = v)}
|
|
12
|
+
/>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SvToggleButton - a button with a sticky pressed on/off state. Your copy, edit freely. Docs: https://www.svgrid.com/docs/help/ui-components/sv-toggle-button
|
|
3
|
+
import { SvToggleButton } from '@svgrid/grid'
|
|
4
|
+
|
|
5
|
+
let bold = $state(false)
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<SvToggleButton pressed={bold} onChange={(v) => (bold = v)}>Bold</SvToggleButton>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SvCalendar - a themeable month / year / decade calendar. This is your copy,
|
|
3
|
+
// edit it freely. Docs: https://www.svgrid.com/docs/help/ui-components/sv-calendar
|
|
4
|
+
import { SvCalendar } from '@svgrid/grid'
|
|
5
|
+
|
|
6
|
+
let value = $state<Date[]>([new Date()])
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<SvCalendar
|
|
10
|
+
{value}
|
|
11
|
+
selectionMode="range"
|
|
12
|
+
firstDayOfWeek={1}
|
|
13
|
+
footer
|
|
14
|
+
onChange={(dates) => (value = dates)}
|
|
15
|
+
/>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SvDateRangeInput - a compact start/end field over a two-month range calendar.
|
|
3
|
+
// This is your copy, edit it freely.
|
|
4
|
+
// Docs: https://www.svgrid.com/docs/help/ui-components/sv-date-range-input
|
|
5
|
+
import { SvDateRangeInput } from '@svgrid/grid'
|
|
6
|
+
|
|
7
|
+
let range = $state<[Date, Date] | null>(null)
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<SvDateRangeInput
|
|
11
|
+
value={range}
|
|
12
|
+
formatString="yyyy-MM-dd"
|
|
13
|
+
months={2}
|
|
14
|
+
onChange={(v) => (range = v)}
|
|
15
|
+
/>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SvDateTimePicker - a masked text field with DATE / TIME dropdown tabs. This
|
|
3
|
+
// is your copy, edit it freely.
|
|
4
|
+
// Docs: https://www.svgrid.com/docs/help/ui-components/sv-date-time-picker
|
|
5
|
+
import { SvDateTimePicker } from '@svgrid/grid'
|
|
6
|
+
|
|
7
|
+
let value = $state<Date | null>(new Date())
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<SvDateTimePicker
|
|
11
|
+
{value}
|
|
12
|
+
formatString="yyyy-MM-dd HH:mm"
|
|
13
|
+
dropDownDisplayMode="both"
|
|
14
|
+
nullable
|
|
15
|
+
onChange={(d) => (value = d)}
|
|
16
|
+
/>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SvTimePicker - an analog clock-dial time picker. This is your copy, edit it
|
|
3
|
+
// freely. Docs: https://www.svgrid.com/docs/help/ui-components/sv-time-picker
|
|
4
|
+
import { SvTimePicker } from '@svgrid/grid'
|
|
5
|
+
|
|
6
|
+
let time = $state(new Date())
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<SvTimePicker
|
|
10
|
+
value={time}
|
|
11
|
+
format="12-hour"
|
|
12
|
+
minuteInterval={5}
|
|
13
|
+
footer
|
|
14
|
+
onChange={(d) => (time = d)}
|
|
15
|
+
/>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SvAlert - an inline contextual message with icon and actions. Your copy, edit freely. Docs: https://www.svgrid.com/docs/help/ui-components/sv-alert
|
|
3
|
+
import { SvAlert, SvButton } from '@svgrid/grid'
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<SvAlert variant="success" title="Saved">Your changes are live.</SvAlert>
|
|
7
|
+
|
|
8
|
+
<SvAlert variant="danger" title="Payment failed" dismissible>
|
|
9
|
+
We could not charge your card.
|
|
10
|
+
{#snippet actions()}<SvButton size="sm" variant="danger">Retry</SvButton>{/snippet}
|
|
11
|
+
</SvAlert>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SvAvatarGroup - overlapping avatars with a "+N" overflow pill. Your copy, edit freely. Docs: https://www.svgrid.com/docs/help/ui-components/sv-avatar-group
|
|
3
|
+
import { SvAvatarGroup } from '@svgrid/grid'
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<SvAvatarGroup max={3} avatars={[
|
|
7
|
+
{ name: 'Ada Lovelace' },
|
|
8
|
+
{ name: 'Alan Turing' },
|
|
9
|
+
{ name: 'Grace Hopper' },
|
|
10
|
+
{ name: 'Ken Thompson' },
|
|
11
|
+
]} />
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SvAvatar - a user avatar with initials-and-color fallback. Your copy, edit freely. Docs: https://www.svgrid.com/docs/help/ui-components/sv-avatar
|
|
3
|
+
import { SvAvatar } from '@svgrid/grid'
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<SvAvatar name="Ada Lovelace" src="/ada.jpg" status="online" />
|
|
7
|
+
<SvAvatar name="Grace Hopper" />
|
|
8
|
+
<SvAvatar name="Alan Turing" size="lg" shape="square" />
|