@studiocms/ui 1.0.0-beta.0 → 1.0.0-beta.2

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 (56) hide show
  1. package/dist/components/Accordion/accordion.css +6 -6
  2. package/dist/components/Badge/Badge.astro +12 -3
  3. package/dist/components/Badge/badge.css +15 -45
  4. package/dist/components/Breadcrumbs/breadcrumbs.css +2 -2
  5. package/dist/components/Center/Center.astro +1 -1
  6. package/dist/components/Checkbox/Checkbox.astro +6 -5
  7. package/dist/components/Checkbox/checkbox.css +9 -9
  8. package/dist/components/Checkbox/checkbox.js +1 -0
  9. package/dist/components/Dropdown/dropdown.css +12 -12
  10. package/dist/components/Footer/footer.css +4 -0
  11. package/dist/components/Group/group.css +12 -6
  12. package/dist/components/Icon/Icon.astro +14 -6
  13. package/dist/components/Icon/IconBase.astro +2 -0
  14. package/dist/components/Input/Input.astro +57 -13
  15. package/dist/components/Input/input.css +30 -4
  16. package/dist/components/Modal/modal.css +4 -4
  17. package/dist/components/Progress/Progress.astro +3 -2
  18. package/dist/components/Progress/progress.css +1 -1
  19. package/dist/components/RadioGroup/RadioGroup.astro +1 -1
  20. package/dist/components/RadioGroup/radiogroup.css +9 -9
  21. package/dist/components/RadioGroup/radiogroup.js +0 -2
  22. package/dist/components/Row/row.css +1 -1
  23. package/dist/components/SearchSelect/SearchSelect.astro +8 -4
  24. package/dist/components/SearchSelect/searchselect.css +3 -7
  25. package/dist/components/SearchSelect/searchselect.js +13 -6
  26. package/dist/components/Select/select.css +1 -5
  27. package/dist/components/Select/select.js +3 -3
  28. package/dist/components/Skeleton/skeleton.d.ts +6 -70
  29. package/dist/components/Tabs/TabItem.astro +8 -2
  30. package/dist/components/Tabs/Tabs.astro +8 -2
  31. package/dist/components/Tabs/tabs.css +2 -2
  32. package/dist/components/Textarea/textarea.css +4 -4
  33. package/dist/components/Toast/Toaster.astro +4 -4
  34. package/dist/components/Toast/toaster.css +9 -9
  35. package/dist/components/Toast/toaster.js +2 -2
  36. package/dist/components/Toggle/Toggle.astro +2 -2
  37. package/dist/components/Toggle/toggle.css +7 -7
  38. package/dist/components/Toggle/toggle.js +1 -0
  39. package/dist/components/User/User.astro +1 -1
  40. package/dist/components/User/user.css +2 -2
  41. package/dist/css/colors.css +63 -53
  42. package/dist/index.d.ts +7 -0
  43. package/dist/index.js +31 -41
  44. package/dist/toolbar/ColorPicker.js +4 -64
  45. package/dist/toolbar/icon.js +1 -1
  46. package/dist/toolbar/index.js +4 -4
  47. package/dist/utils/headers.d.ts +54 -8
  48. package/dist/utils/headers.js +8 -1
  49. package/dist/utils/stubs/icons-d-ts.stub +9 -0
  50. package/dist/utils/typegen.d.ts +6 -0
  51. package/dist/utils/typegen.js +14 -0
  52. package/package.json +16 -5
  53. package/dist/events.d.js +0 -0
  54. package/dist/utils/integration-utils.d.ts +0 -130
  55. package/dist/utils/integration-utils.js +0 -161
  56. package/dist/virtuals.d.js +0 -0
@@ -15,7 +15,7 @@
15
15
  overflow: hidden;
16
16
  }
17
17
  .sui-accordion-item[data-open=true] > .sui-accordion-details {
18
- bottom: .25rem;
18
+ bottom: 0.25rem;
19
19
  grid-template-rows: 1fr;
20
20
  }
21
21
  .sui-accordion-content {
@@ -46,11 +46,11 @@
46
46
  color: var(--text-normal);
47
47
  display: flex;
48
48
  flex-direction: row;
49
- gap: .5rem;
49
+ gap: 0.5rem;
50
50
  align-items: center;
51
- padding-left: .5rem;
52
- padding-bottom: .5rem;
53
- padding-top: .5rem;
51
+ padding-left: 0.5rem;
52
+ padding-bottom: 0.5rem;
53
+ padding-top: 0.5rem;
54
54
  user-select: none;
55
55
  width: 100%;
56
56
  border: none;
@@ -63,7 +63,7 @@
63
63
  outline-offset: 2px;
64
64
  }
65
65
  .sui-summary-chevron {
66
- transition: all .3s ease;
66
+ transition: all 0.3s ease;
67
67
  }
68
68
  .sui-accordion-item[data-open=true] > .sui-accordion-summary .sui-summary-chevron {
69
69
  transform: rotate(90deg);
@@ -5,6 +5,15 @@ import type { AvailableIcons } from 'studiocms:ui/icons';
5
5
  import type { HTMLAttributes } from 'astro/types';
6
6
  import Icon from '../Icon/Icon.astro';
7
7
 
8
+ /**
9
+ * The variant of the badge.
10
+ */
11
+ type BadgeVariant = 'flat' | 'outlined';
12
+ /**
13
+ * @deprecated The `default` variant is deprecated and will be removed in a future release. Use `flat` or `outlined` instead.
14
+ */
15
+ type DeprecatedBadgeVariants = 'default';
16
+
8
17
  interface Props extends Omit<HTMLAttributes<'span'>, 'color'> {
9
18
  /**
10
19
  * The color of the badge. Defaults to `primary`.
@@ -23,9 +32,9 @@ interface Props extends Omit<HTMLAttributes<'span'>, 'color'> {
23
32
  */
24
33
  size?: 'sm' | 'md' | 'lg';
25
34
  /**
26
- * The variant of the badge. Defaults to `default`.
35
+ * The variant of the badge. Defaults to `outlined`. The `default` variant is deprecated and will be removed in a future release.
27
36
  */
28
- variant?: 'default' | 'flat' | 'outline';
37
+ variant?: BadgeVariant | DeprecatedBadgeVariants;
29
38
  /**
30
39
  * The rounding of the badge. Full is a half-circle, semi is a quarter-circle. Defaults to `full`.
31
40
  */
@@ -40,7 +49,7 @@ const {
40
49
  color = 'primary',
41
50
  icon,
42
51
  size = 'md',
43
- variant = 'default',
52
+ variant = 'outlined',
44
53
  rounding = 'full',
45
54
  iconPosition = 'left',
46
55
  label,
@@ -1,98 +1,68 @@
1
1
  .sui-badge {
2
2
  display: flex;
3
3
  flex-direction: row;
4
- gap: .5rem;
4
+ gap: 0.5rem;
5
5
  align-items: center;
6
- padding: .125rem .75rem;
6
+ padding: 0.125rem 0.75rem;
7
7
  border-radius: var(--radius-full);
8
- font-size: .875rem;
8
+ font-size: 0.875rem;
9
9
  }
10
10
  .sui-badge.semi {
11
11
  border-radius: var(--radius-md);
12
12
  }
13
- .sui-badge.primary {
14
- background-color: var(--primary-base);
15
- color: var(--text-inverted);
16
- }
17
13
  .sui-badge.primary.flat,
18
14
  .sui-badge.primary.outlined {
19
15
  background-color: var(--primary-flat);
20
- color: var(--primary-base);
16
+ color: var(--text-normal);
21
17
  }
22
18
  .sui-badge.primary.outlined {
23
- border: 1px solid var(--primary-base);
24
- }
25
- .sui-badge.success {
26
- background-color: var(--success-base);
27
- color: var(--text-inverted);
19
+ border: 1px solid var(--primary-vibrant);
28
20
  }
29
21
  .sui-badge.success.flat,
30
22
  .sui-badge.success.outlined {
31
23
  background-color: var(--success-flat);
32
- color: var(--success-base);
24
+ color: var(--text-normal);
33
25
  }
34
26
  .sui-badge.success.outlined {
35
27
  border: 1px solid var(--success-base);
36
28
  }
37
- .sui-badge.warning {
38
- background-color: var(--warning-base);
39
- color: var(--text-inverted);
40
- }
41
29
  .sui-badge.warning.flat,
42
30
  .sui-badge.warning.outlined {
43
31
  background-color: var(--warning-flat);
44
- color: var(--warning-base);
32
+ color: var(--text-normal);
45
33
  }
46
34
  .sui-badge.warning.outlined {
47
35
  border: 1px solid var(--warning-base);
48
36
  }
49
- .sui-badge.danger {
50
- background-color: var(--danger-base);
51
- color: var(--text-normal);
52
- }
53
37
  .sui-badge.danger.flat,
54
38
  .sui-badge.danger.outlined {
55
39
  background-color: var(--danger-flat);
56
- color: var(--danger-base);
40
+ color: var(--text-normal);
57
41
  }
58
42
  .sui-badge.danger.outlined {
59
- border: 1px solid var(--danger-base);
60
- }
61
- .sui-badge.info {
62
- background-color: var(--info-base);
63
- color: var(--text-normal);
43
+ border: 1px solid var(--danger-vibrant);
64
44
  }
65
45
  .sui-badge.info.flat,
66
46
  .sui-badge.info.outlined {
67
47
  background-color: var(--info-flat);
68
- color: var(--info-base);
48
+ color: var(--text-normal);
69
49
  }
70
50
  .sui-badge.info.outlined {
71
- border: 1px solid var(--info-base);
72
- }
73
- .sui-badge.mono {
74
- background-color: var(--mono-base);
75
- color: var(--text-inverted);
51
+ border: 1px solid var(--info-vibrant);
76
52
  }
77
53
  .sui-badge.mono.flat,
78
54
  .sui-badge.mono.outlined {
79
55
  background-color: var(--mono-flat);
80
- color: var(--mono-base);
56
+ color: var(--text-normal);
81
57
  }
82
58
  .sui-badge.mono.outlined {
83
59
  border: 1px solid var(--mono-base);
84
60
  }
85
- [data-theme=light] .sui-badge.danger {
86
- color: var(--text-inverted);
87
- }
88
- [data-theme=light] .sui-badge.info {
89
- color: var(--text-inverted);
90
- }
91
61
  .sui-badge.sm {
92
- padding: .125rem .75rem;
62
+ padding: 0.125rem 0.75rem;
93
63
  }
94
64
  .sui-badge.sm {
95
- font-size: .75rem;
65
+ font-size: 0.75rem;
96
66
  }
97
67
  .sui-badge.sm > svg {
98
68
  width: 14px;
@@ -100,7 +70,7 @@
100
70
  }
101
71
  .sui-badge.lg {
102
72
  font-size: 1rem;
103
- padding: .25rem 1rem;
73
+ padding: 0.25rem 1rem;
104
74
  }
105
75
  .sui-badge.lg {
106
76
  font-size: 1rem;
@@ -1,13 +1,13 @@
1
1
  .sui-breadcrumbs {
2
2
  display: flex;
3
3
  flex-direction: row;
4
- gap: .25rem;
4
+ gap: 0.25rem;
5
5
  }
6
6
  .sui-breadcrumbs > *:last-child {
7
7
  display: none;
8
8
  }
9
9
  .sui-breadcrumbs a {
10
- color: var(--primary-base);
10
+ color: var(--primary-hover);
11
11
  text-decoration: none;
12
12
  }
13
13
  .sui-breadcrumbs a:hover {
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  import './center.css';
3
3
  ---
4
- <!-- This was made for Jumper, to keep him happy <3 -->
4
+ {/* This was made for Jumper, to keep him happy <3 */}
5
5
  <div class="sui-center">
6
6
  <slot />
7
7
  </div>
@@ -59,7 +59,7 @@ const iconSizes = {
59
59
  lg: 24,
60
60
  };
61
61
  ---
62
- <label
62
+ <label
63
63
  class="sui-checkmark-label"
64
64
  for={name}
65
65
  class:list={[
@@ -72,9 +72,9 @@ const iconSizes = {
72
72
  class="sui-checkmark-container"
73
73
  tabindex="0"
74
74
  role="checkbox"
75
- aria-checked={defaultChecked}
75
+ aria-checked={defaultChecked || "false"}
76
76
  aria-labelledby={`label-${name}`}
77
- >
77
+ >
78
78
  <Checkmark
79
79
  class={'sui-checkmark'}
80
80
  width={iconSizes[size]}
@@ -88,8 +88,9 @@ const iconSizes = {
88
88
  disabled={disabled}
89
89
  required={isRequired}
90
90
  class="sui-checkbox"
91
- value={value}
91
+ value={value}
92
92
  hidden
93
+ aria-checked={defaultChecked || "false"}
93
94
  />
94
95
  </div>
95
96
  <span id={`label-${name}`}>
@@ -97,5 +98,5 @@ const iconSizes = {
97
98
  </span>
98
99
  </label>
99
100
  <script>
100
- import "studiocms:ui/scripts/checkbox"
101
+ import "studiocms:ui/scripts/checkbox";
101
102
  </script>
@@ -2,9 +2,9 @@
2
2
  display: flex;
3
3
  flex-direction: row;
4
4
  align-items: center;
5
- gap: .5rem;
5
+ gap: 0.5rem;
6
6
  position: relative;
7
- margin: .25rem 0;
7
+ margin: 0.25rem 0;
8
8
  }
9
9
  .sui-checkmark-label.disabled {
10
10
  opacity: 0.5;
@@ -20,9 +20,9 @@
20
20
  border-radius: var(--radius-md);
21
21
  cursor: pointer;
22
22
  transition:
23
- background-color .15s,
24
- border .15s,
25
- transform .15s;
23
+ background-color 0.15s,
24
+ border 0.15s,
25
+ transform 0.15s;
26
26
  transition-timing-function: ease;
27
27
  }
28
28
  .sui-checkmark-container:focus-visible {
@@ -37,7 +37,7 @@
37
37
  scale: 0.9;
38
38
  }
39
39
  .sui-checkmark-label.sm {
40
- font-size: .875em;
40
+ font-size: 0.875em;
41
41
  }
42
42
  .sui-checkmark-label.sm .sui-checkmark-container {
43
43
  width: 1.25rem;
@@ -53,7 +53,7 @@
53
53
  .sui-checkmark-label.lg .sui-checkmark-container {
54
54
  width: 1.75rem;
55
55
  height: 1.75rem;
56
- padding: .125rem;
56
+ padding: 0.125rem;
57
57
  }
58
58
  .primary .sui-checkmark-container:has(.sui-checkbox:checked) {
59
59
  border-color: var(--primary-base);
@@ -80,7 +80,7 @@
80
80
  background-color: var(--mono-base);
81
81
  }
82
82
  .req-star {
83
- color: var(--danger-base);
83
+ color: var(--danger-vibrant);
84
84
  font-weight: 700;
85
85
  }
86
86
  .sui-checkbox {
@@ -91,7 +91,7 @@
91
91
  margin: 0;
92
92
  }
93
93
  .sui-checkmark polyline {
94
- transition: all .15s ease;
94
+ transition: all 0.15s ease;
95
95
  color: var(--text-normal);
96
96
  }
97
97
  .primary .sui-checkmark polyline {
@@ -17,6 +17,7 @@ function loadCheckboxes() {
17
17
  box.dataset.initialized = "true";
18
18
  box.addEventListener("change", (e) => {
19
19
  box.parentElement.ariaChecked = e.target.checked ? "true" : "false";
20
+ box.ariaChecked = e.target.checked ? "true" : "false";
20
21
  });
21
22
  }
22
23
  }
@@ -5,7 +5,7 @@
5
5
  position: relative;
6
6
  display: flex;
7
7
  flex-direction: column;
8
- gap: .25rem;
8
+ gap: 0.25rem;
9
9
  }
10
10
  .sui-dropdown {
11
11
  position: absolute;
@@ -68,17 +68,17 @@
68
68
  }
69
69
  }
70
70
  .sui-dropdown.initialized {
71
- animation: pop-down .15s ease forwards;
71
+ animation: pop-down 0.15s ease forwards;
72
72
  }
73
73
  .sui-dropdown.initialized.active {
74
74
  display: flex;
75
75
  border: 1px solid var(--border);
76
76
  height: auto;
77
77
  pointer-events: all;
78
- animation: pop-up .15s ease forwards;
78
+ animation: pop-up 0.15s ease forwards;
79
79
  }
80
80
  .sui-dropdown.initialized.below {
81
- top: calc(100% + .25rem + var(--offset)) !important;
81
+ top: calc(100% + 0.25rem + var(--offset)) !important;
82
82
  bottom: auto;
83
83
  transform-origin: top center;
84
84
  }
@@ -90,7 +90,7 @@
90
90
  }
91
91
  .sui-dropdown.above {
92
92
  top: auto;
93
- bottom: calc(100% + .25rem + var(--offset)) !important;
93
+ bottom: calc(100% + 0.25rem + var(--offset)) !important;
94
94
  transform-origin: bottom center;
95
95
  }
96
96
  .sui-dropdown.above.start {
@@ -101,8 +101,8 @@
101
101
  }
102
102
  .sui-dropdown-option {
103
103
  cursor: pointer;
104
- font-size: .975em;
105
- transition: all .15s ease;
104
+ font-size: 0.975em;
105
+ transition: all 0.15s ease;
106
106
  display: flex;
107
107
  flex-direction: row;
108
108
  align-items: center;
@@ -114,9 +114,9 @@
114
114
  display: flex;
115
115
  flex-direction: row;
116
116
  align-items: center;
117
- padding: .5rem .75rem;
117
+ padding: 0.5rem 0.75rem;
118
118
  width: 100%;
119
- gap: .5rem;
119
+ gap: 0.5rem;
120
120
  }
121
121
  .sui-dropdown-option:hover,
122
122
  .sui-dropdown-option:focus,
@@ -153,14 +153,14 @@
153
153
  color: var(--text-dark);
154
154
  }
155
155
  .sui-dropdown-option.danger {
156
- color: var(--danger-base);
156
+ color: var(--danger-vibrant);
157
157
  }
158
158
  .sui-dropdown-option.danger:hover {
159
159
  background-color: var(--danger-base);
160
160
  color: var(--text-light);
161
161
  }
162
162
  .sui-dropdown-option.info {
163
- color: var(--info-base);
163
+ color: var(--info-vibrant);
164
164
  }
165
165
  .sui-dropdown-option.info:hover {
166
166
  background-color: var(--info-base);
@@ -181,5 +181,5 @@
181
181
  justify-content: space-between;
182
182
  }
183
183
  .sui-dropdown-option.has-icon .sui-dropdown-line-container {
184
- padding-left: .5rem;
184
+ padding-left: 0.5rem;
185
185
  }
@@ -24,6 +24,9 @@ footer.sui-footer {
24
24
  margin: 0 !important;
25
25
  padding: 0 !important;
26
26
  }
27
+ .links ul > li {
28
+ margin-bottom: 0.5rem;
29
+ }
27
30
  .links ul li,
28
31
  .links ul li * {
29
32
  color: var(--text-normal) !important;
@@ -35,6 +38,7 @@ footer.sui-footer {
35
38
  .sui-footer-link-label {
36
39
  font-size: 1.125em;
37
40
  font-weight: 700;
41
+ margin-bottom: 0.75rem !important;
38
42
  }
39
43
  .separator {
40
44
  height: 1px;
@@ -3,17 +3,23 @@
3
3
  flex-direction: row;
4
4
  align-items: center;
5
5
  }
6
- .sui-group .sui-button:not(:first-child):not(:last-child),
7
- .sui-group .sui-badge:not(:first-child):not(:last-child) {
6
+ .sui-group > .sui-button:not(:first-child):not(:last-child),
7
+ .sui-group .sui-dropdown-container:not(:first-child):not(:last-child) .sui-button,
8
+ .sui-group > .sui-badge:not(:first-child):not(:last-child) {
8
9
  border-radius: 0;
9
10
  }
10
- .sui-group .sui-button:first-child,
11
- .sui-group .sui-badge:first-child {
11
+ .sui-group > .sui-button:first-child,
12
+ .sui-group .sui-dropdown-container:first-child .sui-button,
13
+ .sui-group > .sui-badge:first-child {
12
14
  border-top-right-radius: 0;
13
15
  border-bottom-right-radius: 0;
14
16
  }
15
- .sui-group .sui-button:last-child,
16
- .sui-group .sui-badge:last-child {
17
+ .sui-group > .sui-button:last-child,
18
+ .sui-group .sui-dropdown-container:last-child .sui-button,
19
+ .sui-group > .sui-badge:last-child {
17
20
  border-top-left-radius: 0;
18
21
  border-bottom-left-radius: 0;
19
22
  }
23
+ .sui-group .sui-button:focus-visible {
24
+ z-index: 2;
25
+ }
@@ -1,11 +1,13 @@
1
1
  ---
2
- // @ts-ignore - This is a TypeGenerated Module, available in dev thanks to manual d.ts file - This comment is here because this causes a type-error during build
3
- import { type AvailableIcons, type IconCollections, iconCollections } from 'studiocms:ui/icons';
4
- // @ts-ignore - This is a TypeGenerated Module, available in dev thanks to manual d.ts file - This comment is here because this causes a type-error during build
5
- import * as Icons from 'studiocms:ui/icons';
2
+ import {
3
+ type AvailableIcons,
4
+ collections,
5
+ type IconCollections,
6
+ iconCollections,
7
+ } from 'studiocms:ui/icons';
6
8
  import type { HTMLAttributes } from 'astro/types';
9
+ import { errorHintBuilder, StudioCMS_UI_IconError } from './errors.js';
7
10
  import IconBase from './IconBase.astro';
8
- import { StudioCMS_UI_IconError, errorHintBuilder } from './errors.js';
9
11
 
10
12
  /**
11
13
  * Props interface for the Icon component.
@@ -45,6 +47,7 @@ const { name: __name, height, width, ...rest } = Astro.props;
45
47
  */
46
48
  const _nameSplit = __name.split(':');
47
49
 
50
+ /* v8 ignore start */
48
51
  /**
49
52
  * Checks if the icon name contains a colon and if the split name length is not equal to 1.
50
53
  * If both conditions are met, throws a StudioCMS_UI_IconError with a message indicating an invalid icon name.
@@ -60,6 +63,7 @@ if (__name.includes(':') && _nameSplit.length !== 2) {
60
63
  errorHintBuilder({ iconCollections, prefix: '', iconName: __name })
61
64
  );
62
65
  }
66
+ /* v8 ignore stop */
63
67
 
64
68
  /**
65
69
  * Extracts the prefix from the `_nameSplit` array and casts it to the `IconCollections` type.
@@ -83,8 +87,9 @@ const name: AvailableIcons = _nameSplit[1] as AvailableIcons;
83
87
  * @constant
84
88
  * @param {string} prefix - The prefix used to identify the icon collection.
85
89
  */
86
- const iconCollection = Icons[prefix];
90
+ const iconCollection = collections[prefix];
87
91
 
92
+ /* v8 ignore start */
88
93
  /**
89
94
  * Validates the provided icon collection prefix and throws an error if it is invalid.
90
95
  *
@@ -101,7 +106,9 @@ if (!prefix || !iconCollections.includes(prefix) || !iconCollection) {
101
106
  errorHintBuilder({ iconCollections, prefix, iconName: name })
102
107
  );
103
108
  }
109
+ /* v8 ignore stop */
104
110
 
111
+ /* v8 ignore start */
105
112
  /**
106
113
  * Throws an error if the icon name is invalid.
107
114
  *
@@ -117,5 +124,6 @@ if (!name) {
117
124
  errorHintBuilder({ iconCollections, prefix, iconName: name })
118
125
  );
119
126
  }
127
+ /* v8 ignore stop */
120
128
  ---
121
129
  <IconBase {iconCollection} {name} {prefix} {...rest} {height} {width} />
@@ -89,6 +89,7 @@ const attributes = props as SVGAttributes;
89
89
  */
90
90
  const iconData = getIconData(iconCollection, name);
91
91
 
92
+ /* v8 ignore start */
92
93
  /**
93
94
  * Throws an error if the icon data is missing from the collection.
94
95
  *
@@ -105,6 +106,7 @@ if (!iconData) {
105
106
  `Icon "${name}" is missing in ${iconCollection.prefix} collection`
106
107
  );
107
108
  }
109
+ /* v8 ignore stop */
108
110
 
109
111
  /**
110
112
  * Converts icon data to SVG format and renders it with specified attributes.
@@ -1,7 +1,11 @@
1
1
  ---
2
+ // @ts-expect-error - This is a TypeGenerated Module, available in dev thanks to manual d.ts file - This comment is here because this causes a type-error during build
3
+ import { type AvailableIcons } from 'studiocms:ui/icons';
2
4
  import type { HTMLAttributes } from 'astro/types';
3
5
  import { generateID } from '../../utils/generateID.js';
4
6
  import './input.css';
7
+ import { AstroError } from 'astro/errors';
8
+ import Icon from '../Icon/Icon.astro';
5
9
 
6
10
  /**
7
11
  * The props for the input component.
@@ -39,6 +43,19 @@ interface Props extends HTMLAttributes<'input'> {
39
43
  * Additional classes to apply to the input.
40
44
  */
41
45
  class?: string;
46
+ /**
47
+ * An optional icon to be shown in the input.
48
+ */
49
+ icon?:
50
+ | AvailableIcons
51
+ | {
52
+ name: AvailableIcons;
53
+ position: 'left' | 'right';
54
+ };
55
+ /**
56
+ * A description to be shown below the input
57
+ */
58
+ description?: string;
42
59
  }
43
60
 
44
61
  const {
@@ -50,26 +67,53 @@ const {
50
67
  isRequired = false,
51
68
  disabled = false,
52
69
  class: className,
70
+ icon,
71
+ description,
53
72
  ...props
54
73
  } = Astro.props;
74
+
75
+ if (typeof icon === 'object') {
76
+ if (!icon.name) throw new AstroError('Missing icon name for input!');
77
+ if (!icon.position) throw new AstroError('Missing icon position for input!');
78
+
79
+ if (!['left', 'right'].includes(icon.position))
80
+ throw new AstroError('Invalid icon position for input!');
81
+ }
82
+
83
+ const iconPos = icon
84
+ ? typeof icon === 'string' || (typeof icon === 'object' && icon.position === 'left')
85
+ ? 'left'
86
+ : 'right'
87
+ : undefined;
55
88
  ---
56
89
 
57
90
  <label for={name} class="sui-input-label" class:list={[disabled && "disabled"]}>
58
- {label && (
91
+ {label && (
59
92
  <span class="label">
60
93
  {label} <span class="req-star">{isRequired && "*"}</span>
61
94
  </span>
62
95
  )}
63
- <input
64
- placeholder={placeholder}
65
- name={name}
66
- id={name}
67
- type={type}
68
- class="sui-input"
69
- class:list={[className]}
70
- required={isRequired}
71
- disabled={disabled}
72
- value={defaultValue}
73
- {...props}
74
- />
96
+ <div class="sui-input-wrapper">
97
+ {typeof icon === "object" && (
98
+ <Icon name={icon.name} width={20} height={20} class="input-icon" class:list={[`icon-${iconPos}`]} />
99
+ )}
100
+ {typeof icon === "string" && (
101
+ <Icon name={icon} width={20} height={20} class="input-icon icon-left" />
102
+ )}
103
+ <input
104
+ placeholder={placeholder}
105
+ name={name}
106
+ id={name}
107
+ type={type}
108
+ class="sui-input"
109
+ class:list={[className, iconPos && `icon-${iconPos}`]}
110
+ required={isRequired}
111
+ disabled={disabled}
112
+ value={defaultValue}
113
+ {...props}
114
+ />
115
+ </div>
116
+ {description && (
117
+ <span class="sui-input-desc">{description}</span>
118
+ )}
75
119
  </label>
@@ -2,7 +2,8 @@
2
2
  width: 100%;
3
3
  display: flex;
4
4
  flex-direction: column;
5
- gap: .25rem;
5
+ gap: 0.25rem;
6
+ position: relative;
6
7
  }
7
8
  .sui-input-label.disabled {
8
9
  opacity: 0.5;
@@ -13,12 +14,13 @@
13
14
  font-size: 14px;
14
15
  }
15
16
  .sui-input {
16
- padding: .5rem 1rem;
17
+ padding: 0.5rem 1rem;
17
18
  border-radius: var(--radius-md);
18
19
  border: 1px solid var(--border);
19
20
  background: var(--background-step-2);
20
21
  color: var(--text-normal);
21
- transition: all .15s ease;
22
+ transition: all 0.15s ease;
23
+ width: 100%;
22
24
  }
23
25
  .sui-input:hover {
24
26
  background: var(--background-step-3);
@@ -33,6 +35,30 @@
33
35
  border: 1px solid var(--border);
34
36
  }
35
37
  .req-star {
36
- color: var(--danger-base);
38
+ color: var(--danger-vibrant);
37
39
  font-weight: 700;
38
40
  }
41
+ .sui-input-desc {
42
+ font-size: 0.825rem;
43
+ }
44
+ .input-icon {
45
+ position: absolute;
46
+ top: 50%;
47
+ transform: translateY(-50%);
48
+ }
49
+ .input-icon.icon-left {
50
+ left: 0.5rem;
51
+ }
52
+ .input-icon.icon-right {
53
+ right: 0.5rem;
54
+ }
55
+ .sui-input.icon-left {
56
+ padding-left: calc(1.25rem + 20px);
57
+ }
58
+ .sui-input.icon-right {
59
+ padding-right: calc(1.25rem + 20px);
60
+ }
61
+ .sui-input-wrapper {
62
+ position: relative;
63
+ width: 100%;
64
+ }