@ethlete/components 1.0.0-next.78 → 1.0.0-next.79

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 (252) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/design/accordion.ts +73 -0
  3. package/design/avatar.ts +90 -0
  4. package/design/badge.ts +48 -0
  5. package/design/banner.ts +77 -0
  6. package/design/bracket.ts +457 -0
  7. package/design/breadcrumb.ts +145 -0
  8. package/design/button.ts +49 -0
  9. package/design/calendar.ts +306 -0
  10. package/design/card.ts +92 -0
  11. package/design/carousel.ts +240 -0
  12. package/design/chart.ts +512 -0
  13. package/design/checkbox.ts +275 -0
  14. package/design/chip.ts +64 -0
  15. package/design/config.json +4 -1
  16. package/design/control.ts +19 -0
  17. package/design/copy-button.ts +59 -0
  18. package/design/description-list.ts +41 -0
  19. package/design/dialog.ts +214 -0
  20. package/design/divider.ts +27 -0
  21. package/design/empty-state.ts +45 -0
  22. package/design/floating-action.ts +55 -0
  23. package/design/grid.ts +148 -0
  24. package/design/input.ts +86 -0
  25. package/design/kbd.ts +19 -0
  26. package/design/loader.ts +152 -0
  27. package/design/masonry.ts +115 -0
  28. package/design/match.ts +131 -0
  29. package/design/menu.ts +143 -0
  30. package/design/notification.ts +129 -0
  31. package/design/otp-input.ts +107 -0
  32. package/design/pagination.ts +120 -0
  33. package/design/picture.ts +80 -0
  34. package/design/progress-steps.ts +79 -0
  35. package/design/query-error.ts +60 -0
  36. package/design/radio-group.ts +132 -0
  37. package/design/scheduler.ts +369 -0
  38. package/design/scrollable.ts +218 -0
  39. package/design/scrollbar.ts +47 -0
  40. package/design/sdk/assets/fallback.svg +1 -0
  41. package/design/sdk/assets/tall.svg +1 -0
  42. package/design/sdk/assets/wide.svg +1 -0
  43. package/design/sdk/components/accent-on-light/accent-on-light.css +35 -0
  44. package/design/sdk/components/accent-on-light/accent-on-light.ts +10 -0
  45. package/design/sdk/components/accent-on-light/shared/controls.ts +46 -0
  46. package/design/sdk/components/accordion/accordion.css +17 -0
  47. package/design/sdk/components/accordion/accordion.ts +64 -0
  48. package/design/sdk/components/avatar/avatar.css +14 -0
  49. package/design/sdk/components/avatar/avatar.ts +78 -0
  50. package/design/sdk/components/badge/badge.css +13 -0
  51. package/design/sdk/components/badge/badge.ts +52 -0
  52. package/design/sdk/components/banner/banner.css +18 -0
  53. package/design/sdk/components/banner/banner.ts +87 -0
  54. package/design/sdk/components/bracket/bracket.css +59 -0
  55. package/design/sdk/components/bracket/bracket.ts +143 -0
  56. package/design/sdk/components/breadcrumb/breadcrumb.css +25 -0
  57. package/design/sdk/components/breadcrumb/breadcrumb.ts +81 -0
  58. package/design/sdk/components/button/button.css +33 -0
  59. package/design/sdk/components/button/button.ts +50 -7
  60. package/design/sdk/components/calendar/calendar.css +33 -0
  61. package/design/sdk/components/calendar/calendar.ts +96 -0
  62. package/design/sdk/components/card/card.css +19 -0
  63. package/design/sdk/components/card/card.ts +61 -0
  64. package/design/sdk/components/carousel/carousel.css +44 -0
  65. package/design/sdk/components/carousel/carousel.ts +74 -0
  66. package/design/sdk/components/chart/chart.css +56 -0
  67. package/design/sdk/components/chart/chart.ts +98 -0
  68. package/design/sdk/components/chip/chip.css +18 -0
  69. package/design/sdk/components/chip/chip.ts +76 -0
  70. package/design/sdk/components/copy-button/copy-button.css +14 -0
  71. package/design/sdk/components/copy-button/copy-button.ts +59 -0
  72. package/design/sdk/components/description-list/description-list.css +7 -0
  73. package/design/sdk/components/description-list/description-list.ts +53 -0
  74. package/design/sdk/components/dialog/dialog.css +29 -0
  75. package/design/sdk/components/dialog/dialog.ts +78 -0
  76. package/design/sdk/components/divider/divider.css +26 -0
  77. package/design/sdk/components/divider/divider.ts +43 -0
  78. package/design/sdk/components/empty-state/empty-state.css +15 -0
  79. package/design/sdk/components/empty-state/empty-state.ts +56 -0
  80. package/design/sdk/components/floating-action/floating-action.css +51 -0
  81. package/design/sdk/components/floating-action/floating-action.ts +63 -0
  82. package/design/sdk/components/form-field/form-field.ts +23 -5
  83. package/design/sdk/components/grid/grid.css +17 -0
  84. package/design/sdk/components/grid/grid.ts +80 -0
  85. package/design/sdk/components/input/input.css +75 -0
  86. package/design/sdk/components/input/input.ts +79 -0
  87. package/design/sdk/components/kbd/kbd.css +25 -0
  88. package/design/sdk/components/kbd/kbd.ts +57 -0
  89. package/design/sdk/components/loader/loader.css +37 -0
  90. package/design/sdk/components/loader/loader.ts +68 -0
  91. package/design/sdk/components/masonry/masonry.css +7 -0
  92. package/design/sdk/components/masonry/masonry.ts +42 -0
  93. package/design/sdk/components/match/match.css +25 -0
  94. package/design/sdk/components/match/match.ts +76 -0
  95. package/design/sdk/components/menu/menu.css +22 -0
  96. package/design/sdk/components/menu/menu.ts +91 -0
  97. package/design/sdk/components/notification/notification.css +32 -0
  98. package/design/sdk/components/notification/notification.ts +106 -0
  99. package/design/sdk/components/otp-input/otp-input.css +35 -0
  100. package/design/sdk/components/otp-input/otp-input.ts +64 -0
  101. package/design/sdk/components/pagination/pagination.css +14 -0
  102. package/design/sdk/components/pagination/pagination.ts +75 -0
  103. package/design/sdk/components/picture/picture.css +37 -0
  104. package/design/sdk/components/picture/picture.ts +96 -0
  105. package/design/sdk/components/progress-steps/progress-steps.css +37 -0
  106. package/design/sdk/components/progress-steps/progress-steps.ts +91 -0
  107. package/design/sdk/components/query-error/query-error.css +33 -0
  108. package/design/sdk/components/query-error/query-error.ts +120 -0
  109. package/design/sdk/components/scheduler/scheduler.css +55 -0
  110. package/design/sdk/components/scheduler/scheduler.ts +239 -0
  111. package/design/sdk/components/scrollable/scrollable.css +58 -0
  112. package/design/sdk/components/scrollable/scrollable.ts +133 -0
  113. package/design/sdk/components/scrollbar/scrollbar.css +78 -0
  114. package/design/sdk/components/scrollbar/scrollbar.ts +123 -0
  115. package/design/sdk/components/select/select.ts +1 -1
  116. package/design/sdk/components/select/shared/select-part.ts +16 -1
  117. package/design/sdk/components/select-panel/select-panel.css +3 -0
  118. package/design/sdk/components/select-panel/select-panel.ts +124 -0
  119. package/design/sdk/components/skeleton/skeleton.css +22 -0
  120. package/design/sdk/components/skeleton/skeleton.ts +50 -0
  121. package/design/sdk/components/slider/slider.css +52 -0
  122. package/design/sdk/components/slider/slider.ts +86 -0
  123. package/design/sdk/components/standings/standings.css +33 -0
  124. package/design/sdk/components/standings/standings.ts +130 -0
  125. package/design/sdk/components/stat-tile/stat-tile.css +15 -0
  126. package/design/sdk/components/stat-tile/stat-tile.ts +84 -0
  127. package/design/sdk/components/stream/stream.css +48 -0
  128. package/design/sdk/components/stream/stream.ts +38 -0
  129. package/design/sdk/components/switch/switch.css +35 -0
  130. package/design/sdk/components/switch/switch.ts +63 -0
  131. package/design/sdk/components/table/table.css +67 -0
  132. package/design/sdk/components/table/table.ts +160 -0
  133. package/design/sdk/components/tabs/tabs.css +13 -0
  134. package/design/sdk/components/tabs/tabs.ts +74 -0
  135. package/design/sdk/components/textarea/textarea.css +116 -0
  136. package/design/sdk/components/textarea/textarea.ts +64 -0
  137. package/design/sdk/components/timeline/timeline.css +30 -0
  138. package/design/sdk/components/timeline/timeline.ts +87 -0
  139. package/design/sdk/components/toolbar/toolbar.css +24 -0
  140. package/design/sdk/components/toolbar/toolbar.ts +87 -0
  141. package/design/sdk/components/tooltip/tooltip.css +15 -0
  142. package/design/sdk/components/tooltip/tooltip.ts +28 -0
  143. package/design/sdk/components/tree/selected-tint.css +15 -0
  144. package/design/sdk/components/tree/tree.ts +70 -0
  145. package/design/sdk/foundations/colors/colors.css +108 -4
  146. package/design/sdk/foundations/elevation/elevation.css +6 -0
  147. package/design/sdk/foundations/elevation/elevation.ts +11 -0
  148. package/design/sdk/foundations/elevation/shared/elevation/scene.css +101 -0
  149. package/design/sdk/foundations/elevation/shared/elevation/scene.ts +74 -0
  150. package/design/sdk/foundations/icons/icons.ts +119 -0
  151. package/design/skeleton.ts +67 -0
  152. package/design/slider.ts +155 -0
  153. package/design/standings.ts +161 -0
  154. package/design/stat-tile.ts +109 -0
  155. package/design/stream.ts +92 -0
  156. package/design/switch.ts +83 -0
  157. package/design/table.ts +302 -0
  158. package/design/tabs.ts +75 -0
  159. package/design/textarea.ts +92 -0
  160. package/design/timeline.ts +52 -0
  161. package/design/toolbar.ts +95 -0
  162. package/design/tooltip.ts +87 -0
  163. package/fesm2022/ethlete-components.mjs +228 -187
  164. package/fesm2022/ethlete-components.mjs.map +1 -1
  165. package/package.json +1 -1
  166. package/src/lib/accordion/accordion.component.css +2 -1
  167. package/src/lib/avatar/avatar.component.css +10 -0
  168. package/src/lib/badge/badge.component.css +2 -2
  169. package/src/lib/banner/banner.component.css +3 -1
  170. package/src/lib/breadcrumb/breadcrumb-outlet.component.css +5 -0
  171. package/src/lib/breadcrumb/breadcrumb.component.css +5 -1
  172. package/src/lib/button/button.component.css +19 -0
  173. package/src/lib/button/icon-button.component.css +19 -20
  174. package/src/lib/button/split-button.component.css +5 -1
  175. package/src/lib/calendar/calendar.component.css +13 -3
  176. package/src/lib/card/card.component.css +4 -0
  177. package/src/lib/carousel/carousel-autoplay-styles.component.css +1 -1
  178. package/src/lib/carousel/carousel.component.css +2 -2
  179. package/src/lib/chart/chart-axis.component.css +3 -3
  180. package/src/lib/chip/chip.component.css +8 -2
  181. package/src/lib/description-list/description-list.component.css +2 -1
  182. package/src/lib/empty-state/empty-state.component.css +4 -0
  183. package/src/lib/floating-action/floating-action-styles.component.css +27 -0
  184. package/src/lib/focus-ring/focus-ring-styles.component.css +4 -1
  185. package/src/lib/forms/checkbox/checkbox.component.css +31 -5
  186. package/src/lib/forms/color-input/color-input.component.css +3 -1
  187. package/src/lib/forms/color-input/color-picker-panel.component.css +6 -2
  188. package/src/lib/forms/date-time/date-input/date-input.component.css +1 -1
  189. package/src/lib/forms/date-time/date-time-input/date-time-input.component.css +1 -1
  190. package/src/lib/forms/date-time/duration-input/duration-input.component.css +1 -1
  191. package/src/lib/forms/date-time/range-input-shell-styles.component.css +1 -1
  192. package/src/lib/forms/date-time/time-input/time-input.component.css +1 -1
  193. package/src/lib/forms/description/description.component.css +7 -0
  194. package/src/lib/forms/dropzone/dropzone.component.css +26 -0
  195. package/src/lib/forms/form-field/form-field-control-suffix-styles.component.css +4 -2
  196. package/src/lib/forms/form-field/form-field-text-shell-styles.component.css +54 -5
  197. package/src/lib/forms/form-field/form-field-textarea-styles.component.css +62 -0
  198. package/src/lib/forms/form-field/form-field.component.css +6 -0
  199. package/src/lib/forms/form-field/headless/label.directive.css +1 -1
  200. package/src/lib/forms/input/input.component.css +6 -2
  201. package/src/lib/forms/input/number-input.component.css +2 -2
  202. package/src/lib/forms/input/password-input.component.css +4 -3
  203. package/src/lib/forms/otp-input/otp-input.component.css +23 -4
  204. package/src/lib/forms/phone-input/phone-input.component.css +4 -3
  205. package/src/lib/forms/rating/rating.component.css +2 -1
  206. package/src/lib/forms/rich-text-editor/rich-text-editor.component.css +1 -1
  207. package/src/lib/forms/selection-card-styles.component.css +3 -2
  208. package/src/lib/forms/selection-list/checkbox-group/checkbox-group-select-all.component.css +4 -2
  209. package/src/lib/forms/selection-list/checkbox-group/checkbox-option.component.css +4 -2
  210. package/src/lib/forms/selection-list/radio-group/radio.component.css +5 -2
  211. package/src/lib/forms/selection-list/segmented-button-group/segmented-button.component.css +2 -1
  212. package/src/lib/forms/slider/range-slider.component.css +98 -6
  213. package/src/lib/forms/slider/slider.component.css +96 -4
  214. package/src/lib/forms/switch/switch.component.css +47 -4
  215. package/src/lib/forms/tag-input/tag-input.component.css +2 -2
  216. package/src/lib/forms/textarea/textarea-autosize-styles.component.css +2 -3
  217. package/src/lib/forms/textarea/textarea.component.css +7 -2
  218. package/src/lib/grid/grid-item-toolbar.component.css +10 -0
  219. package/src/lib/grid/grid-item.component.css +6 -1
  220. package/src/lib/loader/brand-loader/brand-loader.component.css +4 -1
  221. package/src/lib/loader/progress-bar/progress-bar.component.css +1 -1
  222. package/src/lib/loader/spinner/spinner-styles.component.css +1 -1
  223. package/src/lib/match/match-card.component.css +9 -3
  224. package/src/lib/match/match-score.component.css +4 -1
  225. package/src/lib/menu/menu-selection-item.component.css +1 -1
  226. package/src/lib/menu/menu.component.css +5 -0
  227. package/src/lib/overlay/overlay-container.component.css +4 -0
  228. package/src/lib/pagination/pagination.component.css +4 -0
  229. package/src/lib/progress-steps/progress-step.component.css +4 -4
  230. package/src/lib/query-error/query-error.component.css +4 -0
  231. package/src/lib/scheduler/scheduler-appointment-styles.component.css +21 -5
  232. package/src/lib/scheduler/scheduler-month-view.component.css +4 -2
  233. package/src/lib/scheduler/scheduler-time-grid-view.component.css +8 -2
  234. package/src/lib/scrollable/headless/scrollable-navigation.component.css +1 -1
  235. package/src/lib/standings/standings-pick.component.css +1 -1
  236. package/src/lib/standings/standings.component.css +10 -10
  237. package/src/lib/stat-tile/stat-tile-sparkline.component.css +1 -1
  238. package/src/lib/stream/consent/stream-consent.component.css +99 -0
  239. package/src/lib/stream/error/stream-player-error.component.css +100 -0
  240. package/src/lib/stream/loading/stream-player-loading.component.css +16 -0
  241. package/src/lib/stream/pip/pip-slot-placeholder.component.css +68 -0
  242. package/src/lib/stream/stream-player-slot-styles.component.css +4 -0
  243. package/src/lib/table/table-resize-grip.component.css +2 -1
  244. package/src/lib/table/table-select-cell.component.css +45 -15
  245. package/src/lib/table/table.component.css +15 -5
  246. package/src/lib/tabs/tab-bar-styles.component.css +1 -1
  247. package/src/lib/tabs/tabs/tab-group.component.css +111 -0
  248. package/src/lib/time-picker/time-picker.component.css +2 -1
  249. package/src/lib/timeline/timeline-item.component.css +5 -1
  250. package/src/lib/toolbar/toolbar.component.css +4 -0
  251. package/src/lib/tooltip/tooltip.component.css +14 -0
  252. package/types/ethlete-components.d.ts +11 -2
@@ -0,0 +1,152 @@
1
+ import brandLoaderBase from '../src/lib/loader/brand-loader/brand-loader.component.css?inline';
2
+ import progressBarBase from '../src/lib/loader/progress-bar/progress-bar.component.css?inline';
3
+ import spinnerBase from '../src/lib/loader/spinner/spinner-styles.component.css?inline';
4
+
5
+ const BASE_STROKE_WIDTH = 10;
6
+
7
+ const BRAND_E_PATH =
8
+ 'M15.905 6.21401C15.7794 6.8902 16.2818 7.51877 16.9517 7.51877H23.8598L22.9201 12.3378H15.6817C15.1048 12.3378 14.6071 12.7569 14.5001 13.3378C14.4861 13.4188 14.4768 13.4997 14.4768 13.5811C14.4768 14.2426 15.0071 14.8092 15.6817 14.8092H23.9435L22.9201 19.9997H6.79183L7.2198 17.814H7.64313C7.94085 17.814 8.20601 17.6907 8.40139 17.495C8.59212 17.2954 8.71307 17.0235 8.71307 16.7235C8.71307 16.1188 8.23858 15.6331 7.64778 15.6288H2.73534C2.14919 15.6288 1.67004 15.1426 1.67004 14.5378C1.67004 14.2383 1.791 13.9621 1.98172 13.7664C2.17711 13.5711 2.44227 13.4473 2.73534 13.4473H8.07111L8.96428 8.88115H9.39226C9.68533 8.88115 9.95049 8.75686 10.1459 8.5621C10.3366 8.3621 10.4576 8.08591 10.4576 7.78543C10.4576 7.18543 9.9784 6.69543 9.39226 6.69543H4.48447C3.89367 6.69543 3.41452 6.20448 3.41452 5.60496C3.41452 5.30448 3.53547 5.02877 3.7262 4.82829C3.92158 4.63353 4.19139 4.50924 4.48447 4.50924H10.2436C10.5366 4.50924 10.8064 4.3902 10.9972 4.1902C11.1879 3.99496 11.3089 3.72401 11.3089 3.41877C11.3089 2.81401 10.8344 2.32829 10.2436 2.32829H1.06994C0.479149 2.32829 0 1.83829 0 1.23305C0 0.933054 0.12095 0.656864 0.31168 0.461626C0.507061 0.266864 0.776873 0.142578 1.06994 0.142578H26.4369L25.4135 5.33305H16.9517C16.4446 5.33305 16.0027 5.70496 15.905 6.21401Z';
9
+
10
+ const BRAND_E_OUTLINE_PATH =
11
+ 'M1.07 0.14 L26.44 0.14 L25.41 5.33 L16.95 5.33 C16.44 5.33 16.0 5.70 15.91 6.21 C15.78 6.89 16.28 7.52 16.95 7.52 L23.86 7.52 L22.92 12.34 L15.68 12.34 C15.10 12.34 14.61 12.76 14.50 13.34 C14.49 13.42 14.48 13.50 14.48 13.58 C14.48 14.24 15.01 14.81 15.68 14.81 L23.94 14.81 L22.92 20.0 L6.79 20.0 L7.22 17.81 L7.64 17.81 C7.94 17.81 8.21 17.69 8.40 17.50 C8.59 17.30 8.71 17.02 8.71 16.72 C8.71 16.12 8.24 15.63 7.65 15.63 L2.74 15.63 C2.15 15.63 1.67 15.14 1.67 14.54 C1.67 14.24 1.79 13.96 1.98 13.77 C2.18 13.57 2.44 13.45 2.74 13.45 L8.07 13.45 L8.96 8.88 L9.39 8.88 C9.69 8.88 9.95 8.76 10.15 8.56 C10.34 8.36 10.46 8.09 10.46 7.79 C10.46 7.19 9.98 6.70 9.39 6.70 L4.48 6.70 C3.89 6.70 3.41 6.20 3.41 5.60 C3.41 5.30 3.54 5.03 3.73 4.83 C3.92 4.63 4.19 4.51 4.48 4.51 L10.24 4.51 C10.54 4.51 10.81 4.39 10.997 4.19 C11.188 3.99 11.31 3.72 11.31 3.42 C11.31 2.81 10.83 2.33 10.24 2.33 L1.07 2.33 C0.48 2.33 0 1.84 0 1.23 C0 0.93 0.12 0.66 0.31 0.46 C0.51 0.27 0.78 0.14 1.07 0.14 Z';
12
+
13
+ export type SpinnerMockOptions = {
14
+ /** Left out, 18, as `et-spinner` defaults to. */
15
+ diameter?: number;
16
+ /** Left out, 2.25, as `et-spinner` defaults to. */
17
+ strokeWidth?: number;
18
+ /** Left out, `false`. */
19
+ track?: boolean;
20
+ /** The progress, 0-100. Left out, the spinner is indeterminate. */
21
+ value?: number;
22
+ /** A color theme name the app registers. Left out, the spinner inherits `currentColor`. */
23
+ color?: string;
24
+ /** Where the indeterminate sweep is drawn, as a fraction of `--et-spinner-duration`. Left out, 0.3. */
25
+ frozenAt?: number;
26
+ /** Extra attributes on the spinner, e.g. design markers. */
27
+ attributes?: string;
28
+ };
29
+
30
+ export type ProgressBarMockOptions = {
31
+ /** The progress, 0-100. Left out, the bar is indeterminate. */
32
+ value?: number;
33
+ /** A color theme name the app registers. Left out, the bar inherits `currentColor`. */
34
+ color?: string;
35
+ /** Where the indeterminate sweep is drawn, as a fraction of its duration. Left out, 0.62: both bars in view. */
36
+ frozenAt?: number;
37
+ /** Extra attributes on the bar, e.g. design markers or an inline style. */
38
+ attributes?: string;
39
+ };
40
+
41
+ export type BrandLoaderMockOptions = {
42
+ /** Prefix of the clip path ids, unique per loader on one page. Left out, `et-brand-loader-mock`. */
43
+ id?: string;
44
+ /** Where the animation is drawn, as a fraction of its duration. Left out, 0.55: outline drawn, fill half swept. */
45
+ frozenAt?: number;
46
+ /** Extra attributes on the loader, e.g. design markers or an inline style. */
47
+ attributes?: string;
48
+ };
49
+
50
+ /** The stylesheet the loader mocks draw with: the real spinner, progress bar and brand loader CSS, plus the rules that hold their animations at one frame. */
51
+ export const loaderStyles = /* @__PURE__ */ [
52
+ spinnerBase,
53
+ progressBarBase,
54
+ brandLoaderBase,
55
+ `.et-spinner[data-et-frozen-at] :is(.et-spinner-indeterminate-container, .et-spinner-layer, .et-spinner-circle-graphic) {
56
+ animation-play-state: paused;
57
+ animation-delay: calc(var(--et-spinner-frozen-at) * -1 * var(--et-spinner-duration));
58
+ }
59
+ .et-spinner[data-et-frozen-at] :is(.et-spinner-track-container, .et-spinner-determinate-graphic, .et-spinner-determinate-circle) {
60
+ transition: none;
61
+ }
62
+ .et-progress-bar[data-et-frozen-at] :is(.et-progress-bar__bar, .et-progress-bar__bar-inner) {
63
+ animation-play-state: paused;
64
+ animation-delay: calc(var(--et-progress-bar-frozen-at) * -1 * var(--_et-progress-bar-duration));
65
+ transition: none;
66
+ }
67
+ .et-brand-loader[data-et-frozen-at] :is(.et-brand-loader__ghost, .et-brand-loader__outline, .et-brand-loader__fill, .et-brand-loader__fill-clip-rect) {
68
+ animation-play-state: paused;
69
+ animation-delay: calc(var(--et-brand-loader-frozen-at) * -1 * var(--_et-brand-loader-duration));
70
+ }`,
71
+ ].join('\n');
72
+
73
+ const clamp = (value: number) => Math.min(100, Math.max(0, value));
74
+
75
+ const colorClasses = (base: string, color: string | undefined) =>
76
+ color ? `${base} ${base}--themed et-color--${color}` : base;
77
+
78
+ const progressAttributes = (value: number | undefined) =>
79
+ value === undefined ? '' : ` aria-valuenow="${clamp(value)}" aria-valuemin="0" aria-valuemax="100"`;
80
+
81
+ /** Draws an `et-spinner` as the component renders it, as HTML, with the indeterminate sweep held at one frame. */
82
+ export const spinner = (options: SpinnerMockOptions = {}) => {
83
+ const diameter = options.diameter ?? 18;
84
+ const strokeWidth = options.strokeWidth ?? 2.25;
85
+ const radius = Math.max(1, (diameter - BASE_STROKE_WIDTH) / 2);
86
+ const circumference = 2 * Math.PI * radius;
87
+ const box = radius * 2 + strokeWidth;
88
+ const strokePercent = (strokeWidth / diameter) * 100;
89
+ const determinate = options.value !== undefined;
90
+
91
+ type Graphic = { graphic: string; circle?: string; offset?: number; hidden?: boolean };
92
+
93
+ const svg = ({ graphic, circle, offset, hidden }: Graphic) => {
94
+ const dashOffset = offset === undefined ? '' : ` stroke-dashoffset: ${offset}px;`;
95
+ const circleStyle = `style="stroke-dasharray: ${circumference}px;${dashOffset} stroke-width: ${strokePercent}%"`;
96
+ const circleClass = circle ? ` class="${circle}"` : '';
97
+
98
+ return `<svg viewBox="0 0 ${box} ${box}" class="${graphic}" xmlns="http://www.w3.org/2000/svg" focusable="false"${hidden ? ' aria-hidden="true"' : ''}><circle r="${radius}"${circleClass} cx="50%" cy="50%" ${circleStyle}></circle></svg>`;
99
+ };
100
+
101
+ const circle = svg({ graphic: 'et-spinner-circle-graphic', offset: circumference / 2 });
102
+ const track = options.track
103
+ ? `<div class="et-spinner-track-container" aria-hidden="true">${svg({ graphic: 'et-spinner-track-graphic', circle: 'et-spinner-track-circle' })}</div>`
104
+ : '';
105
+ const indicator = determinate
106
+ ? svg({
107
+ graphic: 'et-spinner-determinate-graphic',
108
+ circle: 'et-spinner-determinate-circle',
109
+ offset: circumference - (clamp(options.value ?? 0) / 100) * circumference,
110
+ hidden: true,
111
+ })
112
+ : `<div class="et-spinner-indeterminate"><div class="et-spinner-indeterminate-container" aria-hidden="true"><div class="et-spinner-layer"><div class="et-spinner-circle-clipper et-spinner-circle-left">${circle}</div><div class="et-spinner-gap-patch">${circle}</div><div class="et-spinner-circle-clipper et-spinner-circle-right">${circle}</div></div></div></div>`;
113
+
114
+ const classes = colorClasses('et-spinner', options.color) + (determinate ? ' et-spinner--determinate' : '');
115
+ const attributes = [
116
+ options.attributes,
117
+ `class="${classes}"`,
118
+ `role="progressbar" aria-label="Loading"${progressAttributes(options.value)}`,
119
+ `data-et-frozen-at style="--et-spinner-size: ${diameter}px; --et-spinner-stroke-width: ${strokeWidth}px; --et-spinner-frozen-at: ${options.frozenAt ?? 0.3}"`,
120
+ ].filter(Boolean);
121
+
122
+ return `<et-spinner ${attributes.join(' ')}>${track}${indicator}</et-spinner>`;
123
+ };
124
+
125
+ /** Draws an `et-progress-bar` as the component renders it, as HTML, with the indeterminate sweep held at one frame. */
126
+ export const progressBar = (options: ProgressBarMockOptions = {}) => {
127
+ const indeterminate = options.value === undefined;
128
+ const scale = indeterminate ? '' : ` style="transform: scaleX(${clamp(options.value ?? 0) / 100})"`;
129
+ const classes =
130
+ colorClasses('et-progress-bar', options.color) + (indeterminate ? ' et-progress-bar--indeterminate' : '');
131
+ const attributes = [
132
+ options.attributes,
133
+ `class="${classes}"`,
134
+ `role="progressbar" aria-label="Loading"${progressAttributes(options.value)}`,
135
+ `data-et-frozen-at style="--et-progress-bar-frozen-at: ${options.frozenAt ?? 0.62}"`,
136
+ ].filter(Boolean);
137
+
138
+ return `<et-progress-bar ${attributes.join(' ')}><div class="et-progress-bar__track" aria-hidden="true"><div${scale} class="et-progress-bar__bar et-progress-bar__bar--primary"><span class="et-progress-bar__bar-inner"></span></div><div class="et-progress-bar__bar et-progress-bar__bar--secondary"><span class="et-progress-bar__bar-inner"></span></div></div></et-progress-bar>`;
139
+ };
140
+
141
+ /** Draws an `et-brand-loader` as the component renders it, as HTML, with its animation held at one frame. */
142
+ export const brandLoader = (options: BrandLoaderMockOptions = {}) => {
143
+ const id = options.id ?? 'et-brand-loader-mock';
144
+ const attributes = [
145
+ options.attributes,
146
+ 'class="et-brand-loader"',
147
+ 'role="progressbar" aria-label="Loading"',
148
+ `data-et-frozen-at style="--et-brand-loader-frozen-at: ${options.frozenAt ?? 0.55}"`,
149
+ ].filter(Boolean);
150
+
151
+ return `<et-brand-loader ${attributes.join(' ')}><svg class="et-brand-loader__svg" viewBox="0 0 26.4369 20" aria-hidden="true"><defs><clipPath id="${id}-shape" clipPathUnits="userSpaceOnUse"><path d="${BRAND_E_PATH}"></path></clipPath><clipPath id="${id}-fill" clipPathUnits="userSpaceOnUse"><rect class="et-brand-loader__fill-clip-rect" x="0" y="0" width="0" height="20"></rect></clipPath></defs><path d="${BRAND_E_PATH}" class="et-brand-loader__ghost"></path><path d="${BRAND_E_OUTLINE_PATH}" clip-path="url(#${id}-shape)" class="et-brand-loader__outline" pathLength="100"></path><path d="${BRAND_E_PATH}" clip-path="url(#${id}-fill)" class="et-brand-loader__fill"></path></svg></et-brand-loader>`;
152
+ };
@@ -0,0 +1,115 @@
1
+ import masonryBase from '../src/lib/masonry/masonry-styles.component.css?inline';
2
+
3
+ export type MasonryMockItem = {
4
+ title: string;
5
+ /** A muted second line under the title. */
6
+ subtitle?: string;
7
+ /** The tile's block size in px, which the packing places by. */
8
+ height: number;
9
+ };
10
+
11
+ export type MasonryMockOptions = {
12
+ items: readonly MasonryMockItem[];
13
+ /** Left out, 448: the container width the masonry would measure, in px. */
14
+ width?: number;
15
+ /** Left out, 250, the component default: the minimum column width. */
16
+ columnWidth?: number;
17
+ /** Left out, 16, the component default. */
18
+ gap?: number;
19
+ dir?: 'ltr' | 'rtl';
20
+ /** Extra attributes on the host, e.g. a style or design markers. */
21
+ attributes?: string;
22
+ };
23
+
24
+ /** The stylesheets the masonry mock draws with: the real masonry CSS plus a neutral demo tile. */
25
+ export const masonryStyles = /* @__PURE__ */ [
26
+ masonryBase,
27
+ `@layer components {
28
+ .et-masonry-mock-tile {
29
+ display: flex;
30
+ flex-direction: column;
31
+ gap: 2px;
32
+ box-sizing: border-box;
33
+ padding: 12px;
34
+ border-radius: 8px;
35
+ background: var(--et-surface-background-solid);
36
+ }
37
+ .et-masonry-mock-title {
38
+ overflow: hidden;
39
+ font-size: 1.4rem;
40
+ font-weight: 600;
41
+ white-space: nowrap;
42
+ text-overflow: ellipsis;
43
+ color: var(--et-surface-color-solid);
44
+ }
45
+ .et-masonry-mock-subtitle {
46
+ font-size: 1.2rem;
47
+ color: var(--et-surface-color-muted-solid);
48
+ }
49
+ }`,
50
+ ].join('\n');
51
+
52
+ const resolveColumns = ({ width, columnWidth, gap }: { width: number; columnWidth: number; gap: number }) => {
53
+ const minInlineSize = Math.max(1, columnWidth);
54
+ const safeGap = Math.max(0, gap);
55
+ const count = Math.max(1, Math.floor((width + safeGap) / (minInlineSize + safeGap)));
56
+
57
+ return { count, inlineSize: Math.max(0, (width - (count - 1) * safeGap) / count) };
58
+ };
59
+
60
+ const pack = ({
61
+ heights,
62
+ count,
63
+ inlineSize,
64
+ gap,
65
+ }: {
66
+ heights: readonly number[];
67
+ count: number;
68
+ inlineSize: number;
69
+ gap: number;
70
+ }) => {
71
+ const columnBlockSizes = Array.from({ length: count }, () => 0);
72
+
73
+ const placements = heights.map((height) => {
74
+ let column = 0;
75
+
76
+ for (let candidate = 1; candidate < count; candidate++) {
77
+ if ((columnBlockSizes[candidate] ?? 0) < (columnBlockSizes[column] ?? 0)) column = candidate;
78
+ }
79
+
80
+ const blockOffset = columnBlockSizes[column] ?? 0;
81
+ columnBlockSizes[column] = blockOffset + height + gap;
82
+
83
+ return { column, inlineOffset: column * (inlineSize + gap), blockOffset };
84
+ });
85
+
86
+ return { placements, blockSize: Math.max(0, Math.max(...columnBlockSizes, 0) - gap) };
87
+ };
88
+
89
+ const styled = (css: string) => ` style="${css}"`;
90
+
91
+ /** Draws an `etMasonry` list with its items packed by the masonry maths, settled and positioned. */
92
+ export const masonry = (options: MasonryMockOptions) => {
93
+ const width = options.width ?? 448;
94
+ const gap = Math.max(0, options.gap ?? 16);
95
+ const columns = resolveColumns({ width, columnWidth: options.columnWidth ?? 250, gap });
96
+ const packing = pack({
97
+ heights: options.items.map((item) => item.height),
98
+ count: columns.count,
99
+ inlineSize: columns.inlineSize,
100
+ gap,
101
+ });
102
+
103
+ const items = options.items.map((item, index) => {
104
+ const placement = packing.placements[index];
105
+ const subtitle = item.subtitle ? `<div class="et-masonry-mock-subtitle">${item.subtitle}</div>` : '';
106
+
107
+ return [
108
+ `<li class="et-masonry-item" role="listitem" data-positioned="" data-column="${placement?.column}"${styled(`width: ${columns.inlineSize}px; --_et-masonry-item-inline-offset: ${placement?.inlineOffset}px; --_et-masonry-item-block-offset: ${placement?.blockOffset}px;`)}>`,
109
+ `<div class="et-masonry-mock-tile et-surface--inherited" data-et-surface-fill=""${styled(`height: ${item.height}px;`)}><div class="et-masonry-mock-title">${item.title}</div>${subtitle}</div>`,
110
+ `</li>`,
111
+ ].join('');
112
+ });
113
+
114
+ return `<ul class="et-masonry" role="list" data-settled=""${options.dir ? ` dir="${options.dir}"` : ''} ${styled(`width: ${width}px; height: ${packing.blockSize}px; margin: 0; padding: 0; list-style: none;`)} ${options.attributes ?? ''}>${items.join('')}</ul>`;
115
+ };
@@ -0,0 +1,131 @@
1
+ import focusRing from '../src/lib/focus-ring/focus-ring-styles.component.css?inline';
2
+ import cardBase from '../src/lib/match/match-card.component.css?inline';
3
+ import participantBase from '../src/lib/match/match-participant.component.css?inline';
4
+ import scoreBase from '../src/lib/match/match-score.component.css?inline';
5
+
6
+ export type MatchMockParticipant = {
7
+ /** The participant id the card puts on its side as `data-participant-id`, which the bracket's journey highlight reads. */
8
+ id?: string;
9
+ name: string;
10
+ code?: string;
11
+ subtitle?: string;
12
+ seed?: number;
13
+ };
14
+
15
+ export type MatchMockOptions = {
16
+ /** Left out, the side is still to be decided and draws the `TBD` label. */
17
+ home?: MatchMockParticipant;
18
+ away?: MatchMockParticipant;
19
+ status: 'scheduled' | 'live' | 'finished';
20
+ resultKind?: 'score' | 'points' | 'outcome';
21
+ homeScore?: number | string;
22
+ awayScore?: number | string;
23
+ winnerSide?: 'home' | 'away';
24
+ gameScores?: readonly { home: number; away: number }[];
25
+ label?: string;
26
+ /** The kick-off as the card formats it, e.g. `10/31/2026 8:00 PM`. */
27
+ startTime?: string;
28
+ size?: 'auto' | 'compact' | 'expanded' | 'wide';
29
+ showSeeds?: boolean;
30
+ hideNames?: boolean;
31
+ /** Draws the card on an `<a>`, the interactive host. */
32
+ interactive?: boolean;
33
+ /** A color theme name the app registers, for the live badge. Left out, the badge takes the color theme around the card. */
34
+ liveColor?: string;
35
+ /** A design marker on the host, e.g. `hover` or `focus-visible`. */
36
+ force?: string;
37
+ /** Extra classes on the host, e.g. the class a bracket cell gives its card. */
38
+ className?: string;
39
+ /** Extra attributes on the host, e.g. a style or design markers. */
40
+ attributes?: string;
41
+ };
42
+
43
+ /** The stylesheets the match mock draws with: the real match card, match participant, match score and focus ring CSS. */
44
+ export const matchStyles = /* @__PURE__ */ [focusRing, participantBase, scoreBase, cardBase].join('\n');
45
+
46
+ const SEPARATOR = ' : ';
47
+
48
+ const OUTCOME = { win: 'W', loss: 'L', draw: 'D' } as const;
49
+
50
+ const participant = ({
51
+ value,
52
+ compact,
53
+ showSeed,
54
+ }: {
55
+ value: MatchMockParticipant | undefined;
56
+ compact: boolean;
57
+ showSeed: boolean;
58
+ }) => {
59
+ const name = value ? (compact ? (value.code ?? value.name) : value.name) : 'TBD';
60
+ const mark = value
61
+ ? `<span data-mark="${(Array.from(name.trim())[0] ?? '').toUpperCase()}" class="et-match-participant-emblem-mark" aria-hidden="true"></span>`
62
+ : '';
63
+ const subtitle = value?.subtitle ? `<span class="et-match-participant-subtitle">${value.subtitle}</span>` : '';
64
+ const seed =
65
+ showSeed && value?.seed !== undefined
66
+ ? `<span class="et-match-participant-seed"><span aria-hidden="true">${value.seed}</span></span>`
67
+ : '';
68
+ const attributes = [compact ? ' data-compact=""' : '', value ? '' : ' data-tbd=""'].join('');
69
+
70
+ return `<et-match-participant class="et-match-participant et-focus-ring et-match-card-participant"${attributes}><span class="et-match-participant-emblem">${mark}</span><span class="et-match-participant-names"><span class="et-match-participant-name${value ? '' : ' et-match-participant-name--tbd'}">${name}</span>${subtitle}</span>${seed}</et-match-participant>`;
71
+ };
72
+
73
+ const outcomeFor = (options: MatchMockOptions, side: 'home' | 'away') => {
74
+ if (!options.winnerSide) return OUTCOME.draw;
75
+
76
+ return options.winnerSide === side ? OUTCOME.win : OUTCOME.loss;
77
+ };
78
+
79
+ /** Draws an `et-match-card` with its meta row, both sides, the result and the game scores, as HTML, without its visually hidden live region. Score animations are drawn at rest. */
80
+ export const matchCard = (options: MatchMockOptions) => {
81
+ const size = options.size ?? 'auto';
82
+ const resultKind = options.resultKind ?? 'score';
83
+ const compact = size === 'compact';
84
+ const live = options.status === 'live';
85
+ const drawsScore = resultKind !== 'outcome' && options.homeScore !== undefined && options.awayScore !== undefined;
86
+ const drawsOutcome = resultKind === 'outcome' && options.status === 'finished';
87
+
88
+ const liveBadge = live
89
+ ? `<span class="et-match-card-live${options.liveColor ? ` et-color--${options.liveColor}` : ''}"><span class="et-match-card-live-dot"></span> Live </span>`
90
+ : options.startTime
91
+ ? `<span class="et-match-card-time">${options.startTime}</span>`
92
+ : '';
93
+ const meta =
94
+ options.label || live || options.startTime
95
+ ? `<div class="et-match-card-meta">${options.label ? `<span class="et-match-card-label">${options.label}</span>` : ''}${liveBadge}</div>`
96
+ : '';
97
+
98
+ const side = (key: 'home' | 'away') => {
99
+ const value = key === 'home' ? options.home : options.away;
100
+ const score = key === 'home' ? options.homeScore : options.awayScore;
101
+ const result = drawsScore
102
+ ? `<et-match-score class="et-match-score et-match-card-score" aria-hidden="true"><span class="et-match-score-digit">${score}</span></et-match-score>`
103
+ : drawsOutcome
104
+ ? `<span class="et-match-card-outcome" aria-hidden="true">${outcomeFor(options, key)}</span>`
105
+ : '';
106
+
107
+ const participantId = value?.id ? ` data-participant-id="${value.id}"` : '';
108
+
109
+ return `<div class="et-match-card-side" data-side="${key}"${participantId}>${participant({ value, compact, showSeed: !!options.showSeeds })}${result}</div>`;
110
+ };
111
+
112
+ const games = options.gameScores?.length
113
+ ? `<ol class="et-match-card-games">${options.gameScores.map((game, index) => `<li aria-label="Game ${index + 1}: ${game.home}${SEPARATOR}${game.away}" class="et-match-card-game">${game.home}${SEPARATOR}${game.away}</li>`).join('')}</ol>`
114
+ : '';
115
+
116
+ const homeName = options.home?.name ?? 'TBD';
117
+ const awayName = options.away?.name ?? 'TBD';
118
+ const tag = options.interactive ? 'a' : 'et-match-card';
119
+ const hostAttributes = [
120
+ ` data-status="${options.status}" data-size="${size}" data-result-kind="${resultKind}"`,
121
+ options.winnerSide ? ` data-winner="${options.winnerSide}"` : '',
122
+ options.interactive ? ' data-interactive="" href="#"' : ' role="group"',
123
+ options.hideNames ? ' data-hide-names=""' : '',
124
+ options.force ? ` data-et-force="${options.force}"` : '',
125
+ ` aria-label="${homeName} vs ${awayName}"`,
126
+ ].join('');
127
+
128
+ const classes = ['et-match-card', options.className, 'et-focus-ring'].filter(Boolean).join(' ');
129
+
130
+ return `<${tag} class="${classes}"${hostAttributes} ${options.attributes ?? ''}><div class="et-match-card-content">${meta}<div class="et-match-card-sides">${side('home')}<span class="et-match-card-versus" aria-hidden="true">${drawsScore || drawsOutcome ? SEPARATOR.trim() : 'vs'}</span>${side('away')}</div>${games}</div></${tag}>`;
131
+ };
package/design/menu.ts ADDED
@@ -0,0 +1,143 @@
1
+ import { CHEVRON_ICON } from '../src/lib/icon/headless/chevron-icon';
2
+ import { IconDefinition } from '../src/lib/icon/headless/icon-definition';
3
+ import groupLabel from '../src/lib/menu/menu-group-label.component.css?inline';
4
+ import itemShortcut from '../src/lib/menu/menu-item-shortcut.component.css?inline';
5
+ import itemSubmenuIcon from '../src/lib/menu/menu-item-submenu-icon.component.css?inline';
6
+ import item from '../src/lib/menu/menu-item.component.css?inline';
7
+ import selectionItem from '../src/lib/menu/menu-selection-item.component.css?inline';
8
+ import separator from '../src/lib/menu/menu-separator.component.css?inline';
9
+ import menuBase from '../src/lib/menu/menu.component.css?inline';
10
+ import { iconMarkup } from './icon';
11
+
12
+ type MenuMockRowState = {
13
+ disabled?: boolean;
14
+ /** The menu's active item: the keyboard or the pointer points at it. */
15
+ active?: boolean;
16
+ /** Extra attributes on the row's element, e.g. design markers. */
17
+ attributes?: string;
18
+ };
19
+
20
+ export type MenuMockItem = MenuMockRowState & {
21
+ kind?: 'item';
22
+ label: string;
23
+ icon?: IconDefinition;
24
+ shortcut?: string;
25
+ /** Draws the item as the trigger of a submenu, with its chevron. */
26
+ submenu?: boolean | 'open';
27
+ destructive?: boolean;
28
+ };
29
+
30
+ export type MenuMockSelectionItem = MenuMockRowState & {
31
+ kind: 'checkbox' | 'radio';
32
+ label: string;
33
+ checked?: boolean | 'mixed';
34
+ };
35
+
36
+ export type MenuMockGroup = {
37
+ kind: 'checkbox-group' | 'radio-group';
38
+ label?: string;
39
+ items: MenuMockSelectionItem[];
40
+ };
41
+
42
+ export type MenuMockRow =
43
+ MenuMockItem | MenuMockSelectionItem | MenuMockGroup | { kind: 'separator' } | { kind: 'label'; label: string };
44
+
45
+ export type MenuMockOptions = {
46
+ rows: MenuMockRow[];
47
+ /** Unique per frame: the ids of the panel, group labels and submenus derive from it. */
48
+ id?: string;
49
+ /** The app's default color theme name, as the menu syncs it from its trigger. */
50
+ color?: string;
51
+ /** A surface theme name the app registers, for the panel. */
52
+ surface?: string;
53
+ };
54
+
55
+ /** The stylesheets `menu()` draws with: the real `et-menu` CSS. */
56
+ export const menuStyles = /* @__PURE__ */ [
57
+ menuBase,
58
+ item,
59
+ selectionItem,
60
+ groupLabel,
61
+ separator,
62
+ itemShortcut,
63
+ itemSubmenuIcon,
64
+ ].join('\n');
65
+
66
+ const rowAttributes = (row: MenuMockRowState) =>
67
+ [
68
+ row.attributes,
69
+ `tabindex="${row.active && !row.disabled ? 0 : -1}"`,
70
+ row.disabled && 'aria-disabled="true"',
71
+ row.active && 'data-active="true"',
72
+ ].filter(Boolean);
73
+
74
+ const menuItem = (row: MenuMockItem, submenuId: string) => {
75
+ const attributes = [
76
+ 'et-menu-item',
77
+ 'type="button"',
78
+ 'role="menuitem"',
79
+ ...rowAttributes(row),
80
+ row.submenu && `aria-haspopup="menu" aria-expanded="${row.submenu === 'open'}"`,
81
+ row.submenu === 'open' && `aria-controls="${submenuId}" data-menu-open="true"`,
82
+ 'class="et-menu-item et-color--inherited"',
83
+ `data-variant="${row.destructive ? 'destructive' : 'default'}"`,
84
+ ].filter(Boolean);
85
+ const icon = row.icon ? iconMarkup(row.icon) : '';
86
+ const shortcut = row.shortcut
87
+ ? `<et-menu-item-shortcut class="et-menu-item-shortcut" aria-hidden="true">${row.shortcut}</et-menu-item-shortcut>`
88
+ : '';
89
+ const chevron = row.submenu
90
+ ? `<et-menu-item-submenu-icon class="et-menu-item-submenu-icon" aria-hidden="true">${iconMarkup(CHEVRON_ICON)}</et-menu-item-submenu-icon>`
91
+ : '';
92
+
93
+ return `<button ${attributes.join(' ')}><span class="et-menu-item-icon">${icon}</span><span class="et-menu-item-content">${row.label}</span><span class="et-menu-item-trailing">${shortcut}${chevron}</span></button>`;
94
+ };
95
+
96
+ const selectionRow = (row: MenuMockSelectionItem) => {
97
+ const attributes = [
98
+ `role="${row.kind === 'checkbox' ? 'menuitemcheckbox' : 'menuitemradio'}"`,
99
+ ...rowAttributes(row),
100
+ `aria-checked="${row.checked ?? false}"`,
101
+ `class="et-menu-item et-menu-selection-item et-menu-${row.kind}-item"`,
102
+ ].filter(Boolean);
103
+
104
+ return `<et-menu-${row.kind}-item ${attributes.join(' ')}><span class="et-menu-item-content">${row.label}</span><span class="et-menu-item-check" aria-hidden="true"></span></et-menu-${row.kind}-item>`;
105
+ };
106
+
107
+ const groupLabelMarkup = (label: string, id: string) =>
108
+ `<et-menu-group-label class="et-menu-group-label" role="presentation" id="${id}">${label}</et-menu-group-label>`;
109
+
110
+ /** Draws the open `<et-menu>` panel with the DOM it renders inside its overlay. */
111
+ export const menu = (options: MenuMockOptions) => {
112
+ const { id = 'menu' } = options;
113
+
114
+ const rowMarkup = (row: MenuMockRow, index: number) => {
115
+ switch (row.kind) {
116
+ case 'separator':
117
+ return '<et-menu-separator class="et-menu-separator" role="separator" aria-orientation="horizontal"></et-menu-separator>';
118
+ case 'label':
119
+ return groupLabelMarkup(row.label, `et-menu-group-label-${id}-${index}`);
120
+ case 'checkbox':
121
+ case 'radio':
122
+ return selectionRow(row);
123
+ case 'checkbox-group':
124
+ case 'radio-group': {
125
+ const labelId = `et-menu-group-label-${id}-${index}`;
126
+ const label = row.label ? groupLabelMarkup(row.label, labelId) : '';
127
+ const labelledBy = row.label ? ` aria-labelledby="${labelId}"` : '';
128
+
129
+ return `<et-menu-${row.kind} class="et-menu-group et-menu-${row.kind}" role="group"${labelledBy}>${label}${row.items.map(selectionRow).join('')}</et-menu-${row.kind}>`;
130
+ }
131
+ default:
132
+ return menuItem(row, `et-menu-panel-${id}-${index}`);
133
+ }
134
+ };
135
+
136
+ const classes = [
137
+ 'et-menu',
138
+ `et-color--${options.color ?? 'inherited'}`,
139
+ `et-surface--${options.surface ?? 'inherited'}`,
140
+ ];
141
+
142
+ return `<et-menu id="et-menu-panel-${id}" class="${classes.join(' ')}" role="menu" aria-orientation="vertical" tabindex="-1"><div class="et-menu-header"><div class="et-menu-search-field"></div></div><div class="et-menu-body-wrapper"><div class="et-menu-body"><div class="et-menu-body-content">${options.rows.map(rowMarkup).join('')}</div></div></div></et-menu>`;
143
+ };