@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,430 @@
1
+ ---
2
+ import Icon from '../utils/Icon.astro';
3
+ import { generateID } from '../utils/generateID';
4
+ import Input from './Input.astro';
5
+
6
+ interface Option {
7
+ label: string;
8
+ value: string;
9
+ disabled?: boolean;
10
+ };
11
+
12
+ interface Props {
13
+ label?: string;
14
+ defaultValue?: string;
15
+ class?: string;
16
+ name?: string;
17
+ isRequired?: boolean;
18
+ options: Option[];
19
+ disabled?: boolean;
20
+ fullWidth?: boolean;
21
+ };
22
+
23
+ const {
24
+ label,
25
+ defaultValue,
26
+ class: className,
27
+ name = generateID('search-select'),
28
+ isRequired,
29
+ options = [],
30
+ disabled,
31
+ fullWidth,
32
+ } = Astro.props;
33
+ ---
34
+
35
+ <div
36
+ id={`${name}-container`}
37
+ class="search-select-label"
38
+ class:list={[disabled && "disabled", className, fullWidth && "full"]}
39
+ >
40
+ <label class="label" for={`${name}-search-select-btn`}>
41
+ {label}
42
+ <span class="req-star">{isRequired && "*"}</span>
43
+ </label>
44
+ <div class="search-input-wrapper" id={`${name}-search-input-wrapper`}>
45
+ <Input
46
+ placeholder={options.find((x) => x.value === defaultValue)?.label || "Select"}
47
+ />
48
+ <Icon name="chevron-up-down" class="search-select-indicator" width={24} height={24} />
49
+ </div>
50
+ <ul class="search-select-dropdown" role="listbox" id={`${name}-dropdown`}>
51
+ {
52
+ options.map((x, i) => (
53
+ <li
54
+ class="search-select-option"
55
+ role="option"
56
+ value={x.value}
57
+ class:list={[
58
+ x.disabled && "disabled",
59
+ i === 0 && 'focused',
60
+ ]}
61
+ id={defaultValue === x.value ? `${name}-selected` : ""}
62
+ data-option-index={i}
63
+ data-value={x.value}
64
+ >
65
+ {x.label}
66
+ </li>
67
+ ))
68
+ }
69
+ </ul>
70
+ <select class="hidden-select" id={name} name={name} required={isRequired}>
71
+ <option value={""}> Select </option>
72
+ {
73
+ options.map((x) => (
74
+ <option
75
+ value={x.value}
76
+ selected={defaultValue === x.value}
77
+ disabled={x.disabled}
78
+ >
79
+ {x.label}
80
+ </option>
81
+ ))
82
+ }
83
+ </select>
84
+ </div>
85
+ <script is:inline define:vars={{ id: name, options, defaultValue }}>
86
+ const container = document.getElementById(`${id}-container`);
87
+ const hiddenSelect = document.getElementById(id);
88
+ const searchWrapper = document.getElementById(`${id}-search-input-wrapper`);
89
+ const searchInput = searchWrapper.querySelector('input');
90
+ const valueSpan = document.getElementById(`${id}-value-span`);
91
+ const dropdown = document.getElementById(`${id}-dropdown`);
92
+ let optionElements = container.querySelectorAll("li");
93
+
94
+ let active = false;
95
+
96
+ let filteredOptions = options;
97
+
98
+ searchWrapper.addEventListener("click", () => {
99
+ const { bottom, left, right, width, x, y, height } = searchWrapper.getBoundingClientRect();
100
+
101
+ const optionHeight = 36;
102
+ const totalBorderSize = 2;
103
+ const margin = 4;
104
+
105
+ const dropdownHeight = options.length * optionHeight + totalBorderSize + margin;
106
+
107
+ const CustomRect = {
108
+ top: bottom + margin,
109
+ left,
110
+ right,
111
+ bottom: bottom + margin + dropdownHeight,
112
+ width,
113
+ height: dropdownHeight,
114
+ x,
115
+ y: y + height + margin,
116
+ };
117
+
118
+ if (active) {
119
+ dropdown.classList.remove("active", "above");
120
+ active = false;
121
+ return;
122
+ }
123
+
124
+ active = true;
125
+
126
+ if (
127
+ CustomRect.top >= 0 &&
128
+ CustomRect.left >= 0 &&
129
+ CustomRect.bottom <=
130
+ (window.innerHeight || document.documentElement.clientHeight) &&
131
+ CustomRect.right <=
132
+ (window.innerWidth || document.documentElement.clientWidth)
133
+ ) {
134
+ dropdown.classList.add("active");
135
+ } else {
136
+ dropdown.classList.add("active", "above");
137
+ }
138
+ });
139
+
140
+ const handleSelection = (e, option) => {
141
+ e.stopImmediatePropagation();
142
+
143
+ if (option.id === `${id}-selected` || !id) return;
144
+
145
+ const currentlySelected = document.getElementById(`${id}-selected`);
146
+
147
+ if (currentlySelected) {
148
+ currentlySelected.classList.remove("selected");
149
+ currentlySelected.id = "";
150
+ }
151
+
152
+ option.id = `${id}-selected`;
153
+ option.classList.add("selected");
154
+
155
+ const index = options.findIndex((x) => x.value === option.dataset.value);
156
+ focusIndex = index;
157
+
158
+ const opt = options[index];
159
+ hiddenSelect.value = opt.value;
160
+
161
+ searchInput.placeholder = opt.label;
162
+ dropdown.classList.remove("active", "above");
163
+ searchInput.blur();
164
+
165
+ searchInput.value = "";
166
+ filteredOptions = options;
167
+ constructOptionsBasedOnOptions(options);
168
+
169
+ active = false;
170
+ }
171
+
172
+ optionElements.forEach((option) => {
173
+ option.addEventListener("click", (e) => handleSelection(e, option));
174
+ });
175
+
176
+ window.addEventListener("scroll", () => {
177
+ dropdown.classList.remove("active", "above");
178
+ active = false;
179
+ });
180
+
181
+ hideOnClickOutside(container);
182
+
183
+ function hideOnClickOutside(element) {
184
+ const outsideClickListener = (event) => {
185
+ if (
186
+ !element.contains(event.target) &&
187
+ isVisible(element) &&
188
+ active === true
189
+ ) {
190
+ // or use: event.target.closest(selector) === null
191
+ dropdown.classList.remove("active", "above");
192
+ active = false;
193
+ }
194
+ };
195
+
196
+ const removeClickListener = () => {
197
+ document.removeEventListener("click", outsideClickListener);
198
+ };
199
+
200
+ document.addEventListener("click", outsideClickListener);
201
+ }
202
+
203
+ // source (2018-03-11): https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js
204
+ const isVisible = (elem) =>
205
+ !!elem &&
206
+ !!(
207
+ elem.offsetWidth ||
208
+ elem.offsetHeight ||
209
+ elem.getClientRects().length
210
+ );
211
+
212
+ let focusIndex = 0;
213
+
214
+ const recomputeOptions = () => {
215
+ for (const entry of optionElements) {
216
+ if (entry.dataset.optionIndex == focusIndex) {
217
+ entry.classList.add('focused');
218
+ } else {
219
+ entry.classList.remove('focused');
220
+ }
221
+ }
222
+ }
223
+
224
+ searchInput.addEventListener('keydown', (e) => {
225
+ if (e.key === "Escape") {
226
+ e.preventDefault();
227
+ e.stopImmediatePropagation();
228
+
229
+ active = false;
230
+ dropdown.classList.remove("active", "above");
231
+ searchInput.blur();
232
+
233
+ return;
234
+ }
235
+
236
+ if (e.key === "ArrowUp" && focusIndex > 0) {
237
+ e.preventDefault();
238
+ e.stopImmediatePropagation();
239
+
240
+ focusIndex--;
241
+ recomputeOptions();
242
+
243
+ return;
244
+ }
245
+
246
+ if (e.key === "ArrowDown" && focusIndex + 1 < filteredOptions.filter(x => !x.disabled).length) {
247
+ e.preventDefault();
248
+ e.stopImmediatePropagation();
249
+
250
+ focusIndex++;
251
+ recomputeOptions();
252
+
253
+ return;
254
+ }
255
+
256
+ if (e.key === "Enter") {
257
+ e.preventDefault();
258
+ e.stopImmediatePropagation();
259
+
260
+ console.log(optionElements, focusIndex);
261
+
262
+ for (const entry of optionElements) {
263
+ if (entry.dataset.optionIndex == focusIndex) {
264
+ entry.click();
265
+ }
266
+ }
267
+
268
+ return;
269
+ }
270
+ });
271
+
272
+ searchInput.addEventListener('keyup', (e) => {
273
+ if (["Enter", "ArrowUp", "ArrowDown"].includes(e.key)) return;
274
+
275
+ if (searchInput.value.trim().length === 0) {
276
+ constructOptionsBasedOnOptions(options);
277
+ filteredOptions = options;
278
+ return;
279
+ };
280
+
281
+ filteredOptions = options.filter(x => x.label.includes(searchInput.value));
282
+ focusIndex = 0;
283
+
284
+ constructOptionsBasedOnOptions(filteredOptions);
285
+ });
286
+
287
+ function constructOptionsBasedOnOptions(options) {
288
+ dropdown.innerHTML = '';
289
+
290
+ if (options.length === 0) {
291
+ const element = document.createElement('li');
292
+ element.classList.add('empty-search-results');
293
+ element.textContent = "No results found.";
294
+
295
+ dropdown.appendChild(element);
296
+ }
297
+
298
+ let i = 0;
299
+
300
+ for (const option of options) {
301
+ const element = document.createElement('li');
302
+ element.classList.add(...[
303
+ 'search-select-option',
304
+ option.disabled && "disabled",
305
+ focusIndex === i && 'focused',
306
+ ].filter(Boolean));
307
+ element.role = "option";
308
+ element.value = option.value;
309
+ element.id = "";
310
+ element.dataset.optionIndex = i;
311
+ element.dataset.value = option.value;
312
+ element.textContent = option.label;
313
+
314
+ element.addEventListener("click", (e) => handleSelection(e, element));
315
+
316
+ dropdown.appendChild(element);
317
+
318
+ i++;
319
+ }
320
+
321
+ optionElements = container.querySelectorAll("li");
322
+ }
323
+ </script>
324
+ <style is:global>
325
+ .search-select-label {
326
+ width: fit-content;
327
+ display: flex;
328
+ flex-direction: column;
329
+ gap: 0.25rem;
330
+ min-width: 200px;
331
+ position: relative;
332
+ }
333
+
334
+ .search-select-label.full {
335
+ width: 100%;
336
+ }
337
+
338
+ .search-select-label.disabled {
339
+ opacity: 0.5;
340
+ pointer-events: none;
341
+ color: hsl(var(--text-muted));
342
+ }
343
+
344
+ .label {
345
+ font-size: 14px;
346
+ }
347
+
348
+ .req-star {
349
+ color: hsl(var(--danger-base));
350
+ font-weight: 700;
351
+ }
352
+
353
+ .search-select-dropdown {
354
+ position: absolute;
355
+ width: 100%;
356
+ border: 1px solid hsl(var(--border));
357
+ list-style: none;
358
+ margin: 0;
359
+ padding: 0;
360
+ flex-direction: column;
361
+ border-radius: 0.5rem;
362
+ background-color: hsl(var(--background-step-2));
363
+ overflow: hidden;
364
+ top: calc(100% + 0.25rem);
365
+ left: 0;
366
+ display: none;
367
+ z-index: 90;
368
+ box-shadow: 0px 4px 8px hsl(var(--shadow), 0.5);
369
+ }
370
+
371
+ .search-select-dropdown.above {
372
+ top: auto;
373
+ bottom: calc(100% - 18px + 0.25rem);
374
+ }
375
+
376
+ .search-select-option, .empty-search-results {
377
+ padding: 0.5rem;
378
+ cursor: pointer;
379
+ font-size: 0.975em;
380
+ transition: all 0.15s ease;
381
+ }
382
+
383
+ .search-select-option.disabled {
384
+ pointer-events: none;
385
+ color: hsl(var(--text-muted));
386
+ }
387
+
388
+ .search-select-option:hover, .search-select-option.focused {
389
+ background-color: hsl(var(--background-step-3));
390
+ }
391
+
392
+ .search-select-option.selected {
393
+ background-color: hsl(var(--primary-base));
394
+ color: hsl(var(--text-inverted));
395
+ cursor: default;
396
+ }
397
+
398
+ .hidden-select {
399
+ height: 0;
400
+ width: 0;
401
+ border: none;
402
+ outline: none;
403
+ position: absolute;
404
+ background-color: transparent;
405
+ pointer-events: none;
406
+ opacity: 0;
407
+ }
408
+
409
+ .search-input-wrapper {
410
+ width: 100%;
411
+ position: relative;
412
+ height: fit-content;
413
+ cursor: pointer;
414
+ }
415
+
416
+ .search-select-indicator {
417
+ position: absolute;
418
+ top: calc(65% + 1px);
419
+ transform: translateY(-65%);
420
+ right: .5rem;
421
+ }
422
+
423
+ .search-input-wrapper:has(input:focus) + .search-select-dropdown {
424
+ display: flex;
425
+ }
426
+
427
+ .search-select-dropdown.active, .search-select-dropdown:has(> li:active) {
428
+ display: flex;
429
+ }
430
+ </style>