Skip to content

Commit 8b3769c

Browse files
Merge pull request #63 from umbraco/feature/uui-icon
Feature/uui icon
2 parents 05916da + a3df7e1 commit 8b3769c

21 files changed

+3045
-132
lines changed

package-lock.json

Lines changed: 2755 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/uui-icon/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# uui-icon
2+
3+
![npm](https://img.shields.io/npm/v/@umbraco-ui/uui-icon?logoColor=%231B264F)
4+
5+
Umbraco style icon component.
6+
7+
## Installation
8+
9+
### ES imports
10+
11+
```zsh
12+
npm i @umbraco-ui/uui-icon
13+
```
14+
15+
Import the registration of `<uui-icon>` via:
16+
17+
```javascript
18+
import '@umbraco-ui/uui-icon/lib';
19+
```
20+
21+
When looking to leverage the `UUIIconElement` base class as a type and/or for extension purposes, do so via:
22+
23+
```javascript
24+
import { UUIIconElement } from '@umbraco-ui/uui-icon/lib/uui-icon.element';
25+
```
26+
27+
### CDN
28+
29+
The component is available via CDN. This means it can be added to your application without the need of any bundler configuration. Here is how to use it with jsDelivr.
30+
31+
```html
32+
<!-- Latest Version -->
33+
<script src="https://cdn.jsdelivr.net/npm/@umbraco-ui/uui-icon@latest/dist/uui-icon.min.js"></script>
34+
35+
<!-- Specific version -->
36+
<script src="https://cdn.jsdelivr.net/npm/@umbraco-ui/[email protected]/dist/uui-icon.min.js"></script>
37+
```
38+
39+
## Usage
40+
41+
```html
42+
<uui-icon></uui-icon>
43+
```
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { UUIIconHost } from './UUIIconHost';
2+
import { UUIIconRegistryEvent } from './UUIIconRegistryEvent';
3+
4+
export class UUIIconRegistry extends EventTarget {
5+
private icons: Record<string, UUIIconHost> = {};
6+
7+
public defineIcon(iconName: string, svgString: string) {
8+
if (this.icons[iconName]) {
9+
this.icons[iconName].svg = svgString;
10+
}
11+
this.icons[iconName] = new UUIIconHost(svgString);
12+
}
13+
14+
public async getIcon(iconName: string): Promise<string> {
15+
if (this.icons[iconName]) {
16+
return this.icons[iconName].promise;
17+
} else {
18+
const icon = new UUIIconHost();
19+
this.icons[iconName] = icon;
20+
21+
const event = new UUIIconRegistryEvent(
22+
UUIIconRegistryEvent.ICON_REQUEST,
23+
{
24+
cancelable: true,
25+
detail: { iconName },
26+
}
27+
);
28+
this.dispatchEvent(event);
29+
30+
if (event.defaultPrevented === false) {
31+
// as no one prevented default we will reject, to show fallback.
32+
console.log('reject');
33+
icon.reject();
34+
}
35+
36+
return icon.promise;
37+
}
38+
}
39+
}
40+
41+
export const iconRegistry = new UUIIconRegistry();
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { UUIEvent } from '@umbraco-ui/uui-base/lib/events';
2+
3+
export class UUIIconRegistryEvent extends UUIEvent<{ iconName: string }> {
4+
public static readonly ICON_REQUEST = 'icon_request';
5+
6+
public acceptRequest() {
7+
this.preventDefault();
8+
}
9+
}

packages/uui-icon/lib/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { UUIIconElement } from './uui-icon.element';
2+
import { defineElement } from '@umbraco-ui/uui-base/lib/registration';
3+
4+
defineElement('uui-icon', UUIIconElement as any);

src/components/uui-icon/uui-icon.element.ts renamed to packages/uui-icon/lib/uui-icon.element.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import { LitElement, css } from 'lit';
22
import { property } from 'lit/decorators.js';
3-
import { UUIIconService } from '../../service/iconservice/UUIIconService';
3+
import { iconRegistry } from './UUIIconRegistry';
4+
45
/**
5-
* @element uui-icon
6-
*
6+
* @element uui-icon
77
*/
8-
9-
// TODO: Allow for slotted SVG.
108
export class UUIIconElement extends LitElement {
119
static styles = [
1210
css`
@@ -32,11 +30,18 @@ export class UUIIconElement extends LitElement {
3230
set name(newValue) {
3331
this._name = newValue;
3432
if (this._name !== '' && this._name !== null) {
35-
UUIIconService.getIcon(this._name).then((svg: string) => {
36-
if (this.shadowRoot) {
37-
this.shadowRoot.innerHTML = svg;
38-
}
39-
});
33+
iconRegistry
34+
.getIcon(this._name)
35+
.then((svg: string) => {
36+
if (this.shadowRoot) {
37+
this.shadowRoot.innerHTML = svg;
38+
}
39+
})
40+
.catch(() => {
41+
if (this.fallback && this.shadowRoot) {
42+
this.shadowRoot.innerHTML = this.fallback;
43+
}
44+
});
4045
}
4146
}
4247

@@ -53,6 +58,9 @@ export class UUIIconElement extends LitElement {
5358
}
5459
}
5560

61+
@property()
62+
fallback: string | null = null;
63+
5664
connectedCallback() {
5765
super.connectedCallback();
5866
if (this._svg) {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Story } from '@storybook/web-components';
2+
import { html } from 'lit-html';
3+
import '@umbraco-ui/uui-icon/lib/index';
4+
import { iconRegistry } from './UUIIconRegistry';
5+
import { UUIIconRegistryEvent } from './UUIIconRegistryEvent';
6+
7+
export default {
8+
id: 'uui-icon',
9+
title: 'Basics/Icon',
10+
component: 'uui-icon',
11+
parameters: {
12+
docs: {
13+
source: {
14+
code: `<uui-icon></uui-icon>`,
15+
},
16+
},
17+
},
18+
};
19+
20+
// Example of how to define icon data:
21+
iconRegistry.defineIcon(
22+
'bug',
23+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M85.065 420.232c-6.507 6.509-6.507 17.054 0 23.56 6.504 6.514 17.056 6.514 23.559.002l33.049-33.042c-5.737-10.21-10.526-21.23-14.25-32.877l-42.358 42.357zm339.124-198.011c6.51-6.501 6.51-17.053 0-23.562-6.502-6.504-17.055-6.504-23.562 0l-29.451 29.452c5.74 10.208 10.526 21.231 14.251 32.879l38.762-38.769zm-305.782 97.213c0-5.818.263-11.562.759-17.226l-46.137-.002c-9.204.002-16.662 7.458-16.662 16.66 0 9.203 7.458 16.664 16.662 16.664h46.046a197.503 197.503 0 0 1-.668-16.096zm8.151-55.715a177.918 177.918 0 0 1 13.86-33.274l-31.786-31.786c-6.51-6.504-17.061-6.504-23.565 0-6.509 6.51-6.509 17.062 0 23.562l41.491 41.498zm257.974 116.854c-3.882 11.521-8.813 22.389-14.676 32.448l30.776 30.772c6.505 6.512 17.056 6.512 23.56-.002 6.507-6.506 6.507-17.051 0-23.56l-39.66-39.658zm51.697-78.367l-42.545.002c.497 5.663.758 11.407.758 17.226 0 5.432-.234 10.8-.666 16.097h42.453c9.203 0 16.662-7.461 16.662-16.664 0-9.203-7.459-16.659-16.662-16.661zM162.407 70.389c27.314.198 39.683 10.33 46.938 21.625 5.964 9.492 7.656 20.515 8.036 27.072-15.226 10.442-26.149 26.689-29.561 45.572h138.623c-3.413-18.886-14.338-35.135-29.564-45.577.376-6.559 2.071-17.583 8.04-27.068 7.246-11.295 19.614-21.425 46.942-21.625a7.233 7.233 0 0 0 0-14.466c-31.162-.202-49.95 13.171-59.234 28.45-5.947 9.627-8.48 19.591-9.55 27.353a70.4 70.4 0 0 0-25.938-4.981 70.43 70.43 0 0 0-25.96 4.986c-1.069-7.761-3.602-17.725-9.549-27.358-9.287-15.281-28.068-28.652-59.223-28.45-4.006 0-7.238 3.238-7.238 7.233s3.232 7.234 7.238 7.234zm-18.253 248.188c0 71.594 44.03 130.722 100.454 138.429V193.879h-37.77c-37.118 22.856-62.684 70.152-62.684 124.698zm14.814 4.98c0-10.557 12.448-19.117 27.805-19.117 15.354 0 27.802 8.561 27.802 19.117s-12.447 19.112-27.802 19.112c-15.357 0-27.805-8.556-27.805-19.112zm54.263 82.629c-7.163 0-12.966-8.796-12.966-19.645 0-10.85 5.803-19.648 12.966-19.648 7.158 0 12.964 8.799 12.964 19.648.001 10.849-5.806 19.645-12.964 19.645zm9.525-132.331c-7.467 7.463-22.32 4.714-33.177-6.146-10.857-10.854-13.61-25.714-6.144-33.176 7.465-7.464 22.318-4.717 33.176 6.141 10.857 10.859 13.611 25.715 6.145 33.181zm84.664-79.976h-37.77v263.127c56.423-7.707 100.459-66.835 100.459-138.429 0-54.546-25.566-101.842-62.689-124.698zm-10.414 46.795c10.859-10.857 25.711-13.604 33.176-6.141 7.469 7.462 4.716 22.322-6.141 33.176-10.861 10.86-25.713 13.609-33.18 6.146-7.465-7.466-4.713-22.322 6.145-33.181zm3.382 165.512c-7.159 0-12.964-8.796-12.964-19.645 0-10.85 5.805-19.648 12.964-19.648 7.16 0 12.964 8.799 12.964 19.648s-5.804 19.645-12.964 19.645zm26.46-63.517c-15.357 0-27.807-8.556-27.807-19.112s12.449-19.117 27.807-19.117c15.355 0 27.804 8.561 27.804 19.117s-12.449 19.112-27.804 19.112z"/></svg>'
24+
);
25+
26+
iconRegistry.defineIcon(
27+
'info',
28+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M230.384 224.6c4.293-11.035-1.231-16.553-6.143-16.553-22.688 0-52.154 53.374-63.173 53.374-4.311 0-7.99-4.307-7.99-7.985 0-11.043 26.991-36.805 34.982-44.783 24.528-23.312 56.44-41.104 92.027-41.104 26.368 0 54.601 15.945 32.515 75.471L268.42 362.638c-3.665 9.205-10.415 24.557-10.415 34.367 0 4.29 2.438 8.595 7.348 8.595 18.396 0 52.155-52.158 60.748-52.158 3.061 0 7.351 3.675 7.351 9.196 0 17.793-71.771 93.876-133.744 93.876-22.088 0-37.423-10.421-37.423-33.738 0-29.441 20.854-79.757 25.169-90.194l42.93-107.982zm33.125-120.861c0-26.992 23.309-49.073 50.308-49.073 24.556 0 42.336 16.554 42.336 41.716 0 28.233-23.303 49.094-50.914 49.094-25.151 0-41.73-16.576-41.73-41.737z"/></svg>'
29+
);
30+
31+
iconRegistry.defineIcon(
32+
'delete',
33+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M401.431 167.814l-58.757-58.76-88.029 88.026-88.028-88.026-58.76 58.76 88.026 88.027-88.026 88.024 58.76 58.768 88.028-88.031 88.029 88.031 58.757-58.768-88.027-88.024z"/></svg>'
34+
);
35+
36+
// Example of how to implement a listener, emitted when a icon is begin requested:
37+
iconRegistry.addEventListener(UUIIconRegistryEvent.ICON_REQUEST, ((
38+
event: UUIIconRegistryEvent
39+
) => {
40+
if (event.detail.iconName === 'check') {
41+
// we will call acceptRequest to let know that we reacted to this request.
42+
event.acceptRequest();
43+
setTimeout(() => {
44+
iconRegistry.defineIcon(
45+
'check',
46+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M461.884 68.14c-132.601 81.297-228.817 183.87-272.048 235.345l-105.874-82.95-46.751 37.691 182.941 186.049c31.485-80.646 131.198-238.264 252.956-350.252L461.884 68.14z"/></svg>'
47+
);
48+
}, 2000);
49+
}
50+
}) as EventListener);
51+
52+
const fallbackSVG =
53+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M85.065 420.232c-6.507 6.509-6.507 17.054 0 23.56 6.504 6.514 17.056 6.514 23.559.002l33.049-33.042c-5.737-10.21-10.526-21.23-14.25-32.877l-42.358 42.357zm339.124-198.011c6.51-6.501 6.51-17.053 0-23.562-6.502-6.504-17.055-6.504-23.562 0l-29.451 29.452c5.74 10.208 10.526 21.231 14.251 32.879l38.762-38.769zm-305.782 97.213c0-5.818.263-11.562.759-17.226l-46.137-.002c-9.204.002-16.662 7.458-16.662 16.66 0 9.203 7.458 16.664 16.662 16.664h46.046a197.503 197.503 0 0 1-.668-16.096zm8.151-55.715a177.918 177.918 0 0 1 13.86-33.274l-31.786-31.786c-6.51-6.504-17.061-6.504-23.565 0-6.509 6.51-6.509 17.062 0 23.562l41.491 41.498zm257.974 116.854c-3.882 11.521-8.813 22.389-14.676 32.448l30.776 30.772c6.505 6.512 17.056 6.512 23.56-.002 6.507-6.506 6.507-17.051 0-23.56l-39.66-39.658zm51.697-78.367l-42.545.002c.497 5.663.758 11.407.758 17.226 0 5.432-.234 10.8-.666 16.097h42.453c9.203 0 16.662-7.461 16.662-16.664 0-9.203-7.459-16.659-16.662-16.661zM162.407 70.389c27.314.198 39.683 10.33 46.938 21.625 5.964 9.492 7.656 20.515 8.036 27.072-15.226 10.442-26.149 26.689-29.561 45.572h138.623c-3.413-18.886-14.338-35.135-29.564-45.577.376-6.559 2.071-17.583 8.04-27.068 7.246-11.295 19.614-21.425 46.942-21.625a7.233 7.233 0 0 0 0-14.466c-31.162-.202-49.95 13.171-59.234 28.45-5.947 9.627-8.48 19.591-9.55 27.353a70.4 70.4 0 0 0-25.938-4.981 70.43 70.43 0 0 0-25.96 4.986c-1.069-7.761-3.602-17.725-9.549-27.358-9.287-15.281-28.068-28.652-59.223-28.45-4.006 0-7.238 3.238-7.238 7.233s3.232 7.234 7.238 7.234zm-18.253 248.188c0 71.594 44.03 130.722 100.454 138.429V193.879h-37.77c-37.118 22.856-62.684 70.152-62.684 124.698zm14.814 4.98c0-10.557 12.448-19.117 27.805-19.117 15.354 0 27.802 8.561 27.802 19.117s-12.447 19.112-27.802 19.112c-15.357 0-27.805-8.556-27.805-19.112zm54.263 82.629c-7.163 0-12.966-8.796-12.966-19.645 0-10.85 5.803-19.648 12.966-19.648 7.158 0 12.964 8.799 12.964 19.648.001 10.849-5.806 19.645-12.964 19.645zm9.525-132.331c-7.467 7.463-22.32 4.714-33.177-6.146-10.857-10.854-13.61-25.714-6.144-33.176 7.465-7.464 22.318-4.717 33.176 6.141 10.857 10.859 13.611 25.715 6.145 33.181zm84.664-79.976h-37.77v263.127c56.423-7.707 100.459-66.835 100.459-138.429 0-54.546-25.566-101.842-62.689-124.698zm-10.414 46.795c10.859-10.857 25.711-13.604 33.176-6.141 7.469 7.462 4.716 22.322-6.141 33.176-10.861 10.86-25.713 13.609-33.18 6.146-7.465-7.466-4.713-22.322 6.145-33.181zm3.382 165.512c-7.159 0-12.964-8.796-12.964-19.645 0-10.85 5.805-19.648 12.964-19.648 7.16 0 12.964 8.799 12.964 19.648s-5.804 19.645-12.964 19.645zm26.46-63.517c-15.357 0-27.807-8.556-27.807-19.112s12.449-19.117 27.807-19.117c15.355 0 27.804 8.561 27.804 19.117s-12.449 19.112-27.804 19.112z"/></svg>';
54+
55+
export const Overview: Story = () => html`
56+
<uui-icon name="bug"></uui-icon>
57+
<uui-icon name="check"></uui-icon>
58+
<br />
59+
<p style="color:green">
60+
Icons use the color of the context:<br />
61+
<uui-icon name="check"></uui-icon>
62+
<uui-icon name="bug"></uui-icon>
63+
<uui-icon name="info"></uui-icon>
64+
</p>
65+
<p>
66+
or can be colored individually by setting the color css property on each:<br />
67+
<uui-icon name="check" style="color:green"></uui-icon>
68+
<uui-icon name="bug" style="color:red"></uui-icon>
69+
<uui-icon name="info" style="color:blue"></uui-icon>
70+
</p>
71+
<p style="font-size:22px;">
72+
The icons use the font-size of its context:<br />
73+
<uui-icon name="bug"></uui-icon>
74+
<uui-icon name="info"></uui-icon>
75+
</p>
76+
<p>
77+
Example of icon that is not captured by icon service, therefore showing the
78+
bug-fallback icon<br />
79+
<uui-icon name="not_existing" fallback=${fallbackSVG}></uui-icon>
80+
</p>
81+
`;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { html, fixture, expect } from '@open-wc/testing';
2+
import { UUIIconElement } from './uui-icon.element';
3+
import '.';
4+
5+
describe('UUIIconElement', () => {
6+
let element: UUIIconElement;
7+
8+
beforeEach(async () => {
9+
element = await fixture(html` <uui-icon></uui-icon> `);
10+
});
11+
12+
it('passes the a11y audit', async () => {
13+
await expect(element).shadowDom.to.be.accessible();
14+
});
15+
});

packages/uui-icon/package.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "@umbraco-ui/uui-icon",
3+
"version": "0.0.0",
4+
"license": "MIT",
5+
"keywords": [
6+
"Umbraco",
7+
"Custom elements",
8+
"Web components",
9+
"UI",
10+
"Lit",
11+
"Icon"
12+
],
13+
"description": "Umbraco UI icon component",
14+
"repository": {
15+
"type": "git",
16+
"url": "https://github.com/umbraco/Umbraco.UI.git",
17+
"directory": "packages/uui-icon"
18+
},
19+
"bugs": {
20+
"url": "https://github.com/umbraco/Umbraco.UI/issues"
21+
},
22+
"main": "./dist/uui-icon.min.js",
23+
"module": "./lib/index.js",
24+
"customElements": "custom-elements.json",
25+
"files": [
26+
"dist",
27+
"lib/**/*.d.ts",
28+
"lib/**/*.js",
29+
"custom-elements.json"
30+
],
31+
"dependencies": {
32+
"@umbraco-ui/uui-base": "0.0.12"
33+
},
34+
"scripts": {
35+
"build": "npm run analyze && tsc --build --force && rollup -c rollup.config.js",
36+
"clean": "tsc --build --clean && rimraf dist lib/*.js lib/**/*.js custom-elements.json",
37+
"analyze": "web-component-analyzer **/*.element.ts --outFile custom-elements.json"
38+
},
39+
"publishConfig": {
40+
"access": "public"
41+
},
42+
"homepage": "https://uui.umbraco.com/?path=/story/uui-icon"
43+
}

0 commit comments

Comments
 (0)