@studiocms/ui 0.0.1 → 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/README.md +28 -544
- package/package.json +11 -6
- package/src/components/Button.astro +303 -269
- package/src/components/Card.astro +37 -13
- package/src/components/Center.astro +2 -2
- package/src/components/Checkbox.astro +99 -29
- package/src/components/Divider.astro +15 -8
- package/src/components/Dropdown/Dropdown.astro +102 -41
- package/src/components/Dropdown/dropdown.ts +111 -23
- package/src/components/Footer.astro +137 -0
- package/src/components/Input.astro +42 -14
- package/src/components/Modal/Modal.astro +84 -30
- package/src/components/Modal/modal.ts +43 -9
- package/src/components/RadioGroup.astro +153 -29
- package/src/components/Row.astro +16 -7
- package/src/components/SearchSelect.astro +278 -222
- package/src/components/Select.astro +260 -127
- package/src/components/Sidebar/Double.astro +12 -12
- package/src/components/Sidebar/Single.astro +6 -6
- package/src/components/Sidebar/helpers.ts +53 -7
- 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 +56 -15
- package/src/components/ThemeToggle.astro +14 -8
- package/src/components/Toast/Toaster.astro +171 -31
- package/src/components/Toggle.astro +89 -21
- package/src/components/User.astro +34 -15
- package/src/components/index.ts +24 -22
- package/src/components.ts +2 -0
- package/src/css/colors.css +65 -65
- package/src/css/resets.css +0 -1
- package/src/integration.ts +18 -0
- package/src/layouts/RootLayout.astro +1 -2
- package/src/types/index.ts +1 -1
- package/src/utils/ThemeHelper.ts +135 -117
- package/src/utils/create-resolver.ts +30 -0
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
class DropdownHelper {
|
|
2
|
-
container: HTMLDivElement;
|
|
3
|
-
toggleEl: HTMLDivElement;
|
|
4
|
-
dropdown: HTMLUListElement;
|
|
2
|
+
private container: HTMLDivElement;
|
|
3
|
+
private toggleEl: HTMLDivElement;
|
|
4
|
+
private dropdown: HTMLUListElement;
|
|
5
|
+
|
|
6
|
+
private alignment: 'start' | 'center' | 'end';
|
|
7
|
+
private triggerOn: 'left' | 'right' | 'both';
|
|
8
|
+
private fullWidth = false;
|
|
9
|
+
private focusIndex = -1;
|
|
5
10
|
|
|
6
|
-
alignment: 'start' | 'center' | 'end';
|
|
7
|
-
triggerOn: 'left' | 'right' | 'both';
|
|
8
11
|
active = false;
|
|
9
|
-
fullWidth = false;
|
|
10
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
|
+
*/
|
|
11
18
|
constructor(id: string, fullWidth?: boolean) {
|
|
12
19
|
this.container = document.getElementById(`${id}-container`) as HTMLDivElement;
|
|
13
20
|
|
|
@@ -21,6 +28,44 @@ class DropdownHelper {
|
|
|
21
28
|
this.toggleEl = document.getElementById(`${id}-toggle-btn`) as HTMLDivElement;
|
|
22
29
|
this.dropdown = document.getElementById(`${id}-dropdown`) as HTMLUListElement;
|
|
23
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
|
+
|
|
24
69
|
if (this.triggerOn === 'left') {
|
|
25
70
|
this.toggleEl.addEventListener('click', this.toggle);
|
|
26
71
|
} else if (this.triggerOn === 'both') {
|
|
@@ -36,31 +81,56 @@ class DropdownHelper {
|
|
|
36
81
|
});
|
|
37
82
|
}
|
|
38
83
|
|
|
39
|
-
|
|
84
|
+
this.toggleEl.addEventListener('keydown', (e) => {
|
|
85
|
+
if (!this.active) return;
|
|
40
86
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
this.dropdown.classList.remove('initialized');
|
|
44
|
-
});
|
|
87
|
+
if (e.key === 'Enter') {
|
|
88
|
+
e.preventDefault();
|
|
45
89
|
|
|
46
|
-
|
|
90
|
+
const focused = this.dropdown.querySelector('li.focused') as HTMLLIElement;
|
|
47
91
|
|
|
48
|
-
|
|
49
|
-
|
|
92
|
+
if (!focused) {
|
|
93
|
+
this.hide();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
50
96
|
|
|
51
|
-
|
|
52
|
-
|
|
97
|
+
focused.click();
|
|
98
|
+
}
|
|
53
99
|
|
|
54
|
-
|
|
55
|
-
|
|
100
|
+
if (e.key === 'ArrowDown') {
|
|
101
|
+
e.preventDefault();
|
|
56
102
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
+
});
|
|
62
129
|
};
|
|
63
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Registers callbacks to hide the dropdown when an option is clicked.
|
|
133
|
+
*/
|
|
64
134
|
private initialOptClickRegistration = () => {
|
|
65
135
|
const dropdownOpts = this.dropdown.querySelectorAll('li');
|
|
66
136
|
|
|
@@ -69,6 +139,9 @@ class DropdownHelper {
|
|
|
69
139
|
}
|
|
70
140
|
};
|
|
71
141
|
|
|
142
|
+
/**
|
|
143
|
+
* A function to toggle the dropdown.
|
|
144
|
+
*/
|
|
72
145
|
public toggle = () => {
|
|
73
146
|
if (this.active) {
|
|
74
147
|
this.hide();
|
|
@@ -78,13 +151,22 @@ class DropdownHelper {
|
|
|
78
151
|
this.show();
|
|
79
152
|
};
|
|
80
153
|
|
|
154
|
+
/**
|
|
155
|
+
* A function to hide the dropdown.
|
|
156
|
+
*/
|
|
81
157
|
public hide = () => {
|
|
82
158
|
this.dropdown.classList.remove('active');
|
|
83
159
|
this.active = false;
|
|
160
|
+
this.focusIndex = -1;
|
|
161
|
+
|
|
162
|
+
this.dropdown.querySelector('li.focused')?.classList.remove('focused');
|
|
84
163
|
|
|
85
164
|
setTimeout(() => this.dropdown.classList.remove('above', 'below'), 200);
|
|
86
165
|
};
|
|
87
166
|
|
|
167
|
+
/**
|
|
168
|
+
* A function to show the dropdown.
|
|
169
|
+
*/
|
|
88
170
|
public show = () => {
|
|
89
171
|
const isMobile = window.matchMedia('screen and (max-width: 840px)').matches;
|
|
90
172
|
|
|
@@ -144,11 +226,17 @@ class DropdownHelper {
|
|
|
144
226
|
CustomRect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
|
145
227
|
) {
|
|
146
228
|
this.dropdown.classList.add('active', 'below');
|
|
229
|
+
this.focusIndex = -1;
|
|
147
230
|
} else {
|
|
148
231
|
this.dropdown.classList.add('active', 'above');
|
|
232
|
+
this.focusIndex = this.dropdown.children.length;
|
|
149
233
|
}
|
|
150
234
|
};
|
|
151
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
|
+
*/
|
|
152
240
|
private hideOnClickOutside = (element: HTMLElement) => {
|
|
153
241
|
const outsideClickListener = (event: MouseEvent) => {
|
|
154
242
|
if (!event.target) return;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types';
|
|
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
|
+
*/
|
|
18
|
+
interface Props extends HTMLAttributes<'footer'> {
|
|
19
|
+
/**
|
|
20
|
+
* The links to display in the footer.
|
|
21
|
+
*/
|
|
22
|
+
links: {
|
|
23
|
+
[label: string]: FooterLink[];
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* The copyright text to display in the footer.
|
|
27
|
+
*/
|
|
28
|
+
copyright: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { copyright, links, ...props } = Astro.props;
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
<footer {...props}>
|
|
35
|
+
<div class="upper">
|
|
36
|
+
<div>
|
|
37
|
+
<slot name="brand" />
|
|
38
|
+
</div>
|
|
39
|
+
<div class="links">
|
|
40
|
+
{Object.keys(links).map((groupLabel) => (
|
|
41
|
+
<ul>
|
|
42
|
+
<li class="sui-footer-link-label">{groupLabel}</li>
|
|
43
|
+
{links[groupLabel]!.map((item) => (
|
|
44
|
+
<li>
|
|
45
|
+
<a href={item.href}>{item.label}</a>
|
|
46
|
+
</li>
|
|
47
|
+
))}
|
|
48
|
+
</ul>
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<hr class="separator" />
|
|
53
|
+
<div class="lower">
|
|
54
|
+
<span class="copyright-span">{copyright}</span>
|
|
55
|
+
<slot name="socials" />
|
|
56
|
+
</div>
|
|
57
|
+
</footer>
|
|
58
|
+
<style>
|
|
59
|
+
footer {
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
gap: 2rem;
|
|
63
|
+
background-color: hsl(var(--background-step-1));
|
|
64
|
+
padding: 2rem 10vw;
|
|
65
|
+
color: hsl(var(--text-normal)) !important;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.upper, .lower {
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-direction: row;
|
|
71
|
+
justify-content: space-between;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.links {
|
|
75
|
+
display: flex;
|
|
76
|
+
justify-content: flex-end;
|
|
77
|
+
flex-direction: row;
|
|
78
|
+
flex-wrap: wrap;
|
|
79
|
+
gap: 2rem;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.links ul {
|
|
83
|
+
list-style-type: none;
|
|
84
|
+
margin: 0 !important;
|
|
85
|
+
padding: 0 !important;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.links ul li, .links ul li * {
|
|
89
|
+
color: hsl(var(--text-normal)) !important;
|
|
90
|
+
width: fit-content;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.links ul li:has(a):hover {
|
|
94
|
+
text-decoration: underline;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.sui-footer-link-label {
|
|
98
|
+
font-size: 1.125em;
|
|
99
|
+
font-weight: 700;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.separator {
|
|
103
|
+
height: 1px;
|
|
104
|
+
width: 100%;
|
|
105
|
+
border: none;
|
|
106
|
+
background: hsl(var(--border));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.lower {
|
|
110
|
+
align-items: center;
|
|
111
|
+
flex-wrap: wrap;
|
|
112
|
+
gap: 1rem;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@media screen and (max-width: 1440px) {
|
|
116
|
+
footer {
|
|
117
|
+
padding: 2rem;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@media screen and (max-width: 1280px) {
|
|
122
|
+
.upper {
|
|
123
|
+
flex-direction: column;
|
|
124
|
+
gap: 2rem;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.links {
|
|
128
|
+
justify-content: flex-start;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@media screen and (max-width: 640px) {
|
|
133
|
+
.links ul {
|
|
134
|
+
width: calc(50% - 1rem);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
</style>
|
|
@@ -2,16 +2,43 @@
|
|
|
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
|
|
|
16
43
|
const {
|
|
17
44
|
label,
|
|
@@ -26,16 +53,18 @@ const {
|
|
|
26
53
|
} = Astro.props;
|
|
27
54
|
---
|
|
28
55
|
|
|
29
|
-
<label for={name} class="input-label" class:list={[disabled && "disabled"]}>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
56
|
+
<label for={name} class="sui-input-label" class:list={[disabled && "disabled"]}>
|
|
57
|
+
{label && (
|
|
58
|
+
<span class="label">
|
|
59
|
+
{label} <span class="req-star">{isRequired && "*"}</span>
|
|
60
|
+
</span>
|
|
61
|
+
)}
|
|
33
62
|
<input
|
|
34
63
|
placeholder={placeholder}
|
|
35
64
|
name={name}
|
|
36
65
|
id={name}
|
|
37
66
|
type={type}
|
|
38
|
-
class="input"
|
|
67
|
+
class="sui-input"
|
|
39
68
|
class:list={[className]}
|
|
40
69
|
required={isRequired}
|
|
41
70
|
disabled={disabled}
|
|
@@ -44,15 +73,14 @@ const {
|
|
|
44
73
|
/>
|
|
45
74
|
</label>
|
|
46
75
|
<style>
|
|
47
|
-
.input-label {
|
|
76
|
+
.sui-input-label {
|
|
48
77
|
width: 100%;
|
|
49
78
|
display: flex;
|
|
50
79
|
flex-direction: column;
|
|
51
80
|
gap: .25rem;
|
|
52
|
-
margin-top: .5rem;
|
|
53
81
|
}
|
|
54
82
|
|
|
55
|
-
.input-label.disabled {
|
|
83
|
+
.sui-input-label.disabled {
|
|
56
84
|
opacity: 0.5;
|
|
57
85
|
pointer-events: none;
|
|
58
86
|
color: hsl(var(--text-muted));
|
|
@@ -62,7 +90,7 @@ const {
|
|
|
62
90
|
font-size: 14px;
|
|
63
91
|
}
|
|
64
92
|
|
|
65
|
-
.input {
|
|
93
|
+
.sui-input {
|
|
66
94
|
padding: .5rem 1rem;
|
|
67
95
|
border-radius: 8px;
|
|
68
96
|
border: 1px solid hsl(var(--border));
|
|
@@ -71,18 +99,18 @@ const {
|
|
|
71
99
|
transition: all .15s ease;
|
|
72
100
|
}
|
|
73
101
|
|
|
74
|
-
.input:hover {
|
|
102
|
+
.sui-input:hover {
|
|
75
103
|
background: hsl(var(--background-step-3));
|
|
76
104
|
}
|
|
77
105
|
|
|
78
|
-
.input:active,
|
|
79
|
-
.input:focus {
|
|
106
|
+
.sui-input:active,
|
|
107
|
+
.sui-input:focus {
|
|
80
108
|
border: 1px solid hsl(var(--primary-base));
|
|
81
109
|
outline: none;
|
|
82
110
|
background: hsl(var(--background-step-2));
|
|
83
111
|
}
|
|
84
112
|
|
|
85
|
-
.disabled .input:active {
|
|
113
|
+
.disabled .sui-input:active {
|
|
86
114
|
border: 1px solid hsl(var(--border));
|
|
87
115
|
}
|
|
88
116
|
|
|
@@ -1,57 +1,104 @@
|
|
|
1
1
|
---
|
|
2
|
+
import type { StudioCMSColorway } from 'src/utils/colors';
|
|
2
3
|
import Icon from '../../utils/Icon.astro';
|
|
3
4
|
import Button from '../Button.astro';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
interface ButtonType {
|
|
7
|
+
label: string;
|
|
8
|
+
color: StudioCMSColorway;
|
|
9
|
+
}
|
|
6
10
|
|
|
11
|
+
/**
|
|
12
|
+
* The props for the Modal component.
|
|
13
|
+
*/
|
|
7
14
|
interface Props {
|
|
15
|
+
/**
|
|
16
|
+
* The ID of the modal. Required due to the helper.
|
|
17
|
+
*/
|
|
8
18
|
id: string;
|
|
19
|
+
/**
|
|
20
|
+
* The size of the modal. Defaults to `md`.
|
|
21
|
+
*/
|
|
9
22
|
size?: 'sm' | 'md' | 'lg';
|
|
23
|
+
/**
|
|
24
|
+
* Whether the modal is dismissable. Defaults to `true`.
|
|
25
|
+
*/
|
|
10
26
|
dismissable?: boolean;
|
|
11
|
-
|
|
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
|
+
*/
|
|
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
|
+
*/
|
|
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
|
+
*/
|
|
12
41
|
isForm?: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const {
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const {
|
|
45
|
+
id,
|
|
46
|
+
size = 'md',
|
|
47
|
+
dismissable = true,
|
|
48
|
+
isForm = false,
|
|
49
|
+
cancelButton,
|
|
50
|
+
actionButton,
|
|
51
|
+
} = Astro.props;
|
|
16
52
|
---
|
|
17
|
-
<!-- Should: Be centered on mobile, scroll happens inside, blur bg -->
|
|
18
53
|
<dialog
|
|
19
54
|
popover
|
|
20
55
|
id={id}
|
|
21
56
|
data-dismissable={`${dismissable}`}
|
|
22
|
-
data-
|
|
23
|
-
|
|
57
|
+
data-has-action-button={actionButton}
|
|
58
|
+
data-has-cancel-button={cancelButton}
|
|
59
|
+
class="sui-modal"
|
|
24
60
|
class:list={[size]}
|
|
25
|
-
data-form={
|
|
61
|
+
data-form={isForm}
|
|
26
62
|
>
|
|
27
|
-
<div class="modal-header">
|
|
63
|
+
<div class="sui-modal-header">
|
|
28
64
|
<slot name="header" />
|
|
29
|
-
{(dismissable ||
|
|
65
|
+
{(dismissable || (!cancelButton && !actionButton)) && (
|
|
30
66
|
<button class="x-mark-container" id={`${id}-btn-x`}>
|
|
31
67
|
<Icon name="x-mark" width={24} height={24} class={'dismiss-icon'} />
|
|
32
68
|
</button>
|
|
33
69
|
)}
|
|
34
70
|
</div>
|
|
35
71
|
<form id={`${id}-form-element`}>
|
|
36
|
-
<div class="modal-body">
|
|
72
|
+
<div class="sui-modal-body">
|
|
37
73
|
<slot />
|
|
38
74
|
</div>
|
|
39
|
-
<div class="modal-footer">
|
|
40
|
-
{
|
|
41
|
-
<Button
|
|
42
|
-
|
|
75
|
+
<div class="sui-modal-footer">
|
|
76
|
+
{cancelButton && (
|
|
77
|
+
<Button
|
|
78
|
+
id={`${id}-btn-cancel`}
|
|
79
|
+
color={typeof cancelButton === 'string' ? 'danger' : cancelButton.color}
|
|
80
|
+
variant='flat'
|
|
81
|
+
type={isForm ? 'reset' : 'button'}
|
|
82
|
+
>
|
|
83
|
+
{typeof cancelButton === 'string' ? cancelButton : cancelButton.label}
|
|
43
84
|
</Button>
|
|
44
85
|
)}
|
|
45
|
-
{
|
|
46
|
-
<Button
|
|
47
|
-
|
|
86
|
+
{actionButton && (
|
|
87
|
+
<Button
|
|
88
|
+
id={`${id}-btn-confirm`}
|
|
89
|
+
type={'submit'}
|
|
90
|
+
color={typeof actionButton === 'string' ? 'primary' : actionButton.color}
|
|
91
|
+
variant='solid'
|
|
92
|
+
type={isForm ? 'submit' : 'button'}
|
|
93
|
+
>
|
|
94
|
+
{typeof actionButton === 'string' ? actionButton : actionButton.label}
|
|
48
95
|
</Button>
|
|
49
96
|
)}
|
|
50
97
|
</div>
|
|
51
98
|
</form>
|
|
52
99
|
</dialog>
|
|
53
100
|
<style>
|
|
54
|
-
.modal {
|
|
101
|
+
.sui-modal {
|
|
55
102
|
border: 1px solid hsl(var(--border));
|
|
56
103
|
border-radius: .5rem;
|
|
57
104
|
padding: 1.5rem;
|
|
@@ -59,35 +106,37 @@ const { id, size = 'md', dismissable = true, isForm = false, buttons = [] } = As
|
|
|
59
106
|
animation: hide .25s ease;
|
|
60
107
|
overflow: visible;
|
|
61
108
|
margin: auto;
|
|
109
|
+
z-index: 50;
|
|
110
|
+
max-width: calc(100% - 4rem);
|
|
62
111
|
}
|
|
63
112
|
|
|
64
|
-
.modal.sm {
|
|
113
|
+
.sui-modal.sm {
|
|
65
114
|
width: 384px;
|
|
66
115
|
}
|
|
67
116
|
|
|
68
|
-
.modal.md {
|
|
117
|
+
.sui-modal.md {
|
|
69
118
|
width: 448px;
|
|
70
119
|
}
|
|
71
120
|
|
|
72
|
-
.modal.lg {
|
|
121
|
+
.sui-modal.lg {
|
|
73
122
|
width: 608px;
|
|
74
123
|
}
|
|
75
124
|
|
|
76
|
-
.modal[open] {
|
|
125
|
+
.sui-modal[open] {
|
|
77
126
|
animation: show .25s ease-in-out;
|
|
78
127
|
}
|
|
79
128
|
|
|
80
|
-
html:has(.modal[open]),
|
|
81
|
-
body:has(.modal[open]) {
|
|
129
|
+
html:has(.sui-modal[open]),
|
|
130
|
+
body:has(.sui-modal[open]) {
|
|
82
131
|
overflow: hidden;
|
|
83
132
|
}
|
|
84
133
|
|
|
85
|
-
.modal[open]::backdrop {
|
|
134
|
+
.sui-modal[open]::backdrop {
|
|
86
135
|
background-color: rgba(0, 0, 0, 0.75);
|
|
87
136
|
animation: backdrop .3s ease-in-out forwards;
|
|
88
137
|
}
|
|
89
138
|
|
|
90
|
-
.modal-header:has(*) {
|
|
139
|
+
.sui-modal-header:has(*) {
|
|
91
140
|
margin-bottom: 1rem;
|
|
92
141
|
|
|
93
142
|
display: flex;
|
|
@@ -115,11 +164,16 @@ const { id, size = 'md', dismissable = true, isForm = false, buttons = [] } = As
|
|
|
115
164
|
background-color: hsl(var(--default-base));
|
|
116
165
|
}
|
|
117
166
|
|
|
118
|
-
.
|
|
167
|
+
.x-mark-container:focus-visible {
|
|
168
|
+
outline: 2px solid hsl(var(--text-normal));
|
|
169
|
+
outline-offset: 2px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.sui-modal-footer {
|
|
119
173
|
display: none;
|
|
120
174
|
}
|
|
121
175
|
|
|
122
|
-
.modal-footer:has(*) {
|
|
176
|
+
.sui-modal-footer:has(*) {
|
|
123
177
|
display: flex;
|
|
124
178
|
flex-direction: row;
|
|
125
179
|
gap: 1rem;
|