@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,340 @@
|
|
|
1
|
+
<!-- sds-bottom-sheet — ported from kdls-design-system v2.5.1 (0f3edf8)
|
|
2
|
+
components/BottomSheet/BottomSheet.tsx.
|
|
3
|
+
|
|
4
|
+
A panel that rises from the bottom of the screen with a list of choices in it. It is
|
|
5
|
+
the mobile shape of a Select, and on a phone it is the better one: a dropdown anchored
|
|
6
|
+
to a control near the bottom of a 812px screen has nowhere to open, and a list of
|
|
7
|
+
twenty options in a floating panel is a list nobody can scroll without closing it.
|
|
8
|
+
|
|
9
|
+
It shares foundations/overlay.js with Modal and Drawer — the scroll lock, the focus
|
|
10
|
+
trap, Escape — and the option vocabulary with Select, so `options` means the same
|
|
11
|
+
thing in both and a value moves between them without translation.
|
|
12
|
+
|
|
13
|
+
Divergences:
|
|
14
|
+
|
|
15
|
+
KDLS draws each option as an <li> with a mousedown handler. That is not focusable, not
|
|
16
|
+
activatable by keyboard, and invisible to anything that goes looking for controls —
|
|
17
|
+
on a list whose whole purpose is choosing. Here each row is a real <button> inside its
|
|
18
|
+
<li>, with the browser's chrome taken off in CSS.
|
|
19
|
+
|
|
20
|
+
KDLS closes on any mousedown on the overlay, including the mousedown that ends a drag
|
|
21
|
+
started inside the sheet. This closes on a press that both starts and ends outside.
|
|
22
|
+
|
|
23
|
+
No portal, for the reason in components/Floating/floating.marko: Marko 5 has none, and
|
|
24
|
+
a node moved to <body> returns on the next re-render. The sheet is position:fixed and
|
|
25
|
+
covers the viewport, so the only thing lost is escaping a transformed ancestor. -->
|
|
26
|
+
|
|
27
|
+
import { normaliseOptions, filterOptions, toArray, toggleValue, isSelected,
|
|
28
|
+
selectableValues, selectAllState } from "../Select/select-options.js";
|
|
29
|
+
import { lockScroll, unlockScroll, trapFocus } from "../../foundations/overlay.js";
|
|
30
|
+
import dismissIcon from "../../icons/generated/fluent-dismiss-regular.marko";
|
|
31
|
+
import searchIcon from "../../icons/generated/fluent-search-regular.marko";
|
|
32
|
+
import checkIcon from "../../icons/generated/fluent-checkmark-regular.marko";
|
|
33
|
+
|
|
34
|
+
class {
|
|
35
|
+
onCreate(input) {
|
|
36
|
+
this.state = {
|
|
37
|
+
query: "",
|
|
38
|
+
value: input.value ?? input.defaultValue ?? (input.multiple ? [] : null),
|
|
39
|
+
};
|
|
40
|
+
this.release = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onMount() {
|
|
44
|
+
this.onKeyDown = (event) => {
|
|
45
|
+
if (event.key !== "Escape" || !this.input.isOpen) return;
|
|
46
|
+
event.stopPropagation();
|
|
47
|
+
this.input.onClose?.();
|
|
48
|
+
};
|
|
49
|
+
document.addEventListener("keydown", this.onKeyDown);
|
|
50
|
+
if (this.input.isOpen) this.open();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
onUpdate() {
|
|
54
|
+
if (this.input.isOpen && !this.locked) this.open();
|
|
55
|
+
else if (!this.input.isOpen && this.locked) this.shut();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onDestroy() {
|
|
59
|
+
document.removeEventListener("keydown", this.onKeyDown);
|
|
60
|
+
this.shut();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
open() {
|
|
64
|
+
this.locked = true;
|
|
65
|
+
lockScroll();
|
|
66
|
+
requestAnimationFrame(() => {
|
|
67
|
+
if (!this.input.isOpen) return;
|
|
68
|
+
this.release = trapFocus(this.getEl("panel"));
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
shut() {
|
|
73
|
+
if (!this.locked) return;
|
|
74
|
+
this.locked = false;
|
|
75
|
+
unlockScroll();
|
|
76
|
+
this.release?.();
|
|
77
|
+
this.release = null;
|
|
78
|
+
this.state.query = "";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get currentValue() {
|
|
82
|
+
return this.input.value !== undefined ? this.input.value : this.state.value;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get options() {
|
|
86
|
+
return normaliseOptions(this.input);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
commit(next) {
|
|
90
|
+
if (this.input.value === undefined) this.state.value = next;
|
|
91
|
+
this.input.onChange?.(next);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
choose(option) {
|
|
95
|
+
if (option.disabled) return;
|
|
96
|
+
if (this.input.multiple) {
|
|
97
|
+
this.commit(toggleValue(toArray(this.currentValue), option.value));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this.commit(option.value);
|
|
101
|
+
// A single choice is the whole interaction: staying open asks the visitor to close a
|
|
102
|
+
// sheet whose job is done.
|
|
103
|
+
this.input.onClose?.();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The rows on screen — what a search has left, which is what the reader can act on. */
|
|
107
|
+
get visibleOptions() {
|
|
108
|
+
const all = this.options;
|
|
109
|
+
return this.input.filterOption === false ? all : filterOptions(all, this.state.query);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* "Selecionar todos" — todos of what is on screen, merged rather than replacing.
|
|
114
|
+
*
|
|
115
|
+
* It read every option and replaced the whole selection, so under a search it selected
|
|
116
|
+
* rows the reader could not see and threw away the ones chosen outside the filter.
|
|
117
|
+
* Ticking a box must never remove a choice. Same fix the Select needed.
|
|
118
|
+
*/
|
|
119
|
+
toggleAll() {
|
|
120
|
+
const visible = selectableValues(this.visibleOptions);
|
|
121
|
+
const current = toArray(this.currentValue);
|
|
122
|
+
const { checked } = selectAllState(this.visibleOptions, this.currentValue);
|
|
123
|
+
this.commit(checked
|
|
124
|
+
? current.filter((v) => !visible.includes(v))
|
|
125
|
+
: [...current, ...visible.filter((v) => !current.includes(v))]);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
clear() {
|
|
129
|
+
this.commit(this.input.multiple ? [] : null);
|
|
130
|
+
// And close. KDLS's own prop doc says so — "resets the value to empty and closes the
|
|
131
|
+
// sheet" — and leaving it open puts the reader back in a list they have just emptied,
|
|
132
|
+
// with no indication that anything happened.
|
|
133
|
+
this.input.onClose?.();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
handleSearch(event) {
|
|
137
|
+
this.state.query = event.target.value;
|
|
138
|
+
this.input.onSearch?.(event.target.value);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Closes only on a press that both starts and ends outside the sheet. */
|
|
142
|
+
handleOverlayDown(event) {
|
|
143
|
+
if (this.getEl("panel")?.contains(event.target)) return;
|
|
144
|
+
this.input.onClose?.();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
$ {
|
|
149
|
+
const {
|
|
150
|
+
isOpen,
|
|
151
|
+
title,
|
|
152
|
+
searchable = false,
|
|
153
|
+
searchPlaceholder = "Digite para buscar...",
|
|
154
|
+
multiple = false,
|
|
155
|
+
showSelectAll = false,
|
|
156
|
+
selectAllLabel = "Selecionar todos",
|
|
157
|
+
clearable = false,
|
|
158
|
+
emptyMessage = "Nenhuma opção",
|
|
159
|
+
closeLabel = "Fechar",
|
|
160
|
+
applyLabel = "Aplicar",
|
|
161
|
+
clearLabel = "Limpar",
|
|
162
|
+
class: extraClass = "",
|
|
163
|
+
} = input;
|
|
164
|
+
|
|
165
|
+
const value = component.currentValue;
|
|
166
|
+
const all = component.options;
|
|
167
|
+
const visible = input.filterOption === false ? all : filterOptions(all, state.query);
|
|
168
|
+
// Reads the visible rows, because that is what ticking it acts on. Reading the whole
|
|
169
|
+
// list made the box claim "all chosen" while the filtered rows in front of you were not.
|
|
170
|
+
const allState = selectAllState(visible, value);
|
|
171
|
+
const hasValue = toArray(value).length > 0;
|
|
172
|
+
|
|
173
|
+
// A sheet with a list needs a name for that list; a sheet holding arbitrary body content
|
|
174
|
+
// is named by its title alone.
|
|
175
|
+
// `component.id`, not a random string: Marko's is the same on the server and in the
|
|
176
|
+
// browser, and a random one drawn in the render block is neither.
|
|
177
|
+
const listId = `sds-bottom-sheet-${component.id}`;
|
|
178
|
+
const showList = all.length > 0 || !input.renderBody;
|
|
179
|
+
// `(clearable || true)` was always true, so this read as `multiple` alone: a
|
|
180
|
+
// single-select sheet asked to be `clearable` got no footer at all and the "Limpar" it
|
|
181
|
+
// was promised never appeared. A constant comparison hiding inside a condition, the same
|
|
182
|
+
// shape the Skeleton shapes had.
|
|
183
|
+
const showFooter = Boolean(input.footer) || multiple || clearable;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
<if(isOpen)>
|
|
187
|
+
<div class="sds-bottomsheet__overlay" on-mousedown("handleOverlayDown")>
|
|
188
|
+
<div
|
|
189
|
+
key="panel"
|
|
190
|
+
class=`sds-bottomsheet ${extraClass}`.trim()
|
|
191
|
+
role="dialog"
|
|
192
|
+
aria-modal="true"
|
|
193
|
+
aria-label=title
|
|
194
|
+
>
|
|
195
|
+
<div class="sds-bottomsheet__title-row">
|
|
196
|
+
<if(title)>
|
|
197
|
+
<sds-text as="span" variant="labelSemiBold" class="sds-bottomsheet__title">${title}</sds-text>
|
|
198
|
+
</if>
|
|
199
|
+
<sds-icon-button
|
|
200
|
+
class="sds-bottomsheet__close"
|
|
201
|
+
variant="neutral"
|
|
202
|
+
aria-label=closeLabel
|
|
203
|
+
onClick=() => input.onClose?.()
|
|
204
|
+
>
|
|
205
|
+
<@icon><${dismissIcon}/></@icon>
|
|
206
|
+
</sds-icon-button>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<if(searchable)>
|
|
210
|
+
<div class="sds-bottomsheet__search">
|
|
211
|
+
<div class="sds-bottomsheet__searchable-field">
|
|
212
|
+
<span class="sds-bottomsheet__searchable-icon" aria-hidden="true">
|
|
213
|
+
<sds-icon size="sm"><${searchIcon}/></sds-icon>
|
|
214
|
+
</span>
|
|
215
|
+
<input
|
|
216
|
+
class="sds-bottomsheet__searchable-input"
|
|
217
|
+
type="search"
|
|
218
|
+
value=state.query
|
|
219
|
+
placeholder=searchPlaceholder
|
|
220
|
+
aria-label=searchPlaceholder
|
|
221
|
+
on-input("handleSearch")
|
|
222
|
+
/>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
</if>
|
|
226
|
+
|
|
227
|
+
<if(multiple && showSelectAll)>
|
|
228
|
+
// The listener is on the wrapper. `sds-checkbox` is markup: it does not hydrate,
|
|
229
|
+
// it takes no `onChange`, and says so in its own header — so the handler here was
|
|
230
|
+
// waiting for a callback that is never called. The box ticked natively and nothing
|
|
231
|
+
// happened, exactly as in the Select. The native `change` bubbles; the wrapper
|
|
232
|
+
// hears it.
|
|
233
|
+
//
|
|
234
|
+
// It also wore `__header`, the sheet's own title bar class, which is 16px padding
|
|
235
|
+
// all round and a row layout meant for a title and a close button. Its own class
|
|
236
|
+
// now, so changing one does not move the other.
|
|
237
|
+
<div class="sds-bottomsheet__select-all" on-change("toggleAll")>
|
|
238
|
+
<sds-checkbox
|
|
239
|
+
label=selectAllLabel
|
|
240
|
+
checked=allState.checked
|
|
241
|
+
indeterminate=allState.indeterminate
|
|
242
|
+
/>
|
|
243
|
+
</div>
|
|
244
|
+
</if>
|
|
245
|
+
|
|
246
|
+
<div class="sds-bottomsheet__body sds-scroll sds-scroll--thin">
|
|
247
|
+
<if(showList)>
|
|
248
|
+
<ul class="sds-bottomsheet__list" id=listId role="listbox" aria-label=title
|
|
249
|
+
aria-multiselectable=(multiple ? "true" : null)>
|
|
250
|
+
<if(!visible.length)>
|
|
251
|
+
<li class="sds-bottomsheet__empty">${emptyMessage}</li>
|
|
252
|
+
</if>
|
|
253
|
+
<for|option| of=visible>
|
|
254
|
+
<if(option.section)>
|
|
255
|
+
// A section heading, not a header. It wore `__header`, whose rule is a
|
|
256
|
+
// 16px-padded flex COLUMN with a bottom border — a container for the search
|
|
257
|
+
// box and the select-all row, not a label. So "Capitais" rendered as a
|
|
258
|
+
// block the height of a panel section with a rule under it.
|
|
259
|
+
<li class="sds-bottomsheet__section" role="presentation">${option.label}</li>
|
|
260
|
+
</if>
|
|
261
|
+
<else>
|
|
262
|
+
<li
|
|
263
|
+
class=[
|
|
264
|
+
"sds-bottomsheet__item",
|
|
265
|
+
isSelected(value, option.value) ? "sds-bottomsheet__item--active" : "",
|
|
266
|
+
option.disabled ? "sds-bottomsheet__item--disabled" : "",
|
|
267
|
+
].filter(Boolean).join(" ")
|
|
268
|
+
role="none"
|
|
269
|
+
>
|
|
270
|
+
<button
|
|
271
|
+
type="button"
|
|
272
|
+
class="sds-bottomsheet__item-button"
|
|
273
|
+
role="option"
|
|
274
|
+
aria-selected=(isSelected(value, option.value) ? "true" : "false")
|
|
275
|
+
disabled=(option.disabled || null)
|
|
276
|
+
on-click("choose", option)
|
|
277
|
+
>
|
|
278
|
+
<if(multiple)>
|
|
279
|
+
<span class="sds-bottomsheet__item-checkbox" aria-hidden="true">
|
|
280
|
+
<sds-checkbox checked=isSelected(value, option.value) disabled=option.disabled/>
|
|
281
|
+
</span>
|
|
282
|
+
</if>
|
|
283
|
+
<if(option.icon)>
|
|
284
|
+
<span class="sds-bottomsheet__item-icon-left" aria-hidden="true">
|
|
285
|
+
<sds-icon><${(option.icon && option.icon.renderBody) || option.icon}/></sds-icon>
|
|
286
|
+
</span>
|
|
287
|
+
</if>
|
|
288
|
+
<span class="sds-bottomsheet__item-label">${option.label}</span>
|
|
289
|
+
<if(!multiple && isSelected(value, option.value))>
|
|
290
|
+
// The mark on the chosen row. `.sds-bottomsheet__item-check` has been
|
|
291
|
+
// in the stylesheet since the port with nothing rendering it, so a
|
|
292
|
+
// single-select sheet showed which row was chosen only by its
|
|
293
|
+
// background — and `aria-selected` said so to a screen reader while
|
|
294
|
+
// the eye had to infer it.
|
|
295
|
+
<span class="sds-bottomsheet__item-check" aria-hidden="true">
|
|
296
|
+
<sds-icon size="sm"><${checkIcon}/></sds-icon>
|
|
297
|
+
</span>
|
|
298
|
+
</if>
|
|
299
|
+
</button>
|
|
300
|
+
</li>
|
|
301
|
+
</else>
|
|
302
|
+
</for>
|
|
303
|
+
</ul>
|
|
304
|
+
</if>
|
|
305
|
+
<else>
|
|
306
|
+
<${input.renderBody}/>
|
|
307
|
+
</else>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
<if(input.footer)>
|
|
311
|
+
<div class="sds-bottomsheet__footer">
|
|
312
|
+
<${input.footer}/>
|
|
313
|
+
</div>
|
|
314
|
+
</if>
|
|
315
|
+
<else-if(showFooter)>
|
|
316
|
+
// `clearable` OR `multiple`, which is KDLS's gate. It read `multiple` alone here,
|
|
317
|
+
// so a single-select sheet asked to be clearable got no footer and the "Limpar" it
|
|
318
|
+
// was promised never appeared. (The `showFooter` above said the same thing in a
|
|
319
|
+
// way that could not be false — `multiple && (clearable || true)` — and nothing
|
|
320
|
+
// read it; the template decided again, differently, down here.)
|
|
321
|
+
<div class="sds-bottomsheet__footer">
|
|
322
|
+
<div class="sds-bottomsheet__footer-actions">
|
|
323
|
+
<if(clearable)>
|
|
324
|
+
<sds-button variant="ghost" size="md" label=clearLabel
|
|
325
|
+
disabled=(!hasValue) onClick=() => component.clear()/>
|
|
326
|
+
</if>
|
|
327
|
+
// Only a multiple-select sheet needs one: a single choice closes the sheet by
|
|
328
|
+
// being made, so an "Aplicar" there is a button with nothing left to apply.
|
|
329
|
+
// Full width when it stands alone, which is what KDLS does.
|
|
330
|
+
<if(multiple)>
|
|
331
|
+
<sds-button variant="primary" size="md" label=applyLabel
|
|
332
|
+
width=(clearable ? "hug" : "fill")
|
|
333
|
+
onClick=() => input.onClose?.()/>
|
|
334
|
+
</if>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
</else-if>
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
</if>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/* SDS BulkActionBar.
|
|
2
|
+
Ported from kdls-design-system v2.5.1 (0f3edf8) components/BulkActionBar/BulkActionBar.css
|
|
3
|
+
by scripts/port-css.mjs. Divergences beyond the mechanical rename are noted inline.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* Two shadows were written as literals and are snapped to the system here, the same two
|
|
7
|
+
the Drawer had: 0.15 is not a step on the opacity scale (05/10/20/30/40/50) so it reads
|
|
8
|
+
--color-opacity-20, and the dark one was black because --color-opacity-* inverts with the
|
|
9
|
+
theme — that value is --color-scrim. *//* ── BulkActionBar ──────────────────────────────────────────────────────
|
|
10
|
+
Always-fixed bar shown when one or more rows are selected — rendered via
|
|
11
|
+
portal into document.body, always pinned to the bottom of the viewport.
|
|
12
|
+
BEM root: .sds-bulk-action-bar
|
|
13
|
+
z-index: 100 — see component-rules.md z-index scale
|
|
14
|
+
─────────────────────────────────────────────────────────────────── */
|
|
15
|
+
|
|
16
|
+
.sds-bulk-action-bar {
|
|
17
|
+
position: fixed;
|
|
18
|
+
bottom: 0;
|
|
19
|
+
left: 0;
|
|
20
|
+
right: 0;
|
|
21
|
+
height: 80px;
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
gap: var(--spacing-6);
|
|
25
|
+
padding: 0 var(--spacing-6);
|
|
26
|
+
background: var(--component-bg-color);
|
|
27
|
+
border-top: 1px solid var(--border-static-color);
|
|
28
|
+
box-shadow: 0 -4px 24px var(--color-opacity-20);
|
|
29
|
+
z-index: var(--z-sticky);
|
|
30
|
+
}
|
|
31
|
+
/* ── Identity — countLabel + "Limpar" (default), or ✕ + countLabel (dismiss mode) ──
|
|
32
|
+
Gap is asymmetric: to the right of countLabel it's spacing-6 (default mode, before "Limpar");
|
|
33
|
+
to the left of countLabel (dismiss mode, after the ✕) it stays the tighter spacing-2. ── */
|
|
34
|
+
/* Divergence: the identity is pinned left in both modes.
|
|
35
|
+
|
|
36
|
+
KDLS pushes it to the far right when the bar is in clear mode, so the count — the one
|
|
37
|
+
thing on this bar that changes, and the thing it announces — moved depending on which
|
|
38
|
+
of two props the caller passed. The bar is fixed over everything while it is up; what
|
|
39
|
+
it says about itself belongs at the start of the line, and the actions at the end. */
|
|
40
|
+
.sds-bulk-action-bar__identity {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
gap: var(--spacing-4);
|
|
44
|
+
flex-shrink: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.sds-bulk-action-bar--dismiss .sds-bulk-action-bar__identity {
|
|
48
|
+
gap: var(--spacing-2);
|
|
49
|
+
}
|
|
50
|
+
.sds-bulk-action-bar__label {
|
|
51
|
+
display: inline-flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
gap: var(--spacing-2);
|
|
54
|
+
font-family: var(--font-family-base);
|
|
55
|
+
}
|
|
56
|
+
/* ── Divider — same height as the primary Button (48px). Absent entirely in dismiss mode (not rendered) ── */
|
|
57
|
+
/* ── Groups + secondary + primary ── */
|
|
58
|
+
/* The actions end the line, whichever way the identity was built. */
|
|
59
|
+
.sds-bulk-action-bar__groups {
|
|
60
|
+
display: flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
gap: var(--spacing-6);
|
|
63
|
+
margin-left: auto;
|
|
64
|
+
}
|
|
65
|
+
.sds-bulk-action-bar--dismiss .sds-bulk-action-bar__groups {
|
|
66
|
+
margin-left: auto; /* dismiss mode: this cluster is what gets pushed right, identity stays left */
|
|
67
|
+
}
|
|
68
|
+
.sds-bulk-action-bar__group {
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-direction: column;
|
|
71
|
+
gap: 0;
|
|
72
|
+
}
|
|
73
|
+
.sds-bulk-action-bar__group-label {
|
|
74
|
+
font-family: var(--font-family-base);
|
|
75
|
+
font-size: var(--font-size-label-sm);
|
|
76
|
+
font-weight: var(--font-weight-medium);
|
|
77
|
+
color: var(--label-color);
|
|
78
|
+
}
|
|
79
|
+
.sds-bulk-action-bar__group-actions {
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
gap: var(--spacing-4);
|
|
83
|
+
}
|
|
84
|
+
/* ── Dark mode ── */
|
|
85
|
+
[data-theme="dark"] .sds-bulk-action-bar {
|
|
86
|
+
border-top-color: var(--border-interactive-color);
|
|
87
|
+
box-shadow: 0 -4px 24px var(--color-scrim);
|
|
88
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
<!-- sds-bulk-action-bar — ported from kdls-design-system v2.5.1 (0f3edf8)
|
|
2
|
+
components/BulkActionBar/BulkActionBar.tsx.
|
|
3
|
+
|
|
4
|
+
The bar that appears when rows are selected: how many, a way to let go of them, and
|
|
5
|
+
the things that can be done to them. Every action follows a promise the caller
|
|
6
|
+
returns, which is what lets a button spin without being told to.
|
|
7
|
+
|
|
8
|
+
`onClear` and `onDismiss` are two shapes of the same idea and KDLS makes them mutually
|
|
9
|
+
exclusive in the type system, which does not exist here — so the check is at render
|
|
10
|
+
and it throws, naming both. Getting it wrong silently would draw two ways out of the
|
|
11
|
+
same bar, or none.
|
|
12
|
+
|
|
13
|
+
Divergences:
|
|
14
|
+
|
|
15
|
+
No portal, for the reason measured in components/Floating. The bar is position:fixed,
|
|
16
|
+
so it floats over the page from wherever it is written; what a portal would add is
|
|
17
|
+
escaping an ancestor with a transform, and the component warns about that case through
|
|
18
|
+
sds-floating when it is used inside one.
|
|
19
|
+
|
|
20
|
+
It is a <section> with an accessible name and the count lives in an aria-live region.
|
|
21
|
+
KDLS renders a bare div, so a keyboard user who selects a row is given a bar full of
|
|
22
|
+
new actions and told nothing about it — the count changing is the one thing that
|
|
23
|
+
needs announcing here. -->
|
|
24
|
+
|
|
25
|
+
import dismissIcon from "../../icons/generated/fluent-dismiss-regular.marko";
|
|
26
|
+
|
|
27
|
+
class {
|
|
28
|
+
onCreate() {
|
|
29
|
+
this.state = { primary: false, secondary: false, group: {} };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async run(mark, handler) {
|
|
33
|
+
const result = handler?.();
|
|
34
|
+
if (!(result instanceof Promise)) return;
|
|
35
|
+
mark(true);
|
|
36
|
+
try {
|
|
37
|
+
await result;
|
|
38
|
+
} finally {
|
|
39
|
+
mark(false);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
handlePrimary() {
|
|
44
|
+
return this.run((v) => { this.state.primary = v; }, this.input.onPrimary);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
handleSecondary() {
|
|
48
|
+
return this.run((v) => { this.state.secondary = v; }, this.input.onSecondary);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
handleGroupAction(key, action) {
|
|
52
|
+
return this.run((v) => {
|
|
53
|
+
this.state.group = { ...this.state.group, [key]: v };
|
|
54
|
+
}, action.onClick);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
$ {
|
|
59
|
+
const {
|
|
60
|
+
countLabel,
|
|
61
|
+
groups = [],
|
|
62
|
+
primaryLabel, onPrimary, primaryLoading = false, primaryDisabled = false, primaryDisabledTooltip,
|
|
63
|
+
secondaryLabel, onSecondary, secondaryLoading = false, secondaryDisabled = false, secondaryDisabledTooltip,
|
|
64
|
+
actions = [],
|
|
65
|
+
onClear,
|
|
66
|
+
onDismiss,
|
|
67
|
+
clearLabel = "Limpar",
|
|
68
|
+
dismissLabel = "Fechar",
|
|
69
|
+
label = "Ações em massa",
|
|
70
|
+
class: extraClass = "",
|
|
71
|
+
} = input;
|
|
72
|
+
|
|
73
|
+
if (countLabel === undefined) throw new Error("sds-bulk-action-bar needs a `countLabel`.");
|
|
74
|
+
if (Boolean(onClear) === Boolean(onDismiss)) {
|
|
75
|
+
throw new Error("sds-bulk-action-bar needs exactly one of `onClear` or `onDismiss` — one draws a Limpar link before the count, the other a ✕.");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Two shapes, and a bar is one of them.
|
|
79
|
+
//
|
|
80
|
+
// grouped — `groups`, each a labelled set of links. A list of things you can do to
|
|
81
|
+
// the selection, sorted by what they are about.
|
|
82
|
+
// buttons — `primaryLabel`, `secondaryLabel` and as many loose `actions` links as
|
|
83
|
+
// needed. One act stands out, one stands beside it, the rest are links.
|
|
84
|
+
//
|
|
85
|
+
// Mixing them puts a labelled group next to an unlabelled primary and asks the reader
|
|
86
|
+
// to work out whether the group heading covers the button after it. KDLS allows the
|
|
87
|
+
// mix; this refuses it, for the same reason it refuses onClear with onDismiss.
|
|
88
|
+
const grouped = groups.length > 0;
|
|
89
|
+
const buttons = Boolean(primaryLabel) || Boolean(secondaryLabel) || actions.length > 0;
|
|
90
|
+
if (grouped && buttons) {
|
|
91
|
+
throw new Error("sds-bulk-action-bar takes either `groups` or the button set (`primaryLabel`, `secondaryLabel`, `actions`) — not both. A labelled group beside an unlabelled button leaves the reader guessing how far the label reaches.");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const isDismiss = Boolean(onDismiss);
|
|
95
|
+
const classes = [
|
|
96
|
+
"sds-bulk-action-bar",
|
|
97
|
+
isDismiss ? "sds-bulk-action-bar--dismiss" : "",
|
|
98
|
+
extraClass,
|
|
99
|
+
].filter(Boolean).join(" ");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
<section class=classes aria-label=label>
|
|
103
|
+
<!-- The way out comes first, then what it applies to. KDLS puts the clear link after
|
|
104
|
+
the count and the dismiss button before it, so the same bar read in two different
|
|
105
|
+
orders depending on which prop the caller passed. One order: leave, then the count
|
|
106
|
+
it would leave. -->
|
|
107
|
+
<div class="sds-bulk-action-bar__identity">
|
|
108
|
+
<if(isDismiss)>
|
|
109
|
+
<sds-icon-button variant="neutral" aria-label=dismissLabel onClick=onDismiss>
|
|
110
|
+
<@icon><${dismissIcon}/></@icon>
|
|
111
|
+
</sds-icon-button>
|
|
112
|
+
</if>
|
|
113
|
+
<else>
|
|
114
|
+
<sds-button variant="link" label=clearLabel onClick=onClear class="sds-bulk-action-bar__clear"/>
|
|
115
|
+
</else>
|
|
116
|
+
|
|
117
|
+
<!-- Body weight, not title: the count is a fact about the selection, not a heading
|
|
118
|
+
over the bar. KDLS sets it in titleSm, which makes it shout beside the links. -->
|
|
119
|
+
<sds-text as="span" variant="bodyMd" class="sds-bulk-action-bar__label" aria-live="polite">
|
|
120
|
+
${countLabel}
|
|
121
|
+
</sds-text>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div class="sds-bulk-action-bar__groups">
|
|
125
|
+
<for|group, i| of=groups>
|
|
126
|
+
<div class="sds-bulk-action-bar__group">
|
|
127
|
+
<if(group.groupLabel)>
|
|
128
|
+
<span class="sds-bulk-action-bar__group-label">${group.groupLabel}</span>
|
|
129
|
+
</if>
|
|
130
|
+
<div class="sds-bulk-action-bar__group-actions">
|
|
131
|
+
<for|action, j| of=(group.actions || [])>
|
|
132
|
+
<sds-button
|
|
133
|
+
variant="link"
|
|
134
|
+
label=action.label
|
|
135
|
+
onClick=() => component.handleGroupAction(`${i}-${j}`, action)
|
|
136
|
+
isLoading=(state.group[`${i}-${j}`] || Boolean(action.loading))
|
|
137
|
+
disabled=action.disabled
|
|
138
|
+
disabledTooltip=action.disabledTooltip
|
|
139
|
+
disabledTooltipPosition="top"
|
|
140
|
+
class="sds-bulk-action-bar__group-button"
|
|
141
|
+
/>
|
|
142
|
+
</for>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</for>
|
|
146
|
+
|
|
147
|
+
<for|action, i| of=actions>
|
|
148
|
+
<sds-button
|
|
149
|
+
variant="link"
|
|
150
|
+
label=action.label
|
|
151
|
+
onClick=() => component.handleGroupAction(`action-${i}`, action)
|
|
152
|
+
isLoading=(state.group[`action-${i}`] || Boolean(action.loading))
|
|
153
|
+
disabled=action.disabled
|
|
154
|
+
disabledTooltip=action.disabledTooltip
|
|
155
|
+
disabledTooltipPosition="top"
|
|
156
|
+
class="sds-bulk-action-bar__group-button"
|
|
157
|
+
/>
|
|
158
|
+
</for>
|
|
159
|
+
|
|
160
|
+
<if(secondaryLabel)>
|
|
161
|
+
<sds-button
|
|
162
|
+
variant="ghost"
|
|
163
|
+
label=secondaryLabel
|
|
164
|
+
onClick=() => component.handleSecondary()
|
|
165
|
+
isLoading=(state.secondary || secondaryLoading)
|
|
166
|
+
disabled=secondaryDisabled
|
|
167
|
+
disabledTooltip=secondaryDisabledTooltip
|
|
168
|
+
disabledTooltipPosition="top"
|
|
169
|
+
class="sds-bulk-action-bar__secondary"
|
|
170
|
+
/>
|
|
171
|
+
</if>
|
|
172
|
+
|
|
173
|
+
<if(primaryLabel)>
|
|
174
|
+
<sds-button
|
|
175
|
+
variant="primary"
|
|
176
|
+
label=primaryLabel
|
|
177
|
+
onClick=() => component.handlePrimary()
|
|
178
|
+
isLoading=(state.primary || primaryLoading)
|
|
179
|
+
disabled=primaryDisabled
|
|
180
|
+
disabledTooltip=primaryDisabledTooltip
|
|
181
|
+
disabledTooltipPosition="top"
|
|
182
|
+
class="sds-bulk-action-bar__primary"
|
|
183
|
+
/>
|
|
184
|
+
</if>
|
|
185
|
+
</div>
|
|
186
|
+
</section>
|