@fusion.dev/helix-icons 0.99.0 → 0.101.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 +29 -0
- package/dist/build.d.ts +64 -0
- package/dist/build.js +73 -0
- package/dist/cli.d.ts +17 -0
- package/dist/cli.js +103 -0
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -38,6 +38,35 @@ an attribute would put every weight in the lookup `<helix-icon name>` reads.
|
|
|
38
38
|
Every Helix component that draws a mark also offers a slot over it, so you can
|
|
39
39
|
put your own drawing in one place without replacing the package.
|
|
40
40
|
|
|
41
|
+
## Your own set
|
|
42
|
+
|
|
43
|
+
The generator that builds this set is the package's own command, so a folder of
|
|
44
|
+
SVG files becomes a module the element draws from:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
npx helix-icons build --source ./brand-icons --out src/icons.ts
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { peak } from './icons'
|
|
52
|
+
|
|
53
|
+
<helix-icon icon={peak} />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Any set, not only ours. A subset of Phosphor larger than the 108 here is the
|
|
57
|
+
same command against the package it resolves from, and `--suffix` is for a set
|
|
58
|
+
that spells a weight into the file name:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
npx helix-icons build \
|
|
62
|
+
--source node_modules/@phosphor-icons/core/assets/bold --suffix -bold \
|
|
63
|
+
--list mine.txt --out src/icons.ts
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`--index` also writes the record `<helix-icon name>` reads. It names every glyph,
|
|
67
|
+
so nothing can drop any of it: ask for it when you want names in markup, and
|
|
68
|
+
leave it off when you import.
|
|
69
|
+
|
|
41
70
|
Phosphor is MIT and its notice travels in `LICENSE` beside ours.
|
|
42
71
|
|
|
43
72
|
[Documentation](https://fusion.dev/helix)
|
package/dist/build.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns a directory of SVG files into the module `<helix-icon>` draws from.
|
|
3
|
+
*
|
|
4
|
+
* **Published, because the shape is ours and the tool that makes it should
|
|
5
|
+
* travel with it.** An icon is `{ content, name, viewBox }`, so any set answers
|
|
6
|
+
* it, and until this shipped the only way to get one was to hand-write the
|
|
7
|
+
* objects or to send a pull request against this package. A consumer's own
|
|
8
|
+
* drawings, a subset of Phosphor larger than the hundred and eight here, or
|
|
9
|
+
* another library's set are all the same job, and it is this one.
|
|
10
|
+
*
|
|
11
|
+
* **A directory and a suffix, rather than knowledge of anybody's layout.** The
|
|
12
|
+
* sets differ in how they name a file for a weight: Phosphor writes
|
|
13
|
+
* `caret-down-bold.svg` and leaves regular bare, and another library will do
|
|
14
|
+
* something else. So the caller says which directories to look in and what a
|
|
15
|
+
* file is called, and this stays a reader of files.
|
|
16
|
+
*
|
|
17
|
+
* **Sources are a precedence, and a name in two of them is an error.** A
|
|
18
|
+
* precedence decides silently: the glyph on the page would be whichever
|
|
19
|
+
* directory sat higher, and nothing about it looks wrong afterwards. This is the
|
|
20
|
+
* rule the Helix set has always been built under, and it is the consumer's now
|
|
21
|
+
* as well.
|
|
22
|
+
*/
|
|
23
|
+
/** One place to look for glyphs, and what a file is called when it is there. */
|
|
24
|
+
export interface IconSource {
|
|
25
|
+
/** Where the SVG files are. */
|
|
26
|
+
directory: string;
|
|
27
|
+
/**
|
|
28
|
+
* What sits between the name and `.svg`, for a set that spells a weight into
|
|
29
|
+
* the file name. Phosphor's bold is `caret-down-bold.svg`; its regular, and
|
|
30
|
+
* most other sets, are bare.
|
|
31
|
+
*/
|
|
32
|
+
suffix?: string;
|
|
33
|
+
}
|
|
34
|
+
/** What to build, and where to put it. */
|
|
35
|
+
export interface BuildOptions {
|
|
36
|
+
/**
|
|
37
|
+
* The names to resolve, in the order they should be written.
|
|
38
|
+
*
|
|
39
|
+
* Left out, every glyph in the sources is taken, which is what a consumer
|
|
40
|
+
* pointing at their own folder of drawings usually means.
|
|
41
|
+
*/
|
|
42
|
+
names?: string[];
|
|
43
|
+
/** Where to write the module. */
|
|
44
|
+
out: string;
|
|
45
|
+
sources: IconSource[];
|
|
46
|
+
/**
|
|
47
|
+
* Also write the record `<helix-icon name>` reads.
|
|
48
|
+
*
|
|
49
|
+
* **Off by default, and the default is the point.** The record names every
|
|
50
|
+
* glyph, so no bundler can drop any of it: it is what a consumer pays for the
|
|
51
|
+
* convenience of naming an icon in markup. A module built for import does not
|
|
52
|
+
* need one, and one that has it charges everybody for a door they may not
|
|
53
|
+
* use.
|
|
54
|
+
*/
|
|
55
|
+
index?: boolean;
|
|
56
|
+
/** Where the emitted module imports `HelixIcon` from. */
|
|
57
|
+
typeImport?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Writes the module, and says how many glyphs are in it.
|
|
61
|
+
*
|
|
62
|
+
* @throws where a name resolves to no file, or to more than one.
|
|
63
|
+
*/
|
|
64
|
+
export declare function build(options: BuildOptions): number;
|
package/dist/build.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { optimize } from 'svgo';
|
|
4
|
+
const SVGO = {
|
|
5
|
+
multipass: true,
|
|
6
|
+
plugins: [{ name: 'preset-default', params: { overrides: { cleanupIds: false } } }],
|
|
7
|
+
};
|
|
8
|
+
const camel = (slug) => slug.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
9
|
+
const namesIn = (sources) => {
|
|
10
|
+
const found = new Set();
|
|
11
|
+
for (const { directory, suffix = '' } of sources) {
|
|
12
|
+
if (!existsSync(directory)) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
for (const file of readdirSync(directory)) {
|
|
16
|
+
if (!file.endsWith('.svg')) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const bare = file.slice(0, -'.svg'.length);
|
|
20
|
+
if (suffix === '' || bare.endsWith(suffix)) {
|
|
21
|
+
found.add(suffix === '' ? bare : bare.slice(0, -suffix.length));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return [...found].sort();
|
|
26
|
+
};
|
|
27
|
+
const sourceFor = (slug, sources) => {
|
|
28
|
+
const candidates = sources
|
|
29
|
+
.map(({ directory, suffix = '' }) => join(directory, `${slug}${suffix}.svg`))
|
|
30
|
+
.filter((file) => existsSync(file));
|
|
31
|
+
if (candidates.length > 1) {
|
|
32
|
+
throw new Error(`icons: "${slug}" resolves to ${candidates.length} files, and the order between them is a ` +
|
|
33
|
+
`silent decision:\n ${candidates.join('\n ')}\n` +
|
|
34
|
+
'Rename one, or name the sources in the order you mean.');
|
|
35
|
+
}
|
|
36
|
+
if (candidates.length === 0) {
|
|
37
|
+
throw new Error(`icons: no SVG for "${slug}" in ${sources.map((one) => one.directory).join(', ')}`);
|
|
38
|
+
}
|
|
39
|
+
return readFileSync(candidates[0], 'utf8');
|
|
40
|
+
};
|
|
41
|
+
const drawn = (slug, sources) => {
|
|
42
|
+
const source = optimize(sourceFor(slug, sources), SVGO).data;
|
|
43
|
+
const viewBox = /viewBox="([^"]+)"/.exec(source)?.[1] ?? '0 0 256 256';
|
|
44
|
+
const content = source
|
|
45
|
+
.replace(/^[\s\S]*?<svg[^>]*>/, '')
|
|
46
|
+
.replace(/<\/svg>[\s\S]*$/, '')
|
|
47
|
+
.replace(/<title>[\s\S]*?<\/title>/g, '')
|
|
48
|
+
.replace(/\s+/g, ' ')
|
|
49
|
+
.trim();
|
|
50
|
+
return { camel: camel(slug), content, slug, viewBox };
|
|
51
|
+
};
|
|
52
|
+
export function build(options) {
|
|
53
|
+
const { index = false, out, sources, typeImport = '@fusion.dev/helix-icons' } = options;
|
|
54
|
+
const names = options.names ?? namesIn(sources);
|
|
55
|
+
const entries = names.map((slug) => drawn(slug, sources));
|
|
56
|
+
const body = entries
|
|
57
|
+
.map(({ camel: name, content, slug, viewBox }) => `export const ${name}: HelixIcon = {\n content: '${content.replace(/'/g, "\\'")}',\n name: '${slug}',\n viewBox: '${viewBox}',\n}`)
|
|
58
|
+
.join('\n\n');
|
|
59
|
+
const record = entries.map(({ camel: name }) => ` [${name}.name]: ${name},`).join('\n');
|
|
60
|
+
writeFileSync(out, `// Generated by @fusion.dev/helix-icons. Do not edit.
|
|
61
|
+
import type { HelixIcon } from '${typeImport}'
|
|
62
|
+
|
|
63
|
+
${body}
|
|
64
|
+
${index
|
|
65
|
+
? `
|
|
66
|
+
/** Every icon, by name, for lookup from a \`name\` attribute. */
|
|
67
|
+
export const icons: Record<string, HelixIcon> = {
|
|
68
|
+
${record}
|
|
69
|
+
}
|
|
70
|
+
`
|
|
71
|
+
: ''}`);
|
|
72
|
+
return entries.length;
|
|
73
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `helix-icons build`, which turns SVG files into the module the element draws.
|
|
4
|
+
*
|
|
5
|
+
* ```sh
|
|
6
|
+
* helix-icons build --source ./brand-icons --out src/icons.ts
|
|
7
|
+
* helix-icons build --source node_modules/@phosphor-icons/core/assets/regular \
|
|
8
|
+
* --list mine.txt --out src/icons.ts
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* **A flag per source, in the order they are searched**, so a set that keeps
|
|
12
|
+
* its weights in separate folders is two flags rather than a convention this
|
|
13
|
+
* has to know. `--suffix` applies to the source before it, because that is what
|
|
14
|
+
* it is about: Phosphor's bold folder names a file `caret-down-bold.svg` and its
|
|
15
|
+
* regular folder names the same glyph `caret-down.svg`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function main(argv: string[]): number;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { build } from './build.js';
|
|
5
|
+
const USAGE = `helix-icons build --source <dir> [--suffix <text>] [--list <file>] [--out <file>]
|
|
6
|
+
|
|
7
|
+
--source <dir> Where SVG files are. Repeat for a fallback, searched in order.
|
|
8
|
+
--suffix <text> What a file name carries after the icon's name, for the source
|
|
9
|
+
before it: --source …/bold --suffix -bold reads caret-down-bold.svg.
|
|
10
|
+
--list <file> Names to build, one per line, # for a comment. Default: every
|
|
11
|
+
glyph the sources hold.
|
|
12
|
+
--out <file> Where to write the module. Default: icons.generated.ts
|
|
13
|
+
--index Also write the record \`<helix-icon name>\` reads. It names every
|
|
14
|
+
glyph, so nothing can drop any of it: for markup, not for imports.
|
|
15
|
+
--type-import <s> Where the module imports \`HelixIcon\` from.
|
|
16
|
+
Default: @fusion.dev/helix-icons`;
|
|
17
|
+
const after = (argv, at) => {
|
|
18
|
+
const value = argv[at + 1];
|
|
19
|
+
return value === undefined || value.startsWith('--') ? undefined : value;
|
|
20
|
+
};
|
|
21
|
+
export function main(argv) {
|
|
22
|
+
if (argv[0] !== 'build' || argv.includes('--help')) {
|
|
23
|
+
console.log(USAGE);
|
|
24
|
+
return argv[0] === 'build' ? 0 : 1;
|
|
25
|
+
}
|
|
26
|
+
const sources = [];
|
|
27
|
+
let list;
|
|
28
|
+
let out = 'icons.generated.ts';
|
|
29
|
+
let index = false;
|
|
30
|
+
let typeImport;
|
|
31
|
+
for (const [at, flag] of argv.entries()) {
|
|
32
|
+
switch (flag) {
|
|
33
|
+
case '--source': {
|
|
34
|
+
const directory = after(argv, at);
|
|
35
|
+
if (directory === undefined) {
|
|
36
|
+
console.error('helix-icons: --source needs a directory');
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
39
|
+
sources.push({ directory: resolve(directory) });
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
case '--suffix': {
|
|
43
|
+
const suffix = after(argv, at);
|
|
44
|
+
const last = sources.at(-1);
|
|
45
|
+
if (suffix === undefined || last === undefined) {
|
|
46
|
+
console.error('helix-icons: --suffix names what a file carries, after the --source it is for');
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
last.suffix = suffix;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case '--list':
|
|
53
|
+
list = after(argv, at);
|
|
54
|
+
break;
|
|
55
|
+
case '--out':
|
|
56
|
+
out = after(argv, at) ?? out;
|
|
57
|
+
break;
|
|
58
|
+
case '--index':
|
|
59
|
+
index = true;
|
|
60
|
+
break;
|
|
61
|
+
case '--type-import':
|
|
62
|
+
typeImport = after(argv, at);
|
|
63
|
+
break;
|
|
64
|
+
default:
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (sources.length === 0) {
|
|
69
|
+
console.error('helix-icons: needs at least one --source\n');
|
|
70
|
+
console.error(USAGE);
|
|
71
|
+
return 1;
|
|
72
|
+
}
|
|
73
|
+
let names;
|
|
74
|
+
if (list !== undefined) {
|
|
75
|
+
if (!existsSync(list)) {
|
|
76
|
+
console.error(`helix-icons: no list at ${list}`);
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
names = readFileSync(list, 'utf8')
|
|
80
|
+
.split('\n')
|
|
81
|
+
.map((line) => line.trim())
|
|
82
|
+
.filter((line) => line !== '' && !line.startsWith('#'))
|
|
83
|
+
.sort();
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const many = build({
|
|
87
|
+
index,
|
|
88
|
+
out: resolve(out),
|
|
89
|
+
sources,
|
|
90
|
+
...(names ? { names } : {}),
|
|
91
|
+
...(typeImport ? { typeImport } : {}),
|
|
92
|
+
});
|
|
93
|
+
console.log(`helix-icons: ${many} written to ${out}`);
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
catch (why) {
|
|
97
|
+
console.error(why instanceof Error ? why.message : String(why));
|
|
98
|
+
return 1;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (process.argv[1]?.includes('helix-icons') || process.argv[1]?.endsWith('cli.js')) {
|
|
102
|
+
process.exit(main(process.argv.slice(2)));
|
|
103
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusion.dev/helix-icons",
|
|
3
3
|
"description": "Helix icon set.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.101.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ross Sheppard <ross.sheppard@fusion.dev>",
|
|
7
7
|
"repository": {
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"types": "./dist/bold.d.ts",
|
|
22
22
|
"default": "./dist/bold.js"
|
|
23
23
|
},
|
|
24
|
+
"./build": {
|
|
25
|
+
"types": "./dist/build.d.ts",
|
|
26
|
+
"default": "./dist/build.js"
|
|
27
|
+
},
|
|
24
28
|
"./regular": {
|
|
25
29
|
"types": "./dist/regular.d.ts",
|
|
26
30
|
"default": "./dist/regular.js"
|
|
@@ -31,16 +35,22 @@
|
|
|
31
35
|
"raw"
|
|
32
36
|
],
|
|
33
37
|
"sideEffects": false,
|
|
38
|
+
"bin": {
|
|
39
|
+
"helix-icons": "./dist/cli.js"
|
|
40
|
+
},
|
|
34
41
|
"scripts": {
|
|
35
42
|
"build": "bun run icons && tsc -p tsconfig.build.json && tsc -p tsconfig.types.json",
|
|
36
43
|
"icons": "bun scripts/build-icons.mjs",
|
|
44
|
+
"test": "bun test src",
|
|
37
45
|
"typecheck": "bun run icons && tsc -p tsconfig.json --noEmit"
|
|
38
46
|
},
|
|
39
47
|
"bugs": "https://github.com/project-fusion/fusion/issues",
|
|
40
|
-
"
|
|
41
|
-
"@phosphor-icons/core": "^2.1.1",
|
|
48
|
+
"dependencies": {
|
|
42
49
|
"svgo": "^4.1.0"
|
|
43
50
|
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@phosphor-icons/core": "^2.1.1"
|
|
53
|
+
},
|
|
44
54
|
"homepage": "https://fusion.dev",
|
|
45
55
|
"keywords": [
|
|
46
56
|
"icons",
|