mirror of
https://github.com/lucide-icons/lucide.git
synced 2025-12-18 00:07:41 +01:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01fa96ced3 | ||
|
|
481b27cc49 |
15
icons/menu-square.json
Normal file
15
icons/menu-square.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../icon.schema.json",
|
||||||
|
"tags": [
|
||||||
|
"bars",
|
||||||
|
"navigation",
|
||||||
|
"hamburger",
|
||||||
|
"options",
|
||||||
|
"menu bar",
|
||||||
|
"panel"
|
||||||
|
],
|
||||||
|
"categories": [
|
||||||
|
"layout",
|
||||||
|
"account"
|
||||||
|
]
|
||||||
|
}
|
||||||
16
icons/menu-square.svg
Normal file
16
icons/menu-square.svg
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<rect width="18" height="18" x="3" y="3" rx="2" />
|
||||||
|
<path d="M7 8h10" />
|
||||||
|
<path d="M7 12h10" />
|
||||||
|
<path d="M7 16h10" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 332 B |
@@ -173,6 +173,31 @@ const ControlPath = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Radii = ({
|
||||||
|
paths,
|
||||||
|
...props
|
||||||
|
}: { paths: Path[] } & PathProps<
|
||||||
|
'strokeWidth' | 'stroke' | 'strokeDasharray' | 'strokeOpacity',
|
||||||
|
any
|
||||||
|
>) => {
|
||||||
|
return (
|
||||||
|
<g className="svg-preview-radii-group" {...props}>
|
||||||
|
{paths
|
||||||
|
.filter(({ circle }) => circle)
|
||||||
|
.map(({ c, prev, next, circle: { x, y, r } }) =>
|
||||||
|
c.name === 'circle' ? (
|
||||||
|
<path d={`M${x} ${y}h.01`} />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<path d={`M${prev.x} ${prev.y} ${x} ${y} ${next.x} ${next.y}`} />
|
||||||
|
<circle cy={y} cx={x} r={r} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const SvgPreview = React.forwardRef<
|
const SvgPreview = React.forwardRef<
|
||||||
SVGSVGElement,
|
SVGSVGElement,
|
||||||
{
|
{
|
||||||
@@ -184,6 +209,7 @@ const SvgPreview = React.forwardRef<
|
|||||||
|
|
||||||
const darkModeCss = `@media screen and (prefers-color-scheme: dark) {
|
const darkModeCss = `@media screen and (prefers-color-scheme: dark) {
|
||||||
.svg-preview-grid-group,
|
.svg-preview-grid-group,
|
||||||
|
.svg-preview-radii-group,
|
||||||
.svg-preview-shadow-mask-group,
|
.svg-preview-shadow-mask-group,
|
||||||
.svg-preview-shadow-group {
|
.svg-preview-shadow-group {
|
||||||
stroke: #fff;
|
stroke: #fff;
|
||||||
@@ -223,6 +249,13 @@ const SvgPreview = React.forwardRef<
|
|||||||
'#52A675',
|
'#52A675',
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
<Radii
|
||||||
|
paths={paths}
|
||||||
|
strokeWidth={0.12}
|
||||||
|
strokeDasharray="0 0.25 0.25"
|
||||||
|
stroke="#777"
|
||||||
|
strokeOpacity={0.3}
|
||||||
|
/>
|
||||||
<ControlPath radius={1} paths={paths} pointSize={1} stroke="#fff" strokeWidth={0.125} />
|
<ControlPath radius={1} paths={paths} pointSize={1} stroke="#fff" strokeWidth={0.125} />
|
||||||
{children}
|
{children}
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type Path = {
|
|||||||
prev: Point;
|
prev: Point;
|
||||||
next: Point;
|
next: Point;
|
||||||
isStart: boolean;
|
isStart: boolean;
|
||||||
|
circle: { x: number; y: number; r: number };
|
||||||
c: ReturnType<typeof getCommands>[number];
|
c: ReturnType<typeof getCommands>[number];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,33 +12,66 @@ export function assert(value: unknown): asserts value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const extractPaths = (node: INode): { d: string; name: typeof node.name }[] => {
|
const convertToPathNode = (node: INode): { d: string; name: typeof node.name } => {
|
||||||
if (/(rect|circle|ellipse|polygon|polyline|line|path)/.test(node.name)) {
|
if (node.name === 'path') {
|
||||||
return [{ d: toPath(node), name: node.name }];
|
return { d: node.attributes.d, name: node.name };
|
||||||
|
}
|
||||||
|
if (node.name === 'circle') {
|
||||||
|
const cx = parseFloat(node.attributes.cx);
|
||||||
|
const cy = parseFloat(node.attributes.cy);
|
||||||
|
const r = parseFloat(node.attributes.r);
|
||||||
|
return {
|
||||||
|
d: [
|
||||||
|
`M ${cx} ${cy - r}`,
|
||||||
|
`a ${r} ${r} 0 0 1 ${r} ${r}`,
|
||||||
|
`a ${r} ${r} 0 0 1 ${0 - r} ${r}`,
|
||||||
|
`a ${r} ${r} 0 0 1 ${0 - r} ${0 - r}`,
|
||||||
|
`a ${r} ${r} 0 0 1 ${r} ${0 - r}`,
|
||||||
|
].join(''),
|
||||||
|
name: node.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { d: toPath(node).replace(/z$/i, ''), name: node.name };
|
||||||
|
};
|
||||||
|
|
||||||
|
const extractNodes = (node: INode): INode[] => {
|
||||||
|
if (['rect', 'circle', 'ellipse', 'polygon', 'polyline', 'line', 'path'].includes(node.name)) {
|
||||||
|
return [node];
|
||||||
} else if (node.children && Array.isArray(node.children)) {
|
} else if (node.children && Array.isArray(node.children)) {
|
||||||
return node.children.flatMap(extractPaths);
|
return node.children.flatMap(extractNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getNodes = (src: string) =>
|
||||||
|
extractNodes(parseSync(src.includes('<svg') ? src : `<svg>${src}</svg>`));
|
||||||
|
|
||||||
export const getCommands = (src: string) =>
|
export const getCommands = (src: string) =>
|
||||||
extractPaths(parseSync(src)).flatMap(({ d, name }, idx) =>
|
getNodes(src)
|
||||||
new SVGPathData(d).toAbs().commands.map((c) => ({ ...c, id: idx, name }))
|
.map(convertToPathNode)
|
||||||
);
|
.flatMap(({ d, name }, idx) =>
|
||||||
|
new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name }))
|
||||||
|
);
|
||||||
|
|
||||||
export const getPaths = (src: string) => {
|
export const getPaths = (src: string) => {
|
||||||
const commands = getCommands(src.includes('<svg') ? src : `<svg>${src}</svg>`);
|
const commands = getCommands(src.includes('<svg') ? src : `<svg>${src}</svg>`);
|
||||||
const paths: Path[] = [];
|
const paths: Path[] = [];
|
||||||
let prev: Point | undefined = undefined;
|
let prev: Point | undefined = undefined;
|
||||||
let start: Point | undefined = undefined;
|
let start: Point | undefined = undefined;
|
||||||
const addPath = (c: (typeof commands)[number], next: Point, d?: string) => {
|
const addPath = (
|
||||||
|
c: typeof commands[number],
|
||||||
|
next: Point,
|
||||||
|
d?: string,
|
||||||
|
circle?: Path['circle']
|
||||||
|
) => {
|
||||||
assert(prev);
|
assert(prev);
|
||||||
paths.push({
|
paths.push({
|
||||||
c,
|
c,
|
||||||
d: d || `M${prev.x} ${prev.y}L${next.x} ${next.y}`,
|
d: d || `M ${prev.x} ${prev.y} L ${next.x} ${next.y}`,
|
||||||
prev,
|
prev,
|
||||||
next,
|
next,
|
||||||
|
circle,
|
||||||
isStart: start === prev,
|
isStart: start === prev,
|
||||||
});
|
});
|
||||||
prev = next;
|
prev = next;
|
||||||
@@ -77,7 +110,7 @@ export const getPaths = (src: string) => {
|
|||||||
}
|
}
|
||||||
case SVGPathData.CURVE_TO: {
|
case SVGPathData.CURVE_TO: {
|
||||||
assert(prev);
|
assert(prev);
|
||||||
addPath(c, c, `M ${prev.x},${prev.y} ${encodeSVGPath(c)}`);
|
addPath(c, c, `M ${prev.x} ${prev.y} ${encodeSVGPath(c)}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SVGPathData.SMOOTH_CURVE_TO: {
|
case SVGPathData.SMOOTH_CURVE_TO: {
|
||||||
@@ -86,16 +119,16 @@ export const getPaths = (src: string) => {
|
|||||||
const reflectedCp1 = {
|
const reflectedCp1 = {
|
||||||
x:
|
x:
|
||||||
previousCommand &&
|
previousCommand &&
|
||||||
(previousCommand.type === SVGPathData.SMOOTH_CURVE_TO ||
|
(previousCommand.type === SVGPathData.SMOOTH_CURVE_TO ||
|
||||||
previousCommand.type === SVGPathData.CURVE_TO)
|
previousCommand.type === SVGPathData.CURVE_TO)
|
||||||
? previousCommand.relative
|
? previousCommand.relative
|
||||||
? previousCommand.x2 - previousCommand.x
|
? previousCommand.x2 - previousCommand.x
|
||||||
: previousCommand.x2 - prev.x
|
: previousCommand.x2 - prev.x
|
||||||
: 0,
|
: 0,
|
||||||
y:
|
y:
|
||||||
previousCommand &&
|
previousCommand &&
|
||||||
(previousCommand.type === SVGPathData.SMOOTH_CURVE_TO ||
|
(previousCommand.type === SVGPathData.SMOOTH_CURVE_TO ||
|
||||||
previousCommand.type === SVGPathData.CURVE_TO)
|
previousCommand.type === SVGPathData.CURVE_TO)
|
||||||
? previousCommand.relative
|
? previousCommand.relative
|
||||||
? previousCommand.y2 - previousCommand.y
|
? previousCommand.y2 - previousCommand.y
|
||||||
: previousCommand.y2 - prev.y
|
: previousCommand.y2 - prev.y
|
||||||
@@ -104,7 +137,7 @@ export const getPaths = (src: string) => {
|
|||||||
addPath(
|
addPath(
|
||||||
c,
|
c,
|
||||||
c,
|
c,
|
||||||
`M ${prev.x},${prev.y} ${encodeSVGPath({
|
`M ${prev.x} ${prev.y} ${encodeSVGPath({
|
||||||
type: SVGPathData.CURVE_TO,
|
type: SVGPathData.CURVE_TO,
|
||||||
relative: false,
|
relative: false,
|
||||||
x: c.x,
|
x: c.x,
|
||||||
@@ -119,7 +152,7 @@ export const getPaths = (src: string) => {
|
|||||||
}
|
}
|
||||||
case SVGPathData.QUAD_TO: {
|
case SVGPathData.QUAD_TO: {
|
||||||
assert(prev);
|
assert(prev);
|
||||||
addPath(c, c, `M ${prev.x},${prev.y} ${encodeSVGPath(c)}`);
|
addPath(c, c, `M ${prev.x} ${prev.y} ${encodeSVGPath(c)}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SVGPathData.SMOOTH_QUAD_TO: {
|
case SVGPathData.SMOOTH_QUAD_TO: {
|
||||||
@@ -157,7 +190,7 @@ export const getPaths = (src: string) => {
|
|||||||
addPath(
|
addPath(
|
||||||
c,
|
c,
|
||||||
c,
|
c,
|
||||||
`M ${prev.x},${prev.y} ${encodeSVGPath({
|
`M ${prev.x} ${prev.y} ${encodeSVGPath({
|
||||||
type: SVGPathData.QUAD_TO,
|
type: SVGPathData.QUAD_TO,
|
||||||
relative: false,
|
relative: false,
|
||||||
x: c.x,
|
x: c.x,
|
||||||
@@ -170,10 +203,22 @@ export const getPaths = (src: string) => {
|
|||||||
}
|
}
|
||||||
case SVGPathData.ARC: {
|
case SVGPathData.ARC: {
|
||||||
assert(prev);
|
assert(prev);
|
||||||
|
const center = arcEllipseCenter(
|
||||||
|
prev.x,
|
||||||
|
prev.y,
|
||||||
|
c.rX,
|
||||||
|
c.rY,
|
||||||
|
c.xRot,
|
||||||
|
c.lArcFlag,
|
||||||
|
c.sweepFlag,
|
||||||
|
c.x,
|
||||||
|
c.y
|
||||||
|
);
|
||||||
addPath(
|
addPath(
|
||||||
c,
|
c,
|
||||||
c,
|
c,
|
||||||
`M ${prev.x},${prev.y} A ${c.rX} ${c.rY} ${c.xRot} ${c.lArcFlag} ${c.sweepFlag} ${c.x} ${c.y}`
|
`M ${prev.x} ${prev.y} A${c.rX} ${c.rY} ${c.xRot} ${c.lArcFlag} ${c.sweepFlag} ${c.x} ${c.y}`,
|
||||||
|
c.rX === c.rY ? { ...center, r: c.rX } : undefined
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -184,3 +229,58 @@ export const getPaths = (src: string) => {
|
|||||||
}
|
}
|
||||||
return paths;
|
return paths;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const arcEllipseCenter = (
|
||||||
|
x1: number,
|
||||||
|
y1: number,
|
||||||
|
rx: number,
|
||||||
|
ry: number,
|
||||||
|
a: number,
|
||||||
|
fa: number,
|
||||||
|
fs: number,
|
||||||
|
x2: number,
|
||||||
|
y2: number
|
||||||
|
) => {
|
||||||
|
const phi = (a * Math.PI) / 180;
|
||||||
|
|
||||||
|
const M = [
|
||||||
|
[Math.cos(phi), Math.sin(phi)],
|
||||||
|
[-Math.sin(phi), Math.cos(phi)],
|
||||||
|
];
|
||||||
|
const V = [(x1 - x2) / 2, (y1 - y2) / 2];
|
||||||
|
|
||||||
|
const [x1p, y1p] = [M[0][0] * V[0] + M[0][1] * V[1], M[1][0] * V[0] + M[1][1] * V[1]];
|
||||||
|
|
||||||
|
rx = Math.abs(rx);
|
||||||
|
ry = Math.abs(ry);
|
||||||
|
|
||||||
|
const lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);
|
||||||
|
if (lambda > 1) {
|
||||||
|
rx = Math.sqrt(lambda) * rx;
|
||||||
|
ry = Math.sqrt(lambda) * ry;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sign = fa === fs ? -1 : 1;
|
||||||
|
|
||||||
|
const co =
|
||||||
|
sign *
|
||||||
|
Math.sqrt(
|
||||||
|
Math.max(rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p, 0) /
|
||||||
|
(rx * rx * y1p * y1p + ry * ry * x1p * x1p)
|
||||||
|
);
|
||||||
|
|
||||||
|
const V2 = [(rx * y1p) / ry, (-ry * x1p) / rx];
|
||||||
|
const Cp = [V2[0] * co, V2[1] * co];
|
||||||
|
|
||||||
|
const M2 = [
|
||||||
|
[Math.cos(phi), -Math.sin(phi)],
|
||||||
|
[Math.sin(phi), Math.cos(phi)],
|
||||||
|
];
|
||||||
|
const V3 = [(x1 + x2) / 2, (y1 + y2) / 2];
|
||||||
|
const C = [
|
||||||
|
M2[0][0] * Cp[0] + M2[0][1] * Cp[1] + V3[0],
|
||||||
|
M2[1][0] * Cp[0] + M2[1][1] * Cp[1] + V3[1],
|
||||||
|
];
|
||||||
|
|
||||||
|
return { x: C[0], y: C[1] };
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user