@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,129 @@
1
+ class ModalHelper {
2
+ public element: HTMLDialogElement;
3
+ public cancelButton: HTMLButtonElement | undefined;
4
+ public confirmButton: HTMLButtonElement | undefined;
5
+
6
+ private isForm = false;
7
+ private modalForm: HTMLFormElement;
8
+
9
+ constructor(id: string, triggerID?: string) {
10
+ const element = document.getElementById(id) as HTMLDialogElement;
11
+
12
+ if (!element) {
13
+ throw new Error(`No modal with ID ${id} found.`);
14
+ }
15
+
16
+ this.element = element as HTMLDialogElement;
17
+ this.modalForm = document.getElementById(`${id}-form-element`) as HTMLFormElement;
18
+
19
+ const isDismissable = this.element.dataset.dismissable === 'true';
20
+ const isForm = this.element.dataset.form === 'true';
21
+
22
+ if (isDismissable) {
23
+ this.addDismissiveClickListener();
24
+ }
25
+
26
+ if (isForm) this.isForm = true;
27
+
28
+ this.addButtonListeners(id, isDismissable);
29
+
30
+ if (triggerID) {
31
+ this.bindTrigger(triggerID);
32
+ }
33
+ }
34
+
35
+ private addButtonListeners = (id: string, dismissable: boolean) => {
36
+ if (dismissable || !this.element.dataset.buttons) {
37
+ const xMarkButton = document.getElementById(`${id}-btn-x`) as HTMLButtonElement;
38
+ xMarkButton.addEventListener('click', this.hide);
39
+ }
40
+
41
+ if (!this.element.dataset.buttons) return;
42
+
43
+ const usedButtons = this.element.dataset.buttons.split(';');
44
+
45
+ if (usedButtons.includes('cancel')) {
46
+ this.cancelButton = document.getElementById(`${id}-btn-cancel`) as HTMLButtonElement;
47
+ this.cancelButton.addEventListener('click', this.hide);
48
+ }
49
+
50
+ if (usedButtons.includes('confirm')) {
51
+ this.confirmButton = document.getElementById(`${id}-btn-confirm`) as HTMLButtonElement;
52
+ this.confirmButton.addEventListener('click', this.hide);
53
+ }
54
+ };
55
+
56
+ private addDismissiveClickListener = () => {
57
+ this.element.addEventListener('click', (e: MouseEvent) => {
58
+ if (!e.target) return;
59
+
60
+ const { left, right, top, bottom } = this.element.getBoundingClientRect();
61
+
62
+ const clickWithinModalBox =
63
+ e.clientX < right && e.clientX > left && e.clientY < bottom && e.clientY > top;
64
+
65
+ if (!clickWithinModalBox) {
66
+ this.element.close();
67
+ }
68
+ });
69
+ };
70
+
71
+ public show = () => {
72
+ this.element.showModal();
73
+ };
74
+
75
+ public hide = () => {
76
+ this.element.close();
77
+ };
78
+
79
+ public bindTrigger = (elementID: string) => {
80
+ const element = document.getElementById(elementID);
81
+
82
+ if (!element) {
83
+ throw new Error(`No element with ID ${elementID} found.`);
84
+ }
85
+
86
+ element.addEventListener('click', this.show);
87
+ };
88
+
89
+ public registerCancelCallback = (func: () => void) => {
90
+ if (!this.cancelButton) {
91
+ throw new Error('Unable to register cancel callback without a cancel button.');
92
+ }
93
+
94
+ this.cancelButton.removeEventListener('click', this.hide);
95
+
96
+ this.cancelButton.addEventListener('click', () => {
97
+ func();
98
+ this.hide();
99
+ });
100
+ };
101
+
102
+ public registerConfirmCallback = (func: (data?: FormData | undefined) => void) => {
103
+ if (!this.confirmButton) {
104
+ throw new Error('Unable to register cancel callback without a confirmation button.');
105
+ }
106
+
107
+ this.confirmButton.removeEventListener('click', this.hide);
108
+
109
+ if (this.isForm) {
110
+ this.modalForm.addEventListener('submit', (e) => {
111
+ e.preventDefault();
112
+
113
+ const formData = new FormData(this.modalForm);
114
+
115
+ func(formData);
116
+ this.hide();
117
+
118
+ setTimeout(() => this.modalForm.reset(), 450);
119
+ });
120
+ } else {
121
+ this.confirmButton.addEventListener('click', () => {
122
+ func();
123
+ this.hide();
124
+ });
125
+ }
126
+ };
127
+ }
128
+
129
+ export { ModalHelper };
@@ -0,0 +1,175 @@
1
+ ---
2
+ import type { StudioCMSColorway } from '../utils/colors';
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
+ color?: StudioCMSColorway;
14
+ defaultValue?: string;
15
+ options: Option[];
16
+ disabled?: boolean;
17
+ name?: string;
18
+ isRequired?: boolean;
19
+ horizontal?: boolean;
20
+ class?: string;
21
+ };
22
+
23
+ const {
24
+ label,
25
+ color,
26
+ defaultValue,
27
+ options,
28
+ disabled,
29
+ name = generateID('radio'),
30
+ isRequired,
31
+ horizontal,
32
+ class: className,
33
+ } = Astro.props;
34
+ ---
35
+
36
+ <div
37
+ class="radio-container"
38
+ class:list={[
39
+ disabled && "disabled",
40
+ horizontal && "horizontal",
41
+ color,
42
+ className
43
+ ]}
44
+ >
45
+ <span>
46
+ {label} <span class="req-star">{isRequired && "*"}</span>
47
+ </span>
48
+ <div class="radio-inputs">
49
+ {options.map(({ label, value, disabled: individuallyDisabled }) => (
50
+ <label
51
+ for={value}
52
+ class="radio-label"
53
+ class:list={[ individuallyDisabled && "disabled" ]}
54
+ >
55
+ <div class="radio-box-container">
56
+ <div class="radio-box" />
57
+ </div>
58
+ <input
59
+ class="radio-toggle"
60
+ type="radio"
61
+ value={value}
62
+ id={value}
63
+ name={name}
64
+ checked={value === defaultValue}
65
+ disabled={disabled || individuallyDisabled}
66
+ required={isRequired}
67
+ />
68
+ <span>{label}</span>
69
+ </label>
70
+ ))}
71
+ </div>
72
+ </div>
73
+ <style>
74
+ .radio-container {
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: .5rem;
78
+ }
79
+
80
+ .radio-container.disabled {
81
+ opacity: 0.5;
82
+ color: hsl(var(--text-muted));
83
+ }
84
+
85
+ .radio-label.disabled {
86
+ opacity: 0.5;
87
+ color: hsl(var(--text-muted));
88
+ pointer-events: none;
89
+ }
90
+
91
+ .req-star {
92
+ color: hsl(var(--danger-base));
93
+ font-weight: 700;
94
+ }
95
+
96
+ .radio-inputs {
97
+ display: flex;
98
+ flex-direction: column;
99
+ gap: .75rem;
100
+ }
101
+
102
+ .radio-container.horizontal .radio-inputs {
103
+ flex-direction: row;
104
+ }
105
+
106
+ .radio-label {
107
+ display: flex;
108
+ flex-direction: row;
109
+ gap: .5rem;
110
+ position: relative;
111
+ align-items: center;
112
+ }
113
+
114
+ .radio-label:hover .radio-box {
115
+ outline-color: hsl(var(--default-hover));
116
+ }
117
+
118
+ .radio-container:not(.disabled) .radio-label:active .radio-box {
119
+ outline-color: hsl(var(--default-active));
120
+ scale: 0.9;
121
+ }
122
+
123
+ .radio-label:has(.radio-toggle:checked) .radio-box {
124
+ background-color: hsl(var(--text-normal));
125
+ outline-color: hsl(var(--text-normal));
126
+ }
127
+
128
+ .radio-container.primary .radio-label:has(.radio-toggle:checked) .radio-box {
129
+ background-color: hsl(var(--primary-base));
130
+ outline-color: hsl(var(--primary-base));
131
+ }
132
+
133
+ .radio-container.success .radio-label:has(.radio-toggle:checked) .radio-box {
134
+ background-color: hsl(var(--success-base));
135
+ outline-color: hsl(var(--success-base));
136
+ }
137
+
138
+ .radio-container.warning .radio-label:has(.radio-toggle:checked) .radio-box {
139
+ background-color: hsl(var(--warning-base));
140
+ outline-color: hsl(var(--warning-base));
141
+ }
142
+
143
+ .radio-container.danger .radio-label:has(.radio-toggle:checked) .radio-box {
144
+ background-color: hsl(var(--danger-base));
145
+ outline-color: hsl(var(--danger-base));
146
+ }
147
+
148
+ .radio-box-container {
149
+ width: 20px;
150
+ height: 20px;
151
+ display: flex;
152
+ flex-direction: row;
153
+ align-items: center;
154
+ justify-content: center;
155
+ cursor: pointer;
156
+ }
157
+
158
+ .radio-box {
159
+ width: 12px;
160
+ height: 12px;
161
+ border-radius: 20px;
162
+ outline: 1px solid hsl(var(--default-base));
163
+ outline-offset: 4px;
164
+ transition: all .15s ease;
165
+ }
166
+
167
+ .radio-toggle {
168
+ width: 0;
169
+ height: 0;
170
+ visibility: hidden;
171
+ opacity: 0;
172
+ margin: 0;
173
+ position: absolute;
174
+ }
175
+ </style>
@@ -0,0 +1,38 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+
4
+ interface Props extends HTMLAttributes<'div'> {
5
+ alignCenter?: boolean;
6
+ gapSize?: 'sm' | 'md' | 'lg';
7
+ };
8
+
9
+ const { alignCenter, gapSize = 'md', ...props } = Astro.props;
10
+ ---
11
+
12
+ <div class="row" class:list={[alignCenter && "align", gapSize]} {...props}>
13
+ <slot />
14
+ </div>
15
+ <style>
16
+ .row {
17
+ display: flex;
18
+ flex-direction: row;
19
+ position: relative;
20
+ flex-wrap: wrap;
21
+ }
22
+
23
+ .row.align {
24
+ align-items: center;
25
+ }
26
+
27
+ .row.sm {
28
+ gap: .5rem;
29
+ }
30
+
31
+ .row.md {
32
+ gap: 1rem;
33
+ }
34
+
35
+ .row.lg {
36
+ gap: 2rem;
37
+ }
38
+ </style>