@momentum-design/components 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/.eslintrc.js +16 -0
  2. package/CONTRIBUTING.md +5 -0
  3. package/README.md +39 -0
  4. package/SCRIPTS.md +15 -0
  5. package/TESTING.md +38 -0
  6. package/config/api-extractor.json +33 -0
  7. package/config/custom-elements-manifest.config.js +28 -0
  8. package/config/esbuild/configs/browser.js +21 -0
  9. package/config/esbuild/configs/e2e.js +10 -0
  10. package/config/esbuild/esbuild-e2e.config.js +22 -0
  11. package/config/esbuild/esbuild.config.js +9 -0
  12. package/config/playwright/playwright.config.ts +107 -0
  13. package/config/playwright/public/index.css +43 -0
  14. package/config/playwright/public/index.html +26 -0
  15. package/config/playwright/setup/Components.page.ts +163 -0
  16. package/config/playwright/setup/constants.ts +27 -0
  17. package/config/playwright/setup/index.ts +42 -0
  18. package/config/playwright/setup/steps/accessibility.ts +9 -0
  19. package/config/playwright/setup/types.ts +5 -0
  20. package/config/playwright/setup/utils/accessibility.ts +70 -0
  21. package/config/playwright/setup/utils/visual-regression.ts +35 -0
  22. package/config/plop/actions/AddComponent.ts +21 -0
  23. package/config/plop/actions/AddToComponentExports.ts +16 -0
  24. package/config/plop/actions/AddToComponentImports.ts +16 -0
  25. package/config/plop/constants/index.ts +31 -0
  26. package/config/plop/esbuild.config.plop.js +4 -0
  27. package/config/plop/generators/component/index.ts +25 -0
  28. package/config/plop/plopfile.ts +6 -0
  29. package/config/plop/prompts/index.ts +8 -0
  30. package/config/plop/templates/add/component/index.ts.hbs +12 -0
  31. package/config/plop/templates/add/component/{{componentName}}.component.ts.hbs +22 -0
  32. package/config/plop/templates/add/component/{{componentName}}.constants.ts.hbs +5 -0
  33. package/config/plop/templates/add/component/{{componentName}}.e2e-test.ts.hbs +67 -0
  34. package/config/plop/templates/add/component/{{componentName}}.fixtures.ts.hbs +13 -0
  35. package/config/plop/templates/add/component/{{componentName}}.stories.ts.hbs +18 -0
  36. package/config/plop/templates/add/component/{{componentName}}.styles.ts.hbs +8 -0
  37. package/config/plop/tsconfig.plop.json +11 -0
  38. package/config/storybook/MomentumStorybookTheme.js +41 -0
  39. package/config/storybook/main.js +21 -0
  40. package/config/storybook/manager.js +17 -0
  41. package/config/storybook/preview.js +63 -0
  42. package/config/storybook/provider/iconProvider.js +8 -0
  43. package/config/storybook/provider/themeProvider.js +31 -0
  44. package/config/storybook/public/background-graphic.png +0 -0
  45. package/config/storybook/public/fonts/Inter.var.woff2 +0 -0
  46. package/config/storybook/public/momentum-logo.png +0 -0
  47. package/config/storybook/themes/index.js +14 -0
  48. package/config/storybook/themes/themes.css +15 -0
  49. package/data/custom-elements.json +677 -0
  50. package/dist/browser/index.js +366 -0
  51. package/dist/browser/index.js.map +7 -0
  52. package/dist/components/avatar/avatar.component.d.ts +28 -0
  53. package/dist/components/avatar/avatar.component.js +79 -0
  54. package/dist/components/avatar/avatar.constants.d.ts +7 -0
  55. package/dist/components/avatar/avatar.constants.js +14 -0
  56. package/dist/components/avatar/avatar.styles.d.ts +2 -0
  57. package/dist/components/avatar/avatar.styles.js +20 -0
  58. package/dist/components/avatar/avatar.types.d.ts +1 -0
  59. package/dist/components/avatar/avatar.types.js +2 -0
  60. package/dist/components/avatar/index.d.ts +7 -0
  61. package/dist/components/avatar/index.js +7 -0
  62. package/dist/components/badge/badge.component.d.ts +51 -0
  63. package/dist/components/badge/badge.component.js +114 -0
  64. package/dist/components/badge/badge.constants.d.ts +8 -0
  65. package/dist/components/badge/badge.constants.js +15 -0
  66. package/dist/components/badge/badge.styles.d.ts +2 -0
  67. package/dist/components/badge/badge.styles.js +26 -0
  68. package/dist/components/badge/badge.types.d.ts +1 -0
  69. package/dist/components/badge/badge.types.js +2 -0
  70. package/dist/components/badge/index.d.ts +7 -0
  71. package/dist/components/badge/index.js +7 -0
  72. package/dist/components/icon/icon.component.d.ts +63 -0
  73. package/dist/components/icon/icon.component.js +158 -0
  74. package/dist/components/icon/icon.constants.d.ts +6 -0
  75. package/dist/components/icon/icon.constants.js +12 -0
  76. package/dist/components/icon/icon.styles.d.ts +2 -0
  77. package/dist/components/icon/icon.styles.js +15 -0
  78. package/dist/components/icon/icon.utils.d.ts +2 -0
  79. package/dist/components/icon/icon.utils.js +13 -0
  80. package/dist/components/icon/index.d.ts +7 -0
  81. package/dist/components/icon/index.js +7 -0
  82. package/dist/components/iconprovider/iconprovider.component.d.ts +34 -0
  83. package/dist/components/iconprovider/iconprovider.component.js +71 -0
  84. package/dist/components/iconprovider/iconprovider.constants.d.ts +7 -0
  85. package/dist/components/iconprovider/iconprovider.constants.js +14 -0
  86. package/dist/components/iconprovider/iconprovider.context.d.ts +9 -0
  87. package/dist/components/iconprovider/iconprovider.context.js +9 -0
  88. package/dist/components/iconprovider/index.d.ts +7 -0
  89. package/dist/components/iconprovider/index.js +7 -0
  90. package/dist/components/text/fonts.styles.d.ts +1 -0
  91. package/dist/components/text/fonts.styles.js +100 -0
  92. package/dist/components/text/index.d.ts +7 -0
  93. package/dist/components/text/index.js +7 -0
  94. package/dist/components/text/text.component.d.ts +29 -0
  95. package/dist/components/text/text.component.js +41 -0
  96. package/dist/components/text/text.constants.d.ts +9 -0
  97. package/dist/components/text/text.constants.js +28 -0
  98. package/dist/components/text/text.styles.d.ts +2 -0
  99. package/dist/components/text/text.styles.js +17 -0
  100. package/dist/components/text/text.types.d.ts +1 -0
  101. package/dist/components/text/text.types.js +2 -0
  102. package/dist/components/text/text.utils.d.ts +20 -0
  103. package/dist/components/text/text.utils.js +50 -0
  104. package/dist/components/themeprovider/index.d.ts +7 -0
  105. package/dist/components/themeprovider/index.js +7 -0
  106. package/dist/components/themeprovider/themeprovider.component.d.ts +48 -0
  107. package/dist/components/themeprovider/themeprovider.component.js +86 -0
  108. package/dist/components/themeprovider/themeprovider.constants.d.ts +10 -0
  109. package/dist/components/themeprovider/themeprovider.constants.js +31 -0
  110. package/dist/components/themeprovider/themeprovider.context.d.ts +9 -0
  111. package/dist/components/themeprovider/themeprovider.context.js +13 -0
  112. package/dist/components/themeprovider/themeprovider.styles.d.ts +2 -0
  113. package/dist/components/themeprovider/themeprovider.styles.js +13 -0
  114. package/dist/components/themeprovider/themeprovider.types.d.ts +5 -0
  115. package/dist/components/themeprovider/themeprovider.types.js +2 -0
  116. package/dist/components/themeprovider/themeprovider.utils.d.ts +9 -0
  117. package/dist/components/themeprovider/themeprovider.utils.js +10 -0
  118. package/dist/index.d.ts +8 -0
  119. package/dist/index.js +19 -0
  120. package/dist/models/component/component.component.d.ts +38 -0
  121. package/dist/models/component/component.component.js +45 -0
  122. package/dist/models/component/component.types.d.ts +15 -0
  123. package/dist/models/component/component.types.js +2 -0
  124. package/dist/models/component/index.d.ts +3 -0
  125. package/dist/models/component/index.js +5 -0
  126. package/dist/models/index.d.ts +4 -0
  127. package/dist/models/index.js +8 -0
  128. package/dist/models/provider/index.d.ts +2 -0
  129. package/dist/models/provider/index.js +5 -0
  130. package/dist/models/provider/provider.component.d.ts +70 -0
  131. package/dist/models/provider/provider.component.js +56 -0
  132. package/dist/models/provider/provider.styles.d.ts +2 -0
  133. package/dist/models/provider/provider.styles.js +14 -0
  134. package/dist/utils/provider/index.d.ts +13 -0
  135. package/dist/utils/provider/index.js +14 -0
  136. package/dist/utils/styles/index.d.ts +2 -0
  137. package/dist/utils/styles/index.js +14 -0
  138. package/dist/utils/tag-name/constants.d.ts +7 -0
  139. package/dist/utils/tag-name/constants.js +10 -0
  140. package/dist/utils/tag-name/index.d.ts +4 -0
  141. package/dist/utils/tag-name/index.js +10 -0
  142. package/dist/utils/types.d.ts +1 -0
  143. package/dist/utils/types.js +2 -0
  144. package/jest.config.js +3 -0
  145. package/package.json +78 -0
  146. package/scripts/copyFonts.js +31 -0
  147. package/scripts/copyIcons.js +31 -0
  148. package/scripts/copyTokens.js +24 -0
  149. package/src/components/avatar/__screenshots__/mdc-avatar.png +0 -0
  150. package/src/components/avatar/avatar.component.ts +74 -0
  151. package/src/components/avatar/avatar.constants.ts +12 -0
  152. package/src/components/avatar/avatar.e2e-test.ts +70 -0
  153. package/src/components/avatar/avatar.stories.ts +25 -0
  154. package/src/components/avatar/avatar.styles.ts +20 -0
  155. package/src/components/avatar/avatar.types.ts +1 -0
  156. package/src/components/avatar/index.ts +12 -0
  157. package/src/components/badge/__screenshots__/mdc-badge.png +0 -0
  158. package/src/components/badge/badge.component.ts +121 -0
  159. package/src/components/badge/badge.constants.ts +13 -0
  160. package/src/components/badge/badge.e2e-test.ts +68 -0
  161. package/src/components/badge/badge.stories.ts +33 -0
  162. package/src/components/badge/badge.styles.ts +26 -0
  163. package/src/components/badge/badge.types.ts +1 -0
  164. package/src/components/badge/index.ts +12 -0
  165. package/src/components/icon/__screenshots__/mdc-icon-default.png +0 -0
  166. package/src/components/icon/__screenshots__/mdc-icon-scale.png +0 -0
  167. package/src/components/icon/icon.component.ts +155 -0
  168. package/src/components/icon/icon.constants.ts +10 -0
  169. package/src/components/icon/icon.e2e-test.ts +101 -0
  170. package/src/components/icon/icon.stories.ts +34 -0
  171. package/src/components/icon/icon.styles.ts +15 -0
  172. package/src/components/icon/icon.utils.ts +13 -0
  173. package/src/components/icon/index.ts +12 -0
  174. package/src/components/iconprovider/__screenshots__/mdc-iconprovider.png +0 -0
  175. package/src/components/iconprovider/iconprovider.component.ts +76 -0
  176. package/src/components/iconprovider/iconprovider.constants.ts +12 -0
  177. package/src/components/iconprovider/iconprovider.context.ts +16 -0
  178. package/src/components/iconprovider/iconprovider.e2e-test.ts +65 -0
  179. package/src/components/iconprovider/iconprovider.stories.ts +27 -0
  180. package/src/components/iconprovider/iconprovider.stories.utils.ts +27 -0
  181. package/src/components/iconprovider/index.ts +12 -0
  182. package/src/components/text/__screenshots__/mdc-text-body-large.png +0 -0
  183. package/src/components/text/__screenshots__/mdc-text-body-regular.png +0 -0
  184. package/src/components/text/__screenshots__/mdc-text-body-small.png +0 -0
  185. package/src/components/text/__screenshots__/mdc-text-heading-1.png +0 -0
  186. package/src/components/text/__screenshots__/mdc-text-heading-2.png +0 -0
  187. package/src/components/text/__screenshots__/mdc-text-heading-3.png +0 -0
  188. package/src/components/text/__screenshots__/mdc-text-heading-4.png +0 -0
  189. package/src/components/text/__screenshots__/mdc-text-heading-5.png +0 -0
  190. package/src/components/text/__screenshots__/mdc-text-heading-6.png +0 -0
  191. package/src/components/text/__screenshots__/mdc-text-heading-7.png +0 -0
  192. package/src/components/text/__screenshots__/mdc-text-image-title.png +0 -0
  193. package/src/components/text/__screenshots__/mdc-text-label.png +0 -0
  194. package/src/components/text/fonts.styles.ts +99 -0
  195. package/src/components/text/index.ts +12 -0
  196. package/src/components/text/text.component.ts +51 -0
  197. package/src/components/text/text.constants.ts +27 -0
  198. package/src/components/text/text.e2e-test.ts +76 -0
  199. package/src/components/text/text.stories.ts +29 -0
  200. package/src/components/text/text.styles.ts +17 -0
  201. package/src/components/text/text.types.ts +13 -0
  202. package/src/components/text/text.utils.ts +51 -0
  203. package/src/components/themeprovider/__screenshots__/mdc-themeprovider-darkWebex.png +0 -0
  204. package/src/components/themeprovider/__screenshots__/mdc-themeprovider-lightWebex.png +0 -0
  205. package/src/components/themeprovider/index.ts +12 -0
  206. package/src/components/themeprovider/themeprovider.component.ts +91 -0
  207. package/src/components/themeprovider/themeprovider.constants.ts +32 -0
  208. package/src/components/themeprovider/themeprovider.context.ts +18 -0
  209. package/src/components/themeprovider/themeprovider.e2e-test.ts +89 -0
  210. package/src/components/themeprovider/themeprovider.stories.styles.css +22 -0
  211. package/src/components/themeprovider/themeprovider.stories.ts +38 -0
  212. package/src/components/themeprovider/themeprovider.stories.utils.ts +23 -0
  213. package/src/components/themeprovider/themeprovider.styles.ts +13 -0
  214. package/src/components/themeprovider/themeprovider.types.ts +7 -0
  215. package/src/components/themeprovider/themeprovider.utils.ts +16 -0
  216. package/src/index.ts +22 -0
  217. package/src/models/component/component.component.ts +46 -0
  218. package/src/models/component/component.types.ts +16 -0
  219. package/src/models/component/index.ts +7 -0
  220. package/src/models/index.ts +11 -0
  221. package/src/models/provider/index.ts +3 -0
  222. package/src/models/provider/provider.component.ts +87 -0
  223. package/src/models/provider/provider.styles.ts +14 -0
  224. package/src/stories/colors.mdx +32 -0
  225. package/src/stories/icons.mdx +13 -0
  226. package/src/stories/typography.mdx +20 -0
  227. package/src/utils/mixins/DisabledMixin.ts +19 -0
  228. package/src/utils/mixins/TabIndexMixin.ts +19 -0
  229. package/src/utils/provider/index.ts +21 -0
  230. package/src/utils/styles/index.ts +13 -0
  231. package/src/utils/tag-name/constants.ts +10 -0
  232. package/src/utils/tag-name/index.ts +15 -0
  233. package/src/utils/types.ts +1 -0
  234. package/tsconfig.json +45 -0
  235. package/tsconfig.module.json +47 -0
@@ -0,0 +1,155 @@
1
+ import { html } from 'lit';
2
+ import { property, state } from 'lit/decorators.js';
3
+ import styles from './icon.styles';
4
+ import { Component } from '../../models';
5
+ import providerUtils from '../../utils/provider';
6
+ import IconProvider from '../iconprovider/iconprovider.component';
7
+ import { dynamicSVGImport } from './icon.utils';
8
+ import { DEFAULTS } from './icon.constants';
9
+
10
+ /**
11
+ * Icon component, which has to be mounted inside of a `IconProvider`
12
+ * component.
13
+ *
14
+ * The `IconProvider` component defines where icons should be consumed from (`url`).
15
+ * This `Icon` component accepts the `name` attribute, which will be
16
+ * the file name of the icon to be loaded from the given `url`.
17
+ *
18
+ * Once fetched, the icon will be mounted. If fetching wasn't successful,
19
+ * nothing will be shown.
20
+ *
21
+ * The `scale` attribute allows scaling the icon based on the provided
22
+ * `length-unit` attribute (which will either come from the IconProvider or
23
+ * could be overridden per icon). For example:
24
+ * if `scale = 1` and `length-unit = 'em'`, the size of the icon will be
25
+ * `width: 1em; height: 1em`.
26
+ *
27
+ * For accessibility the `role` and `aria-label` of the icon can be set.
28
+ *
29
+ * @tag mdc-icon
30
+ * @tagname mdc-icon
31
+ */
32
+ class Icon extends Component {
33
+ @state()
34
+ private iconData?: HTMLElement;
35
+
36
+ @state()
37
+ private lengthUnitFromContext?: string;
38
+
39
+ /**
40
+ * Name of the icon (= filename)
41
+ */
42
+ @property({ type: String, reflect: true })
43
+ name?: string = DEFAULTS.NAME;
44
+
45
+ /**
46
+ * Scale of the icon (works in combination with length unit)
47
+ */
48
+ @property({ type: Number })
49
+ scale?: number = DEFAULTS.SCALE;
50
+
51
+ /**
52
+ * Length unit attribute for overridding length-unit from `IconProvider`
53
+ */
54
+ @property({ type: String, attribute: 'length-unit' })
55
+ lengthUnit?: string;
56
+
57
+ /**
58
+ * Role attribute to be set for accessibility
59
+ */
60
+ @property({ type: String })
61
+ override role: string | null = null;
62
+
63
+ /**
64
+ * Aria-label attribute to be set for accessibility
65
+ */
66
+ @property({ type: String, attribute: 'aria-label' })
67
+ override ariaLabel: string | null = null;
68
+
69
+ private iconProviderContext = providerUtils.consume({ host: this, context: IconProvider.Context });
70
+
71
+ /**
72
+ * Get Icon Data function which will fetch the icon (currently only svg)
73
+ * and sets state and attributes once fetched successfully
74
+ */
75
+ private async getIconData() {
76
+ if (this.iconProviderContext.value) {
77
+ const { fileExtension, url } = this.iconProviderContext.value;
78
+ if (url && fileExtension && this.name) {
79
+ const iconHtml = await dynamicSVGImport(url, this.name, fileExtension);
80
+
81
+ // update iconData state once fetched:
82
+ this.iconData = iconHtml as HTMLElement;
83
+
84
+ // when icon got fetched, set role and aria-label:
85
+ this.setRoleOnIcon();
86
+ this.setAriaLabelOnIcon();
87
+ }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Updates the size by setting the width and height
93
+ */
94
+ private updateSize() {
95
+ if (this.scale && (this.lengthUnit || this.lengthUnitFromContext)) {
96
+ const value = `${this.scale}${this.lengthUnit || this.lengthUnitFromContext}`;
97
+ this.style.width = value;
98
+ this.style.height = value;
99
+ }
100
+ }
101
+
102
+ private setRoleOnIcon() {
103
+ if (this.role) {
104
+ // pass through role attribute to svg if set on mdc-icon
105
+ this.iconData?.setAttribute('role', this.role);
106
+ } else {
107
+ this.iconData?.removeAttribute('role');
108
+ }
109
+ }
110
+
111
+ private setAriaLabelOnIcon() {
112
+ if (this.ariaLabel) {
113
+ // pass through aria-label attribute to svg if set on mdc-icon
114
+ this.iconData?.setAttribute('aria-label', this.ariaLabel);
115
+ } else {
116
+ this.iconData?.removeAttribute('aria-label');
117
+ }
118
+ }
119
+
120
+ override updated(changedProperties: Map<string, any>) {
121
+ super.updated(changedProperties);
122
+
123
+ if (changedProperties.has('name')) {
124
+ // fetch icon data if name changes:
125
+ this.getIconData().catch((err) => {
126
+ console.error(err);
127
+ });
128
+ }
129
+
130
+ if (changedProperties.has('role')) {
131
+ this.setRoleOnIcon();
132
+ }
133
+
134
+ if (changedProperties.has('ariaLabel')) {
135
+ this.setAriaLabelOnIcon();
136
+ }
137
+
138
+ if (changedProperties.has('scale') || changedProperties.has('lengthUnit')) {
139
+ this.updateSize();
140
+ }
141
+
142
+ if (this.lengthUnitFromContext !== this.iconProviderContext.value?.lengthUnit) {
143
+ this.lengthUnitFromContext = this.iconProviderContext.value?.lengthUnit;
144
+ this.updateSize();
145
+ }
146
+ }
147
+
148
+ override render() {
149
+ return html` ${this.iconData} `;
150
+ }
151
+
152
+ static override styles = styles;
153
+ }
154
+
155
+ export default Icon;
@@ -0,0 +1,10 @@
1
+ import utils from '../../utils/tag-name';
2
+
3
+ const TAG_NAME = utils.constructTagName('icon');
4
+
5
+ const DEFAULTS = {
6
+ NAME: undefined,
7
+ SCALE: 1,
8
+ };
9
+
10
+ export { TAG_NAME, DEFAULTS };
@@ -0,0 +1,101 @@
1
+ import { expect } from '@playwright/test';
2
+ import { ComponentsPage, test } from '../../../config/playwright/setup';
3
+ import steps from '../../../config/playwright/setup/steps/accessibility';
4
+
5
+ type SetupOptions = {
6
+ componentsPage: ComponentsPage;
7
+ name: string;
8
+ scale?: number;
9
+ role?: string;
10
+ ariaLabel?: string;
11
+ };
12
+ const setup = async (args: SetupOptions) => {
13
+ const { componentsPage, ...restArgs } = args;
14
+ await componentsPage.mount({
15
+ html: `
16
+ <mdc-icon
17
+ name="${restArgs.name}"
18
+ ${restArgs.scale ? `scale="${restArgs.scale}"` : ''}
19
+ ${restArgs.role ? `role="${restArgs.role}"` : ''}
20
+ ${restArgs.ariaLabel ? `aria-label="${restArgs.ariaLabel}"` : ''}
21
+ >
22
+ </mdc-icon>
23
+ `,
24
+ clearDocument: true,
25
+ });
26
+ const icon = componentsPage.page.locator('mdc-icon');
27
+ await icon.waitFor();
28
+ return icon;
29
+ };
30
+
31
+ test('mdc-icon', async ({ componentsPage }) => {
32
+ const name = 'accessibility-regular';
33
+ await setup({ componentsPage, name });
34
+
35
+ /**
36
+ * ACCESSIBILITY
37
+ */
38
+ await test.step('accessibility with default props', async () => {
39
+ await steps.automaticA11yCheckStep(componentsPage);
40
+ });
41
+
42
+ const iconWithRole = await setup({
43
+ componentsPage,
44
+ name,
45
+ role: 'graphics-document',
46
+ ariaLabel: 'test aria label',
47
+ });
48
+
49
+ await test.step('accessibility with role / aria-label passed in', async () => {
50
+ await steps.automaticA11yCheckStep(componentsPage);
51
+ });
52
+
53
+ /**
54
+ * VISUAL REGRESSION
55
+ */
56
+
57
+ // TODO: fix visual regression test on CI
58
+ // await test.step('visual-regression', async () => {
59
+ // await test.step('matches screenshot of element with role / aria-label passed in', async () => {
60
+ // await componentsPage.visualRegression.takeScreenshot('mdc-icon-default', { element: iconWithRole });
61
+ // });
62
+
63
+ // await test.step('matches screenshot of element with scale set to 2', async () => {
64
+ // const iconScaled = await setup({
65
+ // componentsPage,
66
+ // name,
67
+ // scale: 2,
68
+ // });
69
+ // await componentsPage.visualRegression.takeScreenshot('mdc-icon-scale', { element: iconScaled });
70
+ // });
71
+ // });
72
+
73
+ /**
74
+ * ATTRIBUTES
75
+ */
76
+ await test.step('attributes', async () => {
77
+ await test.step('attributes should be present on component by default', async () => {
78
+ const icon = await setup({ componentsPage, name });
79
+ await expect(icon).toHaveAttribute('name', name);
80
+ await expect(icon).toHaveAttribute('style', 'width: 1em; height: 1em;');
81
+ });
82
+
83
+ await test.step('attributes should be present on component with scale passed in', async () => {
84
+ const icon = await setup({ componentsPage, name, scale: 2 });
85
+ await expect(icon).toHaveAttribute('name', name);
86
+ await expect(icon).toHaveAttribute('scale', '2');
87
+ await expect(icon).toHaveAttribute('style', 'width: 2em; height: 2em;');
88
+ });
89
+
90
+ await test.step('attributes should be present on component with role / aria-label passed in', async () => {
91
+ const iconWithRole = await setup({
92
+ componentsPage,
93
+ name,
94
+ role: 'graphics-document',
95
+ ariaLabel: 'test aria label',
96
+ });
97
+ await expect(iconWithRole).toHaveAttribute('name', name);
98
+ await expect(iconWithRole).toHaveAttribute('style', 'width: 1em; height: 1em;');
99
+ });
100
+ });
101
+ });
@@ -0,0 +1,34 @@
1
+ import type { Meta, StoryObj, Args } from '@storybook/web-components';
2
+ import '.';
3
+ import { html } from 'lit';
4
+
5
+ const render = (args: Args) => html` <mdc-icon name="${args.name}" scale="${args.scale}"></mdc-icon> `;
6
+ const renderAccessibility = (args: Args) => html`
7
+ <mdc-icon name="${args.name}" scale="${args.scale}" role="${args.role}" aria-label="${args['aria-label']}"></mdc-icon>
8
+ `;
9
+
10
+ const meta: Meta = {
11
+ tags: ['autodocs'],
12
+ component: 'mdc-icon',
13
+ render,
14
+ argTypes: {},
15
+ };
16
+
17
+ export default meta;
18
+
19
+ export const Primary: StoryObj = {
20
+ args: {
21
+ name: 'accessibility-regular',
22
+ scale: 1,
23
+ },
24
+ };
25
+
26
+ export const Accessibility: StoryObj = {
27
+ render: renderAccessibility,
28
+ args: {
29
+ name: 'accessibility-regular',
30
+ scale: 1,
31
+ role: 'graphics-document',
32
+ 'aria-label': 'This is the accessibility icon',
33
+ },
34
+ };
@@ -0,0 +1,15 @@
1
+ import { css } from 'lit';
2
+ import { hostFitContentStyles } from '../../utils/styles';
3
+
4
+ const styles = [
5
+ hostFitContentStyles,
6
+ css`
7
+ svg {
8
+ height: 100%;
9
+ width: 100%;
10
+ fill: currentColor;
11
+ }
12
+ `,
13
+ ];
14
+
15
+ export default styles;
@@ -0,0 +1,13 @@
1
+ /* eslint-disable implicit-arrow-linebreak */
2
+
3
+ const dynamicSVGImport = async (url: string, name: string, fileExtension: string): Promise<Element> =>
4
+ fetch(`${url}/${name}.${fileExtension}`)
5
+ .then((response) => {
6
+ if (!response.ok) {
7
+ throw new Error('There was a problem while fetching the icon!');
8
+ }
9
+ return response.text();
10
+ })
11
+ .then((iconResponse) => new DOMParser().parseFromString(iconResponse, 'text/html').body.children[0]);
12
+
13
+ export { dynamicSVGImport };
@@ -0,0 +1,12 @@
1
+ import Icon from './icon.component';
2
+ import { TAG_NAME } from './icon.constants';
3
+
4
+ Icon.register(TAG_NAME);
5
+
6
+ declare global {
7
+ interface HTMLElementTagNameMap {
8
+ ['mdc-icon']: Icon
9
+ }
10
+ }
11
+
12
+ export default Icon;
@@ -0,0 +1,76 @@
1
+ import { property } from 'lit/decorators.js';
2
+ import { Provider } from '../../models';
3
+ import IconProviderContext from './iconprovider.context';
4
+ import { ALLOWED_FILE_EXTENSIONS, DEFAULTS } from './iconprovider.constants';
5
+
6
+ /**
7
+ * IconProvider component, which allows to be consumed from sub components
8
+ * (see `providerUtils.consume` for how to consume)
9
+ *
10
+ * Bundling icons will be up to the consumer of this component, such
11
+ * that only a url has to be passed in from which the icons will be
12
+ * fetched.
13
+ *
14
+ * @tag mdc-iconprovider
15
+ * @tagname mdc-iconprovider
16
+ */
17
+ class IconProvider extends Provider<IconProviderContext> {
18
+ constructor() {
19
+ // initialise the context by running the Provider constructor:
20
+ super({
21
+ context: IconProviderContext.context,
22
+ initialValue: new IconProviderContext(),
23
+ });
24
+ }
25
+
26
+ public static get Context() {
27
+ return IconProviderContext.context;
28
+ }
29
+
30
+ /**
31
+ * Url of where icons will be fetched from
32
+ */
33
+ @property({ type: String })
34
+ url?: string;
35
+
36
+ /**
37
+ * File extension of icons, default: 'svg'
38
+ */
39
+ @property({ type: String, attribute: 'file-extension', reflect: true })
40
+ fileExtension?: string = DEFAULTS.FILE_EXTENSION;
41
+
42
+ /**
43
+ * Length unit used for sizing of icons, default: 'em'
44
+ */
45
+ @property({ type: String, attribute: 'length-unit', reflect: true })
46
+ lengthUnit?: string = DEFAULTS.LENGTH_UNIT;
47
+
48
+ private updateValuesInContext() {
49
+ // only update fileExtension on context if its an allowed fileExtension
50
+ if (this.fileExtension && ALLOWED_FILE_EXTENSIONS.includes(this.fileExtension)) {
51
+ this.context.value.fileExtension = this.fileExtension;
52
+ }
53
+ this.context.value.url = this.url;
54
+ this.context.value.lengthUnit = this.lengthUnit;
55
+ }
56
+
57
+ protected updateContext(): void {
58
+ let shouldUpdateConsumers = false;
59
+
60
+ if (
61
+ this.context.value.fileExtension !== this.fileExtension
62
+ || this.context.value.url !== this.url
63
+ || this.context.value.lengthUnit !== this.lengthUnit
64
+ ) {
65
+ this.updateValuesInContext();
66
+
67
+ shouldUpdateConsumers = true;
68
+ }
69
+
70
+ if (shouldUpdateConsumers) {
71
+ this.context.updateObservers();
72
+ }
73
+ }
74
+ }
75
+
76
+ export default IconProvider;
@@ -0,0 +1,12 @@
1
+ import utils from '../../utils/tag-name';
2
+
3
+ const TAG_NAME = utils.constructTagName('iconprovider');
4
+
5
+ const ALLOWED_FILE_EXTENSIONS = ['svg'];
6
+
7
+ const DEFAULTS = {
8
+ FILE_EXTENSION: 'svg',
9
+ LENGTH_UNIT: 'em',
10
+ };
11
+
12
+ export { TAG_NAME, DEFAULTS, ALLOWED_FILE_EXTENSIONS };
@@ -0,0 +1,16 @@
1
+ import { createContext } from '@lit-labs/context';
2
+
3
+ import { TAG_NAME } from './iconprovider.constants';
4
+
5
+ class IconProviderContext {
6
+ public fileExtension?: string;
7
+
8
+ public url?: string;
9
+
10
+ public lengthUnit?: string;
11
+
12
+ // create typed lit context as part of the IconProviderContext
13
+ public static context = createContext<IconProviderContext>(TAG_NAME);
14
+ }
15
+
16
+ export default IconProviderContext;
@@ -0,0 +1,65 @@
1
+ import { expect } from '@playwright/test';
2
+ import { ComponentsPage, test } from '../../../config/playwright/setup';
3
+ import steps from '../../../config/playwright/setup/steps/accessibility';
4
+ import { DEFAULTS } from './iconprovider.constants';
5
+
6
+ type SetupOptions = {
7
+ componentsPage: ComponentsPage;
8
+ url: string;
9
+ fileExtension?: string;
10
+ lengthUnit?: string;
11
+ };
12
+ const setup = async (args: SetupOptions) => {
13
+ const { componentsPage, ...restArgs } = args;
14
+ await componentsPage.mount({
15
+ html: `
16
+ <mdc-iconprovider
17
+ url="${restArgs.url}"
18
+ id="local"
19
+ ${restArgs.fileExtension ? `file-extension="${restArgs.fileExtension}"` : ''}
20
+ ${restArgs.lengthUnit ? `length-unit="${restArgs.lengthUnit}"` : ''}
21
+ >
22
+ <mdc-icon name="accessibility-regular" scale="2"></mdc-icon>
23
+ </mdc-iconprovider>
24
+ `,
25
+ });
26
+ };
27
+
28
+ test('mdc-iconprovider', async ({ componentsPage }) => {
29
+ const url = '/dist/icons/svg';
30
+ await setup({ componentsPage, url });
31
+ const iconprovider = componentsPage.page.locator('mdc-iconprovider#local');
32
+
33
+ // initial check for the iconprovider be visible on the screen:
34
+ await iconprovider.waitFor();
35
+
36
+ /**
37
+ * ACCESSIBILITY
38
+ */
39
+ await test.step('accessibility', async () => {
40
+ await steps.automaticA11yCheckStep(componentsPage);
41
+ });
42
+
43
+ /**
44
+ * VISUAL REGRESSION
45
+ */
46
+ // TODO: fix visual regression test on CI
47
+ // await test.step('visual-regression', async () => {
48
+ // await test.step('matches screenshot of element with default values', async () => {
49
+ // await componentsPage.visualRegression.takeScreenshot('mdc-iconprovider', {
50
+ // element: iconprovider,
51
+ // });
52
+ // });
53
+ // });
54
+
55
+ /**
56
+ * ATTRIBUTES
57
+ */
58
+ await test.step('attributes', async () => {
59
+ await test.step('attribute X should be present on component by default', async () => {
60
+ await expect(iconprovider).toHaveAttribute('url', url);
61
+ await expect(iconprovider).toHaveAttribute('file-extension', DEFAULTS.FILE_EXTENSION);
62
+ await expect(iconprovider).toHaveAttribute('length-unit', DEFAULTS.LENGTH_UNIT);
63
+ });
64
+ });
65
+ });
@@ -0,0 +1,27 @@
1
+ import type { Meta, StoryObj, Args } from '@storybook/web-components';
2
+ import '.';
3
+ import './iconprovider.stories.utils';
4
+ import { html } from 'lit';
5
+
6
+ const render = (args: Args) => html`
7
+ <mdc-iconprovider url="${args.url}" file-extension="${args.fileExtension}" length-unit="${args.lengthUnit}">
8
+ <mdc-subcomponent-icon></mdc-subcomponent-icon>
9
+ </mdc-iconprovider>
10
+ `;
11
+
12
+ const meta: Meta = {
13
+ tags: ['autodocs'],
14
+ component: 'mdc-iconprovider',
15
+ render,
16
+ argTypes: {},
17
+ };
18
+
19
+ export default meta;
20
+
21
+ export const Primary: StoryObj = {
22
+ args: {
23
+ url: '/test',
24
+ fileExtension: 'svg',
25
+ lengthUnit: 'em',
26
+ },
27
+ };
@@ -0,0 +1,27 @@
1
+ import { html } from 'lit';
2
+ import { Component } from '../../models';
3
+ import IconProvider from './iconprovider.component';
4
+ import providerUtils from '../../utils/provider';
5
+
6
+ // Subcomponent to be rendered in storybook, to showcase that the
7
+ // theme can be consumed as a subcomponent
8
+ class SubComponentIconProvider extends Component {
9
+ currentTheme?: string;
10
+
11
+ private iconProviderContext = providerUtils.consume({ host: this, context: IconProvider.Context });
12
+
13
+ override render() {
14
+ return html`
15
+ <p>URL: ${this.iconProviderContext.value?.url}</p>
16
+ <p>File Extension: ${this.iconProviderContext.value?.fileExtension}</p>
17
+ <p>Length Unit: ${this.iconProviderContext.value?.lengthUnit}</p>
18
+ `;
19
+ }
20
+ }
21
+
22
+ SubComponentIconProvider.register('mdc-subcomponent-icon');
23
+ declare global {
24
+ interface HTMLElementTagNameMap {
25
+ ['mdc-subcomponent-icon']: SubComponentIconProvider;
26
+ }
27
+ }
@@ -0,0 +1,12 @@
1
+ import IconProvider from './iconprovider.component';
2
+ import { TAG_NAME } from './iconprovider.constants';
3
+
4
+ IconProvider.register(TAG_NAME);
5
+
6
+ export default IconProvider;
7
+
8
+ declare global {
9
+ interface HTMLElementTagNameMap {
10
+ ['mdc-iconprovider']: IconProvider
11
+ }
12
+ }