@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/button.ts
CHANGED
|
@@ -2,6 +2,9 @@ import { interactiveStyles } from '@ethlete/core/design/interactive-styles';
|
|
|
2
2
|
import focusRing from '../src/lib/focus-ring/focus-ring-styles.component.css?inline';
|
|
3
3
|
import buttonProperties from '../src/lib/button/button-properties-styles.component.css?inline';
|
|
4
4
|
import buttonBase from '../src/lib/button/button.component.css?inline';
|
|
5
|
+
import { BUTTON_SPINNER_CONFIG } from '../src/lib/button/button.types';
|
|
6
|
+
import iconButtonBase from '../src/lib/button/icon-button.component.css?inline';
|
|
7
|
+
import { spinner } from './loader';
|
|
5
8
|
|
|
6
9
|
export type ButtonMockOptions = {
|
|
7
10
|
/** The label, as HTML. */
|
|
@@ -9,6 +12,8 @@ export type ButtonMockOptions = {
|
|
|
9
12
|
/** The icon, as HTML. Leave it out for a button without one. */
|
|
10
13
|
icon?: string;
|
|
11
14
|
iconAlignment?: 'start' | 'end';
|
|
15
|
+
/** Lets the label fill the space beside the icon, like `stretchContents` on `et-button`. */
|
|
16
|
+
stretchContents?: boolean;
|
|
12
17
|
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
13
18
|
variant?: 'filled' | 'outline' | 'tonal' | 'transparent';
|
|
14
19
|
/** A color theme name the app registers. Left out, the button takes the color theme around it. */
|
|
@@ -16,6 +21,8 @@ export type ButtonMockOptions = {
|
|
|
16
21
|
/** Makes the button a toggle: `false` draws it unpressed, left out it is no toggle. */
|
|
17
22
|
pressed?: boolean;
|
|
18
23
|
disabled?: boolean;
|
|
24
|
+
/** Draws the busy button with its spinner, like `loading` on `et-button`. Needs `loaderStyles` from the loader mock. */
|
|
25
|
+
loading?: boolean;
|
|
19
26
|
tag?: 'button' | 'a';
|
|
20
27
|
class?: string;
|
|
21
28
|
style?: string;
|
|
@@ -46,12 +53,66 @@ export const button = (options: ButtonMockOptions) => {
|
|
|
46
53
|
options.pressed && 'data-pressed="true"',
|
|
47
54
|
options.pressed !== undefined && `aria-pressed="${options.pressed}"`,
|
|
48
55
|
options.disabled && (tag === 'button' ? 'disabled aria-disabled="true"' : 'aria-disabled="true"'),
|
|
56
|
+
options.loading && `data-loading="true" aria-busy="true"${options.disabled ? '' : ' aria-disabled="true"'}`,
|
|
49
57
|
`data-icon-alignment="${iconAlignment}"`,
|
|
50
58
|
`data-variant="${variant}"`,
|
|
51
59
|
`data-size="${size}"`,
|
|
60
|
+
options.stretchContents && 'data-stretch-contents="true"',
|
|
61
|
+
'data-can-animate="true"',
|
|
52
62
|
options.pressed && `data-pressed-variant="${PRESSED_VARIANT[variant]}"`,
|
|
53
63
|
options.style && `style="${options.style}"`,
|
|
54
64
|
].filter(Boolean);
|
|
55
65
|
|
|
56
|
-
|
|
66
|
+
const loader = options.loading
|
|
67
|
+
? `<div class="et-button-loader" aria-hidden="true">${spinner(BUTTON_SPINNER_CONFIG[size]).replace('class="et-spinner', 'class="et-button-loader-spinner et-spinner')}</div>`
|
|
68
|
+
: '';
|
|
69
|
+
|
|
70
|
+
return `<${tag} ${attributes.join(' ')}>${iconAlignment === 'start' ? iconBox : ''}<div class="et-button-contents">${label}</div>${iconAlignment === 'end' ? iconBox : ''}${loader}</${tag}>`;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type IconButtonMockOptions = Omit<ButtonMockOptions, 'label' | 'icon' | 'iconAlignment'> & {
|
|
74
|
+
/** The icon, as HTML. */
|
|
75
|
+
icon: string;
|
|
76
|
+
/** The accessible name: an icon-only button has no visible label. */
|
|
77
|
+
ariaLabel: string;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const ICON_PRESSED_VARIANT = {
|
|
81
|
+
filled: 'transparent',
|
|
82
|
+
outline: 'filled',
|
|
83
|
+
tonal: 'filled',
|
|
84
|
+
transparent: 'tonal',
|
|
85
|
+
} as const;
|
|
86
|
+
|
|
87
|
+
/** The stylesheets `iconButton()` draws with: the real `et-icon-button` CSS. */
|
|
88
|
+
export const iconButtonStyles = `${interactiveStyles}\n${focusRing}\n${buttonProperties}\n${iconButtonBase}`;
|
|
89
|
+
|
|
90
|
+
/** Draws `<button et-icon-button>` as HTML, the square icon-only button, with the DOM and classes the component renders. */
|
|
91
|
+
export const iconButton = (options: IconButtonMockOptions) => {
|
|
92
|
+
const { icon, ariaLabel, size = 'md', variant = 'transparent', tag = 'button' } = options;
|
|
93
|
+
|
|
94
|
+
const classes = [
|
|
95
|
+
'et-surface-interactive',
|
|
96
|
+
'et-color-interactive',
|
|
97
|
+
'et-icon-button',
|
|
98
|
+
'et-focus-ring',
|
|
99
|
+
options.color ? `et-color--${options.color}` : 'et-color--inherited',
|
|
100
|
+
options.class,
|
|
101
|
+
].filter(Boolean);
|
|
102
|
+
|
|
103
|
+
const attributes = [
|
|
104
|
+
`class="${classes.join(' ')}"`,
|
|
105
|
+
tag === 'button' && 'type="button"',
|
|
106
|
+
`aria-label="${ariaLabel}"`,
|
|
107
|
+
options.pressed && 'data-pressed="true"',
|
|
108
|
+
options.pressed !== undefined && `aria-pressed="${options.pressed}"`,
|
|
109
|
+
options.disabled && (tag === 'button' ? 'disabled aria-disabled="true"' : 'aria-disabled="true"'),
|
|
110
|
+
`data-variant="${variant}"`,
|
|
111
|
+
`data-size="${size}"`,
|
|
112
|
+
'data-can-animate="true"',
|
|
113
|
+
options.pressed && `data-pressed-variant="${ICON_PRESSED_VARIANT[variant]}"`,
|
|
114
|
+
options.style && `style="${options.style}"`,
|
|
115
|
+
].filter(Boolean);
|
|
116
|
+
|
|
117
|
+
return `<${tag} ${attributes.join(' ')}><div class="et-icon-button-icon">${icon}</div></${tag}>`;
|
|
57
118
|
};
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import iconButtonBase from '../src/lib/button/icon-button.component.css?inline';
|
|
2
|
+
import coarseGridBase from '../src/lib/calendar/calendar-coarse-grid-styles.component.css?inline';
|
|
3
|
+
import comparisonBandBase from '../src/lib/calendar/calendar-comparison-band-styles.component.css?inline';
|
|
4
|
+
import weekNumbersBase from '../src/lib/calendar/calendar-week-numbers-styles.component.css?inline';
|
|
5
|
+
import calendarBase from '../src/lib/calendar/calendar.component.css?inline';
|
|
6
|
+
import { CHEVRON_ICON } from '../src/lib/icon/headless/chevron-icon';
|
|
7
|
+
import iconBase from '../src/lib/icon/headless/icon-styles.component.css?inline';
|
|
8
|
+
import { buttonStyles } from './button';
|
|
9
|
+
import { iconMarkup } from './icon';
|
|
10
|
+
|
|
11
|
+
export type CalendarMockRange = {
|
|
12
|
+
/** An ISO date, `YYYY-MM-DD`. */
|
|
13
|
+
start: string;
|
|
14
|
+
/** An ISO date, `YYYY-MM-DD`. */
|
|
15
|
+
end: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type CalendarMockOptions = {
|
|
19
|
+
/** Left out, `month`. */
|
|
20
|
+
view?: 'month' | 'year' | 'multiYear';
|
|
21
|
+
/** The visible month, as an ISO month `YYYY-MM`. */
|
|
22
|
+
month: string;
|
|
23
|
+
/** The first year of the year grid's page of 24. Left out, the visible year rounded down to a multiple of 24. */
|
|
24
|
+
yearPageStart?: number;
|
|
25
|
+
/** Today, as an ISO date. Pass a fixed date, never the real today. */
|
|
26
|
+
today?: string;
|
|
27
|
+
/** The selected day of a single-value calendar, as an ISO date. */
|
|
28
|
+
value?: string;
|
|
29
|
+
/** The selected range of a range calendar. */
|
|
30
|
+
range?: CalendarMockRange;
|
|
31
|
+
/** The comparison band (`etCalendarComparisonBand`). */
|
|
32
|
+
comparison?: CalendarMockRange;
|
|
33
|
+
/** The earliest selectable day, as an ISO date. */
|
|
34
|
+
min?: string;
|
|
35
|
+
/** The latest selectable day, as an ISO date. */
|
|
36
|
+
max?: string;
|
|
37
|
+
/** Days the date filter turns down, as ISO dates. */
|
|
38
|
+
disabled?: string[];
|
|
39
|
+
weekNumbers?: boolean;
|
|
40
|
+
/** Left out, `1` (Monday), as `et-calendar` defaults to without a locale. */
|
|
41
|
+
weekStartsOn?: number;
|
|
42
|
+
/** Replaces the header label, e.g. with a localized one. */
|
|
43
|
+
headerLabel?: string;
|
|
44
|
+
/** Extra attributes per cell, keyed by ISO date in the month view, `YYYY-MM` in the year view and `YYYY` in the year grid. */
|
|
45
|
+
cellAttributes?: Record<string, string>;
|
|
46
|
+
/** Extra attributes on the previous button, e.g. design markers. */
|
|
47
|
+
previousAttributes?: string;
|
|
48
|
+
/** Extra attributes on the next button, e.g. design markers. */
|
|
49
|
+
nextAttributes?: string;
|
|
50
|
+
/** Extra attributes on the header label button, e.g. design markers. */
|
|
51
|
+
labelAttributes?: string;
|
|
52
|
+
/** Extra attributes on the host, e.g. a style or design markers. */
|
|
53
|
+
attributes?: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type Band = 'start' | 'middle' | 'end' | 'single' | null;
|
|
57
|
+
|
|
58
|
+
type CellFlags = {
|
|
59
|
+
label: string;
|
|
60
|
+
key: string;
|
|
61
|
+
today: boolean;
|
|
62
|
+
selected: boolean;
|
|
63
|
+
disabled: boolean;
|
|
64
|
+
band: Band;
|
|
65
|
+
comparisonBand: Band;
|
|
66
|
+
outsideMonth?: boolean;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const DAY = 86_400_000;
|
|
70
|
+
|
|
71
|
+
const toDay = (iso: string) => {
|
|
72
|
+
const [year = 0, month = 1, day = 1] = iso.split('-').map(Number);
|
|
73
|
+
|
|
74
|
+
return Date.UTC(year, month - 1, day) / DAY;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const toIso = (day: number) => new Date(day * DAY).toISOString().slice(0, 10);
|
|
78
|
+
|
|
79
|
+
const isoWeek = (day: number) => {
|
|
80
|
+
const date = new Date(day * DAY);
|
|
81
|
+
const thursday = day + 3 - ((date.getUTCDay() + 6) % 7);
|
|
82
|
+
const yearStart = Date.UTC(new Date(thursday * DAY).getUTCFullYear(), 0, 1) / DAY;
|
|
83
|
+
|
|
84
|
+
return Math.floor((thursday - yearStart) / 7) + 1;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const monthName = (utc: number, style: 'long' | 'short') =>
|
|
88
|
+
new Intl.DateTimeFormat('en-US', { month: style, timeZone: 'UTC' }).format(utc);
|
|
89
|
+
|
|
90
|
+
const bandFor = (range: { from: number; to: number } | null, [from, to]: [number, number]): Band => {
|
|
91
|
+
if (!range || to < range.from || from > range.to) return null;
|
|
92
|
+
if (range.from === range.to && from <= range.from && to >= range.to) return 'single';
|
|
93
|
+
if (from <= range.from) return 'start';
|
|
94
|
+
if (to >= range.to) return 'end';
|
|
95
|
+
|
|
96
|
+
return 'middle';
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const toRange = (range: CalendarMockRange | undefined) =>
|
|
100
|
+
range
|
|
101
|
+
? { from: Math.min(toDay(range.start), toDay(range.end)), to: Math.max(toDay(range.start), toDay(range.end)) }
|
|
102
|
+
: null;
|
|
103
|
+
|
|
104
|
+
/** The stylesheets `calendar()` draws with: the real calendar, coarse grid, comparison band, week number, icon-button and icon CSS. */
|
|
105
|
+
export const calendarStyles = /* @__PURE__ */ [
|
|
106
|
+
buttonStyles,
|
|
107
|
+
iconBase,
|
|
108
|
+
iconButtonBase,
|
|
109
|
+
calendarBase,
|
|
110
|
+
coarseGridBase,
|
|
111
|
+
comparisonBandBase,
|
|
112
|
+
weekNumbersBase,
|
|
113
|
+
].join('\n');
|
|
114
|
+
|
|
115
|
+
/** Draws `<et-calendar>` as HTML with its default header and the grid of one view, as a static snapshot of one state. */
|
|
116
|
+
export const calendar = (options: CalendarMockOptions) => {
|
|
117
|
+
const { view = 'month', weekStartsOn = 1, cellAttributes = {} } = options;
|
|
118
|
+
const [year = 0, monthNumber = 1] = options.month.split('-').map(Number);
|
|
119
|
+
const month = monthNumber - 1;
|
|
120
|
+
const monthStart = Date.UTC(year, month, 1) / DAY;
|
|
121
|
+
const monthEnd = Date.UTC(year, month + 1, 0) / DAY;
|
|
122
|
+
const min = options.min ? toDay(options.min) : -Infinity;
|
|
123
|
+
const max = options.max ? toDay(options.max) : Infinity;
|
|
124
|
+
const turnedDown = new Set((options.disabled ?? []).map(toDay));
|
|
125
|
+
const today = options.today ? toDay(options.today) : null;
|
|
126
|
+
const value = options.value ? toDay(options.value) : null;
|
|
127
|
+
const range = toRange(options.range);
|
|
128
|
+
const comparison = toRange(options.comparison);
|
|
129
|
+
const pageStart = options.yearPageStart ?? Math.floor(year / 24) * 24;
|
|
130
|
+
|
|
131
|
+
const isSelectable = (day: number) => day >= min && day <= max && !turnedDown.has(day);
|
|
132
|
+
const hasSelectableDay = (from: number, to: number) => {
|
|
133
|
+
for (let day = Math.max(from, min); day <= Math.min(to, max); day++) {
|
|
134
|
+
if (!turnedDown.has(day)) return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return false;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const spanFlags = (from: number, to: number) => ({
|
|
141
|
+
today: today !== null && today >= from && today <= to,
|
|
142
|
+
selected:
|
|
143
|
+
(value !== null && value >= from && value <= to) ||
|
|
144
|
+
(range !== null && ((range.from >= from && range.from <= to) || (range.to >= from && range.to <= to))),
|
|
145
|
+
band: bandFor(range, [from, to]),
|
|
146
|
+
comparisonBand: bandFor(comparison, [from, to]),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const cellButton = (cell: CellFlags, coarse: boolean) => {
|
|
150
|
+
const attributes = [
|
|
151
|
+
cellAttributes[cell.key],
|
|
152
|
+
`class="et-calendar-cell${coarse ? ' et-calendar-cell--coarse' : ''}"`,
|
|
153
|
+
'type="button"',
|
|
154
|
+
'tabindex="-1"',
|
|
155
|
+
`aria-selected="${cell.selected}"`,
|
|
156
|
+
cell.disabled && 'aria-disabled="true"',
|
|
157
|
+
cell.today && 'aria-current="date"',
|
|
158
|
+
cell.selected && 'data-selected=""',
|
|
159
|
+
cell.disabled && 'data-disabled=""',
|
|
160
|
+
cell.today && 'data-today=""',
|
|
161
|
+
cell.band && `data-band="${cell.band}"`,
|
|
162
|
+
cell.comparisonBand && `data-comparison-band="${cell.comparisonBand}"`,
|
|
163
|
+
cell.outsideMonth && 'data-outside-month=""',
|
|
164
|
+
].filter(Boolean);
|
|
165
|
+
|
|
166
|
+
return `<button ${attributes.join(' ')}><span class="et-calendar-cell-content">${cell.label}</span></button>`;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const coarseRows = (cells: CellFlags[]) => {
|
|
170
|
+
const rows: string[] = [];
|
|
171
|
+
|
|
172
|
+
for (let index = 0; index < cells.length; index += 4) {
|
|
173
|
+
const row = cells
|
|
174
|
+
.slice(index, index + 4)
|
|
175
|
+
.map((cell) => cellButton(cell, true))
|
|
176
|
+
.join('');
|
|
177
|
+
rows.push(`<div class="et-calendar-coarse-row" role="row">${row}</div>`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return rows.join('');
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const monthGrid = () => {
|
|
184
|
+
const offset = (new Date(monthStart * DAY).getUTCDay() - weekStartsOn + 7) % 7;
|
|
185
|
+
const weeks: string[] = [];
|
|
186
|
+
|
|
187
|
+
for (let weekStart = monthStart - offset; weekStart <= monthEnd; weekStart += 7) {
|
|
188
|
+
const weekNumber = options.weekNumbers
|
|
189
|
+
? `<span class="et-calendar-week-number" role="rowheader">${isoWeek(weekStart)}</span>`
|
|
190
|
+
: '';
|
|
191
|
+
const days = Array.from({ length: 7 }, (_, index) => {
|
|
192
|
+
const day = weekStart + index;
|
|
193
|
+
|
|
194
|
+
return cellButton(
|
|
195
|
+
{
|
|
196
|
+
label: `${new Date(day * DAY).getUTCDate()}`,
|
|
197
|
+
key: toIso(day),
|
|
198
|
+
disabled: !isSelectable(day),
|
|
199
|
+
outsideMonth: day < monthStart || day > monthEnd,
|
|
200
|
+
...spanFlags(day, day),
|
|
201
|
+
},
|
|
202
|
+
false,
|
|
203
|
+
);
|
|
204
|
+
}).join('');
|
|
205
|
+
weeks.push(`<div class="et-calendar-week" role="row">${weekNumber}${days}</div>`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return `<div class="et-calendar-months" role="presentation"><div class="et-calendar-month" role="presentation">${weeks.join('')}</div></div>`;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const yearGrid = () =>
|
|
212
|
+
coarseRows(
|
|
213
|
+
Array.from({ length: 12 }, (_, index) => {
|
|
214
|
+
const from = Date.UTC(year, index, 1) / DAY;
|
|
215
|
+
const to = Date.UTC(year, index + 1, 0) / DAY;
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
label: monthName(from * DAY, 'short'),
|
|
219
|
+
key: `${year}-${String(index + 1).padStart(2, '0')}`,
|
|
220
|
+
disabled: !hasSelectableDay(from, to),
|
|
221
|
+
...spanFlags(from, to),
|
|
222
|
+
};
|
|
223
|
+
}),
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const multiYearGrid = () =>
|
|
227
|
+
coarseRows(
|
|
228
|
+
Array.from({ length: 24 }, (_, index) => {
|
|
229
|
+
const cellYear = pageStart + index;
|
|
230
|
+
const from = Date.UTC(cellYear, 0, 1) / DAY;
|
|
231
|
+
const to = Date.UTC(cellYear, 11, 31) / DAY;
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
label: `${cellYear}`,
|
|
235
|
+
key: `${cellYear}`,
|
|
236
|
+
disabled: !hasSelectableDay(from, to),
|
|
237
|
+
...spanFlags(from, to),
|
|
238
|
+
};
|
|
239
|
+
}),
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
const weekdays = () => {
|
|
243
|
+
const heading = options.weekNumbers
|
|
244
|
+
? '<span class="et-calendar-week-number-heading" role="columnheader"></span>'
|
|
245
|
+
: '';
|
|
246
|
+
const names = Array.from({ length: 7 }, (_, index) => {
|
|
247
|
+
const name = new Intl.DateTimeFormat('en-US', { weekday: 'short', timeZone: 'UTC' })
|
|
248
|
+
.format(Date.UTC(2023, 0, 1 + ((weekStartsOn + index) % 7)))
|
|
249
|
+
.slice(0, 2);
|
|
250
|
+
|
|
251
|
+
return `<span class="et-calendar-weekday" role="columnheader">${name}</span>`;
|
|
252
|
+
}).join('');
|
|
253
|
+
|
|
254
|
+
return `<div class="et-calendar-weekdays" role="row">${heading}${names}</div>`;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const [canGoPrev, canGoNext] =
|
|
258
|
+
view === 'month'
|
|
259
|
+
? [min < monthStart, max > monthEnd]
|
|
260
|
+
: view === 'year'
|
|
261
|
+
? [min < Date.UTC(year, 0, 1) / DAY, max > Date.UTC(year, 11, 31) / DAY]
|
|
262
|
+
: [min < Date.UTC(pageStart, 0, 1) / DAY, max > Date.UTC(pageStart + 23, 11, 31) / DAY];
|
|
263
|
+
|
|
264
|
+
const headerLabel =
|
|
265
|
+
options.headerLabel ??
|
|
266
|
+
(view === 'month'
|
|
267
|
+
? `${monthName(monthStart * DAY, 'long')} ${year}`
|
|
268
|
+
: view === 'year'
|
|
269
|
+
? `${year}`
|
|
270
|
+
: `${pageStart} – ${pageStart + 23}`);
|
|
271
|
+
|
|
272
|
+
const navButton = (side: 'previous' | 'next', { enabled, extra }: { enabled: boolean; extra?: string }) => {
|
|
273
|
+
const attributes = [
|
|
274
|
+
extra,
|
|
275
|
+
`class="et-calendar-nav-button et-calendar-nav-button--${side} et-icon-button et-color-interactive et-focus-ring et-color--inherited"`,
|
|
276
|
+
'type="button"',
|
|
277
|
+
`aria-label="${side === 'previous' ? 'Previous' : 'Next'}"`,
|
|
278
|
+
!enabled && 'disabled',
|
|
279
|
+
'data-variant="transparent"',
|
|
280
|
+
'data-size="sm"',
|
|
281
|
+
].filter(Boolean);
|
|
282
|
+
|
|
283
|
+
return `<button ${attributes.join(' ')}><div class="et-icon-button-icon">${iconMarkup(CHEVRON_ICON)}</div></button>`;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const label = [
|
|
287
|
+
options.labelAttributes,
|
|
288
|
+
'class="et-calendar-header-label"',
|
|
289
|
+
'type="button"',
|
|
290
|
+
`aria-label="${view === 'multiYear' ? 'Choose date' : 'Choose year'}"`,
|
|
291
|
+
].filter(Boolean);
|
|
292
|
+
|
|
293
|
+
const header = `<div class="et-calendar-header">${navButton('previous', { enabled: canGoPrev, extra: options.previousAttributes })}<button ${label.join(' ')}><span class="et-calendar-header-label-text" aria-live="polite"><span class="et-calendar-header-label-value">${headerLabel}</span></span>${iconMarkup(CHEVRON_ICON, 'et-calendar-header-label-icon')}</button>${navButton('next', { enabled: canGoNext, extra: options.nextAttributes })}</div>`;
|
|
294
|
+
|
|
295
|
+
const grid = view === 'month' ? monthGrid() : view === 'year' ? yearGrid() : multiYearGrid();
|
|
296
|
+
const body = `<div class="et-calendar-grid">${view === 'month' ? weekdays() : ''}<div class="et-calendar-weeks-viewport" role="presentation"><div class="et-calendar-weeks" role="rowgroup">${grid}</div></div></div>`;
|
|
297
|
+
|
|
298
|
+
const host = [
|
|
299
|
+
options.attributes,
|
|
300
|
+
'class="et-calendar"',
|
|
301
|
+
`data-view="${view}"`,
|
|
302
|
+
options.weekNumbers && 'data-week-numbers=""',
|
|
303
|
+
].filter(Boolean);
|
|
304
|
+
|
|
305
|
+
return `<div ${host.join(' ')}>${header}${body}</div>`;
|
|
306
|
+
};
|
package/design/card.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import cardBase from '../src/lib/card/card.component.css?inline';
|
|
2
|
+
import { button, ButtonMockOptions, buttonStyles } from './button';
|
|
3
|
+
|
|
4
|
+
export type CardMockOptions = {
|
|
5
|
+
variant?: 'elevated' | 'outlined' | 'filled';
|
|
6
|
+
/** A surface theme name the app registers. Left out, the card takes the surface around it. */
|
|
7
|
+
surface?: string;
|
|
8
|
+
/** The projected content, as HTML. `cardContent()` draws a typical one. */
|
|
9
|
+
content: string;
|
|
10
|
+
/** Extra attributes on the card, e.g. design markers or a style. */
|
|
11
|
+
attributes?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type CardContentMockOptions = {
|
|
15
|
+
title?: string;
|
|
16
|
+
/** Muted text under the title, e.g. a date or a category. */
|
|
17
|
+
subtitle?: string;
|
|
18
|
+
text?: string;
|
|
19
|
+
/** Media at the top, as HTML. `cardPlaceholderMedia` draws a generic one. */
|
|
20
|
+
media?: string;
|
|
21
|
+
actions?: ButtonMockOptions[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** A generic landscape placeholder as an inline SVG, for drawing media in a card. */
|
|
25
|
+
export const cardPlaceholderMedia =
|
|
26
|
+
'<svg class="et-card-mock-media" viewBox="0 0 320 140" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Placeholder image"><rect width="320" height="140" fill="#8a94a6"/><circle cx="250" cy="40" r="16" fill="#e6e9ef"/><path d="M0 140 90 60l60 50 40-30 130 60z" fill="#e6e9ef"/></svg>';
|
|
27
|
+
|
|
28
|
+
/** The stylesheets `card()` and `cardContent()` draw with: the real card and button CSS, plus the mock's content layout. */
|
|
29
|
+
export const cardStyles = /* @__PURE__ */ [
|
|
30
|
+
buttonStyles,
|
|
31
|
+
cardBase,
|
|
32
|
+
`@layer components {
|
|
33
|
+
.et-card-mock-media {
|
|
34
|
+
display: block;
|
|
35
|
+
inline-size: calc(100% + 2 * var(--et-card-padding));
|
|
36
|
+
block-size: auto;
|
|
37
|
+
margin: calc(-1 * var(--et-card-padding)) calc(-1 * var(--et-card-padding)) 0;
|
|
38
|
+
border-start-start-radius: inherit;
|
|
39
|
+
border-start-end-radius: inherit;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.et-card-mock-title {
|
|
43
|
+
margin: 0;
|
|
44
|
+
font-size: 1.6rem;
|
|
45
|
+
font-weight: 600;
|
|
46
|
+
line-height: 1.4;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.et-card-mock-subtitle {
|
|
50
|
+
margin: 0.2rem 0 0;
|
|
51
|
+
font-size: 1.3rem;
|
|
52
|
+
color: var(--et-surface-color-muted-solid);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.et-card-mock-text {
|
|
56
|
+
margin: 0;
|
|
57
|
+
font-size: 1.4rem;
|
|
58
|
+
line-height: 1.5;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.et-card-mock-actions {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-wrap: wrap;
|
|
64
|
+
justify-content: flex-end;
|
|
65
|
+
gap: 8px;
|
|
66
|
+
}
|
|
67
|
+
}`,
|
|
68
|
+
].join('\n');
|
|
69
|
+
|
|
70
|
+
/** Draws an `et-card` as the component renders it, as HTML. */
|
|
71
|
+
export const card = (options: CardMockOptions) => {
|
|
72
|
+
const attributes = [
|
|
73
|
+
options.attributes,
|
|
74
|
+
`data-variant="${options.variant ?? 'outlined'}"`,
|
|
75
|
+
`class="et-card et-surface--${options.surface ?? 'inherited'}"`,
|
|
76
|
+
].filter(Boolean);
|
|
77
|
+
|
|
78
|
+
return `<et-card ${attributes.join(' ')}>${options.content}</et-card>`;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** Draws typical consumer content for a card: media, a title with subtitle, text and an actions row. The SDK card ships none of these parts. */
|
|
82
|
+
export const cardContent = (options: CardContentMockOptions) => {
|
|
83
|
+
const heading = options.title
|
|
84
|
+
? `<div><h3 class="et-card-mock-title">${options.title}</h3>${options.subtitle ? `<p class="et-card-mock-subtitle">${options.subtitle}</p>` : ''}</div>`
|
|
85
|
+
: '';
|
|
86
|
+
const text = options.text ? `<p class="et-card-mock-text">${options.text}</p>` : '';
|
|
87
|
+
const actions = options.actions?.length
|
|
88
|
+
? `<div class="et-card-mock-actions">${options.actions.map((each) => button(each)).join('')}</div>`
|
|
89
|
+
: '';
|
|
90
|
+
|
|
91
|
+
return `${options.media ?? ''}${heading}${text}${actions}`;
|
|
92
|
+
};
|