@ethlete/components 1.0.0-next.78 → 1.0.0-next.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +113 -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 +4 -1
- 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/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 +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 +50 -7
- 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 +23 -5
- 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.ts +1 -1
- package/design/sdk/components/select/shared/select-part.ts +16 -1
- 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/foundations/colors/colors.css +108 -4
- 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/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/fesm2022/ethlete-components.mjs +228 -187
- 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 +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 +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/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/types/ethlete-components.d.ts +11 -2
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { banner, BannerMockType, bannerStyles } from '@ethlete/components/design/banner';
|
|
2
|
+
import { definePart, drawing } from '@design-system';
|
|
3
|
+
import frame from './banner.css?inline';
|
|
4
|
+
|
|
5
|
+
type Interaction = 'hover' | 'active' | 'focus-visible';
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
type: BannerMockType | 'all';
|
|
9
|
+
dismissible?: boolean;
|
|
10
|
+
close?: Interaction;
|
|
11
|
+
action?: Interaction;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const COLORS: Record<BannerMockType, string | undefined> = {
|
|
15
|
+
info: undefined,
|
|
16
|
+
success: undefined,
|
|
17
|
+
warning: 'warning',
|
|
18
|
+
error: 'danger',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const COPY: Record<BannerMockType, { heading: string; description: string; action?: string }> = {
|
|
22
|
+
info: {
|
|
23
|
+
heading: 'Round 4 is open',
|
|
24
|
+
description: 'Predictions close at kick-off of the first match.',
|
|
25
|
+
action: 'View fixtures',
|
|
26
|
+
},
|
|
27
|
+
success: { heading: 'Changes saved', description: 'Both teams and the referee get an email with the new time.' },
|
|
28
|
+
warning: {
|
|
29
|
+
heading: 'Storage almost full',
|
|
30
|
+
description: 'You are running low on storage space - consider upgrading your plan.',
|
|
31
|
+
action: 'Upgrade',
|
|
32
|
+
},
|
|
33
|
+
error: {
|
|
34
|
+
heading: 'Result not saved',
|
|
35
|
+
description: 'The connection dropped before the server answered.',
|
|
36
|
+
action: 'Retry',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const mark = (control: string, interaction?: Interaction) =>
|
|
41
|
+
[`data-et-control="${control}" data-can-animate`, interaction && `data-et-force="${interaction}"`]
|
|
42
|
+
.filter(Boolean)
|
|
43
|
+
.join(' ');
|
|
44
|
+
|
|
45
|
+
const draw = (type: BannerMockType, { dismissible = true, close, action }: Omit<Props, 'type'> = {}) =>
|
|
46
|
+
banner({
|
|
47
|
+
type,
|
|
48
|
+
color: COLORS[type],
|
|
49
|
+
...COPY[type],
|
|
50
|
+
actionAttributes: mark('action', action),
|
|
51
|
+
dismissible,
|
|
52
|
+
dismissAttributes: mark('close', close),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const render = ({ type, ...rest }: Props) =>
|
|
56
|
+
type === 'all'
|
|
57
|
+
? (['info', 'success', 'warning', 'error'] as const).map((each) => draw(each)).join('')
|
|
58
|
+
: draw(type, rest);
|
|
59
|
+
|
|
60
|
+
export default definePart({
|
|
61
|
+
note: 'Decided rules only',
|
|
62
|
+
madeBy: ['claude-opus-5-5'],
|
|
63
|
+
states: {
|
|
64
|
+
info: { type: 'info' },
|
|
65
|
+
success: { type: 'success' },
|
|
66
|
+
warning: { type: 'warning' },
|
|
67
|
+
error: { type: 'error' },
|
|
68
|
+
'without-dismiss': { type: 'info', dismissible: false },
|
|
69
|
+
'close-hover': { type: 'info', close: 'hover' },
|
|
70
|
+
'close-focus-visible': { type: 'info', close: 'focus-visible' },
|
|
71
|
+
'action-hover': { type: 'info', action: 'hover' },
|
|
72
|
+
'action-focus-visible': { type: 'info', action: 'focus-visible' },
|
|
73
|
+
'error-close-focus-visible': { type: 'error', close: 'focus-visible' },
|
|
74
|
+
'error-action-focus-visible': { type: 'error', action: 'focus-visible' },
|
|
75
|
+
all: { type: 'all' },
|
|
76
|
+
},
|
|
77
|
+
widths: [480],
|
|
78
|
+
controls: {
|
|
79
|
+
close: { kind: 'button', input: ['pointer', 'keyboard'] },
|
|
80
|
+
action: { kind: 'button', input: ['pointer', 'keyboard'] },
|
|
81
|
+
},
|
|
82
|
+
render: (props: Props) =>
|
|
83
|
+
drawing({
|
|
84
|
+
body: `<div class="banner-frame">${render(props)}</div>`,
|
|
85
|
+
styles: [bannerStyles, frame].join('\n'),
|
|
86
|
+
}),
|
|
87
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
.t-frame {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
display: grid;
|
|
4
|
+
justify-items: start;
|
|
5
|
+
gap: 1.6rem;
|
|
6
|
+
padding: 1.6rem;
|
|
7
|
+
background: var(--et-surface-background-solid);
|
|
8
|
+
color: var(--et-surface-color-solid);
|
|
9
|
+
font-size: 1.4rem;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@layer components {
|
|
13
|
+
.et-bracket-participants-toggle {
|
|
14
|
+
--_et-focus-ring-color: var(--et-theme-color-ink-solid);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.et-bracket-participants-toggle[data-pressed='true']:where([data-pressed-variant='filled']) {
|
|
18
|
+
--et-button-shadow: inset 0 0 0 1px var(--et-theme-color-ink-solid);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.et-match-card:where([data-winner='home']) .et-match-card-side:where([data-side='home']),
|
|
22
|
+
.et-match-card:where([data-winner='away']) .et-match-card-side:where([data-side='away']) {
|
|
23
|
+
--_et-match-card-side-marker-color: light-dark(
|
|
24
|
+
var(--et-theme-color-ink-solid),
|
|
25
|
+
var(--et-theme-color-primary-solid)
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.et-match-card-live-dot {
|
|
30
|
+
background: light-dark(var(--et-theme-color-ink-solid), var(--et-theme-color-primary-solid));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.et-bracket-final-host {
|
|
34
|
+
border-color: light-dark(
|
|
35
|
+
var(--et-theme-color-ink-solid),
|
|
36
|
+
color-mix(in srgb, var(--et-theme-color-primary-solid, currentColor) 45%, transparent)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.et-bracket-default-match-card .et-match-card-meta,
|
|
41
|
+
.et-bracket-default-match-card .et-match-card-sides {
|
|
42
|
+
grid-area: 1 / 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.et-bracket-default-match-card .et-match-card-meta {
|
|
46
|
+
justify-self: end;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.et-bracket-default-match-card .et-match-card-live {
|
|
50
|
+
gap: 0;
|
|
51
|
+
padding: 0;
|
|
52
|
+
font-size: 0;
|
|
53
|
+
background: none;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.et-bracket-default-match-card:where([data-status='live']) .et-match-card-sides {
|
|
57
|
+
padding-inline-end: 12px;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bracket,
|
|
3
|
+
BracketMockMatch,
|
|
4
|
+
BracketMockOptions,
|
|
5
|
+
BracketMockParticipant,
|
|
6
|
+
BracketMockRound,
|
|
7
|
+
bracketParticipants,
|
|
8
|
+
bracketStyles,
|
|
9
|
+
} from '@ethlete/components/design/bracket';
|
|
10
|
+
import { definePart, drawing } from '@design-system';
|
|
11
|
+
import frame from './bracket.css?inline';
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
stage: 'in-progress' | 'decided' | 'groups-done';
|
|
15
|
+
density?: 'compact';
|
|
16
|
+
mirrored?: boolean;
|
|
17
|
+
journey?: { participantId: string; pinned?: boolean };
|
|
18
|
+
legend?: { focused?: string; focusVisible?: string };
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const NBR: BracketMockParticipant = { id: 'nbr', name: 'Northbridge Rovers', code: 'NBR', seed: 1 };
|
|
22
|
+
const HBC: BracketMockParticipant = { id: 'hbc', name: 'Harbor City', code: 'HBC', seed: 2 };
|
|
23
|
+
const EVU: BracketMockParticipant = { id: 'evu', name: 'Eastvale United', code: 'EVU', seed: 3 };
|
|
24
|
+
const MBA: BracketMockParticipant = { id: 'mba', name: 'Millbrook Athletic', code: 'MBA', seed: 4 };
|
|
25
|
+
const RSW: BracketMockParticipant = { id: 'rsw', name: 'Riverside Wanderers', code: 'RSW', seed: 5 };
|
|
26
|
+
const SGF: BracketMockParticipant = { id: 'sgf', name: 'Stonegate FC', code: 'SGF', seed: 6 };
|
|
27
|
+
const WPA: BracketMockParticipant = { id: 'wpa', name: 'Westport Albion', code: 'WPA', seed: 7 };
|
|
28
|
+
const LST: BracketMockParticipant = { id: 'lst', name: 'Lakeside Town', code: 'LST', seed: 8 };
|
|
29
|
+
|
|
30
|
+
const PARTICIPANTS = [NBR, HBC, EVU, MBA, RSW, SGF, WPA, LST];
|
|
31
|
+
|
|
32
|
+
const won = (
|
|
33
|
+
home: BracketMockParticipant,
|
|
34
|
+
away: BracketMockParticipant,
|
|
35
|
+
homeScore: number,
|
|
36
|
+
awayScore: number,
|
|
37
|
+
): BracketMockMatch => ({
|
|
38
|
+
home,
|
|
39
|
+
away,
|
|
40
|
+
homeScore,
|
|
41
|
+
awayScore,
|
|
42
|
+
winnerSide: homeScore > awayScore ? 'home' : 'away',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const QUARTER_FINALS: BracketMockRound = {
|
|
46
|
+
name: 'Quarter-finals',
|
|
47
|
+
matches: [won(NBR, LST, 3, 0), won(MBA, RSW, 1, 2), won(EVU, SGF, 2, 1), won(HBC, WPA, 0, 1)],
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const roundsOf = (stage: Props['stage']): BracketMockRound[] => {
|
|
51
|
+
if (stage === 'groups-done') {
|
|
52
|
+
return [
|
|
53
|
+
{ ...QUARTER_FINALS, type: 'single-elimination-bracket' },
|
|
54
|
+
{
|
|
55
|
+
name: 'Semi-finals',
|
|
56
|
+
type: 'single-elimination-bracket',
|
|
57
|
+
matches: [won(NBR, RSW, 2, 1), won(EVU, WPA, 1, 3)],
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const decided = stage === 'decided';
|
|
63
|
+
|
|
64
|
+
return [
|
|
65
|
+
QUARTER_FINALS,
|
|
66
|
+
{
|
|
67
|
+
name: 'Semi-finals',
|
|
68
|
+
matches: [
|
|
69
|
+
won(NBR, RSW, 2, 1),
|
|
70
|
+
decided ? won(EVU, WPA, 1, 3) : { home: EVU, away: WPA, status: 'live', homeScore: 1, awayScore: 1 },
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'Final',
|
|
75
|
+
matches: [decided ? won(NBR, WPA, 1, 2) : { home: NBR, status: 'scheduled', startTime: '11/21/2026 7:00 PM' }],
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const legendOf = (legend: NonNullable<Props['legend']>) => {
|
|
81
|
+
const markup = bracketParticipants({ participants: PARTICIPANTS, focusedParticipantId: legend.focused });
|
|
82
|
+
const name = PARTICIPANTS.find((participant) => participant.id === legend.focusVisible)?.name;
|
|
83
|
+
|
|
84
|
+
return name
|
|
85
|
+
? markup.replace(
|
|
86
|
+
new RegExp(`<button ([^>]*)><div class="et-button-icon"></div><div class="et-button-contents">${name}<`),
|
|
87
|
+
`<button data-et-force="focus-visible" $1><div class="et-button-icon"></div><div class="et-button-contents">${name}<`,
|
|
88
|
+
)
|
|
89
|
+
: markup;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const render = (props: Props) => {
|
|
93
|
+
const options: BracketMockOptions = {
|
|
94
|
+
rounds: roundsOf(props.stage),
|
|
95
|
+
mirrored: props.mirrored,
|
|
96
|
+
journey: props.journey,
|
|
97
|
+
settings: {
|
|
98
|
+
density: props.density,
|
|
99
|
+
showContinueElement: props.stage === 'groups-done',
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return `${props.legend ? legendOf(props.legend) : ''}${bracket(options)}`;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export default definePart({
|
|
107
|
+
note: "The SDK bracket (et-bracket) in flow, first drawing, drawn by the mock from @ethlete/components/design/bracket with the real markup and CSS: the bracket, its default round header, default match cell (et-match-card at compact), default final card (trophy, round name, champion line), continue cell and participants legend sheets, plus the match card, icon and button sheets; cell positions and connector paths come from @ethlete/bracket (createBracket, the single elimination grid, drawMan) as in the lib; default settings and labels, en-US kick-off. Emblems are the participant's letter fallback; the move transitions at rest. Fixtures are fictional: an eight-club single elimination knockout (Northbridge Rovers NBR 1, Harbor City HBC 2, Eastvale United EVU 3, Millbrook Athletic MBA 4, Riverside Wanderers RSW 5, Stonegate FC SGF 6, Westport Albion WPA 7, Lakeside Town LST 8) over Quarter-finals, Semi-finals and Final; the brand theme only. Framed as a page with 16px padding at 1040 and 1680 (the default bracket is 980px wide, the grid does not follow the frame width). States: in-progress (quarter-finals and one semi-final finished, the other semi-final live 1 : 1 with the LIVE badge, the final with Northbridge Rovers against TBD and the kick-off, champion not decided), decided (Westport Albion beat Northbridge Rovers 2 : 1, the champion line), journey-hover (the hover journey of Riverside Wanderers: the rest at half opacity, the semi-final where they went out dashed and their row struck through), journey-pinned (the pinned journey of the champion with the participants legend, its toggle pressed; the rest at a quarter opacity), legend-focus-visible (the Eastvale United toggle forced), compact (density compact, in-progress), continue (quarter-finals and semi-finals with the continue cell, 2 winners advance, dashed connectors) and mirrored (decided, folded in half, compact, so it fits 1040). Numbers (light / dark), from shot pixels at scale 2 at 1040, brand mint, a: winner bar (primary) vs card 1.33 / 13.50; live dot (primary) vs its badge tint 1.24 / 9.67, vs card 1.33 / 13.28; badge tint vs card 1.07 / 1.37; final frame edge (45 % mint) vs page 1.22 / 3.90, vs the glow beside it 1.15 / 2.98, vs its own tint 1.18 / 3.50; the final tint (6 %) vs page 1.03 / 1.11; pressed legend toggle (mint fill) vs page 1.33 / 13.50, its label 15.82 / 15.82; legend toggle focus ring (primary) vs page 1.37 / 13.06; outline toggle edge and label (ink) 7.68 light. Q98 numbers only: connectors, card edges, the continue cell's dashed edge and the journey endpoint's dashed outline vs page 1.26 / 1.73. The journey dimming lowers text under 4.5:1 by design: at half opacity the check reports a dimmed winner code at 3.41 / 2.68 and the dimmed Final label at 2.14 / 3.93, at a quarter opacity 1.73 / 2.21, the struck-through row that went out (0.55 opacity) 2.62 / 2.98; exempted with contrast exceptions, an open point, no decided rule. Decided rules that arise: Q88 (the legend toggle focus ring; the default cells are not links, so no card ring) and Q99a (the winner bar, the live dot and the final frame edge take ink in light; the pressed legend toggle is a mint fill, so it takes a 1px inset ink edge). The ink edge on a mint fill arises, so Q102 arises: b draws the edge in both schemes, c keeps dark as today (light-dark(ink, transparent)). The badge tint and the final tint are band tints, not fills, so they are kept, and the final glow is kept. Q101 does not arise: the only filled button is a pressed toggle, whose hover keeps the fill in the lib. Findings for later: (1) lib bug: the live meta row grows the bracket's compact card past matchHeight: the live semi-final is 87px tall in its 75px cell (65 in 52 at compact), so it hangs 12px (13px) below its cell, the incoming connector meets it 6px above its centre and the gap to the card below shrinks. Drawn as is in every variant. (2) In the mirrored compact layout the final card in its 200px column shows full names and cuts the loser ('Northbridge Rove…') next to the score, where every other compact cell shows codes. (3) The eliminated row's line-through also strikes the emblem letter and the score. (4) The lit journey path keeps the grey connector colour (1.26 / 1.73), so it reads only by the rest fading; the dimmed text is under 4.5:1 (numbers above). (5) In dark the final glow lightens the page beside the frame edge, so the edge drops from 3.90 against the bare page to 2.98 against the glow. Later rounds, left out here: double elimination, swiss and round robin layouts, the third-place match, the mirrored layout at default density, the rounds list and adaptive layout, the skeleton, the prediction pick card, a custom match component on a link (card hover and focus ring), emblems as images, scheduled cells with kick-off times, long names, RTL and the move transitions. Omitted option value (Q182): alignRoundHeaders center draws the same pixels as start, because the default round header fills its column and is already centred.",
|
|
108
|
+
madeBy: ['claude-opus-5-5'],
|
|
109
|
+
states: {
|
|
110
|
+
'in-progress': { stage: 'in-progress' },
|
|
111
|
+
decided: { stage: 'decided' },
|
|
112
|
+
'journey-hover': { stage: 'decided', journey: { participantId: 'rsw' } },
|
|
113
|
+
'journey-pinned': {
|
|
114
|
+
stage: 'decided',
|
|
115
|
+
journey: { participantId: 'wpa', pinned: true },
|
|
116
|
+
legend: { focused: 'wpa' },
|
|
117
|
+
},
|
|
118
|
+
'legend-focus-visible': { stage: 'decided', legend: { focusVisible: 'evu' } },
|
|
119
|
+
compact: { stage: 'in-progress', density: 'compact' },
|
|
120
|
+
continue: { stage: 'groups-done' },
|
|
121
|
+
mirrored: { stage: 'decided', density: 'compact', mirrored: true },
|
|
122
|
+
},
|
|
123
|
+
contrast: {
|
|
124
|
+
exceptions: [
|
|
125
|
+
{
|
|
126
|
+
selector: '.et-bracket-host--journey-hover .et-bracket-element--match:not(.et-bracket-journey-active)',
|
|
127
|
+
reason:
|
|
128
|
+
"The journey highlight fades every cell off the path to half opacity (a quarter when pinned) on purpose; its contrast is an open point, not this round's question.",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
selector: '.et-bracket-journey-eliminated',
|
|
132
|
+
reason:
|
|
133
|
+
"The row that went out is struck through and faded to 0.55 on purpose; its contrast is an open point, not this round's question.",
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
widths: [1040, 1680],
|
|
138
|
+
render: (props: Props) =>
|
|
139
|
+
drawing({
|
|
140
|
+
body: `<div class="t-frame">${render(props)}</div>`,
|
|
141
|
+
styles: [bracketStyles, frame].join('\n'),
|
|
142
|
+
}),
|
|
143
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.breadcrumb-frame {
|
|
2
|
+
padding: 2.4rem 1.6rem;
|
|
3
|
+
background: var(--et-surface-background-solid);
|
|
4
|
+
color: var(--et-surface-color-solid);
|
|
5
|
+
font-size: 1.4rem;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.breadcrumb-overflow-stage {
|
|
9
|
+
display: grid;
|
|
10
|
+
gap: 1rem;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.breadcrumb-overflow-stage > .et-breadcrumb-mock-pane {
|
|
14
|
+
margin-inline-start: 4.2rem;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@layer components {
|
|
18
|
+
.et-breadcrumb-item:where(a, button):focus-visible {
|
|
19
|
+
outline-color: var(--et-theme-color-ink-solid);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.et-breadcrumb-overflow-trigger {
|
|
23
|
+
--_et-focus-ring-color: var(--et-theme-color-ink-solid);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
breadcrumb,
|
|
3
|
+
BreadcrumbMockCrumb,
|
|
4
|
+
breadcrumbOverflowPanel,
|
|
5
|
+
breadcrumbStyles,
|
|
6
|
+
} from '@ethlete/components/design/breadcrumb';
|
|
7
|
+
import { definePart, drawing } from '@design-system';
|
|
8
|
+
import frame from './breadcrumb.css?inline';
|
|
9
|
+
|
|
10
|
+
type Interaction = 'hover' | 'active' | 'focus-visible';
|
|
11
|
+
|
|
12
|
+
type Props = {
|
|
13
|
+
long?: boolean;
|
|
14
|
+
open?: boolean;
|
|
15
|
+
link?: Interaction;
|
|
16
|
+
current?: Interaction;
|
|
17
|
+
overflow?: Interaction;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const mark = (control: string, interaction?: Interaction) =>
|
|
21
|
+
[`data-et-control="${control}"`, interaction && `data-et-force="${interaction}"`].filter(Boolean).join(' ');
|
|
22
|
+
|
|
23
|
+
const shortTrail = (props: Props): BreadcrumbMockCrumb[] => [
|
|
24
|
+
{ label: 'Home', href: '#' },
|
|
25
|
+
{ label: 'Competitions', href: '#', attributes: mark('link', props.link) },
|
|
26
|
+
{ label: 'Regionalliga Nordost', attributes: mark('current', props.current) },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const longTrail = (props: Props): BreadcrumbMockCrumb[] => [
|
|
30
|
+
{ label: 'Home', href: '#' },
|
|
31
|
+
{ label: 'Competitions', href: '#' },
|
|
32
|
+
{ label: 'Regionalliga Nordost', href: '#' },
|
|
33
|
+
{ label: 'Season 2026/27', href: '#' },
|
|
34
|
+
{ label: 'Matchday 14', href: '#' },
|
|
35
|
+
{ label: 'Chemie Leipzig vs. Lokomotive Leipzig', attributes: mark('current', props.current) },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const hiddenCrumbs = [1, 2, 3, 4];
|
|
39
|
+
|
|
40
|
+
const longMarkup = (props: Props) => {
|
|
41
|
+
const trail = longTrail(props);
|
|
42
|
+
const panel = props.open
|
|
43
|
+
? breadcrumbOverflowPanel(hiddenCrumbs.map((index) => trail[index]!)).replace(
|
|
44
|
+
'class="et-overlay ',
|
|
45
|
+
'data-et-surface="overflow.body" class="et-overlay ',
|
|
46
|
+
)
|
|
47
|
+
: '';
|
|
48
|
+
|
|
49
|
+
return `<div class="breadcrumb-overflow-stage">${panel}${breadcrumb({
|
|
50
|
+
crumbs: trail,
|
|
51
|
+
collapsed: hiddenCrumbs,
|
|
52
|
+
overflowAttributes: mark('overflow', props.overflow),
|
|
53
|
+
})}</div>`;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default definePart({
|
|
57
|
+
note: 'Decided rules only',
|
|
58
|
+
madeBy: ['claude-opus-5-5'],
|
|
59
|
+
states: {
|
|
60
|
+
default: {},
|
|
61
|
+
'link-hover': { link: 'hover' },
|
|
62
|
+
'link-active': { link: 'active' },
|
|
63
|
+
'link-focus-visible': { link: 'focus-visible' },
|
|
64
|
+
'current-page': { current: 'hover' },
|
|
65
|
+
long: { long: true },
|
|
66
|
+
'overflow-hover': { long: true, overflow: 'hover' },
|
|
67
|
+
'overflow-active': { long: true, overflow: 'active' },
|
|
68
|
+
'overflow-focus-visible': { long: true, overflow: 'focus-visible' },
|
|
69
|
+
'overflow-open': { long: true, open: true },
|
|
70
|
+
},
|
|
71
|
+
widths: [480],
|
|
72
|
+
controls: {
|
|
73
|
+
link: { kind: 'button', input: ['pointer', 'keyboard'] },
|
|
74
|
+
overflow: { kind: 'disclosure', input: ['pointer', 'keyboard'], surfaces: { body: 'overflow-open' } },
|
|
75
|
+
},
|
|
76
|
+
render: (props: Props) =>
|
|
77
|
+
drawing({
|
|
78
|
+
body: `<div class="breadcrumb-frame">${props.long ? longMarkup(props) : breadcrumb({ crumbs: shortTrail(props) })}</div>`,
|
|
79
|
+
styles: [breadcrumbStyles, frame].join('\n'),
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.et-button {
|
|
3
|
+
--_et-focus-ring-color: light-dark(var(--et-theme-color-ink-solid), var(--et-theme-color-primary-solid));
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.et-icon-button:where([data-variant='filled']) {
|
|
7
|
+
--et-button-shadow: inset 0 0 0 1px light-dark(var(--et-theme-color-ink-solid), transparent);
|
|
8
|
+
|
|
9
|
+
@media (hover: hover) {
|
|
10
|
+
&:hover {
|
|
11
|
+
--et-theme-color-primary-rgb: var(--et-color-primary-active);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
&:active {
|
|
16
|
+
--_et-button-background: color-mix(
|
|
17
|
+
in srgb,
|
|
18
|
+
var(--et-theme-color-primary-solid) 84%,
|
|
19
|
+
var(--et-theme-color-on-primary-solid)
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.et-icon-button:where([data-variant='filled']) {
|
|
25
|
+
box-shadow: var(--et-button-shadow, none);
|
|
26
|
+
|
|
27
|
+
@media (hover: hover) {
|
|
28
|
+
&:hover {
|
|
29
|
+
--et-theme-color-primary-opacity: 1;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -1,17 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
button,
|
|
3
|
+
buttonStyles,
|
|
4
|
+
ButtonMockOptions,
|
|
5
|
+
iconButton,
|
|
6
|
+
IconButtonMockOptions,
|
|
7
|
+
iconButtonStyles,
|
|
8
|
+
} from '@ethlete/components/design/button';
|
|
9
|
+
import { iconMarkup } from '@ethlete/components/design/icon';
|
|
10
|
+
import { SDK_ICONS } from '@ethlete/components/design/icon-set';
|
|
2
11
|
import { definePart, drawing, forceInteraction, interactionRows } from '@design-system';
|
|
12
|
+
import hover from './button.css?inline';
|
|
3
13
|
|
|
4
14
|
type ButtonInteraction = 'hover' | 'active' | 'focus-visible';
|
|
5
15
|
|
|
6
|
-
type
|
|
16
|
+
type IconOnly = IconButtonMockOptions & { iconOnly: true };
|
|
17
|
+
|
|
18
|
+
type Props = (ButtonMockOptions | IconOnly) & { interaction?: ButtonInteraction };
|
|
7
19
|
|
|
8
20
|
const control = ({ interaction, ...options }: Props) => {
|
|
9
|
-
const
|
|
21
|
+
const drawn = 'iconOnly' in options ? iconButton(options) : button(options);
|
|
22
|
+
const markup = drawn.replace(/^<(button|a) /, '<$1 data-et-control="button" data-can-animate ');
|
|
10
23
|
|
|
11
24
|
return interaction ? forceInteraction(markup, interaction) : markup;
|
|
12
25
|
};
|
|
13
26
|
|
|
14
|
-
|
|
27
|
+
const plus = iconMarkup(SDK_ICONS.find((each) => each.name === 'et-plus')!);
|
|
28
|
+
const SIZES = ['xs', 'sm', 'md', 'lg', 'xl'] as const;
|
|
29
|
+
const VARIANTS = ['filled', 'outline', 'tonal', 'transparent'] as const;
|
|
30
|
+
type Size = (typeof SIZES)[number];
|
|
31
|
+
|
|
32
|
+
const interactive = interactionRows<
|
|
15
33
|
Props,
|
|
16
34
|
'filled' | 'outline' | 'tonal' | 'transparent' | 'pressed' | 'danger' | 'small' | 'disabled',
|
|
17
35
|
ButtonInteraction
|
|
@@ -29,11 +47,36 @@ export const { states, grid } = interactionRows<
|
|
|
29
47
|
{ interactions: ['hover', 'active', 'focus-visible'], force: (props, interaction) => ({ ...props, interaction }) },
|
|
30
48
|
);
|
|
31
49
|
|
|
50
|
+
const iconOnly = interactionRows<Props, `icon-${(typeof VARIANTS)[number]}`, ButtonInteraction>(
|
|
51
|
+
Object.fromEntries(
|
|
52
|
+
VARIANTS.map((variant) => [`icon-${variant}`, { iconOnly: true, icon: plus, ariaLabel: 'Add', variant }]),
|
|
53
|
+
) as Record<`icon-${(typeof VARIANTS)[number]}`, Props>,
|
|
54
|
+
{ interactions: ['hover', 'active', 'focus-visible'], force: (props, interaction) => ({ ...props, interaction }) },
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const sizeStates = Object.fromEntries(
|
|
58
|
+
SIZES.map((size) => [`size-${size}`, { label: 'Add', icon: plus, variant: 'filled', size }]),
|
|
59
|
+
) as Record<`size-${Size}`, Props>;
|
|
60
|
+
|
|
61
|
+
export const states = { ...interactive.states, ...iconOnly.states, ...sizeStates };
|
|
62
|
+
export const grid = {
|
|
63
|
+
columns: interactive.grid.columns,
|
|
64
|
+
rows: {
|
|
65
|
+
...interactive.grid.rows,
|
|
66
|
+
...Object.fromEntries(VARIANTS.map((variant) => [`icon ${variant}`, iconOnly.grid.rows[`icon-${variant}`]])),
|
|
67
|
+
...Object.fromEntries(SIZES.map((size) => [`size ${size}`, { rest: `size-${size}` as const }])),
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
32
71
|
export default definePart({
|
|
33
|
-
note: '
|
|
34
|
-
madeBy: ['claude-opus-5-5'],
|
|
72
|
+
note: 'scale of the decided shape, Q182, lead merge',
|
|
73
|
+
madeBy: ['claude-opus-5-5', 'claude-sonnet-5-5'],
|
|
35
74
|
states,
|
|
36
75
|
grid,
|
|
37
76
|
controls: { button: { kind: 'button', input: ['pointer', 'keyboard'], disabled: true } },
|
|
38
|
-
render: (props: Props) =>
|
|
77
|
+
render: (props: Props) =>
|
|
78
|
+
drawing({
|
|
79
|
+
body: control(props),
|
|
80
|
+
styles: ['iconOnly' in props ? iconButtonStyles : buttonStyles, hover].join('\n'),
|
|
81
|
+
}),
|
|
39
82
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
.t-frame {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
padding: 1.6rem;
|
|
4
|
+
background: var(--et-surface-background-solid);
|
|
5
|
+
color: var(--et-surface-color-solid);
|
|
6
|
+
font-size: 1.4rem;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
@layer components {
|
|
10
|
+
.et-calendar .et-calendar-nav-button {
|
|
11
|
+
--_et-focus-ring-color: var(--et-theme-color-ink-solid);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.et-calendar .et-calendar-header-label:focus-visible,
|
|
15
|
+
.et-calendar .et-calendar-cell:focus-visible .et-calendar-cell-content {
|
|
16
|
+
outline-color: var(--et-theme-color-ink-solid);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.et-calendar .et-calendar-cell::before {
|
|
20
|
+
background: light-dark(
|
|
21
|
+
color-mix(in srgb, var(--et-theme-color-primary-solid) 32%, transparent),
|
|
22
|
+
color-mix(in srgb, var(--et-theme-color-primary-solid) 12%, transparent)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.et-calendar
|
|
27
|
+
.et-calendar-cell:where([data-selected], [data-band='start'], [data-band='end']):where(
|
|
28
|
+
:not([data-disabled], [data-today])
|
|
29
|
+
)
|
|
30
|
+
.et-calendar-cell-content {
|
|
31
|
+
box-shadow: inset 0 0 0 1px var(--et-theme-color-ink-solid);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { calendar, calendarStyles, CalendarMockOptions } from '@ethlete/components/design/calendar';
|
|
2
|
+
import { definePart, drawing } from '@design-system';
|
|
3
|
+
import frame from './calendar.css?inline';
|
|
4
|
+
|
|
5
|
+
type Interaction = 'hover' | 'focus-visible';
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
view?: CalendarMockOptions['view'];
|
|
9
|
+
selected?: boolean;
|
|
10
|
+
range?: boolean;
|
|
11
|
+
day?: Interaction;
|
|
12
|
+
selectedDay?: Interaction;
|
|
13
|
+
rangeDay?: Interaction;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
limits?: boolean;
|
|
16
|
+
weekNumbers?: boolean;
|
|
17
|
+
comparison?: boolean;
|
|
18
|
+
nav?: Interaction;
|
|
19
|
+
label?: Interaction;
|
|
20
|
+
compact?: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const TODAY = '2026-09-14';
|
|
24
|
+
|
|
25
|
+
const force = (interaction: Interaction | undefined, key: string) =>
|
|
26
|
+
interaction ? { [key]: `data-et-force="${interaction}"` } : {};
|
|
27
|
+
|
|
28
|
+
const render = (props: Props) => {
|
|
29
|
+
const view = props.view ?? 'month';
|
|
30
|
+
const coarseKey = view === 'year' ? '2026-03' : '2021';
|
|
31
|
+
|
|
32
|
+
return calendar({
|
|
33
|
+
view,
|
|
34
|
+
month: '2026-09',
|
|
35
|
+
today: TODAY,
|
|
36
|
+
value: props.selected
|
|
37
|
+
? view === 'year'
|
|
38
|
+
? '2026-05-20'
|
|
39
|
+
: view === 'multiYear'
|
|
40
|
+
? '2024-05-20'
|
|
41
|
+
: '2026-09-17'
|
|
42
|
+
: undefined,
|
|
43
|
+
range: props.range
|
|
44
|
+
? { start: '2026-09-09', end: '2026-09-16' }
|
|
45
|
+
: props.comparison
|
|
46
|
+
? { start: '2026-09-15', end: '2026-09-18' }
|
|
47
|
+
: undefined,
|
|
48
|
+
comparison: props.comparison ? { start: '2026-09-07', end: '2026-09-11' } : undefined,
|
|
49
|
+
min: props.limits ? '2026-09-07' : undefined,
|
|
50
|
+
max: props.limits ? '2026-09-24' : undefined,
|
|
51
|
+
disabled: props.disabled ? ['2026-09-05', '2026-09-06', '2026-09-26', '2026-09-27'] : undefined,
|
|
52
|
+
weekNumbers: props.weekNumbers,
|
|
53
|
+
cellAttributes: {
|
|
54
|
+
...force(props.day, view === 'month' ? '2026-09-22' : coarseKey),
|
|
55
|
+
...force(props.selectedDay, '2026-09-17'),
|
|
56
|
+
...force(props.rangeDay, '2026-09-09'),
|
|
57
|
+
},
|
|
58
|
+
nextAttributes: props.nav ? `data-et-force="${props.nav}"` : undefined,
|
|
59
|
+
labelAttributes: props.label ? `data-et-force="${props.label}"` : undefined,
|
|
60
|
+
attributes: props.compact ? 'style="--et-calendar-cell-size: 32px"' : undefined,
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default definePart({
|
|
65
|
+
note: 'Decided rules only',
|
|
66
|
+
madeBy: ['claude-opus-5-5'],
|
|
67
|
+
states: {
|
|
68
|
+
rest: {},
|
|
69
|
+
selected: { selected: true },
|
|
70
|
+
'selected-hover': { selected: true, selectedDay: 'hover' },
|
|
71
|
+
'selected-focus-visible': { selected: true, selectedDay: 'focus-visible' },
|
|
72
|
+
range: { range: true },
|
|
73
|
+
'range-focus-visible': { range: true, rangeDay: 'focus-visible' },
|
|
74
|
+
'day-hover': { day: 'hover' },
|
|
75
|
+
'day-focus-visible': { day: 'focus-visible' },
|
|
76
|
+
disabled: { disabled: true },
|
|
77
|
+
limits: { limits: true, selected: true },
|
|
78
|
+
'week-numbers': { weekNumbers: true, selected: true },
|
|
79
|
+
comparison: { comparison: true },
|
|
80
|
+
'year-view': { view: 'year', selected: true },
|
|
81
|
+
'year-view-hover': { view: 'year', selected: true, day: 'hover' },
|
|
82
|
+
'year-view-focus-visible': { view: 'year', selected: true, day: 'focus-visible' },
|
|
83
|
+
'multi-year-view': { view: 'multiYear', selected: true },
|
|
84
|
+
'nav-hover': { nav: 'hover' },
|
|
85
|
+
'nav-focus-visible': { nav: 'focus-visible' },
|
|
86
|
+
'label-hover': { label: 'hover' },
|
|
87
|
+
'label-focus-visible': { label: 'focus-visible' },
|
|
88
|
+
'long-label': { compact: true, selected: true },
|
|
89
|
+
},
|
|
90
|
+
widths: [480],
|
|
91
|
+
render: (props: Props) =>
|
|
92
|
+
drawing({
|
|
93
|
+
body: `<div class="t-frame">${render(props)}</div>`,
|
|
94
|
+
styles: [calendarStyles, frame].join('\n'),
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.card-frame {
|
|
2
|
+
padding: 2.4rem 1.6rem;
|
|
3
|
+
background: var(--et-surface-background-solid);
|
|
4
|
+
color: var(--et-surface-color-solid);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.card-frame .et-card {
|
|
8
|
+
max-inline-size: 32rem;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@layer components {
|
|
12
|
+
.et-card .et-focus-ring {
|
|
13
|
+
--_et-focus-ring-color: var(--et-theme-color-ink-solid);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.et-card .et-button[data-variant='filled'] {
|
|
17
|
+
--et-button-shadow: inset 0 0 0 1px var(--et-theme-color-ink-solid);
|
|
18
|
+
}
|
|
19
|
+
}
|