@covalent/components 0.0.0-COVALENT

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 (264) hide show
  1. package/.babelrc +10 -0
  2. package/.eslintrc.json +18 -0
  3. package/.storybook/main.js +34 -0
  4. package/.storybook/manager-head.html +62 -0
  5. package/.storybook/manager.js +1 -0
  6. package/.storybook/preview-head.html +20 -0
  7. package/.storybook/preview.js +20 -0
  8. package/README.md +179 -0
  9. package/component-config.json +313 -0
  10. package/index.html +288 -0
  11. package/jest.config.js +18 -0
  12. package/package.json +315 -0
  13. package/project.json +104 -0
  14. package/public/index.scss +1 -0
  15. package/public/reset.css +128 -0
  16. package/src/action-ribbon/_action-ribbon.theme.scss +90 -0
  17. package/src/action-ribbon/action-ribbon-base.ts +164 -0
  18. package/src/action-ribbon/action-ribbon.scss +20 -0
  19. package/src/action-ribbon/action-ribbon.spec.ts +11 -0
  20. package/src/action-ribbon/action-ribbon.stories.js +78 -0
  21. package/src/action-ribbon/action-ribbon.ts +26 -0
  22. package/src/alert/_alert.theme.scss +116 -0
  23. package/src/alert/alert-base.ts +175 -0
  24. package/src/alert/alert.scss +55 -0
  25. package/src/alert/alert.spec.ts +26 -0
  26. package/src/alert/alert.stories.js +76 -0
  27. package/src/alert/alert.ts +26 -0
  28. package/src/app-shell/app-shell.scss +387 -0
  29. package/src/app-shell/app-shell.stories.js +323 -0
  30. package/src/app-shell/app-shell.ts +388 -0
  31. package/src/badge/badge.scss +60 -0
  32. package/src/badge/badge.spec.ts +40 -0
  33. package/src/badge/badge.stories.js +88 -0
  34. package/src/badge/badge.ts +122 -0
  35. package/src/button/Overview.mdx +160 -0
  36. package/src/button/button.scss +28 -0
  37. package/src/button/button.spec.ts +11 -0
  38. package/src/button/button.stories.js +102 -0
  39. package/src/button/button.ts +17 -0
  40. package/src/card/card-base.ts +69 -0
  41. package/src/card/card.scss +45 -0
  42. package/src/card/card.spec.ts +11 -0
  43. package/src/card/card.ts +21 -0
  44. package/src/card/cards.stories.js +40 -0
  45. package/src/checkbox/checkbox.scss +8 -0
  46. package/src/checkbox/checkbox.spec.ts +11 -0
  47. package/src/checkbox/checkbox.stories.js +61 -0
  48. package/src/checkbox/checkbox.ts +18 -0
  49. package/src/chips/chip-base.ts +276 -0
  50. package/src/chips/chip-set-base.ts +184 -0
  51. package/src/chips/chip-set.scss +15 -0
  52. package/src/chips/chip-set.spec.ts +11 -0
  53. package/src/chips/chip-set.ts +27 -0
  54. package/src/chips/chip.scss +40 -0
  55. package/src/chips/chip.spec.ts +11 -0
  56. package/src/chips/chip.ts +26 -0
  57. package/src/chips/chips.stories.js +81 -0
  58. package/src/circular-progress/circular-progress.spec.ts +11 -0
  59. package/src/circular-progress/circular-progress.stories.js +40 -0
  60. package/src/circular-progress/circular-progress.ts +16 -0
  61. package/src/code-editor/code-editor.scss +20 -0
  62. package/src/code-editor/code-editor.spec.ts +11 -0
  63. package/src/code-editor/code-editor.stories.js +44 -0
  64. package/src/code-editor/code-editor.theme.ts +199 -0
  65. package/src/code-editor/code-editor.ts +231 -0
  66. package/src/code-snippet/code-snippet.scss +126 -0
  67. package/src/code-snippet/code-snippet.spec.ts +11 -0
  68. package/src/code-snippet/code-snippet.stories.js +134 -0
  69. package/src/code-snippet/code-snippet.ts +93 -0
  70. package/src/data-table/_data-table.theme.scss +39 -0
  71. package/src/data-table/data-table.stories.js +24 -0
  72. package/src/data-table/data-table.stories.scss +11 -0
  73. package/src/dialog/Overview.mdx +39 -0
  74. package/src/dialog/dialog.scss +17 -0
  75. package/src/dialog/dialog.spec.ts +11 -0
  76. package/src/dialog/dialog.stories.js +89 -0
  77. package/src/dialog/dialog.ts +44 -0
  78. package/src/drawer/drawer.scss +4 -0
  79. package/src/drawer/drawer.spec.ts +11 -0
  80. package/src/drawer/drawer.ts +18 -0
  81. package/src/empty-state/_empty-state.theme.scss +0 -0
  82. package/src/empty-state/empty-state.scss +67 -0
  83. package/src/empty-state/empty-state.spec.ts +11 -0
  84. package/src/empty-state/empty-state.stories.js +117 -0
  85. package/src/empty-state/empty-state.ts +61 -0
  86. package/src/expansion-panel/Overview.mdx +108 -0
  87. package/src/expansion-panel/expansion-panel-incorrect-example.png +0 -0
  88. package/src/expansion-panel/expansion-panel-item.scss +243 -0
  89. package/src/expansion-panel/expansion-panel-item.ts +95 -0
  90. package/src/expansion-panel/expansion-panel.spec.ts +11 -0
  91. package/src/expansion-panel/expansion-panel.stories.js +76 -0
  92. package/src/expansion-panel/expansion-panel.ts +94 -0
  93. package/src/focused-page/focused-page.scss +113 -0
  94. package/src/focused-page/focused-page.spec.ts +11 -0
  95. package/src/focused-page/focused-page.stories.js +167 -0
  96. package/src/focused-page/focused-page.ts +201 -0
  97. package/src/formfield/formfield.scss +8 -0
  98. package/src/formfield/formfield.spec.ts +11 -0
  99. package/src/formfield/formfield.ts +24 -0
  100. package/src/full-screen-dialog/full-screen-dialog.scss +37 -0
  101. package/src/full-screen-dialog/full-screen-dialog.spec.ts +11 -0
  102. package/src/full-screen-dialog/full-screen-dialog.stories.js +172 -0
  103. package/src/full-screen-dialog/full-screen-dialog.ts +84 -0
  104. package/src/icon/_icon-list.ts +316 -0
  105. package/src/icon/icon-demo.scss +25 -0
  106. package/src/icon/icon-demo.ts +37 -0
  107. package/src/icon/icon.spec.ts +11 -0
  108. package/src/icon/icon.stories.js +55 -0
  109. package/src/icon/icon.ts +16 -0
  110. package/src/icon-button/_icon-button.theme.scss +9 -0
  111. package/src/icon-button/icon-button.scss +12 -0
  112. package/src/icon-button/icon-button.spec.ts +11 -0
  113. package/src/icon-button/icon-button.stories.js +24 -0
  114. package/src/icon-button/icon-button.ts +19 -0
  115. package/src/icon-button-toggle/icon-button-toggle.scss +19 -0
  116. package/src/icon-button-toggle/icon-button-toggle.spec.ts +11 -0
  117. package/src/icon-button-toggle/icon-button-toggle.stories.js +32 -0
  118. package/src/icon-button-toggle/icon-button-toggle.ts +50 -0
  119. package/src/icon-checkbox/icon-check-toggle.ts +64 -0
  120. package/src/icon-checkbox/icon-check.spec.ts +11 -0
  121. package/src/icon-checkbox/icon-checkbox.scss +95 -0
  122. package/src/icon-checkbox/icon-checkbox.stories.js +77 -0
  123. package/src/icon-lockup/icon-lockup.scss +47 -0
  124. package/src/icon-lockup/icon-lockup.spec.ts +11 -0
  125. package/src/icon-lockup/icon-lockup.stories.js +93 -0
  126. package/src/icon-lockup/icon-lockup.ts +125 -0
  127. package/src/icon-radio/icon-radio-toggle.ts +43 -0
  128. package/src/icon-radio/icon-radio.scss +63 -0
  129. package/src/icon-radio/icon-radio.spec.ts +11 -0
  130. package/src/icon-radio/icon-radio.stories.js +23 -0
  131. package/src/index.scss +1 -0
  132. package/src/index.ts +57 -0
  133. package/src/linear-progress/linear-progress.scss +4 -0
  134. package/src/linear-progress/linear-progress.spec.ts +11 -0
  135. package/src/linear-progress/linear-progress.stories.js +43 -0
  136. package/src/linear-progress/linear-progress.ts +18 -0
  137. package/src/list/Overview.mdx +91 -0
  138. package/src/list/_list.theme.scss +100 -0
  139. package/src/list/check-list-item.spec.ts +11 -0
  140. package/src/list/check-list-item.ts +25 -0
  141. package/src/list/list-item.scss +56 -0
  142. package/src/list/list-item.spec.ts +11 -0
  143. package/src/list/list-item.ts +31 -0
  144. package/src/list/list.scss +25 -0
  145. package/src/list/list.stories.js +120 -0
  146. package/src/list/list.ts +23 -0
  147. package/src/list/nav-list-item.scss +159 -0
  148. package/src/list/nav-list-item.ts +223 -0
  149. package/src/list/radio-list-item.ts +25 -0
  150. package/src/menu/menu.scss +3 -0
  151. package/src/menu/menu.spec.ts +11 -0
  152. package/src/menu/menu.stories.js +110 -0
  153. package/src/menu/menu.ts +23 -0
  154. package/src/notebook-cell/notebook-cell.scss +185 -0
  155. package/src/notebook-cell/notebook-cell.spec.ts +11 -0
  156. package/src/notebook-cell/notebook-cell.stories.js +87 -0
  157. package/src/notebook-cell/notebook-cell.ts +300 -0
  158. package/src/radio/radio.scss +3 -0
  159. package/src/radio/radio.spec.ts +11 -0
  160. package/src/radio/radio.stories.js +56 -0
  161. package/src/radio/radio.ts +18 -0
  162. package/src/select/select.scss +16 -0
  163. package/src/select/select.spec.ts +11 -0
  164. package/src/select/select.stories.js +57 -0
  165. package/src/select/select.ts +18 -0
  166. package/src/side-sheet/side-sheet.scss +49 -0
  167. package/src/side-sheet/side-sheet.spec.ts +11 -0
  168. package/src/side-sheet/side-sheet.stories.js +96 -0
  169. package/src/side-sheet/side-sheet.ts +37 -0
  170. package/src/skeleton/_skeleton.styles.scss +24 -0
  171. package/src/skeleton/skeleton.stories.js +77 -0
  172. package/src/slider/slider-range.ts +16 -0
  173. package/src/slider/slider.spec.ts +11 -0
  174. package/src/slider/slider.stories.js +54 -0
  175. package/src/slider/slider.ts +16 -0
  176. package/src/snackbar/snackbar.scss +8 -0
  177. package/src/snackbar/snackbar.spec.ts +11 -0
  178. package/src/snackbar/snackbar.stories.js +42 -0
  179. package/src/snackbar/snackbar.ts +18 -0
  180. package/src/status-dialog/status-dialog.scss +204 -0
  181. package/src/status-dialog/status-dialog.spec.ts +48 -0
  182. package/src/status-dialog/status-dialog.stories.js +136 -0
  183. package/src/status-dialog/status-dialog.ts +188 -0
  184. package/src/status-header/_status-header.theme.scss +79 -0
  185. package/src/status-header/status-header-base.ts +42 -0
  186. package/src/status-header/status-header-item.scss +17 -0
  187. package/src/status-header/status-header-item.spec.ts +11 -0
  188. package/src/status-header/status-header-item.ts +32 -0
  189. package/src/status-header/status-header.scss +57 -0
  190. package/src/status-header/status-header.spec.ts +11 -0
  191. package/src/status-header/status-header.stories.js +114 -0
  192. package/src/status-header/status-header.ts +26 -0
  193. package/src/switch/switch.scss +17 -0
  194. package/src/switch/switch.spec.ts +11 -0
  195. package/src/switch/switch.stories.js +41 -0
  196. package/src/switch/switch.ts +18 -0
  197. package/src/tab/Overview.mdx +38 -0
  198. package/src/tab/tab-bar.spec.ts +11 -0
  199. package/src/tab/tab-bar.ts +16 -0
  200. package/src/tab/tab.scss +10 -0
  201. package/src/tab/tab.spec.ts +11 -0
  202. package/src/tab/tab.stories.js +30 -0
  203. package/src/tab/tab.ts +18 -0
  204. package/src/text-lockup/text-lockup.scss +66 -0
  205. package/src/text-lockup/text-lockup.spec.ts +11 -0
  206. package/src/text-lockup/text-lockup.stories.js +67 -0
  207. package/src/text-lockup/text-lockup.ts +55 -0
  208. package/src/textarea/textarea.spec.ts +11 -0
  209. package/src/textarea/textarea.stories.js +39 -0
  210. package/src/textarea/textarea.ts +19 -0
  211. package/src/textfield/textfield.scss +34 -0
  212. package/src/textfield/textfield.spec.ts +11 -0
  213. package/src/textfield/textfield.stories.js +60 -0
  214. package/src/textfield/textfield.ts +25 -0
  215. package/src/theme/_index.scss +46 -0
  216. package/src/theme/prebuilt/dark-theme.scss +17 -0
  217. package/src/theme/prebuilt/light-theme.scss +17 -0
  218. package/src/toolbar/toolbar.scss +37 -0
  219. package/src/toolbar/toolbar.spec.ts +11 -0
  220. package/src/toolbar/toolbar.stories.js +66 -0
  221. package/src/toolbar/toolbar.ts +27 -0
  222. package/src/tooltip/tooltip.scss +16 -0
  223. package/src/tooltip/tooltip.spec.ts +11 -0
  224. package/src/tooltip/tooltip.stories.js +72 -0
  225. package/src/tooltip/tooltip.ts +185 -0
  226. package/src/top-app-bar/top-app-bar-fixed.ts +23 -0
  227. package/src/top-app-bar/top-app-bar.scss +14 -0
  228. package/src/top-app-bar/top-app-bar.spec.ts +11 -0
  229. package/src/top-app-bar/top-app-bar.stories.js +41 -0
  230. package/src/top-app-bar/top-app-bar.ts +23 -0
  231. package/src/tree-list/tree-list-item.scss +96 -0
  232. package/src/tree-list/tree-list-item.spec.ts +11 -0
  233. package/src/tree-list/tree-list-item.ts +87 -0
  234. package/src/tree-list/tree-list.scss +13 -0
  235. package/src/tree-list/tree-list.spec.ts +11 -0
  236. package/src/tree-list/tree-list.stories.js +151 -0
  237. package/src/tree-list/tree-list.ts +53 -0
  238. package/src/typography/typography.scss +45 -0
  239. package/src/typography/typography.spec.ts +11 -0
  240. package/src/typography/typography.stories.js +23 -0
  241. package/src/typography/typography.ts +27 -0
  242. package/stories/Introduction.mdx +47 -0
  243. package/stories/color-use.mdx +509 -0
  244. package/stories/demos/dialog.component.html +187 -0
  245. package/stories/demos/dialog.component.js +57 -0
  246. package/stories/demos/grid.content.html +99 -0
  247. package/stories/demos/lorem-ipsum.content.html +338 -0
  248. package/stories/demos/material-web.content.html +2125 -0
  249. package/stories/demos/table-column-sorting.content.html +92 -0
  250. package/stories/demos/table-pagination.content.html +139 -0
  251. package/stories/demos/table-progress-indicator.content.html +77 -0
  252. package/stories/demos/table-row-selection.content.html +219 -0
  253. package/stories/demos/table.content.html +64 -0
  254. package/stories/demos/top-app-bar.component.js +57 -0
  255. package/stories/guide-representing-state.mdx +282 -0
  256. package/stories/info-and-help.mdx +484 -0
  257. package/stories/item-detail-and-editing.mdx +529 -0
  258. package/stories/markdown-elements.mdx +194 -0
  259. package/stories/writing-and-naming.mdx +157 -0
  260. package/tsconfig.json +34 -0
  261. package/tsconfig.lib.json +17 -0
  262. package/tsconfig.spec.json +14 -0
  263. package/types.d.ts +15 -0
  264. package/vite.config.js +58 -0
@@ -0,0 +1,388 @@
1
+ import { css, html, nothing, unsafeCSS } from 'lit';
2
+ import {
3
+ customElement,
4
+ property,
5
+ queryAssignedElements,
6
+ } from 'lit/decorators.js';
7
+ import { classMap } from 'lit/directives/class-map.js';
8
+ import { DrawerBase } from '@material/mwc-drawer/mwc-drawer-base';
9
+ import styles from './app-shell.scss?inline';
10
+
11
+ import '../top-app-bar/top-app-bar-fixed';
12
+ import '../icon-button/icon-button';
13
+ import '../card/card';
14
+
15
+ declare global {
16
+ interface HTMLElementTagNameMap {
17
+ 'cv-app-shell': CovalentAppShell;
18
+ }
19
+ }
20
+
21
+ /**
22
+ * App Shell element.
23
+ *
24
+ * @slot - This element has a slot
25
+ */
26
+ @customElement('cv-app-shell')
27
+ export class CovalentAppShell extends DrawerBase {
28
+ static override styles = [
29
+ css`
30
+ ${unsafeCSS(styles)}
31
+ `,
32
+ ];
33
+
34
+ element = document.querySelector('.help');
35
+ helpWidth = 0;
36
+ private _startX!: number;
37
+ private _startWidth!: number;
38
+ private _resizing = false;
39
+
40
+ @queryAssignedElements({ slot: 'navigation' })
41
+ navigationListElements!: HTMLElement[];
42
+
43
+ @queryAssignedElements({ slot: 'user-menu' })
44
+ userMenuElements!: HTMLElement[];
45
+
46
+ /**
47
+ * The name of the application to show in the small app bar
48
+ */
49
+ @property()
50
+ appName = '';
51
+
52
+ /**
53
+ * The name of the section shown above the navigation
54
+ */
55
+ @property()
56
+ sectionName = '';
57
+
58
+ @property({ type: Boolean, reflect: true })
59
+ drawerOpen = false;
60
+
61
+ @property({ type: Boolean, reflect: true })
62
+ helpOpen = false;
63
+
64
+ /**
65
+ * Make help resizable with drag/drop handle
66
+ */
67
+ @property({ type: Boolean, reflect: true })
68
+ helpResizable = false;
69
+
70
+ /**
71
+ * Wrap the main area with a contained card surface
72
+ */
73
+ @property({ type: Boolean, reflect: true })
74
+ contained = false;
75
+
76
+ /**
77
+ * Force the left navigation visibly open
78
+ */
79
+ @property({ type: Boolean, reflect: true })
80
+ forcedOpen = false;
81
+
82
+ /**
83
+ * Make the content area full width
84
+ */
85
+ @property({ type: Boolean, reflect: true })
86
+ fullWidth = false;
87
+
88
+ private hovered = false;
89
+ private hoverTimeout: any | undefined;
90
+ private hoverEntryDuration = 250;
91
+ private hoverExitDuration = 250;
92
+
93
+ constructor() {
94
+ super();
95
+ this.resizeEvent();
96
+ this._resize = this._resize.bind(this);
97
+ this._stopResize = this._stopResize.bind(this);
98
+ this._startResizing = this._startResizing.bind(this);
99
+ this._setupEventListeners();
100
+ window.addEventListener('DOMContentLoaded', () => {
101
+ this.setupHelpPanelListeners();
102
+ const storedWidth = localStorage.getItem('helpWidth');
103
+ if (storedWidth) {
104
+ this.helpWidth = parseInt(storedWidth, 10);
105
+ this.updateHelpPanelWidth();
106
+ }
107
+ });
108
+ }
109
+
110
+ setupHelpPanelListeners() {
111
+ const helpToggle = document.querySelector('.help-item');
112
+ const helpClose = document.querySelector('.help-close');
113
+
114
+ helpToggle?.addEventListener('click', () => {
115
+ this.toggleHelpPanel();
116
+ });
117
+
118
+ helpClose?.addEventListener('click', () => {
119
+ this.toggleHelpPanel(false);
120
+ });
121
+ }
122
+
123
+ toggleHelpPanel(open?: boolean) {
124
+ this.helpOpen = open !== undefined ? open : !this.helpOpen;
125
+ if (this.helpOpen) {
126
+ const storedWidth = localStorage.getItem('helpWidth');
127
+ this.helpWidth = storedWidth ? parseInt(storedWidth, 10) : 320;
128
+ } else {
129
+ this.helpWidth = 0;
130
+ }
131
+ this.updateHelpPanelWidth();
132
+ this.requestUpdate();
133
+ }
134
+
135
+ private _setupEventListeners() {
136
+ window.addEventListener('DOMContentLoaded', () => {
137
+ const helpToggle = document.querySelector('.help-item');
138
+ const helpClose = document.querySelector('.help-close');
139
+
140
+ helpToggle?.addEventListener('click', () => {
141
+ this.helpOpen = !this.helpOpen;
142
+ this.helpWidth = this.helpOpen ? 320 : 0;
143
+ this.requestUpdate();
144
+ });
145
+
146
+ helpClose?.addEventListener('click', () => {
147
+ this.helpOpen = false;
148
+ this.helpWidth = 0;
149
+ this.requestUpdate();
150
+ });
151
+ });
152
+ }
153
+
154
+ private _startResizing(event: MouseEvent) {
155
+ if (!this.helpResizable) return;
156
+
157
+ const resizeHandle = this.shadowRoot?.querySelector('.resize-handle');
158
+ if (event.target === resizeHandle) {
159
+ this._startX = event.clientX;
160
+ this._startWidth = this.helpWidth;
161
+ this._resizing = true;
162
+ document.addEventListener('mousemove', this._resize);
163
+ document.addEventListener('mouseup', this._stopResize);
164
+ (event.target as HTMLElement).classList.add('helpResizable');
165
+ }
166
+
167
+ resizeHandle?.addEventListener('dblclick', () => {
168
+ if (this.helpWidth > 320 || this.helpWidth !== 320) {
169
+ this.helpWidth = 320;
170
+ localStorage.setItem('helpWidth', '320');
171
+ this.updateHelpPanelWidth();
172
+ this.requestUpdate();
173
+ }
174
+ });
175
+
176
+ this.requestUpdate();
177
+ }
178
+
179
+ private _resize(event: MouseEvent) {
180
+ const diff = event.clientX - this._startX;
181
+ const windowWidth = window.innerWidth;
182
+ const mainMinWidth = 600;
183
+ const maxWidthForHelp = Math.max(320, windowWidth - mainMinWidth);
184
+ const newWidth = Math.max(
185
+ 320,
186
+ Math.min(maxWidthForHelp, this._startWidth - diff)
187
+ );
188
+ if (this.helpWidth !== newWidth) {
189
+ this.helpWidth = newWidth;
190
+ localStorage.setItem('helpWidth', this.helpWidth.toString());
191
+ this.updateHelpPanelWidth();
192
+ }
193
+ event.preventDefault();
194
+ }
195
+
196
+ private _stopResize() {
197
+ document.removeEventListener('mousemove', this._resize);
198
+ document.removeEventListener('mouseup', this._stopResize);
199
+ this._resizing = false;
200
+ const resizeHandle = this.shadowRoot?.querySelector('.resize-handle');
201
+ if (resizeHandle) {
202
+ resizeHandle.classList.remove('helpResizable');
203
+ }
204
+
205
+ this.requestUpdate();
206
+ }
207
+
208
+ private _toggleOpen(forcedOpen = false) {
209
+ if (this.mdcFoundation.isOpening() || this.mdcFoundation.isClosing()) {
210
+ return;
211
+ }
212
+
213
+ this.open = forcedOpen ? forcedOpen : !this.open;
214
+ this.forcedOpen = forcedOpen;
215
+
216
+ this.dispatchEvent(
217
+ new Event('CovalentAppShell:toggle', { bubbles: true, composed: true })
218
+ );
219
+
220
+ this.requestUpdate();
221
+ }
222
+
223
+ private updateHelpPanelWidth() {
224
+ const appShell = this.shadowRoot?.querySelector(
225
+ '.app-shell'
226
+ ) as HTMLElement;
227
+ appShell?.style.setProperty('--cv-help-width', `${this.helpWidth}px`);
228
+ }
229
+
230
+ private _handleMenuClick() {
231
+ this.mdcRoot.dispatchEvent(
232
+ new Event('transitionend', { bubbles: true, composed: true })
233
+ );
234
+ // Forcefully toggle the open/close state
235
+ this._toggleOpen(!this.forcedOpen);
236
+
237
+ this.dispatchEvent(new Event('CovalentAppShell:menuClick'));
238
+
239
+ this.hovered = false;
240
+ }
241
+
242
+ private _handleNavMouseOver = () => {
243
+ this.hovered = true;
244
+
245
+ // clear timeout if user hovers over nav before it closes
246
+ clearTimeout(this.hoverTimeout);
247
+
248
+ if (!this.open && !this.forcedOpen) {
249
+ this.hoverTimeout = setTimeout(() => {
250
+ if (this.hovered) {
251
+ this._toggleOpen();
252
+ }
253
+ }, this.hoverEntryDuration);
254
+ }
255
+ };
256
+
257
+ private _handleNavMouseOut = () => {
258
+ this.hovered = false;
259
+
260
+ // clear timeout if user leaves nav before it opens
261
+ clearTimeout(this.hoverTimeout);
262
+
263
+ if (this.open && !this.forcedOpen) {
264
+ this.hoverTimeout = setTimeout(() => {
265
+ if (!this.hovered) {
266
+ this._toggleOpen();
267
+ }
268
+ }, this.hoverExitDuration);
269
+ }
270
+ };
271
+
272
+ private _handleDrawerClosed() {
273
+ this.forcedOpen = false;
274
+ this.hovered = false;
275
+ this.requestUpdate();
276
+ }
277
+
278
+ resizeEvent() {
279
+ // TODO should be configurable outside appshell
280
+ const mql = window.matchMedia('(max-width: 767px)');
281
+ if (mql.matches && this.type !== 'modal') {
282
+ this.type = 'modal';
283
+ } else if (!mql.matches && this.type !== 'dismissible') {
284
+ this.type = 'dismissible';
285
+ }
286
+ this.requestUpdate();
287
+ }
288
+
289
+ override connectedCallback() {
290
+ super.connectedCallback();
291
+ this.addEventListener('MDCDrawer:closed', this._handleDrawerClosed);
292
+ window.addEventListener('resize', () => this.resizeEvent());
293
+ }
294
+
295
+ override disconnectedCallback() {
296
+ super.disconnectedCallback();
297
+ this.removeEventListener('MDCDrawer:closed', this._handleDrawerClosed);
298
+ window.removeEventListener('resize', this.resizeEvent);
299
+ }
300
+
301
+ protected renderSection() {
302
+ return this.sectionName
303
+ ? html`<div class="current-section">
304
+ <slot name="section-action"></slot>
305
+ <span class="current-section-name">${this.sectionName}</span>
306
+ </div>`
307
+ : nothing;
308
+ }
309
+
310
+ protected renderMain() {
311
+ return this.contained
312
+ ? html`<cv-card class="wrapper-card"><slot></slot></cv-card>`
313
+ : html`<slot></slot>`;
314
+ }
315
+
316
+ override render() {
317
+ const dismissible = this.type === 'dismissible' || this.type === 'modal';
318
+ const modal = this.type === 'modal';
319
+ const classes = {
320
+ 'cov-drawer--forced-open': this.forcedOpen,
321
+ 'cov-drawer--open': this.drawerOpen || this.forcedOpen,
322
+ 'cov-drawer--hovered': this.hovered,
323
+ 'cov-help--open': this.helpOpen,
324
+ 'cov-help--closed': !this.helpOpen,
325
+ 'cov-help--resizing': this._resizing,
326
+ 'cov-content--full-width': this.fullWidth,
327
+ };
328
+ const drawerClasses = {
329
+ 'mdc-drawer--dismissible': dismissible,
330
+ 'mdc-drawer--modal': modal,
331
+ };
332
+
333
+ const scrim = modal
334
+ ? html`<div
335
+ class="mdc-drawer-scrim"
336
+ @click="${this._handleScrimClick}"
337
+ ></div>`
338
+ : nothing;
339
+
340
+ return html`
341
+ <div class="app-shell ${classMap(classes)}">
342
+ <span class="header"
343
+ ><cv-top-app-bar-fixed centerTitle>
344
+ <cv-icon-button
345
+ class="toggle-drawer"
346
+ @click=${this._handleMenuClick}
347
+ slot="navigationIcon"
348
+ icon="menu"
349
+ ></cv-icon-button>
350
+ <span slot="title">${this.appName}</span>
351
+ </cv-top-app-bar-fixed>
352
+ </span>
353
+ <nav
354
+ class="navigation mdc-drawer ${classMap(drawerClasses)}"
355
+ @mouseenter="${this._handleNavMouseOver}"
356
+ @mouseleave="${this._handleNavMouseOut}"
357
+ >
358
+ <div class="navigation-toolbar">
359
+ <cv-icon-button
360
+ @click="${this._handleMenuClick}"
361
+ class="toggle-drawer"
362
+ icon="menu"
363
+ ></cv-icon-button>
364
+ <slot name="logo"></slot>
365
+ </div>
366
+ ${this.renderSection()}
367
+ <slot name="navigation"></slot>
368
+ </nav>
369
+ ${scrim}
370
+ <slot name="mini-list"></slot>
371
+ <div class="main mdc-drawer-app-content">
372
+ <div class="main-wrapper">
373
+ <slot name="user-menu"></slot>
374
+ ${this.renderMain()}
375
+ </div>
376
+ </div>
377
+ <div class="help" @mousedown="${this._startResizing}">
378
+ ${this.helpResizable
379
+ ? html`<div class="resize-handle"></div>`
380
+ : nothing}
381
+ <slot name="help"></slot>
382
+ </div>
383
+ </div>
384
+ `;
385
+ }
386
+ }
387
+
388
+ export default CovalentAppShell;
@@ -0,0 +1,60 @@
1
+ .cv-badge {
2
+ background-color: var(--cv-theme-negative);
3
+ border-radius: 11px;
4
+ box-sizing: border-box;
5
+ color: var(--cv-theme-on-negative);
6
+ display: inline-block;
7
+ font-family: var(--mdc-typography-caption-font-family);
8
+ font-size: var(--mdc-typography-caption-font-size);
9
+ font-weight: var(--mdc-typography-caption-font-weight);
10
+ line-height: var(--mdc-typography-caption-line-height);
11
+ height: 16px;
12
+ min-width: 16px;
13
+ padding: 0 4px;
14
+ position: absolute;
15
+ text-align: center;
16
+
17
+ &.small {
18
+ height: 2px;
19
+ padding: 2px;
20
+ min-width: 2px;
21
+ }
22
+
23
+ &.top-right {
24
+ top: var(--cv-badge-position-y, -2px);
25
+ right: var(--cv-badge-position-x, -2px);
26
+ }
27
+
28
+ &.top-left {
29
+ top: var(--cv-badge-position-y, -2px);
30
+ left: var(--cv-badge-position-x, -2px);
31
+ }
32
+
33
+ &.bottom-right {
34
+ bottom: var(--cv-badge-position-y, -2px);
35
+ right: var(--cv-badge-position-x, -2px);
36
+ }
37
+
38
+ &.bottom-left {
39
+ bottom: var(--cv-badge-position-y, -2px);
40
+ left: var(--cv-badge-position-x, -2px);
41
+ }
42
+
43
+ &.isolated {
44
+ position: relative;
45
+ inset: 0;
46
+ }
47
+ }
48
+
49
+ .cv-badge-container {
50
+ position: relative;
51
+ display: inline-flex;
52
+ }
53
+
54
+ .wrapped-content {
55
+ flex-grow: 1;
56
+ }
57
+
58
+ .hidden {
59
+ display: none;
60
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+ import { it, describe, expect, beforeAll } from 'vitest';
5
+ import { CovalentBadge } from './badge';
6
+
7
+ describe('Covalent Badge', () => {
8
+ let badgeElements: NodeListOf<CovalentBadge>;
9
+
10
+ beforeAll(() => {
11
+ document.body.innerHTML = `<cv-badge content="99"></cv-badge>
12
+ <cv-badge content="0" showZero="true"></cv-badge>
13
+ <cv-badge content="hello" size="small"></cv-badge>
14
+ <cv-badge content="1000" max="999"></cv-badge>
15
+ `;
16
+ badgeElements = document.body.querySelectorAll('cv-badge');
17
+ });
18
+
19
+ it('should work', () => {
20
+ expect(new CovalentBadge()).toBeDefined();
21
+ });
22
+
23
+ it('should show content', () => {
24
+ expect(badgeElements[0]?.shadowRoot?.innerHTML).toContain('99');
25
+ });
26
+
27
+ it('should show zero when showZero prop is true', () => {
28
+ expect(badgeElements[1]?.shadowRoot?.innerHTML).toContain('0');
29
+ });
30
+
31
+ it('should not show content when size is small', () => {
32
+ if (badgeElements[2]?.shadowRoot?.innerHTML) {
33
+ expect(badgeElements[2]?.shadowRoot?.innerHTML).not.toContain('hello');
34
+ }
35
+ });
36
+
37
+ it('should cap the content based on max prop', () => {
38
+ expect(badgeElements[3]?.shadowRoot?.innerHTML).toContain('999+');
39
+ });
40
+ });
@@ -0,0 +1,88 @@
1
+ import './badge';
2
+
3
+ import '../icon/icon';
4
+ import '../list/list-item';
5
+ import '../list/list';
6
+ import '../typography/typography';
7
+
8
+ export default {
9
+ title: 'Components/Badge',
10
+ argTypes: {
11
+ size: { options: ['large', 'small'], control: { type: 'select' } },
12
+ verticalAlignment: {
13
+ options: ['top', 'bottom'],
14
+ control: { type: 'select' },
15
+ },
16
+ horizontalAlignment: {
17
+ options: ['right', 'left'],
18
+ control: { type: 'select' },
19
+ },
20
+ },
21
+ args: {
22
+ content: 22,
23
+ max: 99,
24
+ size: 'large',
25
+ verticalAlignment: 'top',
26
+ horizontalAlignment: 'right',
27
+ showZero: true,
28
+ hideBadge: false,
29
+ },
30
+ };
31
+
32
+ const Template = ({
33
+ content,
34
+ max,
35
+ size,
36
+ showZero,
37
+ hideBadge,
38
+ verticalAlignment,
39
+ horizontalAlignment,
40
+ }) => {
41
+ return `<cv-badge${content || content == 0 ? ` content=${content}` : ''}${
42
+ size ? ` size=${size}` : ''
43
+ }${max ? ` max=${max}` : ''}${
44
+ verticalAlignment ? ` verticalAlignment=${verticalAlignment}` : ''
45
+ }${horizontalAlignment ? ` horizontalAlignment=${horizontalAlignment}` : ''}${
46
+ showZero ? ' showZero' : ''
47
+ }${hideBadge ? ' hideBadge' : ''}>
48
+ <cv-icon style="font-size: 36px">chat<cv-icon>
49
+ </cv-badge>`;
50
+ };
51
+
52
+ const ListTemplate = () => {
53
+ return `
54
+ <div style="margin-bottom: 3em;">
55
+ <cv-typography scale="headline5">Basic</cv-typography>
56
+ <cv-badge content='Hello world'></cv-badge>
57
+ </div>
58
+ <cv-typography scale="headline5">Used in a list</cv-typography>
59
+ <cv-list>
60
+ <cv-list-item>
61
+ <span style="padding-right: 2em;">Item 1</span>
62
+ <cv-badge content=22 max=999></cv-badge>
63
+ </cv-list-item>
64
+ <cv-list-item>
65
+ <span style="padding-right: 2em;">Item 2</span>
66
+ <cv-badge content=1000 max=999></cv-badge>
67
+ </cv-list-item>
68
+ </cv-list>`;
69
+ };
70
+
71
+ export const Large = Template.bind({});
72
+ Large.args = {
73
+ content: 3,
74
+ max: 99,
75
+ };
76
+
77
+ export const LargeWithLimit = Template.bind({});
78
+ LargeWithLimit.args = {
79
+ content: 9999,
80
+ max: 99,
81
+ };
82
+
83
+ export const Small = Template.bind({});
84
+ Small.args = {
85
+ size: 'small',
86
+ };
87
+
88
+ export const Basic = ListTemplate.bind({});
@@ -0,0 +1,122 @@
1
+ import { LitElement, html, css, unsafeCSS } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { classMap } from 'lit/directives/class-map.js';
4
+ import styles from './badge.scss?inline';
5
+
6
+ export type BadgeSize = 'small' | 'large';
7
+ export type BadgeAlignmentVertical = 'top' | 'bottom';
8
+ export type BadgeAlignmentHorizonatal = 'left' | 'right';
9
+
10
+ /**
11
+ * Badge
12
+ *
13
+ * @slot - This element has a slot
14
+ */
15
+
16
+ @customElement('cv-badge')
17
+ export class CovalentBadge extends LitElement {
18
+ static override styles = [
19
+ css`
20
+ ${unsafeCSS(styles)}
21
+ `,
22
+ ];
23
+
24
+ /**
25
+ * The content to be displayed in the badge
26
+ */
27
+ @property()
28
+ content?: number | string;
29
+
30
+ /**
31
+ * Caps the value of badge content
32
+ */
33
+ @property()
34
+ max?: number = 100;
35
+
36
+ /**
37
+ * Sets the size of the badge
38
+ * 'small' represents a dot to notify that something has changed without providing content
39
+ */
40
+ @property()
41
+ size?: BadgeSize = 'large';
42
+
43
+ /**
44
+ * Controls the visibility of the badge
45
+ */
46
+ @property({ type: Boolean, reflect: true })
47
+ hideBadge? = false;
48
+
49
+ /**
50
+ * Aligns the badge vertically in the wrapped element
51
+ */
52
+ @property()
53
+ verticalAlignment?: BadgeAlignmentVertical = 'top';
54
+
55
+ /**
56
+ * Aligns the badge horizontally in the wrapped element
57
+ */
58
+ @property()
59
+ horizontalAlignment?: BadgeAlignmentHorizonatal = 'right';
60
+
61
+ /**
62
+ * Shows the badge when count is zero
63
+ */
64
+ @property({ type: Boolean, reflect: true })
65
+ showZero?: boolean;
66
+
67
+ firstUpdated(): void {
68
+ const slot = this.renderRoot.querySelector('slot');
69
+
70
+ if (slot) {
71
+ const assignedNodes = slot.assignedNodes({
72
+ flatten: true,
73
+ }) as Element[];
74
+
75
+ // Filter out non-element nodes
76
+ const elements = assignedNodes.filter(
77
+ (node) => node.nodeType === Node.ELEMENT_NODE
78
+ );
79
+
80
+ if (!elements.length) {
81
+ const badge = this.renderRoot.querySelector('#badge-content');
82
+ badge?.classList?.add('isolated');
83
+ }
84
+ }
85
+ }
86
+
87
+ getContent(): number | string {
88
+ let content = this.content;
89
+ const numericContent = Number(content);
90
+ if (this.showZero && content == 0) {
91
+ content = content.toString();
92
+ } else if (!isNaN(numericContent) && this.max) {
93
+ content = numericContent > this.max ? `${this.max}+` : numericContent;
94
+ }
95
+ return content || '';
96
+ }
97
+
98
+ override render() {
99
+ const classes: { [key: string]: boolean } = {
100
+ 'cv-badge': true,
101
+ small: this.size === 'small',
102
+ hidden: !!this.hideBadge || (!this.showZero && this.content == 0),
103
+ };
104
+ classes[`${this.verticalAlignment}-${this.horizontalAlignment}`] = true;
105
+ return html`
106
+ <div class="cv-badge-container">
107
+ <slot class="wrapped-content"></slot>
108
+ <span id="badge-content" class="${classMap(classes)}">
109
+ ${this.size !== 'small' ? this.getContent() : ''}
110
+ </span>
111
+ </div>
112
+ `;
113
+ }
114
+ }
115
+
116
+ declare global {
117
+ interface HTMLElementTagNameMap {
118
+ 'cv-badge': CovalentBadge;
119
+ }
120
+ }
121
+
122
+ export default CovalentBadge;