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

feat(icon): review icons bundle #477

Merged
merged 38 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f569fd7
Icon component
epessina Jun 3, 2024
36af679
Ignored icon packs
epessina Jun 3, 2024
9ee87cd
Snapshots and docs
epessina Jun 3, 2024
ce43c24
Build icons script
epessina Jun 4, 2024
c48594a
Fixed licence
epessina Jun 4, 2024
963f231
Fixed tests
epessina Jun 4, 2024
f610a8d
Props
epessina Jun 4, 2024
d43d638
Props
epessina Jun 4, 2024
fb11a91
Updated svgr
epessina Jun 4, 2024
b9ef3dd
Tests
epessina Jun 4, 2024
cf5580a
Ignored icons from lint
epessina Jun 4, 2024
2a539fe
Docs and stories
epessina Jun 4, 2024
694c99f
Icons story
epessina Jun 4, 2024
bfee608
Added licence
epessina Jun 4, 2024
28ac8ea
Added licence
epessina Jun 4, 2024
81fce54
Snapshots
epessina Jun 4, 2024
e130189
Optional component
epessina Jun 4, 2024
5473c24
Readme
epessina Jun 5, 2024
d6bc1e1
Removed ?react from svg imports
epessina Jun 5, 2024
be2a55c
Typings
epessina Jun 5, 2024
b8cfd7a
Fixed icons
epessina Jul 1, 2024
b14024a
Update src/assets/icons/icons.stories.mdx
epessina Jul 1, 2024
104778d
Revert "Update src/assets/icons/icons.stories.mdx"
epessina Jul 2, 2024
46fed17
Removed title attribute from typing
epessina Jul 2, 2024
faf962d
Fixed drawer
epessina Jul 13, 2024
625493e
Icons as single files
epessina Jul 20, 2024
be3724f
Fixed licence
epessina Jul 23, 2024
0fe0004
Fixed snapshots
epessina Jul 23, 2024
6737e2a
Merge branch 'main' into feat/icons-bundle
Jul 29, 2024
ed28364
Merge branch 'main' into feat/icons-bundle
epessina Sep 6, 2024
773880b
Updated cheerio
epessina Sep 6, 2024
b249b03
Dedupe
epessina Sep 6, 2024
cf5214f
Updated react-icons
epessina Sep 6, 2024
55a621e
Merge branch 'main' into feat/icons-bundle
Sep 9, 2024
c7cf03b
0.8.0-rc.0
fredmaggiowski Sep 11, 2024
5522cd9
Merge branch 'main' into feat/icons-bundle
Sep 12, 2024
8c2d222
Merge branch 'feat/icons-bundle' of github.com:mia-platform/design-sy…
Sep 12, 2024
200f5a4
Fixed publish job on pipeline
MIA-Deltat1995 Sep 13, 2024
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
3 changes: 2 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ module.exports = {
"src/vite-env.d.ts",
// FIXME: This component has a react/boolean-prop naming problem, because boolean props do not have the symbol at the beginning of the name.
"src/components/Typography/BodyX/BodyX.tsx",
"src/components/Typography/HX/HX.tsx"
"src/components/Typography/HX/HX.tsx",
"icons/**/*"
],
parser: "@typescript-eslint/parser",
parserOptions: {
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ jobs:
- run: yarn
- run: yarn lint
- run: yarn build
- if: ${{ startsWith(github.ref, 'refs/tags/') }}
run: yarn build-icons
- run: yarn test
- name: Coveralls
uses: coverallsapp/github-action@v2
Expand Down Expand Up @@ -63,6 +65,8 @@ jobs:
registry-url: ${{ secrets.MIA_NPM_REGISTRY_URL }}
- run: yarn install --frozen-lockfile
- run: yarn build
- if: ${{ startsWith(github.ref, 'refs/tags/') }}
run: yarn build-icons
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.MIA_NPM_REGISTRY_TOKEN }}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ pids
build
dist
dist-ssr
icons/**/*
!icons/package.json

# Eslint
.eslintcache
Expand Down
9 changes: 7 additions & 2 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ import withTheme from "./decorators/theme"

const preview: Preview = {
parameters: {
actions: {
argTypesRegex: "^on[A-Z].*"
options: {
storySort: {
method: 'alphabetical',
}
},
actions: {
argTypesRegex: "^on[A-Z].*"
},
backgrounds,
docs,
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@ If a `theme-generator.json` file is changed, it is required to regenerate the ne
yarn update-themes
```

## Icons

Icons are shipped in the `@mia-platform-internal/console-design-system-react/icons` sub-package. The SVG components can be created and placed in the `/icons` directory running:

```bash
yarn build-icons
```

This command launches a script that copies a set of icon packs from the dist of the [react-icons](https://github.com/react-icons/react-icons) dependency and build the set of Mia-Platform icons from the files contained in `/src/assets/icons`. To add a new icon you just need to place a new `.svg` file here (and don't forget to update the `/src/assets/icons/icons.stories.mdx` story file).

Given the high number of files, the script is pretty slow: Therefore, in CI it is run only on tags.

## License

All files under the src folder must have the license boilerplate attached to files. This is checked by CI.
Expand Down
3 changes: 3 additions & 0 deletions icons/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "@mia-platform-internal/console-design-system-react/icons"
}
15 changes: 11 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"name": "@mia-platform-internal/console-design-system-react",
"packageManager": "yarn@4.2.2",
"version": "0.7.8",
"version": "0.8.0-rc.0",
"type": "module",
"main": "dist/cjs/console-design-system-react.cjs",
"module": "dist/es/console-design-system-react.js",
"types": "dist/src/index.d.ts",
"files": [
"dist"
"dist",
"icons"
],
"description": "Mia Platform Design System",
"author": "Mia Platform Core Team <core@mia-platform.eu>",
Expand All @@ -31,6 +32,7 @@
"scripts": {
"dev": "vite",
"preview": "vite preview",
"build-icons": "node --no-warnings --loader=ts-node/esm scripts/build-icons.ts",
"build": "rm -rf ./dist -rf && tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives",
"lint:fix": "yarn lint --fix",
Expand All @@ -49,7 +51,7 @@
"@fontsource/inter": "^5.0.20",
"antd": "5.17.0",
"lodash-es": "^4.17.21",
"react-icons": "^4.11.0"
"react-icons": "^5.3.0"
},
"peerDependencies": {
"react": "^18.3.0",
Expand Down Expand Up @@ -81,13 +83,17 @@
"@typescript-eslint/eslint-plugin": "^7.17.0",
"@typescript-eslint/parser": "^7.15.0",
"@vitejs/plugin-react-swc": "^3.6.0",
"camelcase": "^8.0.0",
"cheerio": "^1.0.0",
"classnames": "^2.5.1",
"domhandler": "^5.0.3",
"eslint": "^8.57.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-react": "^7.34.3",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.9",
"eslint-plugin-storybook": "^0.8.0",
"glob": "^10.4.1",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.6.2",
"jest-environment-jsdom": "^29.6.2",
Expand All @@ -97,14 +103,15 @@
"react-dom": "^18.3.1",
"rollup-plugin-visualizer": "^5.12.0",
"storybook": "^7.6.17",
"tar": "^7.4.0",
"traverse": "^0.6.8",
"ts-jest": "^29.1.5",
"ts-node": "^10.9.2",
"typescript": "^5.3.3",
"typescript-plugin-css-modules": "^5.1.0",
"vite": "^5.3.1",
"vite-plugin-dts": "^3.7.3",
"vite-plugin-svgr": "^3.3.0",
"vite-plugin-svgr": "^4.2.0",
"vite-tsconfig-paths": "^4.3.1"
},
"engines": {
Expand Down
180 changes: 180 additions & 0 deletions scripts/build-icons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/**
* Copyright 2024 Mia srl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/

/* eslint-disable no-console */

import { Cheerio, load } from 'cheerio'
import { type TarOptionsWithAliasesAsyncNoFile, extract } from 'tar'
import { Element } from 'domhandler'
import { IconTree } from 'react-icons'
import camelcase from 'camelcase'
import fs from 'fs/promises'
import { glob } from 'glob'
import https from 'https'
import path from 'path'
import url from 'url'

const REACT_ICONS_VERSION = '5.3.0'
const REACT_ICONS_PACKAGES = ['lib', 'ai', 'fi', 'pi']

const dirname = path.dirname(url.fileURLToPath(import.meta.url))
const rootDir = path.resolve(dirname, '..')
const outDir = path.resolve(rootDir, 'icons')

async function setup(): Promise<void> {
const outDirContent = await fs.readdir(outDir)

const promises = outDirContent.reduce<Promise<void>[]>((acc, cur) => {
if (cur !== 'package.json') {
const pathToRemove = path.resolve(outDir, cur)
return [...acc, fs.rm(pathToRemove, { recursive: true, force: true })]
}

return acc
}, [])

await Promise.all(promises)
}

async function downloadReactIconsPackage(): Promise<void> {
const pkgUrl = `https://github.com/react-icons/react-icons/releases/download/v${REACT_ICONS_VERSION}/react-icons-all-files-${REACT_ICONS_VERSION}.tgz`

const tarOptions: TarOptionsWithAliasesAsyncNoFile = {
cwd: outDir,
filter: (entryPath) => REACT_ICONS_PACKAGES.some((pkgName) => entryPath.startsWith(`package/${pkgName}`)),
strip: 1,
}

const urlToCall = await new Promise<string>((resolve, reject) => {
const reqUrl = new url.URL(pkgUrl)

const request = https.get(reqUrl, (res) => resolve(res.headers.location ?? ''))

request.on('error', reject)
})

await new Promise<void>((resolve, reject) => {
const request = https.get(urlToCall)

request.on('response', (res) => {
res.pipe(extract(tarOptions), { end: true })

res.on('error', reject)

res.on('end', resolve)
})

request.on('error', reject)
})
}

async function svgStringToTree(svg: string): Promise<IconTree> {
const $doc = load(svg, { xmlMode: true })
const $svg = $doc('svg')

const normalizeAttributes = (attributes: Record<string, string> = {}, tagName: string): Record<string, string> => {
const attributesToFilerOut = [
'class',
...(tagName === 'svg' ? ['xmlns', 'xmlns:xlink', 'xml:space', 'width', 'height'] : []),
]

return Object
.keys(attributes)
.filter((name) => !attributesToFilerOut.includes(name))
.reduce<Record<string, string>>((nextAttributes, attributeName) => {
const newName = attributeName.startsWith('aria-') ? attributeName : camelcase(attributeName)
nextAttributes[newName] = attributes[attributeName]
return nextAttributes
}, {})
}

function elementToTree(element: Cheerio<Element>): IconTree[] {
return (
element
.filter((_, { tagName }) => Boolean(tagName && !['style', 'title'].includes(tagName)))
.map((_, { tagName, attribs, children }) => ({
tag: tagName,
attr: normalizeAttributes(attribs, tagName),
child: children?.length ? elementToTree($doc(children) as unknown as Cheerio<Element>) : [],
}))
.get()
)
}

const tree = elementToTree($svg)
return tree[0]
}

async function buildMiaIcons(): Promise<void> {
const miaIconsPackageDir = path.resolve(outDir, 'mi')
await fs.mkdir(miaIconsPackageDir)

const svgFiles = await glob(path.resolve(rootDir, 'src/assets/icons/*.svg'))

const promises = svgFiles.map(async(filePath) => {
const iconNameRaw = path.basename(filePath, path.extname(filePath))
const iconName = `Mi${camelcase(iconNameRaw, { pascalCase: true })}`

const svgStr = await fs.readFile(filePath, 'utf-8')

const iconTree = await svgStringToTree(svgStr)
const iconTreeStr = JSON.stringify(iconTree)

const esmFilePath = path.resolve(miaIconsPackageDir, `${iconName}.mjs`)
const esmTemplate = `// THIS FILE IS AUTO GENERATED\n`
+ `import { GenIcon } from '../lib/index.mjs';\n`
+ `export function ${iconName} (props) {\n`
+ ` return GenIcon(${iconTreeStr})(props);\n`
+ `};\n`

const cjsFilePath = path.resolve(miaIconsPackageDir, `${iconName}.js`)
const cjsTemplate = `// THIS FILE IS AUTO GENERATED\n`
+ `var GenIcon = require('../lib').GenIcon\n`
+ `module.exports.${iconName} = function ${iconName} (props) {\n`
+ ` return GenIcon(${iconTreeStr})(props);\n`
+ `};\n`

const declarationsFilePath = path.resolve(miaIconsPackageDir, `${iconName}.d.ts`)
const declarationsTemplate = `// THIS FILE IS AUTO GENERATED\n`
+ `import { IconType } from '../lib/index.mjs'\n`
+ `export declare const ${iconName}: IconType;\n`

await fs.writeFile(esmFilePath, esmTemplate, 'utf-8')
await fs.writeFile(cjsFilePath, cjsTemplate, 'utf-8')
await fs.writeFile(declarationsFilePath, declarationsTemplate, 'utf-8')
})

await Promise.all(promises)
}

async function main(): Promise<void> {
console.info(`» Start icons building process`)

await setup()

console.debug(`» Downloading icons from react-icons v${REACT_ICONS_VERSION} archive...`)
await downloadReactIconsPackage()
console.debug('» Icon files downloaded and extracted')

await buildMiaIcons()
console.debug('» Mia-Platform icons built')
}

main()
.then(() => console.log('✔️ Icons built correctly'))
.catch((error) => console.error('Error building icons', error))
17 changes: 17 additions & 0 deletions src/assets/icons/MiaPlatform.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions src/assets/icons/MiaPlatformColored.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading