Skip to content
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

Merged
merged 6 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ jobs:

- run: yarn run install-frozen-lockfile

- run: yarn svgr

Copy link
Contributor Author

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.

- run: yarn run lint

- uses: paambaati/[email protected]
Expand Down
22 changes: 22 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The 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).

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"build": "yarn turbo run package",
"install-frozen-lockfile": "./scripts/install-frozen-lockfile.sh",
"dev-server": "yarn turbo run dev --parallel",
"postinstall": "husky install && git submodule init && git submodule update",
"postinstall": "husky install && git submodule init && git submodule update && yarn svgr",
"storybook": "yarn workspace dvc-vscode-webview storybook",
"build-storybook": "yarn turbo run build-storybook --filter=dvc-vscode-webview",
"setup:venv": "yarn turbo run lint:build && yarn workspace dvc run setup-venv",
Expand Down
28 changes: 28 additions & 0 deletions webview/icons/codicons.mjs
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'
]
82 changes: 82 additions & 0 deletions webview/icons/generate.mjs
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}`)
11 changes: 4 additions & 7 deletions webview/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/"
Copy link
Contributor

Choose a reason for hiding this comment

The 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 🤣

Copy link
Contributor Author

@sroy3 sroy3 May 12, 2023

Choose a reason for hiding this comment

The 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": {
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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
}
}
11 changes: 5 additions & 6 deletions webview/src/shared/components/icons/Add.tsx
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
6 changes: 3 additions & 3 deletions webview/src/shared/components/icons/ArrowDown.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import { SVGProps } from 'react'
const SvgArrowDown = (props: SVGProps<SVGSVGElement>) => (
import type { SVGProps } from 'react'
const ArrowDown = (props: SVGProps<SVGSVGElement>) => (
<svg
width={16}
height={16}
Expand All @@ -16,4 +16,4 @@ const SvgArrowDown = (props: SVGProps<SVGSVGElement>) => (
/>
</svg>
)
export default SvgArrowDown
export default ArrowDown
6 changes: 3 additions & 3 deletions webview/src/shared/components/icons/ArrowUp.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import { SVGProps } from 'react'
const SvgArrowUp = (props: SVGProps<SVGSVGElement>) => (
import type { SVGProps } from 'react'
const ArrowUp = (props: SVGProps<SVGSVGElement>) => (
<svg
width={16}
height={16}
Expand All @@ -16,4 +16,4 @@ const SvgArrowUp = (props: SVGProps<SVGSVGElement>) => (
/>
</svg>
)
export default SvgArrowUp
export default ArrowUp
6 changes: 3 additions & 3 deletions webview/src/shared/components/icons/Beaker.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import { SVGProps } from 'react'
const SvgBeaker = (props: SVGProps<SVGSVGElement>) => (
import type { SVGProps } from 'react'
const Beaker = (props: SVGProps<SVGSVGElement>) => (
<svg
width={16}
height={16}
Expand All @@ -12,4 +12,4 @@ const SvgBeaker = (props: SVGProps<SVGSVGElement>) => (
<path d="M13.893 13.558L10 6.006v-4h1v-1H9.994V1l-.456.005H5V2h1v3.952l-3.894 7.609A1 1 0 0 0 3 15.006h10a1 1 0 0 0 .893-1.448zm-7-7.15L7 6.193V2.036l2-.024v4.237l.11.215 1.827 3.542H5.049l1.844-3.598zM3 14.017l1.54-3.011h6.916l1.547 3L3 14.017z" />
</svg>
)
export default SvgBeaker
export default Beaker
6 changes: 3 additions & 3 deletions webview/src/shared/components/icons/Check.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import { SVGProps } from 'react'
const SvgCheck = (props: SVGProps<SVGSVGElement>) => (
import type { SVGProps } from 'react'
const Check = (props: SVGProps<SVGSVGElement>) => (
<svg
width={16}
height={16}
Expand All @@ -16,4 +16,4 @@ const SvgCheck = (props: SVGProps<SVGSVGElement>) => (
/>
</svg>
)
export default SvgCheck
export default Check
6 changes: 3 additions & 3 deletions webview/src/shared/components/icons/ChevronDown.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import { SVGProps } from 'react'
const SvgChevronDown = (props: SVGProps<SVGSVGElement>) => (
import type { SVGProps } from 'react'
const ChevronDown = (props: SVGProps<SVGSVGElement>) => (
<svg
width={16}
height={16}
Expand All @@ -16,4 +16,4 @@ const SvgChevronDown = (props: SVGProps<SVGSVGElement>) => (
/>
</svg>
)
export default SvgChevronDown
export default ChevronDown
6 changes: 3 additions & 3 deletions webview/src/shared/components/icons/ChevronRight.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import { SVGProps } from 'react'
const SvgChevronRight = (props: SVGProps<SVGSVGElement>) => (
import type { SVGProps } from 'react'
const ChevronRight = (props: SVGProps<SVGSVGElement>) => (
<svg
sroy3 marked this conversation as resolved.
Show resolved Hide resolved
width={16}
height={16}
Expand All @@ -16,4 +16,4 @@ const SvgChevronRight = (props: SVGProps<SVGSVGElement>) => (
/>
</svg>
)
export default SvgChevronRight
export default ChevronRight
39 changes: 19 additions & 20 deletions webview/src/shared/components/icons/Clock.tsx
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
8 changes: 3 additions & 5 deletions webview/src/shared/components/icons/Close.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from 'react'
import { SVGProps } from 'react'

const SvgClose = (props: SVGProps<SVGSVGElement>) => (
import type { SVGProps } from 'react'
const Close = (props: SVGProps<SVGSVGElement>) => (
<svg
width={16}
height={16}
Expand All @@ -17,5 +16,4 @@ const SvgClose = (props: SVGProps<SVGSVGElement>) => (
/>
</svg>
)

export default SvgClose
export default Close
6 changes: 3 additions & 3 deletions webview/src/shared/components/icons/Copy.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import { SVGProps } from 'react'
const SvgCopy = (props: SVGProps<SVGSVGElement>) => (
import type { SVGProps } from 'react'
const Copy = (props: SVGProps<SVGSVGElement>) => (
<svg
width={16}
height={16}
Expand All @@ -21,4 +21,4 @@ const SvgCopy = (props: SVGProps<SVGSVGElement>) => (
/>
</svg>
)
export default SvgCopy
export default Copy
8 changes: 3 additions & 5 deletions webview/src/shared/components/icons/Ellipsis.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from 'react'
import { SVGProps } from 'react'

const SvgEllipsis = (props: SVGProps<SVGSVGElement>) => (
import type { SVGProps } from 'react'
const Ellipsis = (props: SVGProps<SVGSVGElement>) => (
<svg
width={16}
height={16}
Expand All @@ -13,5 +12,4 @@ const SvgEllipsis = (props: SVGProps<SVGSVGElement>) => (
<path d="M4 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm5 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm5 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0z" />
</svg>
)

export default SvgEllipsis
export default Ellipsis
Loading