@studiocms/ui 0.0.1 → 0.1.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.
@@ -8,9 +8,9 @@ interface Option {
8
8
  value: string;
9
9
  disabled?: boolean;
10
10
  color?: StudioCMSColorway;
11
- icon?: HeroIconName;
12
- href?: string;
13
- };
11
+ icon?: HeroIconName;
12
+ href?: string;
13
+ }
14
14
 
15
15
  interface Props {
16
16
  options: Option[];
@@ -18,27 +18,34 @@ interface Props {
18
18
  id: string;
19
19
  align?: 'start' | 'center' | 'end';
20
20
  triggerOn?: 'left' | 'right' | 'both';
21
- offset?: number;
22
- };
23
-
24
- const { options, disabled = false, align = 'center', id, triggerOn = 'left', offset = 0 } = Astro.props;
21
+ offset?: number;
22
+ }
23
+
24
+ const {
25
+ options,
26
+ disabled = false,
27
+ align = 'center',
28
+ id,
29
+ triggerOn = 'left',
30
+ offset = 0,
31
+ } = Astro.props;
25
32
  ---
26
33
  <div
27
- class="dropdown-container"
34
+ class="sui-dropdown-container"
28
35
  class:list={[disabled && 'disabled']}
29
36
  data-align={align}
30
37
  id={`${id}-container`}
31
38
  data-trigger={triggerOn}
32
- transition:persist transition:persist-props
39
+ transition:persist
40
+ transition:persist-props
33
41
  >
34
- <div class="dropdown-toggle" id={`${id}-toggle-btn`}>
42
+ <div class="sui-dropdown-toggle" id={`${id}-toggle-btn`}>
35
43
  <slot />
36
44
  </div>
37
- <ul class="dropdown above" class:list={[align]} role="listbox" id={`${id}-dropdown`} transition:persist transition:persist-props>
45
+ <ul class="sui-dropdown" class:list={[align]} role="listbox" id={`${id}-dropdown`} transition:persist transition:persist-props>
38
46
  {options.map(({ value, disabled, color, label, icon, href }) => (
39
47
  <li
40
- class="dropdown-option"
41
- role="option"
48
+ class="sui-dropdown-option"
42
49
  data-value={value}
43
50
  class:list={[disabled && "disabled", icon && "has-icon", color, href && "has-href"]}
44
51
  >
@@ -46,7 +53,7 @@ const { options, disabled = false, align = 'center', id, triggerOn = 'left', off
46
53
  <Icon width={24} height={24} name={icon} />
47
54
  )}
48
55
  {href ? (
49
- <a href={href} class="dropdown-link">{label}</a>
56
+ <a href={href} class="sui-dropdown-link">{label}</a>
50
57
  ) : (
51
58
  <span>{label}</span>
52
59
  )}
@@ -55,18 +62,18 @@ const { options, disabled = false, align = 'center', id, triggerOn = 'left', off
55
62
  </ul>
56
63
  </div>
57
64
  <style define:vars={{ offset: `${offset}px` }}>
58
- .dropdown-toggle {
65
+ .sui-dropdown-toggle {
59
66
  width: fit-content;
60
67
  }
61
68
 
62
- .dropdown-container {
69
+ .sui-dropdown-container {
63
70
  position: relative;
64
71
  display: flex;
65
72
  flex-direction: column;
66
73
  gap: .25rem;
67
74
  }
68
75
 
69
- .dropdown {
76
+ .sui-dropdown {
70
77
  position: absolute;
71
78
  list-style: none;
72
79
  margin: 0;
@@ -134,11 +141,11 @@ const { options, disabled = false, align = 'center', id, triggerOn = 'left', off
134
141
  }
135
142
  }
136
143
 
137
- .dropdown.initialized {
144
+ .sui-dropdown.initialized {
138
145
  animation: pop-down .15s ease forwards;
139
146
  }
140
147
 
141
- .dropdown.initialized.active {
148
+ .sui-dropdown.initialized.active {
142
149
  display: flex;
143
150
  border: 1px solid hsl(var(--border));
144
151
  height: auto;
@@ -147,36 +154,36 @@ const { options, disabled = false, align = 'center', id, triggerOn = 'left', off
147
154
  animation: pop-up .15s ease forwards;
148
155
  }
149
156
 
150
- .dropdown.initialized.below {
157
+ .sui-dropdown.initialized.below {
151
158
  top: calc(100% + .25rem + var(--offset)) !important;
152
159
  bottom: auto;
153
160
 
154
161
  transform-origin: top center;
155
162
  }
156
163
 
157
- .dropdown.below.start {
164
+ .sui-dropdown.below.start {
158
165
  transform-origin: top left;
159
166
  }
160
167
 
161
- .dropdown.below.end {
168
+ .sui-dropdown.below.end {
162
169
  transform-origin: top right;
163
170
  }
164
171
 
165
- .dropdown.above {
172
+ .sui-dropdown.above {
166
173
  top: auto;
167
174
  bottom: calc(100% + .25rem + var(--offset)) !important;
168
175
  transform-origin: bottom center;
169
176
  }
170
177
 
171
- .dropdown.above.start {
178
+ .sui-dropdown.above.start {
172
179
  transform-origin: bottom left;
173
180
  }
174
181
 
175
- .dropdown.above.end {
182
+ .sui-dropdown.above.end {
176
183
  transform-origin: bottom right;
177
184
  }
178
185
 
179
- .dropdown-option {
186
+ .sui-dropdown-option {
180
187
  padding: .5rem .75rem;
181
188
  cursor: pointer;
182
189
  font-size: .975em;
@@ -187,67 +194,68 @@ const { options, disabled = false, align = 'center', id, triggerOn = 'left', off
187
194
  align-items: center;
188
195
  width: 100%;
189
196
  white-space: normal;
197
+ user-select: none;
190
198
  }
191
199
 
192
- .dropdown-option:hover {
200
+ .sui-dropdown-option:hover {
193
201
  background-color: hsl(var(--background-step-3));
194
202
  }
195
203
 
196
- .dropdown-option.has-href {
204
+ .sui-dropdown-option.has-href {
197
205
  padding: 0;
198
206
  }
199
207
 
200
- .dropdown-link {
208
+ .sui-dropdown-link {
201
209
  padding: .5rem .75rem;
202
210
  width: 100%;
203
211
  }
204
212
 
205
- .dropdown-option.primary {
213
+ .sui-dropdown-option.primary {
206
214
  color: hsl(var(--primary-base));
207
215
  }
208
216
 
209
- .dropdown-option.primary:hover {
217
+ .sui-dropdown-option.primary:hover {
210
218
  background-color: hsl(var(--primary-base));
211
219
  color: hsl(var(--text-inverted));
212
220
  }
213
221
 
214
- .dropdown-option.success {
222
+ .sui-dropdown-option.success {
215
223
  color: hsl(var(--success-base));
216
224
  }
217
225
 
218
- .dropdown-option.success:hover {
226
+ .sui-dropdown-option.success:hover {
219
227
  background-color: hsl(var(--success-base));
220
228
  color: hsl(var(--text-dark));
221
229
  }
222
230
 
223
- .dropdown-option.warning {
231
+ .sui-dropdown-option.warning {
224
232
  color: hsl(var(--warning-base));
225
233
  }
226
234
 
227
- .dropdown-option.warning:hover {
235
+ .sui-dropdown-option.warning:hover {
228
236
  background-color: hsl(var(--warning-base));
229
237
  color: hsl(var(--text-dark));
230
238
  }
231
239
 
232
- .dropdown-option.danger {
240
+ .sui-dropdown-option.danger {
233
241
  color: hsl(var(--danger-base));
234
242
  }
235
243
 
236
- .dropdown-option.danger:hover {
244
+ .sui-dropdown-option.danger:hover {
237
245
  background-color: hsl(var(--danger-base));
238
246
  color: hsl(var(--text-light));
239
247
  }
240
248
 
241
- .dropdown-option.disabled {
249
+ .sui-dropdown-option.disabled {
242
250
  pointer-events: none;
243
251
  color: hsl(var(--text-muted));
244
252
  }
245
253
 
246
- .dropdown-option.end {
254
+ .sui-dropdown-option.end {
247
255
  justify-content: space-between;
248
256
  }
249
257
 
250
- .dropdown-option.has-icon {
258
+ .sui-dropdown-option.has-icon {
251
259
  padding-left: .5rem;
252
260
  }
253
261
  </style>
@@ -1,12 +1,13 @@
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;
5
9
 
6
- alignment: 'start' | 'center' | 'end';
7
- triggerOn: 'left' | 'right' | 'both';
8
10
  active = false;
9
- fullWidth = false;
10
11
 
11
12
  constructor(id: string, fullWidth?: boolean) {
12
13
  this.container = document.getElementById(`${id}-container`) as HTMLDivElement;
@@ -0,0 +1,120 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+
4
+ interface Props extends HTMLAttributes<'footer'> {
5
+ links: {
6
+ [label: string]: {
7
+ label: string;
8
+ href: string;
9
+ }[];
10
+ };
11
+ copyright: string;
12
+ }
13
+
14
+ const { copyright, links, ...props } = Astro.props;
15
+ ---
16
+
17
+ <footer {...props}>
18
+ <div class="upper">
19
+ <div>
20
+ <slot name="brand" />
21
+ </div>
22
+ <div class="links">
23
+ {Object.keys(links).map((groupLabel) => (
24
+ <ul>
25
+ <li class="sui-footer-link-label">{groupLabel}</li>
26
+ {links[groupLabel]!.map((item) => (
27
+ <li>
28
+ <a href={item.href}>{item.label}</a>
29
+ </li>
30
+ ))}
31
+ </ul>
32
+ ))}
33
+ </div>
34
+ </div>
35
+ <hr class="separator" />
36
+ <div class="lower">
37
+ <span class="copyright-span">{copyright}</span>
38
+ <slot name="socials" />
39
+ </div>
40
+ </footer>
41
+ <style>
42
+ footer {
43
+ display: flex;
44
+ flex-direction: column;
45
+ gap: 2rem;
46
+ background-color: hsl(var(--background-step-1));
47
+ padding: 2rem 10vw;
48
+ color: hsl(var(--text-normal)) !important;
49
+ }
50
+
51
+ .upper, .lower {
52
+ display: flex;
53
+ flex-direction: row;
54
+ justify-content: space-between;
55
+ }
56
+
57
+ .links {
58
+ display: flex;
59
+ justify-content: flex-end;
60
+ flex-direction: row;
61
+ flex-wrap: wrap;
62
+ gap: 2rem;
63
+ }
64
+
65
+ .links ul {
66
+ list-style-type: none;
67
+ margin: 0 !important;
68
+ padding: 0 !important;
69
+ }
70
+
71
+ .links ul li, .links ul li * {
72
+ color: hsl(var(--text-normal)) !important;
73
+ width: fit-content;
74
+ }
75
+
76
+ .links ul li:has(a):hover {
77
+ text-decoration: underline;
78
+ }
79
+
80
+ .sui-footer-link-label {
81
+ font-size: 1.125em;
82
+ font-weight: 700;
83
+ }
84
+
85
+ .separator {
86
+ height: 1px;
87
+ width: 100%;
88
+ border: none;
89
+ background: hsl(var(--border));
90
+ }
91
+
92
+ .lower {
93
+ align-items: center;
94
+ flex-wrap: wrap;
95
+ gap: 1rem;
96
+ }
97
+
98
+ @media screen and (max-width: 1440px) {
99
+ footer {
100
+ padding: 2rem;
101
+ }
102
+ }
103
+
104
+ @media screen and (max-width: 1280px) {
105
+ .upper {
106
+ flex-direction: column;
107
+ gap: 2rem;
108
+ }
109
+
110
+ .links {
111
+ justify-content: flex-start;
112
+ }
113
+ }
114
+
115
+ @media screen and (max-width: 640px) {
116
+ .links ul {
117
+ width: calc(50% - 1rem);
118
+ }
119
+ }
120
+ </style>
@@ -11,7 +11,7 @@ interface Props extends HTMLAttributes<'input'> {
11
11
  disabled?: boolean;
12
12
  defaultValue?: string;
13
13
  class?: string;
14
- };
14
+ }
15
15
 
16
16
  const {
17
17
  label,
@@ -26,16 +26,18 @@ const {
26
26
  } = Astro.props;
27
27
  ---
28
28
 
29
- <label for={name} class="input-label" class:list={[disabled && "disabled"]}>
30
- <span class="label">
31
- {label} <span class="req-star">{isRequired && "*"}</span>
32
- </span>
29
+ <label for={name} class="sui-input-label" class:list={[disabled && "disabled"]}>
30
+ {label && (
31
+ <span class="label">
32
+ {label} <span class="req-star">{isRequired && "*"}</span>
33
+ </span>
34
+ )}
33
35
  <input
34
36
  placeholder={placeholder}
35
37
  name={name}
36
38
  id={name}
37
39
  type={type}
38
- class="input"
40
+ class="sui-input"
39
41
  class:list={[className]}
40
42
  required={isRequired}
41
43
  disabled={disabled}
@@ -44,15 +46,14 @@ const {
44
46
  />
45
47
  </label>
46
48
  <style>
47
- .input-label {
49
+ .sui-input-label {
48
50
  width: 100%;
49
51
  display: flex;
50
52
  flex-direction: column;
51
53
  gap: .25rem;
52
- margin-top: .5rem;
53
54
  }
54
55
 
55
- .input-label.disabled {
56
+ .sui-input-label.disabled {
56
57
  opacity: 0.5;
57
58
  pointer-events: none;
58
59
  color: hsl(var(--text-muted));
@@ -62,7 +63,7 @@ const {
62
63
  font-size: 14px;
63
64
  }
64
65
 
65
- .input {
66
+ .sui-input {
66
67
  padding: .5rem 1rem;
67
68
  border-radius: 8px;
68
69
  border: 1px solid hsl(var(--border));
@@ -71,18 +72,18 @@ const {
71
72
  transition: all .15s ease;
72
73
  }
73
74
 
74
- .input:hover {
75
+ .sui-input:hover {
75
76
  background: hsl(var(--background-step-3));
76
77
  }
77
78
 
78
- .input:active,
79
- .input:focus {
79
+ .sui-input:active,
80
+ .sui-input:focus {
80
81
  border: 1px solid hsl(var(--primary-base));
81
82
  outline: none;
82
83
  background: hsl(var(--background-step-2));
83
84
  }
84
85
 
85
- .disabled .input:active {
86
+ .disabled .sui-input:active {
86
87
  border: 1px solid hsl(var(--border));
87
88
  }
88
89
 
@@ -1,57 +1,80 @@
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
- type ModalButton = 'cancel' | 'confirm';
6
+ interface ButtonType {
7
+ label: string;
8
+ color: StudioCMSColorway;
9
+ }
6
10
 
7
11
  interface Props {
8
12
  id: string;
9
13
  size?: 'sm' | 'md' | 'lg';
10
14
  dismissable?: boolean;
11
- buttons?: ModalButton[];
15
+ cancelButton?: string | ButtonType;
16
+ actionButton?: string | ButtonType;
12
17
  isForm?: boolean;
13
- };
14
-
15
- const { id, size = 'md', dismissable = true, isForm = false, buttons = [] } = Astro.props;
18
+ }
19
+
20
+ const {
21
+ id,
22
+ size = 'md',
23
+ dismissable = true,
24
+ isForm = false,
25
+ cancelButton,
26
+ actionButton,
27
+ } = Astro.props;
16
28
  ---
17
- <!-- Should: Be centered on mobile, scroll happens inside, blur bg -->
18
29
  <dialog
19
30
  popover
20
31
  id={id}
21
32
  data-dismissable={`${dismissable}`}
22
- data-buttons={buttons.join(";")}
23
- class="modal"
33
+ data-has-action-button={actionButton}
34
+ data-has-cancel-button={cancelButton}
35
+ class="sui-modal"
24
36
  class:list={[size]}
25
37
  data-form={`${isForm}`}
26
38
  >
27
- <div class="modal-header">
39
+ <div class="sui-modal-header">
28
40
  <slot name="header" />
29
- {(dismissable || buttons.length === 0) && (
41
+ {(dismissable || (!cancelButton && !actionButton)) && (
30
42
  <button class="x-mark-container" id={`${id}-btn-x`}>
31
43
  <Icon name="x-mark" width={24} height={24} class={'dismiss-icon'} />
32
44
  </button>
33
45
  )}
34
46
  </div>
35
47
  <form id={`${id}-form-element`}>
36
- <div class="modal-body">
48
+ <div class="sui-modal-body">
37
49
  <slot />
38
50
  </div>
39
- <div class="modal-footer">
40
- {buttons.includes('cancel') && (
41
- <Button id={`${id}-btn-cancel`} color='danger' variant='flat' type={isForm ? 'reset' : 'button'}>
42
- Cancel
51
+ <div class="sui-modal-footer">
52
+ {cancelButton && (
53
+ <Button
54
+ id={`${id}-btn-cancel`}
55
+ color={typeof cancelButton === 'string' ? 'danger' : cancelButton.color}
56
+ variant='flat'
57
+ type={isForm ? 'reset' : 'button'}
58
+ >
59
+ {typeof cancelButton === 'string' ? cancelButton : cancelButton.label}
43
60
  </Button>
44
61
  )}
45
- {buttons.includes('confirm') && (
46
- <Button id={`${id}-btn-confirm`} type={'submit'} color='primary' variant='solid' type={isForm ? 'submit' : 'button'}>
47
- Confirm
62
+ {actionButton && (
63
+ <Button
64
+ id={`${id}-btn-confirm`}
65
+ type={'submit'}
66
+ color={typeof actionButton === 'string' ? 'primary' : actionButton.color}
67
+ variant='solid'
68
+ type={isForm ? 'submit' : 'button'}
69
+ >
70
+ {typeof actionButton === 'string' ? actionButton : actionButton.label}
48
71
  </Button>
49
72
  )}
50
73
  </div>
51
74
  </form>
52
75
  </dialog>
53
76
  <style>
54
- .modal {
77
+ .sui-modal {
55
78
  border: 1px solid hsl(var(--border));
56
79
  border-radius: .5rem;
57
80
  padding: 1.5rem;
@@ -59,35 +82,36 @@ const { id, size = 'md', dismissable = true, isForm = false, buttons = [] } = As
59
82
  animation: hide .25s ease;
60
83
  overflow: visible;
61
84
  margin: auto;
85
+ z-index: 50;
62
86
  }
63
87
 
64
- .modal.sm {
88
+ .sui-modal.sm {
65
89
  width: 384px;
66
90
  }
67
91
 
68
- .modal.md {
92
+ .sui-modal.md {
69
93
  width: 448px;
70
94
  }
71
95
 
72
- .modal.lg {
96
+ .sui-modal.lg {
73
97
  width: 608px;
74
98
  }
75
99
 
76
- .modal[open] {
100
+ .sui-modal[open] {
77
101
  animation: show .25s ease-in-out;
78
102
  }
79
103
 
80
- html:has(.modal[open]),
81
- body:has(.modal[open]) {
104
+ html:has(.sui-modal[open]),
105
+ body:has(.sui-modal[open]) {
82
106
  overflow: hidden;
83
107
  }
84
108
 
85
- .modal[open]::backdrop {
109
+ .sui-modal[open]::backdrop {
86
110
  background-color: rgba(0, 0, 0, 0.75);
87
111
  animation: backdrop .3s ease-in-out forwards;
88
112
  }
89
113
 
90
- .modal-header:has(*) {
114
+ .sui-modal-header:has(*) {
91
115
  margin-bottom: 1rem;
92
116
 
93
117
  display: flex;
@@ -115,11 +139,11 @@ const { id, size = 'md', dismissable = true, isForm = false, buttons = [] } = As
115
139
  background-color: hsl(var(--default-base));
116
140
  }
117
141
 
118
- .modal-footer {
142
+ .sui-modal-footer {
119
143
  display: none;
120
144
  }
121
145
 
122
- .modal-footer:has(*) {
146
+ .sui-modal-footer:has(*) {
123
147
  display: flex;
124
148
  flex-direction: row;
125
149
  gap: 1rem;
@@ -1,7 +1,7 @@
1
1
  class ModalHelper {
2
- public element: HTMLDialogElement;
3
- public cancelButton: HTMLButtonElement | undefined;
4
- public confirmButton: HTMLButtonElement | undefined;
2
+ private element: HTMLDialogElement;
3
+ private cancelButton: HTMLButtonElement | undefined;
4
+ private confirmButton: HTMLButtonElement | undefined;
5
5
 
6
6
  private isForm = false;
7
7
  private modalForm: HTMLFormElement;
@@ -33,21 +33,22 @@ class ModalHelper {
33
33
  }
34
34
 
35
35
  private addButtonListeners = (id: string, dismissable: boolean) => {
36
- if (dismissable || !this.element.dataset.buttons) {
36
+ if (
37
+ dismissable ||
38
+ (!this.element.dataset.hasCancelButton && !this.element.dataset.hasActionButton)
39
+ ) {
37
40
  const xMarkButton = document.getElementById(`${id}-btn-x`) as HTMLButtonElement;
38
41
  xMarkButton.addEventListener('click', this.hide);
39
42
  }
40
43
 
41
- if (!this.element.dataset.buttons) return;
44
+ if (!!this.element.dataset.hasCancelButton && !this.element.dataset.hasActionButton) return;
42
45
 
43
- const usedButtons = this.element.dataset.buttons.split(';');
44
-
45
- if (usedButtons.includes('cancel')) {
46
+ if (this.element.dataset.hasCancelButton) {
46
47
  this.cancelButton = document.getElementById(`${id}-btn-cancel`) as HTMLButtonElement;
47
48
  this.cancelButton.addEventListener('click', this.hide);
48
49
  }
49
50
 
50
- if (usedButtons.includes('confirm')) {
51
+ if (this.element.dataset.hasActionButton) {
51
52
  this.confirmButton = document.getElementById(`${id}-btn-confirm`) as HTMLButtonElement;
52
53
  this.confirmButton.addEventListener('click', this.hide);
53
54
  }