@cssints/icons 0.0.0-stage → 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 +21 -0
- package/README.md +24 -2
- package/dist/index.d.mts +34 -0
- package/dist/index.mjs +118 -0
- package/package.json +33 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexei Accio
|
|
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
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @cssints/icons
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Icons for [cssints](../../README.md): a scope() style with a mask kernel and one class per icon, from an iconify JSON collection
|
|
4
|
+
(`@iconify-json/*`) or a folder of SVG files. No runtime. The API is in [docs/API.md](../../docs/API.md#icons-cssintsicons-decided-2026-10-02-cssints-1x1o).
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { icons } from "@cssints/icons" with { type: "cssints" };
|
|
8
|
+
|
|
9
|
+
export const mdi = icons<typeof import("@iconify-json/mdi/icons.json")>({
|
|
10
|
+
provider: "iconify-json",
|
|
11
|
+
collection: "mdi",
|
|
12
|
+
});
|
|
13
|
+
// in a component: cn(mdi("home"), color("crimson"))
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd packages/icons && bun run test # fixtures through the engine, and the type test
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Credits
|
|
21
|
+
|
|
22
|
+
Icons come from [Iconify](https://iconify.design) data: `@iconify/utils` (MIT, Vjacheslav Trushkin) does the parsing.
|
|
23
|
+
The collections (`@iconify-json/*`) are your dependency and are not shipped with this package. Each collection has its
|
|
24
|
+
own license (in its package, `info.license`: Material Design Icons is Apache-2.0, Phosphor is MIT), and the CSS that
|
|
25
|
+
`icons()` prints holds the icons you use: keep the notice that your collection asks for.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Style } from "cssints/plugin";
|
|
2
|
+
//#region src/index.d.ts
|
|
3
|
+
/** An iconify JSON collection as TypeScript sees it: `typeof import("@iconify-json/mdi/icons.json")`. */
|
|
4
|
+
export interface Collection {
|
|
5
|
+
icons: Record<string, unknown>;
|
|
6
|
+
aliases?: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
/** The names of a collection: its icons and its aliases. `string` when the collection is not given. */
|
|
9
|
+
export type IconName<C extends Collection> = (keyof C["icons"] | keyof NonNullable<C["aliases"]>) & string;
|
|
10
|
+
/** `icons(...)` gives this: call it with a name where the style is used (`cn(mdi("home"), ...)`). */
|
|
11
|
+
export type Icons<N extends string> = (name: N) => Style;
|
|
12
|
+
/** Where the icons come from: the `@iconify-json/<collection>` package, or the `.svg` files of a directory. */
|
|
13
|
+
export type Options = {
|
|
14
|
+
provider: "iconify-json";
|
|
15
|
+
collection: string;
|
|
16
|
+
} | {
|
|
17
|
+
provider: "fs";
|
|
18
|
+
dir: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Icons from the `@iconify-json/<collection>` package installed in the project (read from `node_modules` when the
|
|
22
|
+
* module runs; the project is the working directory), or from the `.svg` files of a directory (relative to the working
|
|
23
|
+
* directory, named without the extension). Say which collection in the type argument to have the names checked as you
|
|
24
|
+
* type: `icons<typeof import("@iconify-json/mdi/icons.json")>({ provider: "iconify-json", collection: "mdi" })`. A name
|
|
25
|
+
* that does not exist is a build error that suggests the nearest one.
|
|
26
|
+
*
|
|
27
|
+
* One scope, "icons", whatever the collection: its kernels are the same, and `build` is the same code, which the engine
|
|
28
|
+
* takes as one definition however many times `icons()` runs.
|
|
29
|
+
*
|
|
30
|
+
* @param {Options} options where the icons come from
|
|
31
|
+
* @returns {Icons<IconName<C>>} a function of the icon name: a style for `cn()`
|
|
32
|
+
*/
|
|
33
|
+
export declare const icons: <C extends Collection = Collection>(options: Options) => Icons<IconName<C>>;
|
|
34
|
+
//#endregion
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { convertParsedSVG, getIconData, iconToHTML, iconToSVG, parseSVGContent, svgToURL } from "@iconify/utils";
|
|
5
|
+
import { arg, dependsOn, scope } from "cssints/plugin";
|
|
6
|
+
//#region src/index.ts
|
|
7
|
+
const box = "display:inline-block;width:1em;height:1em";
|
|
8
|
+
const mask = { css: `&{${box};background-color:currentColor;mask:var(--_svg) no-repeat center/100% 100%}` };
|
|
9
|
+
const image = { css: `&{${box};background:var(--_svg) no-repeat center/100% 100%}` };
|
|
10
|
+
const distance = (a, b) => {
|
|
11
|
+
let row = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
12
|
+
for (let i = 1; i <= a.length; i++) {
|
|
13
|
+
const next = [i];
|
|
14
|
+
for (let j = 1; j <= b.length; j++) next[j] = Math.min(row[j] + 1, next[j - 1] + 1, row[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
15
|
+
row = next;
|
|
16
|
+
}
|
|
17
|
+
return row[b.length];
|
|
18
|
+
};
|
|
19
|
+
const nearest = (name, names) => {
|
|
20
|
+
const near = Math.max(2, name.length >> 1);
|
|
21
|
+
let best;
|
|
22
|
+
let least = near + 1;
|
|
23
|
+
for (const n of names) {
|
|
24
|
+
const d = distance(name, n);
|
|
25
|
+
if (d < least) [best, least] = [n, d];
|
|
26
|
+
}
|
|
27
|
+
return best;
|
|
28
|
+
};
|
|
29
|
+
const g = globalThis;
|
|
30
|
+
const iconify = (collection) => {
|
|
31
|
+
const pkg = `@iconify-json/${collection}/icons.json`;
|
|
32
|
+
let json;
|
|
33
|
+
try {
|
|
34
|
+
const file = createRequire(join(process.cwd(), "_")).resolve(pkg);
|
|
35
|
+
const mtime = statSync(file).mtimeMs;
|
|
36
|
+
const cache = g.__cssintsIcons ??= /* @__PURE__ */ new Map();
|
|
37
|
+
const hit = cache.get(file);
|
|
38
|
+
json = hit?.mtime === mtime ? hit.json : JSON.parse(readFileSync(file, "utf8"));
|
|
39
|
+
cache.set(file, {
|
|
40
|
+
mtime,
|
|
41
|
+
json
|
|
42
|
+
});
|
|
43
|
+
} catch (e) {
|
|
44
|
+
throw new Error(`cssints: icons: cannot read ${pkg} from ${process.cwd()}: install @iconify-json/${collection}`, { cause: e });
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
label: `iconify-json ${collection}`,
|
|
48
|
+
get: (name) => getIconData(json, name) ?? void 0,
|
|
49
|
+
names: () => [...Object.keys(json.icons), ...Object.keys(json.aliases ?? {})]
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
const files = (dir) => {
|
|
53
|
+
const abs = resolve(dir);
|
|
54
|
+
const list = () => {
|
|
55
|
+
try {
|
|
56
|
+
return readdirSync(abs).filter((f) => f.endsWith(".svg"));
|
|
57
|
+
} catch (e) {
|
|
58
|
+
throw new Error(`cssints: icons: cannot read the directory ${abs}`, { cause: e });
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
list();
|
|
62
|
+
return {
|
|
63
|
+
label: dir,
|
|
64
|
+
names: () => list().map((f) => f.slice(0, -4)),
|
|
65
|
+
get: (name) => {
|
|
66
|
+
if (!list().includes(`${name}.svg`)) return void 0;
|
|
67
|
+
const file = join(abs, `${name}.svg`);
|
|
68
|
+
dependsOn(file);
|
|
69
|
+
const svg = parseSVGContent(readFileSync(file, "utf8"));
|
|
70
|
+
const icon = svg && convertParsedSVG(svg);
|
|
71
|
+
if (!icon) throw new Error(`cssints: icons: ${name}.svg is not an SVG with a viewBox`);
|
|
72
|
+
return icon;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
const urlOf = (icon) => {
|
|
77
|
+
const { body, viewBox } = iconToSVG(icon);
|
|
78
|
+
const [x, y, w, h] = viewBox;
|
|
79
|
+
const side = Math.max(w, h);
|
|
80
|
+
return svgToURL(iconToHTML(body.replace(/currentColor/g, "black"), {
|
|
81
|
+
viewBox: `${x - (side - w) / 2} ${y - (side - h) / 2} ${side} ${side}`,
|
|
82
|
+
width: `${side}`,
|
|
83
|
+
height: `${side}`
|
|
84
|
+
}));
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Icons from the `@iconify-json/<collection>` package installed in the project (read from `node_modules` when the
|
|
88
|
+
* module runs; the project is the working directory), or from the `.svg` files of a directory (relative to the working
|
|
89
|
+
* directory, named without the extension). Say which collection in the type argument to have the names checked as you
|
|
90
|
+
* type: `icons<typeof import("@iconify-json/mdi/icons.json")>({ provider: "iconify-json", collection: "mdi" })`. A name
|
|
91
|
+
* that does not exist is a build error that suggests the nearest one.
|
|
92
|
+
*
|
|
93
|
+
* One scope, "icons", whatever the collection: its kernels are the same, and `build` is the same code, which the engine
|
|
94
|
+
* takes as one definition however many times `icons()` runs.
|
|
95
|
+
*
|
|
96
|
+
* @param {Options} options where the icons come from
|
|
97
|
+
* @returns {Icons<IconName<C>>} a function of the icon name: a style for `cn()`
|
|
98
|
+
*/
|
|
99
|
+
const icons = (options) => {
|
|
100
|
+
const source = options.provider === "fs" ? files(options.dir) : iconify(options.collection);
|
|
101
|
+
return scope("icons", {
|
|
102
|
+
mask,
|
|
103
|
+
image,
|
|
104
|
+
icon: { args: arg() }
|
|
105
|
+
}).build({ icon: (name) => {
|
|
106
|
+
const icon = source.get(name);
|
|
107
|
+
if (!icon) {
|
|
108
|
+
const near = nearest(name, source.names());
|
|
109
|
+
throw new Error(`cssints: icons: no icon ${JSON.stringify(name)} in ${source.label}${near ? `, did you mean ${near}?` : ""}`);
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
decls: [["--_svg", urlOf(icon)]],
|
|
113
|
+
uses: [icon.body.includes("currentColor") ? "mask" : "image"]
|
|
114
|
+
};
|
|
115
|
+
} }).export().icon;
|
|
116
|
+
};
|
|
117
|
+
//#endregion
|
|
118
|
+
export { icons };
|
package/package.json
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cssints/icons",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Icons for cssints: iconify JSON collections and SVG folders as a mask style, no runtime",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/alexeiaccio/css-in-ts-compiler.git",
|
|
9
|
+
"directory": "packages/icons"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://cssints.dev",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"type": "module",
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.mts",
|
|
20
|
+
"default": "./dist/index.mjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@iconify/utils": "^3.0.0"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"cssints": "^0.2.0"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=22"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
}
|
|
35
|
+
}
|