@dubium/icons 0.0.2-alpha.1 → 0.0.2-alpha.3

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 CHANGED
@@ -0,0 +1,101 @@
1
+
2
+
3
+ ## Vite: только используемые SPA-иконки в dist
4
+
5
+ Для SPA рекомендуется подключить compile-time plugin:
6
+
7
+ ```ts
8
+ // vite.config.ts
9
+ import react from "@vitejs/plugin-react"
10
+ import { defineConfig } from "vite"
11
+ import { dubiumIcons } from "@dubium/icons/vite"
12
+
13
+ export default defineConfig({
14
+ plugins: [
15
+ dubiumIcons(),
16
+ react(),
17
+ ],
18
+ })
19
+ ```
20
+
21
+ После этого API остаётся прежним:
22
+
23
+ ```tsx
24
+ import { Icon } from "@dubium/icons/spa"
25
+
26
+ export const Header = () => (
27
+ <>
28
+ <Icon name="User" />
29
+ <Icon name="Search" />
30
+ </>
31
+ )
32
+ ```
33
+
34
+ `@dubium/icons/vite` заранее сканирует `src`, создаёт virtual registry и генерирует
35
+ только такие lazy imports:
36
+
37
+ ```ts
38
+ {
39
+ User: () => import("@dubium/icons/icons/User"),
40
+ Search: () => import("@dubium/icons/icons/Search"),
41
+ }
42
+ ```
43
+
44
+ Поэтому остальные иконки из npm-пакета не попадают в `dist` consuming-приложения.
45
+
46
+ ### Динамический name
47
+
48
+ Значение вида `<Icon name={item.icon} />` невозможно определить на этапе сборки.
49
+ Перечислите допустимые runtime-иконки вручную:
50
+
51
+ ```ts
52
+ dubiumIcons({
53
+ include: ["User", "Search", "Settings"],
54
+ })
55
+ ```
56
+
57
+ По умолчанию плагин предупреждает о динамических `name`. Чтобы делать это ошибкой сборки:
58
+
59
+ ```ts
60
+ dubiumIcons({
61
+ include: ["User", "Search"],
62
+ strictDynamicNames: true,
63
+ })
64
+ ```
65
+
66
+ По умолчанию сканируется каталог `src`. Для монорепозитория или другой структуры:
67
+
68
+ ```ts
69
+ dubiumIcons({
70
+ scan: ["src", "app", "../shared/src"],
71
+ })
72
+ ```
73
+
74
+
75
+
76
+ ## Lightweight 5-icon test build
77
+
78
+ This archive intentionally keeps only the first five source SVG files so local testing stays fast:
79
+
80
+ - `123.svg` → `Icon123Icon`
81
+ - `24-hours.svg` → `Icon24HoursIcon`
82
+ - `2fa.svg` → `Icon2faIcon`
83
+ - `360-view.svg` → `Icon360ViewIcon`
84
+ - `360.svg` → `Icon360Icon`
85
+
86
+ Bundler-friendly container usage:
87
+
88
+ ```tsx
89
+ import { Icon } from "@dubium/icons/container"
90
+ import { Icon360Icon } from "@dubium/icons/icons/Icon360Icon"
91
+
92
+ export function Example() {
93
+ return <Icon icon={Icon360Icon} size={24} />
94
+ }
95
+ ```
96
+
97
+ The older short subpath remains compatible too:
98
+
99
+ ```tsx
100
+ import Icon360Icon from "@dubium/icons/icons/Icon360"
101
+ ```
@@ -0,0 +1,3 @@
1
+ import type { IIconComponentProps } from "./types.js";
2
+ export declare const Icon123Icon: ({ color, ...props }: IIconComponentProps) => import("react").JSX.Element;
3
+ export default Icon123Icon;
@@ -0,0 +1,18 @@
1
+ import { jsx as e } from "react/jsx-runtime";
2
+ //#region src/icons/collection/Icon123Icon.tsx
3
+ var t = ({ color: t = "var(--icon-color, currentColor)", ...n }) => /* @__PURE__ */ e("svg", {
4
+ ...n,
5
+ fill: "none",
6
+ viewBox: "0 0 24 24",
7
+ width: "100%",
8
+ height: "100%",
9
+ children: /* @__PURE__ */ e("path", {
10
+ stroke: t,
11
+ strokeLinecap: "round",
12
+ strokeLinejoin: "round",
13
+ strokeWidth: "2",
14
+ d: "m3 10 2-2v8m4-8h3a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h3m4-8h2.5A1.5 1.5 0 0 1 21 9.5v1a1.5 1.5 0 0 1-1.5 1.5m0 0H18m1.5 0a1.5 1.5 0 0 1 1.5 1.5v1a1.5 1.5 0 0 1-1.5 1.5H17"
15
+ })
16
+ });
17
+ //#endregion
18
+ export { t as Icon123Icon, t as default };
@@ -0,0 +1,3 @@
1
+ import type { IIconComponentProps } from "./types.js";
2
+ export declare const Icon24HoursIcon: ({ color, ...props }: IIconComponentProps) => import("react").JSX.Element;
3
+ export default Icon24HoursIcon;
@@ -0,0 +1,18 @@
1
+ import { jsx as e } from "react/jsx-runtime";
2
+ //#region src/icons/collection/Icon24HoursIcon.tsx
3
+ var t = ({ color: t = "var(--icon-color, currentColor)", ...n }) => /* @__PURE__ */ e("svg", {
4
+ ...n,
5
+ fill: "none",
6
+ viewBox: "0 0 24 24",
7
+ width: "100%",
8
+ height: "100%",
9
+ children: /* @__PURE__ */ e("path", {
10
+ stroke: t,
11
+ strokeLinecap: "round",
12
+ strokeLinejoin: "round",
13
+ strokeWidth: "2",
14
+ d: "M20 11A8.1 8.1 0 0 0 4.5 9M4 5v4h4m-4 4a8 8 0 0 0 3 5.24M11 15h2a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1h-1a1 1 0 0 0-1 1v1a1 1 0 0 0 1 1h2m3-6v2a1 1 0 0 0 1 1h1m1-3v6"
15
+ })
16
+ });
17
+ //#endregion
18
+ export { t as Icon24HoursIcon, t as default };
@@ -0,0 +1,3 @@
1
+ import type { IIconComponentProps } from "./types.js";
2
+ export declare const Icon2faIcon: ({ color, ...props }: IIconComponentProps) => import("react").JSX.Element;
3
+ export default Icon2faIcon;
@@ -0,0 +1,18 @@
1
+ import { jsx as e } from "react/jsx-runtime";
2
+ //#region src/icons/collection/Icon2faIcon.tsx
3
+ var t = ({ color: t = "var(--icon-color, currentColor)", ...n }) => /* @__PURE__ */ e("svg", {
4
+ ...n,
5
+ fill: "none",
6
+ viewBox: "0 0 24 24",
7
+ width: "100%",
8
+ height: "100%",
9
+ children: /* @__PURE__ */ e("path", {
10
+ stroke: t,
11
+ strokeLinecap: "round",
12
+ strokeLinejoin: "round",
13
+ strokeWidth: "2",
14
+ d: "M7 16H3l3.47-4.66A2 2 0 1 0 3 9.8m7 6.2V8h4m-4 4h3m4 4v-6a2 2 0 1 1 4 0v6m-4-3h4"
15
+ })
16
+ });
17
+ //#endregion
18
+ export { t as Icon2faIcon, t as default };
@@ -0,0 +1,3 @@
1
+ import type { IIconComponentProps } from "./types.js";
2
+ export declare const Icon360Icon: ({ color, ...props }: IIconComponentProps) => import("react").JSX.Element;
3
+ export default Icon360Icon;
@@ -0,0 +1,18 @@
1
+ import { jsx as e } from "react/jsx-runtime";
2
+ //#region src/icons/collection/Icon360Icon.tsx
3
+ var t = ({ color: t = "var(--icon-color, currentColor)", ...n }) => /* @__PURE__ */ e("svg", {
4
+ ...n,
5
+ fill: "none",
6
+ viewBox: "0 0 24 24",
7
+ width: "100%",
8
+ height: "100%",
9
+ children: /* @__PURE__ */ e("path", {
10
+ stroke: t,
11
+ strokeLinecap: "round",
12
+ strokeLinejoin: "round",
13
+ strokeWidth: "2",
14
+ d: "M17 15.33c2.41-.72 4-1.94 4-3.33 0-2.21-4.03-4-9-4s-9 1.79-9 4 4.03 4 9 4m-3 3 3-3-3-3"
15
+ })
16
+ });
17
+ //#endregion
18
+ export { t as Icon360Icon, t as default };
@@ -0,0 +1,3 @@
1
+ import type { IIconComponentProps } from "./types.js";
2
+ export declare const Icon360ViewIcon: ({ color, ...props }: IIconComponentProps) => import("react").JSX.Element;
3
+ export default Icon360ViewIcon;
@@ -0,0 +1,18 @@
1
+ import { jsx as e } from "react/jsx-runtime";
2
+ //#region src/icons/collection/Icon360ViewIcon.tsx
3
+ var t = ({ color: t = "var(--icon-color, currentColor)", ...n }) => /* @__PURE__ */ e("svg", {
4
+ ...n,
5
+ fill: "none",
6
+ viewBox: "0 0 24 24",
7
+ width: "100%",
8
+ height: "100%",
9
+ children: /* @__PURE__ */ e("path", {
10
+ stroke: t,
11
+ strokeLinecap: "round",
12
+ strokeLinejoin: "round",
13
+ strokeWidth: "2",
14
+ d: "M14 6a1 1 0 0 0-1-1h-2a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1h-3M3 5h2.5A1.5 1.5 0 0 1 7 6.5v1A1.5 1.5 0 0 1 5.5 9m0 0H4m1.5 0A1.5 1.5 0 0 1 7 10.5v1A1.5 1.5 0 0 1 5.5 13H3m0 3c0 1.66 4.03 3 9 3s9-1.34 9-3m-4-9v4a2 2 0 0 0 4 0V7a2 2 0 1 0-4 0"
15
+ })
16
+ });
17
+ //#endregion
18
+ export { t as Icon360ViewIcon, t as default };
@@ -3,4 +3,8 @@
3
3
  * Do not edit manually.
4
4
  */
5
5
  export type { IIconComponentProps } from "./types.js";
6
- export { default as UserIcon } from "./UserIcon.js";
6
+ export { default as Icon123Icon } from "./Icon123Icon.js";
7
+ export { default as Icon24HoursIcon } from "./Icon24HoursIcon.js";
8
+ export { default as Icon2faIcon } from "./Icon2faIcon.js";
9
+ export { default as Icon360Icon } from "./Icon360Icon.js";
10
+ export { default as Icon360ViewIcon } from "./Icon360ViewIcon.js";
@@ -0,0 +1,6 @@
1
+ import { Icon123Icon as e } from "./Icon123Icon.js";
2
+ import { Icon24HoursIcon as t } from "./Icon24HoursIcon.js";
3
+ import { Icon2faIcon as n } from "./Icon2faIcon.js";
4
+ import { Icon360Icon as r } from "./Icon360Icon.js";
5
+ import { Icon360ViewIcon as i } from "./Icon360ViewIcon.js";
6
+ export { e as Icon123Icon, t as Icon24HoursIcon, n as Icon2faIcon, r as Icon360Icon, i as Icon360ViewIcon };
@@ -1,11 +1,6 @@
1
1
  import type { SVGProps } from "react";
2
- /**
3
- * Базовые пропсы для SVG-иконки.
4
- *
5
- * Расширяет стандартные SVG-атрибуты и добавляет поддержку цвета.
6
- *
7
- * @property color - Цвет заливки иконки (обычно прокидывается в `fill`)
8
- */
2
+ /** Props shared by generated SVG icon components. */
9
3
  export interface IIconComponentProps extends SVGProps<SVGSVGElement> {
10
4
  color?: string;
5
+ secondaryColor?: string;
11
6
  }
@@ -1,57 +1,25 @@
1
1
  import { type MemoExoticComponent } from "react";
2
2
  import type { TEmptyIconRegistry, TIconName, TIconRegistry } from "./Icon.types.js";
3
- /**
4
- * Свойства компонента иконки
5
- */
6
3
  export interface IconProps<TCustomIcons extends TIconRegistry = TEmptyIconRegistry> {
7
- /**
8
- * Атрибут aria-label
9
- */
10
4
  ariaLabel?: string;
11
- /**
12
- * Цвет иконки
13
- */
14
5
  color?: string;
15
- /**
16
- * Угол поворота
17
- */
6
+ secondaryColor?: string;
18
7
  deg?: number;
19
- /**
20
- * Высота (если нужен прямоугольник)
21
- */
22
8
  height?: number;
23
- /**
24
- * Имя иконки
25
- */
26
9
  name: TIconName<TCustomIcons>;
27
- /**
28
- * Обработчик клика
29
- */
30
10
  onClick?: VoidFunction;
31
- /**
32
- * Приоритетный размер (квадратный)
33
- */
34
11
  size?: number;
35
- /**
36
- * Ширина (если нужен прямоугольник)
37
- */
38
12
  width?: number;
39
13
  }
40
14
  /**
41
- * Компонент иконки, который динамически загружает SVG иконки по имени.
42
- *
43
- * @param props - Свойства компонента иконки
44
- * @param props.name - Имя иконки для отображения
45
- * @param props.size - Размер иконки в пикселях (по умолчанию 24)
46
- * @param props.color - Цвет иконки (по умолчанию "currentColor")
47
- * @param props.deg - Угол поворота иконки в градусах (по умолчанию 0)
48
- * @param props.onClick - Обработчик клика по иконке
15
+ * SPA-иконка с lazy loading.
49
16
  *
50
- * @example
51
- * <Icon name="Arrow" size={32} color="blue" deg={90} />
17
+ * Встроенные loader-ы поступают из virtual:@dubium/icons-registry.
18
+ * Этот virtual module создаёт @dubium/icons/vite на этапе сборки приложения
19
+ * и помещает в него только статически используемые <Icon name="..." />.
52
20
  *
53
- * @returns React-элемент иконки
21
+ * Пользовательский IconProvider имеет приоритет над встроенным registry.
54
22
  */
55
- declare const IconComponentBase: <TCustomIcons extends TIconRegistry = TEmptyIconRegistry>({ name, size, width: propWidth, height: propHeight, color, deg, onClick: handleOnClick, ariaLabel, }: IconProps<TCustomIcons>) => import("react").JSX.Element;
23
+ declare const IconComponentBase: <TCustomIcons extends TIconRegistry = TEmptyIconRegistry>({ name, size, width: propWidth, height: propHeight, color, secondaryColor, deg, onClick: handleOnClick, ariaLabel, }: IconProps<TCustomIcons>) => import("react").JSX.Element;
56
24
  declare const MemoizedIcon: MemoExoticComponent<typeof IconComponentBase> & typeof IconComponentBase;
57
25
  export { MemoizedIcon as Icon };
@@ -1,62 +1,56 @@
1
1
  import { useIconContext as e } from "../provider/IconProvider.context.js";
2
- import { defaultIcons as t } from "./Icon.registry.js";
2
+ import { jsx as t } from "react/jsx-runtime";
3
3
  import { memo as n, useEffect as r, useMemo as i, useState as a } from "react";
4
- import { jsx as o } from "react/jsx-runtime";
4
+ import { iconRegistry as o } from "virtual:@dubium/icons-registry";
5
5
  //#region src/icons/spa/icon/Icon.tsx
6
- var s = /* @__PURE__ */ new Map(), c = n(({ name: n, size: c = 24, width: l, height: u, color: d = "currentColor", deg: f = 0, onClick: p, ariaLabel: m }) => {
7
- let { icons: h } = e(), g = i(() => ({
8
- ...t,
9
- ...h
10
- }), [h]), _ = g[n], [v, y] = a(() => _ ? s.get(_) ?? null : null), [b, x] = a(!1), S = l ?? c, C = u ?? c, w = i(() => ({
11
- display: "inline-flex",
6
+ var s = /* @__PURE__ */ new Map(), c = n(({ name: n, size: c = 24, width: l, height: u, color: d = "var(--icon-color, currentColor)", secondaryColor: f = "var(--icon-secondary-color, currentColor)", deg: p = 0, onClick: m, ariaLabel: h }) => {
7
+ let { icons: g } = e(), _ = g[n] ?? o[String(n)], [v, y] = a(() => _ ? s.get(_) ?? null : null), [b, x] = a(!1), S = l ?? c, C = u ?? c, w = i(() => ({
12
8
  alignItems: "center",
13
- justifyContent: "center",
14
- width: S,
9
+ display: "inline-flex",
15
10
  height: C,
16
- transform: `rotate(${f}deg)`
11
+ justifyContent: "center",
12
+ transform: `rotate(${p}deg)`,
13
+ width: S
17
14
  }), [
18
- S,
19
15
  C,
20
- f
16
+ S,
17
+ p
21
18
  ]);
22
19
  return r(() => {
23
- let e = !1;
24
- return (async () => {
25
- let t = String(n), r = g[n];
26
- if (!r) {
27
- console.warn(`Иконка "${t}" не найдена`), y(() => null);
28
- return;
29
- }
30
- let i = s.get(r);
31
- if (i) {
32
- y(() => i);
33
- return;
34
- }
35
- x(!0), y(() => null), r().then((t) => {
36
- if (e) return;
37
- let n = t.default;
38
- s.set(r, n), y(() => n);
39
- }).catch((n) => {
40
- e || (console.error(`Ошибка загрузки иконки "${t}":`, n), y(() => null));
41
- }).finally(() => {
42
- e || x(!1);
43
- });
44
- })(), () => {
20
+ let e = !1, t = String(n), r = g[n] ?? o[t];
21
+ if (!r) return x(!1), y(null), console.warn(`Иконка "${t}" не найдена в compile-time registry. Для динамического name добавьте её в dubiumIcons({ include: ["..."] }).`), () => {
45
22
  e = !0;
46
23
  };
47
- }, [n, g]), b || !v ? /* @__PURE__ */ o("div", {
24
+ let i = s.get(r);
25
+ return i ? (x(!1), y(() => i), () => {
26
+ e = !0;
27
+ }) : (x(!0), y(null), r().then((t) => {
28
+ if (e) return;
29
+ let n = t.default;
30
+ s.set(r, n), y(() => n);
31
+ }).catch((n) => {
32
+ e || (console.error(`Ошибка загрузки иконки "${t}":`, n), y(null));
33
+ }).finally(() => {
34
+ e || x(!1);
35
+ }), () => {
36
+ e = !0;
37
+ });
38
+ }, [g, n]), b || !v ? /* @__PURE__ */ t("div", {
48
39
  "aria-hidden": "true",
49
40
  role: "img",
50
41
  style: {
51
42
  ...w,
52
43
  visibility: "hidden"
53
44
  }
54
- }) : /* @__PURE__ */ o("div", {
55
- "aria-label": m || n,
56
- onClick: p,
45
+ }) : /* @__PURE__ */ t("div", {
46
+ "aria-label": h || String(n),
47
+ onClick: m,
57
48
  role: "img",
58
49
  style: w,
59
- children: v ? /* @__PURE__ */ o(v, { color: d }) : null
50
+ children: /* @__PURE__ */ t(v, {
51
+ color: d,
52
+ secondaryColor: f
53
+ })
60
54
  });
61
55
  });
62
56
  c.displayName = "Icon";
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * AUTO-GENERATED by scripts/sync-icons.mjs.
3
- * Source of truth: src/icons/collection/*Icon.tsx.
3
+ * Source of truth: icons/source/*.svg.
4
4
  */
5
5
  export declare const defaultIcons: {
6
- readonly User: () => Promise<typeof import("../../collection/UserIcon.js")>;
6
+ readonly Icon123: () => Promise<typeof import("../../collection/Icon123Icon.js")>;
7
+ readonly Icon24Hours: () => Promise<typeof import("../../collection/Icon24HoursIcon.js")>;
8
+ readonly Icon2fa: () => Promise<typeof import("../../collection/Icon2faIcon.js")>;
9
+ readonly Icon360: () => Promise<typeof import("../../collection/Icon360Icon.js")>;
10
+ readonly Icon360View: () => Promise<typeof import("../../collection/Icon360ViewIcon.js")>;
7
11
  };
8
12
  export type TDefaultIconName = keyof typeof defaultIcons;
@@ -1,10 +1,10 @@
1
1
  import { IconContext as e } from "./IconProvider.context.js";
2
- import { useMemo as t } from "react";
3
- import { jsx as n } from "react/jsx-runtime";
2
+ import { jsx as t } from "react/jsx-runtime";
3
+ import { useMemo as n } from "react";
4
4
  //#region src/icons/spa/provider/IconProvider.tsx
5
5
  var r = ({ children: r, icons: i }) => {
6
- let a = t(() => ({ icons: i ?? {} }), [i]);
7
- return /* @__PURE__ */ n(e.Provider, {
6
+ let a = n(() => ({ icons: i ?? {} }), [i]);
7
+ return /* @__PURE__ */ t(e.Provider, {
8
8
  value: a,
9
9
  children: r
10
10
  });
@@ -7,20 +7,21 @@ var r = (e, t) => Object.prototype.hasOwnProperty.call(e, t), i = async (t) => {
7
7
  let n = e[t];
8
8
  return (await n()).default;
9
9
  }, a = async (e, a) => {
10
- let { name: o, size: s = 24, width: c, height: l, color: u = "currentColor", deg: d = 0, ariaLabel: f, style: p } = e, m = String(o), h = (a && r(a, m) ? a[m] : void 0) ?? await i(m);
11
- return h ? /* @__PURE__ */ n(t, {
12
- ariaLabel: f,
13
- deg: d,
10
+ let { name: o, size: s = 24, width: c, height: l, color: u = "var(--icon-color, currentColor)", secondaryColor: d = "var(--icon-secondary-color, currentColor)", deg: f = 0, ariaLabel: p, style: m } = e, h = String(o), g = (a && r(a, h) ? a[h] : void 0) ?? await i(h);
11
+ return g ? /* @__PURE__ */ n(t, {
12
+ ariaLabel: p,
13
+ deg: f,
14
14
  height: l,
15
15
  size: s,
16
- style: p,
16
+ style: m,
17
17
  width: c,
18
- children: /* @__PURE__ */ n(h, {
18
+ children: /* @__PURE__ */ n(g, {
19
19
  "aria-hidden": "true",
20
20
  color: u,
21
- focusable: "false"
21
+ focusable: "false",
22
+ secondaryColor: d
22
23
  })
23
- }) : (console.warn(`Иконка "${m}" не найдена`), null);
24
+ }) : (console.warn(`Иконка "${h}" не найдена`), null);
24
25
  }, o = (e) => a(e), s = a;
25
26
  //#endregion
26
27
  export { o as Icon, s as renderIconWithRegistry };
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * AUTO-GENERATED by scripts/sync-icons.mjs.
3
- * Source of truth: src/icons/collection/*Icon.tsx.
3
+ * Source of truth: icons/source/*.svg.
4
4
  */
5
5
  export declare const defaultIcons: {
6
- readonly User: () => Promise<typeof import("../../collection/UserIcon.js")>;
6
+ readonly Icon123: () => Promise<typeof import("../../collection/Icon123Icon.js")>;
7
+ readonly Icon24Hours: () => Promise<typeof import("../../collection/Icon24HoursIcon.js")>;
8
+ readonly Icon2fa: () => Promise<typeof import("../../collection/Icon2faIcon.js")>;
9
+ readonly Icon360: () => Promise<typeof import("../../collection/Icon360Icon.js")>;
10
+ readonly Icon360View: () => Promise<typeof import("../../collection/Icon360ViewIcon.js")>;
7
11
  };
8
12
  export type TDefaultIconName = keyof typeof defaultIcons;
@@ -1,4 +1,10 @@
1
1
  //#region src/icons/ssr/icon/Icon.registry.ts
2
- var e = { User: () => import("../../collection/UserIcon.js") };
2
+ var e = {
3
+ Icon123: () => import("../../collection/Icon123Icon.js"),
4
+ Icon24Hours: () => import("../../collection/Icon24HoursIcon.js"),
5
+ Icon2fa: () => import("../../collection/Icon2faIcon.js"),
6
+ Icon360: () => import("../../collection/Icon360Icon.js"),
7
+ Icon360View: () => import("../../collection/Icon360ViewIcon.js")
8
+ };
3
9
  //#endregion
4
10
  export { e as defaultIcons };
@@ -65,6 +65,10 @@ export interface IconProps<TCustomIcons extends TIconRegistry = TEmptyIconRegist
65
65
  * Цвет иконки
66
66
  */
67
67
  color?: string;
68
+ /**
69
+ * Вторичный цвет двухцветной иконки
70
+ */
71
+ secondaryColor?: string;
68
72
  /**
69
73
  * Угол поворота в градусах
70
74
  */
@@ -0,0 +1,50 @@
1
+ import type { Plugin } from "vite";
2
+ export interface DubiumIconsPluginOptions {
3
+ /**
4
+ * Имена компонентов, которые плагин должен анализировать.
5
+ *
6
+ * @default ["Icon"]
7
+ */
8
+ componentNames?: readonly string[];
9
+ /**
10
+ * Иконки, которые необходимо включить вручную.
11
+ *
12
+ * Используется для runtime-имён:
13
+ * `<Icon name={valueFromApi} />`.
14
+ */
15
+ include?: readonly string[];
16
+ /**
17
+ * Имя npm-пакета с иконками.
18
+ *
19
+ * @default "@dubium/icons"
20
+ */
21
+ packageName?: string;
22
+ /**
23
+ * Каталоги приложения, которые нужно сканировать.
24
+ * Пути могут быть абсолютными или относительными к Vite root.
25
+ *
26
+ * @default ["src"]
27
+ */
28
+ scan?: readonly string[];
29
+ /**
30
+ * Если true, сборка завершится ошибкой при обнаружении
31
+ * `<Icon name={dynamicValue} />`.
32
+ *
33
+ * При false будет выведено предупреждение.
34
+ *
35
+ * @default false
36
+ */
37
+ strictDynamicNames?: boolean;
38
+ }
39
+ /**
40
+ * Vite-плагин для compile-time извлечения используемых иконок.
41
+ *
42
+ * Он сканирует JSX/TSX, находит статические:
43
+ * `<Icon name="User" />`,
44
+ * а затем создаёт virtual registry только для найденных имён.
45
+ *
46
+ * Благодаря этому Rollup/Vite видит dynamic import() только реально
47
+ * используемых иконок, и остальные иконки не попадают в dist приложения.
48
+ */
49
+ export declare const dubiumIcons: (options?: DubiumIconsPluginOptions) => Plugin;
50
+ export default dubiumIcons;
@@ -0,0 +1,167 @@
1
+ import { readFile as e, readdir as t } from "node:fs/promises";
2
+ import { extname as n, isAbsolute as r, join as i, relative as a, resolve as o } from "node:path";
3
+ //#region src/icons/vite/index.ts
4
+ var s = "virtual:@dubium/icons-registry", c = `\0${s}`, l = /* @__PURE__ */ new Set([
5
+ ".js",
6
+ ".jsx",
7
+ ".mjs",
8
+ ".cjs",
9
+ ".ts",
10
+ ".tsx"
11
+ ]), u = /* @__PURE__ */ new Set([
12
+ ".git",
13
+ ".next",
14
+ ".nuxt",
15
+ ".output",
16
+ ".turbo",
17
+ "build",
18
+ "coverage",
19
+ "dist",
20
+ "node_modules"
21
+ ]), d = (e) => e.replaceAll("\\", "/"), f = (e, t) => {
22
+ if (e.size !== t.size) return !1;
23
+ for (let n of e) if (!t.has(n)) return !1;
24
+ return !0;
25
+ }, p = (e) => {
26
+ let t = e.map((e) => e.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"));
27
+ return RegExp(`<\\s*(?:${t.join("|")})\\b`, "gu");
28
+ }, m = (e, t) => {
29
+ let n = null, r = !1, i = 0;
30
+ for (let a = t; a < e.length; a += 1) {
31
+ let t = e[a];
32
+ if (n) {
33
+ if (r) {
34
+ r = !1;
35
+ continue;
36
+ }
37
+ if (t === "\\") {
38
+ r = !0;
39
+ continue;
40
+ }
41
+ t === n && (n = null);
42
+ continue;
43
+ }
44
+ if (t === "'" || t === "\"" || t === "`") {
45
+ n = t;
46
+ continue;
47
+ }
48
+ if (t === "{") {
49
+ i += 1;
50
+ continue;
51
+ }
52
+ if (t === "}") {
53
+ i = Math.max(0, i - 1);
54
+ continue;
55
+ }
56
+ if (t === ">" && i === 0) return a;
57
+ }
58
+ return -1;
59
+ }, h = (e) => {
60
+ let t = /\bname\s*=\s*(["'])(.*?)\1/su.exec(e);
61
+ if (t) return t[2].trim();
62
+ let n = /\bname\s*=\s*\{\s*(?:(["'])(.*?)\1|`([^`${}]*)`)\s*\}/su.exec(e);
63
+ return n ? (n[2] ?? n[3] ?? "").trim() : null;
64
+ }, g = (e) => /\bname\s*=/u.test(e), _ = (e, t) => {
65
+ let n = /* @__PURE__ */ new Set(), r = p(t), i = 0;
66
+ for (let t of e.matchAll(r)) {
67
+ let r = t.index ?? 0, a = m(e, r);
68
+ if (a === -1) continue;
69
+ let o = e.slice(r, a + 1), s = h(o);
70
+ if (s) {
71
+ n.add(s);
72
+ continue;
73
+ }
74
+ g(o) && (i += 1);
75
+ }
76
+ return {
77
+ dynamicNames: i,
78
+ icons: n
79
+ };
80
+ }, v = async (e) => {
81
+ let r;
82
+ try {
83
+ r = await t(e, { withFileTypes: !0 });
84
+ } catch {
85
+ return [];
86
+ }
87
+ let a = [];
88
+ for (let t of r) {
89
+ let r = i(e, t.name);
90
+ if (t.isDirectory()) {
91
+ u.has(t.name) || a.push(...await v(r));
92
+ continue;
93
+ }
94
+ t.isFile() && l.has(n(t.name).toLowerCase()) && a.push(r);
95
+ }
96
+ return a;
97
+ }, y = (e, t) => `/**
98
+ * AUTO-GENERATED in memory by @dubium/icons/vite.
99
+ * Contains only icons used by the current application.
100
+ */
101
+ export const iconRegistry = {
102
+ ${e.map((e) => `\t${JSON.stringify(e)}: () => import(${JSON.stringify(`${t}/icons/${e}`)}),`).join("\n")}
103
+ }
104
+ `, b = (t = {}) => {
105
+ let i = t.componentNames?.length ? [...t.componentNames] : ["Icon"], u = t.packageName ?? "@dubium/icons", p = t.scan?.length ? [...t.scan] : ["src"], m = new Set(t.include ?? []), h = /* @__PURE__ */ new Map(), g = /* @__PURE__ */ new Map(), b = process.cwd(), x, S = () => {
106
+ let e = new Set(m);
107
+ for (let t of h.values()) for (let n of t) e.add(n);
108
+ return [...e].sort((e, t) => e.localeCompare(t));
109
+ }, C = async (t) => {
110
+ let r = d(t);
111
+ if (!l.has(n(t).toLowerCase())) return h.delete(r), g.delete(r), !1;
112
+ let a;
113
+ try {
114
+ a = await e(t, "utf8");
115
+ } catch {
116
+ let e = h.delete(r), t = g.delete(r);
117
+ return e || t;
118
+ }
119
+ let o = h.get(r) ?? /* @__PURE__ */ new Set(), s = g.get(r) ?? 0, c = _(a, i);
120
+ return h.set(r, c.icons), c.dynamicNames > 0 ? g.set(r, c.dynamicNames) : g.delete(r), !f(o, c.icons) || s !== c.dynamicNames;
121
+ }, w = async (e) => {
122
+ h.clear(), g.clear();
123
+ for (let t of p) {
124
+ let n = await v(r(t) ? t : o(b, t));
125
+ for (let t of n) e?.addWatchFile(t), await C(t);
126
+ }
127
+ }, T = () => g.size === 0 ? null : `[@dubium/icons] Найдены динамические name у <Icon>:\n${[...g.entries()].map(([e, t]) => ` - ${d(a(b, e))}: ${t}`).join("\n")}\nVite не может определить такие иконки на этапе сборки. Добавьте допустимые имена в dubiumIcons({ include: [...] }).`, E = () => {
128
+ if (!x) return;
129
+ let e = x.moduleGraph.getModuleById(c);
130
+ e && x.moduleGraph.invalidateModule(e), x.ws.send({ type: "full-reload" });
131
+ };
132
+ return {
133
+ name: "dubium-icons",
134
+ enforce: "pre",
135
+ configResolved(e) {
136
+ b = e.root;
137
+ },
138
+ async buildStart() {
139
+ await w(this);
140
+ let e = T();
141
+ e && (t.strictDynamicNames ? this.error(e) : this.warn(e));
142
+ },
143
+ configureServer(e) {
144
+ x = e;
145
+ let t = async (e) => {
146
+ await C(e) && E();
147
+ }, n = async (e) => {
148
+ let t = d(e);
149
+ (h.delete(t) || g.delete(t)) && E();
150
+ };
151
+ return e.watcher.on("add", t), e.watcher.on("unlink", n), () => {
152
+ e.watcher.off("add", t), e.watcher.off("unlink", n);
153
+ };
154
+ },
155
+ resolveId(e) {
156
+ return e === s ? c : null;
157
+ },
158
+ load(e) {
159
+ return e === c ? y(S(), u) : null;
160
+ },
161
+ async handleHotUpdate(e) {
162
+ if (await C(e.file)) return E(), [];
163
+ }
164
+ };
165
+ };
166
+ //#endregion
167
+ export { b as default, b as dubiumIcons };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dubium/icons",
3
- "version": "0.0.2-alpha.1",
3
+ "version": "0.0.2-alpha.3",
4
4
  "private": false,
5
5
  "files": [
6
6
  "dist"
@@ -20,20 +20,31 @@
20
20
  "types": "./dist/collection/index.d.ts",
21
21
  "import": "./dist/collection/index.js"
22
22
  },
23
+ "./icons/*Icon": {
24
+ "types": "./dist/collection/*Icon.d.ts",
25
+ "import": "./dist/collection/*Icon.js"
26
+ },
23
27
  "./icons/*": {
24
28
  "types": "./dist/collection/*Icon.d.ts",
25
29
  "import": "./dist/collection/*Icon.js"
30
+ },
31
+ "./vite": {
32
+ "types": "./dist/vite/index.d.ts",
33
+ "import": "./dist/vite/index.js"
26
34
  }
27
35
  },
28
36
  "scripts": {
29
37
  "dev": "vite",
30
38
  "build": "vite build && tsc -p tsconfig.build.json",
39
+ "build:icons": "vite build --config vite.icons.config.ts",
40
+ "sync:icons": "node scripts/sync-icons.mjs",
31
41
  "lint": "oxlint",
32
42
  "lint:fix": "oxlint --fix",
33
43
  "format": "oxfmt",
34
44
  "format:check": "oxfmt --check",
35
45
  "check": "npm run lint && npm run format:check && npm run typecheck",
36
- "preview": "vite preview"
46
+ "preview": "vite preview",
47
+ "typecheck": "tsc --noEmit"
37
48
  },
38
49
  "devDependencies": {
39
50
  "@types/node": "^24.13.3",
@@ -44,11 +55,18 @@
44
55
  "oxlint": "^1.78.0",
45
56
  "oxlint-tsgolint": "^7.0.2001",
46
57
  "rollup-plugin-visualizer": "^7.0.1",
58
+ "svgo": "^4.0.2",
47
59
  "typescript": "~7.0.2",
48
60
  "vite": "^8.2.1"
49
61
  },
50
62
  "peerDependencies": {
51
63
  "react": "^18.0.0 || ^19.0.0",
52
- "react-dom": "^18.0.0 || ^19.0.0"
64
+ "react-dom": "^18.0.0 || ^19.0.0",
65
+ "vite": "^8.0.0"
66
+ },
67
+ "peerDependenciesMeta": {
68
+ "vite": {
69
+ "optional": true
70
+ }
53
71
  }
54
72
  }
@@ -1,12 +0,0 @@
1
- import type { IIconComponentProps } from "./types.js";
2
- /**
3
- * Иконка.
4
- *
5
- * @remarks
6
- * свойство `color` из {@link IIconComponentProps}.
7
- *
8
- * @param props - Свойства компонента иконки.
9
- * @returns SVG-элемент иконки.
10
- */
11
- declare const UserIcon: ({ color, ...props }: IIconComponentProps) => import("react").JSX.Element;
12
- export default UserIcon;
@@ -1,19 +0,0 @@
1
- import { jsx as e } from "react/jsx-runtime";
2
- //#region src/icons/collection/UserIcon.tsx
3
- var t = ({ color: t = "currentColor", ...n }) => /* @__PURE__ */ e("svg", {
4
- fill: "none",
5
- height: "100%",
6
- viewBox: "0 0 24 24",
7
- width: "100%",
8
- xmlns: "http://www.w3.org/2000/svg",
9
- ...n,
10
- children: /* @__PURE__ */ e("path", {
11
- d: "M6 21V19C6 17.9391 6.42143 16.9217 7.17157 16.1716C7.92172 15.4214 8.93913 15 10 15H14C15.0609 15 16.0783 15.4214 16.8284 16.1716C17.5786 16.9217 18 17.9391 18 19V21M8 7C8 8.06087 8.42143 9.07828 9.17157 9.82843C9.92172 10.5786 10.9391 11 12 11C13.0609 11 14.0783 10.5786 14.8284 9.82843C15.5786 9.07828 16 8.06087 16 7C16 5.93913 15.5786 4.92172 14.8284 4.17157C14.0783 3.42143 13.0609 3 12 3C10.9391 3 9.92172 3.42143 9.17157 4.17157C8.42143 4.92172 8 5.93913 8 7Z",
12
- stroke: t,
13
- strokeLinecap: "round",
14
- strokeLinejoin: "round",
15
- strokeWidth: "2"
16
- })
17
- });
18
- //#endregion
19
- export { t as default };
@@ -1,4 +0,0 @@
1
- //#region src/icons/spa/icon/Icon.registry.ts
2
- var e = { User: () => import("../../collection/UserIcon.js") };
3
- //#endregion
4
- export { e as defaultIcons };