-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Read and transform svg icons from list #3872
Changes from all commits
0b675bb
9131f25
ef5a367
3e4f829
9df8747
7381e88
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,6 +38,8 @@ jobs: | |
|
||
- run: yarn run install-frozen-lockfile | ||
|
||
- run: yarn svgr | ||
|
||
- run: yarn run lint | ||
|
||
- uses: paambaati/[email protected] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -93,3 +93,25 @@ There are some discrepancies between the Storybook environment and the | |
environment of a real VS Code extension, custom themes being a big one. Always | ||
make sure to try out changed components in the full dev environment before | ||
merging! | ||
|
||
## Adding and using icons (SVGs) | ||
|
||
Before adding one or more icons, check the available icons in | ||
`webview/src/shared/component/icons`. You can also start Storybook and verify | ||
the icons' story to see all the icons at once. Try to pick one that's already | ||
available before adding one. | ||
|
||
If none of the icons fit the purpose, you can select one from | ||
[VS Code codicon](https://microsoft.github.io/vscode-codicons/dist/codicon.html). | ||
Once you know which icon you want to add, copy its name and add it to | ||
`webview/icons/codicons.mjs` in the `codicons` constant. If, by some unfortunate | ||
circumstance, none of the codicon meets your needs, you can add a custom SVG to | ||
the `webview/icons` directory. | ||
|
||
If you have added any icon (custom or from codicon), you will have to run | ||
`yarn svgr` or `yarn install` to ensure the icon is now available in the | ||
codebase. | ||
|
||
To use an icon, import it from `webview/src/shared/components/icons` (from the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need this last paragraph about importing? I feel like it would be obvious how to import and render an Icon by looking at the codebase (it definitely was for me when I needed an icon for the first time). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It depends where you import it. If there are no example in the file you're trying to import the icon from, looking at our other imports, you might go directly for the file. I know I've made the comment more than once in PRs. It's mostly that it goes against how we import in general (using an index file). |
||
index file, not the file itself) and use it with the `<Icon />` component | ||
filling its `icon` prop with your imported icon. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
export const codicons = [ | ||
'add', | ||
'arrow-up', | ||
'arrow-down', | ||
'beaker', | ||
'check', | ||
'chevron-down', | ||
'chevron-right', | ||
'close', | ||
'copy', | ||
'ellipsis', | ||
'error', | ||
'filter', | ||
'git-commit', | ||
'git-merge', | ||
'graph-line', | ||
'graph-scatter', | ||
'gripper', | ||
'info', | ||
'list-filter', | ||
'pass-filled', | ||
'pinned', | ||
'refresh', | ||
'sort-precedence', | ||
'star-empty', | ||
'star-full', | ||
'trash' | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { transform } from '@svgr/core' | ||
import { | ||
appendFile, | ||
mkdir, | ||
readdir, | ||
readFile, | ||
rm, | ||
writeFile | ||
} from 'node:fs/promises' | ||
import path from 'path' | ||
import { codicons } from './codicons.mjs' | ||
|
||
const iconsPath = 'src/shared/components/icons/' | ||
|
||
try { | ||
await rm(iconsPath, { recursive: true, force: true }) | ||
await mkdir(iconsPath) | ||
} catch (err) { | ||
console.error(err.message) | ||
} | ||
|
||
const customIcons = [] | ||
try { | ||
const files = await readdir('icons') | ||
for (const file of files) { | ||
if (path.extname(file) === '.svg') { | ||
customIcons.push(`icons/${file}`) | ||
} | ||
} | ||
} catch (err) { | ||
console.error(err.message) | ||
} | ||
|
||
const codiconsPath = '../node_modules/@vscode/codicons/src/icons' | ||
|
||
const allIcons = [ | ||
...customIcons, | ||
...codicons.map(codicon => `${codiconsPath}/${codicon}.svg`) | ||
] | ||
|
||
function toPascalCase(text) { | ||
return text.replace(/(^\w|-\w)/g, clearAndUpper) | ||
} | ||
|
||
function clearAndUpper(text) { | ||
return text.replace(/-/, '').toUpperCase() | ||
} | ||
|
||
const components = [] | ||
await Promise.all( | ||
allIcons.map(async icon => { | ||
try { | ||
const iconContent = await readFile(icon, { encoding: 'utf8' }) | ||
const componentName = toPascalCase(path.basename(icon).split('.')[0]) | ||
const svgComponent = await transform( | ||
iconContent, | ||
{ | ||
typescript: true, | ||
plugins: ['@svgr/plugin-jsx', '@svgr/plugin-prettier'] | ||
}, | ||
{ componentName } | ||
) | ||
await writeFile(`${iconsPath}${componentName}.tsx`, svgComponent) | ||
components.push(componentName) | ||
} catch (err) { | ||
console.error(err.message) | ||
} | ||
}) | ||
) | ||
|
||
await appendFile( | ||
`${iconsPath}index.ts`, | ||
components | ||
.sort() | ||
.map( | ||
componentName => | ||
`export { default as ${componentName} } from './${componentName}'\n` | ||
) | ||
.join('') | ||
) | ||
|
||
console.log(`Icons were generated to ${iconsPath}`) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,7 @@ | |
"test": "jest --collect-coverage", | ||
"storybook": "storybook dev -p 6006", | ||
"build-storybook": "storybook build --webpack-stats-json", | ||
"svgr": "svgr --out-dir src/shared/components/icons --ignore-existing ../node_modules/@vscode/codicons/src/icons && svgr -d src/shared/components/icons icons/" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is my first time actually seeing this command! Looking at it, I'm assuming we used this to generate chosen icon components? I've just been creating them manually 🤣 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that is why I also added steps in the contributing guide. To be honest, I've been creating them manually as well for a while too because the process was broken. Running the command would generate every codicon, you would then have to remove all the ones you don't need. |
||
"svgr": "node --experimental-modules icons/generate.mjs" | ||
}, | ||
"main": "./index.js", | ||
"peerDependencies": { | ||
|
@@ -48,7 +48,9 @@ | |
"@storybook/react": "7.0.8", | ||
"@storybook/react-webpack5": "7.0.8", | ||
"@storybook/testing-library": "0.1.0", | ||
"@svgr/cli": "7.0.0", | ||
"@svgr/core": "^8.0.0", | ||
"@svgr/plugin-jsx": "^8.0.1", | ||
"@svgr/plugin-prettier": "^8.0.1", | ||
"@swc/core": "1.3.56", | ||
"@swc/jest": "0.2.26", | ||
"@testing-library/jest-dom": "5.16.5", | ||
|
@@ -81,10 +83,5 @@ | |
"webpack": "5.82.0", | ||
"webpack-cli": "5.0.2", | ||
"webpack-dev-server": "4.13.3" | ||
}, | ||
"svgr": { | ||
"typescript": true, | ||
"dimensions": false, | ||
"ignoreExisting": true | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,15 @@ | ||
import * as React from 'react' | ||
import { SVGProps } from 'react' | ||
|
||
const SvgAdd = (props: SVGProps<SVGSVGElement>) => ( | ||
import type { SVGProps } from 'react' | ||
const Add = (props: SVGProps<SVGSVGElement>) => ( | ||
<svg | ||
width={16} | ||
height={16} | ||
viewBox="0 0 16 16" | ||
xmlns="http://www.w3.org/2000/svg" | ||
fill="currentColor" | ||
{...props} | ||
> | ||
<path d="M14 7v1H8v6H7V8H1V7h6V1h1v6h6z" fill="currentColor" /> | ||
<path d="M14 7v1H8v6H7V8H1V7h6V1h1v6h6z" /> | ||
</svg> | ||
) | ||
|
||
export default SvgAdd | ||
export default Add |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,20 @@ | ||
import * as React from 'react' | ||
|
||
function SvgClock(props: React.SVGProps<SVGSVGElement>) { | ||
return ( | ||
<svg | ||
viewBox="0 0 16 16" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
{...props} | ||
> | ||
<path | ||
fillRule="evenodd" | ||
clipRule="evenodd" | ||
d="M14 8A6 6 0 112 8a6 6 0 0112 0zm1 0A7 7 0 111 8a7 7 0 0114 0zM7 4v5.5h3v-1H8V4H7z" | ||
fill="currentColor" | ||
/> | ||
</svg> | ||
) | ||
} | ||
|
||
export default SvgClock | ||
import type { SVGProps } from 'react' | ||
const Clock = (props: SVGProps<SVGSVGElement>) => ( | ||
<svg | ||
width={16} | ||
height={16} | ||
viewBox="0 0 16 16" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
{...props} | ||
> | ||
<path | ||
fillRule="evenodd" | ||
clipRule="evenodd" | ||
d="M14 8C14 11.3137 11.3137 14 8 14C4.68629 14 2 11.3137 2 8C2 4.68629 4.68629 2 8 2C11.3137 2 14 4.68629 14 8ZM15 8C15 11.866 11.866 15 8 15C4.13401 15 1 11.866 1 8C1 4.13401 4.13401 1 8 1C11.866 1 15 4.13401 15 8ZM7 4V9V9.5H7.5H10V8.5H8V4H7Z" | ||
fill="currentColor" | ||
/> | ||
</svg> | ||
) | ||
export default Clock |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sure no one manually creates or edits icons.