@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,161 @@
1
+ import focusRing from '../src/lib/focus-ring/focus-ring-styles.component.css?inline';
2
+ import participantBase from '../src/lib/match/match-participant.component.css?inline';
3
+ import pickBase from '../src/lib/standings/standings-pick.component.css?inline';
4
+ import standingsBase from '../src/lib/standings/standings.component.css?inline';
5
+
6
+ export type StandingsMockFormResult = 'win' | 'tie' | 'loss';
7
+
8
+ export type StandingsMockRow = {
9
+ position: number;
10
+ name: string;
11
+ played: number;
12
+ wins: number;
13
+ ties: number;
14
+ losses: number;
15
+ /** Left out, the table has no difference column unless another row has one. */
16
+ difference?: number;
17
+ points: number;
18
+ /** Oldest first. Left out on every row, the table has no form column. */
19
+ form?: readonly StandingsMockFormResult[];
20
+ };
21
+
22
+ export type StandingsMockZone = {
23
+ from: number;
24
+ to: number;
25
+ label: string;
26
+ /** A color theme name the app registers. Left out, the zone takes the color theme around the table. */
27
+ color?: string;
28
+ };
29
+
30
+ export type StandingsMockOptions = {
31
+ rows: readonly StandingsMockRow[];
32
+ zones?: readonly StandingsMockZone[];
33
+ /** The position of the highlighted (own) row. */
34
+ highlighted?: number;
35
+ showLegend?: boolean;
36
+ /** Extra attributes on the host, e.g. a style or design markers. */
37
+ attributes?: string;
38
+ };
39
+
40
+ export type StandingsPickMockRow = {
41
+ name: string;
42
+ /** The consumer's mark template content, as HTML, drawn only while locked. */
43
+ mark?: string;
44
+ /** A design marker for the reorder handle, e.g. `hover` or `focus-visible`. */
45
+ handleForce?: string;
46
+ };
47
+
48
+ export type StandingsPickMockOptions = {
49
+ rows: readonly StandingsPickMockRow[];
50
+ advancingCount: number;
51
+ locked?: boolean;
52
+ /** The index of the card being dragged, and its offset from the slot in px. */
53
+ dragging?: { index: number; offset: number };
54
+ /** Per index, the preview offset in px the other cards slide by while a drag runs. */
55
+ shifts?: readonly number[];
56
+ /** Extra attributes on the host, e.g. a style or design markers. */
57
+ attributes?: string;
58
+ };
59
+
60
+ /** The stylesheets the standings mocks draw with: the real standings, standings pick, match participant and focus ring CSS. */
61
+ export const standingsStyles = /* @__PURE__ */ [focusRing, participantBase, standingsBase, pickBase].join('\n');
62
+
63
+ const forced = (force: string | undefined) => (force ? ` data-et-force="${force}"` : '');
64
+
65
+ const colored = (color: string | undefined) => (color ? ` et-color--${color}` : '');
66
+
67
+ const participant = (name: string, className: string) =>
68
+ `<et-match-participant class="et-match-participant ${className}"><span class="et-match-participant-emblem"><span data-mark="${(Array.from(name.trim())[0] ?? '').toUpperCase()}" class="et-match-participant-emblem-mark" aria-hidden="true"></span></span><span class="et-match-participant-names"><span class="et-match-participant-name">${name}</span></span></et-match-participant>`;
69
+
70
+ const differenceText = (difference: number | undefined) =>
71
+ difference === undefined ? '' : difference > 0 ? `+${difference}` : `${difference}`;
72
+
73
+ const FORM_SHORT = { win: 'W', tie: 'D', loss: 'L' } as const;
74
+
75
+ const headCell = (column: string, label: string) =>
76
+ `<th class="et-standings-cell" data-column="${column}" scope="col">${label}</th>`;
77
+
78
+ /** Draws an `et-standings` table with its zone bands, highlighted row and legend, as HTML. */
79
+ export const standings = (options: StandingsMockOptions) => {
80
+ const zones = options.zones ?? [];
81
+ const hasDifference = options.rows.some((row) => row.difference !== undefined);
82
+ const hasForm = options.rows.some((row) => row.form);
83
+ const zoneOf = (position: number) => zones.find((zone) => position >= zone.from && position <= zone.to);
84
+
85
+ const head = [
86
+ headCell('position', '#'),
87
+ headCell('participant', 'Team'),
88
+ ...['P', 'W', 'D', 'L'].map((label) => headCell('detail', label)),
89
+ hasDifference ? headCell('detail', 'Diff') : '',
90
+ headCell('points', 'Pts'),
91
+ hasForm ? headCell('form', 'Form') : '',
92
+ ].join('');
93
+
94
+ const body = options.rows
95
+ .map((row) => {
96
+ const zone = zoneOf(row.position);
97
+ const highlighted = row.position === options.highlighted;
98
+ const attributes = [
99
+ zone ? ' data-zone=""' : '',
100
+ highlighted ? ' data-highlighted="" aria-current="true"' : '',
101
+ ].join('');
102
+ const form = row.form
103
+ ? `<span class="et-standings-form">${row.form.map((result) => `<span data-result="${result}" class="et-standings-form-result" aria-hidden="true">${FORM_SHORT[result]}</span>`).join('')}</span>`
104
+ : '';
105
+
106
+ return [
107
+ `<tr class="et-standings-row${colored(zone?.color)}"${attributes}>`,
108
+ `<th class="et-standings-cell" data-column="position" scope="row">${row.position}</th>`,
109
+ `<td class="et-standings-cell" data-column="participant">${participant(row.name, 'et-standings-participant')}</td>`,
110
+ ...[row.played, row.wins, row.ties, row.losses].map(
111
+ (value) => `<td class="et-standings-cell" data-column="detail">${value}</td>`,
112
+ ),
113
+ hasDifference
114
+ ? `<td class="et-standings-cell" data-column="detail">${differenceText(row.difference)}</td>`
115
+ : '',
116
+ `<td class="et-standings-cell" data-column="points">${row.points}</td>`,
117
+ hasForm ? `<td class="et-standings-cell" data-column="form">${form}</td>` : '',
118
+ `</tr>`,
119
+ ].join('');
120
+ })
121
+ .join('');
122
+
123
+ const legend =
124
+ options.showLegend && zones.length
125
+ ? `<ul aria-label="What the highlighted positions mean" class="et-standings-legend">${zones.map((zone) => `<li class="et-standings-legend-item"><span class="et-standings-legend-swatch${colored(zone.color)}" aria-hidden="true"></span>${zone.label}</li>`).join('')}</ul>`
126
+ : '';
127
+
128
+ return `<et-standings class="et-standings"${zones.length ? ' data-has-zones=""' : ''} ${options.attributes ?? ''}><div class="et-standings-content"><table class="et-standings-table"><thead><tr class="et-standings-head-row">${head}</tr></thead><tbody>${body}</tbody></table>${legend}</div></et-standings>`;
129
+ };
130
+
131
+ /** Draws an `et-standings-pick` list with its cut line, at rest, mid-drag or locked with marks, as HTML. */
132
+ export const standingsPick = (options: StandingsPickMockOptions) => {
133
+ const items = options.rows
134
+ .map((row, index) => {
135
+ const advancing = index < options.advancingCount;
136
+ const dragging = options.dragging?.index === index;
137
+ const offset = dragging ? options.dragging?.offset : options.shifts?.[index];
138
+ const translate = offset ? ` style="translate: 0 ${offset}px"` : '';
139
+ const mark = options.locked && row.mark ? `<span class="et-standings-pick-mark">${row.mark}</span>` : '';
140
+ const handle = options.locked
141
+ ? ''
142
+ : `<button aria-label="Move ${row.name}, position ${index + 1}" class="et-standings-pick-handle et-focus-ring" type="button"${forced(row.handleForce)}><span class="et-standings-pick-grip" aria-hidden="true"></span></button>`;
143
+ const cut =
144
+ index === options.advancingCount - 1
145
+ ? `<p class="et-standings-pick-cut" aria-hidden="true"><span class="et-standings-pick-cut-line"></span><span class="et-standings-pick-cut-label">Advancing</span><span class="et-standings-pick-cut-line"></span></p>`
146
+ : '';
147
+
148
+ return [
149
+ `<li class="et-standings-pick-item"${advancing ? ' data-advancing=""' : ''}>`,
150
+ `<div class="et-standings-pick-row"><span class="et-standings-pick-position">${index + 1}</span>`,
151
+ `<div class="et-standings-pick-card"${dragging ? ' data-dragging=""' : ''}${translate}>`,
152
+ participant(row.name, 'et-standings-pick-participant'),
153
+ mark,
154
+ handle,
155
+ `</div></div>${cut}</li>`,
156
+ ].join('');
157
+ })
158
+ .join('');
159
+
160
+ return `<et-standings-pick class="et-standings-pick"${options.locked ? ' data-locked=""' : ''} ${options.attributes ?? ''}><ol aria-label="Your predicted order" class="et-standings-pick-list"${options.dragging ? ' data-dragging=""' : ''}>${items}</ol></et-standings-pick>`;
161
+ };
@@ -0,0 +1,109 @@
1
+ import sparklineBase from '../src/lib/stat-tile/stat-tile-sparkline.component.css?inline';
2
+ import statTileBase from '../src/lib/stat-tile/stat-tile.component.css?inline';
3
+ import { skeleton, skeletonItem, skeletonStyles } from './skeleton';
4
+
5
+ export type StatTileDeltaMockOptions = {
6
+ /** The formatted, signed delta, e.g. `+6.8%`. */
7
+ text: string;
8
+ direction: 'up' | 'down' | 'neutral';
9
+ /** Left out, `neutral`. `good` and `bad` draw the delta in the ink of its color theme. */
10
+ sentiment?: 'good' | 'bad' | 'neutral';
11
+ /**
12
+ * The color theme name the delta forces, as the app's semantic theme for `success` (good) or `error` (bad)
13
+ * resolves. Left out, the delta takes the color theme around it.
14
+ */
15
+ color?: string;
16
+ };
17
+
18
+ export type StatTileMockOptions = {
19
+ label: string;
20
+ /** The formatted value. `null` draws the dash. */
21
+ value: string | null;
22
+ unit?: string;
23
+ delta?: StatTileDeltaMockOptions;
24
+ caption?: string;
25
+ /** Replaces the value and footer with the skeleton, as `[loading]` does. */
26
+ loading?: boolean;
27
+ /** The values of a projected `et-stat-tile-sparkline`, oldest first. Left out, none. */
28
+ sparkline?: readonly (number | null)[];
29
+ /** Extra attributes on the stat tile, e.g. design markers. */
30
+ attributes?: string;
31
+ };
32
+
33
+ const VIEW_WIDTH = 100;
34
+ const VIEW_HEIGHT = 32;
35
+ const VIEW_PADDING = 4;
36
+
37
+ const round = (value: number) => Math.round(value * 100) / 100;
38
+
39
+ const DELTA_ICONS = {
40
+ up: 'M6 2 11 9H1Z',
41
+ down: 'M6 10 1 3h10Z',
42
+ neutral: 'M1.5 5h9v2h-9Z',
43
+ } as const;
44
+
45
+ /** The stylesheets `statTile()` draws with: the real stat-tile, sparkline and skeleton CSS. */
46
+ export const statTileStyles = /* @__PURE__ */ [skeletonStyles, statTileBase, sparklineBase].join('\n');
47
+
48
+ /** Draws an `et-stat-tile-sparkline` with the geometry the component computes, as HTML. */
49
+ export const statTileSparkline = (values: readonly (number | null)[]) => {
50
+ const finite = values.filter((value): value is number => value !== null && Number.isFinite(value));
51
+ const min = finite.length ? Math.min(...finite) : 0;
52
+ const span = (finite.length ? Math.max(...finite) : 0) - min;
53
+ const step = values.length > 1 ? VIEW_WIDTH / (values.length - 1) : 0;
54
+ const plotHeight = VIEW_HEIGHT - VIEW_PADDING * 2;
55
+ const points = values.map((value, index) => {
56
+ if (value === null || !Number.isFinite(value)) return null;
57
+
58
+ const ratio = span ? (value - min) / span : 0.5;
59
+
60
+ return { x: values.length > 1 ? index * step : VIEW_WIDTH / 2, y: VIEW_PADDING + (1 - ratio) * plotHeight };
61
+ });
62
+ const segments = points
63
+ .map((point) => (point ? `${round(point.x)},${round(point.y)}` : '|'))
64
+ .join(' ')
65
+ .split('|')
66
+ .map((segment) => segment.trim().split(' ').filter(Boolean))
67
+ .filter((segment) => segment.length > 1);
68
+ const line = segments
69
+ .map((segment) => segment.map((point, index) => `${index ? 'L' : 'M'}${point}`).join(''))
70
+ .join('');
71
+ const current = [...points].reverse().find((point) => point !== null) ?? null;
72
+ const dot = current ? `<path d="M${current.x},${current.y}h0" class="et-stat-tile-sparkline-current"></path>` : '';
73
+
74
+ return `<et-stat-tile-sparkline class="et-stat-tile-sparkline" aria-hidden="true"><svg viewBox="0 0 ${VIEW_WIDTH} ${VIEW_HEIGHT}" class="et-stat-tile-sparkline-svg" preserveAspectRatio="none" aria-hidden="true" focusable="false"><path d="${line}" class="et-stat-tile-sparkline-line"></path>${dot}</svg></et-stat-tile-sparkline>`;
75
+ };
76
+
77
+ const loadingMarkup = () =>
78
+ skeleton({
79
+ content: [
80
+ skeletonItem({ shape: 'rect' }).replace('class="', 'class="et-stat-tile-value-skeleton '),
81
+ skeletonItem({ shape: 'rect' }).replace('class="', 'class="et-stat-tile-delta-skeleton '),
82
+ ].join(''),
83
+ }).replace('class="et-skeleton', 'class="et-stat-tile-skeleton et-skeleton');
84
+
85
+ const deltaMarkup = (delta: StatTileDeltaMockOptions) =>
86
+ `<span class="et-color--${delta.color ?? 'inherited'} et-stat-tile-delta" data-direction="${delta.direction}" data-sentiment="${delta.sentiment ?? 'neutral'}"><svg class="et-stat-tile-delta-icon" viewBox="0 0 12 12" aria-hidden="true" focusable="false"><path d="${DELTA_ICONS[delta.direction]}"></path></svg>${delta.text}</span>`;
87
+
88
+ const valueMarkup = (options: StatTileMockOptions) => {
89
+ const unit = options.unit ? ` <span class="et-stat-tile-unit">${options.unit}</span>` : '';
90
+ const value = options.value === null ? '<span aria-hidden="true">–</span>' : `${options.value}${unit}`;
91
+ const footerParts = [
92
+ options.delta && deltaMarkup(options.delta),
93
+ options.caption && `<span class="et-stat-tile-caption">${options.caption}</span>`,
94
+ ].filter(Boolean);
95
+ const footer = footerParts.length ? ` <span class="et-stat-tile-footer">${footerParts.join(' ')}</span>` : '';
96
+
97
+ return `<span class="et-stat-tile-value">${value}</span>${footer}`;
98
+ };
99
+
100
+ /** Draws an `et-stat-tile` with its projected sparkline as the component renders it, as HTML, without its visually hidden text. */
101
+ export const statTile = (options: StatTileMockOptions) => {
102
+ const attributes = [options.attributes, options.loading && 'aria-busy="true"', 'class="et-stat-tile"'].filter(
103
+ Boolean,
104
+ );
105
+ const body = options.loading ? loadingMarkup() : valueMarkup(options);
106
+ const sparkline = options.sparkline ? statTileSparkline(options.sparkline) : '';
107
+
108
+ return `<et-stat-tile ${attributes.join(' ')}><span class="et-stat-tile-label">${options.label}</span> ${body}${sparkline}</et-stat-tile>`;
109
+ };
@@ -0,0 +1,92 @@
1
+ import iconBase from '../src/lib/icon/headless/icon-styles.component.css?inline';
2
+ import { ARROW_OUT_UP_RIGHT_ICON } from '../src/lib/icon/headless/arrow-out-up-right-icon';
3
+ import { IconDefinition } from '../src/lib/icon/headless/icon-definition';
4
+ import { LOCK_ICON } from '../src/lib/icon/headless/lock-icon';
5
+ import { TRIANGLE_EXCLAMATION_ICON } from '../src/lib/icon/headless/triangle-exclamation-icon';
6
+ import consentBase from '../src/lib/stream/consent/stream-consent.component.css?inline';
7
+ import errorBase from '../src/lib/stream/error/stream-player-error.component.css?inline';
8
+ import loadingBase from '../src/lib/stream/loading/stream-player-loading.component.css?inline';
9
+ import placeholderBase from '../src/lib/stream/pip/pip-slot-placeholder.component.css?inline';
10
+ import overlayCardBase from '../src/lib/stream/stream-overlay-card-styles.component.css?inline';
11
+ import slotBase from '../src/lib/stream/stream-player-slot-styles.component.css?inline';
12
+ import { button, buttonStyles } from './button';
13
+ import { iconMarkup } from './icon';
14
+ import { loaderStyles, spinner } from './loader';
15
+
16
+ export type StreamMockState = 'player' | 'loading' | 'consent' | 'error' | 'pip-placeholder';
17
+
18
+ export type StreamMockOptions = {
19
+ /** What the slot shows: the bare player, or one of the overlays the slot mounts over it. */
20
+ state: StreamMockState;
21
+ /** The player element, as HTML. The consent state draws no player, as the slot holds it back until consent. */
22
+ player?: string;
23
+ /** A color theme name the app registers, for the overlay button. Left out, the button takes the color theme around it. */
24
+ buttonColor?: string;
25
+ /** Extra attributes on the overlay button, e.g. design markers. */
26
+ buttonAttributes?: string;
27
+ /** Extra attributes on the slot host, e.g. a style or design markers. */
28
+ attributes?: string;
29
+ };
30
+
31
+ /** The stylesheets `streamPlayerSlot()` draws with: the real slot, overlay card, consent, error, loading, PiP placeholder, spinner, button and icon CSS. */
32
+ export const streamStyles = /* @__PURE__ */ [
33
+ buttonStyles,
34
+ iconBase,
35
+ loaderStyles,
36
+ slotBase,
37
+ overlayCardBase,
38
+ consentBase,
39
+ errorBase,
40
+ loadingBase,
41
+ placeholderBase,
42
+ ].join('\n');
43
+
44
+ const LABELS = {
45
+ loading: 'Loading',
46
+ consentHeading: 'Content blocked',
47
+ consentDescription: 'Playback requires your consent. Third-party cookies and data may be used.',
48
+ consentAccept: 'Allow and play',
49
+ errorHeading: 'Playback failed',
50
+ errorDescription: 'The player could not be loaded. Please check your connection or try again.',
51
+ errorRetry: 'Retry',
52
+ pipPlaceholderMessage: 'Playing in picture-in-picture',
53
+ pipPlaceholderBack: 'Back to player',
54
+ } as const;
55
+
56
+ const icon = (definition: IconDefinition, className: string) =>
57
+ iconMarkup(definition, className)
58
+ .replace(/^<i /, '<span ')
59
+ .replace(/<\/i>$/, '</span>');
60
+
61
+ const overlayButton = (label: string, options: StreamMockOptions) =>
62
+ button({ label, color: options.buttonColor }).replace('<button ', `<button ${options.buttonAttributes ?? ''} `);
63
+
64
+ const consent = (options: StreamMockOptions) =>
65
+ `<et-stream-consent class="et-stream-consent" role="group" aria-labelledby="et-stream-consent-heading-0"><div class="et-stream-consent-card">${icon(LOCK_ICON, 'et-stream-consent-icon')}<p id="et-stream-consent-heading-0" class="et-stream-consent-heading">${LABELS.consentHeading}</p><p class="et-stream-consent-description">${LABELS.consentDescription}</p>${overlayButton(LABELS.consentAccept, options)}</div></et-stream-consent>`;
66
+
67
+ const loading = () =>
68
+ `<et-stream-player-loading class="et-stream-player-loading" role="status" aria-label="${LABELS.loading}">${spinner({ diameter: 35, strokeWidth: 2, track: true })}</et-stream-player-loading>`;
69
+
70
+ const error = (options: StreamMockOptions) =>
71
+ `<et-stream-player-error class="et-stream-player-error" role="alert"><div class="et-stream-player-error-card">${icon(TRIANGLE_EXCLAMATION_ICON, 'et-stream-player-error-icon')}<p class="et-stream-player-error-heading">${LABELS.errorHeading}</p><p class="et-stream-player-error-description">${LABELS.errorDescription}</p>${overlayButton(LABELS.errorRetry, options)}</div></et-stream-player-error>`;
72
+
73
+ const pipPlaceholder = (options: StreamMockOptions) =>
74
+ `<et-pip-slot-placeholder><div class="et-pip-slot-placeholder-overlay"><div class="et-pip-slot-placeholder-card">${icon(ARROW_OUT_UP_RIGHT_ICON, 'et-pip-slot-placeholder-icon')}<p class="et-pip-slot-placeholder-message">${LABELS.pipPlaceholderMessage}</p>${overlayButton(LABELS.pipPlaceholderBack, options)}</div></div></et-pip-slot-placeholder>`;
75
+
76
+ const OVERLAY = {
77
+ player: () => '',
78
+ loading,
79
+ consent,
80
+ error,
81
+ 'pip-placeholder': pipPlaceholder,
82
+ } as const satisfies Record<StreamMockState, (options: StreamMockOptions) => string>;
83
+
84
+ /**
85
+ * Draws an `etStreamPlayerSlot` host as HTML: the player element and the overlay the slot mounts for the state,
86
+ * with the default labels. The loading spinner is held at one frame.
87
+ */
88
+ export const streamPlayerSlot = (options: StreamMockOptions) => {
89
+ const player = options.state === 'consent' || options.state === 'pip-placeholder' ? '' : (options.player ?? '');
90
+
91
+ return `<div class="et-stream-player-slot" ${options.attributes ?? ''}>${player}${OVERLAY[options.state](options)}</div>`;
92
+ };
@@ -0,0 +1,83 @@
1
+ import { interactiveStyles } from '@ethlete/core/design/interactive-styles';
2
+ import choiceFieldBase from '../src/lib/forms/choice-field/choice-field.component.css?inline';
3
+ import choiceFieldCard from '../src/lib/forms/choice-field/choice-field-card-styles.component.css?inline';
4
+ import label from '../src/lib/forms/form-field/headless/label.directive.css?inline';
5
+ import selectionCard from '../src/lib/forms/selection-card-styles.component.css?inline';
6
+ import switchBase from '../src/lib/forms/switch/switch.component.css?inline';
7
+
8
+ export type SwitchMockOptions = {
9
+ checked?: boolean;
10
+ /** The bulk-edit state: the thumb parks mid-track. Wins over `checked` in the look. */
11
+ indeterminate?: boolean;
12
+ disabled?: boolean;
13
+ /** View-only: the normal look, without the interactive affordance. */
14
+ readonly?: boolean;
15
+ required?: boolean;
16
+ /** The label, as HTML. Draws the switch inside an `<et-choice-field>`; left out, the switch stands alone. */
17
+ label?: string;
18
+ /** Unique per frame: the label id derives from it. */
19
+ id?: string;
20
+ /** The `et-choice-field` size. Only drawn with a `label`. */
21
+ size?: 'sm' | 'md' | 'lg';
22
+ /** The `et-choice-field` variant. Only drawn with a `label`. */
23
+ variant?: 'plain' | 'card';
24
+ /** Where a `card` puts the switch. */
25
+ controlPosition?: 'start' | 'end';
26
+ /** A color theme name the app registers. Only drawn with a `label`. */
27
+ color?: string;
28
+ /** Extra attributes on the `<et-switch>`, e.g. design markers like `data-et-force="hover"`. */
29
+ attributes?: string;
30
+ };
31
+
32
+ /** The stylesheets `switchControl()` draws with: the real `et-switch` and `et-choice-field` CSS. */
33
+ export const switchStyles = /* @__PURE__ */ [
34
+ interactiveStyles,
35
+ label,
36
+ selectionCard,
37
+ choiceFieldBase,
38
+ choiceFieldCard,
39
+ switchBase,
40
+ ].join('\n');
41
+
42
+ /** Draws `<et-switch>` as HTML, inside an `<et-choice-field>` when it has a label, with the DOM the components render. */
43
+ export const switchControl = (options: SwitchMockOptions = {}) => {
44
+ const { id = 'switch', variant = 'plain' } = options;
45
+ const labelId = options.label !== undefined ? `et-label-${id}` : null;
46
+
47
+ const switchAttributes = [
48
+ options.attributes,
49
+ 'role="switch"',
50
+ 'class="et-switch et-color-interactive"',
51
+ 'data-can-animate="true"',
52
+ `aria-checked="${!!options.checked}"`,
53
+ options.indeterminate && 'data-indeterminate="true"',
54
+ options.disabled && 'aria-disabled="true"',
55
+ options.readonly && 'aria-readonly="true" data-readonly="true"',
56
+ options.required && 'aria-required="true"',
57
+ labelId && `aria-labelledby="${labelId}"`,
58
+ `tabindex="${options.disabled ? -1 : 0}"`,
59
+ ].filter(Boolean);
60
+
61
+ const control = `<et-switch ${switchAttributes.join(' ')}><div class="et-switch-track"><div class="et-switch-thumb"></div></div></et-switch>`;
62
+
63
+ if (labelId === null) return control;
64
+
65
+ const fieldAttributes = [
66
+ `class="et-choice-field et-color--${options.color ?? 'inherited'}"`,
67
+ `data-size="${options.size ?? 'md'}"`,
68
+ `data-variant="${variant}"`,
69
+ 'data-can-animate="true"',
70
+ variant === 'card' && `data-control-position="${options.controlPosition ?? 'end'}"`,
71
+ ].filter(Boolean);
72
+
73
+ const controlClasses = [
74
+ variant === 'card' && 'et-selection-card',
75
+ 'et-choice-field-control',
76
+ 'et-color-interactive',
77
+ 'et-color-interactive-container',
78
+ ].filter(Boolean);
79
+
80
+ const labelHtml = `<et-label id="${labelId}"${options.disabled ? ' data-disabled="true"' : ''}><span class="et-label-content">${options.label}</span>${options.required ? '<span class="et-label-required-marker" aria-hidden="true">*</span>' : ''}</et-label>`;
81
+
82
+ return `<et-choice-field ${fieldAttributes.join(' ')}><div class="${controlClasses.join(' ')}"><div class="et-choice-field-control-slot et-selection-card-control">${control}</div><div class="et-choice-field-label-area et-selection-card-content">${labelHtml}</div></div></et-choice-field>`;
83
+ };