@studiocms/ui 0.0.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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +564 -0
  3. package/package.json +49 -0
  4. package/src/components/BaseHead.astro +22 -0
  5. package/src/components/Button.astro +338 -0
  6. package/src/components/Card.astro +62 -0
  7. package/src/components/Center.astro +16 -0
  8. package/src/components/Checkbox.astro +180 -0
  9. package/src/components/Divider.astro +39 -0
  10. package/src/components/Dropdown/Dropdown.astro +253 -0
  11. package/src/components/Dropdown/dropdown.ts +170 -0
  12. package/src/components/Dropdown/index.ts +2 -0
  13. package/src/components/Input.astro +93 -0
  14. package/src/components/Modal/Modal.astro +164 -0
  15. package/src/components/Modal/index.ts +2 -0
  16. package/src/components/Modal/modal.ts +129 -0
  17. package/src/components/RadioGroup.astro +175 -0
  18. package/src/components/Row.astro +38 -0
  19. package/src/components/SearchSelect.astro +430 -0
  20. package/src/components/Select.astro +334 -0
  21. package/src/components/Sidebar/Double.astro +91 -0
  22. package/src/components/Sidebar/Single.astro +42 -0
  23. package/src/components/Sidebar/helpers.ts +133 -0
  24. package/src/components/Sidebar/index.ts +3 -0
  25. package/src/components/Textarea.astro +102 -0
  26. package/src/components/ThemeToggle.astro +40 -0
  27. package/src/components/Toast/Toaster.astro +330 -0
  28. package/src/components/Toast/index.ts +2 -0
  29. package/src/components/Toast/toast.ts +16 -0
  30. package/src/components/Toggle.astro +146 -0
  31. package/src/components/User.astro +68 -0
  32. package/src/components/index.ts +25 -0
  33. package/src/components.ts +24 -0
  34. package/src/css/colors.css +106 -0
  35. package/src/css/global.css +2 -0
  36. package/src/css/resets.css +55 -0
  37. package/src/env.d.ts +15 -0
  38. package/src/icons/Checkmark.astro +13 -0
  39. package/src/icons/ChevronUpDown.astro +13 -0
  40. package/src/icons/User.astro +13 -0
  41. package/src/icons/X-Mark.astro +13 -0
  42. package/src/layouts/RootLayout.astro +34 -0
  43. package/src/layouts/index.ts +2 -0
  44. package/src/layouts.ts +1 -0
  45. package/src/types/index.ts +11 -0
  46. package/src/utils/Icon.astro +41 -0
  47. package/src/utils/ThemeHelper.ts +127 -0
  48. package/src/utils/colors.ts +1 -0
  49. package/src/utils/generateID.ts +5 -0
  50. package/src/utils/headers.ts +190 -0
  51. package/src/utils/iconStrings.ts +29 -0
  52. package/src/utils/iconType.ts +3 -0
  53. package/src/utils/index.ts +1 -0
@@ -0,0 +1,40 @@
1
+ ---
2
+ import type { ComponentProps } from "astro/types";
3
+ import Button from "./Button.astro";
4
+
5
+ interface Props extends ComponentProps<typeof Button> {};
6
+
7
+ const props = Astro.props;
8
+ ---
9
+
10
+ <Button id='sui__theme-toggle' {...props}>
11
+ <div id="dark-content">
12
+ <slot name="dark" />
13
+ </div>
14
+ <div id="light-content">
15
+ <slot name="light" />
16
+ </div>
17
+ </Button>
18
+
19
+ <script>
20
+ import { ThemeHelper } from '../utils/ThemeHelper';
21
+
22
+ const themeToggle = document.getElementById('sui__theme-toggle');
23
+ const themeHelper = new ThemeHelper();
24
+
25
+ themeHelper.registerToggle(themeToggle);
26
+ </script>
27
+
28
+ <style is:global>
29
+ #sui__theme-toggle #dark-content, #sui__theme-toggle #light-content {
30
+ display: none;
31
+ }
32
+
33
+ [data-theme="dark"] #sui__theme-toggle #dark-content {
34
+ display: block;
35
+ }
36
+
37
+ [data-theme="light"] #sui__theme-toggle #light-content {
38
+ display: block;
39
+ }
40
+ </style>
@@ -0,0 +1,330 @@
1
+ ---
2
+ interface Props {
3
+ position?:
4
+ | 'top-left'
5
+ | 'top-right'
6
+ | 'top-center'
7
+ | 'bottom-left'
8
+ | 'bottom-right'
9
+ | 'bottom-center';
10
+ duration?: number;
11
+ closeButton?: boolean;
12
+ offset?: number;
13
+ gap?: number;
14
+ };
15
+
16
+ const {
17
+ position = 'top-center',
18
+ duration = 4000,
19
+ closeButton = false,
20
+ offset = 32,
21
+ gap = 8,
22
+ } = Astro.props;
23
+ ---
24
+ <div
25
+ id="toaster"
26
+ class:list={[
27
+ closeButton && "closeable",
28
+ position,
29
+ ]},
30
+ >
31
+ <div
32
+ id="toast-drawer"
33
+ data-offset={offset}
34
+ data-gap={gap}
35
+ data-duration={duration}
36
+ style={[
37
+ `${position.includes("top-") ? 'top:' : 'bottom:'} ${offset}px;`,
38
+ position.includes("-left") && `left: ${offset}px`,
39
+ position.includes("-right") && `right: ${offset}px`,
40
+ position.includes("-center") && `left: 50%; transform: translateX(-50%);`,
41
+ `--gap: ${gap}px;`,
42
+ `padding-left: ${offset}px;`,
43
+ `padding-right: ${offset}px;`,
44
+ ].filter(Boolean).join("")}
45
+ />
46
+ </div>
47
+ <script>
48
+ import type { ToastProps } from '../../types';
49
+ import type { ValidIconString } from '../../utils/iconStrings';
50
+
51
+ import { generateID } from '../../utils/generateID';
52
+ import { getIconString } from '../../utils/iconStrings';
53
+
54
+ let activeToasts: string[] = [];
55
+
56
+ function removeToast(toastID: string) {
57
+ const toastEl = document.getElementById(toastID);
58
+
59
+ if (!toastEl) return;
60
+
61
+ activeToasts = activeToasts.filter(x => x !== toastID);
62
+
63
+ toastEl.classList.add('closing');
64
+
65
+ setTimeout(() => toastEl.remove(), 400);
66
+ }
67
+
68
+ function createToast(props: ToastProps) {
69
+ const toastParent = document.getElementById('toast-drawer')! as HTMLDivElement;
70
+
71
+ const toastContainer = document.createElement('div');
72
+ const toastID = generateID('toast');
73
+ toastContainer.id = toastID;
74
+ toastContainer.classList.add('toast-container', props.type, `${props.closeButton || props.persistent && "closeable"}`, `${props.persistent && 'persistent'}`);
75
+
76
+ const toastHeader = document.createElement('div');
77
+ toastHeader.classList.add('toast-header');
78
+
79
+ const toastHeaderLeftSide = document.createElement('div');
80
+ toastHeaderLeftSide.classList.add('toast-header-left-side')
81
+
82
+ const toastTitle = document.createElement('span');
83
+ toastTitle.textContent = props.title;
84
+ toastTitle.classList.add('toast-title');
85
+
86
+ let iconString: ValidIconString;
87
+
88
+ if (props.type === 'success') {
89
+ iconString = 'check-circle';
90
+ } else if (props.type === 'danger') {
91
+ iconString = 'exclamation-circle';
92
+ } else if (props.type === 'warning') {
93
+ iconString = 'exclamation-triangle';
94
+ } else {
95
+ iconString = 'information-circle';
96
+ }
97
+
98
+ const toastIcon = getIconString(iconString, 'toast-icon', 24, 24);
99
+ toastHeaderLeftSide.innerHTML = toastIcon;
100
+
101
+ toastHeaderLeftSide.appendChild(toastTitle);
102
+ toastHeader.appendChild(toastHeaderLeftSide);
103
+
104
+ if (props.closeButton || props.persistent) {
105
+ const closeIconContainer = document.createElement('button');
106
+ closeIconContainer.classList.add('close-icon-container');
107
+ closeIconContainer.addEventListener('click', () => removeToast(toastID));
108
+ closeIconContainer.innerHTML = getIconString('x-mark', 'close-icon', 24, 24);
109
+
110
+ toastHeader.appendChild(closeIconContainer);
111
+ }
112
+
113
+ toastContainer.appendChild(toastHeader);
114
+
115
+ if (props.description) {
116
+ const toastDesc = document.createElement('span');
117
+ toastDesc.innerHTML = props.description;
118
+ toastDesc.classList.add('toast-desc');
119
+
120
+ toastContainer.appendChild(toastDesc);
121
+ }
122
+
123
+ if (!props.persistent) {
124
+ const toastProgressBar = document.createElement('div');
125
+ toastProgressBar.classList.add('toast-progress-bar');
126
+ toastProgressBar.style.animationDuration = props.duration ? `${props.duration}ms` : `${toastParent.dataset.duration || 4000}ms`;
127
+
128
+ toastContainer.appendChild(toastProgressBar);
129
+ }
130
+
131
+ toastParent.appendChild(toastContainer);
132
+
133
+ activeToasts.push(toastID);
134
+
135
+ if (!props.persistent) {
136
+ setTimeout(
137
+ () => removeToast(toastID),
138
+ props.duration || (toastParent.dataset.duration ? parseInt(toastParent.dataset.duration) : 4000)
139
+ );
140
+ }
141
+ }
142
+
143
+ document.addEventListener('createtoast', (e) => {
144
+ e.stopImmediatePropagation();
145
+
146
+ const event = e as CustomEvent<ToastProps>;
147
+
148
+ createToast(event.detail);
149
+ });
150
+ </script>
151
+ <style>
152
+ #toaster {
153
+ width: 100vw;
154
+ height: 100vh;
155
+ position: fixed;
156
+ top: 0;
157
+ left: 0;
158
+ z-index: 100;
159
+ pointer-events: none;
160
+ }
161
+
162
+ #toast-drawer {
163
+ max-width: 420px;
164
+ width: 100%;
165
+ height: fit-content;
166
+ position: absolute;
167
+ display: flex;
168
+ flex-direction: column;
169
+ }
170
+
171
+ #toaster.top-left #toast-drawer,
172
+ #toaster.bottom-left #toast-drawer {
173
+ left: 50%;
174
+ transform: translateX(-50%);
175
+ }
176
+ </style>
177
+ <style is:global>
178
+ .toast-container {
179
+ pointer-events: all;
180
+ padding: 1rem;
181
+ border-radius: .5rem;
182
+ border: 1px solid hsl(var(--border));
183
+ background-color: hsl(var(--background-base));
184
+ box-shadow: 0px 4px 8px hsl(var(--shadow), 0.5);
185
+ display: flex;
186
+ flex-direction: column;
187
+ gap: .5rem;
188
+ position: relative;
189
+ overflow: hidden;
190
+ margin-bottom: var(--gap);
191
+ animation: toast-pop-in .25s ease forwards;
192
+ z-index: 90;
193
+ }
194
+
195
+ .toast-header {
196
+ display: flex;
197
+ flex-direction: row;
198
+ align-items: center;
199
+ justify-content: space-between;
200
+ }
201
+
202
+ .toast-header-left-side {
203
+ display: flex;
204
+ flex-direction: row;
205
+ gap: .5rem;
206
+ align-items: center;
207
+ font-weight: 500;
208
+ font-size: 1.125em;
209
+ }
210
+
211
+ .toast-header-left-side svg {
212
+ color: hsl(var(--primary-base));
213
+ }
214
+
215
+ .toast-container.success .toast-header-left-side svg {
216
+ color: hsl(var(--success-base));
217
+ }
218
+
219
+ .toast-container.warning .toast-header-left-side svg {
220
+ color: hsl(var(--warning-base));
221
+ }
222
+
223
+ .toast-container.danger .toast-header-left-side svg {
224
+ color: hsl(var(--danger-base));
225
+ }
226
+
227
+ .toast-progress-bar {
228
+ position: absolute;
229
+ height: 4px;
230
+ width: 100%;
231
+ bottom: 0;
232
+ left: 0%;
233
+ background-color: hsl(var(--primary-base));
234
+ animation: toast-progress forwards linear;
235
+ }
236
+
237
+ .toast-container.success .toast-progress-bar {
238
+ background-color: hsl(var(--success-base));
239
+ }
240
+
241
+ .toast-container.warning .toast-progress-bar {
242
+ background-color: hsl(var(--warning-base));
243
+ }
244
+
245
+ .toast-container.danger .toast-progress-bar {
246
+ background-color: hsl(var(--danger-base));
247
+ }
248
+
249
+ .close-icon-container {
250
+ cursor: pointer;
251
+ height: 1.5rem;
252
+ width: 1.5rem;
253
+ display: flex;
254
+ align-items: center;
255
+ justify-content: center;
256
+ transition: background-color .15s ease;
257
+ border-radius: .25rem;
258
+ }
259
+
260
+ .close-icon-container:hover {
261
+ background-color: hsl(var(--default-base));
262
+ }
263
+
264
+ .toast-container.closing {
265
+ animation: toast-closing .25s ease forwards;
266
+ }
267
+
268
+ .toast-container.persistent {
269
+ border: 1px solid hsl(var(--primary-base));
270
+ }
271
+
272
+ .toast-container.persistent.success {
273
+ border: 1px solid hsl(var(--success-base));
274
+ }
275
+
276
+ .toast-container.persistent.warning {
277
+ border: 1px solid hsl(var(--warning-base));
278
+ }
279
+
280
+ .toast-container.persistent.danger {
281
+ border: 1px solid hsl(var(--danger-base));
282
+ }
283
+
284
+ @keyframes toast-pop-in {
285
+ 0% {
286
+ opacity: 0;
287
+ scale: 0.75;
288
+ }
289
+ 100% {
290
+ opacity: 1;
291
+ scale: 1;
292
+ }
293
+ }
294
+
295
+ @keyframes toast-closing {
296
+ 0% {
297
+ opacity: 1;
298
+ scale: 1;
299
+ max-height: 500px;
300
+ margin-bottom: var(--gap);
301
+ padding: 1rem;
302
+ border: 1px solid hsl(var(--border));
303
+ }
304
+ 62.5% {
305
+ scale: 0.75;
306
+ opacity: 0;
307
+ max-height: 500px;
308
+ margin-bottom: var(--gap);
309
+ padding: 1rem;
310
+ border: 1px solid hsl(var(--border));
311
+ }
312
+ 100% {
313
+ scale: 0.75;
314
+ opacity: 0;
315
+ max-height: 0px;
316
+ margin-bottom: 0;
317
+ padding: 0;
318
+ border: 0px solid hsl(var(--border));
319
+ }
320
+ }
321
+
322
+ @keyframes toast-progress {
323
+ 0% {
324
+ left: 0%;
325
+ }
326
+ 100% {
327
+ left: -100%;
328
+ }
329
+ }
330
+ </style>
@@ -0,0 +1,2 @@
1
+ export { default as Toaster } from './Toaster.astro';
2
+ export { toast } from './toast';
@@ -0,0 +1,16 @@
1
+ import type { ToastProps } from '../../types';
2
+
3
+ /**
4
+ * A function to create toasts with.
5
+
6
+ * @param props The props to pass to the toast
7
+ */
8
+ function toast(props: ToastProps) {
9
+ const createToast = new CustomEvent('createtoast', {
10
+ detail: props,
11
+ });
12
+
13
+ document.dispatchEvent(createToast);
14
+ }
15
+
16
+ export { toast };
@@ -0,0 +1,146 @@
1
+ ---
2
+ import type { StudioCMSColorway } from '../utils/colors';
3
+ import { generateID } from '../utils/generateID';
4
+
5
+ interface Props {
6
+ label: string;
7
+ size?: 'sm' | 'md' | 'lg';
8
+ color?: StudioCMSColorway;
9
+ defaultChecked?: boolean;
10
+ disabled?: boolean;
11
+ name?: string;
12
+ isRequired?: boolean;
13
+ };
14
+
15
+ const {
16
+ size = 'md',
17
+ color = 'default',
18
+ defaultChecked,
19
+ disabled,
20
+ name = generateID('checkbox'),
21
+ label,
22
+ isRequired,
23
+ } = Astro.props;
24
+ ---
25
+ <label
26
+ class="toggle-label"
27
+ for={name}
28
+ class:list={[
29
+ disabled && "disabled",
30
+ color,
31
+ size,
32
+ ]}
33
+ >
34
+ <div class="toggle-container">
35
+ <div class="toggle-switch" />
36
+ <input
37
+ type="checkbox"
38
+ name={name}
39
+ id={name}
40
+ checked={defaultChecked}
41
+ disabled={disabled}
42
+ required={isRequired}
43
+ class="checkbox"
44
+ />
45
+ </div>
46
+ <span>
47
+ {label} <span class="req-star">{isRequired && "*"}</span>
48
+ </span>
49
+ </label>
50
+ <style>
51
+ .toggle-label {
52
+ display: flex;
53
+ flex-direction: row;
54
+ align-items: center;
55
+ gap: .5rem;
56
+ position: relative;
57
+ margin: .25rem 0;
58
+ }
59
+
60
+ .toggle-label.disabled {
61
+ opacity: 0.5;
62
+ pointer-events: none;
63
+ color: hsl(var(--text-muted));
64
+ }
65
+
66
+ .toggle-label:active .toggle-switch {
67
+ transform: scale(0.85);
68
+ }
69
+
70
+ .toggle-container {
71
+ --toggle-height: 12px;
72
+ --toggle-width: 40px;
73
+ display: flex;
74
+ align-items: center;
75
+ cursor: pointer;
76
+ transition: all .15s ease;
77
+ background-color: hsl(var(--default-base));
78
+ width: var(--toggle-width);
79
+ height: var(--toggle-height);
80
+ border-radius: var(--toggle-height);
81
+ }
82
+
83
+ .toggle-switch {
84
+ --switch: calc(var(--toggle-height) * 1.75);
85
+ height: var(--switch);
86
+ width: var(--switch);
87
+ background-color: hsl(var(--text-muted));
88
+ border-radius: var(--toggle-height);
89
+ position: relative;
90
+ left: 0;
91
+ transition: all .15s ease;
92
+ will-change: transform;
93
+ }
94
+
95
+ .toggle-container:has(.checkbox:checked) .toggle-switch {
96
+ left: calc(100% - var(--switch));
97
+ background-color: hsl(var(--text-normal));
98
+ }
99
+
100
+ .toggle-label.sm .toggle-container {
101
+ --toggle-height: 10px;
102
+ --toggle-width: 32px;
103
+ }
104
+
105
+ .toggle-label.sm .toggle-switch {
106
+ --switch: calc(var(--toggle-height) * 1.65);
107
+ }
108
+
109
+ .toggle-label.lg .toggle-container {
110
+ --toggle-height: 16px;
111
+ --toggle-width: 48px;
112
+ }
113
+
114
+ .toggle-label.lg .toggle-switch {
115
+ --switch: calc(var(--toggle-height) * 1.65);
116
+ }
117
+
118
+ .toggle-label.primary .toggle-container:has(.checkbox:checked) {
119
+ background-color: hsl(var(--primary-base));
120
+ }
121
+
122
+ .toggle-label.success .toggle-container:has(.checkbox:checked) {
123
+ background-color: hsl(var(--success-base));
124
+ }
125
+
126
+ .toggle-label.warning .toggle-container:has(.checkbox:checked) {
127
+ background-color: hsl(var(--warning-base));
128
+ }
129
+
130
+ .toggle-label.danger .toggle-container:has(.checkbox:checked) {
131
+ background-color: hsl(var(--danger-base));
132
+ }
133
+
134
+ .req-star {
135
+ color: hsl(var(--danger-base));
136
+ font-weight: 700;
137
+ }
138
+
139
+ .checkbox {
140
+ width: 0;
141
+ height: 0;
142
+ visibility: hidden;
143
+ opacity: 0;
144
+ margin: 0;
145
+ }
146
+ </style>
@@ -0,0 +1,68 @@
1
+ ---
2
+ import { Image } from 'astro:assets';
3
+ import Icon from '../utils/Icon.astro';
4
+
5
+ interface Props {
6
+ name: string;
7
+ description: string;
8
+ avatar?: string;
9
+ class?: string;
10
+ };
11
+
12
+ const { name, description, avatar, class: className } = Astro.props;
13
+ ---
14
+ <div class="user-container" class:list={[ className ]}>
15
+ <div class="avatar-container">
16
+ {avatar ? (
17
+ <Image src={avatar} inferSize alt={name} class="avatar-img" />
18
+ ) : (
19
+ <Icon name='user' width={24} height={24} />
20
+ )}
21
+ </div>
22
+ <div class="text-content">
23
+ <span class="name">{name}</span>
24
+ <span class="description">{description}</span>
25
+ </div>
26
+ </div>
27
+ <style>
28
+ .user-container {
29
+ display: flex;
30
+ flex-direction: row;
31
+ align-items: center;
32
+ gap: 1rem;
33
+ }
34
+
35
+ .avatar-container {
36
+ width: 2.5rem;
37
+ height: 2.5rem;
38
+ background-color: hsl(var(--default-base));
39
+ border-radius: 3rem;
40
+ display: flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+ overflow: hidden;
44
+ border: 1px solid hsl(var(--border));
45
+ }
46
+
47
+ .avatar-img {
48
+ width: 100%;
49
+ height: auto;
50
+ }
51
+
52
+ .text-content {
53
+ display: flex;
54
+ flex-direction: column;
55
+ gap: .125rem;
56
+ }
57
+
58
+ .name {
59
+ font-size: 1em;
60
+ font-weight: 600;
61
+ }
62
+
63
+ .description {
64
+ font-size: .875em;
65
+ font-weight: 400;
66
+ color: hsl(var(--text-muted));
67
+ }
68
+ </style>
@@ -0,0 +1,25 @@
1
+ export { default as Button } from "./Button.astro";
2
+ export { default as Divider } from "./Divider.astro";
3
+ export { default as Input } from "./Input.astro";
4
+ export { default as Row } from "./Row.astro";
5
+ export { default as Center } from "./Center.astro";
6
+ export { default as Textarea } from "./Textarea.astro";
7
+ export { default as Checkbox } from "./Checkbox.astro";
8
+ export { default as Toggle } from "./Toggle.astro";
9
+ export { default as RadioGroup } from "./RadioGroup.astro";
10
+ export { default as Toaster } from "./Toast/Toaster.astro";
11
+ export { default as Card } from "./Card.astro";
12
+ export { default as Modal } from "./Modal/Modal.astro";
13
+ export { default as Select } from "./Select.astro";
14
+ export { default as SearchSelect } from "./SearchSelect.astro";
15
+ export { default as Dropdown } from "./Dropdown/Dropdown.astro";
16
+ export { default as User } from "./User.astro";
17
+ export { default as ThemeToggle } from './ThemeToggle.astro';
18
+
19
+ export { default as Sidebar } from "./Sidebar/Single.astro";
20
+ export { default as DoubleSidebar } from "./Sidebar/Double.astro";
21
+ export { SingleSidebarHelper, DoubleSidebarHelper } from "./Sidebar/helpers";
22
+
23
+ export { toast } from "./Toast/toast";
24
+ export { ModalHelper } from "./Modal/modal";
25
+ export { DropdownHelper } from "./Dropdown/dropdown";
@@ -0,0 +1,24 @@
1
+ export { Button } from './components/index';
2
+ export { Divider } from './components/index';
3
+ export { Input } from './components/index';
4
+ export { Row } from './components/index';
5
+ export { Center } from './components/index';
6
+ export { Textarea } from './components/index';
7
+ export { Checkbox } from './components/index';
8
+ export { Toggle } from './components/index';
9
+ export { RadioGroup } from './components/index';
10
+ export { Toaster, toast } from './components/index';
11
+ export { Card } from './components/index';
12
+ export { Modal, ModalHelper } from './components/index';
13
+ export { Select } from './components/index';
14
+ export { SearchSelect } from './components/index';
15
+ export { Dropdown, DropdownHelper } from './components/index';
16
+ export { User } from './components/index';
17
+ export { ThemeToggle } from './components/index';
18
+
19
+ export {
20
+ Sidebar,
21
+ DoubleSidebar,
22
+ SingleSidebarHelper,
23
+ DoubleSidebarHelper,
24
+ } from './components/index';