@starwind-ui/core 1.5.1 → 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 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.1", dependencies: [] },
18
- { name: "dropdown", type: "component", version: "1.0.1", dependencies: [] },
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.2.0", dependencies: [] },
22
+ { name: "select", type: "component", version: "1.3.0", dependencies: [] },
23
23
  { name: "switch", type: "component", version: "1.1.0", dependencies: [] },
24
- { name: "tabs", type: "component", version: "1.1.1", dependencies: [] },
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\", \"dependencies\": [] },\n\t\t{ \"name\": \"dropdown\", \"type\": \"component\", \"version\": \"1.0.1\", \"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.2.0\", \"dependencies\": [] },\n\t\t{ \"name\": \"switch\", \"type\": \"component\", \"version\": \"1.1.0\", \"dependencies\": [] },\n\t\t{ \"name\": \"tabs\", \"type\": \"component\", \"version\": \"1.1.1\", \"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,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;;;ADDA,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":[]}
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.close();
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.close();
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.close();
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.close();
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
  });
@@ -35,6 +35,7 @@ const { class: className, openOnHover = false, closeDelay = 200, ...rest } = Ast
35
35
  private items: HTMLElement[] = [];
36
36
  private currentFocusIndex: number = -1;
37
37
  private isOpen: boolean = false;
38
+ private isClosing: boolean = false;
38
39
  private animationDuration = 150;
39
40
  private openOnHover: boolean;
40
41
  private closeDelay: number;
@@ -178,6 +179,7 @@ const { class: className, openOnHover = false, closeDelay = 200, ...rest } = Ast
178
179
  if (this.openOnHover) {
179
180
  this.trigger.addEventListener("pointerenter", (e) => {
180
181
  if (e.pointerType !== "mouse") return;
182
+ if (this.isClosing) return;
181
183
  if (!this.isOpen) {
182
184
  this.openDropdown();
183
185
  } else {
@@ -264,6 +266,7 @@ const { class: className, openOnHover = false, closeDelay = 200, ...rest } = Ast
264
266
  }
265
267
 
266
268
  private openDropdown() {
269
+ if (this.isClosing) return;
267
270
  if (!this.content || !this.trigger || this.trigger.disabled) return;
268
271
 
269
272
  this.isOpen = true;
@@ -283,6 +286,7 @@ const { class: className, openOnHover = false, closeDelay = 200, ...rest } = Ast
283
286
  private closeDropdown() {
284
287
  if (!this.content || !this.trigger) return;
285
288
 
289
+ this.isClosing = true;
286
290
  this.isOpen = false;
287
291
  this.content.setAttribute("data-state", "closed");
288
292
 
@@ -296,6 +300,7 @@ const { class: className, openOnHover = false, closeDelay = 200, ...rest } = Ast
296
300
  setTimeout(() => {
297
301
  if (!this.content) return;
298
302
  this.content.style.display = "none";
303
+ this.isClosing = false;
299
304
  }, this.animationDuration - 10);
300
305
 
301
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:outline-none focus-visible:ring-2 focus-visible:ring-ring",
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="starwind-dropdown-trigger" data-as-child>
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 class:list={["starwind-select", "relative", className]} data-name={name} {...rest}>
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]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:animate-out",
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
- "data-[side=bottom]:slide-in-from-top-4 data-[side=bottom]:top-(--select-content-offset)",
35
- top: "data-[side=top]:slide-in-from-bottom-4 data-[side=top]:bottom-(--select-content-offset)",
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.triggers = Array.from(tabs.querySelectorAll("[data-tabs-trigger]"));
49
- this.contents = Array.from(tabs.querySelectorAll("[data-tabs-content]"));
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
- if (!tabInstances.has(tabs)) {
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starwind-ui/core",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "Starwind UI core components and registry",
5
5
  "license": "MIT",
6
6
  "author": {