@cssints/icons 0.0.0-stage → 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 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
- # Temporary Holding Version
1
+ # @cssints/icons
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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.
@@ -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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.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.1.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=22"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ }
35
+ }