@starwind-ui/core 1.4.1 → 1.5.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.
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/src/components/breadcrumb/Breadcrumb.astro +11 -0
- package/dist/src/components/breadcrumb/BreadcrumbEllipsis.astro +21 -0
- package/dist/src/components/breadcrumb/BreadcrumbItem.astro +14 -0
- package/dist/src/components/breadcrumb/BreadcrumbLink.astro +20 -0
- package/dist/src/components/breadcrumb/BreadcrumbList.astro +16 -0
- package/dist/src/components/breadcrumb/BreadcrumbPage.astro +20 -0
- package/dist/src/components/breadcrumb/BreadcrumbSeparator.astro +22 -0
- package/dist/src/components/breadcrumb/index.ts +27 -0
- package/dist/src/components/dropdown/Dropdown.astro +355 -0
- package/dist/src/components/dropdown/DropdownContent.astro +80 -0
- package/dist/src/components/dropdown/DropdownItem.astro +47 -0
- package/dist/src/components/dropdown/DropdownLabel.astro +29 -0
- package/dist/src/components/dropdown/DropdownSeparator.astro +20 -0
- package/dist/src/components/dropdown/DropdownTrigger.astro +47 -0
- package/dist/src/components/dropdown/index.ts +24 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,10 +10,12 @@ var registry_default = {
|
|
|
10
10
|
{ name: "alert", type: "component", version: "1.1.1", dependencies: [] },
|
|
11
11
|
{ name: "avatar", type: "component", version: "1.1.1", dependencies: [] },
|
|
12
12
|
{ name: "badge", type: "component", version: "1.1.1", dependencies: [] },
|
|
13
|
+
{ name: "breadcrumb", type: "component", version: "1.0.0", dependencies: [] },
|
|
13
14
|
{ name: "button", type: "component", version: "2.0.1", dependencies: [] },
|
|
14
15
|
{ name: "card", type: "component", version: "1.1.0", dependencies: [] },
|
|
15
16
|
{ name: "checkbox", type: "component", version: "1.2.0", dependencies: [] },
|
|
16
17
|
{ name: "dialog", type: "component", version: "1.1.1", dependencies: [] },
|
|
18
|
+
{ name: "dropdown", type: "component", version: "1.0.1", dependencies: [] },
|
|
17
19
|
{ name: "input", type: "component", version: "1.1.1", dependencies: [] },
|
|
18
20
|
{ name: "label", type: "component", version: "1.1.1", dependencies: [] },
|
|
19
21
|
{ name: "pagination", type: "component", version: "2.0.1", dependencies: [] },
|
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\": \"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\": \"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,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,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;;;
|
|
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":[]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Dots from "@tabler/icons/outline/dots.svg";
|
|
3
|
+
import type { HTMLAttributes } from "astro/types";
|
|
4
|
+
import { tv } from "tailwind-variants";
|
|
5
|
+
|
|
6
|
+
type Props = HTMLAttributes<"span">;
|
|
7
|
+
|
|
8
|
+
const breadcrumbEllipsis = tv({ base: "flex size-6 items-center justify-center [&>svg]:size-4" });
|
|
9
|
+
|
|
10
|
+
const { class: className, ...rest } = Astro.props;
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<span
|
|
14
|
+
role="presentation"
|
|
15
|
+
aria-hidden="true"
|
|
16
|
+
class={breadcrumbEllipsis({ class: className })}
|
|
17
|
+
{...rest}
|
|
18
|
+
>
|
|
19
|
+
<Dots />
|
|
20
|
+
<span class="sr-only">More</span>
|
|
21
|
+
</span>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
type Props = HTMLAttributes<"li">;
|
|
6
|
+
|
|
7
|
+
const breadcrumbItem = tv({ base: "inline-flex items-center gap-1.5" });
|
|
8
|
+
|
|
9
|
+
const { class: className, ...rest } = Astro.props;
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<li class={breadcrumbItem({ class: className })} {...rest}>
|
|
13
|
+
<slot />
|
|
14
|
+
</li>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
type Props = HTMLAttributes<"a"> & {
|
|
6
|
+
asChild?: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const breadcrumbLink = tv({ base: "hover:text-foreground transition-colors" });
|
|
10
|
+
|
|
11
|
+
const { class: className, asChild = false, ...rest } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
{asChild ? (
|
|
15
|
+
<slot />
|
|
16
|
+
) : (
|
|
17
|
+
<a class={breadcrumbLink({ class: className })} {...rest}>
|
|
18
|
+
<slot />
|
|
19
|
+
</a>
|
|
20
|
+
)}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
type Props = HTMLAttributes<"ol">;
|
|
6
|
+
|
|
7
|
+
const breadcrumbList = tv({
|
|
8
|
+
base: "text-muted-foreground flex flex-wrap items-center gap-1.5 break-words sm:gap-2",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const { class: className, ...rest } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<ol class={breadcrumbList({ class: className })} {...rest}>
|
|
15
|
+
<slot />
|
|
16
|
+
</ol>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
type Props = HTMLAttributes<"span">;
|
|
6
|
+
|
|
7
|
+
const breadcrumbPage = tv({ base: "text-foreground font-normal" });
|
|
8
|
+
|
|
9
|
+
const { class: className, ...rest } = Astro.props;
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<span
|
|
13
|
+
role="link"
|
|
14
|
+
aria-disabled="true"
|
|
15
|
+
aria-current="page"
|
|
16
|
+
class={breadcrumbPage({ class: className })}
|
|
17
|
+
{...rest}
|
|
18
|
+
>
|
|
19
|
+
<slot />
|
|
20
|
+
</span>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
import ChevronRight from "@tabler/icons/outline/chevron-right.svg";
|
|
3
|
+
import type { HTMLAttributes } from "astro/types";
|
|
4
|
+
import { tv } from "tailwind-variants";
|
|
5
|
+
|
|
6
|
+
type Props = HTMLAttributes<"li">;
|
|
7
|
+
|
|
8
|
+
const breadcrumbSeparator = tv({ base: "[&>svg]:size-4" });
|
|
9
|
+
|
|
10
|
+
const { class: className, ...rest } = Astro.props;
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<li
|
|
14
|
+
role="presentation"
|
|
15
|
+
aria-hidden="true"
|
|
16
|
+
class={breadcrumbSeparator({ class: className })}
|
|
17
|
+
{...rest}
|
|
18
|
+
>
|
|
19
|
+
<slot>
|
|
20
|
+
<ChevronRight />
|
|
21
|
+
</slot>
|
|
22
|
+
</li>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import Breadcrumb from "./Breadcrumb.astro";
|
|
2
|
+
import BreadcrumbList from "./BreadcrumbList.astro";
|
|
3
|
+
import BreadcrumbEllipsis from "./BreadcrumbEllipsis.astro";
|
|
4
|
+
import BreadcrumbItem from "./BreadcrumbItem.astro";
|
|
5
|
+
import BreadcrumbLink from "./BreadcrumbLink.astro";
|
|
6
|
+
import BreadcrumbSeparator from "./BreadcrumbSeparator.astro";
|
|
7
|
+
import BreadcrumbPage from "./BreadcrumbPage.astro";
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
Breadcrumb,
|
|
11
|
+
BreadcrumbList,
|
|
12
|
+
BreadcrumbEllipsis,
|
|
13
|
+
BreadcrumbItem,
|
|
14
|
+
BreadcrumbLink,
|
|
15
|
+
BreadcrumbSeparator,
|
|
16
|
+
BreadcrumbPage,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
Root: Breadcrumb,
|
|
21
|
+
List: BreadcrumbList,
|
|
22
|
+
Ellipsis: BreadcrumbEllipsis,
|
|
23
|
+
Item: BreadcrumbItem,
|
|
24
|
+
Link: BreadcrumbLink,
|
|
25
|
+
Separator: BreadcrumbSeparator,
|
|
26
|
+
Page: BreadcrumbPage,
|
|
27
|
+
};
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
|
|
4
|
+
type Props = HTMLAttributes<"div"> & {
|
|
5
|
+
/**
|
|
6
|
+
* When true, the dropdown will open on hover in addition to click
|
|
7
|
+
*/
|
|
8
|
+
openOnHover?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Time in milliseconds to wait before closing when hover open is enabled
|
|
11
|
+
* @default 200
|
|
12
|
+
*/
|
|
13
|
+
closeDelay?: number;
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
|
+
children: any;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const { class: className, openOnHover = false, closeDelay = 200, ...rest } = Astro.props;
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<div
|
|
22
|
+
class:list={["starwind-dropdown", "relative", className]}
|
|
23
|
+
data-open-on-hover={openOnHover ? "true" : undefined}
|
|
24
|
+
data-close-delay={closeDelay}
|
|
25
|
+
{...rest}
|
|
26
|
+
>
|
|
27
|
+
<slot />
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<script>
|
|
31
|
+
class DropdownHandler {
|
|
32
|
+
private dropdown: HTMLElement;
|
|
33
|
+
private trigger: HTMLButtonElement | null;
|
|
34
|
+
private content: HTMLElement | null;
|
|
35
|
+
private items: HTMLElement[] = [];
|
|
36
|
+
private currentFocusIndex: number = -1;
|
|
37
|
+
private isOpen: boolean = false;
|
|
38
|
+
private animationDuration = 150;
|
|
39
|
+
private openOnHover: boolean;
|
|
40
|
+
private closeDelay: number;
|
|
41
|
+
private closeTimerRef: number | null = null;
|
|
42
|
+
|
|
43
|
+
constructor(dropdown: HTMLElement, dropdownIdx: number) {
|
|
44
|
+
this.dropdown = dropdown;
|
|
45
|
+
this.openOnHover = dropdown.getAttribute("data-open-on-hover") === "true";
|
|
46
|
+
this.closeDelay = parseInt(dropdown.getAttribute("data-close-delay") || "200");
|
|
47
|
+
|
|
48
|
+
// Get the temporary trigger element
|
|
49
|
+
const tempTrigger = dropdown.querySelector(".starwind-dropdown-trigger") as HTMLElement;
|
|
50
|
+
|
|
51
|
+
// if trigger is set with asChild, use the first child element for trigger button
|
|
52
|
+
if (tempTrigger?.hasAttribute("data-as-child")) {
|
|
53
|
+
this.trigger = tempTrigger.firstElementChild as HTMLButtonElement;
|
|
54
|
+
} else {
|
|
55
|
+
this.trigger = tempTrigger as HTMLButtonElement;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.content = dropdown.querySelector(".starwind-dropdown-content");
|
|
59
|
+
|
|
60
|
+
if (!this.trigger || !this.content) return;
|
|
61
|
+
|
|
62
|
+
// Get animation duration from inline styles if available
|
|
63
|
+
const animationDurationString = this.content.style.animationDuration;
|
|
64
|
+
if (animationDurationString.endsWith("ms")) {
|
|
65
|
+
this.animationDuration = parseFloat(animationDurationString);
|
|
66
|
+
} else if (animationDurationString.endsWith("s")) {
|
|
67
|
+
this.animationDuration = parseFloat(animationDurationString) * 1000;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.init(dropdownIdx);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private init(dropdownIdx: number) {
|
|
74
|
+
this.setupAccessibility(dropdownIdx);
|
|
75
|
+
this.setupEvents();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private setupAccessibility(dropdownIdx: number) {
|
|
79
|
+
if (!this.trigger || !this.content) return;
|
|
80
|
+
|
|
81
|
+
// Generate unique IDs for accessibility
|
|
82
|
+
this.trigger.id = `starwind-dropdown${dropdownIdx}-trigger`;
|
|
83
|
+
this.content.id = `starwind-dropdown${dropdownIdx}-content`;
|
|
84
|
+
|
|
85
|
+
// Set up additional ARIA attributes
|
|
86
|
+
this.trigger.setAttribute("aria-controls", this.content.id);
|
|
87
|
+
this.content.setAttribute("aria-labelledby", this.trigger.id);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private setupEvents() {
|
|
91
|
+
if (!this.trigger || !this.content) return;
|
|
92
|
+
|
|
93
|
+
// Handle trigger click
|
|
94
|
+
this.trigger.addEventListener("click", (e) => {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
this.toggleDropdown();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Handle keyboard navigation
|
|
100
|
+
this.trigger.addEventListener("keydown", (e) => {
|
|
101
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
this.toggleDropdown();
|
|
104
|
+
} else if (e.key === "Escape" && this.isOpen) {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
this.closeDropdown();
|
|
107
|
+
} else if (this.isOpen && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
|
|
108
|
+
e.preventDefault();
|
|
109
|
+
this.updateDropdownItems();
|
|
110
|
+
if (e.key === "ArrowDown") {
|
|
111
|
+
this.focusItem(0); // Focus first item when opening with arrow down
|
|
112
|
+
} else {
|
|
113
|
+
this.focusItem(this.items.length - 1); // Focus last item when opening with arrow up
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Close dropdown when clicking outside for mouse
|
|
119
|
+
document.addEventListener("pointerdown", (e) => {
|
|
120
|
+
if (this.isOpen && !this.dropdown.contains(e.target as Node)) {
|
|
121
|
+
// only call handler if it's the left button (mousedown gets triggered by all mouse buttons)
|
|
122
|
+
// but not when the control key is pressed (avoiding MacOS right click); also not for touch
|
|
123
|
+
// devices because that would open the menu on scroll. (pen devices behave as touch on iOS).
|
|
124
|
+
if (e.button === 0 && e.ctrlKey === false && e.pointerType === "mouse") {
|
|
125
|
+
this.closeDropdown();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Handle click outside select content to close for mobile
|
|
131
|
+
document.addEventListener("click", (e) => {
|
|
132
|
+
if (
|
|
133
|
+
!(this.trigger?.contains(e.target as Node) || this.content?.contains(e.target as Node)) &&
|
|
134
|
+
this.isOpen
|
|
135
|
+
) {
|
|
136
|
+
this.closeDropdown();
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Handle keyboard navigation and item selection within dropdown
|
|
141
|
+
this.content.addEventListener("keydown", (e) => {
|
|
142
|
+
if (e.key === "Escape") {
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
this.closeDropdown();
|
|
145
|
+
this.trigger?.focus();
|
|
146
|
+
} else if (this.isOpen) {
|
|
147
|
+
this.handleMenuKeydown(e);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Handle item selection
|
|
152
|
+
this.content.addEventListener("click", (e) => {
|
|
153
|
+
const target = e.target as HTMLElement;
|
|
154
|
+
const item = target.closest('[role="menuitem"]');
|
|
155
|
+
if (item && !(item as HTMLElement).hasAttribute("data-disabled")) {
|
|
156
|
+
// Close the dropdown after item selection
|
|
157
|
+
this.closeDropdown();
|
|
158
|
+
console.log("click closing");
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Handle hover on dropdown items
|
|
163
|
+
this.content.addEventListener("mouseover", (e) => {
|
|
164
|
+
const target = e.target as HTMLElement;
|
|
165
|
+
const menuItem = target.closest('[role="menuitem"]');
|
|
166
|
+
if (menuItem && menuItem instanceof HTMLElement && this.isOpen === true) {
|
|
167
|
+
// Update items list before focusing to ensure the index is correct
|
|
168
|
+
this.updateDropdownItems();
|
|
169
|
+
|
|
170
|
+
// Focus the item when hovering
|
|
171
|
+
menuItem.focus();
|
|
172
|
+
|
|
173
|
+
// Update the current focus index
|
|
174
|
+
this.currentFocusIndex = this.items.indexOf(menuItem);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (this.openOnHover) {
|
|
179
|
+
this.trigger.addEventListener("pointerenter", (e) => {
|
|
180
|
+
if (e.pointerType !== "mouse") return;
|
|
181
|
+
if (!this.isOpen) {
|
|
182
|
+
this.openDropdown();
|
|
183
|
+
} else {
|
|
184
|
+
// If the dropdown is already open, make sure to clear any close timer
|
|
185
|
+
this.clearCloseTimer();
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
this.dropdown.addEventListener("pointerleave", (e) => {
|
|
190
|
+
if (e.pointerType !== "mouse") return;
|
|
191
|
+
if (this.isOpen) {
|
|
192
|
+
this.closeDropdownDelayed();
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
this.content.addEventListener("pointerenter", (e) => {
|
|
197
|
+
if (e.pointerType !== "mouse") return;
|
|
198
|
+
// If the user moves the mouse to the content, cancel the close timer
|
|
199
|
+
this.clearCloseTimer();
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private handleMenuKeydown(e: KeyboardEvent) {
|
|
205
|
+
// Make sure we've got an updated list of menu items
|
|
206
|
+
this.updateDropdownItems();
|
|
207
|
+
|
|
208
|
+
// Skip if no items
|
|
209
|
+
if (this.items.length === 0) return;
|
|
210
|
+
|
|
211
|
+
const currentIdx = this.currentFocusIndex;
|
|
212
|
+
|
|
213
|
+
switch (e.key) {
|
|
214
|
+
case "ArrowDown":
|
|
215
|
+
e.preventDefault();
|
|
216
|
+
this.focusItem(currentIdx === -1 ? 0 : currentIdx + 1);
|
|
217
|
+
break;
|
|
218
|
+
case "ArrowUp":
|
|
219
|
+
e.preventDefault();
|
|
220
|
+
this.focusItem(currentIdx === -1 ? this.items.length - 1 : currentIdx - 1);
|
|
221
|
+
break;
|
|
222
|
+
case "Home":
|
|
223
|
+
e.preventDefault();
|
|
224
|
+
this.focusItem(0);
|
|
225
|
+
break;
|
|
226
|
+
case "End":
|
|
227
|
+
e.preventDefault();
|
|
228
|
+
this.focusItem(this.items.length - 1);
|
|
229
|
+
break;
|
|
230
|
+
case "Enter":
|
|
231
|
+
case " ":
|
|
232
|
+
if (currentIdx !== -1) {
|
|
233
|
+
e.preventDefault();
|
|
234
|
+
this.items[currentIdx].click();
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private updateDropdownItems() {
|
|
241
|
+
if (!this.content) return;
|
|
242
|
+
// Get all interactive menuitem elements
|
|
243
|
+
this.items = Array.from(
|
|
244
|
+
this.content.querySelectorAll('[role="menuitem"]:not([data-disabled="true"])'),
|
|
245
|
+
) as HTMLElement[];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private focusItem(idx: number) {
|
|
249
|
+
// Ensure the index wraps around properly
|
|
250
|
+
const targetIdx = (idx + this.items.length) % this.items.length;
|
|
251
|
+
|
|
252
|
+
if (this.items[targetIdx]) {
|
|
253
|
+
this.items[targetIdx].focus();
|
|
254
|
+
this.currentFocusIndex = targetIdx;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private toggleDropdown() {
|
|
259
|
+
if (this.isOpen) {
|
|
260
|
+
this.closeDropdown();
|
|
261
|
+
} else {
|
|
262
|
+
this.openDropdown();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private openDropdown() {
|
|
267
|
+
if (!this.content || !this.trigger || this.trigger.disabled) return;
|
|
268
|
+
|
|
269
|
+
this.isOpen = true;
|
|
270
|
+
this.content.setAttribute("data-state", "open");
|
|
271
|
+
this.trigger.setAttribute("aria-expanded", "true");
|
|
272
|
+
this.content.style.removeProperty("display");
|
|
273
|
+
|
|
274
|
+
// Update the list of dropdown items
|
|
275
|
+
this.updateDropdownItems();
|
|
276
|
+
|
|
277
|
+
// Reset focus index when opening
|
|
278
|
+
this.currentFocusIndex = -1;
|
|
279
|
+
|
|
280
|
+
this.positionContent();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private closeDropdown() {
|
|
284
|
+
if (!this.content || !this.trigger) return;
|
|
285
|
+
|
|
286
|
+
this.isOpen = false;
|
|
287
|
+
this.content.setAttribute("data-state", "closed");
|
|
288
|
+
|
|
289
|
+
// Set focus back on trigger
|
|
290
|
+
requestAnimationFrame(() => {
|
|
291
|
+
if (!this.trigger) return;
|
|
292
|
+
this.trigger.focus();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Give the content time to animate before hiding
|
|
296
|
+
setTimeout(() => {
|
|
297
|
+
if (!this.content) return;
|
|
298
|
+
this.content.style.display = "none";
|
|
299
|
+
}, this.animationDuration - 10);
|
|
300
|
+
|
|
301
|
+
this.trigger.setAttribute("aria-expanded", "false");
|
|
302
|
+
|
|
303
|
+
// Reset focus index when closing
|
|
304
|
+
this.currentFocusIndex = -1;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private closeDropdownDelayed() {
|
|
308
|
+
if (!this.content || !this.trigger) return;
|
|
309
|
+
|
|
310
|
+
// Clear any existing close timer
|
|
311
|
+
this.clearCloseTimer();
|
|
312
|
+
|
|
313
|
+
// Set a new timer to close the dropdown after the delay
|
|
314
|
+
this.closeTimerRef = window.setTimeout(() => {
|
|
315
|
+
if (this.isOpen) {
|
|
316
|
+
this.closeDropdown();
|
|
317
|
+
}
|
|
318
|
+
this.closeTimerRef = null;
|
|
319
|
+
}, this.closeDelay);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private clearCloseTimer() {
|
|
323
|
+
if (this.closeTimerRef !== null) {
|
|
324
|
+
window.clearTimeout(this.closeTimerRef);
|
|
325
|
+
this.closeTimerRef = null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private positionContent() {
|
|
330
|
+
if (!this.content || !this.trigger) return;
|
|
331
|
+
|
|
332
|
+
// Set content width to match trigger width
|
|
333
|
+
this.content.style.width = "var(--starwind-dropdown-trigger-width)";
|
|
334
|
+
this.content.style.setProperty(
|
|
335
|
+
"--starwind-dropdown-trigger-width",
|
|
336
|
+
`${this.trigger.offsetWidth}px`,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Store instances in a WeakMap to avoid memory leaks
|
|
342
|
+
const dropdownInstances = new WeakMap<HTMLElement, DropdownHandler>();
|
|
343
|
+
|
|
344
|
+
// Initialize dropdowns
|
|
345
|
+
const initDropdowns = () => {
|
|
346
|
+
document.querySelectorAll(".starwind-dropdown").forEach((dropdown, idx) => {
|
|
347
|
+
if (dropdown instanceof HTMLElement && !dropdownInstances.has(dropdown)) {
|
|
348
|
+
dropdownInstances.set(dropdown, new DropdownHandler(dropdown, idx));
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
initDropdowns();
|
|
354
|
+
document.addEventListener("astro:after-swap", initDropdowns);
|
|
355
|
+
</script>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
type Props = HTMLAttributes<"div"> & {
|
|
6
|
+
/**
|
|
7
|
+
* Side of the dropdown
|
|
8
|
+
* @default bottom
|
|
9
|
+
*/
|
|
10
|
+
side?: "top" | "bottom";
|
|
11
|
+
/**
|
|
12
|
+
* Alignment of the dropdown
|
|
13
|
+
* @default start
|
|
14
|
+
*/
|
|
15
|
+
align?: "start" | "center" | "end";
|
|
16
|
+
/**
|
|
17
|
+
* Offset distance in pixels
|
|
18
|
+
* @default 4
|
|
19
|
+
*/
|
|
20
|
+
sideOffset?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Open and close animation duration in milliseconds
|
|
23
|
+
* @default 150
|
|
24
|
+
*/
|
|
25
|
+
animationDuration?: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const dropdownContent = tv({
|
|
29
|
+
base: [
|
|
30
|
+
"starwind-dropdown-content",
|
|
31
|
+
"bg-popover text-popover-foreground z-50 min-w-[9rem] overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
|
|
32
|
+
"animate-in fade-in-0 zoom-in-95",
|
|
33
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
34
|
+
"absolute will-change-transform",
|
|
35
|
+
],
|
|
36
|
+
variants: {
|
|
37
|
+
side: {
|
|
38
|
+
bottom: "slide-in-from-top-2 data-[state=closed]:slide-out-to-top-2 top-full",
|
|
39
|
+
top: "slide-in-from-bottom-2 data-[state=closed]:slide-out-to-bottom-2 bottom-full",
|
|
40
|
+
},
|
|
41
|
+
align: {
|
|
42
|
+
start: "slide-in-from-left-1 data-[state=closed]:slide-out-to-left-1 left-0",
|
|
43
|
+
center: "left-1/2 -translate-x-1/2",
|
|
44
|
+
end: "slide-in-from-right-1 data-[state=closed]:slide-out-to-right-1 right-0",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
defaultVariants: {
|
|
48
|
+
side: "bottom",
|
|
49
|
+
align: "start",
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const {
|
|
54
|
+
class: className,
|
|
55
|
+
side = "bottom",
|
|
56
|
+
align = "start",
|
|
57
|
+
sideOffset = 4,
|
|
58
|
+
animationDuration = 150,
|
|
59
|
+
...rest
|
|
60
|
+
} = Astro.props;
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
<div
|
|
64
|
+
class={dropdownContent({ side, align, class: className })}
|
|
65
|
+
role="menu"
|
|
66
|
+
data-side={side}
|
|
67
|
+
data-align={align}
|
|
68
|
+
data-state="closed"
|
|
69
|
+
tabindex="-1"
|
|
70
|
+
aria-orientation="vertical"
|
|
71
|
+
style={{
|
|
72
|
+
display: "none",
|
|
73
|
+
animationDuration: `${animationDuration}ms`,
|
|
74
|
+
marginTop: side === "bottom" ? `${sideOffset}px` : undefined,
|
|
75
|
+
marginBottom: side === "top" ? `${sideOffset}px` : undefined,
|
|
76
|
+
}}
|
|
77
|
+
{...rest}
|
|
78
|
+
>
|
|
79
|
+
<slot />
|
|
80
|
+
</div>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLTag, Polymorphic } from "astro/types";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
type Props<Tag extends HTMLTag> = Polymorphic<{ as: Tag }> & {
|
|
6
|
+
/**
|
|
7
|
+
* Whether the item is inset (has left padding)
|
|
8
|
+
*/
|
|
9
|
+
inset?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Whether the item is disabled
|
|
12
|
+
*/
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const dropdownItem = tv({
|
|
17
|
+
base: [
|
|
18
|
+
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 transition-colors outline-none select-none",
|
|
19
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
20
|
+
"[&>svg]:size-4 [&>svg]:shrink-0",
|
|
21
|
+
],
|
|
22
|
+
variants: {
|
|
23
|
+
inset: {
|
|
24
|
+
true: "pl-8",
|
|
25
|
+
},
|
|
26
|
+
disabled: {
|
|
27
|
+
true: "pointer-events-none opacity-50",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
defaultVariants: {
|
|
31
|
+
inset: false,
|
|
32
|
+
disabled: false,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const { class: className, inset = false, disabled = false, as: Tag = "div", ...rest } = Astro.props;
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
<Tag
|
|
40
|
+
class={dropdownItem({ inset, disabled, class: className })}
|
|
41
|
+
role="menuitem"
|
|
42
|
+
tabindex={disabled ? "-1" : "0"}
|
|
43
|
+
data-disabled={disabled ? "true" : undefined}
|
|
44
|
+
{...rest}
|
|
45
|
+
>
|
|
46
|
+
<slot />
|
|
47
|
+
</Tag>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
type Props = HTMLAttributes<"div"> & {
|
|
6
|
+
/**
|
|
7
|
+
* Whether the label is inset (has left padding)
|
|
8
|
+
*/
|
|
9
|
+
inset?: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const dropdownLabel = tv({
|
|
13
|
+
base: ["px-2 py-1.5 font-semibold"],
|
|
14
|
+
variants: {
|
|
15
|
+
inset: {
|
|
16
|
+
true: "pl-8",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
inset: false,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const { class: className, inset = false, ...rest } = Astro.props;
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
<div class={dropdownLabel({ inset, class: className })} {...rest}>
|
|
28
|
+
<slot />
|
|
29
|
+
</div>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
type Props = HTMLAttributes<"div">;
|
|
6
|
+
|
|
7
|
+
const dropdownSeparator = tv({
|
|
8
|
+
base: "bg-muted -mx-1 my-1 h-px",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const { class: className, ...rest } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<div
|
|
15
|
+
class={dropdownSeparator({ class: className })}
|
|
16
|
+
role="separator"
|
|
17
|
+
aria-orientation="horizontal"
|
|
18
|
+
{...rest}
|
|
19
|
+
>
|
|
20
|
+
</div>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
type Props = Omit<HTMLAttributes<"button">, "role" | "type"> & {
|
|
6
|
+
/**
|
|
7
|
+
* When true, the component will render its child element with a simple wrapper instead of a button component
|
|
8
|
+
*/
|
|
9
|
+
asChild?: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const dropdownTrigger = tv({
|
|
13
|
+
base: [
|
|
14
|
+
"starwind-dropdown-trigger",
|
|
15
|
+
"inline-flex items-center justify-center",
|
|
16
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
17
|
+
],
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const { class: className, asChild = false, ...rest } = Astro.props;
|
|
21
|
+
|
|
22
|
+
// Get the first child element if asChild is true
|
|
23
|
+
let hasChildren = false;
|
|
24
|
+
if (Astro.slots.has("default")) {
|
|
25
|
+
hasChildren = true;
|
|
26
|
+
}
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
{
|
|
30
|
+
asChild && hasChildren ? (
|
|
31
|
+
<div class="starwind-dropdown-trigger" data-as-child>
|
|
32
|
+
<slot />
|
|
33
|
+
</div>
|
|
34
|
+
) : (
|
|
35
|
+
<button
|
|
36
|
+
class={dropdownTrigger({ class: className })}
|
|
37
|
+
type="button"
|
|
38
|
+
role="button"
|
|
39
|
+
aria-haspopup="true"
|
|
40
|
+
aria-expanded="false"
|
|
41
|
+
data-state="closed"
|
|
42
|
+
{...rest}
|
|
43
|
+
>
|
|
44
|
+
<slot />
|
|
45
|
+
</button>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Dropdown from "./Dropdown.astro";
|
|
2
|
+
import DropdownTrigger from "./DropdownTrigger.astro";
|
|
3
|
+
import DropdownContent from "./DropdownContent.astro";
|
|
4
|
+
import DropdownItem from "./DropdownItem.astro";
|
|
5
|
+
import DropdownLabel from "./DropdownLabel.astro";
|
|
6
|
+
import DropdownSeparator from "./DropdownSeparator.astro";
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
Dropdown,
|
|
10
|
+
DropdownTrigger,
|
|
11
|
+
DropdownContent,
|
|
12
|
+
DropdownItem,
|
|
13
|
+
DropdownLabel,
|
|
14
|
+
DropdownSeparator,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default {
|
|
18
|
+
Root: Dropdown,
|
|
19
|
+
Trigger: DropdownTrigger,
|
|
20
|
+
Content: DropdownContent,
|
|
21
|
+
Item: DropdownItem,
|
|
22
|
+
Label: DropdownLabel,
|
|
23
|
+
Separator: DropdownSeparator,
|
|
24
|
+
};
|