@ethlete/components 1.0.0-next.77 → 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.
- package/CHANGELOG.md +119 -0
- package/design/accordion.ts +73 -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 +49 -0
- 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 +22 -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 +214 -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 +40 -1
- package/design/grid.ts +148 -0
- package/design/icon-set.ts +19 -0
- package/design/icon.ts +9 -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 +143 -0
- package/design/notification.ts +129 -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 +64 -0
- package/design/sdk/components/avatar/avatar.css +14 -0
- package/design/sdk/components/avatar/avatar.ts +78 -0
- package/design/sdk/components/badge/badge.css +13 -0
- package/design/sdk/components/badge/badge.ts +52 -0
- package/design/sdk/components/banner/banner.css +18 -0
- package/design/sdk/components/banner/banner.ts +87 -0
- package/design/sdk/components/bracket/bracket.css +59 -0
- package/design/sdk/components/bracket/bracket.ts +143 -0
- package/design/sdk/components/breadcrumb/breadcrumb.css +25 -0
- package/design/sdk/components/breadcrumb/breadcrumb.ts +81 -0
- package/design/sdk/components/button/button.css +33 -0
- package/design/sdk/components/button/button.ts +82 -0
- package/design/sdk/components/calendar/calendar.css +33 -0
- package/design/sdk/components/calendar/calendar.ts +96 -0
- package/design/sdk/components/card/card.css +19 -0
- package/design/sdk/components/card/card.ts +61 -0
- package/design/sdk/components/carousel/carousel.css +44 -0
- package/design/sdk/components/carousel/carousel.ts +74 -0
- package/design/sdk/components/chart/chart.css +56 -0
- package/design/sdk/components/chart/chart.ts +98 -0
- package/design/sdk/components/chip/chip.css +18 -0
- package/design/sdk/components/chip/chip.ts +76 -0
- package/design/sdk/components/copy-button/copy-button.css +14 -0
- package/design/sdk/components/copy-button/copy-button.ts +59 -0
- package/design/sdk/components/description-list/description-list.css +7 -0
- package/design/sdk/components/description-list/description-list.ts +53 -0
- package/design/sdk/components/dialog/dialog.css +29 -0
- package/design/sdk/components/dialog/dialog.ts +78 -0
- package/design/sdk/components/divider/divider.css +26 -0
- package/design/sdk/components/divider/divider.ts +43 -0
- package/design/sdk/components/empty-state/empty-state.css +15 -0
- package/design/sdk/components/empty-state/empty-state.ts +56 -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 +118 -0
- package/design/sdk/components/grid/grid.css +17 -0
- package/design/sdk/components/grid/grid.ts +80 -0
- package/design/sdk/components/input/input.css +75 -0
- package/design/sdk/components/input/input.ts +79 -0
- package/design/sdk/components/kbd/kbd.css +25 -0
- package/design/sdk/components/kbd/kbd.ts +57 -0
- package/design/sdk/components/loader/loader.css +37 -0
- package/design/sdk/components/loader/loader.ts +68 -0
- package/design/sdk/components/masonry/masonry.css +7 -0
- package/design/sdk/components/masonry/masonry.ts +42 -0
- package/design/sdk/components/match/match.css +25 -0
- package/design/sdk/components/match/match.ts +76 -0
- package/design/sdk/components/menu/menu.css +22 -0
- package/design/sdk/components/menu/menu.ts +91 -0
- package/design/sdk/components/notification/notification.css +32 -0
- package/design/sdk/components/notification/notification.ts +106 -0
- package/design/sdk/components/otp-input/otp-input.css +35 -0
- package/design/sdk/components/otp-input/otp-input.ts +64 -0
- package/design/sdk/components/pagination/pagination.css +14 -0
- package/design/sdk/components/pagination/pagination.ts +75 -0
- package/design/sdk/components/picture/picture.css +37 -0
- package/design/sdk/components/picture/picture.ts +96 -0
- package/design/sdk/components/progress-steps/progress-steps.css +37 -0
- package/design/sdk/components/progress-steps/progress-steps.ts +91 -0
- package/design/sdk/components/query-error/query-error.css +33 -0
- package/design/sdk/components/query-error/query-error.ts +120 -0
- package/design/sdk/components/scheduler/scheduler.css +55 -0
- package/design/sdk/components/scheduler/scheduler.ts +239 -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 +123 -0
- package/design/sdk/components/select/select.css +5 -0
- package/design/sdk/components/select/select.ts +16 -0
- package/design/sdk/components/select/shared/select-part.ts +75 -0
- package/design/sdk/components/select-panel/select-panel.css +3 -0
- package/design/sdk/components/select-panel/select-panel.ts +124 -0
- package/design/sdk/components/skeleton/skeleton.css +22 -0
- package/design/sdk/components/skeleton/skeleton.ts +50 -0
- package/design/sdk/components/slider/slider.css +52 -0
- package/design/sdk/components/slider/slider.ts +86 -0
- package/design/sdk/components/standings/standings.css +33 -0
- package/design/sdk/components/standings/standings.ts +130 -0
- package/design/sdk/components/stat-tile/stat-tile.css +15 -0
- package/design/sdk/components/stat-tile/stat-tile.ts +84 -0
- package/design/sdk/components/stream/stream.css +48 -0
- package/design/sdk/components/stream/stream.ts +38 -0
- package/design/sdk/components/switch/switch.css +35 -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 +160 -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 +64 -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 +87 -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/fixtures.ts +0 -0
- package/design/sdk/foundations/colors/colors.css +632 -0
- package/design/sdk/foundations/colors/colors.ts +188 -0
- package/design/sdk/foundations/colors/overrides.json +19 -0
- package/design/sdk/foundations/colors/shared/contrast.ts +120 -0
- 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/select.ts +113 -28
- 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 +92 -0
- package/design/timeline.ts +52 -0
- package/design/toolbar.ts +95 -0
- package/design/tooltip.ts +87 -0
- package/design/tree.ts +106 -0
- package/fesm2022/ethlete-components.mjs +240 -199
- package/fesm2022/ethlete-components.mjs.map +1 -1
- 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.component.css +19 -0
- package/src/lib/button/icon-button.component.css +19 -20
- package/src/lib/button/split-button.component.css +5 -1
- package/src/lib/calendar/calendar.component.css +13 -3
- 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 +8 -2
- 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 +74 -17
- 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/select/select-option-styles.component.css +3 -2
- 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 +47 -4
- 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/icon/headless/align-icons.ts +30 -0
- package/src/lib/icon/headless/arrow-out-up-right-icon.ts +17 -0
- package/src/lib/icon/headless/arrow-right-icon.ts +23 -0
- package/src/lib/icon/headless/arrow-up-icon.ts +30 -0
- package/src/lib/icon/headless/arrows-left-right-icon.ts +31 -0
- package/src/lib/icon/headless/bold-icon.ts +11 -0
- package/src/lib/icon/headless/calendar-icon.ts +21 -0
- package/src/lib/icon/headless/check-icon.ts +14 -0
- package/src/lib/icon/headless/chevron-icon.ts +14 -0
- package/src/lib/icon/headless/circle-check-icon.ts +14 -0
- package/src/lib/icon/headless/circle-info-icon.ts +14 -0
- package/src/lib/icon/headless/clipboard-check-icon.ts +28 -0
- package/src/lib/icon/headless/clock-icon.ts +21 -0
- package/src/lib/icon/headless/code-block-icon.ts +15 -0
- package/src/lib/icon/headless/code-icon.ts +14 -0
- package/src/lib/icon/headless/ellipsis-icon.ts +13 -0
- package/src/lib/icon/headless/ellipsis-vertical-icon.ts +13 -0
- package/src/lib/icon/headless/eye-icons.ts +27 -0
- package/src/lib/icon/headless/eyedropper-icon.ts +22 -0
- package/src/lib/icon/headless/file-icon.ts +16 -0
- package/src/lib/icon/headless/filter-icon.ts +11 -0
- package/src/lib/icon/headless/floppy-disk-icon.ts +27 -0
- package/src/lib/icon/headless/focus-frame-icon.ts +16 -0
- package/src/lib/icon/headless/grid-2x2-icon.ts +17 -0
- package/src/lib/icon/headless/heading-1-icon.ts +11 -0
- package/src/lib/icon/headless/heading-2-icon.ts +11 -0
- package/src/lib/icon/headless/heading-3-icon.ts +11 -0
- package/src/lib/icon/headless/icon-definition.ts +14 -0
- package/src/lib/icon/headless/image-icon.ts +14 -0
- package/src/lib/icon/headless/italic-icon.ts +19 -0
- package/src/lib/icon/headless/link-icon.ts +17 -0
- package/src/lib/icon/headless/list-bulleted-icon.ts +14 -0
- package/src/lib/icon/headless/list-numbered-icon.ts +14 -0
- package/src/lib/icon/headless/location-icon.ts +21 -0
- package/src/lib/icon/headless/lock-icon.ts +14 -0
- package/src/lib/icon/headless/minus-icon.ts +14 -0
- package/src/lib/icon/headless/paragraph-icon.ts +11 -0
- package/src/lib/icon/headless/pause-icon.ts +11 -0
- package/src/lib/icon/headless/pencil-icon.ts +21 -0
- package/src/lib/icon/headless/play-icon.ts +10 -0
- package/src/lib/icon/headless/plus-icon.ts +21 -0
- package/src/lib/icon/headless/quote-icon.ts +15 -0
- package/src/lib/icon/headless/rotate-right-icon.ts +16 -0
- package/src/lib/icon/headless/star-icon.ts +14 -0
- package/src/lib/icon/headless/strikethrough-icon.ts +18 -0
- package/src/lib/icon/headless/table-icon.ts +15 -0
- package/src/lib/icon/headless/thumbtack-icon.ts +18 -0
- package/src/lib/icon/headless/times-icon.ts +14 -0
- package/src/lib/icon/headless/trash-icon.ts +24 -0
- package/src/lib/icon/headless/triangle-exclamation-icon.ts +14 -0
- package/src/lib/icon/headless/trophy-icon.ts +24 -0
- package/src/lib/icon/headless/underline-icon.ts +12 -0
- package/src/lib/icon/headless/undo-redo-icons.ts +24 -0
- package/src/lib/icon/headless/upload-icon.ts +16 -0
- 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 +5 -0
- 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/src/lib/tree/tree.component.css +2 -2
- package/types/ethlete-components.d.ts +62 -53
|
@@ -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
|
+
};
|
package/design/switch.ts
ADDED
|
@@ -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
|
+
};
|
package/design/table.ts
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import checkboxBase from '../src/lib/forms/checkbox/checkbox.component.css?inline';
|
|
2
|
+
import { ARROW_UP_ICON } from '../src/lib/icon/headless/arrow-up-icon';
|
|
3
|
+
import { CHEVRON_ICON } from '../src/lib/icon/headless/chevron-icon';
|
|
4
|
+
import iconBase from '../src/lib/icon/headless/icon-styles.component.css?inline';
|
|
5
|
+
import { TRIANGLE_EXCLAMATION_ICON } from '../src/lib/icon/headless/triangle-exclamation-icon';
|
|
6
|
+
import tableDetailBase from '../src/lib/table/table-detail-styles.component.css?inline';
|
|
7
|
+
import tableExpanderBase from '../src/lib/table/table-expander-cell.component.css?inline';
|
|
8
|
+
import tableGroupHeaderBase from '../src/lib/table/table-group-header-row.component.css?inline';
|
|
9
|
+
import tableSelectBase from '../src/lib/table/table-select-cell.component.css?inline';
|
|
10
|
+
import tableSkeletonRowsBase from '../src/lib/table/table-skeleton-rows.component.css?inline';
|
|
11
|
+
import tableStickyColumnsBase from '../src/lib/table/table-sticky-columns-styles.component.css?inline';
|
|
12
|
+
import tableBase from '../src/lib/table/table.component.css?inline';
|
|
13
|
+
import { iconMarkup } from './icon';
|
|
14
|
+
import { skeletonItem, skeletonStyles } from './skeleton';
|
|
15
|
+
|
|
16
|
+
export type TableColumnMock = {
|
|
17
|
+
header: string;
|
|
18
|
+
/** Left out, `start`. */
|
|
19
|
+
align?: 'start' | 'center' | 'end';
|
|
20
|
+
/** Draws the header label as the sort button. */
|
|
21
|
+
sortable?: boolean;
|
|
22
|
+
/** The column's sort direction. Left out, unsorted. */
|
|
23
|
+
sort?: 'asc' | 'desc';
|
|
24
|
+
/** Pins the column (`etTableStickyColumns`), with its `inset-inline-*` offset in px. */
|
|
25
|
+
sticky?: { side: 'start' | 'end'; offset: number };
|
|
26
|
+
/** Extra attributes on the sort button, e.g. design markers. */
|
|
27
|
+
labelAttributes?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type TableCellMock =
|
|
31
|
+
| string
|
|
32
|
+
| {
|
|
33
|
+
value: string;
|
|
34
|
+
/** `error` draws the built-in cell error mark before the value. */
|
|
35
|
+
state?: 'error';
|
|
36
|
+
/** The error message, drawn as the mark's label and title. */
|
|
37
|
+
message?: string;
|
|
38
|
+
/** Extra attributes on the cell, e.g. design markers. */
|
|
39
|
+
attributes?: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type TableRowMock = {
|
|
43
|
+
cells: TableCellMock[];
|
|
44
|
+
/** Draws the row as selected (`etTableSelection`). */
|
|
45
|
+
selected?: boolean;
|
|
46
|
+
/** Draws the expander open and the detail row under the row, as HTML. */
|
|
47
|
+
detail?: string;
|
|
48
|
+
/** Leaves the expander out, as a row `canExpand` turns down. */
|
|
49
|
+
noExpander?: boolean;
|
|
50
|
+
/** Extra attributes on the row, e.g. design markers. */
|
|
51
|
+
attributes?: string;
|
|
52
|
+
/** Extra attributes on the expander button, e.g. design markers. */
|
|
53
|
+
expanderAttributes?: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type TableMockOptions = {
|
|
57
|
+
columns: TableColumnMock[];
|
|
58
|
+
rows: TableRowMock[];
|
|
59
|
+
/** The `grid-template-columns` of the data columns, without the leading utility tracks. */
|
|
60
|
+
tracks: string;
|
|
61
|
+
/** Left out, `enclosed`, as `et-table` defaults to. */
|
|
62
|
+
appearance?: 'enclosed' | 'divided' | 'zebra' | 'grid' | 'bare';
|
|
63
|
+
/** Left out, `md`, as `et-table` defaults to. */
|
|
64
|
+
density?: 'sm' | 'md' | 'lg';
|
|
65
|
+
/** Draws the selection column; the value is the select-all checkbox state. */
|
|
66
|
+
selection?: 'unchecked' | 'checked' | 'mixed';
|
|
67
|
+
/** Draws the expander column (`etTableRowExpansion`). */
|
|
68
|
+
expandable?: boolean;
|
|
69
|
+
/** Draws the group header row (`etTableGroupHeaders`) over the data columns. */
|
|
70
|
+
groups?: { label?: string; span: number }[];
|
|
71
|
+
/** Draws the message row in place of the body: `empty` with the default label, `error` with the default icon and label. */
|
|
72
|
+
message?: 'empty' | 'error';
|
|
73
|
+
/** Draws this many skeleton rows in place of the body (`etTableSkeleton`), with the bone widths in % per column. */
|
|
74
|
+
placeholder?: { rows: number; widths: number[] };
|
|
75
|
+
/** Draws the busy bar over the first row, its sweep held at this fraction of its run from 0 to 1. */
|
|
76
|
+
busyAt?: number;
|
|
77
|
+
/** Rows with a click handler (`rowClick`), which take the pointer and the pressed tint. */
|
|
78
|
+
interactive?: boolean;
|
|
79
|
+
/** Cells take focus (`etTableKeyboardNav`). */
|
|
80
|
+
cellNavigation?: boolean;
|
|
81
|
+
/** The scroll edges with content past them: draws their shadow marks and scroll fades. */
|
|
82
|
+
scrolled?: { inlineStart?: boolean; inlineEnd?: boolean; blockStart?: boolean };
|
|
83
|
+
/** The error color theme name, the app's `type: 'error'` theme. */
|
|
84
|
+
errorColor: string;
|
|
85
|
+
/** Extra attributes on the host, e.g. a style or design markers. */
|
|
86
|
+
attributes?: string;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/** The stylesheets `table()` draws with: the real table, feature, checkbox, skeleton and icon CSS. */
|
|
90
|
+
export const tableStyles = /* @__PURE__ */ [
|
|
91
|
+
iconBase,
|
|
92
|
+
checkboxBase,
|
|
93
|
+
skeletonStyles,
|
|
94
|
+
tableBase,
|
|
95
|
+
tableStickyColumnsBase,
|
|
96
|
+
tableSelectBase,
|
|
97
|
+
tableExpanderBase,
|
|
98
|
+
tableGroupHeaderBase,
|
|
99
|
+
tableDetailBase,
|
|
100
|
+
tableSkeletonRowsBase,
|
|
101
|
+
`.et-table-busy-bar[data-et-busy-at]::after {
|
|
102
|
+
animation: none;
|
|
103
|
+
transform: translateX(calc(-100% + var(--et-busy-at) * 350%));
|
|
104
|
+
}
|
|
105
|
+
.et-table-row--placeholder .et-skeleton-item::after {
|
|
106
|
+
animation: none;
|
|
107
|
+
}`,
|
|
108
|
+
].join('\n');
|
|
109
|
+
|
|
110
|
+
const checkbox = (state: 'unchecked' | 'checked' | 'mixed', label: string) => {
|
|
111
|
+
const checked = state === 'checked' ? 'true' : state === 'mixed' ? 'mixed' : 'false';
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
`<et-checkbox class="et-checkbox" role="checkbox" aria-checked="${checked}" aria-label="${label}" tabindex="0">` +
|
|
115
|
+
`<div class="et-checkbox-box">` +
|
|
116
|
+
`<svg class="et-checkbox-checkmark" viewBox="0 0 16 16" aria-hidden="true"><polyline class="et-checkbox-checkmark-path" points="3.5 8 6.5 11 12.5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></polyline></svg>` +
|
|
117
|
+
`<svg class="et-checkbox-indeterminate" viewBox="0 0 16 16" aria-hidden="true"><line class="et-checkbox-indeterminate-path" x1="4" y1="8" x2="12" y2="8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"></line></svg>` +
|
|
118
|
+
`</div></et-checkbox>`
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const attr = (name: string, value: string | number | undefined) => (value === undefined ? '' : ` ${name}="${value}"`);
|
|
123
|
+
|
|
124
|
+
const styleAttr = (value: string) => ` style="${value}"`;
|
|
125
|
+
|
|
126
|
+
const stickyOf = (column: TableColumnMock | undefined) =>
|
|
127
|
+
column?.sticky
|
|
128
|
+
? {
|
|
129
|
+
className: ` et-table-sticky-${column.sticky.side}`,
|
|
130
|
+
style: styleAttr(`inset-inline-${column.sticky.side}: ${column.sticky.offset}px`),
|
|
131
|
+
}
|
|
132
|
+
: { className: '', style: '' };
|
|
133
|
+
|
|
134
|
+
const ariaSort = (column: TableColumnMock) =>
|
|
135
|
+
column.sort === 'asc' ? 'ascending' : column.sort === 'desc' ? 'descending' : column.sortable ? 'none' : undefined;
|
|
136
|
+
|
|
137
|
+
const headerCell = (column: TableColumnMock) => {
|
|
138
|
+
const sticky = stickyOf(column);
|
|
139
|
+
const text = `<span class="et-table-cell-text">${column.header}</span>`;
|
|
140
|
+
const indicator = column.sort
|
|
141
|
+
? iconMarkup(ARROW_UP_ICON, 'et-table-sort-indicator').replace('<i ', `<i data-direction="${column.sort}" `)
|
|
142
|
+
: '';
|
|
143
|
+
const label = column.sortable
|
|
144
|
+
? `<button class="et-table-header-label et-table-header-label--sortable" type="button"${column.labelAttributes ? ` ${column.labelAttributes}` : ''}><span class="et-table-header-label-box">${text}${indicator}</span></button>`
|
|
145
|
+
: `<span class="et-table-header-label"><span class="et-table-header-label-box">${text}</span></span>`;
|
|
146
|
+
|
|
147
|
+
return `<div class="et-table-header-cell${sticky.className}" role="columnheader"${attr('data-align', column.align)}${attr('aria-sort', ariaSort(column))}${sticky.style}>${label}</div>`;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const bodyCell = (options: TableMockOptions, column: number) => (cell: TableCellMock) => {
|
|
151
|
+
const sticky = stickyOf(options.columns[column]);
|
|
152
|
+
const resolved = typeof cell === 'string' ? { value: cell } : cell;
|
|
153
|
+
const mark =
|
|
154
|
+
resolved.state === 'error'
|
|
155
|
+
? iconMarkup(TRIANGLE_EXCLAMATION_ICON, `et-table-cell-error-icon et-color--${options.errorColor}`).replace(
|
|
156
|
+
' aria-hidden="true"',
|
|
157
|
+
` role="img" aria-label="${resolved.message ?? ''}" title="${resolved.message ?? ''}"`,
|
|
158
|
+
)
|
|
159
|
+
: '';
|
|
160
|
+
const extra = resolved.attributes ? ` ${resolved.attributes}` : '';
|
|
161
|
+
|
|
162
|
+
return `<div class="et-table-cell${sticky.className}" role="cell"${attr('data-align', options.columns[column]?.align)}${attr('data-state', resolved.state)}${attr('tabindex', options.cellNavigation ? -1 : undefined)}${sticky.style}${extra}>${mark}<span class="et-table-cell-text">${resolved.value}</span></div>`;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const leadHeaderCells = (options: TableMockOptions) =>
|
|
166
|
+
[
|
|
167
|
+
options.selection
|
|
168
|
+
? `<div class="et-table-select-cell et-table-header-cell" role="columnheader"><et-table-select-all-cell>${checkbox(options.selection, 'Select all rows')}</et-table-select-all-cell></div>`
|
|
169
|
+
: '',
|
|
170
|
+
options.expandable ? `<div class="et-table-expander-cell et-table-header-cell" role="columnheader"></div>` : '',
|
|
171
|
+
].join('');
|
|
172
|
+
|
|
173
|
+
const leadBodyCells = (row: TableRowMock, options: TableMockOptions) => {
|
|
174
|
+
const expanded = row.detail !== undefined;
|
|
175
|
+
const expander =
|
|
176
|
+
row.noExpander === true
|
|
177
|
+
? ''
|
|
178
|
+
: `<button class="et-table-expander" type="button" aria-expanded="${expanded}" aria-label="${expanded ? 'Collapse row' : 'Expand row'}"${row.expanderAttributes ? ` ${row.expanderAttributes}` : ''}>${iconMarkup(CHEVRON_ICON, 'et-table-expander-icon').replace('<i ', `<i data-expanded="${expanded}" `)}</button>`;
|
|
179
|
+
|
|
180
|
+
return [
|
|
181
|
+
options.selection
|
|
182
|
+
? `<div class="et-table-select-cell et-table-cell" role="cell"><et-table-select-cell>${checkbox(row.selected ? 'checked' : 'unchecked', 'Select row')}</et-table-select-cell></div>`
|
|
183
|
+
: '',
|
|
184
|
+
options.expandable
|
|
185
|
+
? `<div class="et-table-expander-cell et-table-cell" role="cell"><et-table-expander-cell>${expander}</et-table-expander-cell></div>`
|
|
186
|
+
: '',
|
|
187
|
+
].join('');
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const leadCount = (options: TableMockOptions) => (options.selection ? 1 : 0) + (options.expandable ? 1 : 0);
|
|
191
|
+
|
|
192
|
+
const groupRow = (options: TableMockOptions) =>
|
|
193
|
+
options.groups
|
|
194
|
+
? `<et-table-group-header-row class="et-table-group-row" role="row">${'<div class="et-table-group-cell" aria-hidden="true"></div>'.repeat(leadCount(options))}${options.groups
|
|
195
|
+
.map((group) =>
|
|
196
|
+
group.label
|
|
197
|
+
? `<div class="et-table-group-cell et-table-group-cell--labeled" role="columnheader" aria-colspan="${group.span}"${styleAttr(`grid-column: span ${group.span}`)}><span class="et-table-cell-text">${group.label}</span></div>`
|
|
198
|
+
: `<div class="et-table-group-cell" aria-hidden="true"${styleAttr(`grid-column: span ${group.span}`)}></div>`,
|
|
199
|
+
)
|
|
200
|
+
.join('')}</et-table-group-header-row>`
|
|
201
|
+
: '';
|
|
202
|
+
|
|
203
|
+
const bodyRow = (options: TableMockOptions) => (row: TableRowMock, index: number) => {
|
|
204
|
+
const last = index === options.rows.length - 1;
|
|
205
|
+
const classes = [
|
|
206
|
+
'et-table-row',
|
|
207
|
+
row.selected && 'et-table-row--selected',
|
|
208
|
+
index % 2 === 1 && 'et-table-row--stripe',
|
|
209
|
+
last && 'et-table-row--last',
|
|
210
|
+
options.interactive && 'et-table-row--interactive',
|
|
211
|
+
].filter(Boolean);
|
|
212
|
+
const cells = row.cells.map((cell, column) => bodyCell(options, column)(cell)).join('');
|
|
213
|
+
const detail =
|
|
214
|
+
row.detail === undefined
|
|
215
|
+
? ''
|
|
216
|
+
: `<et-table-row-detail class="et-table-detail-row" role="row"><div class="et-table-detail-cell" role="cell"><div class="et-table-detail-body">${row.detail}</div></div></et-table-row-detail>`;
|
|
217
|
+
|
|
218
|
+
return `<div class="${classes.join(' ')}" role="row"${attr('aria-selected', options.selection ? String(row.selected === true) : undefined)}${attr('tabindex', options.interactive ? 0 : undefined)}${row.attributes ? ` ${row.attributes}` : ''}>${leadBodyCells(row, options)}${cells}</div>${detail}`;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const placeholderRows = (options: TableMockOptions) => {
|
|
222
|
+
const { rows, widths } = options.placeholder ?? { rows: 0, widths: [] };
|
|
223
|
+
const lead = (className: string) => `<div class="${className} et-table-cell" role="cell"></div>`;
|
|
224
|
+
|
|
225
|
+
return `<et-table-skeleton-rows class="et-table-skeleton-rows">${Array.from(
|
|
226
|
+
{ length: rows },
|
|
227
|
+
(_, index) =>
|
|
228
|
+
`<div class="et-table-row et-table-row--placeholder et-skeleton--animated${index % 2 === 1 ? ' et-table-row--stripe' : ''}${index === rows - 1 ? ' et-table-row--last' : ''}" role="row" aria-hidden="true">` +
|
|
229
|
+
(options.selection ? lead('et-table-select-cell') : '') +
|
|
230
|
+
(options.expandable ? lead('et-table-expander-cell') : '') +
|
|
231
|
+
options.columns
|
|
232
|
+
.map(
|
|
233
|
+
(column, position) =>
|
|
234
|
+
`<div class="et-table-cell" role="cell"${attr('data-align', column.align)}>${skeletonItem({ style: `inline-size: ${widths[position] ?? 60}%` })}</div>`,
|
|
235
|
+
)
|
|
236
|
+
.join('') +
|
|
237
|
+
`</div>`,
|
|
238
|
+
).join('')}</et-table-skeleton-rows>`;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const messageRow = (options: TableMockOptions) =>
|
|
242
|
+
options.message === 'error'
|
|
243
|
+
? `<div class="et-table-message-row" role="row"><div class="et-table-error-cell" role="cell" aria-live="polite">${iconMarkup(TRIANGLE_EXCLAMATION_ICON, `et-table-error-icon et-color--${options.errorColor}`)}<span>Could not load data</span></div></div>`
|
|
244
|
+
: `<div class="et-table-message-row" role="row"><div class="et-table-empty-cell" role="cell">No data</div></div>`;
|
|
245
|
+
|
|
246
|
+
const body = (options: TableMockOptions) => {
|
|
247
|
+
if (options.message) return messageRow(options);
|
|
248
|
+
if (options.placeholder) return placeholderRows(options);
|
|
249
|
+
|
|
250
|
+
return options.rows.map(bodyRow(options)).join('');
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const scrollFades = (options: TableMockOptions) => {
|
|
254
|
+
const pinned = (side: 'start' | 'end') => options.columns.some((column) => column.sticky?.side === side);
|
|
255
|
+
const fade = (side: 'start' | 'end', scrolled: boolean | undefined) =>
|
|
256
|
+
`<div class="et-table-scroll-fade et-table-scroll-fade--${side}${scrolled && !pinned(side) ? ' et-table-scroll-fade--visible' : ''}"${styleAttr(`inset-inline-${side}: 0px`)}></div>`;
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
`<div class="et-table-scroll-fades" role="presentation" aria-hidden="true">` +
|
|
260
|
+
fade('start', options.scrolled?.inlineStart) +
|
|
261
|
+
fade('end', options.scrolled?.inlineEnd) +
|
|
262
|
+
`</div>`
|
|
263
|
+
);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/** Draws `<et-table>` with its header, body rows and the built-in features as a static snapshot of one state. */
|
|
267
|
+
export const table = (options: TableMockOptions) => {
|
|
268
|
+
const leads = [
|
|
269
|
+
options.selection && 'var(--et-table-select-width, 44px)',
|
|
270
|
+
options.expandable && 'var(--et-table-expander-width, 32px)',
|
|
271
|
+
];
|
|
272
|
+
const tracks = [...leads.filter(Boolean), options.tracks].join(' ');
|
|
273
|
+
const busy =
|
|
274
|
+
options.busyAt === undefined
|
|
275
|
+
? ''
|
|
276
|
+
: `<div class="et-table-busy-bar" role="presentation" data-et-busy-at${styleAttr(`--et-busy-at: ${options.busyAt}`)}></div>`;
|
|
277
|
+
const host = [
|
|
278
|
+
options.attributes,
|
|
279
|
+
`class="${[
|
|
280
|
+
'et-table-host',
|
|
281
|
+
options.scrolled?.inlineStart && 'et-table-host--scrolled-inline-start',
|
|
282
|
+
options.scrolled?.inlineEnd && 'et-table-host--scrolled-inline-end',
|
|
283
|
+
options.scrolled?.blockStart && 'et-table-host--scrolled-block-start',
|
|
284
|
+
]
|
|
285
|
+
.filter(Boolean)
|
|
286
|
+
.join(' ')}"`,
|
|
287
|
+
`data-appearance="${options.appearance ?? 'enclosed'}"`,
|
|
288
|
+
`data-density="${options.density ?? 'md'}"`,
|
|
289
|
+
options.placeholder || options.busyAt !== undefined ? 'aria-busy="true"' : '',
|
|
290
|
+
].filter(Boolean);
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
`<et-table ${host.join(' ')}>` +
|
|
294
|
+
`<div class="et-table" role="table"${styleAttr(`grid-template-columns: ${tracks}`)}>` +
|
|
295
|
+
groupRow(options) +
|
|
296
|
+
`<div class="et-table-header-row" role="row">${leadHeaderCells(options)}${options.columns.map(headerCell).join('')}</div>` +
|
|
297
|
+
busy +
|
|
298
|
+
body(options) +
|
|
299
|
+
scrollFades(options) +
|
|
300
|
+
`</div></et-table>`
|
|
301
|
+
);
|
|
302
|
+
};
|