Skip to content

Commit

Permalink
Added github comment icon workflow (#946)
Browse files Browse the repository at this point in the history
* feat: added github comment icon workflow

* feat: improved segment node styling

* feat: improved segment node styling

* fix: fixed grid alignment issue

* chore: cleanup

* fix: added ref forwarding to SvgPreview component

* chore: removed svg preview from icon detail overlay

* chore: updated tj-actions/changed-files

* chore: switched to pull_request_target

* chore: simplified path segment highlighting logic

* Fixes incorrect relative links in documentation pages (#973)

* Fixes incorrect relative links in documentation pages

* Unifies documentation page names to avoid 404 links

---------

Co-authored-by: Karsa <[email protected]>

* Add forklift icon (#943)

* Fix vercel build

* Add forklift icon

Co-Authored-By: willythewizard <[email protected]>

---------

Co-authored-by: willythewizard <[email protected]>

* adds utility-pole icon (#971)

Co-authored-by: Karsa <[email protected]>

* 📦 Bump lucide package versions to 0.123.0

* Adds `nfc` icons (#960)

* added nfc icons

* fixes smartphone-nfc

* Update icons/nfc.svg

Co-authored-by: Jakob Guddas <[email protected]>

* Update icons/smartphone-nfc.svg

Co-authored-by: Jakob Guddas <[email protected]>

---------

Co-authored-by: Karsa <[email protected]>
Co-authored-by: Jakob Guddas <[email protected]>

* 📦 Bump lucide package versions to 0.124.0

* fix: updated pnpm-lock.yaml

* fix: added missing api endpoint file

* chore: fixed nextjs path name parsing in production

* chore: only run workflow when path includes icons/*.svg

* chore: added Cache-Control header to gh-icon api route response

* feat: added dark mode support to gh-icon

* feat: switched to using picture tag for gh-icon

* feat: added space between gh-icons in pr comment

* fix: changed icon size base back to 24x24

* feat: added title to gh-icon comment image

* fix: changed gh-icon url

* chore: added groups with class names

* feat: improved shadow masking

* Removes need for building duplicate icons by supporting CSS based dark mode

* chore: resolved type issues

* feat: changed image width from 48% to 400px

---------

Co-authored-by: Karsa <[email protected]>
Co-authored-by: Karsa <[email protected]>
Co-authored-by: Eric Fennis <[email protected]>
Co-authored-by: willythewizard <[email protected]>
Co-authored-by: Lucide Bot <[email protected]>
  • Loading branch information
6 people authored Apr 3, 2023
1 parent 76ce22e commit 93cfd3d
Show file tree
Hide file tree
Showing 8 changed files with 523 additions and 2 deletions.
55 changes: 55 additions & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Pull request comment

on:
pull_request_target:
paths:
- 'icons/*.svg'

permissions:
pull-requests: write
contents: write

jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
ref: refs/pull/${{ github.event.pull_request.number }}/merge
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v35
with:
files: icons/*.svg
- name: Generate comment
id: generate-comment
run: |
delimiter="$(openssl rand -hex 8)"
echo "body<<$delimiter" >> $GITHUB_OUTPUT
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
cat "$file" | # get file content
tr '\n' ' ' | # remove line breaks
sed -e 's/<svg[^>]*>/<svg>/g' | # remove attributes from svg element
base64 -w 0 | # encode svg
sed "s|.*|<img width=\"400\" title=\"$file\" alt=\"$file\" src=\"https://lucide.dev/api/gh-icon/&.svg\"/> |"
done | tr '\n' ' ' >> $GITHUB_OUTPUT
echo >> $GITHUB_OUTPUT
echo "$delimiter" >> $GITHUB_OUTPUT
- name: Find Comment
uses: peter-evans/find-comment@v2
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: Added or changed icons
- name: Create or update comment
uses: peter-evans/create-or-update-comment@v2
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
Added or changed icons
${{ steps.generate-comment.outputs.body }}
edit-mode: replace
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@next/mdx": "^11.0.0",
"@svgr/webpack": "^6.3.1",
"downloadjs": "^1.4.7",
"element-to-path": "^1.2.1",
"framer-motion": "^4",
"fuse.js": "^6.5.3",
"gray-matter": "^4.0.3",
Expand All @@ -38,7 +39,8 @@
"react-color": "^2.19.3",
"react-dom": "17.0.2",
"react-svg-loader": "^3.0.3",
"svgson": "^5.2.1"
"svgson": "^5.2.1",
"svg-pathdata": "^6.0.3"
},
"devDependencies": {
"@next/eslint-plugin-next": "^12.2.5",
Expand Down
2 changes: 1 addition & 1 deletion site/src/components/IconWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ interface IconWrapperProps extends SVGProps<SVGSVGElement> {
}

export const IconWrapper = forwardRef<SVGSVGElement, IconWrapperProps>((props, ref) => {
const defaultAttrs : SVGProps<SVGSVGElement>= {
const defaultAttrs: SVGProps<SVGSVGElement> = {
xmlns: 'http://www.w3.org/2000/svg',
width: '24px',
height: '24px',
Expand Down
225 changes: 225 additions & 0 deletions site/src/components/SvgPreview/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import React from 'react';
import { PathProps, Path } from './types';
import { getPaths, assert } from './utils';

const Grid = ({
radius,
fill,
...props
}: {
strokeWidth: number;
radius: number;
} & PathProps<'stroke', 'strokeWidth'>) => (
<g className="svg-preview-grid-group" strokeLinecap="butt" {...props}>
<rect
width={24 - props.strokeWidth}
height={24 - props.strokeWidth}
x={props.strokeWidth / 2}
y={props.strokeWidth / 2}
rx={radius}
fill={fill}
/>
<path
d={
props.d ||
new Array(Math.floor(24 - 1))
.fill(null)
.flatMap((_, i) => [
`M${props.strokeWidth} ${i + 1}h${24 - props.strokeWidth * 2}`,
`M${i + 1} ${props.strokeWidth}v${24 - 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
id={`svg-preview-shadow-mask-${id}`}
maskUnits="userSpaceOnUse"
strokeOpacity="1"
strokeWidth={props.strokeWidth}
stroke="#000"
>
<rect x={0} y={0} width={24} height={24} 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'>) => (
<g className="svg-preview-colored-path-group" {...props}>
{paths.map(({ d, c }, i) => (
<path key={i} d={d} stroke={colors[(c.name === 'path' ? i : 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="24" height="24" 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(({ d, 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 SvgPreview = React.forwardRef<SVGSVGElement, { src: string; showGrid?: boolean }>(
({ src, showGrid = false }, ref) => {
const paths = getPaths(src);
const darkModeCss = `@media screen and (prefers-color-scheme: dark) {
.svg-preview-grid-group,
.svg-preview-shadow-mask-group,
.svg-preview-shadow-group {
stroke: #fff;
}
}`;
return (
<svg
ref={ref}
xmlns="http://www.w3.org/2000/svg"
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
>
<style>{darkModeCss}</style>
{showGrid && <Grid strokeWidth={0.1} stroke="#777" strokeOpacity={0.3} radius={1} />}
<Shadow paths={paths} strokeWidth={4} stroke="#777" radius={1} strokeOpacity={0.15} />
<ColoredPath
paths={paths}
colors={[
'#1982c4',
'#4267AC',
'#6a4c93',
'#B55379',
'#FF595E',
'#FF7655',
'#ff924c',
'#FFAE43',
'#ffca3a',
'#C5CA30',
'#8ac926',
'#52A675',
]}
/>
<ControlPath radius={1} paths={paths} pointSize={1} stroke="#fff" strokeWidth={0.125} />
</svg>
);
}
);

export default SvgPreview;
21 changes: 21 additions & 0 deletions site/src/components/SvgPreview/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { SVGProps } from 'react';
import { getCommands } from './utils';

export type Point = { x: number; y: number };

export type Path = {
d: string;
prev: Point;
next: Point;
isStart: boolean;
c: ReturnType<typeof getCommands>[number];
};

export type PathProps<
RequiredProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
NeverProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>
> = Required<Pick<React.SVGProps<SVGElement & SVGRectElement & SVGCircleElement>, RequiredProps>> &
Omit<
React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>,
RequiredProps & NeverProps
>;
Loading

0 comments on commit 93cfd3d

Please sign in to comment.