@studiocms/ui 0.1.0 → 0.3.0
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/package.json +2 -2
- package/src/components/Button.astro +302 -269
- package/src/components/Card.astro +27 -3
- package/src/components/Checkbox.astro +72 -3
- package/src/components/Divider.astro +8 -1
- package/src/components/Dropdown/Dropdown.astro +55 -2
- package/src/components/Dropdown/dropdown.ts +104 -17
- package/src/components/Footer.astro +21 -4
- package/src/components/Input.astro +27 -0
- package/src/components/Modal/Modal.astro +31 -1
- package/src/components/Modal/modal.ts +33 -0
- package/src/components/RadioGroup.astro +132 -8
- package/src/components/Row.astro +9 -0
- package/src/components/SearchSelect.astro +249 -197
- package/src/components/Select.astro +229 -105
- package/src/components/Sidebar/helpers.ts +46 -0
- package/src/components/Tabs/TabItem.astro +47 -0
- package/src/components/Tabs/Tabs.astro +376 -0
- package/src/components/Tabs/index.ts +2 -0
- package/src/components/Textarea.astro +30 -0
- package/src/components/ThemeToggle.astro +6 -3
- package/src/components/Toast/Toaster.astro +140 -1
- package/src/components/Toggle.astro +77 -9
- package/src/components/User.astro +20 -2
- package/src/components/index.ts +1 -0
- package/src/components.ts +1 -0
- package/src/css/colors.css +8 -8
- package/src/css/resets.css +0 -1
- package/src/integration.ts +18 -0
- package/src/layouts/RootLayout.astro +0 -1
- package/src/utils/ThemeHelper.ts +8 -1
- package/src/utils/create-resolver.ts +30 -0
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { HTMLTag, Polymorphic } from 'astro/types';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Props for the card component.
|
|
6
|
+
*/
|
|
4
7
|
type Props<As extends HTMLTag = 'div'> = Omit<Polymorphic<{ as: As }>, 'as'> & {
|
|
8
|
+
/**
|
|
9
|
+
* The polymorphic component to render the card as. Defaults to `div`.
|
|
10
|
+
*/
|
|
5
11
|
as?: As;
|
|
6
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Whether the card should be full width. Defaults to `false`.
|
|
14
|
+
*/
|
|
7
15
|
fullWidth?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Whether the card should be full height. Defaults to `false`.
|
|
18
|
+
*/
|
|
8
19
|
fullHeight?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The variant of the card. Defaults to `default`.
|
|
22
|
+
*/
|
|
23
|
+
variant?: 'default' | 'filled';
|
|
9
24
|
};
|
|
10
25
|
|
|
11
|
-
const { as: As = 'div',
|
|
26
|
+
const { as: As = 'div', fullWidth, fullHeight, variant = 'default', ...props } = Astro.props;
|
|
12
27
|
---
|
|
13
|
-
<As class="sui-card" class:list={[fullWidth && "full-w", fullHeight && "full-h",
|
|
28
|
+
<As class="sui-card" class:list={[fullWidth && "full-w", fullHeight && "full-h", variant]} {...props}>
|
|
14
29
|
<div class="sui-card-header">
|
|
15
30
|
<slot name="header" />
|
|
16
31
|
</div>
|
|
@@ -30,6 +45,11 @@ const { as: As = 'div', class: className, fullWidth, fullHeight, ...props } = As
|
|
|
30
45
|
height: fit-content;
|
|
31
46
|
}
|
|
32
47
|
|
|
48
|
+
.sui-card.filled {
|
|
49
|
+
background-color: hsl(var(--background-step-3));
|
|
50
|
+
border: none;
|
|
51
|
+
}
|
|
52
|
+
|
|
33
53
|
.sui-card.full-w {
|
|
34
54
|
width: 100%;
|
|
35
55
|
}
|
|
@@ -54,6 +74,10 @@ const { as: As = 'div', class: className, fullWidth, fullHeight, ...props } = As
|
|
|
54
74
|
padding: 1rem;
|
|
55
75
|
}
|
|
56
76
|
|
|
77
|
+
.filled .sui-card-footer {
|
|
78
|
+
border: none;
|
|
79
|
+
}
|
|
80
|
+
|
|
57
81
|
@media screen and (max-width: 840px) {
|
|
58
82
|
.sui-card {
|
|
59
83
|
width: 100%;
|
|
@@ -3,13 +3,37 @@ import Checkmark from '../icons/Checkmark.astro';
|
|
|
3
3
|
import type { StudioCMSColorway } from '../utils/colors';
|
|
4
4
|
import { generateID } from '../utils/generateID';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* The props for the Checkbox component.
|
|
8
|
+
*/
|
|
6
9
|
interface Props {
|
|
10
|
+
/**
|
|
11
|
+
* The label of the checkbox.
|
|
12
|
+
*/
|
|
7
13
|
label: string;
|
|
14
|
+
/**
|
|
15
|
+
* The size of the checkbox. Defaults to `md`.
|
|
16
|
+
*/
|
|
8
17
|
size?: 'sm' | 'md' | 'lg';
|
|
18
|
+
/**
|
|
19
|
+
* The color of the checkbox. Defaults to `default`.
|
|
20
|
+
*/
|
|
9
21
|
color?: StudioCMSColorway;
|
|
22
|
+
/**
|
|
23
|
+
* Whether the checkbox is checked by default.
|
|
24
|
+
*/
|
|
10
25
|
defaultChecked?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Whether the checkbox is disabled.
|
|
28
|
+
*/
|
|
11
29
|
disabled?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* The name of the checkbox.
|
|
32
|
+
*/
|
|
12
33
|
name?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Whether the checkbox is required.
|
|
36
|
+
*/
|
|
13
37
|
isRequired?: boolean;
|
|
14
38
|
}
|
|
15
39
|
|
|
@@ -38,7 +62,13 @@ const iconSizes = {
|
|
|
38
62
|
size,
|
|
39
63
|
]}
|
|
40
64
|
>
|
|
41
|
-
<div
|
|
65
|
+
<div
|
|
66
|
+
class="sui-checkmark-container"
|
|
67
|
+
tabindex="0"
|
|
68
|
+
role="checkbox"
|
|
69
|
+
aria-checked={defaultChecked}
|
|
70
|
+
aria-labelledby={`label-${name}`}
|
|
71
|
+
>
|
|
42
72
|
<Checkmark
|
|
43
73
|
class={'sui-checkmark'}
|
|
44
74
|
width={iconSizes[size]}
|
|
@@ -52,12 +82,45 @@ const iconSizes = {
|
|
|
52
82
|
disabled={disabled}
|
|
53
83
|
required={isRequired}
|
|
54
84
|
class="sui-checkbox"
|
|
85
|
+
hidden
|
|
55
86
|
/>
|
|
56
87
|
</div>
|
|
57
|
-
<span>
|
|
88
|
+
<span id={`label-${name}`}>
|
|
58
89
|
{label} <span class="req-star">{isRequired && "*"}</span>
|
|
59
90
|
</span>
|
|
60
91
|
</label>
|
|
92
|
+
<script>
|
|
93
|
+
const elements = document.querySelectorAll<HTMLDivElement>('.sui-checkmark-container');
|
|
94
|
+
const checkbox = document.querySelectorAll<HTMLInputElement>('.sui-checkbox');
|
|
95
|
+
|
|
96
|
+
for (const element of elements) {
|
|
97
|
+
if (element.dataset.initialized) continue;
|
|
98
|
+
|
|
99
|
+
element.dataset.initialized = 'true';
|
|
100
|
+
|
|
101
|
+
element.addEventListener('keydown', (e) => {
|
|
102
|
+
if (e.key !== 'Enter' && e.key !== ' ') return;
|
|
103
|
+
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
|
|
106
|
+
const checkbox = element.querySelector<HTMLInputElement>('.sui-checkbox');
|
|
107
|
+
|
|
108
|
+
if (!checkbox) return;
|
|
109
|
+
|
|
110
|
+
checkbox.click();
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (const box of checkbox) {
|
|
115
|
+
if (box.dataset.initialized) continue;
|
|
116
|
+
|
|
117
|
+
box.dataset.initialized = 'true';
|
|
118
|
+
|
|
119
|
+
box.addEventListener('change', (e) => {
|
|
120
|
+
box.parentElement!.ariaChecked = (e.target as HTMLInputElement).checked ? 'true' : 'false';
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
</script>
|
|
61
124
|
<style>
|
|
62
125
|
.sui-checkmark-label {
|
|
63
126
|
display: flex;
|
|
@@ -82,7 +145,13 @@ const iconSizes = {
|
|
|
82
145
|
border: 2px solid hsl(var(--default-base));
|
|
83
146
|
border-radius: .5rem;
|
|
84
147
|
cursor: pointer;
|
|
85
|
-
transition:
|
|
148
|
+
transition: background-color .15s, border .15s, transform .15s;
|
|
149
|
+
transition-timing-function: ease;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.sui-checkmark-container:focus-visible {
|
|
153
|
+
outline: 2px solid hsl(var(--text-normal));
|
|
154
|
+
outline-offset: 2px;
|
|
86
155
|
}
|
|
87
156
|
|
|
88
157
|
.sui-checkmark-label:hover .sui-checkmark-container {
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
---
|
|
2
|
+
/**
|
|
3
|
+
* Props for the divider component.
|
|
4
|
+
*/
|
|
2
5
|
interface Props {
|
|
6
|
+
/**
|
|
7
|
+
* The background color of the divider, used to "hide" content behind the slot. Defaults to `background-base`.
|
|
8
|
+
*/
|
|
3
9
|
background?: 'background-base' | 'background-step-1' | 'background-step-2' | 'background-step-3';
|
|
4
10
|
}
|
|
5
11
|
|
|
6
12
|
const { background = 'background-base' } = Astro.props;
|
|
7
13
|
---
|
|
8
14
|
<div class="sui-divider-container">
|
|
9
|
-
<
|
|
15
|
+
<hr class="sui-divider-line" />
|
|
10
16
|
<div class="sui-divider-content" style={`background-color: hsl(var(--${background}));`}>
|
|
11
17
|
<slot />
|
|
12
18
|
</div>
|
|
@@ -29,6 +35,7 @@ const { background = 'background-base' } = Astro.props;
|
|
|
29
35
|
height: 1px;
|
|
30
36
|
background-color: hsl(var(--border));
|
|
31
37
|
z-index: 1;
|
|
38
|
+
border: none;
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
.sui-divider-content {
|
|
@@ -3,21 +3,63 @@ import Icon from '../../utils/Icon.astro';
|
|
|
3
3
|
import type { StudioCMSColorway } from '../../utils/colors';
|
|
4
4
|
import type { HeroIconName } from '../../utils/iconType';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* An option in the dropdown.
|
|
8
|
+
*/
|
|
6
9
|
interface Option {
|
|
10
|
+
/**
|
|
11
|
+
* The label of the option.
|
|
12
|
+
*/
|
|
7
13
|
label: string;
|
|
14
|
+
/**
|
|
15
|
+
* The value of the option, returned by the helper when listened for.
|
|
16
|
+
*/
|
|
8
17
|
value: string;
|
|
18
|
+
/**
|
|
19
|
+
* Whether the option is disabled.
|
|
20
|
+
*/
|
|
9
21
|
disabled?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The color of the option.
|
|
24
|
+
*/
|
|
10
25
|
color?: StudioCMSColorway;
|
|
26
|
+
/**
|
|
27
|
+
* The icon to display next to the option.
|
|
28
|
+
*/
|
|
11
29
|
icon?: HeroIconName;
|
|
30
|
+
/**
|
|
31
|
+
* The href to link to when the option is clicked. When given, the option will be rendered as an anchor tag.
|
|
32
|
+
*/
|
|
12
33
|
href?: string;
|
|
13
34
|
}
|
|
14
35
|
|
|
36
|
+
/**
|
|
37
|
+
* The props for the Dropdown component.
|
|
38
|
+
*/
|
|
15
39
|
interface Props {
|
|
40
|
+
/**
|
|
41
|
+
* The options to display in the dropdown.
|
|
42
|
+
*/
|
|
16
43
|
options: Option[];
|
|
44
|
+
/**
|
|
45
|
+
* Whether the dropdown is disabled.
|
|
46
|
+
*/
|
|
17
47
|
disabled?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The ID of the dropdown. Required because of the helper.
|
|
50
|
+
*/
|
|
18
51
|
id: string;
|
|
52
|
+
/**
|
|
53
|
+
* The alignment of the dropdown, defaults to `center`. Will not work on mobile due to size constraints.
|
|
54
|
+
*/
|
|
19
55
|
align?: 'start' | 'center' | 'end';
|
|
56
|
+
/**
|
|
57
|
+
* The type of click with which the dropdown is clicked. Defaults to `left`.
|
|
58
|
+
*/
|
|
20
59
|
triggerOn?: 'left' | 'right' | 'both';
|
|
60
|
+
/**
|
|
61
|
+
* The offset of the dropdown from the trigger element in pixels.
|
|
62
|
+
*/
|
|
21
63
|
offset?: number;
|
|
22
64
|
}
|
|
23
65
|
|
|
@@ -42,12 +84,21 @@ const {
|
|
|
42
84
|
<div class="sui-dropdown-toggle" id={`${id}-toggle-btn`}>
|
|
43
85
|
<slot />
|
|
44
86
|
</div>
|
|
45
|
-
<ul
|
|
87
|
+
<ul
|
|
88
|
+
class="sui-dropdown"
|
|
89
|
+
class:list={[align]}
|
|
90
|
+
role="listbox" id={`${id}-dropdown`}
|
|
91
|
+
transition:persist
|
|
92
|
+
transition:persist-props
|
|
93
|
+
aria-labelledby={`${id}-toggle-btn`}
|
|
94
|
+
>
|
|
46
95
|
{options.map(({ value, disabled, color, label, icon, href }) => (
|
|
47
96
|
<li
|
|
48
97
|
class="sui-dropdown-option"
|
|
49
98
|
data-value={value}
|
|
50
99
|
class:list={[disabled && "disabled", icon && "has-icon", color, href && "has-href"]}
|
|
100
|
+
role="option"
|
|
101
|
+
aria-selected="false"
|
|
51
102
|
>
|
|
52
103
|
{icon && (
|
|
53
104
|
<Icon width={24} height={24} name={icon} />
|
|
@@ -197,7 +248,7 @@ const {
|
|
|
197
248
|
user-select: none;
|
|
198
249
|
}
|
|
199
250
|
|
|
200
|
-
.sui-dropdown-option:hover {
|
|
251
|
+
.sui-dropdown-option:hover, .sui-dropdown-option:focus, .sui-dropdown-option.focused {
|
|
201
252
|
background-color: hsl(var(--background-step-3));
|
|
202
253
|
}
|
|
203
254
|
|
|
@@ -208,6 +259,8 @@ const {
|
|
|
208
259
|
.sui-dropdown-link {
|
|
209
260
|
padding: .5rem .75rem;
|
|
210
261
|
width: 100%;
|
|
262
|
+
text-decoration: none;
|
|
263
|
+
color: hsl(var(--text-normal));
|
|
211
264
|
}
|
|
212
265
|
|
|
213
266
|
.sui-dropdown-option.primary {
|
|
@@ -6,9 +6,15 @@ class DropdownHelper {
|
|
|
6
6
|
private alignment: 'start' | 'center' | 'end';
|
|
7
7
|
private triggerOn: 'left' | 'right' | 'both';
|
|
8
8
|
private fullWidth = false;
|
|
9
|
+
private focusIndex = -1;
|
|
9
10
|
|
|
10
11
|
active = false;
|
|
11
12
|
|
|
13
|
+
/**
|
|
14
|
+
* A helper function to interact with dropdowns.
|
|
15
|
+
* @param id The ID of the dropdown.
|
|
16
|
+
* @param fullWidth Whether the dropdown should be full width. Not needed normally.
|
|
17
|
+
*/
|
|
12
18
|
constructor(id: string, fullWidth?: boolean) {
|
|
13
19
|
this.container = document.getElementById(`${id}-container`) as HTMLDivElement;
|
|
14
20
|
|
|
@@ -22,6 +28,44 @@ class DropdownHelper {
|
|
|
22
28
|
this.toggleEl = document.getElementById(`${id}-toggle-btn`) as HTMLDivElement;
|
|
23
29
|
this.dropdown = document.getElementById(`${id}-dropdown`) as HTMLUListElement;
|
|
24
30
|
|
|
31
|
+
if (fullWidth) this.fullWidth = true;
|
|
32
|
+
|
|
33
|
+
this.hideOnClickOutside(this.container);
|
|
34
|
+
|
|
35
|
+
this.initialBehaviorRegistration();
|
|
36
|
+
this.initialOptClickRegistration();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Registers a click callback for the dropdown options. Whenever one of the options
|
|
41
|
+
* is clicked, the callback will be called with the value of the option.
|
|
42
|
+
* @param func The callback function.
|
|
43
|
+
*/
|
|
44
|
+
public registerClickCallback = (func: (value: string) => void) => {
|
|
45
|
+
const dropdownOpts = this.dropdown.querySelectorAll('li');
|
|
46
|
+
|
|
47
|
+
for (const opt of dropdownOpts) {
|
|
48
|
+
opt.removeEventListener('click', this.hide);
|
|
49
|
+
|
|
50
|
+
opt.addEventListener('click', () => {
|
|
51
|
+
func(opt.dataset.value || '');
|
|
52
|
+
this.hide();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Sets up all listeners for the dropdown.
|
|
59
|
+
*/
|
|
60
|
+
private initialBehaviorRegistration = () => {
|
|
61
|
+
window.addEventListener('scroll', this.hide);
|
|
62
|
+
document.addEventListener('keydown', (e) => {
|
|
63
|
+
if (e.key === 'Escape') this.hide();
|
|
64
|
+
});
|
|
65
|
+
document.addEventListener('astro:before-preparation', () => {
|
|
66
|
+
this.dropdown.classList.remove('initialized');
|
|
67
|
+
});
|
|
68
|
+
|
|
25
69
|
if (this.triggerOn === 'left') {
|
|
26
70
|
this.toggleEl.addEventListener('click', this.toggle);
|
|
27
71
|
} else if (this.triggerOn === 'both') {
|
|
@@ -37,31 +81,56 @@ class DropdownHelper {
|
|
|
37
81
|
});
|
|
38
82
|
}
|
|
39
83
|
|
|
40
|
-
|
|
84
|
+
this.toggleEl.addEventListener('keydown', (e) => {
|
|
85
|
+
if (!this.active) return;
|
|
41
86
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this.dropdown.classList.remove('initialized');
|
|
45
|
-
});
|
|
87
|
+
if (e.key === 'Enter') {
|
|
88
|
+
e.preventDefault();
|
|
46
89
|
|
|
47
|
-
|
|
90
|
+
const focused = this.dropdown.querySelector('li.focused') as HTMLLIElement;
|
|
48
91
|
|
|
49
|
-
|
|
50
|
-
|
|
92
|
+
if (!focused) {
|
|
93
|
+
this.hide();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
51
96
|
|
|
52
|
-
|
|
53
|
-
|
|
97
|
+
focused.click();
|
|
98
|
+
}
|
|
54
99
|
|
|
55
|
-
|
|
56
|
-
|
|
100
|
+
if (e.key === 'ArrowDown') {
|
|
101
|
+
e.preventDefault();
|
|
57
102
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
103
|
+
this.focusIndex =
|
|
104
|
+
this.focusIndex === this.dropdown.children.length - 1 ? 0 : this.focusIndex + 1;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (e.key === 'ArrowUp') {
|
|
108
|
+
e.preventDefault();
|
|
109
|
+
|
|
110
|
+
this.focusIndex =
|
|
111
|
+
this.focusIndex === 0 ? this.dropdown.children.length - 1 : this.focusIndex - 1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
115
|
+
if (this.focusIndex > this.dropdown.children.length - 1) {
|
|
116
|
+
this.focusIndex = 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.dropdown.querySelector('li.focused')?.classList.remove('focused');
|
|
120
|
+
|
|
121
|
+
const newFocus = this.dropdown.children[this.focusIndex] as HTMLLIElement;
|
|
122
|
+
|
|
123
|
+
if (!newFocus) return;
|
|
124
|
+
|
|
125
|
+
newFocus.classList.add('focused');
|
|
126
|
+
newFocus.focus();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
63
129
|
};
|
|
64
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Registers callbacks to hide the dropdown when an option is clicked.
|
|
133
|
+
*/
|
|
65
134
|
private initialOptClickRegistration = () => {
|
|
66
135
|
const dropdownOpts = this.dropdown.querySelectorAll('li');
|
|
67
136
|
|
|
@@ -70,6 +139,9 @@ class DropdownHelper {
|
|
|
70
139
|
}
|
|
71
140
|
};
|
|
72
141
|
|
|
142
|
+
/**
|
|
143
|
+
* A function to toggle the dropdown.
|
|
144
|
+
*/
|
|
73
145
|
public toggle = () => {
|
|
74
146
|
if (this.active) {
|
|
75
147
|
this.hide();
|
|
@@ -79,13 +151,22 @@ class DropdownHelper {
|
|
|
79
151
|
this.show();
|
|
80
152
|
};
|
|
81
153
|
|
|
154
|
+
/**
|
|
155
|
+
* A function to hide the dropdown.
|
|
156
|
+
*/
|
|
82
157
|
public hide = () => {
|
|
83
158
|
this.dropdown.classList.remove('active');
|
|
84
159
|
this.active = false;
|
|
160
|
+
this.focusIndex = -1;
|
|
161
|
+
|
|
162
|
+
this.dropdown.querySelector('li.focused')?.classList.remove('focused');
|
|
85
163
|
|
|
86
164
|
setTimeout(() => this.dropdown.classList.remove('above', 'below'), 200);
|
|
87
165
|
};
|
|
88
166
|
|
|
167
|
+
/**
|
|
168
|
+
* A function to show the dropdown.
|
|
169
|
+
*/
|
|
89
170
|
public show = () => {
|
|
90
171
|
const isMobile = window.matchMedia('screen and (max-width: 840px)').matches;
|
|
91
172
|
|
|
@@ -145,11 +226,17 @@ class DropdownHelper {
|
|
|
145
226
|
CustomRect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
|
146
227
|
) {
|
|
147
228
|
this.dropdown.classList.add('active', 'below');
|
|
229
|
+
this.focusIndex = -1;
|
|
148
230
|
} else {
|
|
149
231
|
this.dropdown.classList.add('active', 'above');
|
|
232
|
+
this.focusIndex = this.dropdown.children.length;
|
|
150
233
|
}
|
|
151
234
|
};
|
|
152
235
|
|
|
236
|
+
/**
|
|
237
|
+
* A jQuery-like function to hide the dropdown when clicking outside of it.
|
|
238
|
+
* @param element The element to hide when clicking outside of it.
|
|
239
|
+
*/
|
|
153
240
|
private hideOnClickOutside = (element: HTMLElement) => {
|
|
154
241
|
const outsideClickListener = (event: MouseEvent) => {
|
|
155
242
|
if (!event.target) return;
|
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { HTMLAttributes } from 'astro/types';
|
|
3
3
|
|
|
4
|
+
interface FooterLink {
|
|
5
|
+
/**
|
|
6
|
+
* The label of the link.
|
|
7
|
+
*/
|
|
8
|
+
label: string;
|
|
9
|
+
/**
|
|
10
|
+
* The href of the link.
|
|
11
|
+
*/
|
|
12
|
+
href: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The props for the footer component.
|
|
17
|
+
*/
|
|
4
18
|
interface Props extends HTMLAttributes<'footer'> {
|
|
19
|
+
/**
|
|
20
|
+
* The links to display in the footer.
|
|
21
|
+
*/
|
|
5
22
|
links: {
|
|
6
|
-
[label: string]:
|
|
7
|
-
label: string;
|
|
8
|
-
href: string;
|
|
9
|
-
}[];
|
|
23
|
+
[label: string]: FooterLink[];
|
|
10
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* The copyright text to display in the footer.
|
|
27
|
+
*/
|
|
11
28
|
copyright: string;
|
|
12
29
|
}
|
|
13
30
|
|
|
@@ -2,14 +2,41 @@
|
|
|
2
2
|
import type { HTMLAttributes } from 'astro/types';
|
|
3
3
|
import { generateID } from '../utils/generateID';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* The props for the input component.
|
|
7
|
+
*/
|
|
5
8
|
interface Props extends HTMLAttributes<'input'> {
|
|
9
|
+
/**
|
|
10
|
+
* The label of the input.
|
|
11
|
+
*/
|
|
6
12
|
label?: string;
|
|
13
|
+
/**
|
|
14
|
+
* The type of the input. Defaults to `text`.
|
|
15
|
+
*/
|
|
7
16
|
type?: 'text' | 'password' | 'email' | 'number' | 'tel' | 'url';
|
|
17
|
+
/**
|
|
18
|
+
* The placeholder of the input.
|
|
19
|
+
*/
|
|
8
20
|
placeholder?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Whether the input is required. Defaults to `false`.
|
|
23
|
+
*/
|
|
9
24
|
isRequired?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* The name attribute for the input. Useful for form submission.
|
|
27
|
+
*/
|
|
10
28
|
name?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the input is disabled. Defaults to `false`.
|
|
31
|
+
*/
|
|
11
32
|
disabled?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* The default value of the input.
|
|
35
|
+
*/
|
|
12
36
|
defaultValue?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Additional classes to apply to the input.
|
|
39
|
+
*/
|
|
13
40
|
class?: string;
|
|
14
41
|
}
|
|
15
42
|
|
|
@@ -8,12 +8,36 @@ interface ButtonType {
|
|
|
8
8
|
color: StudioCMSColorway;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* The props for the Modal component.
|
|
13
|
+
*/
|
|
11
14
|
interface Props {
|
|
15
|
+
/**
|
|
16
|
+
* The ID of the modal. Required due to the helper.
|
|
17
|
+
*/
|
|
12
18
|
id: string;
|
|
19
|
+
/**
|
|
20
|
+
* The size of the modal. Defaults to `md`.
|
|
21
|
+
*/
|
|
13
22
|
size?: 'sm' | 'md' | 'lg';
|
|
23
|
+
/**
|
|
24
|
+
* Whether the modal is dismissable. Defaults to `true`.
|
|
25
|
+
*/
|
|
14
26
|
dismissable?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* The cancel button of the modal. If a string is given, the button will have the danger color and flat variant,
|
|
29
|
+
* and the string will be the label. If ButtonType is given, the button will have the color and variant specified.
|
|
30
|
+
*/
|
|
15
31
|
cancelButton?: string | ButtonType;
|
|
32
|
+
/**
|
|
33
|
+
* The action button of the modal. If a string is given, the button will have the primary color and solid variant,
|
|
34
|
+
* and the string will be the label. If ButtonType is given, the button will have the color and variant specified.
|
|
35
|
+
*/
|
|
16
36
|
actionButton?: string | ButtonType;
|
|
37
|
+
/**
|
|
38
|
+
* Whether the modal is a form. Defaults to `false`. When set to true, the modal helpers submit callback will include
|
|
39
|
+
* the form data.
|
|
40
|
+
*/
|
|
17
41
|
isForm?: boolean;
|
|
18
42
|
}
|
|
19
43
|
|
|
@@ -34,7 +58,7 @@ const {
|
|
|
34
58
|
data-has-cancel-button={cancelButton}
|
|
35
59
|
class="sui-modal"
|
|
36
60
|
class:list={[size]}
|
|
37
|
-
data-form={
|
|
61
|
+
data-form={isForm}
|
|
38
62
|
>
|
|
39
63
|
<div class="sui-modal-header">
|
|
40
64
|
<slot name="header" />
|
|
@@ -83,6 +107,7 @@ const {
|
|
|
83
107
|
overflow: visible;
|
|
84
108
|
margin: auto;
|
|
85
109
|
z-index: 50;
|
|
110
|
+
max-width: calc(100% - 4rem);
|
|
86
111
|
}
|
|
87
112
|
|
|
88
113
|
.sui-modal.sm {
|
|
@@ -139,6 +164,11 @@ const {
|
|
|
139
164
|
background-color: hsl(var(--default-base));
|
|
140
165
|
}
|
|
141
166
|
|
|
167
|
+
.x-mark-container:focus-visible {
|
|
168
|
+
outline: 2px solid hsl(var(--text-normal));
|
|
169
|
+
outline-offset: 2px;
|
|
170
|
+
}
|
|
171
|
+
|
|
142
172
|
.sui-modal-footer {
|
|
143
173
|
display: none;
|
|
144
174
|
}
|