@studiocms/ui 0.3.2 → 0.4.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 (156) hide show
  1. package/dist/components/Accordion/Accordion.astro +23 -0
  2. package/dist/components/Accordion/Item.astro +21 -0
  3. package/dist/components/Accordion/accordion.css +64 -0
  4. package/dist/components/Accordion/accordion.d.ts +1 -0
  5. package/dist/components/Accordion/accordion.js +70 -0
  6. package/dist/components/Badge/Badge.astro +49 -0
  7. package/dist/components/Badge/badge.css +111 -0
  8. package/dist/components/Breadcrumbs/Breadcrumbs.astro +31 -0
  9. package/dist/components/Breadcrumbs/breadcrumbs.css +15 -0
  10. package/dist/components/Button/Button.astro +75 -0
  11. package/dist/components/Button/button.css +292 -0
  12. package/{src/components → dist/components/Card}/Card.astro +1 -48
  13. package/dist/components/Card/card.css +38 -0
  14. package/dist/components/Center/Center.astro +7 -0
  15. package/dist/components/Center/center.css +8 -0
  16. package/dist/components/Checkbox/Checkbox.astro +95 -0
  17. package/dist/components/Checkbox/checkbox.css +119 -0
  18. package/dist/components/Checkbox/checkbox.d.ts +2 -0
  19. package/dist/components/Checkbox/checkbox.js +20 -0
  20. package/{src/components → dist/components/Divider}/Divider.astro +2 -25
  21. package/dist/components/Divider/divider.css +21 -0
  22. package/dist/components/Dropdown/Dropdown.astro +116 -0
  23. package/dist/components/Dropdown/dropdown.css +180 -0
  24. package/dist/components/Dropdown/dropdown.d.ts +48 -0
  25. package/dist/components/Dropdown/dropdown.js +201 -0
  26. package/dist/components/Footer/Footer.astro +58 -0
  27. package/dist/components/Footer/footer.css +68 -0
  28. package/dist/components/Group/Group.astro +7 -0
  29. package/dist/components/Group/group.css +19 -0
  30. package/{src/utils → dist/components/Icon}/Icon.astro +1 -1
  31. package/{src/utils/iconType.ts → dist/components/Icon/iconType.d.ts} +0 -1
  32. package/dist/components/Icon/iconType.js +0 -0
  33. package/{src/components → dist/components/Input}/Input.astro +2 -48
  34. package/dist/components/Input/input.css +38 -0
  35. package/{src → dist}/components/Modal/Modal.astro +4 -122
  36. package/dist/components/Modal/modal.css +100 -0
  37. package/dist/components/Modal/modal.d.ts +48 -0
  38. package/dist/components/Modal/modal.js +129 -0
  39. package/dist/components/Progress/Progress.astro +21 -0
  40. package/dist/components/Progress/helper.d.ts +13 -0
  41. package/dist/components/Progress/helper.js +32 -0
  42. package/dist/components/Progress/progress.css +29 -0
  43. package/dist/components/Progress/progress.d.ts +1 -0
  44. package/dist/components/Progress/progress.js +10 -0
  45. package/dist/components/RadioGroup/RadioGroup.astro +124 -0
  46. package/dist/components/RadioGroup/radiogroup.css +96 -0
  47. package/dist/components/RadioGroup/radiogroup.d.ts +1 -0
  48. package/dist/components/RadioGroup/radiogroup.js +48 -0
  49. package/{src/components → dist/components/Row}/Row.astro +1 -24
  50. package/dist/components/Row/row.css +18 -0
  51. package/dist/components/SearchSelect/SearchSelect.astro +135 -0
  52. package/dist/components/SearchSelect/searchselect.css +95 -0
  53. package/dist/components/SearchSelect/searchselect.d.ts +6 -0
  54. package/dist/components/SearchSelect/searchselect.js +166 -0
  55. package/dist/components/Select/Select.astro +147 -0
  56. package/dist/components/Select/select.css +110 -0
  57. package/dist/components/Select/select.d.ts +1 -0
  58. package/dist/components/Select/select.js +143 -0
  59. package/dist/components/Sidebar/helpers.d.ts +76 -0
  60. package/dist/components/Sidebar/helpers.js +160 -0
  61. package/{src → dist}/components/Tabs/TabItem.astro +3 -3
  62. package/dist/components/Tabs/Tabs.astro +150 -0
  63. package/dist/components/Tabs/tabs.css +121 -0
  64. package/dist/components/Tabs/tabs.d.ts +1 -0
  65. package/dist/components/Tabs/tabs.js +82 -0
  66. package/{src/components → dist/components/Textarea}/Textarea.astro +2 -61
  67. package/dist/components/Textarea/textarea.css +49 -0
  68. package/dist/components/ThemeToggle/ThemeToggle.astro +21 -0
  69. package/dist/components/ThemeToggle/themetoggle.css +17 -0
  70. package/dist/components/ThemeToggle/themetoggle.d.ts +1 -0
  71. package/dist/components/ThemeToggle/themetoggle.js +4 -0
  72. package/dist/components/Toast/Toaster.astro +69 -0
  73. package/dist/components/Toast/toast.d.ts +8 -0
  74. package/dist/components/Toast/toast.js +9 -0
  75. package/dist/components/Toast/toaster.css +168 -0
  76. package/dist/components/Toast/toaster.d.ts +1 -0
  77. package/dist/components/Toast/toaster.js +160 -0
  78. package/dist/components/Toggle/Toggle.astro +84 -0
  79. package/dist/components/Toggle/toggle.css +93 -0
  80. package/dist/components/Toggle/toggle.d.ts +2 -0
  81. package/dist/components/Toggle/toggle.js +20 -0
  82. package/{src/components → dist/components/User}/User.astro +3 -43
  83. package/dist/components/User/user.css +35 -0
  84. package/dist/css/colors.css +95 -0
  85. package/dist/css/global.css +3 -0
  86. package/dist/css/radii.css +6 -0
  87. package/dist/css/resets.css +46 -0
  88. package/dist/index.d.ts +12 -0
  89. package/dist/index.js +379 -0
  90. package/dist/toolbar/ColorPicker.d.ts +7 -0
  91. package/dist/toolbar/ColorPicker.js +85 -0
  92. package/dist/toolbar/icon.d.ts +1 -0
  93. package/dist/toolbar/icon.js +4 -0
  94. package/dist/toolbar/index.d.ts +2 -0
  95. package/dist/toolbar/index.js +292 -0
  96. package/dist/types/index.d.ts +11 -0
  97. package/dist/types/index.js +0 -0
  98. package/dist/utils/ThemeHelper.d.ts +49 -0
  99. package/dist/utils/ThemeHelper.js +113 -0
  100. package/{src/utils/colors.ts → dist/utils/colors.d.ts} +1 -1
  101. package/dist/utils/colors.js +0 -0
  102. package/dist/utils/generateID.d.ts +2 -0
  103. package/dist/utils/generateID.js +6 -0
  104. package/dist/utils/headers.d.ts +43 -0
  105. package/dist/utils/headers.js +129 -0
  106. package/dist/utils/iconStrings.d.ts +4 -0
  107. package/dist/utils/iconStrings.js +13 -0
  108. package/dist/utils/integration-utils.d.ts +130 -0
  109. package/dist/utils/integration-utils.js +161 -0
  110. package/package.json +26 -10
  111. package/src/components/BaseHead.astro +0 -22
  112. package/src/components/Button.astro +0 -372
  113. package/src/components/Center.astro +0 -16
  114. package/src/components/Checkbox.astro +0 -250
  115. package/src/components/Dropdown/Dropdown.astro +0 -314
  116. package/src/components/Dropdown/dropdown.ts +0 -258
  117. package/src/components/Dropdown/index.ts +0 -2
  118. package/src/components/Footer.astro +0 -137
  119. package/src/components/Modal/index.ts +0 -2
  120. package/src/components/Modal/modal.ts +0 -163
  121. package/src/components/RadioGroup.astro +0 -299
  122. package/src/components/SearchSelect.astro +0 -486
  123. package/src/components/Select.astro +0 -467
  124. package/src/components/Sidebar/helpers.ts +0 -179
  125. package/src/components/Sidebar/index.ts +0 -3
  126. package/src/components/Tabs/Tabs.astro +0 -393
  127. package/src/components/Tabs/index.ts +0 -2
  128. package/src/components/ThemeToggle.astro +0 -46
  129. package/src/components/Toast/Toaster.astro +0 -470
  130. package/src/components/Toast/index.ts +0 -2
  131. package/src/components/Toast/toast.ts +0 -16
  132. package/src/components/Toggle.astro +0 -214
  133. package/src/components/index.ts +0 -27
  134. package/src/components.ts +0 -26
  135. package/src/css/colors.css +0 -106
  136. package/src/css/global.css +0 -2
  137. package/src/css/resets.css +0 -54
  138. package/src/env.d.ts +0 -15
  139. package/src/integration.ts +0 -31
  140. package/src/layouts/RootLayout.astro +0 -33
  141. package/src/layouts/index.ts +0 -2
  142. package/src/layouts.ts +0 -1
  143. package/src/types/index.ts +0 -11
  144. package/src/utils/ThemeHelper.ts +0 -145
  145. package/src/utils/create-resolver.ts +0 -30
  146. package/src/utils/generateID.ts +0 -5
  147. package/src/utils/headers.ts +0 -190
  148. package/src/utils/iconStrings.ts +0 -29
  149. package/src/utils/index.ts +0 -1
  150. package/src/utils/virtual-module-plugin-builder.ts +0 -37
  151. /package/{src → dist}/components/Sidebar/Double.astro +0 -0
  152. /package/{src → dist}/components/Sidebar/Single.astro +0 -0
  153. /package/{src → dist}/icons/Checkmark.astro +0 -0
  154. /package/{src → dist}/icons/ChevronUpDown.astro +0 -0
  155. /package/{src → dist}/icons/User.astro +0 -0
  156. /package/{src → dist}/icons/X-Mark.astro +0 -0
@@ -0,0 +1,116 @@
1
+ ---
2
+ import type { StudioCMSColorway } from '../../utils/colors.js';
3
+ import Icon from '../Icon/Icon.astro';
4
+ import type { HeroIconName } from '../Icon/iconType.js';
5
+ import './dropdown.css';
6
+
7
+ /**
8
+ * An option in the dropdown.
9
+ */
10
+ interface Option {
11
+ /**
12
+ * The label of the option.
13
+ */
14
+ label: string;
15
+ /**
16
+ * The value of the option, returned by the helper when listened for.
17
+ */
18
+ value: string;
19
+ /**
20
+ * Whether the option is disabled.
21
+ */
22
+ disabled?: boolean;
23
+ /**
24
+ * The color of the option.
25
+ */
26
+ color?: StudioCMSColorway;
27
+ /**
28
+ * The icon to display next to the option.
29
+ */
30
+ icon?: HeroIconName;
31
+ /**
32
+ * The href to link to when the option is clicked. When given, the option will be rendered as an anchor tag.
33
+ */
34
+ href?: string;
35
+ }
36
+
37
+ /**
38
+ * The props for the Dropdown component.
39
+ */
40
+ interface Props {
41
+ /**
42
+ * The options to display in the dropdown.
43
+ */
44
+ options: Option[];
45
+ /**
46
+ * Whether the dropdown is disabled.
47
+ */
48
+ disabled?: boolean;
49
+ /**
50
+ * The ID of the dropdown. Required because of the helper.
51
+ */
52
+ id: string;
53
+ /**
54
+ * The alignment of the dropdown, defaults to `center`. Will not work on mobile due to size constraints.
55
+ */
56
+ align?: 'start' | 'center' | 'end';
57
+ /**
58
+ * The type of click with which the dropdown is clicked. Defaults to `left`.
59
+ */
60
+ triggerOn?: 'left' | 'right' | 'both';
61
+ /**
62
+ * The offset of the dropdown from the trigger element in pixels.
63
+ */
64
+ offset?: number;
65
+ }
66
+
67
+ const {
68
+ options,
69
+ disabled = false,
70
+ align = 'center',
71
+ id,
72
+ triggerOn = 'left',
73
+ offset = 0,
74
+ } = Astro.props;
75
+ ---
76
+ <div
77
+ class="sui-dropdown-container"
78
+ class:list={[disabled && 'disabled']}
79
+ data-align={align}
80
+ id={`${id}-container`}
81
+ data-trigger={triggerOn}
82
+ transition:persist
83
+ transition:persist-props
84
+ >
85
+ <div class="sui-dropdown-toggle" id={`${id}-toggle-btn`}>
86
+ <slot />
87
+ </div>
88
+ <ul
89
+ class="sui-dropdown"
90
+ class:list={[align]}
91
+ role="listbox" id={`${id}-dropdown`}
92
+ transition:persist
93
+ transition:persist-props
94
+ aria-labelledby={`${id}-toggle-btn`}
95
+ style={`--offset: ${offset}px;`}
96
+ >
97
+ {options.map(({ value, disabled, color, label, icon, href }) => (
98
+ <li
99
+ class="sui-dropdown-option"
100
+ data-value={value}
101
+ class:list={[disabled && "disabled", icon && "has-icon", color, href && "has-href"]}
102
+ role="option"
103
+ aria-selected="false"
104
+ >
105
+ {icon && (
106
+ <Icon width={24} height={24} name={icon} />
107
+ )}
108
+ {href ? (
109
+ <a href={href} class="sui-dropdown-link">{label}</a>
110
+ ) : (
111
+ <span>{label}</span>
112
+ )}
113
+ </li>
114
+ ))}
115
+ </ul>
116
+ </div>
@@ -0,0 +1,180 @@
1
+ .sui-dropdown-toggle {
2
+ width: fit-content;
3
+ }
4
+ .sui-dropdown-container {
5
+ position: relative;
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: .25rem;
9
+ }
10
+ .sui-dropdown {
11
+ position: absolute;
12
+ list-style: none;
13
+ margin: 0;
14
+ padding: 0;
15
+ flex-direction: column;
16
+ border-radius: var(--radius-md);
17
+ background-color: hsl(var(--background-step-2));
18
+ overflow: hidden;
19
+ left: 0;
20
+ z-index: 90;
21
+ min-width: 200px;
22
+ max-width: min-content;
23
+ height: 0;
24
+ border: none;
25
+ pointer-events: none;
26
+ user-select: none;
27
+ box-shadow: 0px 4px 8px hsl(var(--shadow), 0.5);
28
+ }
29
+ @keyframes pop-up {
30
+ 0% {
31
+ scale: 0.9;
32
+ opacity: 0;
33
+ }
34
+ 100% {
35
+ scale: 1;
36
+ opacity: 1;
37
+ user-select: all;
38
+ }
39
+ }
40
+ @keyframes pop-down {
41
+ 0% {
42
+ scale: 1;
43
+ height: auto;
44
+ border: initial;
45
+ pointer-events: all;
46
+ top: auto;
47
+ bottom: auto;
48
+ border: 1px solid hsl(var(--border));
49
+ opacity: 1;
50
+ user-select: all;
51
+ }
52
+ 99.9999% {
53
+ scale: 0.9;
54
+ height: auto;
55
+ border: initial;
56
+ pointer-events: all;
57
+ bottom: initial;
58
+ top: auto;
59
+ bottom: auto;
60
+ border: 1px solid hsl(var(--border));
61
+ opacity: 0;
62
+ }
63
+ 100% {
64
+ height: 0;
65
+ border: none;
66
+ pointer-events: none;
67
+ user-select: none;
68
+ }
69
+ }
70
+ .sui-dropdown.initialized {
71
+ animation: pop-down .15s ease forwards;
72
+ }
73
+ .sui-dropdown.initialized.active {
74
+ display: flex;
75
+ border: 1px solid hsl(var(--border));
76
+ height: auto;
77
+ pointer-events: all;
78
+ animation: pop-up .15s ease forwards;
79
+ }
80
+ .sui-dropdown.initialized.below {
81
+ top: calc(100% + .25rem + var(--offset)) !important;
82
+ bottom: auto;
83
+ transform-origin: top center;
84
+ }
85
+ .sui-dropdown.below.start {
86
+ transform-origin: top left;
87
+ }
88
+ .sui-dropdown.below.end {
89
+ transform-origin: top right;
90
+ }
91
+ .sui-dropdown.above {
92
+ top: auto;
93
+ bottom: calc(100% + .25rem + var(--offset)) !important;
94
+ transform-origin: bottom center;
95
+ }
96
+ .sui-dropdown.above.start {
97
+ transform-origin: bottom left;
98
+ }
99
+ .sui-dropdown.above.end {
100
+ transform-origin: bottom right;
101
+ }
102
+ .sui-dropdown-option {
103
+ padding: .5rem .75rem;
104
+ cursor: pointer;
105
+ font-size: .975em;
106
+ transition: all .15s ease;
107
+ display: flex;
108
+ flex-direction: row;
109
+ gap: .5rem;
110
+ align-items: center;
111
+ width: 100%;
112
+ white-space: normal;
113
+ user-select: none;
114
+ }
115
+ .sui-dropdown-option:hover,
116
+ .sui-dropdown-option:focus,
117
+ .sui-dropdown-option.focused {
118
+ background-color: hsl(var(--background-step-3));
119
+ }
120
+ .sui-dropdown-option.has-href {
121
+ padding: 0;
122
+ }
123
+ .sui-dropdown-link {
124
+ padding: .5rem .75rem;
125
+ width: 100%;
126
+ text-decoration: none;
127
+ color: hsl(var(--text-normal));
128
+ }
129
+ .sui-dropdown-option.primary {
130
+ color: hsl(var(--primary-base));
131
+ }
132
+ .sui-dropdown-option.primary:hover {
133
+ background-color: hsl(var(--primary-base));
134
+ color: hsl(var(--text-inverted));
135
+ }
136
+ .sui-dropdown-option.success {
137
+ color: hsl(var(--success-base));
138
+ }
139
+ .sui-dropdown-option.success:hover {
140
+ background-color: hsl(var(--success-base));
141
+ color: hsl(var(--text-dark));
142
+ }
143
+ .sui-dropdown-option.warning {
144
+ color: hsl(var(--warning-base));
145
+ }
146
+ .sui-dropdown-option.warning:hover {
147
+ background-color: hsl(var(--warning-base));
148
+ color: hsl(var(--text-dark));
149
+ }
150
+ .sui-dropdown-option.danger {
151
+ color: hsl(var(--danger-base));
152
+ }
153
+ .sui-dropdown-option.danger:hover {
154
+ background-color: hsl(var(--danger-base));
155
+ color: hsl(var(--text-light));
156
+ }
157
+ .sui-dropdown-option.info {
158
+ color: hsl(var(--info-base));
159
+ }
160
+ .sui-dropdown-option.info:hover {
161
+ background-color: hsl(var(--info-base));
162
+ color: hsl(var(--text-light));
163
+ }
164
+ .sui-dropdown-option.mono {
165
+ color: hsl(var(--text-normal));
166
+ }
167
+ .sui-dropdown-option.mono:hover {
168
+ background-color: hsl(var(--mono-base));
169
+ color: hsl(var(--text-inverted));
170
+ }
171
+ .sui-dropdown-option.disabled {
172
+ pointer-events: none;
173
+ color: hsl(var(--text-muted));
174
+ }
175
+ .sui-dropdown-option.end {
176
+ justify-content: space-between;
177
+ }
178
+ .sui-dropdown-option.has-icon {
179
+ padding-left: .5rem;
180
+ }
@@ -0,0 +1,48 @@
1
+ declare class DropdownHelper {
2
+ private container;
3
+ private toggleEl;
4
+ private dropdown;
5
+ private alignment;
6
+ private triggerOn;
7
+ private fullWidth;
8
+ private focusIndex;
9
+ active: boolean;
10
+ /**
11
+ * A helper function to interact with dropdowns.
12
+ * @param id The ID of the dropdown.
13
+ * @param fullWidth Whether the dropdown should be full width. Not needed normally.
14
+ */
15
+ constructor(id: string, fullWidth?: boolean);
16
+ /**
17
+ * Registers a click callback for the dropdown options. Whenever one of the options
18
+ * is clicked, the callback will be called with the value of the option.
19
+ * @param func The callback function.
20
+ */
21
+ registerClickCallback: (func: (value: string) => void) => void;
22
+ /**
23
+ * Sets up all listeners for the dropdown.
24
+ */
25
+ private initialBehaviorRegistration;
26
+ /**
27
+ * Registers callbacks to hide the dropdown when an option is clicked.
28
+ */
29
+ private initialOptClickRegistration;
30
+ /**
31
+ * A function to toggle the dropdown.
32
+ */
33
+ toggle: () => void;
34
+ /**
35
+ * A function to hide the dropdown.
36
+ */
37
+ hide: () => void;
38
+ /**
39
+ * A function to show the dropdown.
40
+ */
41
+ show: () => void;
42
+ /**
43
+ * A jQuery-like function to hide the dropdown when clicking outside of it.
44
+ * @param element The element to hide when clicking outside of it.
45
+ */
46
+ private hideOnClickOutside;
47
+ }
48
+ export { DropdownHelper };
@@ -0,0 +1,201 @@
1
+ class DropdownHelper {
2
+ container;
3
+ toggleEl;
4
+ dropdown;
5
+ alignment;
6
+ triggerOn;
7
+ fullWidth = false;
8
+ focusIndex = -1;
9
+ active = false;
10
+ /**
11
+ * A helper function to interact with dropdowns.
12
+ * @param id The ID of the dropdown.
13
+ * @param fullWidth Whether the dropdown should be full width. Not needed normally.
14
+ */
15
+ constructor(id, fullWidth) {
16
+ this.container = document.getElementById(`${id}-container`);
17
+ if (!this.container) {
18
+ throw new Error(`Unable to find dropdown with ID ${id}.`);
19
+ }
20
+ this.alignment = this.container.dataset.align;
21
+ this.triggerOn = this.container.dataset.trigger;
22
+ this.toggleEl = document.getElementById(`${id}-toggle-btn`);
23
+ this.dropdown = document.getElementById(`${id}-dropdown`);
24
+ if (fullWidth) this.fullWidth = true;
25
+ this.hideOnClickOutside(this.container);
26
+ this.initialBehaviorRegistration();
27
+ this.initialOptClickRegistration();
28
+ }
29
+ /**
30
+ * Registers a click callback for the dropdown options. Whenever one of the options
31
+ * is clicked, the callback will be called with the value of the option.
32
+ * @param func The callback function.
33
+ */
34
+ registerClickCallback = (func) => {
35
+ const dropdownOpts = this.dropdown.querySelectorAll("li");
36
+ for (const opt of dropdownOpts) {
37
+ opt.removeEventListener("click", this.hide);
38
+ opt.addEventListener("click", () => {
39
+ func(opt.dataset.value || "");
40
+ this.hide();
41
+ });
42
+ }
43
+ };
44
+ /**
45
+ * Sets up all listeners for the dropdown.
46
+ */
47
+ initialBehaviorRegistration = () => {
48
+ window.addEventListener("scroll", this.hide);
49
+ document.addEventListener("keydown", (e) => {
50
+ if (e.key === "Escape") this.hide();
51
+ });
52
+ document.addEventListener("astro:before-preparation", () => {
53
+ this.dropdown.classList.remove("initialized");
54
+ });
55
+ if (this.triggerOn === "left") {
56
+ this.toggleEl.addEventListener("click", this.toggle);
57
+ } else if (this.triggerOn === "both") {
58
+ this.toggleEl.addEventListener("click", this.toggle);
59
+ this.toggleEl.addEventListener("contextmenu", (e) => {
60
+ e.preventDefault();
61
+ this.toggle();
62
+ });
63
+ } else {
64
+ this.toggleEl.addEventListener("contextmenu", (e) => {
65
+ e.preventDefault();
66
+ this.toggle();
67
+ });
68
+ }
69
+ this.toggleEl.addEventListener("keydown", (e) => {
70
+ if (!this.active) return;
71
+ if (e.key === "Enter") {
72
+ e.preventDefault();
73
+ const focused = this.dropdown.querySelector("li.focused");
74
+ if (!focused) {
75
+ this.hide();
76
+ return;
77
+ }
78
+ focused.click();
79
+ }
80
+ if (e.key === "ArrowDown") {
81
+ e.preventDefault();
82
+ this.focusIndex = this.focusIndex === this.dropdown.children.length - 1 ? 0 : this.focusIndex + 1;
83
+ }
84
+ if (e.key === "ArrowUp") {
85
+ e.preventDefault();
86
+ this.focusIndex = this.focusIndex === 0 ? this.dropdown.children.length - 1 : this.focusIndex - 1;
87
+ }
88
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
89
+ if (this.focusIndex > this.dropdown.children.length - 1) {
90
+ this.focusIndex = 0;
91
+ }
92
+ this.dropdown.querySelector("li.focused")?.classList.remove("focused");
93
+ const newFocus = this.dropdown.children[this.focusIndex];
94
+ if (!newFocus) return;
95
+ newFocus.classList.add("focused");
96
+ newFocus.focus();
97
+ }
98
+ });
99
+ };
100
+ /**
101
+ * Registers callbacks to hide the dropdown when an option is clicked.
102
+ */
103
+ initialOptClickRegistration = () => {
104
+ const dropdownOpts = this.dropdown.querySelectorAll("li");
105
+ for (const opt of dropdownOpts) {
106
+ opt.addEventListener("click", this.hide);
107
+ }
108
+ };
109
+ /**
110
+ * A function to toggle the dropdown.
111
+ */
112
+ toggle = () => {
113
+ if (this.active) {
114
+ this.hide();
115
+ return;
116
+ }
117
+ this.show();
118
+ };
119
+ /**
120
+ * A function to hide the dropdown.
121
+ */
122
+ hide = () => {
123
+ this.dropdown.classList.remove("active");
124
+ this.active = false;
125
+ this.focusIndex = -1;
126
+ this.dropdown.querySelector("li.focused")?.classList.remove("focused");
127
+ setTimeout(() => this.dropdown.classList.remove("above", "below"), 200);
128
+ };
129
+ /**
130
+ * A function to show the dropdown.
131
+ */
132
+ show = () => {
133
+ const isMobile = window.matchMedia("screen and (max-width: 840px)").matches;
134
+ const {
135
+ bottom,
136
+ left,
137
+ right,
138
+ width: parentWidth,
139
+ x,
140
+ y,
141
+ height
142
+ } = this.toggleEl.getBoundingClientRect();
143
+ const { width: dropdownWidth } = this.dropdown.getBoundingClientRect();
144
+ const optionHeight = 43.28;
145
+ const totalBorderSize = 2;
146
+ const margin = 4;
147
+ const dropdownHeight = this.dropdown.children.length * optionHeight + totalBorderSize + margin;
148
+ const CustomRect = {
149
+ top: bottom + margin,
150
+ left,
151
+ right,
152
+ bottom: bottom + margin + dropdownHeight,
153
+ width: isMobile || this.fullWidth ? parentWidth : dropdownWidth,
154
+ // Account for scaling of animation
155
+ height: dropdownHeight,
156
+ x,
157
+ y: y + height + margin
158
+ };
159
+ this.active = true;
160
+ if (isMobile || this.fullWidth) {
161
+ this.dropdown.style.maxWidth = `${parentWidth}px`;
162
+ this.dropdown.style.minWidth = "unset";
163
+ this.dropdown.style.width = `${parentWidth}px`;
164
+ this.dropdown.style.left = `calc(${parentWidth / 2}px - ${CustomRect.width / 2}px)`;
165
+ } else {
166
+ if (this.alignment === "end") {
167
+ this.dropdown.style.left = `calc(${parentWidth}px - ${CustomRect.width}px)`;
168
+ }
169
+ if (this.alignment === "center") {
170
+ this.dropdown.style.left = `calc(${parentWidth / 2}px - ${CustomRect.width / 2}px)`;
171
+ }
172
+ }
173
+ if (!this.dropdown.classList.contains("initialized")) {
174
+ this.dropdown.classList.add("initialized");
175
+ }
176
+ if (CustomRect.top >= 0 && CustomRect.left >= 0 && CustomRect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && CustomRect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
177
+ this.dropdown.classList.add("active", "below");
178
+ this.focusIndex = -1;
179
+ } else {
180
+ this.dropdown.classList.add("active", "above");
181
+ this.focusIndex = this.dropdown.children.length;
182
+ }
183
+ };
184
+ /**
185
+ * A jQuery-like function to hide the dropdown when clicking outside of it.
186
+ * @param element The element to hide when clicking outside of it.
187
+ */
188
+ hideOnClickOutside = (element) => {
189
+ const outsideClickListener = (event) => {
190
+ if (!event.target) return;
191
+ if (!element.contains(event.target) && isVisible(element) && this.active === true) {
192
+ this.hide();
193
+ }
194
+ };
195
+ document.addEventListener("click", outsideClickListener);
196
+ };
197
+ }
198
+ const isVisible = (elem) => !!elem && !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length);
199
+ export {
200
+ DropdownHelper
201
+ };
@@ -0,0 +1,58 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+ import './footer.css';
4
+
5
+ interface FooterLink {
6
+ /**
7
+ * The label of the link.
8
+ */
9
+ label: string;
10
+ /**
11
+ * The href of the link.
12
+ */
13
+ href: string;
14
+ }
15
+
16
+ /**
17
+ * The props for the footer component.
18
+ */
19
+ interface Props extends HTMLAttributes<'footer'> {
20
+ /**
21
+ * The links to display in the footer.
22
+ */
23
+ links: {
24
+ [label: string]: FooterLink[];
25
+ };
26
+ /**
27
+ * The copyright text to display in the footer.
28
+ */
29
+ copyright: string;
30
+ }
31
+
32
+ const { copyright, class: className, links, ...props } = Astro.props;
33
+ ---
34
+
35
+ <footer {...props} class={`${className} sui-footer`}>
36
+ <div class="upper">
37
+ <div>
38
+ <slot name="brand" />
39
+ </div>
40
+ <div class="links">
41
+ {Object.keys(links).map((groupLabel) => (
42
+ <ul>
43
+ <li class="sui-footer-link-label">{groupLabel}</li>
44
+ {links[groupLabel]!.map((item) => (
45
+ <li>
46
+ <a href={item.href}>{item.label}</a>
47
+ </li>
48
+ ))}
49
+ </ul>
50
+ ))}
51
+ </div>
52
+ </div>
53
+ <hr class="separator" />
54
+ <div class="lower">
55
+ <span class="copyright-span">{copyright}</span>
56
+ <slot name="socials" />
57
+ </div>
58
+ </footer>
@@ -0,0 +1,68 @@
1
+ footer.sui-footer {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 2rem;
5
+ background-color: hsl(var(--background-step-1));
6
+ padding: 2rem 10vw;
7
+ color: hsl(var(--text-normal)) !important;
8
+ }
9
+ .upper,
10
+ .lower {
11
+ display: flex;
12
+ flex-direction: row;
13
+ justify-content: space-between;
14
+ }
15
+ .links {
16
+ display: flex;
17
+ justify-content: flex-end;
18
+ flex-direction: row;
19
+ flex-wrap: wrap;
20
+ gap: 2rem;
21
+ }
22
+ .links ul {
23
+ list-style-type: none;
24
+ margin: 0 !important;
25
+ padding: 0 !important;
26
+ }
27
+ .links ul li,
28
+ .links ul li * {
29
+ color: hsl(var(--text-normal)) !important;
30
+ width: fit-content;
31
+ }
32
+ .links ul li:has(a):hover {
33
+ text-decoration: underline;
34
+ }
35
+ .sui-footer-link-label {
36
+ font-size: 1.125em;
37
+ font-weight: 700;
38
+ }
39
+ .separator {
40
+ height: 1px;
41
+ width: 100%;
42
+ border: none;
43
+ background: hsl(var(--border));
44
+ }
45
+ .lower {
46
+ align-items: center;
47
+ flex-wrap: wrap;
48
+ gap: 1rem;
49
+ }
50
+ @media screen and (max-width: 1440px) {
51
+ footer.sui-footer {
52
+ padding: 2rem;
53
+ }
54
+ }
55
+ @media screen and (max-width: 1280px) {
56
+ .upper {
57
+ flex-direction: column;
58
+ gap: 2rem;
59
+ }
60
+ .links {
61
+ justify-content: flex-start;
62
+ }
63
+ }
64
+ @media screen and (max-width: 640px) {
65
+ .links ul {
66
+ width: calc(50% - 1rem);
67
+ }
68
+ }
@@ -0,0 +1,7 @@
1
+ ---
2
+ import './group.css';
3
+ ---
4
+
5
+ <div class="sui-group">
6
+ <slot />
7
+ </div>
@@ -0,0 +1,19 @@
1
+ .sui-group {
2
+ display: flex;
3
+ flex-direction: row;
4
+ align-items: center;
5
+ }
6
+ .sui-group .sui-button:not(:first-child):not(:last-child),
7
+ .sui-group .sui-badge:not(:first-child):not(:last-child) {
8
+ border-radius: 0;
9
+ }
10
+ .sui-group .sui-button:first-child,
11
+ .sui-group .sui-badge:first-child {
12
+ border-top-right-radius: 0;
13
+ border-bottom-right-radius: 0;
14
+ }
15
+ .sui-group .sui-button:last-child,
16
+ .sui-group .sui-badge:last-child {
17
+ border-top-left-radius: 0;
18
+ border-bottom-left-radius: 0;
19
+ }
@@ -2,7 +2,7 @@
2
2
  import { icons } from '@iconify-json/heroicons';
3
3
  import { getIconData, iconToSVG, replaceIDs } from '@iconify/utils';
4
4
  import type { HTMLAttributes } from 'astro/types';
5
- import { type HeroIconName } from './iconType';
5
+ import { type HeroIconName } from './iconType.js';
6
6
 
7
7
  interface SVGAttributes extends HTMLAttributes<'svg'> {
8
8
  // biome-ignore lint/suspicious/noExplicitAny: Allow any string index