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

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 (270) hide show
  1. package/CHANGELOG.md +139 -0
  2. package/design/accordion.ts +73 -0
  3. package/design/anchored-dialog.ts +92 -0
  4. package/design/avatar.ts +90 -0
  5. package/design/badge.ts +48 -0
  6. package/design/banner.ts +77 -0
  7. package/design/bracket.ts +457 -0
  8. package/design/breadcrumb.ts +145 -0
  9. package/design/button.ts +62 -1
  10. package/design/calendar.ts +306 -0
  11. package/design/card.ts +92 -0
  12. package/design/carousel.ts +240 -0
  13. package/design/chart.ts +512 -0
  14. package/design/checkbox.ts +275 -0
  15. package/design/chip.ts +64 -0
  16. package/design/config.json +8 -0
  17. package/design/control.ts +19 -0
  18. package/design/copy-button.ts +59 -0
  19. package/design/description-list.ts +41 -0
  20. package/design/dialog.ts +216 -0
  21. package/design/divider.ts +27 -0
  22. package/design/empty-state.ts +45 -0
  23. package/design/floating-action.ts +55 -0
  24. package/design/form-field.ts +3 -0
  25. package/design/grid.ts +148 -0
  26. package/design/input.ts +86 -0
  27. package/design/kbd.ts +19 -0
  28. package/design/loader.ts +152 -0
  29. package/design/masonry.ts +115 -0
  30. package/design/match.ts +131 -0
  31. package/design/menu.ts +147 -0
  32. package/design/notification.ts +133 -0
  33. package/design/otp-input.ts +107 -0
  34. package/design/pagination.ts +120 -0
  35. package/design/picture.ts +80 -0
  36. package/design/progress-steps.ts +79 -0
  37. package/design/query-error.ts +60 -0
  38. package/design/radio-group.ts +132 -0
  39. package/design/scheduler.ts +369 -0
  40. package/design/scrollable.ts +218 -0
  41. package/design/scrollbar.ts +47 -0
  42. package/design/sdk/assets/fallback.svg +1 -0
  43. package/design/sdk/assets/tall.svg +1 -0
  44. package/design/sdk/assets/wide.svg +1 -0
  45. package/design/sdk/components/accent-on-light/accent-on-light.css +35 -0
  46. package/design/sdk/components/accent-on-light/accent-on-light.ts +10 -0
  47. package/design/sdk/components/accent-on-light/shared/controls.ts +46 -0
  48. package/design/sdk/components/accordion/accordion.css +17 -0
  49. package/design/sdk/components/accordion/accordion.ts +68 -0
  50. package/design/sdk/components/anchored-dialog/anchored-dialog.css +63 -0
  51. package/design/sdk/components/anchored-dialog/anchored-dialog.ts +65 -0
  52. package/design/sdk/components/avatar/avatar.css +14 -0
  53. package/design/sdk/components/avatar/avatar.ts +79 -0
  54. package/design/sdk/components/badge/badge.css +13 -0
  55. package/design/sdk/components/badge/badge.ts +53 -0
  56. package/design/sdk/components/banner/banner.css +18 -0
  57. package/design/sdk/components/banner/banner.ts +88 -0
  58. package/design/sdk/components/bracket/bracket.css +59 -0
  59. package/design/sdk/components/bracket/bracket.ts +144 -0
  60. package/design/sdk/components/breadcrumb/breadcrumb.css +25 -0
  61. package/design/sdk/components/breadcrumb/breadcrumb.ts +82 -0
  62. package/design/sdk/components/button/button.css +41 -0
  63. package/design/sdk/components/button/button.ts +59 -7
  64. package/design/sdk/components/calendar/calendar.css +33 -0
  65. package/design/sdk/components/calendar/calendar.ts +97 -0
  66. package/design/sdk/components/card/card.css +19 -0
  67. package/design/sdk/components/card/card.ts +62 -0
  68. package/design/sdk/components/carousel/carousel.css +44 -0
  69. package/design/sdk/components/carousel/carousel.ts +75 -0
  70. package/design/sdk/components/chart/chart.css +56 -0
  71. package/design/sdk/components/chart/chart.ts +99 -0
  72. package/design/sdk/components/checkbox/checkbox.css +65 -0
  73. package/design/sdk/components/checkbox/checkbox.ts +180 -0
  74. package/design/sdk/components/chip/chip.css +18 -0
  75. package/design/sdk/components/chip/chip.ts +77 -0
  76. package/design/sdk/components/copy-button/copy-button.css +14 -0
  77. package/design/sdk/components/copy-button/copy-button.ts +60 -0
  78. package/design/sdk/components/description-list/description-list.css +7 -0
  79. package/design/sdk/components/description-list/description-list.ts +54 -0
  80. package/design/sdk/components/dialog/dialog.css +29 -0
  81. package/design/sdk/components/dialog/dialog.ts +79 -0
  82. package/design/sdk/components/divider/divider.css +26 -0
  83. package/design/sdk/components/divider/divider.ts +44 -0
  84. package/design/sdk/components/empty-state/empty-state.css +15 -0
  85. package/design/sdk/components/empty-state/empty-state.ts +57 -0
  86. package/design/sdk/components/floating-action/floating-action.css +51 -0
  87. package/design/sdk/components/floating-action/floating-action.ts +63 -0
  88. package/design/sdk/components/form-field/form-field.ts +23 -5
  89. package/design/sdk/components/grid/grid.css +17 -0
  90. package/design/sdk/components/grid/grid.ts +81 -0
  91. package/design/sdk/components/input/input.css +75 -0
  92. package/design/sdk/components/input/input.ts +80 -0
  93. package/design/sdk/components/kbd/kbd.css +25 -0
  94. package/design/sdk/components/kbd/kbd.ts +58 -0
  95. package/design/sdk/components/loader/loader.css +37 -0
  96. package/design/sdk/components/loader/loader.ts +69 -0
  97. package/design/sdk/components/masonry/masonry.css +7 -0
  98. package/design/sdk/components/masonry/masonry.ts +43 -0
  99. package/design/sdk/components/match/match.css +25 -0
  100. package/design/sdk/components/match/match.ts +77 -0
  101. package/design/sdk/components/menu/menu.css +22 -0
  102. package/design/sdk/components/menu/menu.ts +103 -0
  103. package/design/sdk/components/notification/notification.css +32 -0
  104. package/design/sdk/components/notification/notification.ts +107 -0
  105. package/design/sdk/components/otp-input/otp-input.css +35 -0
  106. package/design/sdk/components/otp-input/otp-input.ts +65 -0
  107. package/design/sdk/components/pagination/pagination.css +14 -0
  108. package/design/sdk/components/pagination/pagination.ts +76 -0
  109. package/design/sdk/components/picture/picture.css +37 -0
  110. package/design/sdk/components/picture/picture.ts +97 -0
  111. package/design/sdk/components/progress-steps/progress-steps.css +37 -0
  112. package/design/sdk/components/progress-steps/progress-steps.ts +92 -0
  113. package/design/sdk/components/query-error/query-error.css +33 -0
  114. package/design/sdk/components/query-error/query-error.ts +121 -0
  115. package/design/sdk/components/radio-group/radio-group.css +46 -0
  116. package/design/sdk/components/radio-group/radio-group.ts +167 -0
  117. package/design/sdk/components/scheduler/scheduler.css +55 -0
  118. package/design/sdk/components/scheduler/scheduler.ts +240 -0
  119. package/design/sdk/components/scrollable/scrollable.css +58 -0
  120. package/design/sdk/components/scrollable/scrollable.ts +133 -0
  121. package/design/sdk/components/scrollbar/scrollbar.css +78 -0
  122. package/design/sdk/components/scrollbar/scrollbar.ts +124 -0
  123. package/design/sdk/components/select/select.ts +1 -1
  124. package/design/sdk/components/select/shared/select-part.ts +25 -3
  125. package/design/sdk/components/select-panel/select-panel.css +3 -0
  126. package/design/sdk/components/select-panel/select-panel.ts +125 -0
  127. package/design/sdk/components/skeleton/skeleton.css +22 -0
  128. package/design/sdk/components/skeleton/skeleton.ts +51 -0
  129. package/design/sdk/components/slider/slider.css +52 -0
  130. package/design/sdk/components/slider/slider.ts +87 -0
  131. package/design/sdk/components/standings/standings.css +33 -0
  132. package/design/sdk/components/standings/standings.ts +131 -0
  133. package/design/sdk/components/stat-tile/stat-tile.css +15 -0
  134. package/design/sdk/components/stat-tile/stat-tile.ts +85 -0
  135. package/design/sdk/components/stream/stream.css +48 -0
  136. package/design/sdk/components/stream/stream.ts +43 -0
  137. package/design/sdk/components/switch/switch.css +37 -0
  138. package/design/sdk/components/switch/switch.ts +63 -0
  139. package/design/sdk/components/table/table.css +67 -0
  140. package/design/sdk/components/table/table.ts +161 -0
  141. package/design/sdk/components/tabs/tabs.css +13 -0
  142. package/design/sdk/components/tabs/tabs.ts +74 -0
  143. package/design/sdk/components/textarea/textarea.css +116 -0
  144. package/design/sdk/components/textarea/textarea.ts +65 -0
  145. package/design/sdk/components/timeline/timeline.css +30 -0
  146. package/design/sdk/components/timeline/timeline.ts +87 -0
  147. package/design/sdk/components/toolbar/toolbar.css +24 -0
  148. package/design/sdk/components/toolbar/toolbar.ts +88 -0
  149. package/design/sdk/components/tooltip/tooltip.css +15 -0
  150. package/design/sdk/components/tooltip/tooltip.ts +28 -0
  151. package/design/sdk/components/tree/selected-tint.css +15 -0
  152. package/design/sdk/components/tree/tree.ts +70 -0
  153. package/design/sdk/foundations/colors/colors.css +124 -4
  154. package/design/sdk/foundations/colors/colors.ts +1 -1
  155. package/design/sdk/foundations/colors/shared/contrast.ts +1 -1
  156. package/design/sdk/foundations/elevation/elevation.css +6 -0
  157. package/design/sdk/foundations/elevation/elevation.ts +11 -0
  158. package/design/sdk/foundations/elevation/shared/elevation/scene.css +101 -0
  159. package/design/sdk/foundations/elevation/shared/elevation/scene.ts +74 -0
  160. package/design/sdk/foundations/icons/icons.ts +119 -0
  161. package/design/sdk/pictures.json +1183 -0
  162. package/design/select.ts +5 -4
  163. package/design/skeleton.ts +67 -0
  164. package/design/slider.ts +155 -0
  165. package/design/standings.ts +161 -0
  166. package/design/stat-tile.ts +109 -0
  167. package/design/stream.ts +92 -0
  168. package/design/switch.ts +83 -0
  169. package/design/table.ts +302 -0
  170. package/design/tabs.ts +75 -0
  171. package/design/textarea.ts +94 -0
  172. package/design/timeline.ts +52 -0
  173. package/design/toolbar.ts +95 -0
  174. package/design/tooltip.ts +87 -0
  175. package/fesm2022/ethlete-components.mjs +1440 -1209
  176. package/fesm2022/ethlete-components.mjs.map +1 -1
  177. package/generators/generators.json +2 -0
  178. package/package.json +1 -1
  179. package/src/lib/accordion/accordion.component.css +2 -1
  180. package/src/lib/avatar/avatar.component.css +10 -0
  181. package/src/lib/badge/badge.component.css +2 -2
  182. package/src/lib/banner/banner.component.css +3 -1
  183. package/src/lib/breadcrumb/breadcrumb-outlet.component.css +5 -0
  184. package/src/lib/breadcrumb/breadcrumb.component.css +5 -1
  185. package/src/lib/button/button-properties-styles.component.css +6 -6
  186. package/src/lib/button/button.component.css +27 -4
  187. package/src/lib/button/button.types.ts +33 -0
  188. package/src/lib/button/icon-button.component.css +21 -22
  189. package/src/lib/button/split-button.component.css +5 -1
  190. package/src/lib/button/window-control-button.component.css +43 -3
  191. package/src/lib/calendar/calendar.component.css +15 -5
  192. package/src/lib/card/card.component.css +4 -0
  193. package/src/lib/carousel/carousel-autoplay-styles.component.css +1 -1
  194. package/src/lib/carousel/carousel.component.css +2 -2
  195. package/src/lib/chart/chart-axis.component.css +3 -3
  196. package/src/lib/chip/chip.component.css +12 -6
  197. package/src/lib/description-list/description-list.component.css +2 -1
  198. package/src/lib/empty-state/empty-state.component.css +4 -0
  199. package/src/lib/floating-action/floating-action-styles.component.css +27 -0
  200. package/src/lib/focus-ring/focus-ring-styles.component.css +4 -1
  201. package/src/lib/forms/checkbox/checkbox.component.css +31 -5
  202. package/src/lib/forms/color-input/color-input.component.css +3 -1
  203. package/src/lib/forms/color-input/color-picker-panel.component.css +6 -2
  204. package/src/lib/forms/date-time/date-input/date-input.component.css +1 -1
  205. package/src/lib/forms/date-time/date-time-input/date-time-input.component.css +1 -1
  206. package/src/lib/forms/date-time/duration-input/duration-input.component.css +1 -1
  207. package/src/lib/forms/date-time/range-input-shell-styles.component.css +1 -1
  208. package/src/lib/forms/date-time/time-input/time-input.component.css +1 -1
  209. package/src/lib/forms/description/description.component.css +7 -0
  210. package/src/lib/forms/dropzone/dropzone.component.css +26 -0
  211. package/src/lib/forms/form-field/form-field-control-suffix-styles.component.css +4 -2
  212. package/src/lib/forms/form-field/form-field-text-shell-styles.component.css +54 -5
  213. package/src/lib/forms/form-field/form-field-textarea-styles.component.css +62 -0
  214. package/src/lib/forms/form-field/form-field.component.css +6 -0
  215. package/src/lib/forms/form-field/headless/label.directive.css +1 -1
  216. package/src/lib/forms/input/input.component.css +6 -2
  217. package/src/lib/forms/input/number-input.component.css +2 -2
  218. package/src/lib/forms/input/password-input.component.css +4 -3
  219. package/src/lib/forms/otp-input/otp-input.component.css +23 -4
  220. package/src/lib/forms/phone-input/phone-input.component.css +4 -3
  221. package/src/lib/forms/rating/rating.component.css +2 -1
  222. package/src/lib/forms/rich-text-editor/rich-text-editor.component.css +1 -1
  223. package/src/lib/forms/selection-card-styles.component.css +3 -2
  224. package/src/lib/forms/selection-list/checkbox-group/checkbox-group-select-all.component.css +4 -2
  225. package/src/lib/forms/selection-list/checkbox-group/checkbox-option.component.css +4 -2
  226. package/src/lib/forms/selection-list/radio-group/radio.component.css +5 -2
  227. package/src/lib/forms/selection-list/segmented-button-group/segmented-button.component.css +2 -1
  228. package/src/lib/forms/slider/range-slider.component.css +98 -6
  229. package/src/lib/forms/slider/slider.component.css +96 -4
  230. package/src/lib/forms/switch/switch.component.css +45 -15
  231. package/src/lib/forms/tag-input/tag-input.component.css +2 -2
  232. package/src/lib/forms/textarea/textarea-autosize-styles.component.css +2 -3
  233. package/src/lib/forms/textarea/textarea.component.css +7 -2
  234. package/src/lib/grid/grid-item-toolbar.component.css +10 -0
  235. package/src/lib/grid/grid-item.component.css +6 -1
  236. package/src/lib/kbd/kbd.component.css +1 -1
  237. package/src/lib/loader/brand-loader/brand-loader.component.css +4 -1
  238. package/src/lib/loader/progress-bar/progress-bar.component.css +1 -1
  239. package/src/lib/loader/spinner/spinner-styles.component.css +1 -1
  240. package/src/lib/match/match-card.component.css +9 -3
  241. package/src/lib/match/match-score.component.css +4 -1
  242. package/src/lib/menu/menu-selection-item.component.css +1 -1
  243. package/src/lib/menu/menu.component.css +6 -1
  244. package/src/lib/notification/notification.component.css +3 -3
  245. package/src/lib/overlay/overlay-container.component.css +4 -0
  246. package/src/lib/pagination/pagination.component.css +4 -0
  247. package/src/lib/progress-steps/progress-step.component.css +4 -4
  248. package/src/lib/query-error/query-error.component.css +4 -0
  249. package/src/lib/scheduler/scheduler-appointment-styles.component.css +21 -5
  250. package/src/lib/scheduler/scheduler-month-view.component.css +4 -2
  251. package/src/lib/scheduler/scheduler-time-grid-view.component.css +8 -2
  252. package/src/lib/scrollable/headless/scrollable-navigation.component.css +1 -1
  253. package/src/lib/standings/standings-pick.component.css +1 -1
  254. package/src/lib/standings/standings.component.css +10 -10
  255. package/src/lib/stat-tile/stat-tile-sparkline.component.css +1 -1
  256. package/src/lib/stream/consent/stream-consent.component.css +99 -0
  257. package/src/lib/stream/error/stream-player-error.component.css +100 -0
  258. package/src/lib/stream/loading/stream-player-loading.component.css +16 -0
  259. package/src/lib/stream/pip/pip-slot-placeholder.component.css +68 -0
  260. package/src/lib/stream/stream-player-slot-styles.component.css +4 -0
  261. package/src/lib/table/table-resize-grip.component.css +2 -1
  262. package/src/lib/table/table-select-cell.component.css +45 -15
  263. package/src/lib/table/table.component.css +15 -5
  264. package/src/lib/tabs/tab-bar-styles.component.css +1 -1
  265. package/src/lib/tabs/tabs/tab-group.component.css +111 -0
  266. package/src/lib/time-picker/time-picker.component.css +2 -1
  267. package/src/lib/timeline/timeline-item.component.css +5 -1
  268. package/src/lib/toolbar/toolbar.component.css +4 -0
  269. package/src/lib/tooltip/tooltip.component.css +14 -0
  270. package/types/ethlete-components.d.ts +156 -128
@@ -0,0 +1,76 @@
1
+ import {
2
+ pagination,
3
+ type PaginationMockItem,
4
+ type PaginationMockOptions,
5
+ paginationStyles,
6
+ } from '@ethlete/components/design/pagination';
7
+ import { definePart, drawing, interactionRows } from '@design-system';
8
+ import edge from './pagination.css?inline';
9
+
10
+ type ItemInteraction = 'hover' | 'active' | 'focus-visible';
11
+
12
+ type Props = Omit<PaginationMockOptions, 'items'> & {
13
+ page: number;
14
+ pages: (number | null)[];
15
+ total: number;
16
+ target?: number;
17
+ forced?: ItemInteraction;
18
+ };
19
+
20
+ const MARK = 'data-et-control="item" data-can-animate';
21
+
22
+ const items = ({ page, pages, total, target, forced }: Props): PaginationMockItem[] => {
23
+ const attributes = (entry: number) => (forced && entry === target ? `${MARK} data-et-force="${forced}"` : MARK);
24
+
25
+ return [
26
+ { type: 'first', page: 1, disabled: page === 1, attributes: MARK },
27
+ { type: 'previous', page: page - 1, disabled: page === 1, attributes: MARK },
28
+ ...pages.map((entry): PaginationMockItem =>
29
+ entry === null
30
+ ? { type: 'ellipsis' }
31
+ : { type: 'page', page: entry, current: entry === page, attributes: attributes(entry) },
32
+ ),
33
+ { type: 'next', page: page + 1, disabled: page === total, attributes: MARK },
34
+ { type: 'last', page: total, disabled: page === total, attributes: MARK },
35
+ ];
36
+ };
37
+
38
+ const MIDDLE: Props = { page: 10, total: 20, pages: [1, null, 9, 10, 11, null, 20] };
39
+
40
+ const { states, grid } = interactionRows(
41
+ {
42
+ page: { ...MIDDLE, target: 11 },
43
+ current: { ...MIDDLE, target: 10 },
44
+ } satisfies Record<string, Props>,
45
+ {
46
+ interactions: ['hover', 'active', 'focus-visible'],
47
+ force: (props: Props, interaction: ItemInteraction): Props => ({ ...props, forced: interaction }),
48
+ },
49
+ );
50
+
51
+ export default definePart({
52
+ note: 'Decided rules only',
53
+ madeBy: ['claude-opus-5-5'],
54
+ states: {
55
+ first: { page: 1, total: 20, pages: [1, 2, 3, 4, 5, null, 20] },
56
+ middle: MIDDLE,
57
+ last: { page: 20, total: 20, pages: [1, null, 16, 17, 18, 19, 20] },
58
+ few: { page: 2, total: 5, pages: [1, 2, 3, 4, 5] },
59
+ ...states,
60
+ range: { ...MIDDLE, range: 'Showing 91–100 of 200' },
61
+ sm: { ...MIDDLE, size: 'sm' },
62
+ links: { ...MIDDLE, renderAs: 'links' },
63
+ compact: { ...MIDDLE, compactStatus: '10 / 20' },
64
+ },
65
+ grid,
66
+ widths: [480],
67
+ controls: {
68
+ item: { kind: 'button', input: ['pointer', 'keyboard'], disabled: true },
69
+ },
70
+ frame: (body) => `<div class="pagination-frame">${body}</div>`,
71
+ render: (props: Props) =>
72
+ drawing({
73
+ body: pagination({ ...props, items: items(props) }),
74
+ styles: [paginationStyles, edge].join('\n'),
75
+ }),
76
+ });
@@ -0,0 +1,37 @@
1
+ .picture-frame {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 0.8rem;
5
+ max-inline-size: 520px;
6
+ padding: 2.4rem 1.6rem;
7
+ background: var(--et-surface-background-solid);
8
+ color: var(--et-surface-color-solid);
9
+ font-size: 1.4rem;
10
+
11
+ .picture-error {
12
+ margin: 0;
13
+ color: var(--et-surface-color-muted-solid);
14
+ font-size: 1.2rem;
15
+ }
16
+
17
+ .picture-fit-grid {
18
+ display: grid;
19
+ grid-template-columns: repeat(2, 180px);
20
+ gap: 1.6rem;
21
+ }
22
+
23
+ .picture-fit-cell {
24
+ display: flex;
25
+ flex-direction: column;
26
+ gap: 0.4rem;
27
+ margin: 0;
28
+ font-size: 1.2rem;
29
+ color: var(--et-surface-color-muted-solid);
30
+ }
31
+
32
+ .picture-fit-cell .et-picture {
33
+ inline-size: 180px;
34
+ block-size: 120px;
35
+ outline: 1px dashed var(--et-surface-border-solid);
36
+ }
37
+ }
@@ -0,0 +1,97 @@
1
+ import { picture, pictureStyles, type PictureMockOptions } from '@ethlete/components/design/picture';
2
+ import { skeletonItem, skeletonStyles } from '@ethlete/components/design/skeleton';
3
+ import { definePart, drawing } from '@design-system';
4
+ import fallback from '@assets/fallback.svg?raw';
5
+ import tall from '@assets/tall.svg?raw';
6
+ import wide from '@assets/wide.svg?raw';
7
+ import frame from './picture.css?inline';
8
+
9
+ const svgUrl = (markup: string) => `data:image/svg+xml;utf8,${encodeURIComponent(markup.trim())}`;
10
+
11
+ type Fit = NonNullable<PictureMockOptions['fit']>;
12
+
13
+ type Props = {
14
+ ratio?: string;
15
+ caption?: 'short' | 'long' | 'none';
16
+ intrinsic?: boolean;
17
+ loading?: boolean;
18
+ error?: 'slot' | 'bare';
19
+ fit?: boolean;
20
+ };
21
+
22
+ const WIDE = svgUrl(wide);
23
+ const TALL = svgUrl(tall);
24
+ const FALLBACK = svgUrl(fallback);
25
+ const BROKEN = 'data:image/png;base64,this-is-not-an-image';
26
+
27
+ const CAPTIONS = {
28
+ short: 'A crop that follows the viewport.',
29
+ long: 'Fulltime at the home ground: the visitors celebrate in front of the away end after the late winner, while the stewards hold the line and the floodlights come on over the north stand.',
30
+ none: undefined,
31
+ };
32
+
33
+ const FITS: Fit[] = ['contain', 'cover', 'fill', 'none', 'scale-down'];
34
+
35
+ const body = ({ ratio = '16 / 9', caption = 'short', intrinsic, loading, error, fit }: Props) => {
36
+ if (fit) {
37
+ const cells = FITS.map(
38
+ (mode) =>
39
+ `<figure class="picture-fit-cell">${picture({ src: FALLBACK, alt: `fit ${mode}`, fit: mode })}<span>${mode}</span></figure>`,
40
+ ).join('');
41
+
42
+ return `<div class="picture-fit-grid">${cells}</div>`;
43
+ }
44
+
45
+ if (loading) {
46
+ return picture({
47
+ alt: 'An image whose URL arrives after the page',
48
+ aspectRatio: '16 / 9',
49
+ state: 'loading',
50
+ placeholder: skeletonItem({ shape: 'rect', style: 'block-size: 100%; inline-size: 100%' }),
51
+ });
52
+ }
53
+
54
+ if (error) {
55
+ return picture({
56
+ src: BROKEN,
57
+ alt: 'An image that fails to load',
58
+ aspectRatio: '16 / 9',
59
+ state: 'error',
60
+ error: error === 'slot' ? '<p class="picture-error">This image is unavailable.</p>' : undefined,
61
+ });
62
+ }
63
+
64
+ return picture({
65
+ sources: intrinsic ? undefined : [{ srcset: WIDE, media: '(min-width: 700px)' }, { srcset: TALL }],
66
+ src: FALLBACK,
67
+ alt: 'A coloured block labelled with its aspect ratio',
68
+ aspectRatio: ratio === 'none' ? undefined : ratio,
69
+ width: intrinsic ? 800 : undefined,
70
+ height: intrinsic ? 450 : undefined,
71
+ figcaption: CAPTIONS[caption],
72
+ });
73
+ };
74
+
75
+ export default definePart({
76
+ note: "Today's look",
77
+ madeBy: ['claude-opus-5-5'],
78
+ states: {
79
+ default: {},
80
+ 'ratio-4-3': { ratio: '4 / 3' },
81
+ 'no-ratio': { ratio: 'none' },
82
+ 'width-height': { intrinsic: true },
83
+ 'no-caption': { caption: 'none' },
84
+ 'long-caption': { caption: 'long' },
85
+ fit: { fit: true },
86
+ loading: { loading: true },
87
+ 'error-slot': { error: 'slot' },
88
+ 'error-bare': { error: 'bare' },
89
+ },
90
+ widths: [480],
91
+ frame: (body) => `<div class="picture-frame">${body}</div>`,
92
+ render: (props: Props) =>
93
+ drawing({
94
+ body: body(props),
95
+ styles: [skeletonStyles, pictureStyles, frame].join('\n'),
96
+ }),
97
+ });
@@ -0,0 +1,37 @@
1
+ .progress-steps-frame {
2
+ padding: 2.4rem 1.6rem;
3
+ background: var(--et-surface-background-solid);
4
+ color: var(--et-surface-color-solid);
5
+ font-size: 1.4rem;
6
+ }
7
+
8
+ @layer components {
9
+ .et-progress-step {
10
+ --_et-focus-ring-color: var(--et-theme-color-ink-solid);
11
+
12
+ &:is(
13
+ [data-state='current'],
14
+ [data-state='complete'],
15
+ [data-state='success'],
16
+ [data-state='warning'],
17
+ [data-state='error']
18
+ )
19
+ .et-progress-step-marker {
20
+ border-color: var(--et-theme-color-ink-solid);
21
+ }
22
+
23
+ &:is([data-state='complete'], [data-state='success'], [data-state='warning'], [data-state='error']):not(
24
+ :last-child
25
+ )::after {
26
+ background: var(--et-theme-color-ink-solid);
27
+ }
28
+
29
+ &:is(a, button) {
30
+ @media (hover: hover) {
31
+ &:hover .et-progress-step-marker {
32
+ border-color: var(--et-theme-color-ink-solid);
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,92 @@
1
+ import { progressSteps, ProgressStepMockStep, progressStepsStyles } from '@ethlete/components/design/progress-steps';
2
+ import { definePart, drawing } from '@design-system';
3
+ import frame from './progress-steps.css?inline';
4
+
5
+ type Interaction = 'hover' | 'active' | 'focus-visible';
6
+
7
+ type Props = {
8
+ set?: 'checkout' | 'outcomes' | 'described' | 'vertical' | 'links';
9
+ complete?: Interaction;
10
+ upcoming?: Interaction;
11
+ };
12
+
13
+ const mark = (control: string, interaction?: Interaction) =>
14
+ [`data-et-control="${control}"`, interaction && `data-et-force="${interaction}"`].filter(Boolean).join(' ');
15
+
16
+ const checkout: ProgressStepMockStep[] = [
17
+ { label: 'Account', state: 'complete' },
18
+ { label: 'Shipping', state: 'current' },
19
+ { label: 'Payment' },
20
+ { label: 'Review' },
21
+ ];
22
+
23
+ const outcomes: ProgressStepMockStep[] = [
24
+ { label: 'Upload', state: 'success' },
25
+ { label: 'Validate', state: 'warning', color: 'warning' },
26
+ { label: 'Import', state: 'error', color: 'danger' },
27
+ { label: 'Publish' },
28
+ ];
29
+
30
+ const described: ProgressStepMockStep[] = [
31
+ { label: 'Team', state: 'complete', description: '18 players' },
32
+ { label: 'Squad', state: 'error', color: 'danger', description: '2 missing' },
33
+ { label: 'Kit', state: 'current', description: 'Home colours' },
34
+ { label: 'Submit', description: 'By Friday' },
35
+ ];
36
+
37
+ const vertical: ProgressStepMockStep[] = [
38
+ { label: 'Registration received', state: 'complete', description: 'Mon, 12 Oct' },
39
+ { label: 'Documents checked', state: 'warning', color: 'warning', description: 'Photo is blurry' },
40
+ { label: 'Payment', state: 'current', description: 'Waiting for the transfer' },
41
+ { label: 'Licence issued' },
42
+ ];
43
+
44
+ const links = (props: Props): ProgressStepMockStep[] => [
45
+ { label: 'Account', state: 'complete', as: 'a', attributes: mark('complete', props.complete) },
46
+ { label: 'Shipping', state: 'current', as: 'a' },
47
+ { label: 'Payment', as: 'a', attributes: mark('upcoming', props.upcoming) },
48
+ { label: 'Review', as: 'a' },
49
+ ];
50
+
51
+ const steps = (props: Props) => {
52
+ switch (props.set) {
53
+ case 'outcomes':
54
+ return progressSteps({ steps: outcomes });
55
+ case 'described':
56
+ return progressSteps({ steps: described });
57
+ case 'vertical':
58
+ return progressSteps({ steps: vertical, orientation: 'vertical' });
59
+ case 'links':
60
+ return progressSteps({ steps: links(props) });
61
+ default:
62
+ return progressSteps({ steps: checkout });
63
+ }
64
+ };
65
+
66
+ export default definePart({
67
+ note: 'Decided rules only',
68
+ madeBy: ['claude-opus-5-5'],
69
+ states: {
70
+ default: {},
71
+ outcomes: { set: 'outcomes' },
72
+ described: { set: 'described' },
73
+ vertical: { set: 'vertical' },
74
+ links: { set: 'links' },
75
+ 'upcoming-hover': { set: 'links', upcoming: 'hover' },
76
+ 'upcoming-active': { set: 'links', upcoming: 'active' },
77
+ 'upcoming-focus-visible': { set: 'links', upcoming: 'focus-visible' },
78
+ 'complete-hover': { set: 'links', complete: 'hover' },
79
+ 'complete-focus-visible': { set: 'links', complete: 'focus-visible' },
80
+ },
81
+ widths: [480],
82
+ controls: {
83
+ complete: { kind: 'button', input: ['pointer', 'keyboard'] },
84
+ upcoming: { kind: 'button', input: ['pointer', 'keyboard'] },
85
+ },
86
+ frame: (body) => `<div class="progress-steps-frame">${body}</div>`,
87
+ render: (props: Props) =>
88
+ drawing({
89
+ body: steps(props),
90
+ styles: [progressStepsStyles, frame].join('\n'),
91
+ }),
92
+ });
@@ -0,0 +1,33 @@
1
+ .query-error-frame {
2
+ max-inline-size: 520px;
3
+ padding: 2.4rem 1.6rem;
4
+ background: var(--et-surface-background-solid);
5
+ color: var(--et-surface-color-solid);
6
+ font-size: 1.4rem;
7
+
8
+ .query-error-support {
9
+ color: inherit;
10
+ font-size: 1.2rem;
11
+ text-decoration: underline;
12
+ }
13
+ }
14
+
15
+ @layer components {
16
+ .et-banner {
17
+ border-color: var(--et-theme-color-ink-solid);
18
+ }
19
+
20
+ .et-button {
21
+ --_et-focus-ring-color: var(--et-theme-color-ink-solid);
22
+ }
23
+
24
+ .et-button:where([data-variant='filled']) {
25
+ --et-button-shadow: inset 0 0 0 1px var(--et-theme-color-ink-solid);
26
+
27
+ @media (hover: hover) {
28
+ &:hover {
29
+ --et-theme-color-primary-rgb: var(--et-color-primary-active);
30
+ }
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,121 @@
1
+ import { button } from '@ethlete/components/design/button';
2
+ import { queryError, queryErrorStyles } from '@ethlete/components/design/query-error';
3
+ import { definePart, drawing } from '@design-system';
4
+ import frame from './query-error.css?inline';
5
+
6
+ type Interaction = 'hover' | 'active' | 'focus-visible';
7
+
8
+ type Shape = 'message' | 'violations' | 'classValidator' | 'retryable' | 'empty' | 'echoesTitle' | 'network' | 'long';
9
+
10
+ type Props = {
11
+ shape: Shape;
12
+ alwaysAllowRetry?: boolean;
13
+ slots?: boolean;
14
+ color?: string;
15
+ retry?: Interaction;
16
+ };
17
+
18
+ const SHAPES: Record<Shape, { status: number; title: string; messages: string[]; canRetry: boolean }> = {
19
+ message: { status: 403, title: 'Forbidden', messages: ['You do not have access to this team.'], canRetry: false },
20
+ violations: {
21
+ status: 422,
22
+ title: 'Unprocessable entity',
23
+ messages: ['A name is required.', 'The email address is not valid.', 'The start date must be before the end date.'],
24
+ canRetry: false,
25
+ },
26
+ classValidator: {
27
+ status: 400,
28
+ title: 'Bad request',
29
+ messages: ['name should not be empty', 'email must be an email'],
30
+ canRetry: false,
31
+ },
32
+ retryable: {
33
+ status: 503,
34
+ title: 'Service unavailable',
35
+ messages: ['The service is briefly unavailable.'],
36
+ canRetry: true,
37
+ },
38
+ empty: {
39
+ status: 500,
40
+ title: 'Internal server error',
41
+ messages: ['Something went wrong on our end. Please try again later. (Code: 500)'],
42
+ canRetry: false,
43
+ },
44
+ echoesTitle: {
45
+ status: 404,
46
+ title: 'Not found',
47
+ messages: ['The requested resource was not found. (Code: 404)'],
48
+ canRetry: false,
49
+ },
50
+ network: {
51
+ status: 0,
52
+ title: 'Something went wrong',
53
+ messages: ['Something went wrong. Check your internet connection and try again later. (Code: 0)'],
54
+ canRetry: true,
55
+ },
56
+ long: {
57
+ status: 409,
58
+ title: 'Conflict',
59
+ messages: [
60
+ 'The fixture for matchday 14 was moved by another organiser while you were editing it, so your changes to the kick-off time, the venue and the referee assignment could not be saved. Reload the fixture and quote request league.example.com/fixtures/2026_matchday_14_home_visitors_edit_request_8f3a9c2e7b1d',
61
+ ],
62
+ canRetry: false,
63
+ },
64
+ };
65
+
66
+ const mark = (control: string, interaction?: Interaction) =>
67
+ [`data-et-control="${control}" data-can-animate`, interaction && `data-et-force="${interaction}"`]
68
+ .filter(Boolean)
69
+ .join(' ');
70
+
71
+ const slotActions = (interaction?: Interaction) =>
72
+ button({ label: 'Try again', size: 'sm' }).replace('<button ', `<button ${mark('retry', interaction)} `) +
73
+ '<a class="query-error-support" href="#">Contact support</a>';
74
+
75
+ const render = ({ shape, alwaysAllowRetry, slots, color = 'danger', retry }: Props) => {
76
+ const { status, title, messages, canRetry } = SHAPES[shape];
77
+
78
+ return queryError({
79
+ status,
80
+ title: slots && status === 403 ? 'This team is private' : title,
81
+ messages,
82
+ color,
83
+ retry: !slots && (canRetry || alwaysAllowRetry) ? 'Retry' : undefined,
84
+ retryAttributes: mark('retry', retry),
85
+ actions: slots ? slotActions(retry) : undefined,
86
+ });
87
+ };
88
+
89
+ export default definePart({
90
+ note: 'Decided rules only',
91
+ madeBy: ['claude-opus-5-5'],
92
+ states: {
93
+ message: { shape: 'message' },
94
+ violations: { shape: 'violations' },
95
+ 'class-validator': { shape: 'classValidator' },
96
+ retryable: { shape: 'retryable' },
97
+ 'empty-body': { shape: 'empty' },
98
+ 'echoes-title': { shape: 'echoesTitle' },
99
+ network: { shape: 'network' },
100
+ 'always-allow-retry': { shape: 'echoesTitle', alwaysAllowRetry: true },
101
+ 'long-message': { shape: 'long' },
102
+ 'custom-slots': { shape: 'message', slots: true },
103
+ 'retry-hover': { shape: 'retryable', retry: 'hover' },
104
+ 'retry-active': { shape: 'retryable', retry: 'active' },
105
+ 'retry-focus-visible': { shape: 'retryable', retry: 'focus-visible' },
106
+ 'slot-retry-focus-visible': { shape: 'message', slots: true, retry: 'focus-visible' },
107
+ 'brand-color': { shape: 'retryable', color: 'brand' },
108
+ 'brand-retry-hover': { shape: 'retryable', color: 'brand', retry: 'hover' },
109
+ 'brand-retry-focus-visible': { shape: 'retryable', color: 'brand', retry: 'focus-visible' },
110
+ },
111
+ widths: [480],
112
+ controls: {
113
+ retry: { kind: 'button', input: ['pointer', 'keyboard'] },
114
+ },
115
+ frame: (body) => `<div class="query-error-frame">${body}</div>`,
116
+ render: (props: Props) =>
117
+ drawing({
118
+ body: render(props),
119
+ styles: [queryErrorStyles, frame].join('\n'),
120
+ }),
121
+ });
@@ -0,0 +1,46 @@
1
+ .t-frame {
2
+ box-sizing: border-box;
3
+ display: grid;
4
+ justify-items: start;
5
+ gap: 1.6rem;
6
+ padding: 1.6rem;
7
+ background: var(--et-surface-background-solid);
8
+ color: var(--et-surface-color-solid);
9
+ font-size: 1.4rem;
10
+ }
11
+
12
+ @layer components {
13
+ et-radio:focus-visible .et-radio-circle {
14
+ border-color: light-dark(var(--et-theme-color-ink-solid), var(--et-theme-color-primary-solid));
15
+ outline-color: var(--et-theme-color-ink-solid);
16
+ }
17
+
18
+ et-radio[aria-checked='true'] .et-radio-circle,
19
+ et-radio-group[data-mixed] .et-radio-circle {
20
+ border-color: light-dark(var(--et-theme-color-ink-solid), var(--et-theme-color-primary-solid));
21
+ }
22
+
23
+ et-radio[aria-checked='true'] .et-radio-circle::after {
24
+ outline: 1px solid light-dark(var(--et-theme-color-ink-solid), transparent);
25
+ outline-offset: -1px;
26
+ }
27
+
28
+ et-radio.et-selection-card:where([aria-checked='true']) {
29
+ border-color: light-dark(var(--et-theme-color-ink-solid), var(--et-theme-color-primary-solid));
30
+ }
31
+
32
+ et-radio.et-selection-card:where(:focus-visible) {
33
+ border-color: light-dark(var(--et-theme-color-ink-solid), var(--et-theme-color-primary-solid));
34
+ outline-color: var(--et-theme-color-ink-solid);
35
+ }
36
+
37
+ @media (hover: hover) {
38
+ et-radio[aria-checked='true']:hover {
39
+ --et-theme-color-primary-rgb: var(--et-color-primary-active);
40
+ }
41
+ }
42
+
43
+ .et-label-required-marker {
44
+ color: var(--et-theme-color-ink-solid);
45
+ }
46
+ }