@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,457 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BRACKET_DATA_LAYOUT,
|
|
3
|
+
BracketComponents,
|
|
4
|
+
BracketDataSource,
|
|
5
|
+
BracketDrawing,
|
|
6
|
+
BracketMatchComponent,
|
|
7
|
+
BracketRoundHeaderComponent,
|
|
8
|
+
BracketContinueComponent,
|
|
9
|
+
BracketRoundType,
|
|
10
|
+
COMMON_BRACKET_ROUND_TYPE,
|
|
11
|
+
ComputedBracketGrid,
|
|
12
|
+
createBracket,
|
|
13
|
+
createDoubleEliminationGrid,
|
|
14
|
+
createSingleEliminationGrid,
|
|
15
|
+
drawMan,
|
|
16
|
+
FinalizedBracketElement,
|
|
17
|
+
SINGLE_ELIMINATION_BRACKET_ROUND_TYPE,
|
|
18
|
+
TOURNAMENT_MODE,
|
|
19
|
+
} from '@ethlete/bracket';
|
|
20
|
+
import continueBase from '../src/lib/bracket/bracket-default-continue.component.css?inline';
|
|
21
|
+
import finalMatchBase from '../src/lib/bracket/bracket-default-final-match.component.css?inline';
|
|
22
|
+
import defaultMatchBase from '../src/lib/bracket/bracket-default-match.component.css?inline';
|
|
23
|
+
import roundHeaderBase from '../src/lib/bracket/bracket-default-round-header.component.css?inline';
|
|
24
|
+
import participantsBase from '../src/lib/bracket/bracket-participants.component.css?inline';
|
|
25
|
+
import bracketBase from '../src/lib/bracket/bracket.component.css?inline';
|
|
26
|
+
import { ARROW_RIGHT_ICON } from '../src/lib/icon/headless/arrow-right-icon';
|
|
27
|
+
import iconBase from '../src/lib/icon/headless/icon-styles.component.css?inline';
|
|
28
|
+
import { TROPHY_ICON } from '../src/lib/icon/headless/trophy-icon';
|
|
29
|
+
import { button, buttonStyles } from './button';
|
|
30
|
+
import { iconMarkup } from './icon';
|
|
31
|
+
import { matchCard, MatchMockParticipant, matchStyles } from './match';
|
|
32
|
+
|
|
33
|
+
export type BracketMockParticipant = MatchMockParticipant & {
|
|
34
|
+
/** The id the source names the participant by; the journey highlight and the legend key on it. */
|
|
35
|
+
id: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type BracketMockMatch = {
|
|
39
|
+
/** Left out, `<round id>-m<index>`. */
|
|
40
|
+
id?: string;
|
|
41
|
+
/** Left out, the side is still to be decided. */
|
|
42
|
+
home?: BracketMockParticipant;
|
|
43
|
+
away?: BracketMockParticipant;
|
|
44
|
+
/** Left out, `finished` when the match has a winner, `scheduled` otherwise. */
|
|
45
|
+
status?: 'scheduled' | 'live' | 'finished';
|
|
46
|
+
homeScore?: number | string;
|
|
47
|
+
awayScore?: number | string;
|
|
48
|
+
winnerSide?: 'home' | 'away';
|
|
49
|
+
/** The kick-off as the card formats it, e.g. `05/03/2026 1:00 AM`. */
|
|
50
|
+
startTime?: string;
|
|
51
|
+
label?: string;
|
|
52
|
+
/** A design marker on the match card, e.g. `hover` or `focus-visible`. */
|
|
53
|
+
force?: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type BracketMockRound = {
|
|
57
|
+
/** Left out, `r<index>`. */
|
|
58
|
+
id?: string;
|
|
59
|
+
name: string;
|
|
60
|
+
/** Left out, the last round of a single elimination source is the final and every other round a bracket round. */
|
|
61
|
+
type?: BracketRoundType;
|
|
62
|
+
matches: readonly BracketMockMatch[];
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** The layout settings of `et-bracket` that move cells or connectors; left out, each takes the lib default or the density preset. */
|
|
66
|
+
export type BracketMockSettings = {
|
|
67
|
+
density?: 'default' | 'compact';
|
|
68
|
+
columnWidth?: number;
|
|
69
|
+
matchHeight?: number;
|
|
70
|
+
finalMatchHeight?: number;
|
|
71
|
+
finalColumnWidth?: number;
|
|
72
|
+
roundHeaderHeight?: number;
|
|
73
|
+
roundHeaderGap?: number;
|
|
74
|
+
columnGap?: number;
|
|
75
|
+
rowGap?: number;
|
|
76
|
+
rowRoundGap?: number;
|
|
77
|
+
rowSpanRoundId?: string | null;
|
|
78
|
+
thirdPlaceTopOffset?: number | null;
|
|
79
|
+
finalRoundHeaderGap?: number | null;
|
|
80
|
+
alignRoundHeaders?: 'start' | 'center';
|
|
81
|
+
lineStartingCurveAmount?: number;
|
|
82
|
+
lineEndingCurveAmount?: number;
|
|
83
|
+
lineWidth?: number;
|
|
84
|
+
lineDashArray?: number;
|
|
85
|
+
lineDashOffset?: number;
|
|
86
|
+
hideRoundHeaders?: boolean;
|
|
87
|
+
showContinueElement?: boolean;
|
|
88
|
+
continueColumnWidth?: number;
|
|
89
|
+
continueElementHeight?: number;
|
|
90
|
+
continueLineDashArray?: number;
|
|
91
|
+
roundHeaderLevel?: number;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type BracketMockLabels = {
|
|
95
|
+
roundMatchCount: (matches: number) => string;
|
|
96
|
+
winnersAdvance: (winners: number) => string;
|
|
97
|
+
continueLabel: (winners: number) => string;
|
|
98
|
+
champion: (participant: string) => string;
|
|
99
|
+
championPending: string;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export type BracketMockOptions = {
|
|
103
|
+
mode?: 'single-elimination' | 'double-elimination';
|
|
104
|
+
/** Folds the bracket in half, as the mirrored layouts draw it. */
|
|
105
|
+
mirrored?: boolean;
|
|
106
|
+
rounds: readonly BracketMockRound[];
|
|
107
|
+
settings?: BracketMockSettings;
|
|
108
|
+
/** The participant whose journey is lit, as a hover over their row (`pinned` left out) or as `focusedParticipantId`. */
|
|
109
|
+
journey?: { participantId: string; pinned?: boolean };
|
|
110
|
+
labels?: BracketMockLabels;
|
|
111
|
+
/** Extra attributes on the host, e.g. a style or design markers. */
|
|
112
|
+
attributes?: string;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export type BracketParticipantsMockOptions = {
|
|
116
|
+
participants: readonly BracketMockParticipant[];
|
|
117
|
+
/** The participant whose toggle is pressed. */
|
|
118
|
+
focusedParticipantId?: string;
|
|
119
|
+
ariaLabel?: string;
|
|
120
|
+
/** Extra attributes on the host, e.g. a style or design markers. */
|
|
121
|
+
attributes?: string;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
type Settings = Required<Omit<BracketMockSettings, 'density'>>;
|
|
125
|
+
|
|
126
|
+
const DEFAULTS: Settings = {
|
|
127
|
+
columnWidth: 250,
|
|
128
|
+
matchHeight: 75,
|
|
129
|
+
finalMatchHeight: 200,
|
|
130
|
+
finalColumnWidth: 360,
|
|
131
|
+
roundHeaderHeight: 50,
|
|
132
|
+
roundHeaderGap: 20,
|
|
133
|
+
columnGap: 60,
|
|
134
|
+
rowGap: 30,
|
|
135
|
+
rowRoundGap: 20,
|
|
136
|
+
rowSpanRoundId: null,
|
|
137
|
+
thirdPlaceTopOffset: null,
|
|
138
|
+
finalRoundHeaderGap: null,
|
|
139
|
+
alignRoundHeaders: 'start',
|
|
140
|
+
lineStartingCurveAmount: 10,
|
|
141
|
+
lineEndingCurveAmount: 0,
|
|
142
|
+
lineWidth: 2,
|
|
143
|
+
lineDashArray: 0,
|
|
144
|
+
lineDashOffset: 0,
|
|
145
|
+
hideRoundHeaders: false,
|
|
146
|
+
showContinueElement: false,
|
|
147
|
+
continueColumnWidth: 250,
|
|
148
|
+
continueElementHeight: 75,
|
|
149
|
+
continueLineDashArray: 6,
|
|
150
|
+
roundHeaderLevel: 3,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const COMPACT: Partial<Settings> = {
|
|
154
|
+
columnWidth: 140,
|
|
155
|
+
matchHeight: 52,
|
|
156
|
+
finalColumnWidth: 200,
|
|
157
|
+
finalMatchHeight: 132,
|
|
158
|
+
roundHeaderHeight: 34,
|
|
159
|
+
roundHeaderGap: 12,
|
|
160
|
+
columnGap: 32,
|
|
161
|
+
rowGap: 16,
|
|
162
|
+
rowRoundGap: 12,
|
|
163
|
+
lineStartingCurveAmount: 6,
|
|
164
|
+
continueColumnWidth: 140,
|
|
165
|
+
continueElementHeight: 52,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const DEFAULT_LABELS: BracketMockLabels = {
|
|
169
|
+
roundMatchCount: (matches) => `${matches} ${matches === 1 ? 'match' : 'matches'}`,
|
|
170
|
+
winnersAdvance: (winners) => `${winners} ${winners === 1 ? 'winner advances' : 'winners advance'}`,
|
|
171
|
+
continueLabel: (winners) => `${winners} ${winners === 1 ? 'winner advances' : 'winners advance'} to the next stage`,
|
|
172
|
+
champion: (participant) => `Champion: ${participant}`,
|
|
173
|
+
championPending: 'Champion not decided yet',
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/** The stylesheets the bracket mocks draw with: the real bracket, default card, match card, icon and button CSS. */
|
|
177
|
+
export const bracketStyles = /* @__PURE__ */ [
|
|
178
|
+
buttonStyles,
|
|
179
|
+
iconBase,
|
|
180
|
+
matchStyles,
|
|
181
|
+
bracketBase,
|
|
182
|
+
roundHeaderBase,
|
|
183
|
+
defaultMatchBase,
|
|
184
|
+
finalMatchBase,
|
|
185
|
+
continueBase,
|
|
186
|
+
participantsBase,
|
|
187
|
+
].join('\n');
|
|
188
|
+
|
|
189
|
+
const ROUND_HEADER = class {} as unknown as BracketRoundHeaderComponent<null, BracketMockMatch>;
|
|
190
|
+
const MATCH = class {} as unknown as BracketMatchComponent<null, BracketMockMatch>;
|
|
191
|
+
const FINAL_MATCH = class {} as unknown as BracketMatchComponent<null, BracketMockMatch>;
|
|
192
|
+
const CONTINUE = class {} as unknown as BracketContinueComponent<null, BracketMockMatch>;
|
|
193
|
+
|
|
194
|
+
const COMPONENTS: BracketComponents<null, BracketMockMatch> = {
|
|
195
|
+
roundHeader: ROUND_HEADER,
|
|
196
|
+
match: MATCH,
|
|
197
|
+
finalMatch: FINAL_MATCH,
|
|
198
|
+
continue: CONTINUE,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const settingsOf = (settings: BracketMockSettings = {}): Settings => {
|
|
202
|
+
const preset = settings.density === 'compact' ? COMPACT : {};
|
|
203
|
+
const resolved = { ...DEFAULTS, ...preset } as Settings;
|
|
204
|
+
|
|
205
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
206
|
+
if (key !== 'density' && value !== undefined) (resolved as Record<string, unknown>)[key] = value;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return resolved;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const sourceOf = (options: BracketMockOptions): BracketDataSource<null, BracketMockMatch> => {
|
|
213
|
+
const mode = options.mode ?? TOURNAMENT_MODE.SINGLE_ELIMINATION;
|
|
214
|
+
const last = options.rounds.length - 1;
|
|
215
|
+
|
|
216
|
+
const rounds = options.rounds.map((round, index) => ({
|
|
217
|
+
id: round.id ?? `r${index}`,
|
|
218
|
+
name: round.name,
|
|
219
|
+
data: null,
|
|
220
|
+
type:
|
|
221
|
+
round.type ??
|
|
222
|
+
(index === last
|
|
223
|
+
? COMMON_BRACKET_ROUND_TYPE.FINAL
|
|
224
|
+
: SINGLE_ELIMINATION_BRACKET_ROUND_TYPE.SINGLE_ELIMINATION_BRACKET),
|
|
225
|
+
}));
|
|
226
|
+
|
|
227
|
+
const matches = options.rounds.flatMap((round, roundIndex) =>
|
|
228
|
+
round.matches.map((match, index) => {
|
|
229
|
+
const roundId = rounds[roundIndex]?.id ?? '';
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
id: match.id ?? `${roundId}-m${index}`,
|
|
233
|
+
roundId,
|
|
234
|
+
data: match,
|
|
235
|
+
home: match.home?.id ?? null,
|
|
236
|
+
away: match.away?.id ?? null,
|
|
237
|
+
winner: match.winnerSide ?? null,
|
|
238
|
+
status: (match.winnerSide ? 'completed' : 'pending') as 'completed' | 'pending',
|
|
239
|
+
};
|
|
240
|
+
}),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
return { mode, rounds, matches };
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const px = (value: number) => `${value}px`;
|
|
247
|
+
|
|
248
|
+
const styled = (css: string) => ` style="${css}"`;
|
|
249
|
+
|
|
250
|
+
const drawingOf = (grid: ComputedBracketGrid<null, BracketMockMatch>, settings: Settings): BracketDrawing =>
|
|
251
|
+
drawMan({
|
|
252
|
+
columnGap: settings.columnGap,
|
|
253
|
+
upperLowerGap: settings.rowRoundGap,
|
|
254
|
+
columnWidth: settings.columnWidth,
|
|
255
|
+
matchHeight: settings.matchHeight,
|
|
256
|
+
roundHeaderHeight: settings.hideRoundHeaders ? 0 : settings.roundHeaderHeight,
|
|
257
|
+
rowGap: settings.rowGap,
|
|
258
|
+
bracketGrid: grid,
|
|
259
|
+
curve: {
|
|
260
|
+
lineEndingCurveAmount: settings.lineEndingCurveAmount,
|
|
261
|
+
lineStartingCurveAmount: settings.lineStartingCurveAmount,
|
|
262
|
+
},
|
|
263
|
+
path: { dashArray: settings.lineDashArray, dashOffset: settings.lineDashOffset, width: settings.lineWidth },
|
|
264
|
+
continuePath: {
|
|
265
|
+
dashArray: settings.continueLineDashArray,
|
|
266
|
+
dashOffset: settings.lineDashOffset,
|
|
267
|
+
width: settings.lineWidth,
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const svgOf = (drawing: BracketDrawing) => {
|
|
272
|
+
const gradients = drawing.gradients.length
|
|
273
|
+
? `<defs>${drawing.gradients
|
|
274
|
+
.map(
|
|
275
|
+
(gradient) =>
|
|
276
|
+
`<linearGradient id="${gradient.id}" x1="${gradient.fromX}" x2="${gradient.toX}" gradientUnits="userSpaceOnUse" y1="0" y2="0">${gradient.stops.map((stop) => `<stop offset="${stop.offset}" stop-color="${stop.color}"></stop>`).join('')}</linearGradient>`,
|
|
277
|
+
)
|
|
278
|
+
.join('')}</defs>`
|
|
279
|
+
: '';
|
|
280
|
+
const rects = drawing.rects
|
|
281
|
+
.map(
|
|
282
|
+
(rect) =>
|
|
283
|
+
`<rect fill="none" x="${rect.x}" y="${rect.y}" width="${rect.width}" height="${rect.height}" rx="${rect.radius}" stroke="${rect.stroke}" stroke-width="${rect.strokeWidth}" class="${rect.cssClass}"></rect>`,
|
|
284
|
+
)
|
|
285
|
+
.join('');
|
|
286
|
+
const edges = drawing.edges
|
|
287
|
+
.map(
|
|
288
|
+
(edge) =>
|
|
289
|
+
`<path fill="none" d="${edge.d}" stroke="${edge.stroke}" stroke-width="${edge.strokeWidth}" stroke-dasharray="${edge.dashArray}" stroke-dashoffset="${edge.dashOffset}" class="${edge.cssClass}"${styled(`d: ${edge.cssPath.replaceAll('"', '"')};`)}></path>`,
|
|
290
|
+
)
|
|
291
|
+
.join('');
|
|
292
|
+
|
|
293
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="et-bracket-svg">${gradients}${rects}${edges}</svg>`;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
type Journey = { shortId: string; eliminatedAtMatchId: string | null; participantId: string };
|
|
297
|
+
|
|
298
|
+
const journeyOf = (bracket: ReturnType<typeof createBracket<null, BracketMockMatch>>, participantId: string) => {
|
|
299
|
+
const participant = Array.from(bracket.participants.values()).find((candidate) => candidate.id === participantId);
|
|
300
|
+
|
|
301
|
+
if (!participant) return null;
|
|
302
|
+
|
|
303
|
+
const matches = Array.from(participant.matches.values());
|
|
304
|
+
const lastMatch = matches[matches.length - 1];
|
|
305
|
+
const isOut = !!lastMatch && matches.every((match) => !!match.winner) && lastMatch.winner?.id !== participant.id;
|
|
306
|
+
|
|
307
|
+
return { shortId: participant.shortId, eliminatedAtMatchId: isOut ? lastMatch.id : null, participantId };
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const withJourneyClasses = (classes: string, journey: Journey | null) =>
|
|
311
|
+
journey && classes.split(' ').includes(journey.shortId) ? `${classes} et-bracket-journey-active` : classes;
|
|
312
|
+
|
|
313
|
+
const matchCellOf = (
|
|
314
|
+
element: Extract<FinalizedBracketElement<null, BracketMockMatch>, { type: 'match' }>,
|
|
315
|
+
context: { labels: BracketMockLabels; journey: Journey | null },
|
|
316
|
+
) => {
|
|
317
|
+
const data = element.match.data;
|
|
318
|
+
const eliminatedSide =
|
|
319
|
+
context.journey?.eliminatedAtMatchId === element.match.id
|
|
320
|
+
? (['home', 'away'] as const).find((side) => data[side]?.id === context.journey?.participantId)
|
|
321
|
+
: undefined;
|
|
322
|
+
const marked = (html: string) =>
|
|
323
|
+
eliminatedSide
|
|
324
|
+
? html.replace(
|
|
325
|
+
`class="et-match-card-side" data-side="${eliminatedSide}"`,
|
|
326
|
+
`class="et-match-card-side et-bracket-journey-eliminated" data-side="${eliminatedSide}"`,
|
|
327
|
+
)
|
|
328
|
+
: html;
|
|
329
|
+
const card = {
|
|
330
|
+
home: data.home,
|
|
331
|
+
away: data.away,
|
|
332
|
+
status: data.status ?? (data.winnerSide ? 'finished' : 'scheduled'),
|
|
333
|
+
homeScore: data.homeScore,
|
|
334
|
+
awayScore: data.awayScore,
|
|
335
|
+
winnerSide: data.winnerSide,
|
|
336
|
+
startTime: data.startTime,
|
|
337
|
+
label: data.label,
|
|
338
|
+
force: data.force,
|
|
339
|
+
} as const;
|
|
340
|
+
|
|
341
|
+
if (element.component === FINAL_MATCH) {
|
|
342
|
+
const winner = data.winnerSide ? data[data.winnerSide] : undefined;
|
|
343
|
+
const champion = winner ? context.labels.champion(winner.name) : context.labels.championPending;
|
|
344
|
+
|
|
345
|
+
return `<et-bracket-default-final-match class="et-bracket-final-host"${winner ? ' data-decided=""' : ''}><div class="et-bracket-final-header">${iconMarkup(TROPHY_ICON, 'et-bracket-final-trophy')}<span class="et-bracket-final-round">${element.round.name}</span></div>${marked(matchCard({ ...card, className: 'et-bracket-final-card' }))}<p class="et-bracket-final-champion">${champion}</p></et-bracket-default-final-match>`;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return `<et-bracket-default-match class="et-bracket-default-match-host">${marked(matchCard({ ...card, size: 'compact', className: 'et-bracket-default-match-card', attributes: 'size="compact"' }))}</et-bracket-default-match>`;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
/** Draws `et-bracket` with its round headers, default match and final cards, continue cells and connectors, as HTML; the cell positions and connector paths come from `@ethlete/bracket`, as in the lib. */
|
|
352
|
+
export const bracket = (options: BracketMockOptions) => {
|
|
353
|
+
const settings = settingsOf(options.settings);
|
|
354
|
+
const labels = options.labels ?? DEFAULT_LABELS;
|
|
355
|
+
const mode = options.mode ?? TOURNAMENT_MODE.SINGLE_ELIMINATION;
|
|
356
|
+
const layout = options.mirrored ? BRACKET_DATA_LAYOUT.MIRRORED : BRACKET_DATA_LAYOUT.LEFT_TO_RIGHT;
|
|
357
|
+
const data = createBracket(sourceOf(options), { layout });
|
|
358
|
+
const createGrid =
|
|
359
|
+
mode === TOURNAMENT_MODE.DOUBLE_ELIMINATION ? createDoubleEliminationGrid : createSingleEliminationGrid;
|
|
360
|
+
|
|
361
|
+
const grid = createGrid(
|
|
362
|
+
data,
|
|
363
|
+
{
|
|
364
|
+
includeRoundHeaders: !settings.hideRoundHeaders,
|
|
365
|
+
columnGap: settings.columnGap,
|
|
366
|
+
rowRoundGap: settings.rowRoundGap,
|
|
367
|
+
columnWidth: settings.columnWidth,
|
|
368
|
+
matchHeight: settings.matchHeight,
|
|
369
|
+
roundHeaderHeight: settings.hideRoundHeaders ? 0 : settings.roundHeaderHeight,
|
|
370
|
+
rowGap: settings.rowGap,
|
|
371
|
+
rowSpanRoundId: settings.rowSpanRoundId,
|
|
372
|
+
layout,
|
|
373
|
+
finalMatchHeight: settings.finalMatchHeight,
|
|
374
|
+
finalColumnWidth: settings.finalColumnWidth,
|
|
375
|
+
roundHeaderGap: settings.hideRoundHeaders ? 0 : settings.roundHeaderGap,
|
|
376
|
+
finalRoundHeaderGap: settings.hideRoundHeaders ? null : settings.finalRoundHeaderGap,
|
|
377
|
+
thirdPlaceTopOffset: settings.thirdPlaceTopOffset,
|
|
378
|
+
swissGroupPadding: 10,
|
|
379
|
+
swissGroupBorderWidth: settings.lineWidth,
|
|
380
|
+
continueElement:
|
|
381
|
+
settings.showContinueElement && layout === BRACKET_DATA_LAYOUT.LEFT_TO_RIGHT
|
|
382
|
+
? { columnWidth: settings.continueColumnWidth, elementHeight: settings.continueElementHeight }
|
|
383
|
+
: null,
|
|
384
|
+
},
|
|
385
|
+
COMPONENTS,
|
|
386
|
+
);
|
|
387
|
+
const drawing = drawingOf(grid, settings);
|
|
388
|
+
const journey = options.journey ? journeyOf(data, options.journey.participantId) : null;
|
|
389
|
+
|
|
390
|
+
if (journey) {
|
|
391
|
+
for (const edge of drawing.edges) edge.cssClass = withJourneyClasses(edge.cssClass, journey);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const columns = grid.columns
|
|
395
|
+
.map((round) => {
|
|
396
|
+
const elements = round.elements
|
|
397
|
+
.map((element) => {
|
|
398
|
+
const offset = `translate(${px(element.dimensions.left - round.dimensions.left)}, ${px(element.dimensions.top - round.dimensions.top)})`;
|
|
399
|
+
const style = styled(
|
|
400
|
+
`width: ${px(element.dimensions.width)}; height: ${px(element.dimensions.height)}; transform: ${offset};`,
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
if (element.type === 'header') {
|
|
404
|
+
const group = element.roundSwissGroup?.name
|
|
405
|
+
? `<span class="et-bracket-default-round-header-group">${element.roundSwissGroup.name}</span>`
|
|
406
|
+
: '';
|
|
407
|
+
|
|
408
|
+
return `<li class="et-bracket-element et-bracket-element--header" data-align="${settings.alignRoundHeaders}"${style}><et-bracket-default-round-header role="heading" class="et-bracket-default-round-header-host" aria-level="${settings.roundHeaderLevel}"><span class="et-bracket-default-round-header-name">${element.round.name}</span>${group}<span class="et-bracket-default-round-header-count">${labels.roundMatchCount(element.round.matchCount)}</span></et-bracket-default-round-header></li>`;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (element.type === 'continue') {
|
|
412
|
+
const count = element.matches.length;
|
|
413
|
+
|
|
414
|
+
return `<li class="et-bracket-element et-bracket-element--continue"${style}><et-bracket-default-continue class="et-bracket-default-continue-host" role="group" aria-label="${labels.continueLabel(count)}">${iconMarkup(ARROW_RIGHT_ICON, 'et-bracket-default-continue-icon')}<span aria-hidden="true" class="et-bracket-default-continue-text">${labels.winnersAdvance(count)}</span></et-bracket-default-continue></li>`;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const classes = withJourneyClasses(
|
|
418
|
+
`et-bracket-element et-bracket-element--match ${element.classes}`,
|
|
419
|
+
journey,
|
|
420
|
+
);
|
|
421
|
+
const endpoint = journey?.eliminatedAtMatchId === element.match.id ? ' et-bracket-journey-endpoint' : '';
|
|
422
|
+
|
|
423
|
+
return `<li class="${classes}${endpoint}" data-match-id="${element.match.id}"${style}>${matchCellOf(element, { labels, journey })}</li>`;
|
|
424
|
+
})
|
|
425
|
+
.join('');
|
|
426
|
+
|
|
427
|
+
return `<ul class="et-bracket-round"${styled(`width: ${px(round.dimensions.width)}; height: ${px(round.dimensions.height)}; transform: translate(${px(round.dimensions.left)}, ${px(round.dimensions.top)});`)}>${elements}</ul>`;
|
|
428
|
+
})
|
|
429
|
+
.join('');
|
|
430
|
+
|
|
431
|
+
const hostClasses = [
|
|
432
|
+
'et-bracket-host',
|
|
433
|
+
journey && 'et-bracket-host--journey-hover',
|
|
434
|
+
journey && options.journey?.pinned && 'et-bracket-host--journey-focused',
|
|
435
|
+
]
|
|
436
|
+
.filter(Boolean)
|
|
437
|
+
.join(' ');
|
|
438
|
+
|
|
439
|
+
return `<et-bracket class="${hostClasses}" ${options.attributes ?? ''}><section class="et-bracket et-bracket--${data.mode}"${styled(`width: ${px(grid.raw.grid.dimensions.width)}; height: ${px(grid.raw.grid.dimensions.height)}; transform: translateX(0px);`)}>${svgOf(drawing)}${columns}</section></et-bracket>`;
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
/** Draws an `et-bracket-participants` legend with one pin toggle per participant, as HTML. */
|
|
443
|
+
export const bracketParticipants = (options: BracketParticipantsMockOptions) => {
|
|
444
|
+
const toggles = options.participants
|
|
445
|
+
.map((participant) =>
|
|
446
|
+
button({
|
|
447
|
+
label: participant.name,
|
|
448
|
+
size: 'sm',
|
|
449
|
+
variant: 'outline',
|
|
450
|
+
pressed: options.focusedParticipantId === participant.id,
|
|
451
|
+
class: 'et-bracket-participants-toggle',
|
|
452
|
+
}),
|
|
453
|
+
)
|
|
454
|
+
.join('');
|
|
455
|
+
|
|
456
|
+
return `<et-bracket-participants class="et-bracket-participants" role="group" aria-label="${options.ariaLabel ?? 'Participants'}" ${options.attributes ?? ''}>${toggles}</et-bracket-participants>`;
|
|
457
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import breadcrumbOverflowBase from '../src/lib/breadcrumb/breadcrumb-overflow.component.css?inline';
|
|
2
|
+
import breadcrumbBase from '../src/lib/breadcrumb/breadcrumb.component.css?inline';
|
|
3
|
+
import iconButtonBase from '../src/lib/button/icon-button.component.css?inline';
|
|
4
|
+
import { CHEVRON_ICON } from '../src/lib/icon/headless/chevron-icon';
|
|
5
|
+
import { ELLIPSIS_ICON } from '../src/lib/icon/headless/ellipsis-icon';
|
|
6
|
+
import iconBase from '../src/lib/icon/headless/icon-styles.component.css?inline';
|
|
7
|
+
import overlayContainer from '../src/lib/overlay/overlay-container.component.css?inline';
|
|
8
|
+
import floatingTip from '../src/lib/overlay/floating-tip-styles.component.css?inline';
|
|
9
|
+
import overlayArrow from '../src/lib/overlay/strategies/overlay-arrow-styles.component.css?inline';
|
|
10
|
+
import toggletipBase from '../src/lib/toggletip/toggletip.component.css?inline';
|
|
11
|
+
import { buttonStyles } from './button';
|
|
12
|
+
import { iconMarkup } from './icon';
|
|
13
|
+
|
|
14
|
+
export type BreadcrumbMockCrumb = {
|
|
15
|
+
/** The crumb content, as HTML. */
|
|
16
|
+
label: string;
|
|
17
|
+
/** Draws the crumb as a link. Left out, it is a plain `<span>`, as the page you are on. */
|
|
18
|
+
href?: string;
|
|
19
|
+
/** Extra attributes on the crumb, e.g. design markers. */
|
|
20
|
+
attributes?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type BreadcrumbMockOptions = {
|
|
24
|
+
crumbs: BreadcrumbMockCrumb[];
|
|
25
|
+
/**
|
|
26
|
+
* Draws the collapsed trail: the crumbs at these indexes move behind the overflow trigger. First and
|
|
27
|
+
* last must stay, as the component keeps them.
|
|
28
|
+
*/
|
|
29
|
+
collapsed?: number[];
|
|
30
|
+
/** Extra attributes on the overflow trigger, e.g. design markers. */
|
|
31
|
+
overflowAttributes?: string;
|
|
32
|
+
/** Overrides the navigation and overflow labels. */
|
|
33
|
+
labels?: { navigation?: string; overflow?: string };
|
|
34
|
+
/** Extra attributes on the breadcrumb, e.g. design markers. */
|
|
35
|
+
attributes?: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** The stylesheets `breadcrumb()` and `breadcrumbOverflowPanel()` draw with: the real breadcrumb, icon button, toggletip and overlay pane CSS. */
|
|
39
|
+
export const breadcrumbStyles = /* @__PURE__ */ [
|
|
40
|
+
buttonStyles,
|
|
41
|
+
iconBase,
|
|
42
|
+
iconButtonBase,
|
|
43
|
+
overlayContainer,
|
|
44
|
+
overlayArrow,
|
|
45
|
+
floatingTip,
|
|
46
|
+
toggletipBase,
|
|
47
|
+
breadcrumbBase,
|
|
48
|
+
breadcrumbOverflowBase,
|
|
49
|
+
`@layer components {
|
|
50
|
+
.et-breadcrumb-mock-pane {
|
|
51
|
+
--_et-overlay-arrow-pane-border-width: 1px;
|
|
52
|
+
inline-size: max-content;
|
|
53
|
+
}
|
|
54
|
+
.et-breadcrumb-mock-pane > .et-overlay-arrow {
|
|
55
|
+
left: 20px;
|
|
56
|
+
}
|
|
57
|
+
}`,
|
|
58
|
+
].join('\n');
|
|
59
|
+
|
|
60
|
+
const crumb = (item: BreadcrumbMockCrumb, isCurrent: boolean) => {
|
|
61
|
+
const tag = item.href !== undefined ? 'a' : 'span';
|
|
62
|
+
const attributes = [
|
|
63
|
+
item.attributes,
|
|
64
|
+
'etbreadcrumbitem=""',
|
|
65
|
+
'class="et-breadcrumb-item"',
|
|
66
|
+
item.href !== undefined && `href="${item.href}"`,
|
|
67
|
+
isCurrent && 'aria-current="page"',
|
|
68
|
+
].filter(Boolean);
|
|
69
|
+
|
|
70
|
+
return `<${tag} ${attributes.join(' ')}>${item.label}</${tag}>`;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const separator = `<span class="et-breadcrumb-separator" aria-hidden="true">${/* @__PURE__ */ iconMarkup(CHEVRON_ICON, 'et-breadcrumb-chevron')}</span>`;
|
|
74
|
+
|
|
75
|
+
const overflowTrigger = (label: string, attributes?: string) => {
|
|
76
|
+
const classes = [
|
|
77
|
+
'et-surface-interactive',
|
|
78
|
+
'et-color-interactive',
|
|
79
|
+
'et-icon-button',
|
|
80
|
+
'et-focus-ring',
|
|
81
|
+
'et-color--surface',
|
|
82
|
+
'et-breadcrumb-overflow-trigger',
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
return `<et-breadcrumb-overflow class="et-breadcrumb-overflow"><button ${attributes ?? ''} ettoggletiptrigger="" type="button" aria-label="${label}" aria-haspopup="dialog" aria-expanded="false" class="${classes.join(' ')}" data-variant="transparent" data-size="xs" data-can-animate="true"><div class="et-icon-button-icon">${iconMarkup(ELLIPSIS_ICON)}</div></button></et-breadcrumb-overflow>`;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** Draws an `et-breadcrumb` as the component renders it, as HTML: a chevron between crumbs, the last one marked as the current page. */
|
|
89
|
+
export const breadcrumb = (options: BreadcrumbMockOptions) => {
|
|
90
|
+
const { crumbs, collapsed = [] } = options;
|
|
91
|
+
const hidden = new Set(collapsed.filter((index) => index > 0 && index < crumbs.length - 1));
|
|
92
|
+
const overflowLabel = options.labels?.overflow ?? 'Show hidden levels';
|
|
93
|
+
|
|
94
|
+
const slots: string[] = [];
|
|
95
|
+
let overflowDrawn = false;
|
|
96
|
+
|
|
97
|
+
crumbs.forEach((item, index) => {
|
|
98
|
+
if (hidden.has(index)) {
|
|
99
|
+
if (overflowDrawn) return;
|
|
100
|
+
overflowDrawn = true;
|
|
101
|
+
slots.push(
|
|
102
|
+
`<li data-type="overflow" class="et-breadcrumb-slot">${overflowTrigger(overflowLabel, options.overflowAttributes)}`,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
slots.push(`<li data-type="item" class="et-breadcrumb-slot">${crumb(item, index === crumbs.length - 1)}`);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const list = slots.map((slot, index) => `${slot}${index < slots.length - 1 ? separator : ''}</li>`).join('');
|
|
112
|
+
|
|
113
|
+
const hostAttributes = [
|
|
114
|
+
options.attributes,
|
|
115
|
+
'class="et-breadcrumb"',
|
|
116
|
+
'role="navigation"',
|
|
117
|
+
`aria-label="${options.labels?.navigation ?? 'Breadcrumb'}"`,
|
|
118
|
+
hidden.size > 0 && 'data-collapsed=""',
|
|
119
|
+
].filter(Boolean);
|
|
120
|
+
|
|
121
|
+
return `<et-breadcrumb ${hostAttributes.join(' ')}><ol class="et-breadcrumb-list">${list}</ol></et-breadcrumb>`;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Draws the open overflow toggletip with the hidden crumbs, in its overlay pane above the trigger, as HTML.
|
|
126
|
+
* The pane sits in flow with its arrow 20px from the start, so place it over the drawn trigger.
|
|
127
|
+
*/
|
|
128
|
+
export const breadcrumbOverflowPanel = (crumbs: BreadcrumbMockCrumb[], surface?: string) => {
|
|
129
|
+
const surfaceClass = `et-surface--${surface ?? 'inherited'}`;
|
|
130
|
+
const paneClasses = [
|
|
131
|
+
'et-overlay',
|
|
132
|
+
'et-overlay-runtime-pane',
|
|
133
|
+
'et-toggletip-panel',
|
|
134
|
+
'et-overlay--toggletip',
|
|
135
|
+
'et-floating-panel',
|
|
136
|
+
'et-color--inherited',
|
|
137
|
+
surfaceClass,
|
|
138
|
+
'et-with-default-animation',
|
|
139
|
+
'et-animation-enter-done',
|
|
140
|
+
'et-breadcrumb-mock-pane',
|
|
141
|
+
];
|
|
142
|
+
const list = crumbs.map((item) => `<li>${crumb(item, false)}</li>`).join('');
|
|
143
|
+
|
|
144
|
+
return `<div class="${paneClasses.join(' ')}" data-overlay-placement="top-start"><et-toggletip class="et-toggletip et-color--inherited ${surfaceClass}" role="dialog"><div class="et-toggletip__surface"><div class="et-toggletip__content"><ol class="et-breadcrumb-overflow-list" data-toggletip-hug="">${list}</ol></div></div></et-toggletip><div et-floating-arrow class="et-overlay-arrow"></div></div>`;
|
|
145
|
+
};
|
package/design/button.ts
CHANGED
|
@@ -2,6 +2,7 @@ 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 iconButtonBase from '../src/lib/button/icon-button.component.css?inline';
|
|
5
6
|
|
|
6
7
|
export type ButtonMockOptions = {
|
|
7
8
|
/** The label, as HTML. */
|
|
@@ -49,9 +50,57 @@ export const button = (options: ButtonMockOptions) => {
|
|
|
49
50
|
`data-icon-alignment="${iconAlignment}"`,
|
|
50
51
|
`data-variant="${variant}"`,
|
|
51
52
|
`data-size="${size}"`,
|
|
53
|
+
'data-can-animate="true"',
|
|
52
54
|
options.pressed && `data-pressed-variant="${PRESSED_VARIANT[variant]}"`,
|
|
53
55
|
options.style && `style="${options.style}"`,
|
|
54
56
|
].filter(Boolean);
|
|
55
57
|
|
|
56
58
|
return `<${tag} ${attributes.join(' ')}>${iconAlignment === 'start' ? iconBox : ''}<div class="et-button-contents">${label}</div>${iconAlignment === 'end' ? iconBox : ''}</${tag}>`;
|
|
57
59
|
};
|
|
60
|
+
|
|
61
|
+
export type IconButtonMockOptions = Omit<ButtonMockOptions, 'label' | 'icon' | 'iconAlignment'> & {
|
|
62
|
+
/** The icon, as HTML. */
|
|
63
|
+
icon: string;
|
|
64
|
+
/** The accessible name: an icon-only button has no visible label. */
|
|
65
|
+
ariaLabel: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const ICON_PRESSED_VARIANT = {
|
|
69
|
+
filled: 'transparent',
|
|
70
|
+
outline: 'filled',
|
|
71
|
+
tonal: 'filled',
|
|
72
|
+
transparent: 'tonal',
|
|
73
|
+
} as const;
|
|
74
|
+
|
|
75
|
+
/** The stylesheets `iconButton()` draws with: the real `et-icon-button` CSS. */
|
|
76
|
+
export const iconButtonStyles = `${interactiveStyles}\n${focusRing}\n${buttonProperties}\n${iconButtonBase}`;
|
|
77
|
+
|
|
78
|
+
/** Draws `<button et-icon-button>` as HTML, the square icon-only button, with the DOM and classes the component renders. */
|
|
79
|
+
export const iconButton = (options: IconButtonMockOptions) => {
|
|
80
|
+
const { icon, ariaLabel, size = 'md', variant = 'transparent', tag = 'button' } = options;
|
|
81
|
+
|
|
82
|
+
const classes = [
|
|
83
|
+
'et-surface-interactive',
|
|
84
|
+
'et-color-interactive',
|
|
85
|
+
'et-icon-button',
|
|
86
|
+
'et-focus-ring',
|
|
87
|
+
options.color ? `et-color--${options.color}` : 'et-color--inherited',
|
|
88
|
+
options.class,
|
|
89
|
+
].filter(Boolean);
|
|
90
|
+
|
|
91
|
+
const attributes = [
|
|
92
|
+
`class="${classes.join(' ')}"`,
|
|
93
|
+
tag === 'button' && 'type="button"',
|
|
94
|
+
`aria-label="${ariaLabel}"`,
|
|
95
|
+
options.pressed && 'data-pressed="true"',
|
|
96
|
+
options.pressed !== undefined && `aria-pressed="${options.pressed}"`,
|
|
97
|
+
options.disabled && (tag === 'button' ? 'disabled aria-disabled="true"' : 'aria-disabled="true"'),
|
|
98
|
+
`data-variant="${variant}"`,
|
|
99
|
+
`data-size="${size}"`,
|
|
100
|
+
'data-can-animate="true"',
|
|
101
|
+
options.pressed && `data-pressed-variant="${ICON_PRESSED_VARIANT[variant]}"`,
|
|
102
|
+
options.style && `style="${options.style}"`,
|
|
103
|
+
].filter(Boolean);
|
|
104
|
+
|
|
105
|
+
return `<${tag} ${attributes.join(' ')}><div class="et-icon-button-icon">${icon}</div></${tag}>`;
|
|
106
|
+
};
|