@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,239 @@
|
|
|
1
|
+
import {
|
|
2
|
+
scheduler,
|
|
3
|
+
schedulerAgenda,
|
|
4
|
+
SchedulerMockAppointment,
|
|
5
|
+
SchedulerMockDay,
|
|
6
|
+
SchedulerMockMonthCell,
|
|
7
|
+
SchedulerMockTimeBlock,
|
|
8
|
+
SchedulerMockView,
|
|
9
|
+
schedulerMonth,
|
|
10
|
+
schedulerStyles,
|
|
11
|
+
schedulerTimeGrid,
|
|
12
|
+
} from '@ethlete/components/design/scheduler';
|
|
13
|
+
import { definePart, drawing } from '@design-system';
|
|
14
|
+
import frame from './scheduler.css?inline';
|
|
15
|
+
|
|
16
|
+
type Interaction = 'hover' | 'focus-visible';
|
|
17
|
+
|
|
18
|
+
type Props = {
|
|
19
|
+
view: SchedulerMockView;
|
|
20
|
+
appointment?: Interaction;
|
|
21
|
+
slot?: Interaction;
|
|
22
|
+
cell?: Interaction;
|
|
23
|
+
dragging?: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const clock = (time: number) => {
|
|
27
|
+
const hour = Math.floor(time);
|
|
28
|
+
const minutes = String(Math.round((time - hour) * 60)).padStart(2, '0');
|
|
29
|
+
|
|
30
|
+
return `${hour % 12 || 12}:${minutes} ${hour < 12 ? 'AM' : 'PM'}`;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const range = (start: number, end: number) => `${clock(start)}–${clock(end)}`;
|
|
34
|
+
|
|
35
|
+
const WED: SchedulerMockDay = { weekday: 'Wed', date: 4, label: 'Wednesday, March 4th, 2026', today: true };
|
|
36
|
+
const THU: SchedulerMockDay = { weekday: 'Thu', date: 5, label: 'Thursday, March 5th, 2026' };
|
|
37
|
+
const FRI: SchedulerMockDay = { weekday: 'Fri', date: 6, label: 'Friday, March 6th, 2026' };
|
|
38
|
+
|
|
39
|
+
const WEEK: SchedulerMockDay[] = [
|
|
40
|
+
{ weekday: 'Mon', date: 2, label: 'Monday, March 2nd, 2026' },
|
|
41
|
+
{ weekday: 'Tue', date: 3, label: 'Tuesday, March 3rd, 2026' },
|
|
42
|
+
WED,
|
|
43
|
+
THU,
|
|
44
|
+
FRI,
|
|
45
|
+
{ weekday: 'Sat', date: 7, label: 'Saturday, March 7th, 2026' },
|
|
46
|
+
{ weekday: 'Sun', date: 8, label: 'Sunday, March 8th, 2026' },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const TODAY = 2;
|
|
50
|
+
|
|
51
|
+
const block = (
|
|
52
|
+
day: number,
|
|
53
|
+
start: number,
|
|
54
|
+
end: number,
|
|
55
|
+
appointment: SchedulerMockAppointment,
|
|
56
|
+
extra: Partial<SchedulerMockTimeBlock> = {},
|
|
57
|
+
): SchedulerMockTimeBlock => ({ day, start, end, timeRange: range(start, end), ...appointment, ...extra });
|
|
58
|
+
|
|
59
|
+
const weekBlocks = (props: Props): SchedulerMockTimeBlock[] => [
|
|
60
|
+
block(0, 9, 9.5, { title: 'Daily sync', force: props.appointment }),
|
|
61
|
+
block(1, 9, 9.5, { title: 'Daily sync' }),
|
|
62
|
+
block(
|
|
63
|
+
1,
|
|
64
|
+
10,
|
|
65
|
+
12,
|
|
66
|
+
{ title: 'Roadmap planning', color: 'warning', location: 'Room 2', chainCount: 1 },
|
|
67
|
+
{ inlineOffset: 50, inlineSize: 50 },
|
|
68
|
+
),
|
|
69
|
+
block(1, 10.5, 11.5, { title: 'Budget check', color: 'warning' }, { inlineOffset: 0, inlineSize: 50, depth: 1 }),
|
|
70
|
+
block(TODAY, 9, 9.5, { title: 'Daily sync' }),
|
|
71
|
+
block(TODAY, 13, 14.5, { title: 'Design review', location: 'Studio A', selected: true }),
|
|
72
|
+
block(3, 9, 9.5, { title: 'Daily sync' }),
|
|
73
|
+
block(3, 14, 16, { title: 'Hiring panel', color: 'danger', location: 'Room 4' }),
|
|
74
|
+
block(4, 9, 9.5, { title: 'Daily sync' }),
|
|
75
|
+
block(4, props.dragging ? 15.5 : 15, props.dragging ? 16.5 : 16, { title: 'Retro', dragging: props.dragging }),
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
const weekNonBusiness = WEEK.flatMap((_, day) =>
|
|
79
|
+
day >= 5
|
|
80
|
+
? [{ day, from: 8, to: 18 }]
|
|
81
|
+
: [
|
|
82
|
+
{ day, from: 8, to: 9 },
|
|
83
|
+
{ day, from: 17, to: 18 },
|
|
84
|
+
],
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const timeGrid = (props: Props) => {
|
|
88
|
+
const single = props.view === 'day';
|
|
89
|
+
const days = single ? [WED] : WEEK;
|
|
90
|
+
const pick = (day: number) => (single ? (day === TODAY ? 0 : -1) : day);
|
|
91
|
+
const blocks = weekBlocks(props)
|
|
92
|
+
.map((entry) => ({ ...entry, day: pick(entry.day) }))
|
|
93
|
+
.filter((entry) => entry.day >= 0);
|
|
94
|
+
|
|
95
|
+
return schedulerTimeGrid({
|
|
96
|
+
days,
|
|
97
|
+
fromHour: 8,
|
|
98
|
+
toHour: 18,
|
|
99
|
+
hourLabel: (hour) => clock(hour),
|
|
100
|
+
blocks,
|
|
101
|
+
allDay: single ? [] : [{ day: 3, span: 2, title: 'Conference', color: 'danger' }],
|
|
102
|
+
now: { day: pick(TODAY), time: 11.25 },
|
|
103
|
+
nonBusiness: weekNonBusiness
|
|
104
|
+
.map((segment) => ({ ...segment, day: pick(segment.day) }))
|
|
105
|
+
.filter((segment) => segment.day >= 0),
|
|
106
|
+
forcedSlot: props.slot ? { day: single ? 0 : 3, hour: 11, force: props.slot } : undefined,
|
|
107
|
+
ariaLabel: single ? 'Wednesday, March 4, 2026' : 'March 2 – 8, 2026',
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const MONTH_APPOINTMENTS: Record<number, SchedulerMockAppointment[]> = {
|
|
112
|
+
2: [{ title: 'Daily sync', timeRange: range(9, 9.5) }],
|
|
113
|
+
3: [
|
|
114
|
+
{ title: 'Daily sync', timeRange: range(9, 9.5) },
|
|
115
|
+
{ title: 'Roadmap planning', timeRange: range(10, 12) },
|
|
116
|
+
],
|
|
117
|
+
4: [
|
|
118
|
+
{ title: 'Daily sync', timeRange: range(9, 9.5) },
|
|
119
|
+
{ title: 'Design review', timeRange: range(13, 14.5), selected: true },
|
|
120
|
+
],
|
|
121
|
+
5: [{ title: 'Conference', color: 'danger' }],
|
|
122
|
+
6: [{ title: 'Conference', color: 'danger' }],
|
|
123
|
+
12: [{ title: 'Quarterly review', timeRange: range(14, 16) }],
|
|
124
|
+
17: [
|
|
125
|
+
{ title: 'Daily sync', timeRange: range(9, 9.5) },
|
|
126
|
+
{ title: 'Workshop', color: 'warning', timeRange: range(10, 13) },
|
|
127
|
+
],
|
|
128
|
+
25: [{ title: 'Release', color: 'danger' }],
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const monthCell = (date: number, outsideMonth: boolean, props: Props): SchedulerMockMonthCell => {
|
|
132
|
+
const appointments = outsideMonth ? [] : (MONTH_APPOINTMENTS[date] ?? []);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
date,
|
|
136
|
+
label: date === 4 && !outsideMonth ? 'Wednesday, March 4th, 2026' : `${date}`,
|
|
137
|
+
outsideMonth,
|
|
138
|
+
today: !outsideMonth && date === 4,
|
|
139
|
+
appointments: (date === 3 && !outsideMonth ? appointments.slice(0, 2) : appointments).map((appointment) =>
|
|
140
|
+
date === 2 && !outsideMonth ? { ...appointment, force: props.appointment } : appointment,
|
|
141
|
+
),
|
|
142
|
+
overflow: date === 17 && !outsideMonth ? 3 : undefined,
|
|
143
|
+
force: date === 19 && !outsideMonth ? props.cell : undefined,
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const month = (props: Props) => {
|
|
148
|
+
const dates = [23, 24, 25, 26, 27, 28, ...Array.from({ length: 31 }, (_, index) => index + 1), 1, 2, 3, 4, 5];
|
|
149
|
+
const weeks = Array.from({ length: 6 }, (_, week) =>
|
|
150
|
+
dates.slice(week * 7, week * 7 + 7).map((date, index) => {
|
|
151
|
+
const position = week * 7 + index;
|
|
152
|
+
|
|
153
|
+
return monthCell(date, position < 6 || position > 36, props);
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
return schedulerMonth({
|
|
158
|
+
weekdays: WEEK.map((day) => ({ short: day.weekday, long: day.label.split(',')[0] ?? day.weekday })),
|
|
159
|
+
weeks,
|
|
160
|
+
ariaLabel: 'March 2026',
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const agenda = (props: Props) =>
|
|
165
|
+
schedulerAgenda({
|
|
166
|
+
days: [
|
|
167
|
+
{
|
|
168
|
+
...WED,
|
|
169
|
+
month: 'March 2026',
|
|
170
|
+
rows: [
|
|
171
|
+
{ title: 'Daily sync', timeRange: range(9, 9.5), force: props.appointment },
|
|
172
|
+
{
|
|
173
|
+
title: 'Design review',
|
|
174
|
+
timeRange: range(13, 14.5),
|
|
175
|
+
location: 'Studio A',
|
|
176
|
+
selected: true,
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
...THU,
|
|
182
|
+
rows: [
|
|
183
|
+
{ title: 'Conference', color: 'danger' },
|
|
184
|
+
{ title: 'Daily sync', timeRange: range(9, 9.5) },
|
|
185
|
+
{ title: 'Hiring panel', color: 'danger', timeRange: range(14, 16), location: 'Room 4', chainCount: 2 },
|
|
186
|
+
{ title: 'Intro call', color: 'danger', timeRange: range(14, 14.5), guides: ['branch'] },
|
|
187
|
+
{ title: 'Debrief', color: 'danger', timeRange: range(15.5, 16), guides: ['last-branch'] },
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
...FRI,
|
|
192
|
+
rows: [
|
|
193
|
+
{ title: 'Conference', color: 'danger' },
|
|
194
|
+
{ title: 'Retro', timeRange: range(15, 16) },
|
|
195
|
+
],
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const HEADER_LABELS: Record<SchedulerMockView, string> = {
|
|
201
|
+
week: 'March 2 – 8, 2026',
|
|
202
|
+
day: 'Wednesday, March 4, 2026',
|
|
203
|
+
month: 'March 2026',
|
|
204
|
+
agenda: 'March 4 – 10, 2026',
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const render = (props: Props) => {
|
|
208
|
+
const body = props.view === 'month' ? month(props) : props.view === 'agenda' ? agenda(props) : timeGrid(props);
|
|
209
|
+
|
|
210
|
+
return scheduler({ view: props.view, headerLabel: HEADER_LABELS[props.view], addAction: true, body });
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const skipClipped = (markup: string) =>
|
|
214
|
+
markup.replaceAll(
|
|
215
|
+
'class="et-scheduler-time-grid-block',
|
|
216
|
+
'data-et-skip="CLIPPED" class="et-scheduler-time-grid-block',
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
export default definePart({
|
|
220
|
+
note: "The SDK scheduler (et-scheduler) in flow, first drawing, drawn by the mock from @ethlete/components/design/scheduler with the real markup and CSS: the wide header (outline Today button, filled Add appointment, previous and next icon buttons, the range label, the tabs view switch), et-scheduler-time-grid-view (week and day), et-scheduler-month-view and et-scheduler-agenda-view, the appointment badges (color dot, title, time range, location, chain count) and the resize handles; default labels, en-US times. Fixtures are fictional: one week (Mon March 2 to Sun March 8, 2026, today Wed March 4) with a daily sync, an overlapping parent and nested child (Roadmap planning / Budget check), a selected appointment (Design review, brand), a two-day all-day Conference, Hiring panel and Retro; the colors are the sdk product's registered themes brand (default), warning and danger. The time grid draws 8:00 to 18:00 as if the scrolling body sat on that window, with business hours 9 to 17 on weekdays and the weekend outside them; the current time line sits on today at 11:15. Framed as a page with 16px padding. Widths 560, 800 and 1200 (all above the 480px container width where the header switches to floating action buttons). States: week, week-dragging (Retro lifted, with the drag shadow), appointment-hover, appointment-focus-visible (Daily sync on Monday forced), slot-focus-visible (Thursday 11:00 forced), day, month (outside-month days, today, a +3 more trigger, the selected appointment), month-cell-focus-visible, agenda (a month heading, today, nested rows with tree guides, the selected appointment). Numbers (light / dark), from shot pixels at scale 2 at 800, brand mint, a: appointment tint (14 %) vs page 1.07 / 1.37, its title ink on it 5.12 / 9.84; selected appointment (mint fill) vs page 1.33 / 13.50, its text 15.82 / 15.82; today date circle (mint fill) vs page 1.33 / 13.50, its digit 15.82; current time line and its dot vs the today column 1.29 / 12.14; focus ring (primary) vs page 1.35 / 13.50, vs the appointment tint 1.26 / 9.84; Add appointment fill vs page 1.33 / 13.50, its label 15.82. Q98 numbers only: grid lines (hour rows, day columns, header and all-day edges, month cells) vs page 1.26 / 1.73; the non-business band 1.07 / 1.08; the today column tint is 6 % mint. Decided rules that arise: Q88 (every focus ring: slots, all-day cells, month cells, appointments, the overflow trigger, the view switch, the header buttons), Q99a (the current time line and its dot, the appointment color dot; the selected appointment, the today date circles and the filled Add appointment button are mint fills, so they take a 1px inset ink edge) and Q101 (the filled button hover = -active). The ink edge on a mint fill arises, so Q102 arises: b draws the edges in both schemes, c keeps dark as today (light-dark(ink, transparent)). The 32 / 38 / 44 % selection step does not arise: a selected appointment is a solid mint fill, not a tint. Findings for later: (1) lib bug: the badge pieces render inside their component hosts (et-scheduler-badge-color-dot, -title, -time-range, -location, -chain-count), which are inline, so the flex rules on the inner spans never apply: the color dot is 0 x 0 and never shows (also in Storybook), the title does not take flex: 1 or ellipsis, and the time range and location are cut hard at the block edge (the agenda location shrinks to one letter while the title has room). Fixed in the lib (display: contents on the five hosts; the time range and location shrink with an ellipsis before the title). Narrow week blocks still clip, because the gaps, the dot and the chain count alone are wider than the block (31 px at 560), so data-et-skip CLIPPED stays on every time grid block; the month cell skip is gone. (2) The first hour label sat half above the scroll body (translateY(-50%)) and was clipped by its overflow; fixed in the lib with a top padding of half the label line height. (3) Outside-month dates read nearly like in-month ones. (4) The selected appointment edge and the drag shadow share box-shadow, so a selected appointment being dragged would lose one (not drawn); the lib has no selected edge yet, so whoever merges b or c must list the edge and the drag shadow in one rule on [data-selected][data-dragging]. (5) The Add appointment hover (Q101) is not forced here; it is the same et-button as in the stream round (hover 1.69 from rest). Later rounds, left out here: the edit surface (scheduler-edit-surface and its parts, an overlay), the month +n more menu, the narrow header with floating action buttons, the drag-to-create draft and committed blocks, month draft and drop-target cells, the scrollbar, custom badge adornments, the read-only scheduler, RTL and long labels.",
|
|
221
|
+
madeBy: ['claude-opus-5-5'],
|
|
222
|
+
states: {
|
|
223
|
+
week: { view: 'week' },
|
|
224
|
+
'week-dragging': { view: 'week', dragging: true },
|
|
225
|
+
'appointment-hover': { view: 'week', appointment: 'hover' },
|
|
226
|
+
'appointment-focus-visible': { view: 'week', appointment: 'focus-visible' },
|
|
227
|
+
'slot-focus-visible': { view: 'week', slot: 'focus-visible' },
|
|
228
|
+
day: { view: 'day' },
|
|
229
|
+
month: { view: 'month' },
|
|
230
|
+
'month-cell-focus-visible': { view: 'month', cell: 'focus-visible' },
|
|
231
|
+
agenda: { view: 'agenda' },
|
|
232
|
+
},
|
|
233
|
+
widths: [560, 800, 1200],
|
|
234
|
+
render: (props: Props) =>
|
|
235
|
+
drawing({
|
|
236
|
+
body: `<div class="t-frame">${skipClipped(render(props))}</div>`,
|
|
237
|
+
styles: [schedulerStyles, frame].join('\n'),
|
|
238
|
+
}),
|
|
239
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
.scrollable-frame {
|
|
2
|
+
padding: 2.4rem;
|
|
3
|
+
background: var(--et-surface-background-solid);
|
|
4
|
+
color: var(--et-surface-color-solid);
|
|
5
|
+
font-size: 1.4rem;
|
|
6
|
+
|
|
7
|
+
.et-scrollable[direction='vertical'] {
|
|
8
|
+
inline-size: 400px;
|
|
9
|
+
|
|
10
|
+
.et-scrollable-container {
|
|
11
|
+
block-size: 258px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.et-scrollable-container > * {
|
|
15
|
+
translate: 0 var(--scrollable-offset);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.et-scrollable-container {
|
|
20
|
+
block-size: 128px;
|
|
21
|
+
gap: 12px;
|
|
22
|
+
padding-block: 4px;
|
|
23
|
+
margin-block: -4px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.et-scrollable-container > * {
|
|
27
|
+
translate: var(--scrollable-offset) 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.scrollable-item {
|
|
31
|
+
display: flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
min-inline-size: 200px;
|
|
35
|
+
min-block-size: 80px;
|
|
36
|
+
block-size: 100%;
|
|
37
|
+
border: none;
|
|
38
|
+
border-radius: 12px;
|
|
39
|
+
font-size: 1.8rem;
|
|
40
|
+
font-weight: 600;
|
|
41
|
+
background: color-mix(in srgb, var(--et-surface-color-solid) 14%, transparent);
|
|
42
|
+
color: var(--et-surface-color-solid);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.scrollable-item:focus-visible {
|
|
46
|
+
outline: -webkit-focus-ring-color auto 1px;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.scrollable-frame--sized .scrollable-item {
|
|
51
|
+
min-inline-size: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@layer components {
|
|
55
|
+
.et-scrollable-navigation-item.et-scrollable-navigation-item--active::after {
|
|
56
|
+
background-color: light-dark(var(--et-theme-color-ink-solid), var(--et-theme-color-primary));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { scrollable, scrollableStyles } from '@ethlete/components/design/scrollable';
|
|
2
|
+
import { definePart, drawing } from '@design-system';
|
|
3
|
+
import frame from './scrollable.css?inline';
|
|
4
|
+
|
|
5
|
+
type Position = 'start' | 'middle' | 'end';
|
|
6
|
+
type Interaction = 'hover' | 'active';
|
|
7
|
+
|
|
8
|
+
const ITEM_SIZES = ['same', 'full', 'half', 'third', 'quarter'] as const;
|
|
9
|
+
type ItemSize = (typeof ITEM_SIZES)[number];
|
|
10
|
+
|
|
11
|
+
type Props = {
|
|
12
|
+
direction?: 'horizontal' | 'vertical';
|
|
13
|
+
position?: Position;
|
|
14
|
+
items?: number;
|
|
15
|
+
maskVariant?: 'gradient' | 'border';
|
|
16
|
+
buttons?: 'inside' | 'footer' | 'none';
|
|
17
|
+
navigation?: boolean;
|
|
18
|
+
darken?: boolean;
|
|
19
|
+
color?: string;
|
|
20
|
+
endButton?: Interaction;
|
|
21
|
+
focusItem?: number;
|
|
22
|
+
itemSize?: ItemSize;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const ITEM = { horizontal: 200, vertical: 80 };
|
|
26
|
+
const VIEWPORT = { horizontal: 432, vertical: 258 };
|
|
27
|
+
const GAP = 12;
|
|
28
|
+
|
|
29
|
+
const extent = (count: number, size: number) => count * size + (count - 1) * GAP;
|
|
30
|
+
|
|
31
|
+
const offsetOf = (props: Required<Pick<Props, 'direction' | 'position' | 'items'>>) => {
|
|
32
|
+
const size = ITEM[props.direction];
|
|
33
|
+
const max = Math.max(0, extent(props.items, size) - VIEWPORT[props.direction]);
|
|
34
|
+
|
|
35
|
+
if (props.position === 'start') return 0;
|
|
36
|
+
if (props.position === 'end') return max;
|
|
37
|
+
|
|
38
|
+
return Math.min(max, 3 * (size + GAP) - Math.round((VIEWPORT[props.direction] - size) / 2));
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const visibleRange = (offset: number, size: number, viewport: number, count: number) =>
|
|
42
|
+
Array.from({ length: count }, (_, index) => {
|
|
43
|
+
const start = index * (size + GAP) - offset;
|
|
44
|
+
const end = start + size;
|
|
45
|
+
const ratio = Math.max(0, Math.min(end, viewport) - Math.max(start, 0)) / size;
|
|
46
|
+
|
|
47
|
+
return ratio;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const render = (props: Props) => {
|
|
51
|
+
const direction = props.direction ?? 'horizontal';
|
|
52
|
+
const items = props.items ?? 8;
|
|
53
|
+
const position = props.position ?? 'start';
|
|
54
|
+
const size = ITEM[direction];
|
|
55
|
+
const canScroll = extent(items, size) > VIEWPORT[direction];
|
|
56
|
+
const offset = offsetOf({ direction, position, items });
|
|
57
|
+
const ratios = visibleRange(offset, size, VIEWPORT[direction], items);
|
|
58
|
+
const best = ratios.reduce((found, ratio, index) => (ratio > (ratios[found] ?? 0) ? index : found), 0);
|
|
59
|
+
const active = position === 'end' ? items - 1 : position === 'start' ? 0 : best;
|
|
60
|
+
const buttons = props.buttons ?? 'inside';
|
|
61
|
+
|
|
62
|
+
const markup = scrollable({
|
|
63
|
+
direction,
|
|
64
|
+
canScroll,
|
|
65
|
+
position,
|
|
66
|
+
maskVariant: props.maskVariant,
|
|
67
|
+
buttons: buttons === 'none' ? undefined : buttons,
|
|
68
|
+
navigationActive: props.navigation ? active : undefined,
|
|
69
|
+
darken: props.darken,
|
|
70
|
+
itemSize: props.itemSize,
|
|
71
|
+
itemGap: props.itemSize ? GAP : undefined,
|
|
72
|
+
color: props.color,
|
|
73
|
+
endButtonAttributes: props.endButton ? `data-et-force="${props.endButton}"` : undefined,
|
|
74
|
+
containerAttributes: `data-et-skip="CLIPPED" style="--scrollable-offset: -${offset}px"`,
|
|
75
|
+
items: Array.from({ length: items }, (_, index) => ({
|
|
76
|
+
tag: 'button',
|
|
77
|
+
content: `Item ${index}`,
|
|
78
|
+
notIntersecting: props.darken && (ratios[index] ?? 0) < 1,
|
|
79
|
+
attributes: [
|
|
80
|
+
'class="scrollable-item"',
|
|
81
|
+
'type="button"',
|
|
82
|
+
props.focusItem === index && 'data-et-force="focus-visible"',
|
|
83
|
+
]
|
|
84
|
+
.filter(Boolean)
|
|
85
|
+
.join(' '),
|
|
86
|
+
})),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return markup.replace(
|
|
90
|
+
'class="et-scrollable-progress-bar"',
|
|
91
|
+
'data-et-skip="CLIPPED" class="et-scrollable-progress-bar"',
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export default definePart({
|
|
96
|
+
note: 'scale of the decided shape, Q182, lead merge',
|
|
97
|
+
madeBy: ['claude-opus-5-5'],
|
|
98
|
+
states: {
|
|
99
|
+
'horizontal-start': {},
|
|
100
|
+
'horizontal-middle': { position: 'middle' },
|
|
101
|
+
'horizontal-end': { position: 'end' },
|
|
102
|
+
'vertical-start': { direction: 'vertical' },
|
|
103
|
+
'vertical-middle': { direction: 'vertical', position: 'middle' },
|
|
104
|
+
'vertical-end': { direction: 'vertical', position: 'end' },
|
|
105
|
+
'button-hover': { position: 'middle', endButton: 'hover' },
|
|
106
|
+
'button-active': { position: 'middle', endButton: 'active' },
|
|
107
|
+
'border-mask': { position: 'middle', maskVariant: 'border' },
|
|
108
|
+
'vertical-border-mask': { direction: 'vertical', position: 'middle', maskVariant: 'border' },
|
|
109
|
+
'navigation-start': { navigation: true },
|
|
110
|
+
'navigation-middle': { position: 'middle', navigation: true },
|
|
111
|
+
'navigation-end': { position: 'end', navigation: true },
|
|
112
|
+
'vertical-navigation': { direction: 'vertical', position: 'middle', navigation: true },
|
|
113
|
+
'footer-buttons': { position: 'middle', buttons: 'footer', navigation: true },
|
|
114
|
+
'footer-button-hover': { position: 'middle', buttons: 'footer', navigation: true, endButton: 'hover' },
|
|
115
|
+
'danger-navigation': { position: 'middle', navigation: true, color: 'danger' },
|
|
116
|
+
darken: { position: 'middle', darken: true },
|
|
117
|
+
'item-focus-visible': { focusItem: 1 },
|
|
118
|
+
'short-content': { items: 2 },
|
|
119
|
+
'short-content-navigation': { items: 2, navigation: true },
|
|
120
|
+
...(Object.fromEntries(
|
|
121
|
+
ITEM_SIZES.map((itemSize) => [
|
|
122
|
+
`item-size-${itemSize}`,
|
|
123
|
+
itemSize === 'same' ? { itemSize, items: 2 } : { itemSize },
|
|
124
|
+
]),
|
|
125
|
+
) as Record<`item-size-${ItemSize}`, Props>),
|
|
126
|
+
},
|
|
127
|
+
widths: [480],
|
|
128
|
+
render: (props: Props) =>
|
|
129
|
+
drawing({
|
|
130
|
+
body: `<div class="scrollable-frame${props.itemSize ? ' scrollable-frame--sized' : ''}">${render(props)}</div>`,
|
|
131
|
+
styles: [scrollableStyles, frame].join('\n'),
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
.scrollbar-frame {
|
|
2
|
+
padding: 2.4rem;
|
|
3
|
+
background: var(--et-surface-background-solid);
|
|
4
|
+
color: var(--et-surface-color-solid);
|
|
5
|
+
font-size: 1.4rem;
|
|
6
|
+
|
|
7
|
+
.scrollbar-wrapper {
|
|
8
|
+
position: relative;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.scrollbar-viewport {
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.scrollbar-viewport[data-layout='vertical'] {
|
|
16
|
+
block-size: 200px;
|
|
17
|
+
padding-inline-end: 2.4rem;
|
|
18
|
+
|
|
19
|
+
.scrollbar-content {
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
gap: 8px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.scrollbar-item {
|
|
26
|
+
block-size: 36px;
|
|
27
|
+
padding-inline: 1.2rem;
|
|
28
|
+
line-height: 36px;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.scrollbar-viewport[data-layout='horizontal'] {
|
|
33
|
+
padding-block-end: 2.4rem;
|
|
34
|
+
|
|
35
|
+
.scrollbar-content {
|
|
36
|
+
display: flex;
|
|
37
|
+
gap: 8px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.scrollbar-item {
|
|
41
|
+
display: grid;
|
|
42
|
+
flex-shrink: 0;
|
|
43
|
+
place-items: center;
|
|
44
|
+
inline-size: 120px;
|
|
45
|
+
block-size: 80px;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.scrollbar-viewport[data-layout='both'] {
|
|
50
|
+
block-size: 200px;
|
|
51
|
+
padding-block-end: 2.4rem;
|
|
52
|
+
padding-inline-end: 2.4rem;
|
|
53
|
+
|
|
54
|
+
.scrollbar-content {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-direction: column;
|
|
57
|
+
gap: 8px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.scrollbar-grid-row {
|
|
61
|
+
display: flex;
|
|
62
|
+
gap: 8px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.scrollbar-item {
|
|
66
|
+
display: grid;
|
|
67
|
+
flex-shrink: 0;
|
|
68
|
+
place-items: center;
|
|
69
|
+
inline-size: 100px;
|
|
70
|
+
block-size: 40px;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.scrollbar-item {
|
|
75
|
+
border-radius: 8px;
|
|
76
|
+
background: color-mix(in srgb, var(--et-surface-color-solid) 6%, transparent);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { scrollbar, scrollbarHostClass, scrollbarStyles } from '@ethlete/components/design/scrollbar';
|
|
2
|
+
import { definePart, drawing } from '@design-system';
|
|
3
|
+
import frame from './scrollbar.css?inline';
|
|
4
|
+
|
|
5
|
+
type Layout = 'vertical' | 'horizontal' | 'both' | 'rtl';
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
layout: Layout;
|
|
9
|
+
items?: number;
|
|
10
|
+
scroll?: number;
|
|
11
|
+
scrollInline?: number;
|
|
12
|
+
visible?: boolean;
|
|
13
|
+
thumb?: 'hover' | 'dragging';
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const VIEWPORT_BLOCK = 200;
|
|
17
|
+
const VIEWPORT_INLINE = 432;
|
|
18
|
+
const INSET = 4;
|
|
19
|
+
const MIN_THUMB = 24;
|
|
20
|
+
const ROW = 36;
|
|
21
|
+
const CARD = 120;
|
|
22
|
+
const CELL_INLINE = 100;
|
|
23
|
+
const CELL_BLOCK = 40;
|
|
24
|
+
const GAP = 8;
|
|
25
|
+
|
|
26
|
+
const extent = (count: number, size: number) => count * size + (count - 1) * GAP;
|
|
27
|
+
|
|
28
|
+
const geometry = (client: number, scrollSize: number, position: number) => {
|
|
29
|
+
const track = client - 2 * INSET;
|
|
30
|
+
const size = Math.max(MIN_THUMB, Math.round((track * client) / scrollSize));
|
|
31
|
+
const max = scrollSize - client;
|
|
32
|
+
const offset = max > 0 ? Math.round(((track - size) * Math.min(position, max)) / max) : 0;
|
|
33
|
+
|
|
34
|
+
return { size, offset };
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const bar = (
|
|
38
|
+
orientation: 'vertical' | 'horizontal',
|
|
39
|
+
client: number,
|
|
40
|
+
scrollSize: number,
|
|
41
|
+
position: number,
|
|
42
|
+
props: Props,
|
|
43
|
+
) => {
|
|
44
|
+
const { size, offset } = geometry(client, scrollSize, position);
|
|
45
|
+
|
|
46
|
+
return scrollbar({
|
|
47
|
+
orientation,
|
|
48
|
+
thumbSize: size,
|
|
49
|
+
thumbOffset: offset,
|
|
50
|
+
visible: props.visible,
|
|
51
|
+
dragging: props.thumb === 'dragging',
|
|
52
|
+
rtl: props.layout === 'rtl',
|
|
53
|
+
thumbAttributes: props.thumb === 'hover' ? 'data-et-force="hover"' : undefined,
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const items = (count: number, label: string) =>
|
|
58
|
+
Array.from({ length: count }, (_, index) => `<div class="scrollbar-item">${label} ${index + 1}</div>`).join('');
|
|
59
|
+
|
|
60
|
+
const render = (props: Props) => {
|
|
61
|
+
const scroll = props.scroll ?? 0;
|
|
62
|
+
const scrollInline = props.scrollInline ?? 0;
|
|
63
|
+
|
|
64
|
+
if (props.layout === 'vertical') {
|
|
65
|
+
const count = props.items ?? 24;
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
`<div class="${scrollbarHostClass} scrollbar-viewport" data-et-skip="CLIPPED" data-layout="vertical">` +
|
|
69
|
+
`<div class="scrollbar-content" style="margin-block-start: -${scroll}px">${items(count, 'Row')}</div></div>` +
|
|
70
|
+
bar('vertical', VIEWPORT_BLOCK, extent(count, ROW), scroll, props)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (props.layout === 'both') {
|
|
75
|
+
const columns = 12;
|
|
76
|
+
const rows = 20;
|
|
77
|
+
const grid = Array.from(
|
|
78
|
+
{ length: rows },
|
|
79
|
+
(_, row) =>
|
|
80
|
+
`<div class="scrollbar-grid-row">${Array.from({ length: columns }, (_, column) => `<div class="scrollbar-item">R${row + 1} C${column + 1}</div>`).join('')}</div>`,
|
|
81
|
+
).join('');
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
`<div class="${scrollbarHostClass} scrollbar-viewport" data-et-skip="CLIPPED" data-layout="both">` +
|
|
85
|
+
`<div class="scrollbar-content" style="margin-block-start: -${scroll}px; margin-inline-start: -${scrollInline}px">${grid}</div></div>` +
|
|
86
|
+
bar('vertical', VIEWPORT_BLOCK, extent(rows, CELL_BLOCK), scroll, props) +
|
|
87
|
+
bar('horizontal', VIEWPORT_INLINE, extent(columns, CELL_INLINE), scrollInline, props)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const count = props.items ?? 14;
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
`<div class="${scrollbarHostClass} scrollbar-viewport" data-et-skip="CLIPPED" data-layout="horizontal">` +
|
|
95
|
+
`<div class="scrollbar-content" style="margin-inline-start: -${scrollInline}px">${items(count, 'Card')}</div></div>` +
|
|
96
|
+
bar('horizontal', VIEWPORT_INLINE, extent(count, CARD), scrollInline, props)
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export default definePart({
|
|
101
|
+
note: "Today's look",
|
|
102
|
+
madeBy: ['claude-opus-5-5'],
|
|
103
|
+
states: {
|
|
104
|
+
vertical: { layout: 'vertical' },
|
|
105
|
+
'vertical-scrolled': { layout: 'vertical', scroll: 424 },
|
|
106
|
+
'vertical-end': { layout: 'vertical', scroll: 848 },
|
|
107
|
+
horizontal: { layout: 'horizontal' },
|
|
108
|
+
'horizontal-scrolled': { layout: 'horizontal', scrollInline: 600 },
|
|
109
|
+
'both-axes': { layout: 'both', scroll: 300, scrollInline: 200 },
|
|
110
|
+
rtl: { layout: 'rtl' },
|
|
111
|
+
'short-content': { layout: 'vertical', items: 6, scroll: 28 },
|
|
112
|
+
'long-content': { layout: 'vertical', items: 200, scroll: 3000 },
|
|
113
|
+
'thumb-hover': { layout: 'vertical', scroll: 424, thumb: 'hover' },
|
|
114
|
+
dragging: { layout: 'vertical', scroll: 424, thumb: 'dragging' },
|
|
115
|
+
'auto-hidden': { layout: 'vertical', visible: false },
|
|
116
|
+
},
|
|
117
|
+
widths: [480],
|
|
118
|
+
render: (props: Props) =>
|
|
119
|
+
drawing({
|
|
120
|
+
body: `<div class="scrollbar-frame"${props.layout === 'rtl' ? ' dir="rtl"' : ''}><div class="scrollbar-wrapper">${render(props)}</div></div>`,
|
|
121
|
+
styles: [scrollbarStyles, frame].join('\n'),
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
@@ -4,7 +4,7 @@ import styles from './select.css?inline';
|
|
|
4
4
|
import { grid, moreStates, selectBody, SelectProps, states } from './shared/select-part';
|
|
5
5
|
|
|
6
6
|
export default definePart<SelectProps>({
|
|
7
|
-
note: '
|
|
7
|
+
note: 'scale of the decided shape, Q182, lead merge',
|
|
8
8
|
madeBy: ['claude-opus-5-5', 'claude-sonnet-5-5'],
|
|
9
9
|
states: { ...states, ...moreStates },
|
|
10
10
|
grid,
|