diff --git a/.changeset/quick-mails-joke.md b/.changeset/quick-mails-joke.md new file mode 100644 index 0000000000..ff9aa986a9 --- /dev/null +++ b/.changeset/quick-mails-joke.md @@ -0,0 +1,5 @@ +--- +'@swisspost/design-system-components': minor +--- + +Refactored `post-icon` component to use the `` tag to load and show icons under the hood. This enables responsive icons, enables better caching and improves render performance slightly. There is no further action required. diff --git a/.changeset/red-moose-do.md b/.changeset/red-moose-do.md new file mode 100644 index 0000000000..455715252e --- /dev/null +++ b/.changeset/red-moose-do.md @@ -0,0 +1,5 @@ +--- +'@swisspost/design-system-icons': minor +--- + +Added a new set of responsive UI icons. These new icons can be used with the `post-icon` component. These new icons will change their shape based on their size: small icons will render with less flourish and are optimised for a smaller pixel grid. diff --git a/package.json b/package.json index 8e7680618b..08452bd620 100644 --- a/package.json +++ b/package.json @@ -61,8 +61,10 @@ "header:snapshots": "start-server-and-test docs:headless 9001 'pnpm --filter internet-header snapshots'", "intranet-header": "pnpm intranet-header:start", "intranet-header:start": "pnpm --filter design-system-intranet-header-workspace start", - "icons": "pnpm icons:start", - "icons:start": "pnpm --filter design-system-icons dev", + "icons": "pnpm icons:dev", + "icons:dev": "pnpm --filter design-system-icons dev", + "icons:start": "pnpm --filter design-system-icons start", + "icons:build": "pnpm --filter design-system-icons build", "icons:test": "pnpm --filter design-system-icons test", "icons:unit": "pnpm --filter design-system-icons test", "icons:unit:watch": "pnpm --filter design-system-icons test:watch", diff --git a/packages/components/cypress/e2e/card-control.cy.ts b/packages/components/cypress/e2e/card-control.cy.ts index 965b73523c..7ceb108cac 100644 --- a/packages/components/cypress/e2e/card-control.cy.ts +++ b/packages/components/cypress/e2e/card-control.cy.ts @@ -156,7 +156,7 @@ describe('Card-Control', () => { .invoke('attr', 'icon', '1000') .find('.card-control--icon slot[name="icon"] post-icon') .should('exist') - .find('[style*="/1000.svg"]') + .find('use[href*="/1000.svg"]') .should('exist'); cy.get('@card-control') .invoke('removeAttr', 'icon') diff --git a/packages/components/src/components/post-icon/post-icon.scss b/packages/components/src/components/post-icon/post-icon.scss index 18287077c5..37de717aca 100644 --- a/packages/components/src/components/post-icon/post-icon.scss +++ b/packages/components/src/components/post-icon/post-icon.scss @@ -1,5 +1,15 @@ +@use 'sass:list'; @use 'sass:map'; +$post-icon-sizes: ( + '16': 16px, + '24': 24px, + '32': 32px, + '40': 40px, + '48': 48px, + '64': 64px, +); + $post-icon-animations: ( 'cylon': icon-animation-cylon 0.75s ease-in-out infinite alternate, 'cylon-vertical': icon-animation-cylon-vertical 0.75s ease-in-out infinite alternate, @@ -10,25 +20,37 @@ $post-icon-animations: ( ); :host { + container-name: post-icon; + container-type: inline-size; display: inline-block; width: 1em; height: 1em; vertical-align: -0.15em; } -span { +svg { display: block; width: 100%; height: 100%; fill: currentColor; forced-color-adjust: preserve-parent-color; - background-color: currentColor; - -webkit-mask-position: center center; - mask-position: center center; - -webkit-mask-repeat: no-repeat; - mask-repeat: no-repeat; - -webkit-mask-size: 100%; - mask-size: 100%; +} + +@each $key, $value in $post-icon-sizes { + $keys: map.keys($post-icon-sizes); + $index: list.index($keys, $key); + $nextKey: if($index + 1 <= list.length($keys), list.nth($keys, $index + 1), null); + $nextValue: map.get($post-icon-sizes, $nextKey); + + $min: if($index == 1, null, 'min-width: #{$value}'); + $max: if($index == list.length($keys), null, 'max-width: #{$nextValue - 0.02}'); + $connector: if($min and $max, ') and (', ''); + + @container post-icon (#{$min}#{$connector}#{$max}) { + svg { + --pis-#{$key}: block; + } + } } // flip diff --git a/packages/components/src/components/post-icon/post-icon.tsx b/packages/components/src/components/post-icon/post-icon.tsx index 6a48db0aba..7b9de68eaa 100644 --- a/packages/components/src/components/post-icon/post-icon.tsx +++ b/packages/components/src/components/post-icon/post-icon.tsx @@ -1,4 +1,4 @@ -import { Component, Element, Host, h, Prop, State, Watch } from '@stencil/core'; +import { Component, Element, Host, h, Prop, Watch } from '@stencil/core'; import { checkNonEmpty, checkType, checkEmptyOrType, checkEmptyOrOneOf } from '@/utils'; import { version } from '@root/package.json'; @@ -25,13 +25,8 @@ type Animation = (typeof ANIMATION_NAMES)[number]; shadow: true, }) export class PostIcon { - private path: string; - @Element() host: HTMLPostIconElement; - @State() svgStyles: string; - @State() svgOutput: string; - /** * The name of the animation. */ @@ -110,21 +105,7 @@ export class PostIcon { checkEmptyOrType(newValue, 'number', 'The post-icon "scale" prop should be a number.'); } - componentDidLoad() { - this.validateBase(); - this.validateName(); - this.validateFlipH(); - this.validateFlipV(); - this.validateScale(); - this.validateRotate(); - this.validateAnimation(); - } - - componentWillRender() { - this.setPath(); - } - - private setPath() { + private getPath() { // Construct icon path from different possible sources const metaBase = document.head @@ -132,26 +113,38 @@ export class PostIcon { ?.getAttribute('data-post-icon-base') ?? null; const fileBase = `${this.base ?? metaBase ?? CDN_URL}/`.replace(/\/\/$/, '/'); - const fileName = `${this.name}.svg`; + const fileName = `${this.name}.svg#i-${this.name}`; const filePath = `${fileBase}${fileName}`; - this.path = new URL(filePath, window.location.origin).toString(); + return new URL(filePath, window.location.origin).toString(); } - render() { - // create inline styles for some properties - const svgStyles = Object.entries({ - '-webkit-mask-image': `url('${this.path}')`, - 'mask-image': `url('${this.path}')`, - 'transform': + private getStyles() { + return Object.entries({ + transform: (this.scale && !isNaN(Number(this.scale)) ? 'scale(' + this.scale + ')' : '') + (this.rotate && !isNaN(Number(this.rotate)) ? ' rotate(' + this.rotate + 'deg)' : ''), }) .filter(([_key, value]) => value !== null) .reduce((styles, [key, value]) => Object.assign(styles, { [key]: value }), {}); + } + + componentDidLoad() { + this.validateBase(); + this.validateName(); + this.validateFlipH(); + this.validateFlipV(); + this.validateScale(); + this.validateRotate(); + this.validateAnimation(); + } + + render() { return ( - + + + ); } diff --git a/packages/documentation/src/stories/foundations/icons/icon.stories.ts b/packages/documentation/src/stories/foundations/icons/icon.stories.ts index 191949163a..ee403f21c2 100644 --- a/packages/documentation/src/stories/foundations/icons/icon.stories.ts +++ b/packages/documentation/src/stories/foundations/icons/icon.stories.ts @@ -118,8 +118,8 @@ export const Color: Story = { const sizeVariants = [ { style: 'font-size: 1rem' }, - { class: 'h3' }, - { class: 'h1' }, + { class: 'h3 my-0' }, + { class: 'h1 my-0' }, { class: 'fs-huge' }, { style: 'font-size: 4rem' }, { style: 'font-size: 6rem' }, diff --git a/packages/documentation/src/stories/packages/icons/package-icons-icon-meta.sample.html b/packages/documentation/src/stories/packages/icons/package-icons-icon-meta.sample.html index 7b046f6543..be5f3244f5 100644 --- a/packages/documentation/src/stories/packages/icons/package-icons-icon-meta.sample.html +++ b/packages/documentation/src/stories/packages/icons/package-icons-icon-meta.sample.html @@ -1,5 +1,5 @@ - + diff --git a/packages/icons/.gitignore b/packages/icons/.gitignore index c38eb25a35..c6536d864e 100644 --- a/packages/icons/.gitignore +++ b/packages/icons/.gitignore @@ -5,7 +5,9 @@ node_modules .env # Ignore generated files -icons +public/post-icons +public/report.json + icons-optimized icons.json no-svgs.json diff --git a/packages/icons/nodemon.json b/packages/icons/nodemon.json new file mode 100644 index 0000000000..3439012b85 --- /dev/null +++ b/packages/icons/nodemon.json @@ -0,0 +1,6 @@ +{ + "watch": ["src/**/*.ts", "src/icons/**/*.(svg|json)"], + "ext": "ts,svg,json", + "ignore": ["src/**/*.spec.ts"], + "exec": "ts-node ./src/build.ts" +} diff --git a/packages/icons/package.json b/packages/icons/package.json index 99918f40c9..2f6626303c 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -21,7 +21,9 @@ ], "private": false, "scripts": { - "dev": "http-server ./public -o -a localhost -p 9330", + "dev": "pnpm build && http-server ./public -o -a localhost -p 9330", + "start": "nodemon", + "build": "ts-node src/build.ts", "fetchSVGs": "ts-node src/index.ts", "test": "jest", "test:watch": "jest --watch", @@ -43,6 +45,8 @@ "jest": "29.7.0", "mock-fs": "5.2.0", "node-fetch": "2.7.0", + "node-html-parser": "6.1.13", + "nodemon": "3.1.7", "svgo": "3.3.2", "ts-jest": "29.2.4", "ts-node": "10.9.2", diff --git a/packages/icons/public/assets/sprite.svg b/packages/icons/public/assets/sprite.svg deleted file mode 100644 index 390d57bc56..0000000000 --- a/packages/icons/public/assets/sprite.svg +++ /dev/null @@ -1,253 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/icons/public/index.html b/packages/icons/public/index.html index f59429e6b2..b4dd6f5442 100644 --- a/packages/icons/public/index.html +++ b/packages/icons/public/index.html @@ -18,37 +18,83 @@ -

Playground

+
+
+

Playground

+ +
+ +
+

Filters

+
+
+ + +
+
+ + +
+
+
-
-
-

Source Icons

+
+