@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.
- package/CHANGELOG.md +139 -0
- package/design/accordion.ts +73 -0
- package/design/anchored-dialog.ts +92 -0
- package/design/avatar.ts +90 -0
- package/design/badge.ts +48 -0
- package/design/banner.ts +77 -0
- package/design/bracket.ts +457 -0
- package/design/breadcrumb.ts +145 -0
- package/design/button.ts +62 -1
- package/design/calendar.ts +306 -0
- package/design/card.ts +92 -0
- package/design/carousel.ts +240 -0
- package/design/chart.ts +512 -0
- package/design/checkbox.ts +275 -0
- package/design/chip.ts +64 -0
- package/design/config.json +8 -0
- package/design/control.ts +19 -0
- package/design/copy-button.ts +59 -0
- package/design/description-list.ts +41 -0
- package/design/dialog.ts +216 -0
- package/design/divider.ts +27 -0
- package/design/empty-state.ts +45 -0
- package/design/floating-action.ts +55 -0
- package/design/form-field.ts +3 -0
- package/design/grid.ts +148 -0
- package/design/input.ts +86 -0
- package/design/kbd.ts +19 -0
- package/design/loader.ts +152 -0
- package/design/masonry.ts +115 -0
- package/design/match.ts +131 -0
- package/design/menu.ts +147 -0
- package/design/notification.ts +133 -0
- package/design/otp-input.ts +107 -0
- package/design/pagination.ts +120 -0
- package/design/picture.ts +80 -0
- package/design/progress-steps.ts +79 -0
- package/design/query-error.ts +60 -0
- package/design/radio-group.ts +132 -0
- package/design/scheduler.ts +369 -0
- package/design/scrollable.ts +218 -0
- package/design/scrollbar.ts +47 -0
- package/design/sdk/assets/fallback.svg +1 -0
- package/design/sdk/assets/tall.svg +1 -0
- package/design/sdk/assets/wide.svg +1 -0
- package/design/sdk/components/accent-on-light/accent-on-light.css +35 -0
- package/design/sdk/components/accent-on-light/accent-on-light.ts +10 -0
- package/design/sdk/components/accent-on-light/shared/controls.ts +46 -0
- package/design/sdk/components/accordion/accordion.css +17 -0
- package/design/sdk/components/accordion/accordion.ts +68 -0
- package/design/sdk/components/anchored-dialog/anchored-dialog.css +63 -0
- package/design/sdk/components/anchored-dialog/anchored-dialog.ts +65 -0
- package/design/sdk/components/avatar/avatar.css +14 -0
- package/design/sdk/components/avatar/avatar.ts +79 -0
- package/design/sdk/components/badge/badge.css +13 -0
- package/design/sdk/components/badge/badge.ts +53 -0
- package/design/sdk/components/banner/banner.css +18 -0
- package/design/sdk/components/banner/banner.ts +88 -0
- package/design/sdk/components/bracket/bracket.css +59 -0
- package/design/sdk/components/bracket/bracket.ts +144 -0
- package/design/sdk/components/breadcrumb/breadcrumb.css +25 -0
- package/design/sdk/components/breadcrumb/breadcrumb.ts +82 -0
- package/design/sdk/components/button/button.css +41 -0
- package/design/sdk/components/button/button.ts +59 -7
- package/design/sdk/components/calendar/calendar.css +33 -0
- package/design/sdk/components/calendar/calendar.ts +97 -0
- package/design/sdk/components/card/card.css +19 -0
- package/design/sdk/components/card/card.ts +62 -0
- package/design/sdk/components/carousel/carousel.css +44 -0
- package/design/sdk/components/carousel/carousel.ts +75 -0
- package/design/sdk/components/chart/chart.css +56 -0
- package/design/sdk/components/chart/chart.ts +99 -0
- package/design/sdk/components/checkbox/checkbox.css +65 -0
- package/design/sdk/components/checkbox/checkbox.ts +180 -0
- package/design/sdk/components/chip/chip.css +18 -0
- package/design/sdk/components/chip/chip.ts +77 -0
- package/design/sdk/components/copy-button/copy-button.css +14 -0
- package/design/sdk/components/copy-button/copy-button.ts +60 -0
- package/design/sdk/components/description-list/description-list.css +7 -0
- package/design/sdk/components/description-list/description-list.ts +54 -0
- package/design/sdk/components/dialog/dialog.css +29 -0
- package/design/sdk/components/dialog/dialog.ts +79 -0
- package/design/sdk/components/divider/divider.css +26 -0
- package/design/sdk/components/divider/divider.ts +44 -0
- package/design/sdk/components/empty-state/empty-state.css +15 -0
- package/design/sdk/components/empty-state/empty-state.ts +57 -0
- package/design/sdk/components/floating-action/floating-action.css +51 -0
- package/design/sdk/components/floating-action/floating-action.ts +63 -0
- package/design/sdk/components/form-field/form-field.ts +23 -5
- package/design/sdk/components/grid/grid.css +17 -0
- package/design/sdk/components/grid/grid.ts +81 -0
- package/design/sdk/components/input/input.css +75 -0
- package/design/sdk/components/input/input.ts +80 -0
- package/design/sdk/components/kbd/kbd.css +25 -0
- package/design/sdk/components/kbd/kbd.ts +58 -0
- package/design/sdk/components/loader/loader.css +37 -0
- package/design/sdk/components/loader/loader.ts +69 -0
- package/design/sdk/components/masonry/masonry.css +7 -0
- package/design/sdk/components/masonry/masonry.ts +43 -0
- package/design/sdk/components/match/match.css +25 -0
- package/design/sdk/components/match/match.ts +77 -0
- package/design/sdk/components/menu/menu.css +22 -0
- package/design/sdk/components/menu/menu.ts +103 -0
- package/design/sdk/components/notification/notification.css +32 -0
- package/design/sdk/components/notification/notification.ts +107 -0
- package/design/sdk/components/otp-input/otp-input.css +35 -0
- package/design/sdk/components/otp-input/otp-input.ts +65 -0
- package/design/sdk/components/pagination/pagination.css +14 -0
- package/design/sdk/components/pagination/pagination.ts +76 -0
- package/design/sdk/components/picture/picture.css +37 -0
- package/design/sdk/components/picture/picture.ts +97 -0
- package/design/sdk/components/progress-steps/progress-steps.css +37 -0
- package/design/sdk/components/progress-steps/progress-steps.ts +92 -0
- package/design/sdk/components/query-error/query-error.css +33 -0
- package/design/sdk/components/query-error/query-error.ts +121 -0
- package/design/sdk/components/radio-group/radio-group.css +46 -0
- package/design/sdk/components/radio-group/radio-group.ts +167 -0
- package/design/sdk/components/scheduler/scheduler.css +55 -0
- package/design/sdk/components/scheduler/scheduler.ts +240 -0
- package/design/sdk/components/scrollable/scrollable.css +58 -0
- package/design/sdk/components/scrollable/scrollable.ts +133 -0
- package/design/sdk/components/scrollbar/scrollbar.css +78 -0
- package/design/sdk/components/scrollbar/scrollbar.ts +124 -0
- package/design/sdk/components/select/select.ts +1 -1
- package/design/sdk/components/select/shared/select-part.ts +25 -3
- package/design/sdk/components/select-panel/select-panel.css +3 -0
- package/design/sdk/components/select-panel/select-panel.ts +125 -0
- package/design/sdk/components/skeleton/skeleton.css +22 -0
- package/design/sdk/components/skeleton/skeleton.ts +51 -0
- package/design/sdk/components/slider/slider.css +52 -0
- package/design/sdk/components/slider/slider.ts +87 -0
- package/design/sdk/components/standings/standings.css +33 -0
- package/design/sdk/components/standings/standings.ts +131 -0
- package/design/sdk/components/stat-tile/stat-tile.css +15 -0
- package/design/sdk/components/stat-tile/stat-tile.ts +85 -0
- package/design/sdk/components/stream/stream.css +48 -0
- package/design/sdk/components/stream/stream.ts +43 -0
- package/design/sdk/components/switch/switch.css +37 -0
- package/design/sdk/components/switch/switch.ts +63 -0
- package/design/sdk/components/table/table.css +67 -0
- package/design/sdk/components/table/table.ts +161 -0
- package/design/sdk/components/tabs/tabs.css +13 -0
- package/design/sdk/components/tabs/tabs.ts +74 -0
- package/design/sdk/components/textarea/textarea.css +116 -0
- package/design/sdk/components/textarea/textarea.ts +65 -0
- package/design/sdk/components/timeline/timeline.css +30 -0
- package/design/sdk/components/timeline/timeline.ts +87 -0
- package/design/sdk/components/toolbar/toolbar.css +24 -0
- package/design/sdk/components/toolbar/toolbar.ts +88 -0
- package/design/sdk/components/tooltip/tooltip.css +15 -0
- package/design/sdk/components/tooltip/tooltip.ts +28 -0
- package/design/sdk/components/tree/selected-tint.css +15 -0
- package/design/sdk/components/tree/tree.ts +70 -0
- package/design/sdk/foundations/colors/colors.css +124 -4
- package/design/sdk/foundations/colors/colors.ts +1 -1
- package/design/sdk/foundations/colors/shared/contrast.ts +1 -1
- package/design/sdk/foundations/elevation/elevation.css +6 -0
- package/design/sdk/foundations/elevation/elevation.ts +11 -0
- package/design/sdk/foundations/elevation/shared/elevation/scene.css +101 -0
- package/design/sdk/foundations/elevation/shared/elevation/scene.ts +74 -0
- package/design/sdk/foundations/icons/icons.ts +119 -0
- package/design/sdk/pictures.json +1183 -0
- package/design/select.ts +5 -4
- package/design/skeleton.ts +67 -0
- package/design/slider.ts +155 -0
- package/design/standings.ts +161 -0
- package/design/stat-tile.ts +109 -0
- package/design/stream.ts +92 -0
- package/design/switch.ts +83 -0
- package/design/table.ts +302 -0
- package/design/tabs.ts +75 -0
- package/design/textarea.ts +94 -0
- package/design/timeline.ts +52 -0
- package/design/toolbar.ts +95 -0
- package/design/tooltip.ts +87 -0
- package/fesm2022/ethlete-components.mjs +1440 -1209
- package/fesm2022/ethlete-components.mjs.map +1 -1
- package/generators/generators.json +2 -0
- package/package.json +1 -1
- package/src/lib/accordion/accordion.component.css +2 -1
- package/src/lib/avatar/avatar.component.css +10 -0
- package/src/lib/badge/badge.component.css +2 -2
- package/src/lib/banner/banner.component.css +3 -1
- package/src/lib/breadcrumb/breadcrumb-outlet.component.css +5 -0
- package/src/lib/breadcrumb/breadcrumb.component.css +5 -1
- package/src/lib/button/button-properties-styles.component.css +6 -6
- package/src/lib/button/button.component.css +27 -4
- package/src/lib/button/button.types.ts +33 -0
- package/src/lib/button/icon-button.component.css +21 -22
- package/src/lib/button/split-button.component.css +5 -1
- package/src/lib/button/window-control-button.component.css +43 -3
- package/src/lib/calendar/calendar.component.css +15 -5
- package/src/lib/card/card.component.css +4 -0
- package/src/lib/carousel/carousel-autoplay-styles.component.css +1 -1
- package/src/lib/carousel/carousel.component.css +2 -2
- package/src/lib/chart/chart-axis.component.css +3 -3
- package/src/lib/chip/chip.component.css +12 -6
- package/src/lib/description-list/description-list.component.css +2 -1
- package/src/lib/empty-state/empty-state.component.css +4 -0
- package/src/lib/floating-action/floating-action-styles.component.css +27 -0
- package/src/lib/focus-ring/focus-ring-styles.component.css +4 -1
- package/src/lib/forms/checkbox/checkbox.component.css +31 -5
- package/src/lib/forms/color-input/color-input.component.css +3 -1
- package/src/lib/forms/color-input/color-picker-panel.component.css +6 -2
- package/src/lib/forms/date-time/date-input/date-input.component.css +1 -1
- package/src/lib/forms/date-time/date-time-input/date-time-input.component.css +1 -1
- package/src/lib/forms/date-time/duration-input/duration-input.component.css +1 -1
- package/src/lib/forms/date-time/range-input-shell-styles.component.css +1 -1
- package/src/lib/forms/date-time/time-input/time-input.component.css +1 -1
- package/src/lib/forms/description/description.component.css +7 -0
- package/src/lib/forms/dropzone/dropzone.component.css +26 -0
- package/src/lib/forms/form-field/form-field-control-suffix-styles.component.css +4 -2
- package/src/lib/forms/form-field/form-field-text-shell-styles.component.css +54 -5
- package/src/lib/forms/form-field/form-field-textarea-styles.component.css +62 -0
- package/src/lib/forms/form-field/form-field.component.css +6 -0
- package/src/lib/forms/form-field/headless/label.directive.css +1 -1
- package/src/lib/forms/input/input.component.css +6 -2
- package/src/lib/forms/input/number-input.component.css +2 -2
- package/src/lib/forms/input/password-input.component.css +4 -3
- package/src/lib/forms/otp-input/otp-input.component.css +23 -4
- package/src/lib/forms/phone-input/phone-input.component.css +4 -3
- package/src/lib/forms/rating/rating.component.css +2 -1
- package/src/lib/forms/rich-text-editor/rich-text-editor.component.css +1 -1
- package/src/lib/forms/selection-card-styles.component.css +3 -2
- package/src/lib/forms/selection-list/checkbox-group/checkbox-group-select-all.component.css +4 -2
- package/src/lib/forms/selection-list/checkbox-group/checkbox-option.component.css +4 -2
- package/src/lib/forms/selection-list/radio-group/radio.component.css +5 -2
- package/src/lib/forms/selection-list/segmented-button-group/segmented-button.component.css +2 -1
- package/src/lib/forms/slider/range-slider.component.css +98 -6
- package/src/lib/forms/slider/slider.component.css +96 -4
- package/src/lib/forms/switch/switch.component.css +45 -15
- package/src/lib/forms/tag-input/tag-input.component.css +2 -2
- package/src/lib/forms/textarea/textarea-autosize-styles.component.css +2 -3
- package/src/lib/forms/textarea/textarea.component.css +7 -2
- package/src/lib/grid/grid-item-toolbar.component.css +10 -0
- package/src/lib/grid/grid-item.component.css +6 -1
- package/src/lib/kbd/kbd.component.css +1 -1
- package/src/lib/loader/brand-loader/brand-loader.component.css +4 -1
- package/src/lib/loader/progress-bar/progress-bar.component.css +1 -1
- package/src/lib/loader/spinner/spinner-styles.component.css +1 -1
- package/src/lib/match/match-card.component.css +9 -3
- package/src/lib/match/match-score.component.css +4 -1
- package/src/lib/menu/menu-selection-item.component.css +1 -1
- package/src/lib/menu/menu.component.css +6 -1
- package/src/lib/notification/notification.component.css +3 -3
- package/src/lib/overlay/overlay-container.component.css +4 -0
- package/src/lib/pagination/pagination.component.css +4 -0
- package/src/lib/progress-steps/progress-step.component.css +4 -4
- package/src/lib/query-error/query-error.component.css +4 -0
- package/src/lib/scheduler/scheduler-appointment-styles.component.css +21 -5
- package/src/lib/scheduler/scheduler-month-view.component.css +4 -2
- package/src/lib/scheduler/scheduler-time-grid-view.component.css +8 -2
- package/src/lib/scrollable/headless/scrollable-navigation.component.css +1 -1
- package/src/lib/standings/standings-pick.component.css +1 -1
- package/src/lib/standings/standings.component.css +10 -10
- package/src/lib/stat-tile/stat-tile-sparkline.component.css +1 -1
- package/src/lib/stream/consent/stream-consent.component.css +99 -0
- package/src/lib/stream/error/stream-player-error.component.css +100 -0
- package/src/lib/stream/loading/stream-player-loading.component.css +16 -0
- package/src/lib/stream/pip/pip-slot-placeholder.component.css +68 -0
- package/src/lib/stream/stream-player-slot-styles.component.css +4 -0
- package/src/lib/table/table-resize-grip.component.css +2 -1
- package/src/lib/table/table-select-cell.component.css +45 -15
- package/src/lib/table/table.component.css +15 -5
- package/src/lib/tabs/tab-bar-styles.component.css +1 -1
- package/src/lib/tabs/tabs/tab-group.component.css +111 -0
- package/src/lib/time-picker/time-picker.component.css +2 -1
- package/src/lib/timeline/timeline-item.component.css +5 -1
- package/src/lib/toolbar/toolbar.component.css +4 -0
- package/src/lib/tooltip/tooltip.component.css +14 -0
- package/types/ethlete-components.d.ts +156 -128
package/design/select.ts
CHANGED
|
@@ -38,7 +38,7 @@ export type SelectPanelMockOptions = {
|
|
|
38
38
|
multiple?: boolean;
|
|
39
39
|
/** The app's default color theme name: the panel provides it to the options. */
|
|
40
40
|
color?: string;
|
|
41
|
-
/** A surface theme name the app registers, for the panel. */
|
|
41
|
+
/** A surface theme name the app registers, for the panel. Left out, the panel takes the surface one elevation above its parent. */
|
|
42
42
|
surface?: string;
|
|
43
43
|
};
|
|
44
44
|
|
|
@@ -54,7 +54,7 @@ export type SelectMockOptions = {
|
|
|
54
54
|
clearLabel?: string;
|
|
55
55
|
/** The app's default color theme name: the panel provides it to the options. */
|
|
56
56
|
panelColor?: string;
|
|
57
|
-
/** A surface theme name the app registers, for the panel. */
|
|
57
|
+
/** A surface theme name the app registers, for the panel. Left out, the panel takes the surface one elevation above its parent. */
|
|
58
58
|
panelSurface?: string;
|
|
59
59
|
};
|
|
60
60
|
|
|
@@ -122,10 +122,11 @@ export const selectPanel = (options: SelectPanelMockOptions) => {
|
|
|
122
122
|
'et-select-panel',
|
|
123
123
|
'et-color--inherited',
|
|
124
124
|
options.color && `et-color--${options.color}`,
|
|
125
|
-
`et-surface--${options.surface
|
|
125
|
+
options.surface && `et-surface--${options.surface}`,
|
|
126
126
|
].filter(Boolean);
|
|
127
|
+
const autoSurface = options.surface ? '' : ' data-et-auto-surface';
|
|
127
128
|
|
|
128
|
-
return `<et-select-panel class="${classes.join(' ')}"><div class="et-select-panel-scroller et-scrollbar-host"><div class="et-select-panel-body"><div ${listbox.join(' ')}>${options.content}</div><div class="et-select-panel-extras"></div></div></div><et-scrollbar class="et-scrollbar" data-orientation="vertical"><div class="et-scrollbar-thumb"></div></et-scrollbar></et-select-panel>`;
|
|
129
|
+
return `<et-select-panel${autoSurface} class="${classes.join(' ')}"><div class="et-select-panel-scroller et-scrollbar-host"><div class="et-select-panel-body"><div ${listbox.join(' ')}>${options.content}</div><div class="et-select-panel-extras"></div></div></div><et-scrollbar class="et-scrollbar" data-orientation="vertical"><div class="et-scrollbar-thumb"></div></et-scrollbar></et-select-panel>`;
|
|
129
130
|
};
|
|
130
131
|
|
|
131
132
|
/**
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import skeletonBase from '../src/lib/skeleton/skeleton.component.css?inline';
|
|
2
|
+
|
|
3
|
+
export type SkeletonShapeMock = 'text' | 'rect' | 'circle';
|
|
4
|
+
|
|
5
|
+
export type SkeletonItemMockOptions = {
|
|
6
|
+
/** Left out, `text`, as `et-skeleton-item` defaults to. */
|
|
7
|
+
shape?: SkeletonShapeMock;
|
|
8
|
+
/** Inline style on the item, e.g. `--et-skeleton-size: 40px` or `block-size: 140px`. */
|
|
9
|
+
style?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type SkeletonTextMockOptions = {
|
|
13
|
+
/** Left out, 3. */
|
|
14
|
+
lines?: number;
|
|
15
|
+
/** Width (%) of the last line. Left out, 60. */
|
|
16
|
+
lastLineWidth?: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type SkeletonMockOptions = {
|
|
20
|
+
/** The projected content, built from `skeletonItem()`, `skeletonText()` and any layout markup. */
|
|
21
|
+
content: string;
|
|
22
|
+
/** Left out, `true`, as `et-skeleton` defaults to. */
|
|
23
|
+
animated?: boolean;
|
|
24
|
+
/** Where the shimmer is drawn, as a fraction of its sweep from 0 to 1. Left out, 0.5: the highlight centred on each bone. */
|
|
25
|
+
shimmerAt?: number;
|
|
26
|
+
/** Extra attributes on the skeleton, e.g. design markers. */
|
|
27
|
+
attributes?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** The stylesheet `skeleton()` draws with: the real skeleton CSS, plus the rule that holds the shimmer still. */
|
|
31
|
+
export const skeletonStyles = /* @__PURE__ */ [
|
|
32
|
+
skeletonBase,
|
|
33
|
+
`.et-skeleton[data-et-shimmer-at] .et-skeleton-item::after {
|
|
34
|
+
animation-play-state: paused;
|
|
35
|
+
animation-delay: calc(var(--et-skeleton-shimmer-at) * -1 * var(--et-skeleton-duration));
|
|
36
|
+
}`,
|
|
37
|
+
].join('\n');
|
|
38
|
+
|
|
39
|
+
/** Draws one `et-skeleton-item` as the component renders it, as HTML. */
|
|
40
|
+
export const skeletonItem = (options: SkeletonItemMockOptions = {}) => {
|
|
41
|
+
const style = options.style ? ` style="${options.style}"` : '';
|
|
42
|
+
|
|
43
|
+
return `<et-skeleton-item class="et-skeleton-item" aria-hidden="true" data-shape="${options.shape ?? 'text'}"${style}></et-skeleton-item>`;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Draws an `et-skeleton-text` with its lines as the component renders it, as HTML. */
|
|
47
|
+
export const skeletonText = (options: SkeletonTextMockOptions = {}) => {
|
|
48
|
+
const lines = Math.max(1, Math.floor(options.lines ?? 3));
|
|
49
|
+
const items = Array.from({ length: lines }, (_, index) =>
|
|
50
|
+
skeletonItem({ style: `inline-size: ${index === lines - 1 ? (options.lastLineWidth ?? 60) : 100}%` }),
|
|
51
|
+
).join('');
|
|
52
|
+
|
|
53
|
+
return `<et-skeleton-text class="et-skeleton-text">${items}</et-skeleton-text>`;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Draws an `et-skeleton` around `content` as the component renders it, as HTML, without its visually hidden label and with the shimmer held at one frame. */
|
|
57
|
+
export const skeleton = (options: SkeletonMockOptions) => {
|
|
58
|
+
const animated = options.animated ?? true;
|
|
59
|
+
const attributes = [
|
|
60
|
+
options.attributes,
|
|
61
|
+
`class="et-skeleton${animated ? ' et-skeleton--animated' : ''}"`,
|
|
62
|
+
'role="status"',
|
|
63
|
+
animated && `data-et-shimmer-at style="--et-skeleton-shimmer-at: ${options.shimmerAt ?? 0.5}"`,
|
|
64
|
+
].filter(Boolean);
|
|
65
|
+
|
|
66
|
+
return `<et-skeleton ${attributes.join(' ')}>${options.content}</et-skeleton>`;
|
|
67
|
+
};
|
package/design/slider.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import formSupport from '../src/lib/forms/form-field/form-support-styles.component.css?inline';
|
|
2
|
+
import label from '../src/lib/forms/form-field/headless/label.directive.css?inline';
|
|
3
|
+
import hint from '../src/lib/forms/form-field/hint.component.css?inline';
|
|
4
|
+
import rangeSliderBase from '../src/lib/forms/slider/range-slider.component.css?inline';
|
|
5
|
+
import sliderBase from '../src/lib/forms/slider/slider.component.css?inline';
|
|
6
|
+
|
|
7
|
+
/** An interaction a frame forces on a thumb with the `data-et-force` design marker. */
|
|
8
|
+
export type SliderMockForce = 'hover' | 'focus-visible' | 'active';
|
|
9
|
+
|
|
10
|
+
export type SliderMockMark = { value: number; label?: string };
|
|
11
|
+
|
|
12
|
+
export type SliderMockOptions = {
|
|
13
|
+
/** One number draws `et-slider`, a pair draws `et-range-slider` with two thumbs. */
|
|
14
|
+
value: number | [number, number];
|
|
15
|
+
min?: number;
|
|
16
|
+
max?: number;
|
|
17
|
+
/** `true` draws a tick at every `step`; a list draws the given ticks, with labels where set. */
|
|
18
|
+
marks?: boolean | SliderMockMark[];
|
|
19
|
+
step?: number;
|
|
20
|
+
/** The label, as HTML. */
|
|
21
|
+
label?: string;
|
|
22
|
+
/** The hint, as HTML. Hidden while an `error` shows. */
|
|
23
|
+
hint?: string;
|
|
24
|
+
/** The error message, as HTML. Draws the slider touched and invalid. */
|
|
25
|
+
error?: string;
|
|
26
|
+
/** The color theme name the app registers for errors, e.g. `danger` in Storybook. */
|
|
27
|
+
errorColor?: string;
|
|
28
|
+
/** A color theme name the app registers. */
|
|
29
|
+
color?: string;
|
|
30
|
+
required?: boolean;
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
/** View-only: the value shows, the thumb takes focus but does not move. */
|
|
33
|
+
readonly?: boolean;
|
|
34
|
+
/** An unresolved bulk-edit value: the thumbs park at the track start. */
|
|
35
|
+
mixed?: boolean;
|
|
36
|
+
mixedLabel?: string;
|
|
37
|
+
orientation?: 'horizontal' | 'vertical';
|
|
38
|
+
/** Draws the value bubble an `etSliderThumbLabel` template renders above each thumb. */
|
|
39
|
+
valueLabel?: boolean;
|
|
40
|
+
/** The thumb index that is being dragged. */
|
|
41
|
+
dragging?: number;
|
|
42
|
+
/** Forced interactions, per thumb index (a single slider uses index 0). */
|
|
43
|
+
force?: Partial<Record<number, SliderMockForce | SliderMockForce[]>>;
|
|
44
|
+
/** Unique per frame: the label and message ids derive from it. */
|
|
45
|
+
id?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** The stylesheets `slider()` draws with: the real `et-slider`, `et-range-slider` and form support CSS. */
|
|
49
|
+
export const sliderStyles = /* @__PURE__ */ [label, hint, formSupport, sliderBase, rangeSliderBase].join('\n');
|
|
50
|
+
|
|
51
|
+
const styled = (css: string) => ` style="${css}"`;
|
|
52
|
+
|
|
53
|
+
const forceList = (force: SliderMockForce | SliderMockForce[] | undefined) =>
|
|
54
|
+
force === undefined ? [] : Array.isArray(force) ? force : [force];
|
|
55
|
+
|
|
56
|
+
/** Draws `<et-slider>`, or `<et-range-slider>` for a value pair, as HTML, with the DOM the components render. */
|
|
57
|
+
export const slider = (options: SliderMockOptions) => {
|
|
58
|
+
const { id = 'slider', min = 0, max = 100, step = 1, orientation = 'horizontal' } = options;
|
|
59
|
+
const range = Array.isArray(options.value);
|
|
60
|
+
const prefix = range ? 'et-range-slider' : 'et-slider';
|
|
61
|
+
const [start, end] = Array.isArray(options.value) ? options.value : [min, options.value];
|
|
62
|
+
const values = range ? [start, end] : [end];
|
|
63
|
+
const shown = options.mixed ? values.map(() => min) : values;
|
|
64
|
+
const [shownStart, shownEnd] = options.mixed ? [min, min] : [start, end];
|
|
65
|
+
const percent = (value: number) => ((value - min) / (max - min)) * 100;
|
|
66
|
+
const ids = { hint: `et-form-field-hint-${id}`, error: `et-form-field-error-${id}` };
|
|
67
|
+
const labelId = options.label !== undefined ? `et-label-${id}` : null;
|
|
68
|
+
const invalid = options.error !== undefined;
|
|
69
|
+
const supportId = invalid ? ids.error : options.hint !== undefined ? ids.hint : null;
|
|
70
|
+
const colorClass = invalid && options.errorColor ? options.errorColor : (options.color ?? 'inherited');
|
|
71
|
+
const activeRange = options.mixed ? null : { start, end };
|
|
72
|
+
|
|
73
|
+
const markList: SliderMockMark[] =
|
|
74
|
+
options.marks === true
|
|
75
|
+
? Array.from({ length: Math.floor((max - min) / step) + 1 }, (_, index) => ({ value: min + index * step }))
|
|
76
|
+
: options.marks || [];
|
|
77
|
+
const hasMarkLabels = markList.some((mark) => !!mark.label);
|
|
78
|
+
|
|
79
|
+
const hostAttributes = [
|
|
80
|
+
`class="${prefix} et-color--${colorClass}"`,
|
|
81
|
+
options.mixed && 'data-mixed="true"',
|
|
82
|
+
`data-orientation="${orientation}"`,
|
|
83
|
+
invalid && 'data-error="true"',
|
|
84
|
+
options.disabled && 'data-disabled="true"',
|
|
85
|
+
options.readonly && 'data-readonly="true"',
|
|
86
|
+
options.dragging !== undefined && 'data-dragging="true"',
|
|
87
|
+
hasMarkLabels && 'data-mark-labels="true"',
|
|
88
|
+
options.valueLabel && 'data-thumb-value="true"',
|
|
89
|
+
].filter(Boolean);
|
|
90
|
+
|
|
91
|
+
const labelHtml = labelId
|
|
92
|
+
? `<et-label id="${labelId}"${options.disabled ? ' data-disabled="true"' : ''}><span class="et-label-content">${options.label}</span>${options.required ? '<span aria-hidden="true" class="et-label-required-marker">*</span>' : ''}</et-label>`
|
|
93
|
+
: '';
|
|
94
|
+
|
|
95
|
+
const fillStyle = range
|
|
96
|
+
? `--_et-slider-fill-start: ${percent(shownStart)}; --_et-slider-fill-end: ${percent(shownEnd)};`
|
|
97
|
+
: `--_et-slider-fill-end: ${percent(shownEnd)};`;
|
|
98
|
+
|
|
99
|
+
const marksHtml = markList.length
|
|
100
|
+
? `<div aria-hidden="true" class="${prefix}-marks">${markList
|
|
101
|
+
.map((mark) => {
|
|
102
|
+
const active = activeRange !== null && mark.value >= activeRange.start && mark.value <= activeRange.end;
|
|
103
|
+
const markLabel = mark.label ? `<span class="${prefix}-mark-label">${mark.label}</span>` : '';
|
|
104
|
+
|
|
105
|
+
return `<span class="${prefix}-mark"${active ? ' data-active="true"' : ''} data-et-slider-mark-value="${mark.value}"${styled(`--_et-slider-mark-position: ${percent(mark.value)};`)}>${markLabel}</span>`;
|
|
106
|
+
})
|
|
107
|
+
.join('')}</div>`
|
|
108
|
+
: '';
|
|
109
|
+
|
|
110
|
+
const thumbs = shown
|
|
111
|
+
.map((value, index) => {
|
|
112
|
+
const forced = forceList(options.force?.[index]);
|
|
113
|
+
const markLabel = markList.find((mark) => mark.value === values[index])?.label;
|
|
114
|
+
const valueText = options.mixed ? (options.mixedLabel ?? 'Mixed') : markLabel;
|
|
115
|
+
const ariaMin = range && index === 1 ? start : min;
|
|
116
|
+
const ariaMax = range && index === 0 ? end : max;
|
|
117
|
+
const attributes = [
|
|
118
|
+
'role="slider"',
|
|
119
|
+
`class="${prefix}-thumb"`,
|
|
120
|
+
`aria-orientation="${orientation}"`,
|
|
121
|
+
`tabindex="${options.disabled ? -1 : 0}"`,
|
|
122
|
+
`aria-valuemin="${ariaMin}"`,
|
|
123
|
+
`aria-valuemax="${ariaMax}"`,
|
|
124
|
+
!options.mixed && `aria-valuenow="${value}"`,
|
|
125
|
+
valueText && `aria-valuetext="${valueText}"`,
|
|
126
|
+
range ? `aria-label="${index === 0 ? 'Minimum' : 'Maximum'}"` : labelId && `aria-labelledby="${labelId}"`,
|
|
127
|
+
supportId && `aria-describedby="${supportId}"`,
|
|
128
|
+
options.disabled && 'aria-disabled="true"',
|
|
129
|
+
options.readonly && 'aria-readonly="true"',
|
|
130
|
+
invalid && 'aria-invalid="true"',
|
|
131
|
+
options.disabled && 'data-disabled="true"',
|
|
132
|
+
options.readonly && 'data-readonly="true"',
|
|
133
|
+
options.dragging === index && 'data-dragging="true"',
|
|
134
|
+
forced.length > 0 && `data-et-force="${forced.join(' ')}"`,
|
|
135
|
+
styled(`--_et-slider-thumb-position: ${percent(value)};`).trim(),
|
|
136
|
+
].filter(Boolean);
|
|
137
|
+
const bubble = options.valueLabel
|
|
138
|
+
? `<div aria-hidden="true" class="${prefix}-thumb-value">${values[index]}</div>`
|
|
139
|
+
: '';
|
|
140
|
+
|
|
141
|
+
return `<div ${attributes.join(' ')}>${bubble}</div>`;
|
|
142
|
+
})
|
|
143
|
+
.join('');
|
|
144
|
+
|
|
145
|
+
const support =
|
|
146
|
+
options.hint === undefined && options.error === undefined
|
|
147
|
+
? ''
|
|
148
|
+
: `<et-form-support class="et-form-support"><div class="et-form-support-stack">${
|
|
149
|
+
invalid
|
|
150
|
+
? `<div class="et-form-support-content et-form-support-errors${options.errorColor ? ` et-color--${options.errorColor}` : ''}" id="${ids.error}" aria-atomic="true" aria-live="polite" data-active="true" data-direction="from-below" data-state="active"><et-form-error class="et-form-error">${options.error}</et-form-error></div>`
|
|
151
|
+
: `<div class="et-form-support-content et-form-support-hint" id="${ids.hint}" data-active="true" data-direction="from-above" data-state="active"><et-hint>${options.hint}</et-hint></div>`
|
|
152
|
+
}</div></et-form-support>`;
|
|
153
|
+
|
|
154
|
+
return `<${prefix} ${hostAttributes.join(' ')}>${labelHtml}<div class="${prefix}-interaction"><div aria-hidden="true" class="${prefix}-rail"></div><div aria-hidden="true" class="${prefix}-fill"${styled(fillStyle)}></div>${marksHtml}${thumbs}</div>${support}</${prefix}>`;
|
|
155
|
+
};
|
|
@@ -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
|
+
};
|
package/design/stream.ts
ADDED
|
@@ -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
|
+
};
|