mirror of
https://github.com/lucide-icons/lucide.git
synced 2026-08-29 09:38:26 +02:00
* Update images * Adjust color * update accessebility * Add matrix hero * Add typescript documentation * Add basics section to vue pages * Add svelte docs * Fix dynamic sidebar * Add animation? * Some adjustments * Adjust animation * Updates docs * Add scaledown animation * Add docs for vue types * Fix layout accessibility page * fix framework select * adjust easing home animation * Write docs for Vue * Adjust animation home * Adjust home hero animation * Finish svelte docs * Add solid pages * remove spiral animation component * Add group icons * Added solid docs * update preact docs * Adjust examples preact docs * Add snackplayer * Get new editor * Save this * Add examples * Adjust styling * setup custom sandpack * Add script * Format files * Make sandpack plugin work * migrate react docs * Fix svelte and solid examples * Migrate to solid packages * Add darkmode package logos * Fix bug in selector * migrate vue examples * migrate advanced vue items * migrate preact and svelte examples * Add astro docs * adding more docs on guide for lucide library * Fix home animation * Cleanup * Added resources page, with more details and content * Add hero badge for Version 1 * Fix vercel json * Update missing paths * Fix build? * Replace lucide-vue-next * Fix build * Add some docs * update markdown * Setup angular docs * Add basic angular docs * Adjust code examples * Update title and descriptions * Update accessibility link * Update title and description * Add og image * Fix alignment * Add migration guides * Adjust version 1 markdown file * Add migration guide to the main list * Minor fixes * Update docs * Apply feedback * Select icons * Add pointer events none * Fix package links homepage * Format code * Fix types * Fix focus * Fix build * Fix focus * Apply feedback * Adjust imports * Adjust imports * Fix search * Apply feedback * Fix import in font docs * Small fixes * docs(guide): added easter egg icon. stay tuned, guys 😉 * Add llm txt plugin * Adjust logo top * docs(version-1): upgraded @lucide/angular guide for v1 website (#4144) * docs(guide/angular): rewrite some angular documentation * docs(guide/angular): extens angular guide with combining icons & icon provider guides * docs(guide/angular): fix angular sandbox, still needs @lucide/angular release tho * docs(guide/angular): rework the getting started page a bit more, refactor some more prop=>input occurences * docs(guide/angular): context provider => provideLucideConfig * Update docs/guide/angular/migration.md Co-authored-by: Eric Fennis <eric.fennis@gmail.com> * Update docs/guide/angular/advanced/combining-icons.md Co-authored-by: Eric Fennis <eric.fennis@gmail.com> * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * docs(guide/angular): rework a11y for better flow & pacing * docs(guide/angular): salvage original phrase, it has better flow * docs(guide/angular): some more a11y nitpicking * docs(guide/angular): reduce llm fatigue * docs(guides/angular): fix app component selector * docs(guides/angular): fix angular sandpacks * Add sandpack angular to improve tree-shakable stack * Update docs/guide/angular/getting-started.md * docs(guides/angular): fix user import in combinding icons guide * docs(guides/angular): fix nested SVG phrasing * Update docs/guide/angular/advanced/with-lucide-lab.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update docs/guide/angular/advanced/filled-icons.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * docs(guides/angular): fix createLucideIcon guide * docs(guides/angular): upgrade all relevant angular sandpack demos --------- Co-authored-by: Eric Fennis <eric.fennis@gmail.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix(docs): add autogenerated OG images to gitignore * fix(docs): unify the case of "Lucide" & "Lucide Lab" over all docs and readmes * Fix build * Add next tag to all installation guides * Add top bar notification * Minor fixes * Update text * Add todos * Update og image * Adjust install command * Color top bar * fix(docs): ignore all OG images, expect general.png * fix(docs): fix the package list, hide @lucide/icons, downgrade angular logo, link to v0 guide, fix package title alignment * Update docs/.vitepress/theme/components/base/LayoutTop.vue Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update docs/guide/react/advanced/dynamic-icon-component.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update CONTRIBUTING.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update docs/.vitepress/sidebar/resources.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix code example * fix(docs): version 1 => Version 1 * feat(docs): add angular to new features in v1 * Fix next line issue * feat(docs): fix typo (code example_s_) * Fix deadlink * feat(docs): fix some more typos and irky grammar * feat(docs): fix lowercase lucide in sidebar 😅 * Update docs/guide/version-1.md Co-authored-by: Karsa <contact@karsa.org> --------- Co-authored-by: Karsa <contact@karsa.org> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
492 lines
13 KiB
TypeScript
492 lines
13 KiB
TypeScript
import React, { Fragment } from 'react';
|
|
import { PathProps, Path } from './types';
|
|
import getPaths, { assert } from './utils';
|
|
import { GapViolationHighlight } from './GapViolationHighlight.tsx';
|
|
|
|
export const darkModeCss = `
|
|
@media screen and (prefers-color-scheme: light) {
|
|
.svg-preview-grid-rect { fill: none }
|
|
}
|
|
@media screen and (prefers-color-scheme: dark) {
|
|
.svg-preview-grid-rect { fill: none }
|
|
.svg
|
|
.svg-preview-grid-group,
|
|
.svg-preview-radii-group,
|
|
.svg-preview-shadow-mask-group,
|
|
.svg-preview-shadow-group {
|
|
stroke: #fff;
|
|
}
|
|
}
|
|
`;
|
|
|
|
export const Grid = ({
|
|
radius,
|
|
fill,
|
|
height,
|
|
width,
|
|
subGridSize = 0,
|
|
...props
|
|
}: {
|
|
height: number;
|
|
width: number;
|
|
strokeWidth: number;
|
|
subGridSize?: number;
|
|
radius: number;
|
|
} & PathProps<'stroke', 'strokeWidth'>) => (
|
|
<g
|
|
className="svg-preview-grid-group"
|
|
strokeLinecap="butt"
|
|
{...props}
|
|
>
|
|
<rect
|
|
className="svg-preview-grid-rect"
|
|
width={width - props.strokeWidth}
|
|
height={height - props.strokeWidth}
|
|
x={props.strokeWidth / 2}
|
|
y={props.strokeWidth / 2}
|
|
rx={radius}
|
|
fill={fill}
|
|
/>
|
|
<path
|
|
strokeDasharray={
|
|
'0 0.1 ' + '0.1 0.15 '.repeat(subGridSize ? subGridSize * 4 - 1 : 95) + '0 0.15'
|
|
}
|
|
strokeWidth={0.1}
|
|
d={
|
|
props.d ||
|
|
[
|
|
...new Array(Math.floor(width - 1))
|
|
.fill(null)
|
|
.map((_, i) => i)
|
|
.filter((i) => !subGridSize || i % subGridSize !== subGridSize - 1)
|
|
.flatMap((i) => [`M${i + 1} ${props.strokeWidth}v${height - props.strokeWidth * 2}`]),
|
|
...new Array(Math.floor(height - 1))
|
|
.fill(null)
|
|
.map((_, i) => i)
|
|
.filter((i) => !subGridSize || i % subGridSize !== subGridSize - 1)
|
|
.flatMap((i) => [`M${props.strokeWidth} ${i + 1}h${width - props.strokeWidth * 2}`]),
|
|
].join('')
|
|
}
|
|
/>
|
|
{!!subGridSize && (
|
|
<path
|
|
d={
|
|
props.d ||
|
|
[
|
|
...new Array(Math.floor(width - 1))
|
|
.fill(null)
|
|
.map((_, i) => i)
|
|
.filter((i) => i % subGridSize === subGridSize - 1)
|
|
.flatMap((i) => [`M${i + 1} ${props.strokeWidth}v${height - props.strokeWidth * 2}`]),
|
|
...new Array(Math.floor(height - 1))
|
|
.fill(null)
|
|
.map((_, i) => i)
|
|
.filter((i) => i % subGridSize === subGridSize - 1)
|
|
.flatMap((i) => [`M${props.strokeWidth} ${i + 1}h${width - props.strokeWidth * 2}`]),
|
|
].join('')
|
|
}
|
|
/>
|
|
)}
|
|
</g>
|
|
);
|
|
|
|
const Shadow = ({
|
|
radius,
|
|
paths,
|
|
...props
|
|
}: {
|
|
radius: number;
|
|
paths: Path[];
|
|
} & PathProps<'stroke' | 'strokeWidth' | 'strokeOpacity', 'd'>) => {
|
|
const groupedPaths = Object.entries(
|
|
paths.reduce(
|
|
(groups, val) => {
|
|
const key = val.c.id;
|
|
groups[key] = [...(groups[key] || []), val];
|
|
return groups;
|
|
},
|
|
{} as Record<number, Path[]>,
|
|
),
|
|
);
|
|
return (
|
|
<>
|
|
<g
|
|
className="svg-preview-shadow-mask-group"
|
|
{...props}
|
|
>
|
|
{groupedPaths.map(([id, paths]) => (
|
|
<mask
|
|
key={`svg-preview-shadow-mask-${id}`}
|
|
id={`svg-preview-shadow-mask-${id}`}
|
|
maskUnits="userSpaceOnUse"
|
|
strokeOpacity="1"
|
|
strokeWidth={props.strokeWidth}
|
|
stroke="#000"
|
|
>
|
|
<rect
|
|
x={0}
|
|
y={0}
|
|
width="100%"
|
|
height="100%"
|
|
fill="#fff"
|
|
stroke="none"
|
|
rx={radius}
|
|
/>
|
|
<path
|
|
d={paths
|
|
.flatMap(({ prev, next }) => [
|
|
`M${prev.x} ${prev.y}h.01`,
|
|
`M${next.x} ${next.y}h.01`,
|
|
])
|
|
.filter((val, idx, arr) => arr.indexOf(val) === idx)
|
|
.join('')}
|
|
/>
|
|
</mask>
|
|
))}
|
|
</g>
|
|
<g
|
|
className="svg-preview-shadow-group"
|
|
{...props}
|
|
>
|
|
{paths.map(({ d, c: { id } }, i) => (
|
|
<path
|
|
key={i}
|
|
mask={`url(#svg-preview-shadow-mask-${id})`}
|
|
d={d}
|
|
/>
|
|
))}
|
|
<path
|
|
d={paths
|
|
.flatMap(({ prev, next }) => [`M${prev.x} ${prev.y}h.01`, `M${next.x} ${next.y}h.01`])
|
|
.filter((val, idx, arr) => arr.indexOf(val) === idx)
|
|
.join('')}
|
|
/>
|
|
</g>
|
|
</>
|
|
);
|
|
};
|
|
|
|
const ColoredPath = ({
|
|
colors,
|
|
paths,
|
|
...props
|
|
}: { paths: Path[]; colors: string[] } & PathProps<never, 'd' | 'stroke'>) => {
|
|
let idx = 0;
|
|
return (
|
|
<g
|
|
className="svg-preview-colored-path-group"
|
|
{...props}
|
|
>
|
|
{paths.map(({ d, c }, i) => (
|
|
<path
|
|
key={i}
|
|
d={d}
|
|
stroke={colors[(c.name === 'path' ? idx++ : c.id) % colors.length]}
|
|
/>
|
|
))}
|
|
</g>
|
|
);
|
|
};
|
|
|
|
const ControlPath = ({
|
|
paths,
|
|
radius,
|
|
pointSize,
|
|
...props
|
|
}: {
|
|
pointSize: number;
|
|
paths: Path[];
|
|
radius: number;
|
|
} & PathProps<'stroke' | 'strokeWidth', 'd'>) => {
|
|
const controlPaths = paths.map((path, i) => {
|
|
const element = paths.filter((p) => p.c.id === path.c.id);
|
|
const lastElement = element.at(-1)?.next;
|
|
assert(lastElement);
|
|
const isClosed = element[0].prev.x === lastElement.x && element[0].prev.y === lastElement.y;
|
|
const showMarker = !['rect', 'circle', 'ellipse'].includes(path.c.name);
|
|
return {
|
|
...path,
|
|
showMarker,
|
|
startMarker: showMarker && path.isStart && !isClosed,
|
|
endMarker: showMarker && paths[i + 1]?.isStart !== false && !isClosed,
|
|
};
|
|
});
|
|
return (
|
|
<>
|
|
<g
|
|
className="svg-preview-control-path-marker-mask-group"
|
|
strokeWidth={pointSize}
|
|
stroke="#000"
|
|
>
|
|
{controlPaths.map(({ prev, next, showMarker }, i) => {
|
|
return (
|
|
showMarker && (
|
|
<mask
|
|
id={`svg-preview-control-path-marker-mask-${i}`}
|
|
key={i}
|
|
maskUnits="userSpaceOnUse"
|
|
>
|
|
<rect
|
|
x="0"
|
|
y="0"
|
|
width="100%"
|
|
height="100%"
|
|
fill="#fff"
|
|
stroke="none"
|
|
rx={radius}
|
|
/>
|
|
<path d={`M${prev.x} ${prev.y}h.01`} />
|
|
<path d={`M${next.x} ${next.y}h.01`} />
|
|
</mask>
|
|
)
|
|
);
|
|
})}
|
|
</g>
|
|
<g
|
|
className="svg-preview-control-path-group"
|
|
{...props}
|
|
>
|
|
{controlPaths.map(({ d, showMarker }, i) => (
|
|
<path
|
|
key={i}
|
|
mask={showMarker ? `url(#svg-preview-control-path-marker-mask-${i})` : undefined}
|
|
d={d}
|
|
/>
|
|
))}
|
|
</g>
|
|
<g
|
|
className="svg-preview-control-path-marker-group"
|
|
{...props}
|
|
>
|
|
<path
|
|
d={controlPaths
|
|
.flatMap(({ prev, next, showMarker }) =>
|
|
showMarker ? [`M${prev.x} ${prev.y}h.01`, `M${next.x} ${next.y}h.01`] : [],
|
|
)
|
|
.join('')}
|
|
/>
|
|
{controlPaths.map(({ prev, next, startMarker, endMarker }, i) => (
|
|
<React.Fragment key={i}>
|
|
{startMarker && (
|
|
<circle
|
|
cx={prev.x}
|
|
cy={prev.y}
|
|
r={pointSize / 2}
|
|
/>
|
|
)}
|
|
{endMarker && (
|
|
<circle
|
|
cx={next.x}
|
|
cy={next.y}
|
|
r={pointSize / 2}
|
|
/>
|
|
)}
|
|
</React.Fragment>
|
|
))}
|
|
</g>
|
|
</>
|
|
);
|
|
};
|
|
|
|
const Radii = ({
|
|
paths,
|
|
...props
|
|
}: { paths: Path[] } & PathProps<
|
|
'strokeWidth' | 'stroke' | 'strokeDasharray' | 'strokeOpacity',
|
|
any
|
|
>) => {
|
|
return (
|
|
<g
|
|
className="svg-preview-radii-group"
|
|
{...props}
|
|
>
|
|
{paths.map(
|
|
({ circle, next, prev, c }, i) =>
|
|
circle && (
|
|
<React.Fragment key={i}>
|
|
{circle.tangentIntersection && c.name === 'path' && (
|
|
<>
|
|
<circle
|
|
cx={next.x * 2 - circle.tangentIntersection.x}
|
|
cy={next.y * 2 - circle.tangentIntersection.y}
|
|
r={0.25}
|
|
/>
|
|
<circle
|
|
cx={prev.x * 2 - circle.tangentIntersection.x}
|
|
cy={prev.y * 2 - circle.tangentIntersection.y}
|
|
r={0.25}
|
|
/>
|
|
<path
|
|
d={`M${next.x * 2 - circle.tangentIntersection.x} ${
|
|
next.y * 2 - circle.tangentIntersection.y
|
|
}L${circle.tangentIntersection.x} ${circle.tangentIntersection.y}L${prev.x * 2 - circle.tangentIntersection.x} ${
|
|
prev.y * 2 - circle.tangentIntersection.y
|
|
}`}
|
|
/>
|
|
<circle
|
|
cx={circle.tangentIntersection.x}
|
|
cy={circle.tangentIntersection.y}
|
|
r={0.25}
|
|
/>
|
|
</>
|
|
)}
|
|
{c.name === 'path' && (
|
|
<path d={`M${next.x} ${next.y}L${circle.x} ${circle.y}L${prev.x} ${prev.y}`} />
|
|
)}
|
|
<circle
|
|
cy={circle.y}
|
|
cx={circle.x}
|
|
r={0.25}
|
|
strokeDasharray="0"
|
|
stroke={
|
|
(Math.round(circle.x * 100) / 100) % 1 !== 0 ||
|
|
(Math.round(circle.y * 100) / 100) % 1 !== 0
|
|
? 'red'
|
|
: undefined
|
|
}
|
|
/>
|
|
<circle
|
|
cy={circle.y}
|
|
cx={circle.x}
|
|
r={circle.r}
|
|
stroke={(Math.round(circle.r * 1000) / 1000) % 1 !== 0 ? 'red' : undefined}
|
|
/>
|
|
</React.Fragment>
|
|
),
|
|
)}
|
|
</g>
|
|
);
|
|
};
|
|
|
|
const Handles = ({
|
|
paths,
|
|
...props
|
|
}: { paths: Path[] } & PathProps<'strokeWidth' | 'stroke' | 'strokeOpacity', any>) => (
|
|
<g
|
|
className="svg-preview-handles-group"
|
|
{...props}
|
|
>
|
|
{paths.map(({ c, prev, next, cp1, cp2 }, i) => (
|
|
<React.Fragment key={i}>
|
|
{cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />}
|
|
{cp1 && (
|
|
<circle
|
|
cy={cp1.y}
|
|
cx={cp1.x}
|
|
r={0.25}
|
|
/>
|
|
)}
|
|
{cp2 && <path d={`M${next.x} ${next.y} ${cp2.x} ${cp2.y}`} />}
|
|
{cp2 && (
|
|
<circle
|
|
cy={cp2.y}
|
|
cx={cp2.x}
|
|
r={0.25}
|
|
/>
|
|
)}
|
|
</React.Fragment>
|
|
))}
|
|
</g>
|
|
);
|
|
|
|
interface SvgPreviewProps extends React.SVGProps<SVGSVGElement> {
|
|
height?: number;
|
|
width?: number;
|
|
src: string | ReturnType<typeof getPaths>;
|
|
showGrid?: boolean;
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
const SvgPreview = (
|
|
{ src, children, height = 24, width = 24, showGrid = false, ...props }: SvgPreviewProps,
|
|
ref,
|
|
) => {
|
|
const subGridSize =
|
|
Math.max(height, width) % 3 === 0
|
|
? Math.max(height, width) > 24
|
|
? 12
|
|
: 3
|
|
: Math.max(height, width) % 5 === 0
|
|
? 5
|
|
: 0;
|
|
const paths = typeof src === 'string' ? getPaths(src) : src;
|
|
|
|
return (
|
|
<svg
|
|
ref={ref}
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width={width}
|
|
height={height}
|
|
viewBox={`0 0 ${width} ${height}`}
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth={2}
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
{...props}
|
|
>
|
|
<style>{darkModeCss}</style>
|
|
{showGrid && (
|
|
<Grid
|
|
height={height}
|
|
width={width}
|
|
subGridSize={subGridSize}
|
|
strokeWidth={0.1}
|
|
stroke="#777"
|
|
mask="url(#svg-preview-bounding-box-mask)"
|
|
strokeOpacity={0.3}
|
|
radius={1}
|
|
/>
|
|
)}
|
|
<Shadow
|
|
paths={paths}
|
|
strokeWidth={4}
|
|
stroke="#777"
|
|
radius={1}
|
|
strokeOpacity={0.15}
|
|
/>
|
|
<GapViolationHighlight
|
|
paths={paths}
|
|
stroke="red"
|
|
strokeOpacity={0.75}
|
|
strokeWidth={4}
|
|
/>
|
|
<Handles
|
|
paths={paths}
|
|
strokeWidth={0.12}
|
|
stroke="#777"
|
|
strokeOpacity={0.6}
|
|
/>
|
|
<ColoredPath
|
|
paths={paths}
|
|
colors={['#dfdfd6']}
|
|
/>
|
|
<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}
|
|
/>
|
|
<Handles
|
|
paths={paths}
|
|
strokeWidth={0.12}
|
|
stroke="#FFF"
|
|
strokeOpacity={0.3}
|
|
/>
|
|
{children}
|
|
</svg>
|
|
);
|
|
};
|
|
|
|
SvgPreview.displayName = 'SvgPreview';
|
|
|
|
export default SvgPreview;
|