From 28248dc5cc13566509378c050b70f4ce4839a228 Mon Sep 17 00:00:00 2001 From: Helene Kassandra <39946146+HeleneKassandra@users.noreply.github.com> Date: Tue, 5 Dec 2023 18:49:40 +0100 Subject: [PATCH 1/4] feat(ffe-icons): henter og lagrer alle relevante varianter av material symbols i svg BREAKING CHANGE: sletter alle gamle svg'er --- packages/ffe-icons/bin/build.js | 106 ++++++++---------- packages/ffe-icons/bin/deleteSvg.js | 49 ++++++++ packages/ffe-icons/bin/downloadSvgs.js | 78 +++++++++++++ packages/ffe-icons/bin/getIconNames.js | 18 +++ packages/ffe-icons/bin/utils.js | 53 +++++++++ .../icons/300/filled/sm/battery_90.svg | 1 + .../icons/300/open/sm/battery_90.svg | 1 + packages/ffe-icons/less/ffe-icons.less | 2 + packages/ffe-icons/less/icons.less | 26 +++++ packages/ffe-icons/less/theme.less | 14 +++ packages/ffe-icons/package.json | 6 +- 11 files changed, 294 insertions(+), 60 deletions(-) create mode 100644 packages/ffe-icons/bin/deleteSvg.js create mode 100644 packages/ffe-icons/bin/downloadSvgs.js create mode 100644 packages/ffe-icons/bin/getIconNames.js create mode 100644 packages/ffe-icons/bin/utils.js create mode 100644 packages/ffe-icons/icons/300/filled/sm/battery_90.svg create mode 100644 packages/ffe-icons/icons/300/open/sm/battery_90.svg create mode 100644 packages/ffe-icons/less/ffe-icons.less create mode 100644 packages/ffe-icons/less/icons.less create mode 100644 packages/ffe-icons/less/theme.less diff --git a/packages/ffe-icons/bin/build.js b/packages/ffe-icons/bin/build.js index 5c14b1b217..7f742632a0 100755 --- a/packages/ffe-icons/bin/build.js +++ b/packages/ffe-icons/bin/build.js @@ -1,60 +1,48 @@ -#!/usr/bin/env node -'use strict'; // eslint-disable-line strict - -const fs = require('fs'); const path = require('path'); -const svgstore = require('svgstore'); -const mkdirp = require('mkdirp'); - -const ICONS_PATH = path.join(__dirname, '..', 'icons'); - -// convenience to avoid having file extension in config -const appendSvgExtension = icons => - icons.map(name => (name.endsWith('.svg') ? name : `${name}.svg`)); - -const options = require('yargs') - .config('opts') - .options({ - icons: { - default: '**/*.svg', - type: 'array', - coerce: appendSvgExtension, - }, - projectIcons: { - type: 'array', - coerce: appendSvgExtension, - }, - dest: { - default: 'dist', - normalize: true, - coerce: path.resolve, - }, - }).argv; - -const matchesIcon = icons => - icons.includes('*.svg') || icons.includes('**/*.svg') - ? () => true - : fileName => icons.includes(path.basename(fileName)); - -const sprite = svgstore(); - -fs.readdirSync(ICONS_PATH) - .filter(fileName => fileName.match(/\.svg$/)) - .filter(matchesIcon(options.icons)) - .forEach(fileName => { - const iconPath = path.join(ICONS_PATH, fileName); - const iconName = path.basename(fileName, '.svg'); - sprite.add(iconName, fs.readFileSync(iconPath), 'utf-8'); - }); - -if (options.projectIcons) { - options.projectIcons.forEach(fileName => { - const iconPath = path.join(fileName); - const iconName = path.basename(fileName, '.svg'); - sprite.add(iconName, fs.readFileSync(iconPath), 'utf-8'); - }); -} - -mkdirp.sync(options.dest); - -fs.writeFileSync(path.join(options.dest, 'ffe-icons.svg'), sprite.toString()); +const fs = require('fs'); +const { makedirs } = require('./utils'); +const { getIconNames } = require('./getIconNames'); +const { getDownloads, downloadAll } = require('./downloadSvgs'); +const { + createListOfRemovedIcons, + deleteRemovedIconsFiles, +} = require('./deleteSvg'); + +(async () => { + const weights = [300, 500]; + const sizes = [ + { name: 'sm', opsz: 20 }, + { name: 'md', opsz: 24 }, + { name: 'lg', opsz: 40 }, + { name: 'xl', opsz: 48 }, + ]; + const fill = [0, 1]; + + const iconNames = await getIconNames(); + const listOfRemovedIcons = await createListOfRemovedIcons(iconNames); + let downloads = []; + + for (const weight of weights) { + for (const fillValue of fill) { + const type = fillValue === 0 ? 'filled' : 'open'; + for (const size of sizes) { + const dirPath = path.resolve( + __dirname, + `../icons/${weight}/${type}/${size.name}`, + ); + if (!fs.existsSync(dirPath)) { + await makedirs(dirPath); + } + if (listOfRemovedIcons.length > 0) { + await deleteRemovedIconsFiles(listOfRemovedIcons, dirPath); + } + downloads = downloads.concat( + getDownloads(iconNames, weight, fillValue, size, dirPath), + ); + } + } + } + console.log('Downloading SVG files...'); + await downloadAll(downloads); + console.log('All done!'); +})(); diff --git a/packages/ffe-icons/bin/deleteSvg.js b/packages/ffe-icons/bin/deleteSvg.js new file mode 100644 index 0000000000..c2b1fe8981 --- /dev/null +++ b/packages/ffe-icons/bin/deleteSvg.js @@ -0,0 +1,49 @@ +const fs = require('fs/promises'); +const path = require('path'); + +/* Function: createListOfRemovedIcons + Creates and returns an array of all the filenames of svg-files that exist, + but are no longer mentioned in the Material Symbols Codepoints. + + Since we know all the different subfolder / variations of the icons contain the same iconnames, + we only need to check 1 folder. +*/ +const createListOfRemovedIcons = async iconNames => { + const directory = path.resolve(__dirname, '../icons/300/filled/lg'); + try { + await fs.access(directory); + const filesInDir = await fs.readdir(directory); + const removedIcons = filesInDir.filter( + fileName => !iconNames.includes(fileName.replace('.svg', '')), + ); + return removedIcons; + } catch (err) { + console.log('Directory does not exist in check for removed icons'); + return []; + } +}; + +/* Function: deleteSvgFile + Does the actual deleting of the file + */ +const deleteSvgFile = async fileName => { + try { + await fs.unlink(fileName); + console.log(`Deleted file ${fileName}`); + } catch (err) { + console.error(`Failed to delete file ${fileName}: ${err}`); + } +}; + +/* Function: deleteRemovedIconsFiles + Loop through the list of fileNames that should be deleted in a specific directory + and call the delete function. +*/ +const deleteRemovedIconsFiles = async (listOfRemovedIcons, directory) => { + for (const fileName of listOfRemovedIcons) { + const filePath = path.join(directory, fileName); + await deleteSvgFile(filePath); + } +}; + +module.exports = { createListOfRemovedIcons, deleteRemovedIconsFiles }; diff --git a/packages/ffe-icons/bin/downloadSvgs.js b/packages/ffe-icons/bin/downloadSvgs.js new file mode 100644 index 0000000000..5ebcb3af37 --- /dev/null +++ b/packages/ffe-icons/bin/downloadSvgs.js @@ -0,0 +1,78 @@ +const { apply, fileExists } = require('./utils'); +const path = require('path'); +const fs = require('fs/promises'); + +/* Function: generateDownloadUrl + Takes the icon name, fill, weight and size, and generate the url to download the svg from. +*/ +const generateDownloadUrl = (iconName, fill, weight, size) => { + let style = `wght${weight}${fill === 0 ? '' : 'fill1'}`; + if (fill === 0 && weight === 400) { + style = 'default'; // Can technically be removed since we're not supporting the standard value of 400 font weight. But keeping it in to make sure nothing break if we decide to add it later + } + return `https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsrounded/${iconName}/${style}/${size}px.svg`; +}; + +/* Function: getDownloads + Returns an array for each variation of the icons with the download url, filename and filepath. + The array is later used to know what to download and where to save the files. +*/ +const getDownloads = (iconNames, weight, fill, size, dirPath) => { + const downloads = []; + if (!iconNames || !weight || !size || fill === undefined) { + throw new Error('iconNames, weight, fill or size is not provided'); + } + for (const icon of iconNames) { + const safeIconName = icon; // Fix for icons that has a number as the first character - which is not valid const name + downloads.push({ + url: generateDownloadUrl(safeIconName, fill, weight, size.opsz), + fileName: `${icon}.svg`, + filePath: dirPath, + }); + } + return downloads; +}; + +/* Function: download + Does the actual downloading of the file + */ +const download = async downloadElement => { + const { url, fileName, filePath } = downloadElement; + const fileLocation = path.resolve(__dirname, `${filePath}/${fileName}`); + try { + console.log(`Downloading ${fileLocation}`); + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.text(); + await fs.writeFile(fileLocation, data); + } catch (error) { + console.error( + `Failed to download file from ${url} to ${fileLocation}. Error: ${error.message}`, + ); + } +}; + +/* Function: downloadAll + Takes all the downloads, and apply the download function to each of them. + Also let you set if you want to ignore existing files, this is on by default. +*/ +const downloadAll = async (downloads, { ignoreExisting = true } = {}) => { + let allDownloads = []; + if (ignoreExisting) { + for (const file of downloads) { + const fileAlreadyExists = await fileExists( + `${file.filePath}/${file.fileName}`, + ); + if (!fileAlreadyExists) { + allDownloads.push(file); + } + } + } else { + allDownloads = downloads; + } + await apply(download, allDownloads); +}; + +module.exports = { getDownloads, downloadAll }; diff --git a/packages/ffe-icons/bin/getIconNames.js b/packages/ffe-icons/bin/getIconNames.js new file mode 100644 index 0000000000..be2a0ed82a --- /dev/null +++ b/packages/ffe-icons/bin/getIconNames.js @@ -0,0 +1,18 @@ +/* Fetches all the icon names by using the codepoints in the font */ +const getIconNames = async () => { + let iconNames = []; + const url = + 'https://raw.githubusercontent.com/google/material-design-icons/master/variablefont/MaterialSymbolsRounded%5BFILL%2CGRAD%2Copsz%2Cwght%5D.codepoints'; + const response = await fetch(url); + const data = await response.text(); + + const lines = data.split('\n'); + const names = lines + .filter(line => line.trim() !== '') + .map(line => `${line.split(' ')[0]}`); + iconNames = [...iconNames, ...names]; + + return iconNames; +}; + +module.exports = { getIconNames }; diff --git a/packages/ffe-icons/bin/utils.js b/packages/ffe-icons/bin/utils.js new file mode 100644 index 0000000000..ffcd7c688c --- /dev/null +++ b/packages/ffe-icons/bin/utils.js @@ -0,0 +1,53 @@ +const fs = require('fs/promises'); + +/* Create directory */ +const makedirs = async dir => { + try { + await fs.mkdir(dir, { recursive: true }); + console.log(`Created ${dir}`); + } catch (err) { + throw new Error(`Failed to create directory ${dir}: ${err}`); + } +}; + +/* Function: apply + Utility function to run an async function on an array of arguments in parallel with a concurrency limit. + Used to download multiple files at once, but with a concurrency limit to avoid overloading the server. + Arguments: + - func: async function to run (download) + - args: array of arguments to pass to the function (array of download objects) +*/ +const apply = async (func, args) => { + const concurrency = 8; + const results = []; + let i = 0; + const next = async () => { + const j = i++; + if (j >= args.length) { + return; + } + try { + results[j] = await func(args[j]); + } catch (error) { + console.error(error); + } + await next(); + }; + await Promise.all(Array.from({ length: concurrency }, next)); + return results; +}; + +/* Function: fileExists + Checks if a file exists at the given path +*/ +const fileExists = async filePath => { + try { + await fs.access(filePath); + return true; + } catch (error) { + // The file doesn't exist or there was an error accessing it + return false; + } +}; + +module.exports = { makedirs, apply, fileExists }; diff --git a/packages/ffe-icons/icons/300/filled/sm/battery_90.svg b/packages/ffe-icons/icons/300/filled/sm/battery_90.svg new file mode 100644 index 0000000000..2f88955380 --- /dev/null +++ b/packages/ffe-icons/icons/300/filled/sm/battery_90.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ffe-icons/icons/300/open/sm/battery_90.svg b/packages/ffe-icons/icons/300/open/sm/battery_90.svg new file mode 100644 index 0000000000..2f88955380 --- /dev/null +++ b/packages/ffe-icons/icons/300/open/sm/battery_90.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ffe-icons/less/ffe-icons.less b/packages/ffe-icons/less/ffe-icons.less new file mode 100644 index 0000000000..660478faf5 --- /dev/null +++ b/packages/ffe-icons/less/ffe-icons.less @@ -0,0 +1,2 @@ +@import 'theme'; +@import 'icons'; diff --git a/packages/ffe-icons/less/icons.less b/packages/ffe-icons/less/icons.less new file mode 100644 index 0000000000..ac8e5d1253 --- /dev/null +++ b/packages/ffe-icons/less/icons.less @@ -0,0 +1,26 @@ +.ffe-icons { + color: var(--ffe-v-icons-default-color); + mask-repeat: no-repeat; + background-color: currentColor; + display: inline-block; + + &--sm { + height: var(--ffe-v-icons-size-sm); + width: var(--ffe-v-icons-size-sm); + } + + &--md { + height: var(--ffe-v-icons-size-md); + width: var(--ffe-v-icons-size-md); + } + + &--lg { + height: var(--ffe-v-icons-size-lg); + width: var(--ffe-v-icons-size-lg); + } + + &--xl { + height: var(--ffe-v-icons-size-xl); + width: var(--ffe-v-icons-size-xl); + } +} diff --git a/packages/ffe-icons/less/theme.less b/packages/ffe-icons/less/theme.less new file mode 100644 index 0000000000..074ed403d1 --- /dev/null +++ b/packages/ffe-icons/less/theme.less @@ -0,0 +1,14 @@ +:root, +:host { + --ffe-v-icons-size-sm: 20px; + --ffe-v-icons-size-md: 24px; + --ffe-v-icons-size-lg: 40px; + --ffe-v-icons-size-xl: 48px; + --ffe-v-icons-default-color: var(--ffe-farge-vann); + + .native & { + @media (prefers-color-scheme: dark) { + --ffe-v-icons-default-color: var(--ffe-farge-vann-70); + } + } +} diff --git a/packages/ffe-icons/package.json b/packages/ffe-icons/package.json index 256ee3dca3..73723771ec 100644 --- a/packages/ffe-icons/package.json +++ b/packages/ffe-icons/package.json @@ -7,12 +7,16 @@ "bin": { "ffe-icons": "bin/build.js" }, + "files": [ + "less" + ], "repository": { "type": "git", "url": "ssh://git@github.com:SpareBank1/designsystem.git" }, "scripts": { - "build": "node bin/build.js", + "build:": "", + "build:icons": "node bin/build.js", "lint": "eslint bin", "test": "npm run lint" }, From 16bd6da5183267b9f5491e9ee513f99caeb30222 Mon Sep 17 00:00:00 2001 From: Helene Kassandra <39946146+HeleneKassandra@users.noreply.github.com> Date: Sat, 9 Dec 2023 00:57:53 +0100 Subject: [PATCH 2/4] chore: legg til workflow for oppdatering av ffe-icons svger --- .github/workflows/update-ffe-icons.yml | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/update-ffe-icons.yml diff --git a/.github/workflows/update-ffe-icons.yml b/.github/workflows/update-ffe-icons.yml new file mode 100644 index 0000000000..ee618a0427 --- /dev/null +++ b/.github/workflows/update-ffe-icons.yml @@ -0,0 +1,33 @@ +name: update ffe-icons svgs + +on: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: 18 + + - name: Install dependencies + run: npm ci + + - name: Build icons + run: npm run build:icons + working-directory: packages/ffe-icons + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: 'fix(ffe-icons): oppdater ikon SVGer' + title: 'feat(ffe-icons): oppdater ikon SVGer' + body: "Oppdaterer SVG'ene i ffe-icons til å passe med de som er tilgjengelige i Material Symbols" + branch: 'auto-update-icons' From 795b666007b86b28e06d0554db2a5fde745f3d5e Mon Sep 17 00:00:00 2001 From: Helene Kassandra <39946146+HeleneKassandra@users.noreply.github.com> Date: Mon, 18 Dec 2023 15:26:52 +0100 Subject: [PATCH 3/4] feat(component-overview): legg til icon eksempler --- component-overview/examples/icons/Icon-ariahidden.jsx | 7 +++++++ component-overview/examples/icons/Icon.jsx | 6 ++++++ .../messages/message-box/InfoMessage-customIcon.jsx | 4 ++-- component-overview/examples/typography/LinkIcon.jsx | 7 ++----- component-overview/src/style.less | 1 + 5 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 component-overview/examples/icons/Icon-ariahidden.jsx create mode 100644 component-overview/examples/icons/Icon.jsx diff --git a/component-overview/examples/icons/Icon-ariahidden.jsx b/component-overview/examples/icons/Icon-ariahidden.jsx new file mode 100644 index 0000000000..583af51882 --- /dev/null +++ b/component-overview/examples/icons/Icon-ariahidden.jsx @@ -0,0 +1,7 @@ +import { Icon } from '@sb1/ffe-icons-react'; +import batteryIcon from '@sb1/ffe-icons/icons/300/filled/sm/battery_90.svg'; +() => { + + return ; + +} diff --git a/component-overview/examples/icons/Icon.jsx b/component-overview/examples/icons/Icon.jsx new file mode 100644 index 0000000000..576b6070e1 --- /dev/null +++ b/component-overview/examples/icons/Icon.jsx @@ -0,0 +1,6 @@ +import { Icon } from '@sb1/ffe-icons-react'; +import batteryIcon from '@sb1/ffe-icons/icons/300/filled/sm/battery_90.svg'; + +() => { + return ; +} diff --git a/component-overview/examples/messages/message-box/InfoMessage-customIcon.jsx b/component-overview/examples/messages/message-box/InfoMessage-customIcon.jsx index 7bfa92ad78..fb67817474 100644 --- a/component-overview/examples/messages/message-box/InfoMessage-customIcon.jsx +++ b/component-overview/examples/messages/message-box/InfoMessage-customIcon.jsx @@ -1,7 +1,7 @@ import { InfoMessage } from '@sb1/ffe-message-box-react'; import { Paragraph } from '@sb1/ffe-core-react'; -import { HandlevognIkon } from '@sb1/ffe-icons-react'; +import Symbol from '@sb1/ffe-symbols-react'; -}> +}> Du har ingenting i handlevognen din. diff --git a/component-overview/examples/typography/LinkIcon.jsx b/component-overview/examples/typography/LinkIcon.jsx index b284de1020..7b6292fbee 100644 --- a/component-overview/examples/typography/LinkIcon.jsx +++ b/component-overview/examples/typography/LinkIcon.jsx @@ -1,9 +1,6 @@ import { LinkIcon } from '@sb1/ffe-core-react'; -import { SnakkebobleIkon } from '@sb1/ffe-icons-react'; +import Symbol from '@sb1/ffe-symbols-react'; - + diff --git a/component-overview/src/style.less b/component-overview/src/style.less index d6a2f490ea..7d38b16dd5 100644 --- a/component-overview/src/style.less +++ b/component-overview/src/style.less @@ -19,3 +19,4 @@ @import '@sb1/ffe-system-message/less/ffe-system-message'; @import '@sb1/ffe-tables/less/tables'; @import '@sb1/ffe-tabs/less/tabs'; +@import '@sb1/ffe-icons/less/ffe-icons'; From 22bd2aedfd0899f2a3ee3a0c7b59ffc93de2553c Mon Sep 17 00:00:00 2001 From: Helene Kassandra <39946146+HeleneKassandra@users.noreply.github.com> Date: Mon, 18 Dec 2023 15:28:21 +0100 Subject: [PATCH 4/4] feat(ffe-icons-react): legg til ikon wrapper komponent --- packages/ffe-icons-react/package.json | 5 +-- .../scripts/lib/readIconFiles.js | 5 ++- packages/ffe-icons-react/src/Icon.js | 35 +++++++++++++++++++ packages/ffe-icons-react/src/index.d.ts | 12 +++++++ packages/ffe-icons-react/src/index.js | 3 ++ 5 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 packages/ffe-icons-react/src/Icon.js create mode 100644 packages/ffe-icons-react/src/index.d.ts create mode 100644 packages/ffe-icons-react/src/index.js diff --git a/packages/ffe-icons-react/package.json b/packages/ffe-icons-react/package.json index 5b1b44e025..ed2ff6b0fc 100644 --- a/packages/ffe-icons-react/package.json +++ b/packages/ffe-icons-react/package.json @@ -7,7 +7,8 @@ "files": [ "lib", "types", - "es" + "es", + "src" ], "main": "lib/index.js", "types": "types/index.d.ts", @@ -18,7 +19,7 @@ "url": "ssh://git@github.com:SpareBank1/designsystem.git" }, "scripts": { - "build": "node scripts/build.js && ffe-buildtool babel --copy-typedef=gen-src/index.d.ts gen-src", + "build": "ffe-buildtool babel", "clean": "rimraf gen-src lib es types", "lint": "eslint scripts", "test": "eslint gen-src" diff --git a/packages/ffe-icons-react/scripts/lib/readIconFiles.js b/packages/ffe-icons-react/scripts/lib/readIconFiles.js index 4b73179a22..c31734888b 100644 --- a/packages/ffe-icons-react/scripts/lib/readIconFiles.js +++ b/packages/ffe-icons-react/scripts/lib/readIconFiles.js @@ -6,10 +6,13 @@ const caseUtil = require('case'); module.exports = files => files.reduce((acc, filePath) => { const fileName = path.basename(filePath, '.svg'); + const safeFileName = !isNaN(fileName.charAt(0)) + ? `ffe${fileName}` + : fileName; acc.push({ fileName, - iconName: caseUtil.pascal(fileName), + iconName: caseUtil.pascal(safeFileName), svg: svg2jsxStr(fs.readFileSync(filePath, 'utf-8')), }); diff --git a/packages/ffe-icons-react/src/Icon.js b/packages/ffe-icons-react/src/Icon.js new file mode 100644 index 0000000000..8f0bfcf396 --- /dev/null +++ b/packages/ffe-icons-react/src/Icon.js @@ -0,0 +1,35 @@ +import React from 'react'; +import classNames from 'classnames'; +import { string, oneOf } from 'prop-types'; + +const Icon = props => { + const { filePath, className, ariaLabel, size, ...rest } = props; + + return ( + + ); +}; + +Icon.defaultProps = { + size: 'md', +}; + +Icon.propTypes = { + /** The path to the svg-file */ + filePath: string.isRequired, + /** Additional classnames */ + className: string, + /** Aria label, if ariaLabel is hull it'll sett aria-hidden to true */ + ariaLabel: string, + /** Size of the container around the icon */ + size: oneOf(['sm', 'md', 'lg', 'xl']), +}; + +export default Icon; diff --git a/packages/ffe-icons-react/src/index.d.ts b/packages/ffe-icons-react/src/index.d.ts new file mode 100644 index 0000000000..bf90a77cb3 --- /dev/null +++ b/packages/ffe-icons-react/src/index.d.ts @@ -0,0 +1,12 @@ +import * as React from 'react'; + +export interface IconProps { + filePath: string; + className?: string; + ariaLabel?: string; + size: 'sm' | 'md' | 'lg' | 'xl'; +} + +declare class Icon extends React.Component {} + +export default Icon; diff --git a/packages/ffe-icons-react/src/index.js b/packages/ffe-icons-react/src/index.js new file mode 100644 index 0000000000..91a68dc445 --- /dev/null +++ b/packages/ffe-icons-react/src/index.js @@ -0,0 +1,3 @@ +import Icon from './Icon'; + +export { Icon };