@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,334 @@
1
+ ---
2
+ import Icon from '../utils/Icon.astro';
3
+ import { generateID } from '../utils/generateID';
4
+
5
+ interface Option {
6
+ label: string;
7
+ value: string;
8
+ disabled?: boolean;
9
+ };
10
+
11
+ interface Props {
12
+ label?: string;
13
+ defaultValue?: string;
14
+ class?: string;
15
+ name?: string;
16
+ isRequired?: boolean;
17
+ options: Option[];
18
+ disabled?: boolean;
19
+ fullWidth?: boolean;
20
+ };
21
+
22
+ const {
23
+ label,
24
+ defaultValue,
25
+ class: className,
26
+ name = generateID('select'),
27
+ isRequired,
28
+ options = [],
29
+ disabled,
30
+ fullWidth,
31
+ } = Astro.props;
32
+ ---
33
+
34
+ <div
35
+ id={`${name}-container`}
36
+ class="select-label"
37
+ class:list={[disabled && "disabled", className, fullWidth && "full"]}
38
+ >
39
+ <label class="label" for={`${name}-select-btn`}>
40
+ {label}
41
+ <span class="req-star">{isRequired && "*"}</span>
42
+ </label>
43
+ <button
44
+ class="select-button"
45
+ role="combobox"
46
+ id={`${name}-select-btn`}
47
+ type="button"
48
+ >
49
+ <span id={`${name}-value-span`}>
50
+ {
51
+ defaultValue
52
+ ? options.find((x) => x.value === defaultValue)?.label
53
+ : "Select"
54
+ }
55
+ </span>
56
+ <Icon name="chevron-up-down" width={24} height={24} />
57
+ </button>
58
+ <ul class="select-dropdown" role="listbox" id={`${name}-dropdown`}>
59
+ {
60
+ options.map((x, i) => (
61
+ <li
62
+ class="select-option"
63
+ role="option"
64
+ value={x.value}
65
+ class:list={[
66
+ defaultValue === x.value && `selected`,
67
+ x.disabled && "disabled",
68
+ ]}
69
+ id={defaultValue === x.value ? `${name}-selected` : ""}
70
+ data-option-index={i}
71
+ tabindex={x.disabled ? -1 : 0}
72
+ >
73
+ {x.label}
74
+ </li>
75
+ ))
76
+ }
77
+ </ul>
78
+ <select class="hidden-select" id={name} name={name} required={isRequired}>
79
+ <option value={""}> Select </option>
80
+ {
81
+ options.map((x) => (
82
+ <option
83
+ value={x.value}
84
+ selected={defaultValue === x.value}
85
+ disabled={x.disabled}
86
+ >
87
+ {x.label}
88
+ </option>
89
+ ))
90
+ }
91
+ </select>
92
+ </div>
93
+ <script is:inline define:vars={{ id: name, options }}>
94
+ const container = document.getElementById(`${id}-container`);
95
+ const hiddenSelect = document.getElementById(id);
96
+ const button = document.getElementById(`${id}-select-btn`);
97
+ const valueSpan = document.getElementById(`${id}-value-span`);
98
+ const dropdown = document.getElementById(`${id}-dropdown`);
99
+ const optionElements = container.querySelectorAll("li");
100
+
101
+ let active = false;
102
+
103
+ button.addEventListener("click", () => {
104
+ const { bottom, left, right, width, x, y, height } =
105
+ button.getBoundingClientRect();
106
+
107
+ const optionHeight = 36;
108
+ const totalBorderSize = 2;
109
+ const margin = 4;
110
+
111
+ const dropdownHeight =
112
+ options.length * optionHeight + totalBorderSize + margin;
113
+
114
+ const CustomRect = {
115
+ top: bottom + margin,
116
+ left,
117
+ right,
118
+ bottom: bottom + margin + dropdownHeight,
119
+ width,
120
+ height: dropdownHeight,
121
+ x,
122
+ y: y + height + margin,
123
+ };
124
+
125
+ if (active) {
126
+ dropdown.classList.remove("active", "above");
127
+ active = false;
128
+ return;
129
+ }
130
+
131
+ active = true;
132
+
133
+ if (
134
+ CustomRect.top >= 0 &&
135
+ CustomRect.left >= 0 &&
136
+ CustomRect.bottom <=
137
+ (window.innerHeight || document.documentElement.clientHeight) &&
138
+ CustomRect.right <=
139
+ (window.innerWidth || document.documentElement.clientWidth)
140
+ ) {
141
+ dropdown.classList.add("active");
142
+ } else {
143
+ dropdown.classList.add("active", "above");
144
+ }
145
+ });
146
+
147
+ optionElements.forEach((option) => {
148
+ const handleSelection = (e) => {
149
+ e.stopImmediatePropagation();
150
+ if (option.id === `${id}-selected` || !id) return;
151
+
152
+ const currentlySelected = document.getElementById(`${id}-selected`);
153
+
154
+ if (currentlySelected) {
155
+ currentlySelected.classList.remove("selected");
156
+ currentlySelected.id = "";
157
+ }
158
+
159
+ option.id = `${id}-selected`;
160
+ option.classList.add("selected");
161
+
162
+ const opt = options[parseInt(option.dataset.optionIndex)];
163
+ hiddenSelect.value = opt.value;
164
+
165
+ valueSpan.textContent = opt.label;
166
+ dropdown.classList.remove("active", "above");
167
+
168
+ active = false;
169
+ }
170
+
171
+ option.addEventListener("click", handleSelection);
172
+ option.addEventListener("keydown", (e) => {
173
+ if (e.key === 'Enter') {
174
+ handleSelection(e);
175
+ }
176
+ });
177
+ });
178
+
179
+ window.addEventListener("scroll", () => {
180
+ dropdown.classList.remove("active", "above");
181
+ active = false;
182
+ });
183
+
184
+ hideOnClickOutside(container);
185
+
186
+ function hideOnClickOutside(element) {
187
+ const outsideClickListener = (event) => {
188
+ if (
189
+ !element.contains(event.target) &&
190
+ isVisible(element) &&
191
+ active === true
192
+ ) {
193
+ // or use: event.target.closest(selector) === null
194
+ dropdown.classList.remove("active", "above");
195
+ active = false;
196
+ }
197
+ };
198
+
199
+ const removeClickListener = () => {
200
+ document.removeEventListener("click", outsideClickListener);
201
+ };
202
+
203
+ document.addEventListener("click", outsideClickListener);
204
+ }
205
+
206
+ // source (2018-03-11): https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js
207
+ const isVisible = (elem) =>
208
+ !!elem &&
209
+ !!(
210
+ elem.offsetWidth ||
211
+ elem.offsetHeight ||
212
+ elem.getClientRects().length
213
+ );
214
+ </script>
215
+ <style>
216
+ .select-label {
217
+ width: fit-content;
218
+ display: flex;
219
+ flex-direction: column;
220
+ gap: 0.25rem;
221
+ min-width: 200px;
222
+ position: relative;
223
+ }
224
+
225
+ .select-label.full {
226
+ width: 100%;
227
+ }
228
+
229
+ .select-label.disabled {
230
+ opacity: 0.5;
231
+ pointer-events: none;
232
+ color: hsl(var(--text-muted));
233
+ }
234
+
235
+ .label {
236
+ font-size: 14px;
237
+ }
238
+
239
+ .req-star {
240
+ color: hsl(var(--danger-base));
241
+ font-weight: 700;
242
+ }
243
+
244
+ .select-button {
245
+ padding: 0.5rem 0.75rem 0.5rem 1rem;
246
+ border-radius: 8px;
247
+ border: 1px solid hsl(var(--border));
248
+ background: hsl(var(--background-step-2));
249
+ color: hsl(var(--text-normal));
250
+ transition: all 0.15s ease;
251
+ display: flex;
252
+ flex-direction: row;
253
+ align-items: center;
254
+ justify-content: space-between;
255
+ cursor: pointer;
256
+ gap: 1rem;
257
+ }
258
+
259
+ .select-button:hover {
260
+ background: hsl(var(--background-step-3));
261
+ }
262
+
263
+ .select-button.active,
264
+ .select-button:active,
265
+ .select-button:has(+ .select-dropdown.active) {
266
+ border: 1px solid hsl(var(--primary-base));
267
+ background: hsl(var(--background-step-2));
268
+ }
269
+
270
+ .select-dropdown {
271
+ position: absolute;
272
+ width: 100%;
273
+ border: 1px solid hsl(var(--border));
274
+ list-style: none;
275
+ margin: 0;
276
+ padding: 0;
277
+ flex-direction: column;
278
+ border-radius: 0.5rem;
279
+ background-color: hsl(var(--background-step-2));
280
+ overflow: hidden;
281
+ top: calc(100% + 0.25rem);
282
+ left: 0;
283
+ display: none;
284
+ z-index: 90;
285
+ box-shadow: 0px 4px 8px hsl(var(--shadow), 0.5);
286
+ }
287
+
288
+ .select-dropdown.active {
289
+ display: flex;
290
+ }
291
+
292
+ .select-dropdown.above {
293
+ top: auto;
294
+ bottom: calc(100% - 18px + 0.25rem);
295
+ }
296
+
297
+ .select-option {
298
+ padding: 0.5rem;
299
+ cursor: pointer;
300
+ font-size: 0.975em;
301
+ transition: all 0.15s ease;
302
+ }
303
+
304
+ .select-option.disabled {
305
+ pointer-events: none;
306
+ color: hsl(var(--text-muted));
307
+ }
308
+
309
+ .select-option:hover, .select-option:focus {
310
+ background-color: hsl(var(--background-step-3));
311
+ }
312
+
313
+ .select-option:focus {
314
+ outline: none;
315
+ border: none;
316
+ }
317
+
318
+ .select-option.selected {
319
+ background-color: hsl(var(--primary-base));
320
+ color: hsl(var(--text-inverted));
321
+ cursor: default;
322
+ }
323
+
324
+ .hidden-select {
325
+ height: 0;
326
+ width: 0;
327
+ border: none;
328
+ outline: none;
329
+ position: absolute;
330
+ background-color: transparent;
331
+ pointer-events: none;
332
+ opacity: 0;
333
+ }
334
+ </style>
@@ -0,0 +1,91 @@
1
+ <div id="sidebars" class="active inner">
2
+ <div id="sidebar-outer">
3
+ <slot name="outer" />
4
+ </div>
5
+ <div id="sidebar-inner">
6
+ <slot name="inner" />
7
+ </div>
8
+ </div>
9
+ <style>
10
+ #sidebars {
11
+ --sidebars-container-width: calc((280px + 1px) * 2);
12
+ display: flex;
13
+ align-items: center;
14
+ width: var(--sidebars-container-width);
15
+ min-width: var(--sidebars-container-width);
16
+ overflow: hidden;
17
+ transition: all .3s ease;
18
+ z-index: 10;
19
+ height: 100%;
20
+ }
21
+
22
+ #sidebars.active {
23
+ transform: translateX(0%);
24
+ }
25
+
26
+ #sidebar-outer {
27
+ height: 100%;
28
+ min-width: 280px;
29
+ width: 280px;
30
+ background-color: hsl(var(--background-step-1));
31
+ border-right: 1px solid hsl(var(--border));
32
+ gap: 1rem;
33
+ display: flex;
34
+ flex-direction: column;
35
+ align-items: center;
36
+ justify-content: center;
37
+ z-index: 10;
38
+ transition: all .3s ease;
39
+ padding: 1.5rem;
40
+ }
41
+
42
+ #sidebar-inner {
43
+ min-width: 280px;
44
+ width: 280px;
45
+ height: 100%;
46
+ background-color: hsl(var(--background-step-2));
47
+ border-right: 1px solid hsl(var(--border));
48
+ display: flex;
49
+ flex-direction: column;
50
+ gap: 1rem;
51
+ align-items: center;
52
+ justify-content: center;
53
+ z-index: 5;
54
+ position: relative;
55
+ transition: all .3s ease;
56
+ }
57
+
58
+ @media screen and (max-width: 1200px) {
59
+ #sidebars {
60
+ --sidebars-container-width: calc(280px + 1px);
61
+ }
62
+
63
+ #swap-to-inner {
64
+ display: block;
65
+ }
66
+
67
+ #sidebars.inner {
68
+ #sidebar-outer,
69
+ #sidebar-inner {
70
+ transform: translateX(-100%);
71
+ }
72
+ }
73
+ }
74
+
75
+ @media screen and (max-width: 840px) {
76
+ #sidebars {
77
+ transform: translateX(-100%);
78
+ position: absolute;
79
+ top: 0;
80
+ left: 0;
81
+ height: 100%;
82
+ width: 100%;
83
+ }
84
+
85
+ #sidebar-outer,
86
+ #sidebar-inner {
87
+ width: 100%;
88
+ flex: 0 0 100%;
89
+ }
90
+ }
91
+ </style>
@@ -0,0 +1,42 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types'
3
+
4
+ interface Props extends Exclude<HTMLAttributes<'aside'>, 'id'> {};
5
+
6
+ const props = Astro.props;
7
+ ---
8
+ <aside id="sidebar" {...props}>
9
+ <slot />
10
+ </aside>
11
+ <style>
12
+ #sidebar {
13
+ height: 100%;
14
+ min-width: 280px;
15
+ width: 280px;
16
+ background-color: hsl(var(--background-step-1));
17
+ border-right: 1px solid hsl(var(--border));
18
+ gap: 1rem;
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ padding: 1.5rem;
23
+ z-index: 10;
24
+ transition: all .3s ease;
25
+ }
26
+
27
+ #sidebar.active {
28
+ transform: translateX(0%);
29
+ }
30
+
31
+ @media screen and (max-width: 840px) {
32
+ #sidebar {
33
+ transform: translateX(-100%);
34
+ position: absolute;
35
+ top: 0;
36
+ left: 0;
37
+ height: 100%;
38
+ width: 100%;
39
+ border-right: none;
40
+ }
41
+ }
42
+ </style>
@@ -0,0 +1,133 @@
1
+ class SingleSidebarHelper {
2
+ sidebar: HTMLElement;
3
+ sidebarToggle?: HTMLElement | undefined;
4
+
5
+ constructor(toggleID?: string) {
6
+ const sidebarContainer = document.getElementById('sidebar');
7
+
8
+ if (!sidebarContainer) {
9
+ throw new Error(
10
+ `No item with ID 'sidebar' found. Please add the <Sidebar> component to this page.`
11
+ );
12
+ }
13
+
14
+ this.sidebar = sidebarContainer;
15
+
16
+ if (toggleID) {
17
+ const navToggle = document.getElementById(toggleID);
18
+
19
+ if (!navToggle) {
20
+ throw new Error(`No item with ID ${toggleID} found.`);
21
+ }
22
+
23
+ this.sidebarToggle = navToggle;
24
+
25
+ this.sidebarToggle.addEventListener('click', () => {
26
+ this.sidebar.classList.toggle('active');
27
+ });
28
+ }
29
+ }
30
+
31
+ public toggleSidebarOnClick = (elementID: string) => {
32
+ const navToggle = document.getElementById(elementID);
33
+
34
+ if (!navToggle) {
35
+ throw new Error(`No item with ID ${elementID} found.`);
36
+ }
37
+
38
+ this.sidebarToggle = navToggle;
39
+
40
+ this.sidebarToggle.addEventListener('click', () => {
41
+ this.sidebar.classList.toggle('active');
42
+ });
43
+ }
44
+
45
+ public hideSidebarOnClick = (elementID: string) => {
46
+ const element = document.getElementById(elementID);
47
+
48
+ if (!element) {
49
+ throw new Error(`No item with ID ${elementID} found.`);
50
+ }
51
+
52
+ element.addEventListener('click', this.hideSidebar);
53
+ };
54
+
55
+ public showSidebarOnClick = (elementID: string) => {
56
+ const element = document.getElementById(elementID);
57
+
58
+ if (!element) {
59
+ throw new Error(`No item with ID ${elementID} found.`);
60
+ }
61
+
62
+ element.addEventListener('click', this.showSidebar);
63
+ };
64
+
65
+ public hideSidebar = () => {
66
+ this.sidebar.classList.remove('active');
67
+ };
68
+
69
+ public showSidebar = () => {
70
+ this.sidebar.classList.add('active');
71
+ };
72
+ }
73
+
74
+ class DoubleSidebarHelper {
75
+ sidebarsContainer: HTMLElement;
76
+
77
+ constructor() {
78
+ const sidebarsContainer = document.getElementById('sidebars');
79
+
80
+ if (!sidebarsContainer) {
81
+ throw new Error(
82
+ `No item with ID 'sidebars' found. Please add the <DoubleSidebar> component to this page.`
83
+ );
84
+ }
85
+
86
+ this.sidebarsContainer = sidebarsContainer;
87
+ }
88
+
89
+ public hideSidebarOnClick = (elementID: string) => {
90
+ const element = document.getElementById(elementID);
91
+
92
+ if (!element) {
93
+ throw new Error(`No item with ID ${elementID} found.`);
94
+ }
95
+
96
+ element.addEventListener('click', this.hideSidebar);
97
+ };
98
+
99
+ public showOuterOnClick = (elementID: string) => {
100
+ const element = document.getElementById(elementID);
101
+
102
+ if (!element) {
103
+ throw new Error(`No item with ID ${elementID} found.`);
104
+ }
105
+
106
+ element.addEventListener('click', this.showOuterSidebar);
107
+ };
108
+
109
+ public showInnerOnClick = (elementID: string) => {
110
+ const element = document.getElementById(elementID);
111
+
112
+ if (!element) {
113
+ throw new Error(`No item with ID ${elementID} found.`);
114
+ }
115
+
116
+ element.addEventListener('click', this.showInnerSidebar);
117
+ };
118
+
119
+ public showInnerSidebar = () => {
120
+ this.sidebarsContainer.classList.add('inner', 'active');
121
+ };
122
+
123
+ public showOuterSidebar = () => {
124
+ this.sidebarsContainer.classList.add('active');
125
+ this.sidebarsContainer.classList.remove('inner');
126
+ };
127
+
128
+ public hideSidebar = () => {
129
+ this.sidebarsContainer.classList.remove('inner', 'active');
130
+ };
131
+ }
132
+
133
+ export { SingleSidebarHelper, DoubleSidebarHelper };
@@ -0,0 +1,3 @@
1
+ export { default as SingleSidebar } from './Single.astro';
2
+ export { default as DoubleSidebar } from './Double.astro';
3
+ export { SingleSidebarHelper, DoubleSidebarHelper } from './helpers';
@@ -0,0 +1,102 @@
1
+ ---
2
+ import { generateID } from '../utils/generateID';
3
+
4
+ interface Props {
5
+ label?: string;
6
+ placeholder?: string;
7
+ isRequired?: boolean;
8
+ fullWidth?: boolean;
9
+ resize?: boolean;
10
+ name?: string;
11
+ disabled?: boolean;
12
+ defaultValue?: string;
13
+ };
14
+
15
+ const {
16
+ label,
17
+ placeholder,
18
+ isRequired,
19
+ fullWidth,
20
+ resize,
21
+ name = generateID('textarea'),
22
+ disabled,
23
+ defaultValue,
24
+ } = Astro.props;
25
+ ---
26
+ <label
27
+ for={name}
28
+ class="textarea-label"
29
+ class:list={[
30
+ fullWidth && "full",
31
+ resize && "resize",
32
+ disabled && "disabled",
33
+ ]}
34
+ >
35
+ <span class="label">
36
+ {label} <span class="req-star">{isRequired && "*"}</span>
37
+ </span>
38
+ <textarea
39
+ placeholder={placeholder}
40
+ name={name}
41
+ id={name}
42
+ class="textarea"
43
+ required={isRequired}
44
+ disabled={disabled}
45
+
46
+ >{defaultValue}</textarea>
47
+ </label>
48
+ <style>
49
+ .textarea-label {
50
+ display: flex;
51
+ flex-direction: column;
52
+ gap: .25rem;
53
+ margin-top: .5rem;
54
+ max-width: 80ch;
55
+ }
56
+
57
+ .textarea-label.disabled {
58
+ opacity: 0.5;
59
+ pointer-events: none;
60
+ color: hsl(var(--text-muted));
61
+ }
62
+
63
+ .textarea-label.full {
64
+ width: 100%;
65
+ max-width: none;
66
+ }
67
+
68
+ .label {
69
+ font-size: 14px;
70
+ }
71
+
72
+ .textarea {
73
+ padding: .65rem 1rem;
74
+ border-radius: 8px;
75
+ border: 1px solid hsl(var(--border));
76
+ background: hsl(var(--background-step-2));
77
+ color: hsl(var(--text-normal));
78
+ transition: all .15s ease;
79
+ resize: none;
80
+ min-height: 12ch;
81
+ }
82
+
83
+ .textarea:hover {
84
+ background: hsl(var(--background-step-3));
85
+ }
86
+
87
+ .resize .textarea {
88
+ resize: both;
89
+ }
90
+
91
+ .textarea:active,
92
+ .textarea:focus {
93
+ border: 1px solid hsl(var(--primary-base));
94
+ outline: none;
95
+ background: hsl(var(--background-step-2));
96
+ }
97
+
98
+ .req-star {
99
+ color: hsl(var(--danger-base));
100
+ font-weight: 700;
101
+ }
102
+ </style>