@starwind-ui/core 1.5.0 → 1.6.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.
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/src/components/dialog/Dialog.astro +20 -4
- package/dist/src/components/dropdown/Dropdown.astro +32 -15
- package/dist/src/components/dropdown/DropdownTrigger.astro +2 -3
- package/dist/src/components/select/Select.astro +30 -2
- package/dist/src/components/select/SelectContent.astro +19 -8
- package/dist/src/components/table/Table.astro +18 -0
- package/dist/src/components/table/TableBody.astro +16 -0
- package/dist/src/components/table/TableCaption.astro +16 -0
- package/dist/src/components/table/TableCell.astro +16 -0
- package/dist/src/components/table/TableFoot.astro +16 -0
- package/dist/src/components/table/TableHead.astro +16 -0
- package/dist/src/components/table/TableHeader.astro +16 -0
- package/dist/src/components/table/TableRow.astro +16 -0
- package/dist/src/components/table/index.ts +21 -0
- package/dist/src/components/tabs/Tabs.astro +23 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14,14 +14,15 @@ var registry_default = {
|
|
|
14
14
|
{ name: "button", type: "component", version: "2.0.1", dependencies: [] },
|
|
15
15
|
{ name: "card", type: "component", version: "1.1.0", dependencies: [] },
|
|
16
16
|
{ name: "checkbox", type: "component", version: "1.2.0", dependencies: [] },
|
|
17
|
-
{ name: "dialog", type: "component", version: "1.1.
|
|
18
|
-
{ name: "dropdown", type: "component", version: "1.0.
|
|
17
|
+
{ name: "dialog", type: "component", version: "1.1.2", dependencies: [] },
|
|
18
|
+
{ name: "dropdown", type: "component", version: "1.0.2", dependencies: [] },
|
|
19
19
|
{ name: "input", type: "component", version: "1.1.1", dependencies: [] },
|
|
20
20
|
{ name: "label", type: "component", version: "1.1.1", dependencies: [] },
|
|
21
21
|
{ name: "pagination", type: "component", version: "2.0.1", dependencies: [] },
|
|
22
|
-
{ name: "select", type: "component", version: "1.
|
|
22
|
+
{ name: "select", type: "component", version: "1.3.0", dependencies: [] },
|
|
23
23
|
{ name: "switch", type: "component", version: "1.1.0", dependencies: [] },
|
|
24
|
-
{ name: "
|
|
24
|
+
{ name: "table", type: "component", version: "1.0.0", dependencies: [] },
|
|
25
|
+
{ name: "tabs", type: "component", version: "1.2.0", dependencies: [] },
|
|
25
26
|
{ name: "textarea", type: "component", version: "1.1.1", dependencies: [] },
|
|
26
27
|
{ name: "tooltip", type: "component", version: "1.1.1", dependencies: [] }
|
|
27
28
|
]
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/registry.json"],"sourcesContent":["import { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport componentRegistry from \"./registry.json\" with { type: \"json\" };\n\n/**\n * Component metadata interface describing a Starwind UI component\n */\nexport interface ComponentMeta {\n\tname: string;\n\tversion: string;\n\ttype: \"component\";\n\tdependencies: string[];\n}\n\n/**\n * Registry interface containing all available components\n */\nexport interface Registry {\n\tcomponents: ComponentMeta[];\n}\n\nconst __dirname = fileURLToPath(new URL(\".\", import.meta.url));\n\n/**\n * Get the absolute path to a component file\n * @param {string} componentName - The name of the component\n * @param {string} fileName - The name of the file within the component\n * @returns {string} The absolute path to the component file\n */\nexport const getComponentPath = (componentName: string, fileName: string): string => {\n\t// In production (when installed as a dependency), the components will be in dist/src/components\n\t// In development, they will be in src/components\n\tconst componentsDir = __dirname.includes(\"dist\") ? \"src/components\" : \"src/components\";\n\treturn join(__dirname, componentsDir, componentName, fileName);\n};\n\n/**\n * Map of all components and their metadata from registry\n */\nexport const registry = componentRegistry.components as ComponentMeta[];\n","{\n\t\"$schema\": \"https://starwind.dev/registry-schema.json\",\n\t\"components\": [\n\t\t{ \"name\": \"accordion\", \"type\": \"component\", \"version\": \"1.1.0\", \"dependencies\": [] },\n\t\t{ \"name\": \"alert\", \"type\": \"component\", \"version\": \"1.1.1\", \"dependencies\": [] },\n\t\t{ \"name\": \"avatar\", \"type\": \"component\", \"version\": \"1.1.1\", \"dependencies\": [] },\n\t\t{ \"name\": \"badge\", \"type\": \"component\", \"version\": \"1.1.1\", \"dependencies\": [] },\n\t\t{ \"name\": \"breadcrumb\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n\t\t{ \"name\": \"button\", \"type\": \"component\", \"version\": \"2.0.1\", \"dependencies\": [] },\n\t\t{ \"name\": \"card\", \"type\": \"component\", \"version\": \"1.1.0\", \"dependencies\": [] },\n\t\t{ \"name\": \"checkbox\", \"type\": \"component\", \"version\": \"1.2.0\", \"dependencies\": [] },\n\t\t{ \"name\": \"dialog\", \"type\": \"component\", \"version\": \"1.1.
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/registry.json"],"sourcesContent":["import { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport componentRegistry from \"./registry.json\" with { type: \"json\" };\n\n/**\n * Component metadata interface describing a Starwind UI component\n */\nexport interface ComponentMeta {\n\tname: string;\n\tversion: string;\n\ttype: \"component\";\n\tdependencies: string[];\n}\n\n/**\n * Registry interface containing all available components\n */\nexport interface Registry {\n\tcomponents: ComponentMeta[];\n}\n\nconst __dirname = fileURLToPath(new URL(\".\", import.meta.url));\n\n/**\n * Get the absolute path to a component file\n * @param {string} componentName - The name of the component\n * @param {string} fileName - The name of the file within the component\n * @returns {string} The absolute path to the component file\n */\nexport const getComponentPath = (componentName: string, fileName: string): string => {\n\t// In production (when installed as a dependency), the components will be in dist/src/components\n\t// In development, they will be in src/components\n\tconst componentsDir = __dirname.includes(\"dist\") ? \"src/components\" : \"src/components\";\n\treturn join(__dirname, componentsDir, componentName, fileName);\n};\n\n/**\n * Map of all components and their metadata from registry\n */\nexport const registry = componentRegistry.components as ComponentMeta[];\n","{\n\t\"$schema\": \"https://starwind.dev/registry-schema.json\",\n\t\"components\": [\n\t\t{ \"name\": \"accordion\", \"type\": \"component\", \"version\": \"1.1.0\", \"dependencies\": [] },\n\t\t{ \"name\": \"alert\", \"type\": \"component\", \"version\": \"1.1.1\", \"dependencies\": [] },\n\t\t{ \"name\": \"avatar\", \"type\": \"component\", \"version\": \"1.1.1\", \"dependencies\": [] },\n\t\t{ \"name\": \"badge\", \"type\": \"component\", \"version\": \"1.1.1\", \"dependencies\": [] },\n\t\t{ \"name\": \"breadcrumb\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n\t\t{ \"name\": \"button\", \"type\": \"component\", \"version\": \"2.0.1\", \"dependencies\": [] },\n\t\t{ \"name\": \"card\", \"type\": \"component\", \"version\": \"1.1.0\", \"dependencies\": [] },\n\t\t{ \"name\": \"checkbox\", \"type\": \"component\", \"version\": \"1.2.0\", \"dependencies\": [] },\n\t\t{ \"name\": \"dialog\", \"type\": \"component\", \"version\": \"1.1.2\", \"dependencies\": [] },\n\t\t{ \"name\": \"dropdown\", \"type\": \"component\", \"version\": \"1.0.2\", \"dependencies\": [] },\n\t\t{ \"name\": \"input\", \"type\": \"component\", \"version\": \"1.1.1\", \"dependencies\": [] },\n\t\t{ \"name\": \"label\", \"type\": \"component\", \"version\": \"1.1.1\", \"dependencies\": [] },\n\t\t{ \"name\": \"pagination\", \"type\": \"component\", \"version\": \"2.0.1\", \"dependencies\": [] },\n\t\t{ \"name\": \"select\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n\t\t{ \"name\": \"switch\", \"type\": \"component\", \"version\": \"1.1.0\", \"dependencies\": [] },\n\t\t{ \"name\": \"table\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n\t\t{ \"name\": \"tabs\", \"type\": \"component\", \"version\": \"1.2.0\", \"dependencies\": [] },\n\t\t{ \"name\": \"textarea\", \"type\": \"component\", \"version\": \"1.1.1\", \"dependencies\": [] },\n\t\t{ \"name\": \"tooltip\", \"type\": \"component\", \"version\": \"1.1.1\", \"dependencies\": [] }\n\t]\n}\n"],"mappings":";AAAA,SAAS,YAAY;AACrB,SAAS,qBAAqB;;;ACD9B;AAAA,EACC,SAAW;AAAA,EACX,YAAc;AAAA,IACb,EAAE,MAAQ,aAAa,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IACnF,EAAE,MAAQ,SAAS,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IAC/E,EAAE,MAAQ,UAAU,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IAChF,EAAE,MAAQ,SAAS,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IAC/E,EAAE,MAAQ,cAAc,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IACpF,EAAE,MAAQ,UAAU,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IAChF,EAAE,MAAQ,QAAQ,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IAC9E,EAAE,MAAQ,YAAY,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IAClF,EAAE,MAAQ,UAAU,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IAChF,EAAE,MAAQ,YAAY,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IAClF,EAAE,MAAQ,SAAS,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IAC/E,EAAE,MAAQ,SAAS,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IAC/E,EAAE,MAAQ,cAAc,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IACpF,EAAE,MAAQ,UAAU,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IAChF,EAAE,MAAQ,UAAU,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IAChF,EAAE,MAAQ,SAAS,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IAC/E,EAAE,MAAQ,QAAQ,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IAC9E,EAAE,MAAQ,YAAY,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,IAClF,EAAE,MAAQ,WAAW,MAAQ,aAAa,SAAW,SAAS,cAAgB,CAAC,EAAE;AAAA,EAClF;AACD;;;ADFA,IAAM,YAAY,cAAc,IAAI,IAAI,KAAK,YAAY,GAAG,CAAC;AAQtD,IAAM,mBAAmB,CAAC,eAAuB,aAA6B;AAGpF,QAAM,gBAAgB,UAAU,SAAS,MAAM,IAAI,mBAAmB;AACtE,SAAO,KAAK,WAAW,eAAe,eAAe,QAAQ;AAC9D;AAKO,IAAM,WAAW,iBAAkB;","names":[]}
|
|
@@ -90,7 +90,11 @@ const { class: className, ...rest } = Astro.props;
|
|
|
90
90
|
// Add click handlers to all close buttons
|
|
91
91
|
this.closeButtons?.forEach((button) => {
|
|
92
92
|
button.addEventListener("click", () => {
|
|
93
|
-
this
|
|
93
|
+
// Only close if this is the topmost dialog
|
|
94
|
+
const openDialogs = document.querySelectorAll("dialog[open]");
|
|
95
|
+
if (openDialogs.length > 0 && openDialogs[openDialogs.length - 1] === this.dialog) {
|
|
96
|
+
this.close();
|
|
97
|
+
}
|
|
94
98
|
});
|
|
95
99
|
});
|
|
96
100
|
|
|
@@ -104,7 +108,11 @@ const { class: className, ...rest } = Astro.props;
|
|
|
104
108
|
e.clientY <= dialogDimensions.bottom;
|
|
105
109
|
|
|
106
110
|
if (!clickedInDialog) {
|
|
107
|
-
this
|
|
111
|
+
// Only close if this is the topmost dialog
|
|
112
|
+
const openDialogs = document.querySelectorAll("dialog[open]");
|
|
113
|
+
if (openDialogs.length > 0 && openDialogs[openDialogs.length - 1] === this.dialog) {
|
|
114
|
+
this.close();
|
|
115
|
+
}
|
|
108
116
|
}
|
|
109
117
|
});
|
|
110
118
|
|
|
@@ -113,7 +121,11 @@ const { class: className, ...rest } = Astro.props;
|
|
|
113
121
|
if (e.key === "Escape") {
|
|
114
122
|
// prevent default dialog closing behavior so we can add closing animation
|
|
115
123
|
e.preventDefault();
|
|
116
|
-
this
|
|
124
|
+
// Only close if this is the topmost dialog
|
|
125
|
+
const openDialogs = document.querySelectorAll("dialog[open]");
|
|
126
|
+
if (openDialogs.length > 0 && openDialogs[openDialogs.length - 1] === this.dialog) {
|
|
127
|
+
this.close();
|
|
128
|
+
}
|
|
117
129
|
}
|
|
118
130
|
});
|
|
119
131
|
|
|
@@ -130,7 +142,11 @@ const { class: className, ...rest } = Astro.props;
|
|
|
130
142
|
*/
|
|
131
143
|
if (form.method === "dialog") {
|
|
132
144
|
e.preventDefault();
|
|
133
|
-
this
|
|
145
|
+
// Only close if this is the topmost dialog
|
|
146
|
+
const openDialogs = document.querySelectorAll("dialog[open]");
|
|
147
|
+
if (openDialogs.length > 0 && openDialogs[openDialogs.length - 1] === this.dialog) {
|
|
148
|
+
this.close();
|
|
149
|
+
}
|
|
134
150
|
}
|
|
135
151
|
});
|
|
136
152
|
});
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import type { HTMLAttributes } from "astro/types";
|
|
3
3
|
|
|
4
4
|
type Props = HTMLAttributes<"div"> & {
|
|
5
|
-
name?: string;
|
|
6
5
|
/**
|
|
7
6
|
* When true, the dropdown will open on hover in addition to click
|
|
8
7
|
*/
|
|
@@ -16,12 +15,11 @@ type Props = HTMLAttributes<"div"> & {
|
|
|
16
15
|
children: any;
|
|
17
16
|
};
|
|
18
17
|
|
|
19
|
-
const { class: className,
|
|
18
|
+
const { class: className, openOnHover = false, closeDelay = 200, ...rest } = Astro.props;
|
|
20
19
|
---
|
|
21
20
|
|
|
22
21
|
<div
|
|
23
22
|
class:list={["starwind-dropdown", "relative", className]}
|
|
24
|
-
data-name={name}
|
|
25
23
|
data-open-on-hover={openOnHover ? "true" : undefined}
|
|
26
24
|
data-close-delay={closeDelay}
|
|
27
25
|
{...rest}
|
|
@@ -37,6 +35,7 @@ const { class: className, name, openOnHover = false, closeDelay = 200, ...rest }
|
|
|
37
35
|
private items: HTMLElement[] = [];
|
|
38
36
|
private currentFocusIndex: number = -1;
|
|
39
37
|
private isOpen: boolean = false;
|
|
38
|
+
private isClosing: boolean = false;
|
|
40
39
|
private animationDuration = 150;
|
|
41
40
|
private openOnHover: boolean;
|
|
42
41
|
private closeDelay: number;
|
|
@@ -117,9 +116,24 @@ const { class: className, name, openOnHover = false, closeDelay = 200, ...rest }
|
|
|
117
116
|
}
|
|
118
117
|
});
|
|
119
118
|
|
|
120
|
-
// Close dropdown when clicking outside
|
|
119
|
+
// Close dropdown when clicking outside for mouse
|
|
121
120
|
document.addEventListener("pointerdown", (e) => {
|
|
122
121
|
if (this.isOpen && !this.dropdown.contains(e.target as Node)) {
|
|
122
|
+
// only call handler if it's the left button (mousedown gets triggered by all mouse buttons)
|
|
123
|
+
// but not when the control key is pressed (avoiding MacOS right click); also not for touch
|
|
124
|
+
// devices because that would open the menu on scroll. (pen devices behave as touch on iOS).
|
|
125
|
+
if (e.button === 0 && e.ctrlKey === false && e.pointerType === "mouse") {
|
|
126
|
+
this.closeDropdown();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Handle click outside select content to close for mobile
|
|
132
|
+
document.addEventListener("click", (e) => {
|
|
133
|
+
if (
|
|
134
|
+
!(this.trigger?.contains(e.target as Node) || this.content?.contains(e.target as Node)) &&
|
|
135
|
+
this.isOpen
|
|
136
|
+
) {
|
|
123
137
|
this.closeDropdown();
|
|
124
138
|
}
|
|
125
139
|
});
|
|
@@ -142,6 +156,7 @@ const { class: className, name, openOnHover = false, closeDelay = 200, ...rest }
|
|
|
142
156
|
if (item && !(item as HTMLElement).hasAttribute("data-disabled")) {
|
|
143
157
|
// Close the dropdown after item selection
|
|
144
158
|
this.closeDropdown();
|
|
159
|
+
console.log("click closing");
|
|
145
160
|
}
|
|
146
161
|
});
|
|
147
162
|
|
|
@@ -162,8 +177,9 @@ const { class: className, name, openOnHover = false, closeDelay = 200, ...rest }
|
|
|
162
177
|
});
|
|
163
178
|
|
|
164
179
|
if (this.openOnHover) {
|
|
165
|
-
|
|
166
|
-
|
|
180
|
+
this.trigger.addEventListener("pointerenter", (e) => {
|
|
181
|
+
if (e.pointerType !== "mouse") return;
|
|
182
|
+
if (this.isClosing) return;
|
|
167
183
|
if (!this.isOpen) {
|
|
168
184
|
this.openDropdown();
|
|
169
185
|
} else {
|
|
@@ -172,20 +188,18 @@ const { class: className, name, openOnHover = false, closeDelay = 200, ...rest }
|
|
|
172
188
|
}
|
|
173
189
|
});
|
|
174
190
|
|
|
175
|
-
|
|
176
|
-
|
|
191
|
+
this.dropdown.addEventListener("pointerleave", (e) => {
|
|
192
|
+
if (e.pointerType !== "mouse") return;
|
|
177
193
|
if (this.isOpen) {
|
|
178
194
|
this.closeDropdownDelayed();
|
|
179
195
|
}
|
|
180
196
|
});
|
|
181
197
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
});
|
|
188
|
-
}
|
|
198
|
+
this.content.addEventListener("pointerenter", (e) => {
|
|
199
|
+
if (e.pointerType !== "mouse") return;
|
|
200
|
+
// If the user moves the mouse to the content, cancel the close timer
|
|
201
|
+
this.clearCloseTimer();
|
|
202
|
+
});
|
|
189
203
|
}
|
|
190
204
|
}
|
|
191
205
|
|
|
@@ -252,6 +266,7 @@ const { class: className, name, openOnHover = false, closeDelay = 200, ...rest }
|
|
|
252
266
|
}
|
|
253
267
|
|
|
254
268
|
private openDropdown() {
|
|
269
|
+
if (this.isClosing) return;
|
|
255
270
|
if (!this.content || !this.trigger || this.trigger.disabled) return;
|
|
256
271
|
|
|
257
272
|
this.isOpen = true;
|
|
@@ -271,6 +286,7 @@ const { class: className, name, openOnHover = false, closeDelay = 200, ...rest }
|
|
|
271
286
|
private closeDropdown() {
|
|
272
287
|
if (!this.content || !this.trigger) return;
|
|
273
288
|
|
|
289
|
+
this.isClosing = true;
|
|
274
290
|
this.isOpen = false;
|
|
275
291
|
this.content.setAttribute("data-state", "closed");
|
|
276
292
|
|
|
@@ -284,6 +300,7 @@ const { class: className, name, openOnHover = false, closeDelay = 200, ...rest }
|
|
|
284
300
|
setTimeout(() => {
|
|
285
301
|
if (!this.content) return;
|
|
286
302
|
this.content.style.display = "none";
|
|
303
|
+
this.isClosing = false;
|
|
287
304
|
}, this.animationDuration - 10);
|
|
288
305
|
|
|
289
306
|
this.trigger.setAttribute("aria-expanded", "false");
|
|
@@ -13,7 +13,7 @@ const dropdownTrigger = tv({
|
|
|
13
13
|
base: [
|
|
14
14
|
"starwind-dropdown-trigger",
|
|
15
15
|
"inline-flex items-center justify-center",
|
|
16
|
-
"focus-visible:
|
|
16
|
+
"focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none",
|
|
17
17
|
],
|
|
18
18
|
});
|
|
19
19
|
|
|
@@ -28,14 +28,13 @@ if (Astro.slots.has("default")) {
|
|
|
28
28
|
|
|
29
29
|
{
|
|
30
30
|
asChild && hasChildren ? (
|
|
31
|
-
<div class=
|
|
31
|
+
<div class={`starwind-dropdown-trigger ${className}`} data-as-child>
|
|
32
32
|
<slot />
|
|
33
33
|
</div>
|
|
34
34
|
) : (
|
|
35
35
|
<button
|
|
36
36
|
class={dropdownTrigger({ class: className })}
|
|
37
37
|
type="button"
|
|
38
|
-
role="button"
|
|
39
38
|
aria-haspopup="true"
|
|
40
39
|
aria-expanded="false"
|
|
41
40
|
data-state="closed"
|
|
@@ -2,15 +2,27 @@
|
|
|
2
2
|
import type { HTMLAttributes } from "astro/types";
|
|
3
3
|
|
|
4
4
|
type Props = HTMLAttributes<"div"> & {
|
|
5
|
+
/**
|
|
6
|
+
* The name of the select field for form handling
|
|
7
|
+
*/
|
|
5
8
|
name?: string;
|
|
9
|
+
/**
|
|
10
|
+
* The value of the item that should be selected by default
|
|
11
|
+
*/
|
|
12
|
+
defaultValue?: string;
|
|
6
13
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
14
|
children: any;
|
|
8
15
|
};
|
|
9
16
|
|
|
10
|
-
const { class: className, name, ...rest } = Astro.props;
|
|
17
|
+
const { class: className, name, defaultValue, ...rest } = Astro.props;
|
|
11
18
|
---
|
|
12
19
|
|
|
13
|
-
<div
|
|
20
|
+
<div
|
|
21
|
+
class:list={["starwind-select", "relative", className]}
|
|
22
|
+
data-name={name}
|
|
23
|
+
data-value={defaultValue}
|
|
24
|
+
{...rest}
|
|
25
|
+
>
|
|
14
26
|
<slot />
|
|
15
27
|
</div>
|
|
16
28
|
|
|
@@ -50,6 +62,7 @@ const { class: className, name, ...rest } = Astro.props;
|
|
|
50
62
|
this.setupAccessibility(selectIdx);
|
|
51
63
|
this.setupEvents();
|
|
52
64
|
this.setupSelectField();
|
|
65
|
+
this.setInitialState();
|
|
53
66
|
}
|
|
54
67
|
|
|
55
68
|
private setupSelectField() {
|
|
@@ -398,6 +411,21 @@ const { class: className, name, ...rest } = Astro.props;
|
|
|
398
411
|
this.closeSelect();
|
|
399
412
|
}
|
|
400
413
|
|
|
414
|
+
/**
|
|
415
|
+
* Sets the initial state based on the default value attribute
|
|
416
|
+
*/
|
|
417
|
+
private setInitialState(): void {
|
|
418
|
+
const defaultValue = this.select.dataset.value;
|
|
419
|
+
if (defaultValue) {
|
|
420
|
+
const item = this.content?.querySelector(`[data-value="${defaultValue}"]`);
|
|
421
|
+
|
|
422
|
+
if (item && item instanceof HTMLElement) {
|
|
423
|
+
this.handleSelection(item);
|
|
424
|
+
this.selectedItem = item;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
401
429
|
/**
|
|
402
430
|
* TODO: add position logic to avoid collisions with window boundary
|
|
403
431
|
* It will need to switch to top or bottom depending on space available
|
|
@@ -8,6 +8,11 @@ type Props = HTMLAttributes<"div"> & {
|
|
|
8
8
|
* @default bottom
|
|
9
9
|
*/
|
|
10
10
|
side?: "top" | "bottom";
|
|
11
|
+
/**
|
|
12
|
+
* Alignment of the dropdown
|
|
13
|
+
* @default start
|
|
14
|
+
*/
|
|
15
|
+
align?: "start" | "center" | "end";
|
|
11
16
|
/**
|
|
12
17
|
* Offset distance in pixels
|
|
13
18
|
* @default 4
|
|
@@ -25,17 +30,20 @@ const selectContent = tv({
|
|
|
25
30
|
"starwind-select-content",
|
|
26
31
|
"bg-popover text-popover-foreground absolute z-50 min-w-[8rem] rounded-md border shadow-md",
|
|
27
32
|
"fade-in-0 zoom-in-95 animate-in overflow-hidden will-change-transform",
|
|
28
|
-
"data-[state=closed]:
|
|
29
|
-
"left-0",
|
|
33
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
30
34
|
],
|
|
31
35
|
variants: {
|
|
32
36
|
side: {
|
|
33
|
-
bottom:
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
bottom: "slide-in-from-top-2 data-[state=closed]:slide-out-to-top-2 top-full",
|
|
38
|
+
top: "slide-in-from-bottom-2 data-[state=closed]:slide-out-to-bottom-2 bottom-full",
|
|
39
|
+
},
|
|
40
|
+
align: {
|
|
41
|
+
start: "slide-in-from-left-1 data-[state=closed]:slide-out-to-left-1 left-0",
|
|
42
|
+
center: "left-1/2 -translate-x-1/2",
|
|
43
|
+
end: "slide-in-from-right-1 data-[state=closed]:slide-out-to-right-1 right-0",
|
|
36
44
|
},
|
|
37
45
|
},
|
|
38
|
-
defaultVariants: { side: "bottom" },
|
|
46
|
+
defaultVariants: { side: "bottom", align: "start" },
|
|
39
47
|
});
|
|
40
48
|
|
|
41
49
|
const selectContentInner = tv({
|
|
@@ -45,6 +53,7 @@ const selectContentInner = tv({
|
|
|
45
53
|
const {
|
|
46
54
|
class: className,
|
|
47
55
|
side = "bottom",
|
|
56
|
+
align = "start",
|
|
48
57
|
sideOffset = 4,
|
|
49
58
|
animationDuration = 150,
|
|
50
59
|
...rest
|
|
@@ -52,16 +61,18 @@ const {
|
|
|
52
61
|
---
|
|
53
62
|
|
|
54
63
|
<div
|
|
55
|
-
class={selectContent({ side, class: className })}
|
|
64
|
+
class={selectContent({ side, align, class: className })}
|
|
56
65
|
role="listbox"
|
|
57
66
|
data-side={side}
|
|
67
|
+
data-align={align}
|
|
58
68
|
data-state="closed"
|
|
59
69
|
tabindex="-1"
|
|
60
70
|
style={{
|
|
61
|
-
"--select-content-offset": `calc(100% + ${sideOffset}px)`,
|
|
62
71
|
// hide the content initially. Script will remove this
|
|
63
72
|
display: "none",
|
|
64
73
|
animationDuration: `${animationDuration}ms`,
|
|
74
|
+
marginTop: side === "bottom" ? `${sideOffset}px` : undefined,
|
|
75
|
+
marginBottom: side === "top" ? `${sideOffset}px` : undefined,
|
|
65
76
|
}}
|
|
66
77
|
{...rest}
|
|
67
78
|
>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
type Props = HTMLAttributes<"table"> & VariantProps<typeof table>;
|
|
6
|
+
|
|
7
|
+
const table = tv({
|
|
8
|
+
base: "w-full caption-bottom text-sm",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const { class: className, ...rest } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<div data-table-container class="relative w-full overflow-x-auto">
|
|
15
|
+
<table data-table class={table({ class: className })} {...rest} role="table">
|
|
16
|
+
<slot />
|
|
17
|
+
</table>
|
|
18
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
type Props = HTMLAttributes<"tbody"> & VariantProps<typeof tableBody>;
|
|
6
|
+
|
|
7
|
+
const tableBody = tv({
|
|
8
|
+
base: "[&_tr:last-child]:border-0",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const { class: className, ...rest } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<tbody data-table-body class={tableBody({ class: className })} {...rest}>
|
|
15
|
+
<slot />
|
|
16
|
+
</tbody>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
type Props = HTMLAttributes<"caption"> & VariantProps<typeof tableCaption>;
|
|
6
|
+
|
|
7
|
+
const tableCaption = tv({
|
|
8
|
+
base: "text-muted-foreground mt-4 text-sm",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const { class: className, ...rest } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<caption data-table-caption class={tableCaption({ class: className })} {...rest}>
|
|
15
|
+
<slot />
|
|
16
|
+
</caption>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
type Props = HTMLAttributes<"td"> & VariantProps<typeof tableCell>;
|
|
6
|
+
|
|
7
|
+
const tableCell = tv({
|
|
8
|
+
base: "p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const { class: className, ...rest } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<td data-table-cell class={tableCell({ class: className })} {...rest}>
|
|
15
|
+
<slot />
|
|
16
|
+
</td>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
type Props = HTMLAttributes<"tfoot"> & VariantProps<typeof tableFoot>;
|
|
6
|
+
|
|
7
|
+
const tableFoot = tv({
|
|
8
|
+
base: "bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const { class: className, ...rest } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<tfoot data-table-foot class={tableFoot({ class: className })} {...rest}>
|
|
15
|
+
<slot />
|
|
16
|
+
</tfoot>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
type Props = HTMLAttributes<"th"> & VariantProps<typeof tableHead>;
|
|
6
|
+
|
|
7
|
+
const tableHead = tv({
|
|
8
|
+
base: "text-muted-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const { class: className, ...rest } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<th data-table-head class={tableHead({ class: className })} {...rest} role="columnheader">
|
|
15
|
+
<slot />
|
|
16
|
+
</th>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
type Props = HTMLAttributes<"thead"> & VariantProps<typeof tableHeader>;
|
|
6
|
+
|
|
7
|
+
const tableHeader = tv({
|
|
8
|
+
base: "[&_tr]:border-b",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const { class: className, ...rest } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<thead data-table-header class={tableHeader({ class: className })} {...rest}>
|
|
15
|
+
<slot />
|
|
16
|
+
</thead>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
type Props = HTMLAttributes<"tr"> & VariantProps<typeof tableRow>;
|
|
6
|
+
|
|
7
|
+
const tableRow = tv({
|
|
8
|
+
base: "hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const { class: className, ...rest } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<tr data-table-row class={tableRow({ class: className })} {...rest} role="row">
|
|
15
|
+
<slot />
|
|
16
|
+
</tr>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Table from "./Table.astro";
|
|
2
|
+
import TableBody from "./TableBody.astro";
|
|
3
|
+
import TableCaption from "./TableCaption.astro";
|
|
4
|
+
import TableCell from "./TableCell.astro";
|
|
5
|
+
import TableFoot from "./TableFoot.astro";
|
|
6
|
+
import TableHeader from "./TableHeader.astro";
|
|
7
|
+
import TableHead from "./TableHead.astro";
|
|
8
|
+
import TableRow from "./TableRow.astro";
|
|
9
|
+
|
|
10
|
+
export { Table, TableBody, TableCaption, TableCell, TableFoot, TableHead, TableHeader, TableRow };
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
Root: Table,
|
|
14
|
+
Body: TableBody,
|
|
15
|
+
Caption: TableCaption,
|
|
16
|
+
Cell: TableCell,
|
|
17
|
+
Foot: TableFoot,
|
|
18
|
+
Head: TableHead,
|
|
19
|
+
Header: TableHeader,
|
|
20
|
+
Row: TableRow,
|
|
21
|
+
};
|
|
@@ -42,11 +42,13 @@ const { defaultValue, syncKey, class: className, ...rest } = Astro.props;
|
|
|
42
42
|
private storageKey: string;
|
|
43
43
|
private valueToTriggerMap: Map<string, HTMLButtonElement>;
|
|
44
44
|
private valueToContentMap: Map<string, HTMLElement>;
|
|
45
|
+
private parentHandler: TabsHandler | null = null;
|
|
45
46
|
|
|
46
|
-
constructor(tabs: HTMLElement, idx: number) {
|
|
47
|
+
constructor(tabs: HTMLElement, idx: number, parentHandler: TabsHandler | null = null) {
|
|
47
48
|
this.tabs = tabs;
|
|
48
|
-
this.
|
|
49
|
-
this.
|
|
49
|
+
this.parentHandler = parentHandler;
|
|
50
|
+
this.triggers = Array.from(tabs.querySelectorAll(":scope > [data-tabs-list] > [data-tabs-trigger]"));
|
|
51
|
+
this.contents = Array.from(tabs.querySelectorAll(":scope > [data-tabs-content]"));
|
|
50
52
|
this.tabsId = `starwind-tabs${idx}`;
|
|
51
53
|
this.syncKey = tabs.dataset.syncKey;
|
|
52
54
|
this.storageKey = this.syncKey
|
|
@@ -228,6 +230,20 @@ const { defaultValue, syncKey, class: className, ...rest } = Astro.props;
|
|
|
228
230
|
c.setAttribute("data-state", isActive ? "active" : "inactive");
|
|
229
231
|
c.hidden = !isActive;
|
|
230
232
|
});
|
|
233
|
+
|
|
234
|
+
// Initialize any nested tabs in the active content
|
|
235
|
+
if (content.hasAttribute("data-state") && content.getAttribute("data-state") === "active") {
|
|
236
|
+
const nestedTabs = content.querySelectorAll<HTMLElement>(".starwind-tabs");
|
|
237
|
+
|
|
238
|
+
nestedTabs.forEach((nestedTab, nestedIdx) => {
|
|
239
|
+
// Skip tabs that already have instances
|
|
240
|
+
if (!tabInstances.has(nestedTab)) {
|
|
241
|
+
const uniqueIdx = 1000 + nestedIdx;
|
|
242
|
+
const handler = new TabsHandler(nestedTab, uniqueIdx, this);
|
|
243
|
+
tabInstances.set(nestedTab, handler);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
231
247
|
}
|
|
232
248
|
}
|
|
233
249
|
|
|
@@ -235,8 +251,11 @@ const { defaultValue, syncKey, class: className, ...rest } = Astro.props;
|
|
|
235
251
|
const tabInstances = new WeakMap<HTMLElement, TabsHandler>();
|
|
236
252
|
|
|
237
253
|
const setupTabs = () => {
|
|
254
|
+
// First handle top-level tabs
|
|
238
255
|
document.querySelectorAll<HTMLElement>(".starwind-tabs").forEach((tabs, idx) => {
|
|
239
|
-
|
|
256
|
+
// Skip tabs that are nested within other tab contents
|
|
257
|
+
const isNested = !!tabs.closest("[data-tabs-content]");
|
|
258
|
+
if (!isNested && !tabInstances.has(tabs)) {
|
|
240
259
|
tabInstances.set(tabs, new TabsHandler(tabs, idx));
|
|
241
260
|
}
|
|
242
261
|
});
|