@studiocms/ui 0.3.2 → 0.4.1
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/dist/components/Accordion/Accordion.astro +23 -0
- package/dist/components/Accordion/Item.astro +21 -0
- package/dist/components/Accordion/accordion.css +64 -0
- package/dist/components/Accordion/accordion.d.ts +1 -0
- package/dist/components/Accordion/accordion.js +70 -0
- package/dist/components/Badge/Badge.astro +49 -0
- package/dist/components/Badge/badge.css +111 -0
- package/dist/components/Breadcrumbs/Breadcrumbs.astro +31 -0
- package/dist/components/Breadcrumbs/breadcrumbs.css +15 -0
- package/dist/components/Button/Button.astro +75 -0
- package/dist/components/Button/button.css +292 -0
- package/{src/components → dist/components/Card}/Card.astro +1 -48
- package/dist/components/Card/card.css +38 -0
- package/dist/components/Center/Center.astro +7 -0
- package/dist/components/Center/center.css +8 -0
- package/dist/components/Checkbox/Checkbox.astro +95 -0
- package/dist/components/Checkbox/checkbox.css +119 -0
- package/dist/components/Checkbox/checkbox.d.ts +2 -0
- package/dist/components/Checkbox/checkbox.js +20 -0
- package/{src/components → dist/components/Divider}/Divider.astro +2 -25
- package/dist/components/Divider/divider.css +21 -0
- package/dist/components/Dropdown/Dropdown.astro +116 -0
- package/dist/components/Dropdown/dropdown.css +180 -0
- package/dist/components/Dropdown/dropdown.d.ts +48 -0
- package/dist/components/Dropdown/dropdown.js +201 -0
- package/dist/components/Footer/Footer.astro +58 -0
- package/dist/components/Footer/footer.css +68 -0
- package/dist/components/Group/Group.astro +7 -0
- package/dist/components/Group/group.css +19 -0
- package/{src/utils → dist/components/Icon}/Icon.astro +1 -1
- package/{src/utils/iconType.ts → dist/components/Icon/iconType.d.ts} +0 -1
- package/dist/components/Icon/iconType.js +0 -0
- package/{src/components → dist/components/Input}/Input.astro +2 -48
- package/dist/components/Input/input.css +38 -0
- package/{src → dist}/components/Modal/Modal.astro +4 -122
- package/dist/components/Modal/modal.css +100 -0
- package/dist/components/Modal/modal.d.ts +48 -0
- package/dist/components/Modal/modal.js +129 -0
- package/dist/components/Progress/Progress.astro +21 -0
- package/dist/components/Progress/helper.d.ts +13 -0
- package/dist/components/Progress/helper.js +32 -0
- package/dist/components/Progress/progress.css +29 -0
- package/dist/components/Progress/progress.d.ts +1 -0
- package/dist/components/Progress/progress.js +10 -0
- package/dist/components/RadioGroup/RadioGroup.astro +124 -0
- package/dist/components/RadioGroup/radiogroup.css +96 -0
- package/dist/components/RadioGroup/radiogroup.d.ts +1 -0
- package/dist/components/RadioGroup/radiogroup.js +48 -0
- package/{src/components → dist/components/Row}/Row.astro +1 -24
- package/dist/components/Row/row.css +18 -0
- package/dist/components/SearchSelect/SearchSelect.astro +135 -0
- package/dist/components/SearchSelect/searchselect.css +95 -0
- package/dist/components/SearchSelect/searchselect.d.ts +6 -0
- package/dist/components/SearchSelect/searchselect.js +166 -0
- package/dist/components/Select/Select.astro +147 -0
- package/dist/components/Select/select.css +110 -0
- package/dist/components/Select/select.d.ts +1 -0
- package/dist/components/Select/select.js +143 -0
- package/dist/components/Sidebar/helpers.d.ts +76 -0
- package/dist/components/Sidebar/helpers.js +160 -0
- package/{src → dist}/components/Tabs/TabItem.astro +3 -3
- package/dist/components/Tabs/Tabs.astro +150 -0
- package/dist/components/Tabs/tabs.css +121 -0
- package/dist/components/Tabs/tabs.d.ts +1 -0
- package/dist/components/Tabs/tabs.js +82 -0
- package/{src/components → dist/components/Textarea}/Textarea.astro +2 -61
- package/dist/components/Textarea/textarea.css +49 -0
- package/dist/components/ThemeToggle/ThemeToggle.astro +21 -0
- package/dist/components/ThemeToggle/themetoggle.css +17 -0
- package/dist/components/ThemeToggle/themetoggle.d.ts +1 -0
- package/dist/components/ThemeToggle/themetoggle.js +4 -0
- package/dist/components/Toast/Toaster.astro +69 -0
- package/dist/components/Toast/toast.d.ts +8 -0
- package/dist/components/Toast/toast.js +9 -0
- package/dist/components/Toast/toaster.css +168 -0
- package/dist/components/Toast/toaster.d.ts +1 -0
- package/dist/components/Toast/toaster.js +160 -0
- package/dist/components/Toggle/Toggle.astro +84 -0
- package/dist/components/Toggle/toggle.css +93 -0
- package/dist/components/Toggle/toggle.d.ts +2 -0
- package/dist/components/Toggle/toggle.js +20 -0
- package/{src/components → dist/components/User}/User.astro +3 -43
- package/dist/components/User/user.css +35 -0
- package/dist/css/colors.css +95 -0
- package/dist/css/global.css +3 -0
- package/dist/css/radii.css +6 -0
- package/dist/css/resets.css +46 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +379 -0
- package/dist/toolbar/ColorPicker.d.ts +7 -0
- package/dist/toolbar/ColorPicker.js +85 -0
- package/dist/toolbar/icon.d.ts +1 -0
- package/dist/toolbar/icon.js +4 -0
- package/dist/toolbar/index.d.ts +2 -0
- package/dist/toolbar/index.js +292 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/index.js +0 -0
- package/dist/utils/ThemeHelper.d.ts +49 -0
- package/dist/utils/ThemeHelper.js +113 -0
- package/{src/utils/colors.ts → dist/utils/colors.d.ts} +1 -1
- package/dist/utils/colors.js +0 -0
- package/dist/utils/generateID.d.ts +2 -0
- package/dist/utils/generateID.js +6 -0
- package/dist/utils/headers.d.ts +43 -0
- package/dist/utils/headers.js +129 -0
- package/dist/utils/iconStrings.d.ts +4 -0
- package/dist/utils/iconStrings.js +13 -0
- package/dist/utils/integration-utils.d.ts +130 -0
- package/dist/utils/integration-utils.js +161 -0
- package/package.json +26 -10
- package/src/components/BaseHead.astro +0 -22
- package/src/components/Button.astro +0 -372
- package/src/components/Center.astro +0 -16
- package/src/components/Checkbox.astro +0 -250
- package/src/components/Dropdown/Dropdown.astro +0 -314
- package/src/components/Dropdown/dropdown.ts +0 -258
- package/src/components/Dropdown/index.ts +0 -2
- package/src/components/Footer.astro +0 -137
- package/src/components/Modal/index.ts +0 -2
- package/src/components/Modal/modal.ts +0 -163
- package/src/components/RadioGroup.astro +0 -299
- package/src/components/SearchSelect.astro +0 -486
- package/src/components/Select.astro +0 -467
- package/src/components/Sidebar/helpers.ts +0 -179
- package/src/components/Sidebar/index.ts +0 -3
- package/src/components/Tabs/Tabs.astro +0 -393
- package/src/components/Tabs/index.ts +0 -2
- package/src/components/ThemeToggle.astro +0 -46
- package/src/components/Toast/Toaster.astro +0 -470
- package/src/components/Toast/index.ts +0 -2
- package/src/components/Toast/toast.ts +0 -16
- package/src/components/Toggle.astro +0 -214
- package/src/components/index.ts +0 -27
- package/src/components.ts +0 -26
- package/src/css/colors.css +0 -106
- package/src/css/global.css +0 -2
- package/src/css/resets.css +0 -54
- package/src/env.d.ts +0 -15
- package/src/integration.ts +0 -31
- package/src/layouts/RootLayout.astro +0 -33
- package/src/layouts/index.ts +0 -2
- package/src/layouts.ts +0 -1
- package/src/types/index.ts +0 -11
- package/src/utils/ThemeHelper.ts +0 -145
- package/src/utils/create-resolver.ts +0 -30
- package/src/utils/generateID.ts +0 -5
- package/src/utils/headers.ts +0 -190
- package/src/utils/iconStrings.ts +0 -29
- package/src/utils/index.ts +0 -1
- package/src/utils/virtual-module-plugin-builder.ts +0 -37
- /package/{src → dist}/components/Sidebar/Double.astro +0 -0
- /package/{src → dist}/components/Sidebar/Single.astro +0 -0
- /package/{src → dist}/icons/Checkmark.astro +0 -0
- /package/{src → dist}/icons/ChevronUpDown.astro +0 -0
- /package/{src → dist}/icons/User.astro +0 -0
- /package/{src → dist}/icons/X-Mark.astro +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
const allSearchSelects = document.querySelectorAll(".sui-search-select-label");
|
|
2
|
+
for (const container of allSearchSelects) {
|
|
3
|
+
let hideOnClickOutside = function(element) {
|
|
4
|
+
const outsideClickListener = (event) => {
|
|
5
|
+
if (!element.contains(event.target) && isVisible(element) && active === true) {
|
|
6
|
+
dropdown.classList.remove("active", "above");
|
|
7
|
+
active = false;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
document.addEventListener("click", outsideClickListener);
|
|
11
|
+
}, constructOptionsBasedOnOptions = function(options2) {
|
|
12
|
+
dropdown.innerHTML = "";
|
|
13
|
+
if (options2.length === 0) {
|
|
14
|
+
const element = document.createElement("li");
|
|
15
|
+
element.classList.add("empty-search-results");
|
|
16
|
+
element.textContent = "No results found.";
|
|
17
|
+
dropdown.appendChild(element);
|
|
18
|
+
}
|
|
19
|
+
let i = 0;
|
|
20
|
+
for (const option of options2) {
|
|
21
|
+
const element = document.createElement("li");
|
|
22
|
+
element.classList.add(
|
|
23
|
+
...[
|
|
24
|
+
"sui-search-select-option",
|
|
25
|
+
option.disabled && "disabled",
|
|
26
|
+
focusIndex === i && "focused"
|
|
27
|
+
].filter((x) => typeof x === "string")
|
|
28
|
+
);
|
|
29
|
+
element.role = "option";
|
|
30
|
+
element.value = Number.parseInt(option.value);
|
|
31
|
+
element.id = "";
|
|
32
|
+
element.dataset.optionIndex = i.toString();
|
|
33
|
+
element.dataset.value = option.value;
|
|
34
|
+
element.textContent = option.label;
|
|
35
|
+
element.addEventListener("click", (e) => handleSelection(e, element));
|
|
36
|
+
dropdown.appendChild(element);
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
optionElements = container.querySelectorAll("li");
|
|
40
|
+
};
|
|
41
|
+
var hideOnClickOutside2 = hideOnClickOutside, constructOptionsBasedOnOptions2 = constructOptionsBasedOnOptions;
|
|
42
|
+
const hiddenSelect = container.querySelector("select");
|
|
43
|
+
const searchWrapper = container.querySelector(".sui-search-input-wrapper");
|
|
44
|
+
const searchInput = searchWrapper.querySelector("input");
|
|
45
|
+
const dropdown = container.querySelector(".sui-search-select-dropdown");
|
|
46
|
+
let optionElements = container.querySelectorAll("li");
|
|
47
|
+
let active = false;
|
|
48
|
+
const options = JSON.parse(container.dataset.options);
|
|
49
|
+
const id = container.dataset.id;
|
|
50
|
+
let filteredOptions = options;
|
|
51
|
+
searchWrapper.addEventListener("click", () => {
|
|
52
|
+
const { bottom, left, right, width, x, y, height } = searchWrapper.getBoundingClientRect();
|
|
53
|
+
const optionHeight = 36;
|
|
54
|
+
const totalBorderSize = 2;
|
|
55
|
+
const margin = 4;
|
|
56
|
+
const dropdownHeight = options.length * optionHeight + totalBorderSize + margin;
|
|
57
|
+
const CustomRect = {
|
|
58
|
+
top: bottom + margin,
|
|
59
|
+
left,
|
|
60
|
+
right,
|
|
61
|
+
bottom: bottom + margin + dropdownHeight,
|
|
62
|
+
width,
|
|
63
|
+
height: dropdownHeight,
|
|
64
|
+
x,
|
|
65
|
+
y: y + height + margin
|
|
66
|
+
};
|
|
67
|
+
if (active) {
|
|
68
|
+
searchInput.ariaExpanded = "false";
|
|
69
|
+
dropdown.classList.remove("active", "above");
|
|
70
|
+
active = false;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
active = true;
|
|
74
|
+
searchInput.ariaExpanded = "true";
|
|
75
|
+
if (CustomRect.top >= 0 && CustomRect.left >= 0 && CustomRect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && CustomRect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
76
|
+
dropdown.classList.add("active");
|
|
77
|
+
} else {
|
|
78
|
+
dropdown.classList.add("active", "above");
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
const handleSelection = (e, option) => {
|
|
82
|
+
e.stopImmediatePropagation();
|
|
83
|
+
if (option.id === `${id}-selected` || !id) return;
|
|
84
|
+
const currentlySelected = document.getElementById(`${id}-selected`);
|
|
85
|
+
if (currentlySelected) {
|
|
86
|
+
currentlySelected.classList.remove("selected");
|
|
87
|
+
currentlySelected.id = "";
|
|
88
|
+
}
|
|
89
|
+
option.id = `${id}-selected`;
|
|
90
|
+
option.classList.add("selected");
|
|
91
|
+
const index = options.findIndex((x) => x.value === option.dataset.value);
|
|
92
|
+
focusIndex = index;
|
|
93
|
+
const opt = options[index];
|
|
94
|
+
hiddenSelect.value = opt.value;
|
|
95
|
+
searchInput.placeholder = opt.label;
|
|
96
|
+
dropdown.classList.remove("active", "above");
|
|
97
|
+
searchInput.value = "";
|
|
98
|
+
filteredOptions = options;
|
|
99
|
+
constructOptionsBasedOnOptions(options);
|
|
100
|
+
active = false;
|
|
101
|
+
};
|
|
102
|
+
for (const option of optionElements) {
|
|
103
|
+
option.addEventListener("click", (e) => handleSelection(e, option));
|
|
104
|
+
}
|
|
105
|
+
window.addEventListener("scroll", () => {
|
|
106
|
+
dropdown.classList.remove("active", "above");
|
|
107
|
+
active = false;
|
|
108
|
+
});
|
|
109
|
+
hideOnClickOutside(container);
|
|
110
|
+
const isVisible = (elem) => !!elem && !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length);
|
|
111
|
+
let focusIndex = 0;
|
|
112
|
+
const recomputeOptions = () => {
|
|
113
|
+
for (const entry of optionElements) {
|
|
114
|
+
if (Number.parseInt(entry.dataset.optionIndex) === focusIndex) {
|
|
115
|
+
entry.classList.add("focused");
|
|
116
|
+
} else {
|
|
117
|
+
entry.classList.remove("focused");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
searchInput.addEventListener("keydown", (e) => {
|
|
122
|
+
if (e.key === "Escape") {
|
|
123
|
+
e.preventDefault();
|
|
124
|
+
e.stopImmediatePropagation();
|
|
125
|
+
active = false;
|
|
126
|
+
dropdown.classList.remove("active", "above");
|
|
127
|
+
searchInput.blur();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (e.key === "ArrowUp" && focusIndex > 0) {
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
e.stopImmediatePropagation();
|
|
133
|
+
focusIndex--;
|
|
134
|
+
recomputeOptions();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (e.key === "ArrowDown" && focusIndex + 1 < filteredOptions.filter((x) => !x.disabled).length) {
|
|
138
|
+
e.preventDefault();
|
|
139
|
+
e.stopImmediatePropagation();
|
|
140
|
+
focusIndex++;
|
|
141
|
+
recomputeOptions();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (e.key === "Enter") {
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
e.stopImmediatePropagation();
|
|
147
|
+
for (const entry of optionElements) {
|
|
148
|
+
if (Number.parseInt(entry.dataset.optionIndex) === focusIndex) {
|
|
149
|
+
entry.click();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
searchInput.addEventListener("keyup", (e) => {
|
|
156
|
+
if (["Enter", "ArrowUp", "ArrowDown"].includes(e.key)) return;
|
|
157
|
+
if (searchInput.value.trim().length === 0) {
|
|
158
|
+
constructOptionsBasedOnOptions(options);
|
|
159
|
+
filteredOptions = options;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
filteredOptions = options.filter((x) => x.label.includes(searchInput.value));
|
|
163
|
+
focusIndex = 0;
|
|
164
|
+
constructOptionsBasedOnOptions(filteredOptions);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { generateID } from '../../utils/generateID.js';
|
|
3
|
+
import Icon from '../Icon/Icon.astro';
|
|
4
|
+
import './select.css';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The props for the select component.
|
|
8
|
+
*/
|
|
9
|
+
interface Option {
|
|
10
|
+
/**
|
|
11
|
+
* The label of the option.
|
|
12
|
+
*/
|
|
13
|
+
label: string;
|
|
14
|
+
/**
|
|
15
|
+
* The value of the option.
|
|
16
|
+
*/
|
|
17
|
+
value: string;
|
|
18
|
+
/**
|
|
19
|
+
* Whether the option is disabled. Defaults to `false`.
|
|
20
|
+
*/
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The props for the select component.
|
|
26
|
+
*/
|
|
27
|
+
interface Props {
|
|
28
|
+
/**
|
|
29
|
+
* The label of the select.
|
|
30
|
+
*/
|
|
31
|
+
label?: string;
|
|
32
|
+
/**
|
|
33
|
+
* The default value of the select.
|
|
34
|
+
*/
|
|
35
|
+
defaultValue?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Additional classes to apply to the select.
|
|
38
|
+
*/
|
|
39
|
+
class?: string;
|
|
40
|
+
/**
|
|
41
|
+
* The name of the select. Required because of the helper.
|
|
42
|
+
*/
|
|
43
|
+
name?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the select is required. Defaults to `false`.
|
|
46
|
+
*/
|
|
47
|
+
isRequired?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The options to display in the select.
|
|
50
|
+
*/
|
|
51
|
+
options: Option[];
|
|
52
|
+
/**
|
|
53
|
+
* Whether the select is disabled. Defaults to `false`.
|
|
54
|
+
*/
|
|
55
|
+
disabled?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Whether the select is full width. Defaults to `false`.
|
|
58
|
+
*/
|
|
59
|
+
fullWidth?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* The placeholder of the select. Defaults to `Select`.
|
|
62
|
+
*/
|
|
63
|
+
placeholder?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const {
|
|
67
|
+
label,
|
|
68
|
+
defaultValue,
|
|
69
|
+
class: className,
|
|
70
|
+
name = generateID('select'),
|
|
71
|
+
isRequired,
|
|
72
|
+
options = [],
|
|
73
|
+
disabled,
|
|
74
|
+
fullWidth,
|
|
75
|
+
placeholder = 'Select',
|
|
76
|
+
} = Astro.props;
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
<div
|
|
80
|
+
id={`${name}-container`}
|
|
81
|
+
class="sui-select-label"
|
|
82
|
+
class:list={[disabled && "disabled", className, fullWidth && "full"]}
|
|
83
|
+
data-options={JSON.stringify(options)}
|
|
84
|
+
data-id={name}
|
|
85
|
+
>
|
|
86
|
+
{label && (
|
|
87
|
+
<label class="label" for={`${name}-select-btn`}>
|
|
88
|
+
{label}
|
|
89
|
+
<span class="req-star">{isRequired && "*"}</span>
|
|
90
|
+
</label>
|
|
91
|
+
)}
|
|
92
|
+
<button
|
|
93
|
+
class="sui-select-button"
|
|
94
|
+
role="combobox"
|
|
95
|
+
aria-controls={`${name}-dropdown`}
|
|
96
|
+
aria-expanded="false"
|
|
97
|
+
id={`${name}-select-btn`}
|
|
98
|
+
type="button"
|
|
99
|
+
aria-label={placeholder}
|
|
100
|
+
title={placeholder}
|
|
101
|
+
>
|
|
102
|
+
<span class="sui-select-value-span" id={`${name}-value-span`}>
|
|
103
|
+
{
|
|
104
|
+
defaultValue
|
|
105
|
+
? options.find((x) => x.value === defaultValue)?.label
|
|
106
|
+
: placeholder
|
|
107
|
+
}
|
|
108
|
+
</span>
|
|
109
|
+
<Icon name="chevron-up-down" width={24} height={24} />
|
|
110
|
+
</button>
|
|
111
|
+
<ul class="sui-select-dropdown" role="listbox" id={`${name}-dropdown`}>
|
|
112
|
+
{
|
|
113
|
+
options.map((x, i) => (
|
|
114
|
+
<li
|
|
115
|
+
class="sui-select-option"
|
|
116
|
+
role="option"
|
|
117
|
+
value={x.value}
|
|
118
|
+
class:list={[
|
|
119
|
+
defaultValue === x.value && `selected`,
|
|
120
|
+
x.disabled && "disabled",
|
|
121
|
+
]}
|
|
122
|
+
id={defaultValue === x.value ? `${name}-selected` : ""}
|
|
123
|
+
data-option-index={i}
|
|
124
|
+
>
|
|
125
|
+
{x.label}
|
|
126
|
+
</li>
|
|
127
|
+
))
|
|
128
|
+
}
|
|
129
|
+
</ul>
|
|
130
|
+
<select class="sui-hidden-select" id={name} name={name} required={isRequired} hidden tabindex="-1">
|
|
131
|
+
<option value={""}> Select </option>
|
|
132
|
+
{
|
|
133
|
+
options.map((x) => (
|
|
134
|
+
<option
|
|
135
|
+
value={x.value}
|
|
136
|
+
selected={defaultValue === x.value}
|
|
137
|
+
disabled={x.disabled}
|
|
138
|
+
>
|
|
139
|
+
{x.label}
|
|
140
|
+
</option>
|
|
141
|
+
))
|
|
142
|
+
}
|
|
143
|
+
</select>
|
|
144
|
+
</div>
|
|
145
|
+
<script>
|
|
146
|
+
import "studiocms:ui/scripts/select"
|
|
147
|
+
</script>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
.sui-select-label {
|
|
2
|
+
width: fit-content;
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: 0.25rem;
|
|
6
|
+
min-width: 200px;
|
|
7
|
+
position: relative;
|
|
8
|
+
height: fit-content;
|
|
9
|
+
}
|
|
10
|
+
.sui-select-label.full,
|
|
11
|
+
.sui-select-label.full .sui-select-button {
|
|
12
|
+
width: 100%;
|
|
13
|
+
}
|
|
14
|
+
.sui-select-label.disabled {
|
|
15
|
+
opacity: 0.5;
|
|
16
|
+
pointer-events: none;
|
|
17
|
+
color: hsl(var(--text-muted));
|
|
18
|
+
}
|
|
19
|
+
.label {
|
|
20
|
+
font-size: 14px;
|
|
21
|
+
}
|
|
22
|
+
.req-star {
|
|
23
|
+
color: hsl(var(--danger-base));
|
|
24
|
+
font-weight: 700;
|
|
25
|
+
}
|
|
26
|
+
.sui-select-button {
|
|
27
|
+
padding: 0.5rem 0.75rem 0.5rem 1rem;
|
|
28
|
+
border-radius: var(--radius-md);
|
|
29
|
+
border: 1px solid hsl(var(--border));
|
|
30
|
+
background: hsl(var(--background-step-2));
|
|
31
|
+
color: hsl(var(--text-normal));
|
|
32
|
+
transition: background border 0.15s ease;
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: row;
|
|
35
|
+
align-items: center;
|
|
36
|
+
justify-content: space-between;
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
gap: 1rem;
|
|
39
|
+
}
|
|
40
|
+
.sui-select-button:focus {
|
|
41
|
+
border: 1px solid hsl(var(--primary-base));
|
|
42
|
+
}
|
|
43
|
+
.sui-select-button:hover,
|
|
44
|
+
.sui-select-button:focus {
|
|
45
|
+
background: hsl(var(--background-step-3));
|
|
46
|
+
}
|
|
47
|
+
.sui-select-button.active,
|
|
48
|
+
.sui-select-button:active,
|
|
49
|
+
.sui-select-button:has(+ .sui-select-dropdown.active) {
|
|
50
|
+
border: 1px solid hsl(var(--primary-base));
|
|
51
|
+
background: hsl(var(--background-step-2));
|
|
52
|
+
}
|
|
53
|
+
.sui-select-dropdown {
|
|
54
|
+
position: absolute;
|
|
55
|
+
width: 100%;
|
|
56
|
+
border: 1px solid hsl(var(--border));
|
|
57
|
+
list-style: none;
|
|
58
|
+
margin: 0;
|
|
59
|
+
padding: 0;
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
border-radius: var(--radius-md);
|
|
62
|
+
background-color: hsl(var(--background-step-2));
|
|
63
|
+
overflow: hidden;
|
|
64
|
+
top: calc(100% + 0.25rem);
|
|
65
|
+
left: 0;
|
|
66
|
+
display: none;
|
|
67
|
+
z-index: 90;
|
|
68
|
+
box-shadow: 0px 4px 8px hsl(var(--shadow), 0.5);
|
|
69
|
+
}
|
|
70
|
+
.sui-select-dropdown.active {
|
|
71
|
+
display: flex;
|
|
72
|
+
}
|
|
73
|
+
.sui-select-dropdown.above {
|
|
74
|
+
top: auto;
|
|
75
|
+
bottom: calc(100% - 18px + 0.25rem);
|
|
76
|
+
}
|
|
77
|
+
.sui-select-option {
|
|
78
|
+
padding: 0.5rem;
|
|
79
|
+
cursor: pointer;
|
|
80
|
+
font-size: 0.975em;
|
|
81
|
+
transition: all 0.15s ease;
|
|
82
|
+
}
|
|
83
|
+
.sui-select-option.disabled {
|
|
84
|
+
pointer-events: none;
|
|
85
|
+
color: hsl(var(--text-muted));
|
|
86
|
+
}
|
|
87
|
+
.sui-select-option:hover,
|
|
88
|
+
.sui-select-option:focus,
|
|
89
|
+
.sui-select-option.focused {
|
|
90
|
+
background-color: hsl(var(--background-step-3));
|
|
91
|
+
}
|
|
92
|
+
.sui-select-option:focus {
|
|
93
|
+
outline: none;
|
|
94
|
+
border: none;
|
|
95
|
+
}
|
|
96
|
+
.sui-select-option.selected {
|
|
97
|
+
background-color: hsl(var(--primary-base));
|
|
98
|
+
color: hsl(var(--text-inverted));
|
|
99
|
+
cursor: default;
|
|
100
|
+
}
|
|
101
|
+
.sui-hidden-select {
|
|
102
|
+
height: 0;
|
|
103
|
+
width: 0;
|
|
104
|
+
border: none;
|
|
105
|
+
outline: none;
|
|
106
|
+
position: absolute;
|
|
107
|
+
background-color: transparent;
|
|
108
|
+
pointer-events: none;
|
|
109
|
+
opacity: 0;
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare function loadSelects(): void;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
function loadSelects() {
|
|
2
|
+
const allSelects = document.querySelectorAll(".sui-select-label");
|
|
3
|
+
for (const container of allSelects) {
|
|
4
|
+
let hideOnClickOutside2 = function(element) {
|
|
5
|
+
const outsideClickListener = (event) => {
|
|
6
|
+
if (!element.contains(event.target) && isVisible(element) && active === true) {
|
|
7
|
+
closeDropdown();
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
document.addEventListener("click", outsideClickListener);
|
|
11
|
+
};
|
|
12
|
+
var hideOnClickOutside = hideOnClickOutside2;
|
|
13
|
+
const hiddenSelect = container.querySelector("select");
|
|
14
|
+
const button = container.querySelector("button");
|
|
15
|
+
const valueSpan = container.querySelector(".sui-select-value-span");
|
|
16
|
+
const dropdown = container.querySelector(".sui-select-dropdown");
|
|
17
|
+
const optionElements = container.querySelectorAll(".sui-select-option");
|
|
18
|
+
const options = JSON.parse(container.dataset.options);
|
|
19
|
+
const id = container.dataset.id;
|
|
20
|
+
let active = false;
|
|
21
|
+
const closeDropdown = () => {
|
|
22
|
+
dropdown.classList.remove("active", "above");
|
|
23
|
+
active = false;
|
|
24
|
+
button.ariaExpanded = "false";
|
|
25
|
+
focusIndex = -1;
|
|
26
|
+
for (const entry of optionElements) {
|
|
27
|
+
entry.classList.remove("focused");
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const openDropdown = (toggle) => {
|
|
31
|
+
const { bottom, left, right, width, x, y, height } = button.getBoundingClientRect();
|
|
32
|
+
const optionHeight = 36;
|
|
33
|
+
const totalBorderSize = 2;
|
|
34
|
+
const margin = 4;
|
|
35
|
+
const dropdownHeight = options.length * optionHeight + totalBorderSize + margin;
|
|
36
|
+
const CustomRect = {
|
|
37
|
+
top: bottom + margin,
|
|
38
|
+
left,
|
|
39
|
+
right,
|
|
40
|
+
bottom: bottom + margin + dropdownHeight,
|
|
41
|
+
width,
|
|
42
|
+
height: dropdownHeight,
|
|
43
|
+
x,
|
|
44
|
+
y: y + height + margin
|
|
45
|
+
};
|
|
46
|
+
if (active && toggle) {
|
|
47
|
+
closeDropdown();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
active = true;
|
|
51
|
+
button.ariaExpanded = "true";
|
|
52
|
+
focusIndex = Array.from(optionElements).findIndex((x2) => x2.classList.contains("selected"));
|
|
53
|
+
if (CustomRect.top >= 0 && CustomRect.left >= 0 && CustomRect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && CustomRect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
54
|
+
dropdown.classList.add("active");
|
|
55
|
+
} else {
|
|
56
|
+
dropdown.classList.add("active", "above");
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
button.addEventListener("click", () => openDropdown(true));
|
|
60
|
+
let focusIndex = -1;
|
|
61
|
+
const recomputeOptions = () => {
|
|
62
|
+
for (const entry of optionElements) {
|
|
63
|
+
if (Number.parseInt(entry.dataset.optionIndex) === focusIndex) {
|
|
64
|
+
entry.classList.add("focused");
|
|
65
|
+
} else {
|
|
66
|
+
entry.classList.remove("focused");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
button.addEventListener("keydown", (e) => {
|
|
71
|
+
if (e.key === "Tab" || e.key === "Escape") {
|
|
72
|
+
closeDropdown();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (e.key === " " && !active) openDropdown(false);
|
|
76
|
+
if (e.key === "Enter") {
|
|
77
|
+
const currentlyFocused = container.querySelector(".focused");
|
|
78
|
+
if (currentlyFocused) {
|
|
79
|
+
currentlyFocused.classList.remove("focused");
|
|
80
|
+
currentlyFocused.click();
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
e.stopImmediatePropagation();
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
e.stopImmediatePropagation();
|
|
88
|
+
const neighbor = (offset) => {
|
|
89
|
+
return optionElements.item(
|
|
90
|
+
(Array.from(optionElements).findIndex((x) => x.classList.contains("selected")) ?? -1) + offset
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
if (e.key === "ArrowUp" && (focusIndex > 0 || !active)) {
|
|
94
|
+
if (!active) return neighbor(-1)?.click();
|
|
95
|
+
focusIndex--;
|
|
96
|
+
recomputeOptions();
|
|
97
|
+
}
|
|
98
|
+
if (e.key === "ArrowDown" && focusIndex + 1 < optionElements.length) {
|
|
99
|
+
if (!active) return neighbor(1)?.click();
|
|
100
|
+
focusIndex++;
|
|
101
|
+
recomputeOptions();
|
|
102
|
+
}
|
|
103
|
+
if (e.key === "PageUp") {
|
|
104
|
+
focusIndex = 0;
|
|
105
|
+
if (!active) return optionElements.item(focusIndex)?.click();
|
|
106
|
+
recomputeOptions();
|
|
107
|
+
}
|
|
108
|
+
if (e.key === "PageDown") {
|
|
109
|
+
focusIndex = optionElements.length - 1;
|
|
110
|
+
if (!active) return optionElements.item(focusIndex)?.click();
|
|
111
|
+
recomputeOptions();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
const handleSelection = (e, option) => {
|
|
115
|
+
e.stopImmediatePropagation();
|
|
116
|
+
if (option.id === `${id}-selected` || !id) return;
|
|
117
|
+
const currentlySelected = document.getElementById(`${id}-selected`);
|
|
118
|
+
if (currentlySelected) {
|
|
119
|
+
currentlySelected.classList.remove("selected");
|
|
120
|
+
currentlySelected.id = "";
|
|
121
|
+
}
|
|
122
|
+
option.id = `${id}-selected`;
|
|
123
|
+
option.classList.add("selected");
|
|
124
|
+
const opt = options[Number.parseInt(option.dataset.optionIndex)];
|
|
125
|
+
hiddenSelect.value = opt.value;
|
|
126
|
+
valueSpan.textContent = opt.label;
|
|
127
|
+
closeDropdown();
|
|
128
|
+
};
|
|
129
|
+
for (const option of optionElements) {
|
|
130
|
+
const handleSelectionForOption = (e) => handleSelection(e, option);
|
|
131
|
+
option.addEventListener("click", handleSelectionForOption);
|
|
132
|
+
}
|
|
133
|
+
window.addEventListener("scroll", closeDropdown);
|
|
134
|
+
document.addEventListener("keydown", (e) => {
|
|
135
|
+
if (e.key === "Escape" && dropdown.classList.contains("active")) {
|
|
136
|
+
closeDropdown();
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
hideOnClickOutside2(container);
|
|
140
|
+
const isVisible = (elem) => !!elem && !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
document.addEventListener("astro:page-load", loadSelects);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
declare class SingleSidebarHelper {
|
|
2
|
+
private sidebar;
|
|
3
|
+
private sidebarToggle?;
|
|
4
|
+
/**
|
|
5
|
+
* A helper to manage the sidebar with.
|
|
6
|
+
* @param toggleID The ID of the element that should toggle the sidebar.
|
|
7
|
+
*/
|
|
8
|
+
constructor(toggleID?: string);
|
|
9
|
+
/**
|
|
10
|
+
* A helper function register an element which should toggle the sidebar.
|
|
11
|
+
* @param elementID The ID of the element that should toggle the sidebar.
|
|
12
|
+
*/
|
|
13
|
+
toggleSidebarOnClick: (elementID: string) => void;
|
|
14
|
+
/**
|
|
15
|
+
* A helper function to hide the sidebar when an element is clicked.
|
|
16
|
+
* @param elementID The ID of the element that should hide the sidebar.
|
|
17
|
+
*/
|
|
18
|
+
hideSidebarOnClick: (elementID: string) => void;
|
|
19
|
+
/**
|
|
20
|
+
* A helper function to show the sidebar when an element is clicked.
|
|
21
|
+
* @param elementID The ID of the element that should show the sidebar.
|
|
22
|
+
*/
|
|
23
|
+
showSidebarOnClick: (elementID: string) => void;
|
|
24
|
+
/**
|
|
25
|
+
* A function to hide the sidebar.
|
|
26
|
+
*/
|
|
27
|
+
hideSidebar: () => void;
|
|
28
|
+
/**
|
|
29
|
+
* A function to show the sidebar.
|
|
30
|
+
*/
|
|
31
|
+
showSidebar: () => void;
|
|
32
|
+
}
|
|
33
|
+
declare class DoubleSidebarHelper {
|
|
34
|
+
private sidebarsContainer;
|
|
35
|
+
/**
|
|
36
|
+
* A helper to manage the double sidebar with.
|
|
37
|
+
*/
|
|
38
|
+
constructor();
|
|
39
|
+
/**
|
|
40
|
+
* A helper function to hide the sidebar when an element is clicked.
|
|
41
|
+
* @param elementID The ID of the element that should hide the sidebar.
|
|
42
|
+
*/
|
|
43
|
+
hideSidebarOnClick: (elementID: string) => void;
|
|
44
|
+
/**
|
|
45
|
+
* A helper function to show the outer sidebar when an element is clicked.
|
|
46
|
+
* @param elementID The ID of the element that should show the outer sidebar.
|
|
47
|
+
*/
|
|
48
|
+
showOuterOnClick: (elementID: string) => void;
|
|
49
|
+
/**
|
|
50
|
+
* A helper function to show the inner sidebar when an element is clicked.
|
|
51
|
+
* @param elementID The ID of the element that should show the inner sidebar.
|
|
52
|
+
*/
|
|
53
|
+
showInnerOnClick: (elementID: string) => void;
|
|
54
|
+
/**
|
|
55
|
+
* A helper function to toggle between the outer and inner sidebar when an element is clicked.
|
|
56
|
+
* @param elementID The ID of the element that should toggle the sidebar view.
|
|
57
|
+
*/
|
|
58
|
+
toggleStateOnClick: (elementID: string) => void;
|
|
59
|
+
/**
|
|
60
|
+
* A function to show the inner sidebar.
|
|
61
|
+
*/
|
|
62
|
+
showInnerSidebar: () => void;
|
|
63
|
+
/**
|
|
64
|
+
* A function to show the outer sidebar.
|
|
65
|
+
*/
|
|
66
|
+
showOuterSidebar: () => void;
|
|
67
|
+
/**
|
|
68
|
+
* A function to toggle between the outer and inner sidebar.
|
|
69
|
+
*/
|
|
70
|
+
toggleSidebarState: () => void;
|
|
71
|
+
/**
|
|
72
|
+
* A function to hide the sidebar altogether.
|
|
73
|
+
*/
|
|
74
|
+
hideSidebar: () => void;
|
|
75
|
+
}
|
|
76
|
+
export { SingleSidebarHelper, DoubleSidebarHelper };
|