@stateglyph/cli 0.1.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 +21 -0
- package/README.md +24 -0
- package/THIRD_PARTY_NOTICES.md +52 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +238 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kamalveer Singh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# @stateglyph/cli
|
|
2
|
+
|
|
3
|
+
Copy editable StateGlyph components into a React project.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx @stateglyph/cli list
|
|
7
|
+
npx @stateglyph/cli add upload
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Add more than one icon or choose a different destination:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx @stateglyph/cli add upload save --dir src/components/stateglyph
|
|
14
|
+
npx @stateglyph/cli add all
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Use `--dry-run` to preview files and `--force` to replace existing files.
|
|
18
|
+
Without `--force`, the CLI safely skips files that already exist.
|
|
19
|
+
|
|
20
|
+
The generated components are TypeScript React files. They are self-contained
|
|
21
|
+
and depend only on `react` and `lucide-react`, so you can edit them freely in
|
|
22
|
+
your own project.
|
|
23
|
+
|
|
24
|
+
MIT licensed. The source icons from Lucide use the ISC License.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
StateGlyph uses glyphs from [Lucide](https://lucide.dev/) through
|
|
4
|
+
`lucide-react`. Lucide is licensed under the ISC License. Some Lucide glyphs
|
|
5
|
+
are derived from Feather Icons and remain available under the MIT License.
|
|
6
|
+
|
|
7
|
+
## Lucide Icons
|
|
8
|
+
|
|
9
|
+
ISC License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2026 Lucide Icons and Contributors
|
|
12
|
+
|
|
13
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
14
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
15
|
+
copyright notice and this permission notice appear in all copies.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
18
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
19
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
20
|
+
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
21
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
|
22
|
+
OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
23
|
+
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
24
|
+
|
|
25
|
+
## Feather Icons
|
|
26
|
+
|
|
27
|
+
The following Lucide glyphs used by StateGlyph are derived from Feather Icons:
|
|
28
|
+
`check`, `chevron-down`, `chevron-left`, `chevron-right`, `chevron-up`,
|
|
29
|
+
`download`, `maximize`, `minimize`, `minimize-2`, `pause`, `sidebar`,
|
|
30
|
+
`trash-2`, `upload`, and `x`.
|
|
31
|
+
|
|
32
|
+
The MIT License (MIT)
|
|
33
|
+
|
|
34
|
+
Copyright (c) 2013-present Cole Bemis
|
|
35
|
+
|
|
36
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
37
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
38
|
+
in the Software without restriction, including without limitation the rights
|
|
39
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
40
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
41
|
+
furnished to do so, subject to the following conditions:
|
|
42
|
+
|
|
43
|
+
The above copyright notice and this permission notice shall be included in all
|
|
44
|
+
copies or substantial portions of the Software.
|
|
45
|
+
|
|
46
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
47
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
48
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
49
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
50
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
51
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
52
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/add.ts
|
|
7
|
+
import { access, mkdir, writeFile } from "fs/promises";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { stateIconCatalog } from "@stateglyph/core";
|
|
10
|
+
|
|
11
|
+
// src/template.ts
|
|
12
|
+
function toPascalCase(value) {
|
|
13
|
+
return value.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
14
|
+
}
|
|
15
|
+
function renderIconComponent(definition) {
|
|
16
|
+
const componentName = `${toPascalCase(definition.id)}StateIcon`;
|
|
17
|
+
const stateTypeName = `${toPascalCase(definition.id)}State`;
|
|
18
|
+
const propsTypeName = `${componentName}Props`;
|
|
19
|
+
const states = Object.entries(definition.states);
|
|
20
|
+
const lucideComponents = [
|
|
21
|
+
...new Set(states.map(([, stateDefinition]) => stateDefinition.icon))
|
|
22
|
+
].map(toPascalCase).sort();
|
|
23
|
+
const continuousStates = states.filter(([, stateDefinition]) => stateDefinition.continuous).map(([name]) => name);
|
|
24
|
+
const animationName = `stateglyph-${definition.id}-spin`;
|
|
25
|
+
const stateUnion = states.map(([name]) => JSON.stringify(name)).join(" | ");
|
|
26
|
+
const iconRows = states.map(
|
|
27
|
+
([name, stateDefinition]) => ` ${JSON.stringify(name)}: ${toPascalCase(stateDefinition.icon)},`
|
|
28
|
+
).join("\n");
|
|
29
|
+
const labelRows = states.map(
|
|
30
|
+
([name, stateDefinition]) => ` ${JSON.stringify(name)}: ${JSON.stringify(stateDefinition.label)},`
|
|
31
|
+
).join("\n");
|
|
32
|
+
return `import type { SVGProps } from "react";
|
|
33
|
+
import {
|
|
34
|
+
${lucideComponents.map((name) => ` ${name},`).join("\n")}
|
|
35
|
+
type LucideIcon,
|
|
36
|
+
} from "lucide-react";
|
|
37
|
+
|
|
38
|
+
export type ${stateTypeName} = ${stateUnion};
|
|
39
|
+
|
|
40
|
+
export type ${propsTypeName} = Omit<SVGProps<SVGSVGElement>, "children"> & {
|
|
41
|
+
state: ${stateTypeName};
|
|
42
|
+
size?: number | string;
|
|
43
|
+
strokeWidth?: number;
|
|
44
|
+
duration?: number;
|
|
45
|
+
decorative?: boolean;
|
|
46
|
+
label?: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const iconByState = {
|
|
50
|
+
${iconRows}
|
|
51
|
+
} satisfies Record<${stateTypeName}, LucideIcon>;
|
|
52
|
+
|
|
53
|
+
const labelByState = {
|
|
54
|
+
${labelRows}
|
|
55
|
+
} satisfies Record<${stateTypeName}, string>;
|
|
56
|
+
|
|
57
|
+
const continuousStates = new Set<${stateTypeName}>(${JSON.stringify(continuousStates)});
|
|
58
|
+
|
|
59
|
+
export function ${componentName}({
|
|
60
|
+
state,
|
|
61
|
+
size = 24,
|
|
62
|
+
strokeWidth = 2,
|
|
63
|
+
duration = 900,
|
|
64
|
+
decorative = true,
|
|
65
|
+
label,
|
|
66
|
+
style,
|
|
67
|
+
...svgProps
|
|
68
|
+
}: ${propsTypeName}) {
|
|
69
|
+
const Icon = iconByState[state];
|
|
70
|
+
const isContinuous = continuousStates.has(state);
|
|
71
|
+
const accessibleLabel = label ?? labelByState[state];
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<>
|
|
75
|
+
{isContinuous ? (
|
|
76
|
+
<style>{\`@keyframes ${animationName} { to { transform: rotate(360deg); } } @media (prefers-reduced-motion: reduce) { [data-state-icon="${definition.id}"] { animation: none !important; } }\`}</style>
|
|
77
|
+
) : null}
|
|
78
|
+
<Icon
|
|
79
|
+
{...svgProps}
|
|
80
|
+
size={size}
|
|
81
|
+
strokeWidth={strokeWidth}
|
|
82
|
+
data-state-icon="${definition.id}"
|
|
83
|
+
data-state={state}
|
|
84
|
+
aria-hidden={decorative ? true : undefined}
|
|
85
|
+
aria-label={decorative ? undefined : accessibleLabel}
|
|
86
|
+
role={decorative ? undefined : "img"}
|
|
87
|
+
focusable="false"
|
|
88
|
+
style={{
|
|
89
|
+
...style,
|
|
90
|
+
animation:
|
|
91
|
+
style?.animation ??
|
|
92
|
+
(isContinuous
|
|
93
|
+
? \`${animationName} \${duration}ms linear infinite\`
|
|
94
|
+
: undefined),
|
|
95
|
+
}}
|
|
96
|
+
/>
|
|
97
|
+
</>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/add.ts
|
|
104
|
+
async function fileExists(filePath) {
|
|
105
|
+
try {
|
|
106
|
+
await access(filePath);
|
|
107
|
+
return true;
|
|
108
|
+
} catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function addIcons(requestedIcons, options = {}) {
|
|
113
|
+
const cwd = options.cwd ?? process.cwd();
|
|
114
|
+
const directory = options.directory ?? "components/stateglyph";
|
|
115
|
+
const destination = path.resolve(cwd, directory);
|
|
116
|
+
const requestedNames = requestedIcons.includes("all") ? stateIconCatalog.map((icon) => icon.id) : [...new Set(requestedIcons)];
|
|
117
|
+
const unknownNames = requestedNames.filter(
|
|
118
|
+
(name) => !stateIconCatalog.some((icon) => icon.id === name)
|
|
119
|
+
);
|
|
120
|
+
if (unknownNames.length > 0) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Unknown icon${unknownNames.length === 1 ? "" : "s"}: ${unknownNames.join(", ")}. Run 'stateglyph list' to see valid names.`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
const result = { added: [], skipped: [] };
|
|
126
|
+
for (const name of requestedNames) {
|
|
127
|
+
const icon = stateIconCatalog.find((item) => item.id === name);
|
|
128
|
+
if (!icon) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const relativePath = path.join(directory, `${icon.id}-state-icon.tsx`);
|
|
132
|
+
const filePath = path.resolve(cwd, relativePath);
|
|
133
|
+
if (await fileExists(filePath) && !options.force) {
|
|
134
|
+
result.skipped.push(relativePath);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
result.added.push(relativePath);
|
|
138
|
+
if (!options.dryRun) {
|
|
139
|
+
await mkdir(destination, { recursive: true });
|
|
140
|
+
await writeFile(filePath, renderIconComponent(icon), "utf8");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/list.ts
|
|
147
|
+
import { stateIconCatalog as stateIconCatalog2 } from "@stateglyph/core";
|
|
148
|
+
var categoryLabels = {
|
|
149
|
+
async: "Async workflows",
|
|
150
|
+
media: "Media controls",
|
|
151
|
+
navigation: "Navigation and layout",
|
|
152
|
+
feedback: "Feedback and toggles",
|
|
153
|
+
device: "Device and connection",
|
|
154
|
+
form: "Form states",
|
|
155
|
+
commerce: "Commerce",
|
|
156
|
+
notification: "Notifications"
|
|
157
|
+
};
|
|
158
|
+
function formatIconList() {
|
|
159
|
+
const sections = Object.entries(categoryLabels).flatMap(
|
|
160
|
+
([category, label]) => {
|
|
161
|
+
const icons = stateIconCatalog2.filter(
|
|
162
|
+
(icon) => icon.category === category
|
|
163
|
+
);
|
|
164
|
+
if (icons.length === 0) {
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
const rows = icons.map(
|
|
168
|
+
(icon) => ` ${icon.id.padEnd(22)} ${icon.title} (${Object.keys(icon.states).length} states)`
|
|
169
|
+
);
|
|
170
|
+
return [[`${label} (${icons.length})`, ...rows].join("\n")];
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
return [
|
|
174
|
+
`StateGlyph \u2014 ${stateIconCatalog2.length} icons`,
|
|
175
|
+
"",
|
|
176
|
+
...sections.flatMap(
|
|
177
|
+
(section, index) => index === sections.length - 1 ? [section] : [section, ""]
|
|
178
|
+
)
|
|
179
|
+
].join("\n");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/index.ts
|
|
183
|
+
var program = new Command();
|
|
184
|
+
program.name("stateglyph").description("Browse and add StateGlyph components to a React project.").version("0.1.0").showHelpAfterError();
|
|
185
|
+
program.command("list").description("List every available icon, grouped by category.").action(() => {
|
|
186
|
+
process.stdout.write(`${formatIconList()}
|
|
187
|
+
`);
|
|
188
|
+
});
|
|
189
|
+
program.command("add").description(
|
|
190
|
+
"Copy one or more StateGlyph components into the current project."
|
|
191
|
+
).argument("<icons...>", "Icon names to add, or 'all'").option(
|
|
192
|
+
"-d, --dir <directory>",
|
|
193
|
+
"Destination directory",
|
|
194
|
+
"components/stateglyph"
|
|
195
|
+
).option("--force", "Overwrite existing icon files", false).option(
|
|
196
|
+
"--dry-run",
|
|
197
|
+
"Show what would be created without writing files",
|
|
198
|
+
false
|
|
199
|
+
).action(
|
|
200
|
+
async (icons, options) => {
|
|
201
|
+
try {
|
|
202
|
+
const result = await addIcons(icons, {
|
|
203
|
+
directory: options.dir,
|
|
204
|
+
force: options.force,
|
|
205
|
+
dryRun: options.dryRun
|
|
206
|
+
});
|
|
207
|
+
const verb = options.dryRun ? "Would add" : "Added";
|
|
208
|
+
if (result.added.length > 0) {
|
|
209
|
+
process.stdout.write(`${verb}:
|
|
210
|
+
`);
|
|
211
|
+
for (const file of result.added) {
|
|
212
|
+
process.stdout.write(` ${file}
|
|
213
|
+
`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (result.skipped.length > 0) {
|
|
217
|
+
process.stdout.write("Skipped existing files:\n");
|
|
218
|
+
for (const file of result.skipped) {
|
|
219
|
+
process.stdout.write(` ${file}
|
|
220
|
+
`);
|
|
221
|
+
}
|
|
222
|
+
process.stdout.write("Use --force to overwrite them.\n");
|
|
223
|
+
}
|
|
224
|
+
if (!options.dryRun && result.added.length > 0) {
|
|
225
|
+
process.stdout.write(
|
|
226
|
+
"\nThe generated components require react and lucide-react.\n"
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
} catch (error) {
|
|
230
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
231
|
+
program.error(message);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
program.action(() => {
|
|
236
|
+
program.outputHelp();
|
|
237
|
+
});
|
|
238
|
+
await program.parseAsync();
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stateglyph/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Copy StateGlyph components into React projects.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Kamalveer Singh",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"cli",
|
|
9
|
+
"react",
|
|
10
|
+
"icons",
|
|
11
|
+
"typescript",
|
|
12
|
+
"codegen"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/kamalhara/StateGlyph.git",
|
|
17
|
+
"directory": "packages/cli"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/kamalhara/StateGlyph#readme",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/kamalhara/StateGlyph/issues"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"bin": {
|
|
28
|
+
"stateglyph": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE",
|
|
34
|
+
"THIRD_PARTY_NOTICES.md"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
38
|
+
"dev": "tsup src/index.ts --format esm --dts --watch",
|
|
39
|
+
"prepack": "node ../../scripts/sync-package-legal-files.mjs",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"prepublishOnly": "npm run build"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@stateglyph/core": "0.1.0",
|
|
45
|
+
"commander": "^15.0.0"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=22.19.0"
|
|
49
|
+
}
|
|
50
|
+
}
|