@kenlo/sds-design-system 0.9.9
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/README.md +129 -0
- package/components/Accordion/Accordion.css +186 -0
- package/components/Accordion/accordion-variants.js +17 -0
- package/components/Accordion/accordion.marko +264 -0
- package/components/AccordionGroup/AccordionGroup.css +35 -0
- package/components/AccordionGroup/accordion-group.marko +17 -0
- package/components/Alert/Alert.css +104 -0
- package/components/Alert/alert-variants.js +35 -0
- package/components/Alert/alert.marko +94 -0
- package/components/AttachmentList/AttachmentList.css +92 -0
- package/components/AttachmentList/attachment-list.marko +51 -0
- package/components/AttachmentList/attachment-row.marko +167 -0
- package/components/Avatar/Avatar.css +110 -0
- package/components/Avatar/avatar-variants.js +12 -0
- package/components/Avatar/avatar.marko +71 -0
- package/components/Badge/Badge.css +381 -0
- package/components/Badge/badge-dismissible.marko +143 -0
- package/components/Badge/badge-variants.js +35 -0
- package/components/Badge/badge.marko +134 -0
- package/components/BottomSheet/BottomSheet.css +300 -0
- package/components/BottomSheet/bottom-sheet.marko +340 -0
- package/components/BulkActionBar/BulkActionBar.css +88 -0
- package/components/BulkActionBar/bulk-action-bar.marko +186 -0
- package/components/Button/Button.css +340 -0
- package/components/Button/button-variants.js +25 -0
- package/components/Button/button.marko +139 -0
- package/components/Card/Card.css +391 -0
- package/components/Card/card-body.marko +5 -0
- package/components/Card/card-collapsible.marko +94 -0
- package/components/Card/card-footer.marko +12 -0
- package/components/Card/card-header.marko +30 -0
- package/components/Card/card-info-pair.marko +17 -0
- package/components/Card/card-info-row.marko +12 -0
- package/components/Card/card-section.marko +14 -0
- package/components/Card/card-variants.js +24 -0
- package/components/Card/card.marko +124 -0
- package/components/Checkbox/Checkbox.css +249 -0
- package/components/Checkbox/checkbox-variants.js +21 -0
- package/components/Checkbox/checkbox.marko +128 -0
- package/components/ColorPicker/ColorPicker.css +253 -0
- package/components/ColorPicker/color-maths.js +104 -0
- package/components/ColorPicker/color-picker.marko +474 -0
- package/components/DatePicker/DatePicker.css +429 -0
- package/components/DatePicker/date-logic.js +225 -0
- package/components/DatePicker/date-picker.marko +492 -0
- package/components/Drawer/Drawer.css +174 -0
- package/components/Drawer/drawer.marko +262 -0
- package/components/Floating/Floating.css +28 -0
- package/components/Floating/floating-position.js +251 -0
- package/components/Floating/floating.marko +291 -0
- package/components/Form/Form.css +15 -0
- package/components/Form/form-context.js +69 -0
- package/components/Form/form.marko +73 -0
- package/components/GradientPicker/GradientPicker.css +179 -0
- package/components/GradientPicker/gradient-picker.marko +398 -0
- package/components/Grid/Grid.css +161 -0
- package/components/Grid/col.marko +36 -0
- package/components/Grid/grid-variants.js +43 -0
- package/components/Grid/grid.marko +25 -0
- package/components/Header/Header.css +457 -0
- package/components/Header/header.marko +523 -0
- package/components/Icon/Icon.css +44 -0
- package/components/Icon/icon-colors.js +31 -0
- package/components/Icon/icon.marko +56 -0
- package/components/IconButton/IconButton.css +85 -0
- package/components/IconButton/icon-button-variants.js +18 -0
- package/components/IconButton/icon-button.marko +103 -0
- package/components/Incrementer/Incrementer.css +94 -0
- package/components/Incrementer/incrementer.marko +70 -0
- package/components/Input/Input.css +547 -0
- package/components/Input/input-countries.js +109 -0
- package/components/Input/input-masks.js +218 -0
- package/components/Input/input-types.js +194 -0
- package/components/Input/input.marko +530 -0
- package/components/Menu/Menu.css +651 -0
- package/components/Menu/menu-variants.js +89 -0
- package/components/Menu/menu.marko +560 -0
- package/components/Modal/Modal.css +160 -0
- package/components/Modal/modal.marko +296 -0
- package/components/Nav/Nav.css +298 -0
- package/components/Nav/nav-variants.js +23 -0
- package/components/Nav/nav.marko +164 -0
- package/components/Notification/Notification.css +78 -0
- package/components/Notification/notification-variants.js +30 -0
- package/components/Notification/notification.marko +55 -0
- package/components/Pagination/Pagination.css +78 -0
- package/components/Pagination/pagination-pages.js +66 -0
- package/components/Pagination/pagination.marko +141 -0
- package/components/Pill/Pill.css +193 -0
- package/components/Pill/pill-variants.js +16 -0
- package/components/Pill/pill.marko +165 -0
- package/components/Popover/Popover.css +121 -0
- package/components/Popover/popover.marko +96 -0
- package/components/Radio/Radio.css +230 -0
- package/components/Radio/radio-variants.js +16 -0
- package/components/Radio/radio.marko +93 -0
- package/components/RadioGroup/RadioGroup.css +32 -0
- package/components/RadioGroup/radio-group.marko +93 -0
- package/components/SearchBar/SearchBar.css +181 -0
- package/components/SearchBar/search-bar-variants.js +24 -0
- package/components/SearchBar/search-bar.marko +157 -0
- package/components/Select/Select.css +427 -0
- package/components/Select/select-options.js +163 -0
- package/components/Select/select.marko +449 -0
- package/components/Separator/Separator.css +22 -0
- package/components/Separator/separator.marko +14 -0
- package/components/Skeleton/Skeleton.css +100 -0
- package/components/Skeleton/skeleton-avatar.marko +28 -0
- package/components/Skeleton/skeleton-button.marko +22 -0
- package/components/Skeleton/skeleton-image.marko +20 -0
- package/components/Skeleton/skeleton-input.marko +22 -0
- package/components/Skeleton/skeleton-variants.js +16 -0
- package/components/Skeleton/skeleton.marko +53 -0
- package/components/Spinner/Spinner.css +173 -0
- package/components/Spinner/spinner-geometry.js +39 -0
- package/components/Spinner/spinner-overlay.marko +97 -0
- package/components/Spinner/spinner.marko +88 -0
- package/components/StatCard/StatCard.css +90 -0
- package/components/StatCard/stat-card-group.marko +5 -0
- package/components/StatCard/stat-card.marko +57 -0
- package/components/Switch/Switch.css +209 -0
- package/components/Switch/switch.marko +99 -0
- package/components/Table/Table.css +270 -0
- package/components/Table/table-cell.marko +28 -0
- package/components/Table/table-header.marko +27 -0
- package/components/Table/table-logic.js +129 -0
- package/components/Table/table.marko +372 -0
- package/components/Text/Text.css +79 -0
- package/components/Text/text-variants.js +25 -0
- package/components/Text/text.marko +48 -0
- package/components/TextEditor/TextEditor.css +78 -0
- package/components/TextEditor/safe-url.js +50 -0
- package/components/TextEditor/text-editor-commands.js +22 -0
- package/components/TextEditor/text-editor.marko +253 -0
- package/components/Toast/Toast.css +88 -0
- package/components/Toast/toast-api.js +19 -0
- package/components/Toast/toast-store.js +118 -0
- package/components/Toast/toast-variants.js +16 -0
- package/components/Toast/toast.marko +93 -0
- package/components/Toast/toaster.marko +70 -0
- package/components/Toggle/Toggle.css +169 -0
- package/components/Toggle/toggle.marko +93 -0
- package/components/Tooltip/Tooltip.css +115 -0
- package/components/Tooltip/tooltip.marko +77 -0
- package/components/Uploader/Uploader.css +180 -0
- package/components/Uploader/crop-geometry.js +161 -0
- package/components/Uploader/uploader.marko +269 -0
- package/foundations/Scrollbar/Scrollbar.css +26 -0
- package/foundations/attrs.js +27 -0
- package/foundations/breakpoints.js +27 -0
- package/foundations/contrast.js +74 -0
- package/foundations/custom-scheme.js +222 -0
- package/foundations/overlay.js +108 -0
- package/foundations/styles.css +284 -0
- package/foundations/text.js +22 -0
- package/foundations/theme-server.js +81 -0
- package/foundations/theme.js +42 -0
- package/foundations/tokens.css +1656 -0
- package/icons/custom/custom-video-horizontal-filled.marko +6 -0
- package/icons/custom/custom-video-horizontal-regular.marko +6 -0
- package/icons/custom/custom-video-tour-3d-filled.marko +7 -0
- package/icons/custom/custom-video-tour-3d-regular.marko +9 -0
- package/icons/custom/custom-video-vertical-filled.marko +6 -0
- package/icons/custom/custom-video-vertical-regular.marko +6 -0
- package/icons/generated/fluent-add-regular.marko +3 -0
- package/icons/generated/fluent-alert-regular.marko +3 -0
- package/icons/generated/fluent-apps-regular.marko +3 -0
- package/icons/generated/fluent-arrow-download-regular.marko +3 -0
- package/icons/generated/fluent-arrow-left-regular.marko +3 -0
- package/icons/generated/fluent-arrow-right-regular.marko +3 -0
- package/icons/generated/fluent-arrow-rotate-clockwise-regular.marko +3 -0
- package/icons/generated/fluent-arrow-rotate-counterclockwise-regular.marko +3 -0
- package/icons/generated/fluent-border-all-regular.marko +3 -0
- package/icons/generated/fluent-calendar-regular.marko +3 -0
- package/icons/generated/fluent-checkmark-circle-filled.marko +3 -0
- package/icons/generated/fluent-checkmark-regular.marko +3 -0
- package/icons/generated/fluent-chevron-down-regular.marko +3 -0
- package/icons/generated/fluent-chevron-left-regular.marko +3 -0
- package/icons/generated/fluent-chevron-right-regular.marko +3 -0
- package/icons/generated/fluent-chevron-up-regular.marko +3 -0
- package/icons/generated/fluent-circle-half-fill-regular.marko +3 -0
- package/icons/generated/fluent-clock-regular.marko +3 -0
- package/icons/generated/fluent-color-regular.marko +3 -0
- package/icons/generated/fluent-crop-regular.marko +3 -0
- package/icons/generated/fluent-dark-theme-regular.marko +3 -0
- package/icons/generated/fluent-delete-regular.marko +3 -0
- package/icons/generated/fluent-desktop-regular.marko +3 -0
- package/icons/generated/fluent-dismiss-circle-filled.marko +3 -0
- package/icons/generated/fluent-dismiss-regular.marko +3 -0
- package/icons/generated/fluent-document-pdf-regular.marko +3 -0
- package/icons/generated/fluent-document-regular.marko +3 -0
- package/icons/generated/fluent-eye-off-regular.marko +3 -0
- package/icons/generated/fluent-eye-regular.marko +3 -0
- package/icons/generated/fluent-eyedropper-regular.marko +3 -0
- package/icons/generated/fluent-grid-regular.marko +3 -0
- package/icons/generated/fluent-history-regular.marko +3 -0
- package/icons/generated/fluent-home-regular.marko +3 -0
- package/icons/generated/fluent-image-regular.marko +3 -0
- package/icons/generated/fluent-info-regular.marko +3 -0
- package/icons/generated/fluent-link-regular.marko +3 -0
- package/icons/generated/fluent-more-horizontal-regular.marko +3 -0
- package/icons/generated/fluent-navigation-regular.marko +3 -0
- package/icons/generated/fluent-paint-brush-regular.marko +3 -0
- package/icons/generated/fluent-question-circle-regular.marko +3 -0
- package/icons/generated/fluent-search-regular.marko +3 -0
- package/icons/generated/fluent-settings-regular.marko +3 -0
- package/icons/generated/fluent-square-hint-regular.marko +3 -0
- package/icons/generated/fluent-subtract-regular.marko +3 -0
- package/icons/generated/fluent-text-align-center-regular.marko +3 -0
- package/icons/generated/fluent-text-align-left-regular.marko +3 -0
- package/icons/generated/fluent-text-align-right-regular.marko +3 -0
- package/icons/generated/fluent-text-bold-regular.marko +3 -0
- package/icons/generated/fluent-text-bullet-list-regular.marko +3 -0
- package/icons/generated/fluent-text-font-regular.marko +3 -0
- package/icons/generated/fluent-text-font-size-regular.marko +3 -0
- package/icons/generated/fluent-text-italic-regular.marko +3 -0
- package/icons/generated/fluent-text-number-list-ltr-regular.marko +3 -0
- package/icons/generated/fluent-video-regular.marko +3 -0
- package/icons/generated/fluent-warning-filled.marko +3 -0
- package/icons/generated/fluent-weather-moon-regular.marko +3 -0
- package/icons/generated/fluent-weather-sunny-regular.marko +3 -0
- package/icons/generated/fluent-zoom-in-regular.marko +3 -0
- package/icons/generated/fluent-zoom-out-regular.marko +3 -0
- package/icons/manifest.json +61 -0
- package/icons/social/social-facebook-color.marko +7 -0
- package/icons/social/social-facebook.marko +6 -0
- package/icons/social/social-google-color.marko +20 -0
- package/icons/social/social-google.marko +6 -0
- package/icons/social/social-instagram-color.marko +16 -0
- package/icons/social/social-instagram.marko +6 -0
- package/icons/social/social-linkedin-color.marko +8 -0
- package/icons/social/social-linkedin.marko +8 -0
- package/icons/social/social-messenger-color.marko +15 -0
- package/icons/social/social-messenger.marko +6 -0
- package/icons/social/social-pinterest-color.marko +6 -0
- package/icons/social/social-pinterest.marko +6 -0
- package/icons/social/social-telegram-color.marko +7 -0
- package/icons/social/social-telegram.marko +6 -0
- package/icons/social/social-whatsapp-color.marko +8 -0
- package/icons/social/social-whatsapp.marko +7 -0
- package/icons/social/social-x-color.marko +7 -0
- package/icons/social/social-x.marko +6 -0
- package/icons/social/social-youtube-color.marko +7 -0
- package/icons/social/social-youtube.marko +6 -0
- package/marko.json +203 -0
- package/package.json +55 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
<!-- sds-date-picker — ported from kdls-design-system v2.5.1 (0f3edf8)
|
|
2
|
+
components/DatePicker/DatePicker.tsx.
|
|
3
|
+
|
|
4
|
+
A trigger and a calendar: single date, range, date with time, or time alone. The month
|
|
5
|
+
grid, the bounds, the disabled days and times, and what the trigger says are all in
|
|
6
|
+
date-logic.js and tested there — 41 cases, because dates are where off-by-ones live and
|
|
7
|
+
none of them needs a browser to find.
|
|
8
|
+
|
|
9
|
+
Divergences:
|
|
10
|
+
|
|
11
|
+
The dark theme's range highlight was Kenlo blue at 15%, written as a literal in three
|
|
12
|
+
rules. It is the installed scheme's own colour at 15% now — see DatePicker.css.
|
|
13
|
+
|
|
14
|
+
The grid is a <table> with real headers. KDLS renders nested divs, so a screen reader
|
|
15
|
+
is read forty loose numbers with no way to know which column is which day: "15" without
|
|
16
|
+
"quarta-feira" is not a date. The weekday header is a full name, visually abbreviated.
|
|
17
|
+
|
|
18
|
+
Every day is a <button> rather than a div with a click handler, so the calendar can be
|
|
19
|
+
used from the keyboard at all — and arrow keys move through it, which KDLS does not
|
|
20
|
+
offer in any mode.
|
|
21
|
+
|
|
22
|
+
The two invalid combinations KDLS forbids in the type system are checked at render:
|
|
23
|
+
a range with a time, and time-only with disablePast. Neither means anything, and
|
|
24
|
+
silently ignoring one is how a caller ends up debugging a control that quietly does
|
|
25
|
+
half of what it was told. -->
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
MONTHS, WEEKDAYS, WEEKDAY_NAMES, pad2, formatTime, isSameDay, triggerLabel,
|
|
29
|
+
effectiveBounds, isDisabledDate, isDisabledTime, monthGrid, yearGrid, timeSlots,
|
|
30
|
+
isBetween, orderRange, DEFAULT_TIME_INTERVAL,
|
|
31
|
+
} from "./date-logic.js";
|
|
32
|
+
import { assertFeedback as assertInputFeedback, assertWidth as assertInputWidth }
|
|
33
|
+
from "../Input/input-types.js";
|
|
34
|
+
import calendarIcon from "../../icons/generated/fluent-calendar-regular.marko";
|
|
35
|
+
import chevronDown from "../../icons/generated/fluent-chevron-down-regular.marko";
|
|
36
|
+
import dismissIcon from "../../icons/generated/fluent-dismiss-regular.marko";
|
|
37
|
+
import clockIcon from "../../icons/generated/fluent-clock-regular.marko";
|
|
38
|
+
import chevronLeft from "../../icons/generated/fluent-chevron-left-regular.marko";
|
|
39
|
+
import chevronRight from "../../icons/generated/fluent-chevron-right-regular.marko";
|
|
40
|
+
|
|
41
|
+
class {
|
|
42
|
+
onCreate(input) {
|
|
43
|
+
const single = input.isRange ? null : (input.value ?? null);
|
|
44
|
+
const start = input.isRange ? (input.value?.start ?? null) : null;
|
|
45
|
+
this.state = {
|
|
46
|
+
open: false,
|
|
47
|
+
// `input.today`, not `new Date()`. The prop exists so a caller can say what "today"
|
|
48
|
+
// is — a test pinning a month, a page rendered on a server in another timezone — and
|
|
49
|
+
// every other read of today here goes through it. This one did not, so the calendar
|
|
50
|
+
// opened on the real month while everything else in it agreed on the given one.
|
|
51
|
+
view: input.defaultViewDate ?? single ?? start ?? input.today ?? new Date(),
|
|
52
|
+
mode: "calendar",
|
|
53
|
+
rangeStep: "start",
|
|
54
|
+
pendingDate: null,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get today() {
|
|
59
|
+
return this.input.today ?? new Date();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get bounds() {
|
|
63
|
+
const { minDate, maxDate, disablePast, disableFuture } = this.input;
|
|
64
|
+
return effectiveBounds({ minDate, maxDate, disablePast, disableFuture, today: this.today });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
dayDisabled(date) {
|
|
68
|
+
const { min, max } = this.bounds;
|
|
69
|
+
const { filterDate, disableToday, excludeDates, includeDates } = this.input;
|
|
70
|
+
return isDisabledDate(date, {
|
|
71
|
+
min, max, filterDate, disableToday, excludeDates, includeDates, today: this.today,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
slotDisabled(slot) {
|
|
76
|
+
const { min, max } = this.bounds;
|
|
77
|
+
const { onlyTime, hasTime, filterTime, includeTimes, excludeTimes, isRange } = this.input;
|
|
78
|
+
const single = isRange ? null : (this.input.value ?? null);
|
|
79
|
+
return isDisabledTime(slot, {
|
|
80
|
+
min,
|
|
81
|
+
max,
|
|
82
|
+
onlyTime,
|
|
83
|
+
hasTime,
|
|
84
|
+
reference: this.state.pendingDate ?? single,
|
|
85
|
+
filterTime,
|
|
86
|
+
includeTimes,
|
|
87
|
+
excludeTimes,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setOpen(next) {
|
|
92
|
+
if (this.input.disabled || this.input.isLoading) return;
|
|
93
|
+
this.state.open = next;
|
|
94
|
+
if (!next) this.state.mode = "calendar";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
navigate(direction) {
|
|
98
|
+
const view = this.state.view;
|
|
99
|
+
const year = view.getFullYear();
|
|
100
|
+
const month = view.getMonth();
|
|
101
|
+
if (this.state.mode === "year") {
|
|
102
|
+
this.state.view = new Date(yearGrid(year)[0] + direction * 16, month, 1);
|
|
103
|
+
} else if (this.state.mode === "month-grid") {
|
|
104
|
+
this.state.view = new Date(year + direction, month, 1);
|
|
105
|
+
} else {
|
|
106
|
+
this.state.view = new Date(year, month + direction, 1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
setMode(mode) { this.state.mode = mode; }
|
|
111
|
+
|
|
112
|
+
chooseMonth(month) {
|
|
113
|
+
this.state.view = new Date(this.state.view.getFullYear(), month, 1);
|
|
114
|
+
this.state.mode = "calendar";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
chooseYear(year) {
|
|
118
|
+
this.state.view = new Date(year, this.state.view.getMonth(), 1);
|
|
119
|
+
this.state.mode = "month-grid";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
chooseDay(date) {
|
|
123
|
+
if (this.dayDisabled(date)) return;
|
|
124
|
+
const { isRange, hasTime, onlyTime, onChange } = this.input;
|
|
125
|
+
|
|
126
|
+
if (!isRange) {
|
|
127
|
+
if (hasTime) {
|
|
128
|
+
// The date is held until a time is picked: a half-answer is not an answer.
|
|
129
|
+
this.state.pendingDate = date;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
onChange?.(date);
|
|
133
|
+
if (!onlyTime) this.setOpen(false);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const start = this.input.value?.start ?? null;
|
|
138
|
+
if (this.state.rangeStep === "start" || !start) {
|
|
139
|
+
onChange?.({ start: date, end: null });
|
|
140
|
+
this.state.rangeStep = "end";
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
onChange?.(orderRange(start, date));
|
|
144
|
+
this.state.rangeStep = "start";
|
|
145
|
+
this.setOpen(false);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
chooseTime(slot) {
|
|
149
|
+
if (this.slotDisabled(slot)) return;
|
|
150
|
+
const base = this.state.pendingDate ?? this.input.value ?? this.today;
|
|
151
|
+
this.state.pendingDate = null;
|
|
152
|
+
this.input.onChange?.(new Date(
|
|
153
|
+
base.getFullYear(), base.getMonth(), base.getDate(),
|
|
154
|
+
slot.getHours(), slot.getMinutes(),
|
|
155
|
+
));
|
|
156
|
+
this.setOpen(false);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Back to the current month. Navigation, not selection — nothing is chosen, the calendar
|
|
161
|
+
* just stops showing 2031 because you went looking there. Ported from KDLS `handleToday`,
|
|
162
|
+
* which SDS had dropped: after a few taps on "próximo mês" there was no way back but the
|
|
163
|
+
* same number of taps in the other direction.
|
|
164
|
+
*/
|
|
165
|
+
goToToday() {
|
|
166
|
+
this.state.view = this.today;
|
|
167
|
+
this.state.mode = "calendar";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
clear() {
|
|
171
|
+
this.input.onChange?.(this.input.isRange ? { start: null, end: null } : null);
|
|
172
|
+
this.state.pendingDate = null;
|
|
173
|
+
this.state.rangeStep = "start";
|
|
174
|
+
this.setOpen(false);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Arrow keys through the grid.
|
|
179
|
+
*
|
|
180
|
+
* KDLS binds click and nothing else, in every mode, so a calendar cannot be used without
|
|
181
|
+
* a mouse — on a control whose alternative is typing a date into a field it does not have.
|
|
182
|
+
*/
|
|
183
|
+
handleGridKey(date, event) {
|
|
184
|
+
const steps = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 };
|
|
185
|
+
const step = steps[event.key];
|
|
186
|
+
if (step === undefined) return;
|
|
187
|
+
event.preventDefault();
|
|
188
|
+
const next = new Date(date.getFullYear(), date.getMonth(), date.getDate() + step);
|
|
189
|
+
this.state.view = next;
|
|
190
|
+
// The grid is re-rendered around the new date; focus follows it by key.
|
|
191
|
+
requestAnimationFrame(() => {
|
|
192
|
+
this.getEl(`day-${next.getFullYear()}-${next.getMonth()}-${next.getDate()}`)?.focus();
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
$ {
|
|
198
|
+
const {
|
|
199
|
+
label,
|
|
200
|
+
trigger = "input",
|
|
201
|
+
width = "hug",
|
|
202
|
+
value,
|
|
203
|
+
isRange = false,
|
|
204
|
+
hasTime: hasTimeInput = false,
|
|
205
|
+
onlyTime: onlyTimeInput = false,
|
|
206
|
+
disabled = false,
|
|
207
|
+
isLoading = false,
|
|
208
|
+
feedback = "none",
|
|
209
|
+
hint,
|
|
210
|
+
name,
|
|
211
|
+
required = false,
|
|
212
|
+
timeIntervals = DEFAULT_TIME_INTERVAL,
|
|
213
|
+
clearLabel = "Limpar",
|
|
214
|
+
todayLabel = "Hoje",
|
|
215
|
+
// Distinct from `clearLabel` on purpose. Both clear, and while the panel is open both
|
|
216
|
+
// are on screen at once — two buttons announced as "Limpar" is a screen reader reading
|
|
217
|
+
// the same word twice for two different controls.
|
|
218
|
+
clearValueLabel,
|
|
219
|
+
class: extraClass = "",
|
|
220
|
+
} = input;
|
|
221
|
+
|
|
222
|
+
// The trigger renders an Input, so its width and feedback are the Input's vocabulary.
|
|
223
|
+
assertInputWidth(width);
|
|
224
|
+
assertInputFeedback(feedback);
|
|
225
|
+
|
|
226
|
+
// KDLS forbids these in the type system, which does not exist here. Silently ignoring
|
|
227
|
+
// half of what a caller asked for is how they end up debugging the wrong thing.
|
|
228
|
+
if (isRange && (hasTimeInput || onlyTimeInput)) {
|
|
229
|
+
throw new Error("sds-date-picker: `isRange` cannot be combined with `hasTime` or `onlyTime` — a range has two ends and a time has one.");
|
|
230
|
+
}
|
|
231
|
+
if (onlyTimeInput && (input.disablePast || input.disableFuture || input.disableToday)) {
|
|
232
|
+
throw new Error("sds-date-picker: `onlyTime` has no calendar, so `disablePast`, `disableFuture` and `disableToday` mean nothing with it.");
|
|
233
|
+
}
|
|
234
|
+
if (trigger !== "input" && trigger !== "pill") {
|
|
235
|
+
throw new Error(`Unknown date picker trigger: "${trigger}". Allowed: input, pill`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const hasTime = isRange ? false : hasTimeInput;
|
|
239
|
+
const onlyTime = isRange ? false : onlyTimeInput;
|
|
240
|
+
const single = isRange ? null : (value ?? null);
|
|
241
|
+
const start = isRange ? (value?.start ?? null) : null;
|
|
242
|
+
const end = isRange ? (value?.end ?? null) : null;
|
|
243
|
+
|
|
244
|
+
const text = triggerLabel({ isRange, value, hasTime, onlyTime });
|
|
245
|
+
const hasValue = isRange ? Boolean(value?.start || value?.end) : Boolean(value);
|
|
246
|
+
const clearedName = clearValueLabel
|
|
247
|
+
?? (onlyTime ? "Limpar a hora escolhida" : "Limpar a data escolhida");
|
|
248
|
+
const fallbackLabel = isRange ? "Período" : "Data";
|
|
249
|
+
const fieldLabel = label ?? fallbackLabel;
|
|
250
|
+
|
|
251
|
+
const view = state.view;
|
|
252
|
+
const year = view.getFullYear();
|
|
253
|
+
const month = view.getMonth();
|
|
254
|
+
const weeks = monthGrid(year, month);
|
|
255
|
+
const years = yearGrid(year);
|
|
256
|
+
const slots = (hasTime || onlyTime) ? timeSlots(timeIntervals, component.today) : [];
|
|
257
|
+
|
|
258
|
+
const showingTime = !state.pendingDate && (hasTime || onlyTime) && single;
|
|
259
|
+
const selectedHour = showingTime ? single.getHours() : null;
|
|
260
|
+
const selectedMinute = showingTime ? single.getMinutes() : null;
|
|
261
|
+
|
|
262
|
+
const headerLabel = state.mode === "year"
|
|
263
|
+
? `${years[0]} – ${years[15]}`
|
|
264
|
+
: state.mode === "month-grid" ? String(year) : `${MONTHS[month]} ${year}`;
|
|
265
|
+
|
|
266
|
+
const prevLabel = state.mode === "year" ? "Anos anteriores"
|
|
267
|
+
: state.mode === "month-grid" ? "Ano anterior" : "Mês anterior";
|
|
268
|
+
const nextLabel = state.mode === "year" ? "Próximos anos"
|
|
269
|
+
: state.mode === "month-grid" ? "Próximo ano" : "Próximo mês";
|
|
270
|
+
|
|
271
|
+
const hiddenValue = isRange
|
|
272
|
+
? (!start ? "" : !end ? start.toISOString() : `${start.toISOString()}/${end.toISOString()}`)
|
|
273
|
+
: (single ? single.toISOString() : "");
|
|
274
|
+
|
|
275
|
+
const panelClass = [
|
|
276
|
+
"sds-datepicker",
|
|
277
|
+
onlyTime ? "sds-datepicker--only-time" : "",
|
|
278
|
+
hasTime ? "sds-datepicker--has-time" : "",
|
|
279
|
+
disabled ? "sds-datepicker--disabled" : "",
|
|
280
|
+
].filter(Boolean).join(" ");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
<div class=[
|
|
284
|
+
"sds-datepicker-root",
|
|
285
|
+
width === "fill" ? "sds-datepicker-root--fill" : "",
|
|
286
|
+
extraClass,
|
|
287
|
+
].filter(Boolean).join(" ")>
|
|
288
|
+
<sds-floating
|
|
289
|
+
open=state.open
|
|
290
|
+
onOpenChange=(next) => component.setOpen(next)
|
|
291
|
+
placement="bottomLeft"
|
|
292
|
+
trigger="click"
|
|
293
|
+
>
|
|
294
|
+
<@content>
|
|
295
|
+
<div class=panelClass>
|
|
296
|
+
<if(!onlyTime)>
|
|
297
|
+
<div class="sds-datepicker__calendar-col">
|
|
298
|
+
<div class="sds-datepicker__header">
|
|
299
|
+
<sds-icon-button variant="neutral" size="sm" aria-label=prevLabel
|
|
300
|
+
onClick=() => component.navigate(-1)>
|
|
301
|
+
<@icon><${chevronLeft}/></@icon>
|
|
302
|
+
</sds-icon-button>
|
|
303
|
+
|
|
304
|
+
<sds-button
|
|
305
|
+
variant="link"
|
|
306
|
+
size="md"
|
|
307
|
+
label=headerLabel
|
|
308
|
+
onClick=() => component.setMode(state.mode === "calendar" ? "month-grid" : state.mode === "month-grid" ? "year" : "calendar")
|
|
309
|
+
/>
|
|
310
|
+
|
|
311
|
+
<sds-icon-button variant="neutral" size="sm" aria-label=nextLabel
|
|
312
|
+
onClick=() => component.navigate(1)>
|
|
313
|
+
<@icon><${chevronRight}/></@icon>
|
|
314
|
+
</sds-icon-button>
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
<if(state.mode === "calendar")>
|
|
318
|
+
<!-- A real table. KDLS nests divs, so a screen reader meets forty loose
|
|
319
|
+
numbers and cannot tell which column is which day — "15" without
|
|
320
|
+
"quarta-feira" is not a date. -->
|
|
321
|
+
<table class="sds-datepicker__grid" role="grid" aria-label=`${MONTHS[month]} ${year}`>
|
|
322
|
+
<thead>
|
|
323
|
+
<tr class="sds-datepicker__weekdays">
|
|
324
|
+
<for|initial, index| of=WEEKDAYS>
|
|
325
|
+
<th scope="col" abbr=WEEKDAY_NAMES[index]>
|
|
326
|
+
<span aria-hidden="true">${initial}</span>
|
|
327
|
+
<span class="sds-visually-hidden">${WEEKDAY_NAMES[index]}</span>
|
|
328
|
+
</th>
|
|
329
|
+
</for>
|
|
330
|
+
</tr>
|
|
331
|
+
</thead>
|
|
332
|
+
<tbody>
|
|
333
|
+
<for|week| of=weeks>
|
|
334
|
+
<tr class="sds-datepicker__week-row">
|
|
335
|
+
<for|cell| of=week>
|
|
336
|
+
<td class=[
|
|
337
|
+
"sds-datepicker__day-wrap",
|
|
338
|
+
isRange && isBetween(cell.date, start, end) ? "sds-datepicker__day-wrap--in-range" : "",
|
|
339
|
+
isRange && start && end && isSameDay(cell.date, start) ? "sds-datepicker__day-wrap--range-start" : "",
|
|
340
|
+
isRange && start && end && isSameDay(cell.date, end) ? "sds-datepicker__day-wrap--range-end" : "",
|
|
341
|
+
].filter(Boolean).join(" ")>
|
|
342
|
+
<button
|
|
343
|
+
key=`day-${cell.date.getFullYear()}-${cell.date.getMonth()}-${cell.date.getDate()}`
|
|
344
|
+
type="button"
|
|
345
|
+
class=[
|
|
346
|
+
"sds-datepicker__day",
|
|
347
|
+
!cell.current ? "sds-datepicker__day--other" : "",
|
|
348
|
+
isSameDay(cell.date, component.today) ? "sds-datepicker__day--today" : "",
|
|
349
|
+
(isSameDay(cell.date, single) || isSameDay(cell.date, start) || isSameDay(cell.date, end))
|
|
350
|
+
? "sds-datepicker__day--selected" : "",
|
|
351
|
+
component.dayDisabled(cell.date) ? "sds-datepicker__day--disabled" : "",
|
|
352
|
+
].filter(Boolean).join(" ")
|
|
353
|
+
disabled=(component.dayDisabled(cell.date) || null)
|
|
354
|
+
aria-current=(isSameDay(cell.date, component.today) ? "date" : null)
|
|
355
|
+
aria-selected=(isSameDay(cell.date, single) || isSameDay(cell.date, start) || isSameDay(cell.date, end) ? "true" : "false")
|
|
356
|
+
on-click("chooseDay", cell.date)
|
|
357
|
+
on-keydown("handleGridKey", cell.date)
|
|
358
|
+
>
|
|
359
|
+
${cell.date.getDate()}
|
|
360
|
+
</button>
|
|
361
|
+
</td>
|
|
362
|
+
</for>
|
|
363
|
+
</tr>
|
|
364
|
+
</for>
|
|
365
|
+
</tbody>
|
|
366
|
+
</table>
|
|
367
|
+
</if>
|
|
368
|
+
|
|
369
|
+
<else-if(state.mode === "month-grid")>
|
|
370
|
+
<div class="sds-datepicker__month-grid">
|
|
371
|
+
<for|name, index| of=MONTHS>
|
|
372
|
+
<button
|
|
373
|
+
type="button"
|
|
374
|
+
class=[
|
|
375
|
+
"sds-datepicker__month-item",
|
|
376
|
+
index === month ? "sds-datepicker__month-item--selected" : "",
|
|
377
|
+
index === component.today.getMonth() && year === component.today.getFullYear()
|
|
378
|
+
? "sds-datepicker__month-item--today" : "",
|
|
379
|
+
].filter(Boolean).join(" ")
|
|
380
|
+
on-click("chooseMonth", index)
|
|
381
|
+
>
|
|
382
|
+
${name.slice(0, 3)}
|
|
383
|
+
</button>
|
|
384
|
+
</for>
|
|
385
|
+
</div>
|
|
386
|
+
</else-if>
|
|
387
|
+
|
|
388
|
+
<else>
|
|
389
|
+
<div class="sds-datepicker__year-grid">
|
|
390
|
+
<for|candidate| of=years>
|
|
391
|
+
<button
|
|
392
|
+
type="button"
|
|
393
|
+
class=[
|
|
394
|
+
"sds-datepicker__year-item",
|
|
395
|
+
candidate === year ? "sds-datepicker__year-item--selected" : "",
|
|
396
|
+
candidate === component.today.getFullYear() ? "sds-datepicker__year-item--today" : "",
|
|
397
|
+
].filter(Boolean).join(" ")
|
|
398
|
+
on-click("chooseYear", candidate)
|
|
399
|
+
>
|
|
400
|
+
${candidate}
|
|
401
|
+
</button>
|
|
402
|
+
</for>
|
|
403
|
+
</div>
|
|
404
|
+
</else>
|
|
405
|
+
</div>
|
|
406
|
+
</if>
|
|
407
|
+
|
|
408
|
+
<if(hasTime || onlyTime)>
|
|
409
|
+
<div class="sds-datepicker__time-panel">
|
|
410
|
+
<div class="sds-datepicker__time-list sds-scroll sds-scroll--thin"
|
|
411
|
+
role="listbox" aria-label="Horário">
|
|
412
|
+
<for|slot| of=slots>
|
|
413
|
+
<button
|
|
414
|
+
type="button"
|
|
415
|
+
role="option"
|
|
416
|
+
aria-selected=(selectedHour === slot.getHours() && selectedMinute === slot.getMinutes() ? "true" : "false")
|
|
417
|
+
class=[
|
|
418
|
+
"sds-datepicker__time-slot",
|
|
419
|
+
selectedHour === slot.getHours() && selectedMinute === slot.getMinutes()
|
|
420
|
+
? "sds-datepicker__time-slot--selected" : "",
|
|
421
|
+
component.slotDisabled(slot) ? "sds-datepicker__time-slot--disabled" : "",
|
|
422
|
+
].filter(Boolean).join(" ")
|
|
423
|
+
disabled=(component.slotDisabled(slot) || null)
|
|
424
|
+
on-click("chooseTime", slot)
|
|
425
|
+
>
|
|
426
|
+
${formatTime(slot)}
|
|
427
|
+
</button>
|
|
428
|
+
</for>
|
|
429
|
+
</div>
|
|
430
|
+
</div>
|
|
431
|
+
</if>
|
|
432
|
+
|
|
433
|
+
<div class="sds-datepicker__footer">
|
|
434
|
+
<sds-button variant="link" size="md" label=clearLabel onClick=() => component.clear()/>
|
|
435
|
+
// No calendar, nothing to navigate back to: the time-only panel is a list of
|
|
436
|
+
// slots. KDLS makes the same exception.
|
|
437
|
+
<if(!onlyTime)>
|
|
438
|
+
<sds-button variant="link" size="md" label=todayLabel onClick=() => component.goToToday()/>
|
|
439
|
+
</if>
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
</@content>
|
|
443
|
+
|
|
444
|
+
<if(trigger === "pill")>
|
|
445
|
+
<!-- No `active`. A pill has two jobs and they colour differently: as a filter you
|
|
446
|
+
toggle, being on is a state of the data and `--on` fills it with the theme's
|
|
447
|
+
colour; as the trigger of a list, being open or holding a value is a state of
|
|
448
|
+
the control, and filling it would say the filter is applied when it is only the
|
|
449
|
+
thing you press. KDLS passes `active` to a Pill in Pagination — where the pill
|
|
450
|
+
IS the current page — and nowhere else. -->
|
|
451
|
+
<sds-pill
|
|
452
|
+
label=(text || fieldLabel)
|
|
453
|
+
width=width
|
|
454
|
+
iconLeft=(onlyTime ? clockIcon : calendarIcon)
|
|
455
|
+
iconRight=(hasValue ? dismissIcon : chevronDown)
|
|
456
|
+
onClickIconRight=(hasValue && !disabled ? () => component.clear() : undefined)
|
|
457
|
+
iconRightLabel=clearedName
|
|
458
|
+
isLoading=isLoading
|
|
459
|
+
disabled=disabled
|
|
460
|
+
aria-haspopup="dialog"
|
|
461
|
+
aria-expanded=(state.open ? "true" : "false")
|
|
462
|
+
/>
|
|
463
|
+
</if>
|
|
464
|
+
<else>
|
|
465
|
+
// The calendar sits on the left and the right side is the way out, which is how
|
|
466
|
+
// KDLS has it and how every other field here now reads: SearchBar swaps its
|
|
467
|
+
// magnifier for a clear button the moment there is something to clear. The chevron
|
|
468
|
+
// stands there while there is not, so the field still says it opens something.
|
|
469
|
+
<sds-input
|
|
470
|
+
label=fieldLabel
|
|
471
|
+
value=text
|
|
472
|
+
readOnly
|
|
473
|
+
disabled=disabled
|
|
474
|
+
required=required
|
|
475
|
+
feedback=feedback
|
|
476
|
+
hint=hint
|
|
477
|
+
isLoading=isLoading
|
|
478
|
+
width=width
|
|
479
|
+
iconLeft=(onlyTime ? clockIcon : calendarIcon)
|
|
480
|
+
iconRight=(hasValue ? dismissIcon : chevronDown)
|
|
481
|
+
onClickIconRight=(hasValue && !disabled ? () => component.clear() : undefined)
|
|
482
|
+
iconRightLabel=clearedName
|
|
483
|
+
/>
|
|
484
|
+
</else>
|
|
485
|
+
</sds-floating>
|
|
486
|
+
|
|
487
|
+
<if(name)>
|
|
488
|
+
<!-- What a form submits. The trigger is a read-only field or a pill, neither of which
|
|
489
|
+
posts anything on its own. -->
|
|
490
|
+
<input type="hidden" name=name value=hiddenValue/>
|
|
491
|
+
</if>
|
|
492
|
+
</div>
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/* SDS Drawer.
|
|
2
|
+
Ported from kdls-design-system v2.5.1 (0f3edf8) components/Drawer/Drawer.css
|
|
3
|
+
by scripts/port-css.mjs. Divergences beyond the mechanical rename are noted inline.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* Two values were written outside the system and are snapped back to it here.
|
|
7
|
+
|
|
8
|
+
The panel shadow was rgba(26,26,26,0.15). The opacity scale runs 05/10/20/30/40/50, so
|
|
9
|
+
0.15 is not a step on it — it reads --color-opacity-20 now, a touch stronger, which is
|
|
10
|
+
the direction a shadow separating a panel from the page wants to go.
|
|
11
|
+
|
|
12
|
+
The dark-mode veil and shadow were black literals. --color-opacity-* inverts with the
|
|
13
|
+
theme and would light the screen up instead of dimming it, which is why they were
|
|
14
|
+
literals at all; --color-scrim is that value as a token. *//* ============================================================
|
|
15
|
+
SDS Drawer
|
|
16
|
+
Width: 600px (default) | full height | slides from right/left
|
|
17
|
+
Header: Bold 24px, border-bottom neutral-30
|
|
18
|
+
Footer: py-16, border-top neutral-30
|
|
19
|
+
============================================================ */
|
|
20
|
+
|
|
21
|
+
/* ── Overlay ── */
|
|
22
|
+
.sds-drawer-overlay {
|
|
23
|
+
position: fixed;
|
|
24
|
+
inset: 0;
|
|
25
|
+
background: var(--color-opacity-50);
|
|
26
|
+
z-index: var(--z-overlay);
|
|
27
|
+
animation: sds-overlay-in 120ms ease;
|
|
28
|
+
}
|
|
29
|
+
@keyframes sds-overlay-in {
|
|
30
|
+
from { opacity: 0; }
|
|
31
|
+
to { opacity: 1; }
|
|
32
|
+
}
|
|
33
|
+
/* ── Container: slides from right, no border-radius ── */
|
|
34
|
+
.sds-drawer {
|
|
35
|
+
position: fixed;
|
|
36
|
+
top: 0;
|
|
37
|
+
right: 0;
|
|
38
|
+
bottom: 0;
|
|
39
|
+
width: var(--drawer-width, 600px);
|
|
40
|
+
max-width: 100vw;
|
|
41
|
+
background: var(--component-bg-color);
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
z-index: calc(var(--z-overlay) + 1);
|
|
45
|
+
animation: sds-drawer-in 250ms var(--ease-standard);
|
|
46
|
+
box-shadow: -4px 0px 24px var(--color-opacity-20);
|
|
47
|
+
}
|
|
48
|
+
.sds-drawer--left {
|
|
49
|
+
right: auto;
|
|
50
|
+
left: 0;
|
|
51
|
+
box-shadow: 4px 0px 24px var(--color-opacity-20);
|
|
52
|
+
animation: sds-drawer-in-left 250ms var(--ease-standard);
|
|
53
|
+
}
|
|
54
|
+
@keyframes sds-drawer-in {
|
|
55
|
+
from { transform: translateX(100%); }
|
|
56
|
+
to { transform: translateX(0); }
|
|
57
|
+
}
|
|
58
|
+
@keyframes sds-drawer-in-left {
|
|
59
|
+
from { transform: translateX(-100%); }
|
|
60
|
+
to { transform: translateX(0); }
|
|
61
|
+
}
|
|
62
|
+
/* ── Header: pt-[24px] pb-[24px] px-[24px] + border-bottom ── */
|
|
63
|
+
.sds-drawer__header {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: var(--spacing-4);
|
|
67
|
+
padding: var(--spacing-6);
|
|
68
|
+
border-bottom: 1px solid var(--border-interactive-color);
|
|
69
|
+
background: var(--component-bg-color);
|
|
70
|
+
flex-shrink: 0;
|
|
71
|
+
}
|
|
72
|
+
.sds-drawer__title-group {
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
gap: var(--spacing-4);
|
|
76
|
+
flex: 1;
|
|
77
|
+
min-width: 0;
|
|
78
|
+
}
|
|
79
|
+
.sds-drawer__title-group .sds-tooltip-wrapper {
|
|
80
|
+
flex-shrink: 0;
|
|
81
|
+
}
|
|
82
|
+
/* Title: Manrope Bold 24px, tracking -0.8px, neutral-90 */
|
|
83
|
+
.sds-drawer__title {
|
|
84
|
+
font-family: var(--font-family-base);
|
|
85
|
+
font-size: var(--font-size-title-lg);
|
|
86
|
+
font-weight: var(--font-weight-bold);
|
|
87
|
+
line-height: var(--lh-title-lg);
|
|
88
|
+
letter-spacing: -0.8px;
|
|
89
|
+
color: var(--color-neutral-90);
|
|
90
|
+
overflow: hidden;
|
|
91
|
+
white-space: nowrap;
|
|
92
|
+
text-overflow: ellipsis;
|
|
93
|
+
}
|
|
94
|
+
.sds-drawer__header-actions {
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
gap: var(--spacing-4);
|
|
98
|
+
flex-shrink: 0;
|
|
99
|
+
}
|
|
100
|
+
/* ── Body: scrollable, 24px top/bottom padding ── */
|
|
101
|
+
.sds-drawer__body {
|
|
102
|
+
flex: 1;
|
|
103
|
+
overflow-y: auto;
|
|
104
|
+
padding: var(--spacing-6);
|
|
105
|
+
}
|
|
106
|
+
/* ── Footer: px-[24px] py-[16px] + border-top ── */
|
|
107
|
+
.sds-drawer__footer {
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
justify-content: flex-end;
|
|
111
|
+
/* 16px between the buttons, decided by Rafael looking at a three-button footer. KDLS
|
|
112
|
+
uses 24 and the Card here already used 16, so the system disagreed with itself; 16 is
|
|
113
|
+
the side that keeps a row of actions reading as one group. */
|
|
114
|
+
gap: var(--spacing-4);
|
|
115
|
+
padding: var(--spacing-4) var(--spacing-6);
|
|
116
|
+
border-top: 1px solid var(--border-interactive-color);
|
|
117
|
+
background: var(--component-bg-color);
|
|
118
|
+
flex-shrink: 0;
|
|
119
|
+
}
|
|
120
|
+
/* ── Confirmation modal — rendered above the drawer ── */
|
|
121
|
+
.sds-modal-overlay:has(.sds-drawer__confirm-modal) {
|
|
122
|
+
z-index: calc(var(--z-overlay) + 100);
|
|
123
|
+
}
|
|
124
|
+
/* ── Dark mode ── */
|
|
125
|
+
[data-theme="dark"] .sds-drawer-overlay {
|
|
126
|
+
background: var(--color-scrim);
|
|
127
|
+
}
|
|
128
|
+
[data-theme="dark"] .sds-drawer {
|
|
129
|
+
box-shadow: -4px 0px 24px var(--color-scrim);
|
|
130
|
+
}
|
|
131
|
+
[data-theme="dark"] .sds-drawer--left {
|
|
132
|
+
box-shadow: 4px 0px 24px var(--color-scrim);
|
|
133
|
+
}
|
|
134
|
+
[data-theme="dark"] .sds-drawer__header {
|
|
135
|
+
border-bottom-color: var(--border-interactive-color);
|
|
136
|
+
}
|
|
137
|
+
[data-theme="dark"] .sds-drawer__footer {
|
|
138
|
+
border-top-color: var(--border-interactive-color);
|
|
139
|
+
}
|
|
140
|
+
/* ── Mobile (≤799px): same lateral drawer as desktop, just full width —
|
|
141
|
+
the base rule's max-width: 100vw only kicks in below 600px, so widths
|
|
142
|
+
between 600 and 799px need an explicit override here. The footer also
|
|
143
|
+
switches to primary/secondary/tertiary stacked full-width instead of
|
|
144
|
+
sitting in a row ── */
|
|
145
|
+
@media (max-width: 799px) {
|
|
146
|
+
.sds-drawer {
|
|
147
|
+
width: 100%;
|
|
148
|
+
}
|
|
149
|
+
.sds-drawer__footer {
|
|
150
|
+
flex-direction: column-reverse;
|
|
151
|
+
align-items: stretch;
|
|
152
|
+
gap: var(--spacing-4);
|
|
153
|
+
}
|
|
154
|
+
.sds-drawer__footer .sds-button {
|
|
155
|
+
width: 100%;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* The confirmation, drawn inside the drawer's own overlay and wearing the modal's panel —
|
|
160
|
+
a question about leaving is a dialog, not a second drawer. A second veil over the first
|
|
161
|
+
would double the dimming, so this one only holds the panel and centres it. */
|
|
162
|
+
.sds-drawer-overlay--confirm {
|
|
163
|
+
background: transparent;
|
|
164
|
+
/* Above the panel, not below it. It was `z-index: 1` while `.sds-drawer` sits at
|
|
165
|
+
`--z-overlay + 1` — both fixed, both children of the same container — so the question
|
|
166
|
+
was painted underneath the thing it was asking about. Measured in Chrome: the element
|
|
167
|
+
at the centre of the confirmation was `.sds-drawer__body`. You pressed close, an
|
|
168
|
+
invisible dialog opened, and the drawer would not close: stuck, with nothing on
|
|
169
|
+
screen to say why. */
|
|
170
|
+
z-index: calc(var(--z-overlay) + 2);
|
|
171
|
+
display: flex;
|
|
172
|
+
align-items: center;
|
|
173
|
+
justify-content: center;
|
|
174
|
+
}
|