@rdicons/react-ri 0.6.0 → 0.7.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/effects.js +210 -0
- package/package.json +2 -1
- package/runtime.d.ts +8 -0
- package/runtime.js +13 -3
package/effects.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// ../core/src/effects.ts
|
|
2
|
+
var ARGS = { m: 2, l: 2, h: 1, v: 1, c: 6, s: 4, q: 4, t: 2, a: 7, z: 0 };
|
|
3
|
+
var NUM = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
|
|
4
|
+
function firstSubpath(d) {
|
|
5
|
+
let cur = { x: 0, y: 0 };
|
|
6
|
+
let start = null;
|
|
7
|
+
let closed = false;
|
|
8
|
+
let min = { x: Infinity, y: Infinity };
|
|
9
|
+
let max = { x: -Infinity, y: -Infinity };
|
|
10
|
+
const add = (p) => {
|
|
11
|
+
min = { x: Math.min(min.x, p.x), y: Math.min(min.y, p.y) };
|
|
12
|
+
max = { x: Math.max(max.x, p.x), y: Math.max(max.y, p.y) };
|
|
13
|
+
};
|
|
14
|
+
for (const m of d.matchAll(/([a-zA-Z])([^a-zA-Z]*)/g)) {
|
|
15
|
+
const cmd = m[1];
|
|
16
|
+
const lower = cmd.toLowerCase();
|
|
17
|
+
const rel = cmd === lower;
|
|
18
|
+
if (!(lower in ARGS)) return null;
|
|
19
|
+
if (lower === "z") {
|
|
20
|
+
if (start) {
|
|
21
|
+
closed = true;
|
|
22
|
+
cur = start;
|
|
23
|
+
}
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
if (lower === "m" && start) break;
|
|
27
|
+
const args = m[2];
|
|
28
|
+
let pos = 0;
|
|
29
|
+
const number = () => {
|
|
30
|
+
while (pos < args.length && /[\s,]/.test(args[pos])) pos++;
|
|
31
|
+
const r = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/y;
|
|
32
|
+
r.lastIndex = pos;
|
|
33
|
+
const x = r.exec(args);
|
|
34
|
+
if (!x) return null;
|
|
35
|
+
pos = r.lastIndex;
|
|
36
|
+
return parseFloat(x[0]);
|
|
37
|
+
};
|
|
38
|
+
const flag = () => {
|
|
39
|
+
while (pos < args.length && /[\s,]/.test(args[pos])) pos++;
|
|
40
|
+
if (args[pos] !== "0" && args[pos] !== "1") return null;
|
|
41
|
+
return Number(args[pos++]);
|
|
42
|
+
};
|
|
43
|
+
let first = true;
|
|
44
|
+
for (; ; ) {
|
|
45
|
+
const ox = rel ? cur.x : 0, oy = rel ? cur.y : 0;
|
|
46
|
+
if (lower === "a") {
|
|
47
|
+
const rx = number(), ry = number(), rot = number(), f1 = flag(), f2 = flag(), x = number(), y = number();
|
|
48
|
+
if ([rx, ry, rot, f1, f2, x, y].some((v) => v == null)) break;
|
|
49
|
+
const end = { x: ox + x, y: oy + y };
|
|
50
|
+
const mid = { x: (cur.x + end.x) / 2, y: (cur.y + end.y) / 2 };
|
|
51
|
+
add({ x: mid.x - Math.abs(rx), y: mid.y - Math.abs(ry) });
|
|
52
|
+
add({ x: mid.x + Math.abs(rx), y: mid.y + Math.abs(ry) });
|
|
53
|
+
cur = end;
|
|
54
|
+
add(cur);
|
|
55
|
+
} else {
|
|
56
|
+
const a = [];
|
|
57
|
+
for (let k = 0; k < ARGS[lower]; k++) {
|
|
58
|
+
const v = number();
|
|
59
|
+
if (v == null) break;
|
|
60
|
+
a.push(v);
|
|
61
|
+
}
|
|
62
|
+
if (a.length < ARGS[lower]) break;
|
|
63
|
+
switch (lower) {
|
|
64
|
+
case "m": {
|
|
65
|
+
cur = { x: ox + a[0], y: oy + a[1] };
|
|
66
|
+
if (first) start = cur;
|
|
67
|
+
add(cur);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case "l":
|
|
71
|
+
case "t": {
|
|
72
|
+
cur = { x: ox + a[0], y: oy + a[1] };
|
|
73
|
+
add(cur);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
case "h": {
|
|
77
|
+
cur = { x: ox + a[0], y: cur.y };
|
|
78
|
+
add(cur);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
case "v": {
|
|
82
|
+
cur = { x: cur.x, y: oy + a[0] };
|
|
83
|
+
add(cur);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case "c": {
|
|
87
|
+
add({ x: ox + a[0], y: oy + a[1] });
|
|
88
|
+
add({ x: ox + a[2], y: oy + a[3] });
|
|
89
|
+
cur = { x: ox + a[4], y: oy + a[5] };
|
|
90
|
+
add(cur);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case "s":
|
|
94
|
+
case "q": {
|
|
95
|
+
add({ x: ox + a[0], y: oy + a[1] });
|
|
96
|
+
cur = { x: ox + a[2], y: oy + a[3] };
|
|
97
|
+
add(cur);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
first = false;
|
|
103
|
+
}
|
|
104
|
+
if (!start && lower !== "m") return null;
|
|
105
|
+
}
|
|
106
|
+
if (!start || !isFinite(min.x)) return null;
|
|
107
|
+
add(start);
|
|
108
|
+
return { start, end: cur, closed, min, max };
|
|
109
|
+
}
|
|
110
|
+
var attr = (tag, name) => {
|
|
111
|
+
var _a;
|
|
112
|
+
const m = tag.match(new RegExp(`(?:^|\\s)${name}\\s*=\\s*("([^"]*)"|'([^']*)')`));
|
|
113
|
+
return m ? (_a = m[2]) != null ? _a : m[3] : null;
|
|
114
|
+
};
|
|
115
|
+
var num = (v) => v == null ? null : Number.isFinite(parseFloat(v)) ? parseFloat(v) : null;
|
|
116
|
+
var MIN_SIDE = 5;
|
|
117
|
+
function fillableShapes(body) {
|
|
118
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
119
|
+
if (!/stroke=/.test(body)) return [];
|
|
120
|
+
const out = [];
|
|
121
|
+
let i = -1;
|
|
122
|
+
for (const m of body.matchAll(/<(path|circle|ellipse|rect|polygon|line|polyline)\b[^>]*>/g)) {
|
|
123
|
+
i++;
|
|
124
|
+
const tag = m[0];
|
|
125
|
+
const kind = m[1];
|
|
126
|
+
const ownFill = attr(tag, "fill");
|
|
127
|
+
if (ownFill && ownFill !== "none") continue;
|
|
128
|
+
if (kind === "circle") {
|
|
129
|
+
if (((_a = num(attr(tag, "r"))) != null ? _a : 0) * 2 >= MIN_SIDE) out.push(i);
|
|
130
|
+
} else if (kind === "ellipse") {
|
|
131
|
+
if (((_b = num(attr(tag, "rx"))) != null ? _b : 0) * 2 >= MIN_SIDE && ((_c = num(attr(tag, "ry"))) != null ? _c : 0) * 2 >= MIN_SIDE) out.push(i);
|
|
132
|
+
} else if (kind === "rect") {
|
|
133
|
+
if (((_d = num(attr(tag, "width"))) != null ? _d : 0) >= MIN_SIDE && ((_e = num(attr(tag, "height"))) != null ? _e : 0) >= MIN_SIDE) out.push(i);
|
|
134
|
+
} else if (kind === "polygon") {
|
|
135
|
+
const pts = (_h = (_g = ((_f = attr(tag, "points")) != null ? _f : "").match(NUM)) == null ? void 0 : _g.map(Number)) != null ? _h : [];
|
|
136
|
+
const xs = pts.filter((_, k) => k % 2 === 0), ys = pts.filter((_, k) => k % 2 === 1);
|
|
137
|
+
if (xs.length >= 3 && Math.max(...xs) - Math.min(...xs) >= MIN_SIDE && Math.max(...ys) - Math.min(...ys) >= MIN_SIDE) out.push(i);
|
|
138
|
+
} else if (kind === "path") {
|
|
139
|
+
const sp = firstSubpath((_i = attr(tag, "d")) != null ? _i : "");
|
|
140
|
+
if (!sp) continue;
|
|
141
|
+
const w = sp.max.x - sp.min.x, h = sp.max.y - sp.min.y;
|
|
142
|
+
if (w < MIN_SIDE || h < MIN_SIDE) continue;
|
|
143
|
+
const gap = Math.hypot(sp.end.x - sp.start.x, sp.end.y - sp.start.y);
|
|
144
|
+
if (sp.closed || gap <= Math.max(2.5, 0.35 * Math.min(w, h))) out.push(i);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
var esc = (s) => s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
150
|
+
var safeColor = (c) => /^[#a-zA-Z0-9(),.%\s\-/]+$/.test(c) ? c : "#000000";
|
|
151
|
+
function gradientLine(w, h, angle = 45) {
|
|
152
|
+
const a = angle * Math.PI / 180;
|
|
153
|
+
const dx = Math.cos(a), dy = Math.sin(a);
|
|
154
|
+
const half = (Math.abs(w * dx) + Math.abs(h * dy)) / 2;
|
|
155
|
+
const cx = w / 2, cy = h / 2;
|
|
156
|
+
const r = (n) => Math.round(n * 100) / 100;
|
|
157
|
+
return { x1: r(cx - dx * half), y1: r(cy - dy * half), x2: r(cx + dx * half), y2: r(cy + dy * half) };
|
|
158
|
+
}
|
|
159
|
+
function applyEffects(body, w, h, base, o) {
|
|
160
|
+
var _a, _b, _c;
|
|
161
|
+
const id = ((_a = o.gradientId) != null ? _a : "rdg").replace(/[^a-zA-Z0-9_-]/g, "");
|
|
162
|
+
const g = o.gradient;
|
|
163
|
+
const paint = g ? `url(#${id})` : base;
|
|
164
|
+
let out = body.replace(/currentColor/g, paint);
|
|
165
|
+
if (o.duotone) {
|
|
166
|
+
const idx = new Set(fillableShapes(body));
|
|
167
|
+
if (idx.size) {
|
|
168
|
+
const fill = o.duotone.color ? safeColor(o.duotone.color) : paint;
|
|
169
|
+
const opacity = Math.min(1, Math.max(0, (_b = o.duotone.opacity) != null ? _b : 0.2));
|
|
170
|
+
let i = -1;
|
|
171
|
+
out = out.replace(/<(path|circle|ellipse|rect|polygon|line|polyline)\b([^>]*?)(\/?)>/g, (whole, tagName, rest, slash) => {
|
|
172
|
+
i++;
|
|
173
|
+
if (!idx.has(i)) return whole;
|
|
174
|
+
const cleaned = rest.replace(/\sfill="[^"]*"/, "");
|
|
175
|
+
return `<${tagName}${cleaned} fill="${esc(fill)}" fill-opacity="${opacity}"${slash}>`;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (g) {
|
|
180
|
+
const l = gradientLine(w, h, (_c = g.angle) != null ? _c : 45);
|
|
181
|
+
out = `<defs><linearGradient id="${id}" gradientUnits="userSpaceOnUse" x1="${l.x1}" y1="${l.y1}" x2="${l.x2}" y2="${l.y2}"><stop offset="0" stop-color="${esc(safeColor(g.from))}"/><stop offset="1" stop-color="${esc(safeColor(g.to))}"/></linearGradient></defs>${out}`;
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
function parseEffectProps(p, gradientId) {
|
|
186
|
+
var _a, _b;
|
|
187
|
+
let duotone = null;
|
|
188
|
+
const d = p.duotone;
|
|
189
|
+
if (d === true || d === "" || d === "true") duotone = {};
|
|
190
|
+
else if (typeof d === "number") duotone = { opacity: d };
|
|
191
|
+
else if (typeof d === "string" && d) duotone = { color: d };
|
|
192
|
+
else if (d && typeof d === "object") duotone = { color: (_a = d.color) != null ? _a : null, opacity: d.opacity };
|
|
193
|
+
let gradient = null;
|
|
194
|
+
const g = p.gradient;
|
|
195
|
+
if (typeof g === "string" && g) {
|
|
196
|
+
const [from, to, angle] = g.split(",").map((x) => x.trim());
|
|
197
|
+
if (from && to) gradient = { from, to, angle: angle !== void 0 && Number.isFinite(Number(angle)) ? Number(angle) : 45 };
|
|
198
|
+
} else if (Array.isArray(g) && g.length >= 2) gradient = { from: String(g[0]), to: String(g[1]), angle: typeof g[2] === "number" ? g[2] : 45 };
|
|
199
|
+
else if (g && typeof g === "object" && "from" in g) gradient = { from: g.from, to: g.to, angle: (_b = g.angle) != null ? _b : 45 };
|
|
200
|
+
if (!duotone && !gradient) return null;
|
|
201
|
+
return { duotone, gradient, gradientId };
|
|
202
|
+
}
|
|
203
|
+
export {
|
|
204
|
+
applyEffects,
|
|
205
|
+
fillableShapes,
|
|
206
|
+
firstSubpath,
|
|
207
|
+
gradientLine,
|
|
208
|
+
parseEffectProps,
|
|
209
|
+
safeColor
|
|
210
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rdicons/react-ri",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Remix Icon: 3,229 icons as React components. Just this library, tree-shakable. Part of rdicons.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"icons",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"index.d.ts",
|
|
38
38
|
"runtime.js",
|
|
39
39
|
"runtime.d.ts",
|
|
40
|
+
"effects.js",
|
|
40
41
|
"LICENSES",
|
|
41
42
|
"README.md",
|
|
42
43
|
"LICENSE"
|
package/runtime.d.ts
CHANGED
|
@@ -18,6 +18,14 @@ export interface RdIconProps extends Omit<SVGProps<SVGSVGElement>, 'ref' | 'chil
|
|
|
18
18
|
animate?: boolean | 'hover' | 'parent' | 'play' | 'loop'
|
|
19
19
|
/** Animation speed: 2 is twice as fast, 0.5 twice as slow. Default 1 */
|
|
20
20
|
speed?: number
|
|
21
|
+
/**
|
|
22
|
+
* Fills the closed shapes of an outline icon with a soft second tone.
|
|
23
|
+
* `true`: the icon's own color at 20%. A number (0.05 to 0.6): the strength. A string: another color for the fill. An object: `{ color, opacity }`.
|
|
24
|
+
* Icons without a closed shape (arrows, plus) look the same.
|
|
25
|
+
*/
|
|
26
|
+
duotone?: boolean | number | string | { color?: string | null; opacity?: number }
|
|
27
|
+
/** The line runs from one color to another: `['#ec4899', '#4f46e5']`, with an angle as a third item (45 by default), or `{ from, to, angle }`. Replaces `color`. */
|
|
28
|
+
gradient?: [string, string] | [string, string, number] | { from: string; to: string; angle?: number }
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
export type RdIcon = ForwardRefExoticComponent<RdIconProps & RefAttributes<SVGSVGElement>>
|
package/runtime.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { applyEffects, parseEffectProps } from './effects.js'
|
|
3
|
+
|
|
4
|
+
const { createElement, forwardRef } = React
|
|
2
5
|
|
|
3
6
|
const STROKE_WIDTH = /stroke-width="[^"]*"/g
|
|
7
|
+
let legacyIds = 0
|
|
8
|
+
// useId needs React 18; on React 17 a counter kept in a ref gives every icon its own gradient id
|
|
9
|
+
const useUid = React.useId || function useLegacyUid() { const ref = React.useRef(null); if (ref.current === null) ref.current = ':l' + ++legacyIds; return ref.current }
|
|
4
10
|
const escapeHtml = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
5
11
|
|
|
6
12
|
/**
|
|
@@ -9,10 +15,14 @@ const escapeHtml = (s) => String(s).replace(/&/g, '&').replace(/</g, '<')
|
|
|
9
15
|
*/
|
|
10
16
|
export function createIcon(id, body, width = 24, height = 24, anim) {
|
|
11
17
|
const name = id.slice(id.indexOf(':') + 1)
|
|
12
|
-
const Icon = forwardRef(function RdIcon({ size = 24, color, strokeWidth, title, style, animate, speed, className, ...rest }, ref) {
|
|
18
|
+
const Icon = forwardRef(function RdIcon({ size = 24, color, strokeWidth, title, style, animate, speed, duotone, gradient, className, ...rest }, ref) {
|
|
19
|
+
const uid = useUid()
|
|
13
20
|
// Animation is opt-in. Only icons that have motion (anim = { b: animated drawing, c: css }) react to `animate`; the rest ignore it.
|
|
14
21
|
const mode = anim && animate ? (animate === true ? 'hover' : animate) : null
|
|
15
|
-
|
|
22
|
+
let drawing = mode ? anim.b : body
|
|
23
|
+
// colour effects: a soft second tone in closed shapes, and/or a gradient along the line
|
|
24
|
+
const fx = duotone || gradient ? parseEffectProps({ duotone, gradient }, 'rdg' + uid) : null
|
|
25
|
+
if (fx) drawing = applyEffects(drawing, width, height, 'currentColor', fx)
|
|
16
26
|
const html = strokeWidth == null ? drawing : drawing.replace(STROKE_WIDTH, 'stroke-width="' + strokeWidth + '"')
|
|
17
27
|
// Fit the longest side into the `size` box, like the rdicons website does.
|
|
18
28
|
const n = typeof size === 'number' ? size : Number.parseFloat(size)
|