@starwind-ui/core 1.11.2 → 1.12.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
@@ -11,13 +11,19 @@ var registry_default = {
11
11
  {
12
12
  name: "alert-dialog",
13
13
  type: "component",
14
- version: "1.0.1",
14
+ version: "1.0.2",
15
15
  dependencies: ["@starwind-ui/core/button@^2.1.0"]
16
16
  },
17
17
  { name: "aspect-ratio", type: "component", version: "1.0.0", dependencies: [] },
18
18
  { name: "avatar", type: "component", version: "1.2.1", dependencies: [] },
19
19
  { name: "badge", type: "component", version: "1.3.0", dependencies: [] },
20
20
  { name: "breadcrumb", type: "component", version: "1.1.1", dependencies: [] },
21
+ {
22
+ name: "button-group",
23
+ type: "component",
24
+ version: "1.0.0",
25
+ dependencies: ["@starwind-ui/core/separator@^1.0.0"]
26
+ },
21
27
  { name: "button", type: "component", version: "2.2.0", dependencies: [] },
22
28
  { name: "card", type: "component", version: "1.3.0", dependencies: [] },
23
29
  {
@@ -28,7 +34,7 @@ var registry_default = {
28
34
  },
29
35
  { name: "checkbox", type: "component", version: "1.4.0", dependencies: [] },
30
36
  { name: "dialog", type: "component", version: "1.4.1", dependencies: [] },
31
- { name: "dropdown", type: "component", version: "1.2.1", dependencies: [] },
37
+ { name: "dropdown", type: "component", version: "1.2.2", dependencies: [] },
32
38
  { name: "dropzone", type: "component", version: "1.2.0", dependencies: [] },
33
39
  { name: "input", type: "component", version: "1.3.0", dependencies: [] },
34
40
  {
@@ -39,10 +45,10 @@ var registry_default = {
39
45
  },
40
46
  { name: "kbd", type: "component", version: "1.0.0", dependencies: [] },
41
47
  { name: "label", type: "component", version: "1.2.0", dependencies: [] },
42
- { name: "pagination", type: "component", version: "3.0.1", dependencies: [] },
48
+ { name: "pagination", type: "component", version: "3.0.2", dependencies: [] },
43
49
  { name: "progress", type: "component", version: "1.1.0", dependencies: [] },
44
50
  { name: "radio-group", type: "component", version: "1.2.2", dependencies: [] },
45
- { name: "select", type: "component", version: "1.6.1", dependencies: [] },
51
+ { name: "select", type: "component", version: "1.7.0", dependencies: [] },
46
52
  { name: "separator", type: "component", version: "1.0.0", dependencies: [] },
47
53
  {
48
54
  name: "sheet",
@@ -56,6 +62,7 @@ var registry_default = {
56
62
  { name: "table", type: "component", version: "1.1.0", dependencies: [] },
57
63
  { name: "tabs", type: "component", version: "1.4.0", dependencies: [] },
58
64
  { name: "textarea", type: "component", version: "1.3.0", dependencies: [] },
65
+ { name: "toggle", type: "component", version: "1.0.0", dependencies: [] },
59
66
  { name: "tooltip", type: "component", version: "1.3.0", dependencies: [] }
60
67
  ]
61
68
  };
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\";\n\nimport componentRegistry from \"./registry.json\" with { type: \"json\" };\n\n/**\n * Component metadata interface describing a Starwind UI component\n */\nexport interface ComponentMeta {\n name: string;\n version: string;\n type: \"component\";\n dependencies: string[];\n}\n\n/**\n * Registry interface containing all available components\n */\nexport interface Registry {\n components: 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 // In production (when installed as a dependency), the components will be in dist/src/components\n // In development, they will be in src/components\n const componentsDir = __dirname.includes(\"dist\") ? \"src/components\" : \"src/components\";\n return 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 \"$schema\": \"https://starwind.dev/registry-schema.json\",\n \"components\": [\n { \"name\": \"accordion\", \"type\": \"component\", \"version\": \"1.3.2\", \"dependencies\": [] },\n { \"name\": \"alert\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n {\n \"name\": \"alert-dialog\",\n \"type\": \"component\",\n \"version\": \"1.0.1\",\n \"dependencies\": [\"@starwind-ui/core/button@^2.1.0\"]\n },\n { \"name\": \"aspect-ratio\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n { \"name\": \"avatar\", \"type\": \"component\", \"version\": \"1.2.1\", \"dependencies\": [] },\n { \"name\": \"badge\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n { \"name\": \"breadcrumb\", \"type\": \"component\", \"version\": \"1.1.1\", \"dependencies\": [] },\n { \"name\": \"button\", \"type\": \"component\", \"version\": \"2.2.0\", \"dependencies\": [] },\n { \"name\": \"card\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n {\n \"name\": \"carousel\",\n \"type\": \"component\",\n \"version\": \"1.0.1\",\n \"dependencies\": [\"@starwind-ui/core/button@^2.1.0\", \"embla-carousel@^8.6.0\"]\n },\n { \"name\": \"checkbox\", \"type\": \"component\", \"version\": \"1.4.0\", \"dependencies\": [] },\n { \"name\": \"dialog\", \"type\": \"component\", \"version\": \"1.4.1\", \"dependencies\": [] },\n { \"name\": \"dropdown\", \"type\": \"component\", \"version\": \"1.2.1\", \"dependencies\": [] },\n { \"name\": \"dropzone\", \"type\": \"component\", \"version\": \"1.2.0\", \"dependencies\": [] },\n { \"name\": \"input\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n {\n \"name\": \"item\",\n \"type\": \"component\",\n \"version\": \"1.0.0\",\n \"dependencies\": [\"@starwind-ui/core/separator@^1.0.0\"]\n },\n { \"name\": \"kbd\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n { \"name\": \"label\", \"type\": \"component\", \"version\": \"1.2.0\", \"dependencies\": [] },\n { \"name\": \"pagination\", \"type\": \"component\", \"version\": \"3.0.1\", \"dependencies\": [] },\n { \"name\": \"progress\", \"type\": \"component\", \"version\": \"1.1.0\", \"dependencies\": [] },\n { \"name\": \"radio-group\", \"type\": \"component\", \"version\": \"1.2.2\", \"dependencies\": [] },\n { \"name\": \"select\", \"type\": \"component\", \"version\": \"1.6.1\", \"dependencies\": [] },\n { \"name\": \"separator\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n {\n \"name\": \"sheet\",\n \"type\": \"component\",\n \"version\": \"1.1.1\",\n \"dependencies\": [\"@starwind-ui/core/dialog@^1.3.0\"]\n },\n { \"name\": \"skeleton\", \"type\": \"component\", \"version\": \"1.2.0\", \"dependencies\": [] },\n { \"name\": \"spinner\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n { \"name\": \"switch\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n { \"name\": \"table\", \"type\": \"component\", \"version\": \"1.1.0\", \"dependencies\": [] },\n { \"name\": \"tabs\", \"type\": \"component\", \"version\": \"1.4.0\", \"dependencies\": [] },\n { \"name\": \"textarea\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n { \"name\": \"tooltip\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] }\n ]\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/registry.json"],"sourcesContent":["import { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport componentRegistry from \"./registry.json\" with { type: \"json\" };\n\n/**\n * Component metadata interface describing a Starwind UI component\n */\nexport interface ComponentMeta {\n name: string;\n version: string;\n type: \"component\";\n dependencies: string[];\n}\n\n/**\n * Registry interface containing all available components\n */\nexport interface Registry {\n components: 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 // In production (when installed as a dependency), the components will be in dist/src/components\n // In development, they will be in src/components\n const componentsDir = __dirname.includes(\"dist\") ? \"src/components\" : \"src/components\";\n return 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 \"$schema\": \"https://starwind.dev/registry-schema.json\",\n \"components\": [\n { \"name\": \"accordion\", \"type\": \"component\", \"version\": \"1.3.2\", \"dependencies\": [] },\n { \"name\": \"alert\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n {\n \"name\": \"alert-dialog\",\n \"type\": \"component\",\n \"version\": \"1.0.2\",\n \"dependencies\": [\"@starwind-ui/core/button@^2.1.0\"]\n },\n { \"name\": \"aspect-ratio\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n { \"name\": \"avatar\", \"type\": \"component\", \"version\": \"1.2.1\", \"dependencies\": [] },\n { \"name\": \"badge\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n { \"name\": \"breadcrumb\", \"type\": \"component\", \"version\": \"1.1.1\", \"dependencies\": [] },\n {\n \"name\": \"button-group\",\n \"type\": \"component\",\n \"version\": \"1.0.0\",\n \"dependencies\": [\"@starwind-ui/core/separator@^1.0.0\"]\n },\n { \"name\": \"button\", \"type\": \"component\", \"version\": \"2.2.0\", \"dependencies\": [] },\n { \"name\": \"card\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n {\n \"name\": \"carousel\",\n \"type\": \"component\",\n \"version\": \"1.0.1\",\n \"dependencies\": [\"@starwind-ui/core/button@^2.1.0\", \"embla-carousel@^8.6.0\"]\n },\n { \"name\": \"checkbox\", \"type\": \"component\", \"version\": \"1.4.0\", \"dependencies\": [] },\n { \"name\": \"dialog\", \"type\": \"component\", \"version\": \"1.4.1\", \"dependencies\": [] },\n { \"name\": \"dropdown\", \"type\": \"component\", \"version\": \"1.2.2\", \"dependencies\": [] },\n { \"name\": \"dropzone\", \"type\": \"component\", \"version\": \"1.2.0\", \"dependencies\": [] },\n { \"name\": \"input\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n {\n \"name\": \"item\",\n \"type\": \"component\",\n \"version\": \"1.0.0\",\n \"dependencies\": [\"@starwind-ui/core/separator@^1.0.0\"]\n },\n { \"name\": \"kbd\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n { \"name\": \"label\", \"type\": \"component\", \"version\": \"1.2.0\", \"dependencies\": [] },\n { \"name\": \"pagination\", \"type\": \"component\", \"version\": \"3.0.2\", \"dependencies\": [] },\n { \"name\": \"progress\", \"type\": \"component\", \"version\": \"1.1.0\", \"dependencies\": [] },\n { \"name\": \"radio-group\", \"type\": \"component\", \"version\": \"1.2.2\", \"dependencies\": [] },\n { \"name\": \"select\", \"type\": \"component\", \"version\": \"1.7.0\", \"dependencies\": [] },\n { \"name\": \"separator\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n {\n \"name\": \"sheet\",\n \"type\": \"component\",\n \"version\": \"1.1.1\",\n \"dependencies\": [\"@starwind-ui/core/dialog@^1.3.0\"]\n },\n { \"name\": \"skeleton\", \"type\": \"component\", \"version\": \"1.2.0\", \"dependencies\": [] },\n { \"name\": \"spinner\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n { \"name\": \"switch\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n { \"name\": \"table\", \"type\": \"component\", \"version\": \"1.1.0\", \"dependencies\": [] },\n { \"name\": \"tabs\", \"type\": \"component\", \"version\": \"1.4.0\", \"dependencies\": [] },\n { \"name\": \"textarea\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] },\n { \"name\": \"toggle\", \"type\": \"component\", \"version\": \"1.0.0\", \"dependencies\": [] },\n { \"name\": \"tooltip\", \"type\": \"component\", \"version\": \"1.3.0\", \"dependencies\": [] }\n ]\n}\n"],"mappings":"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","names":[]}
@@ -33,7 +33,7 @@ if (Astro.slots.has("default")) {
33
33
  type="button"
34
34
  class={ButtonVariants.button({
35
35
  variant: "default",
36
- class: `starwind-alert-dialog-action ${className}`,
36
+ class: `starwind-alert-dialog-action ${className || ""}`,
37
37
  })}
38
38
  data-slot="alert-dialog-action"
39
39
  {...rest}
@@ -34,7 +34,7 @@ if (Astro.slots.has("default")) {
34
34
  type="button"
35
35
  class={ButtonVariants.button({
36
36
  variant: "outline",
37
- class: `starwind-alert-dialog-close ${className}`,
37
+ class: `starwind-alert-dialog-close ${className || ""}`,
38
38
  })}
39
39
  data-slot="alert-dialog-cancel"
40
40
  {...rest}
@@ -0,0 +1,62 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+ import { tv } from "tailwind-variants";
4
+
5
+ type Props = HTMLAttributes<"div"> & {
6
+ /**
7
+ * The orientation of the button group.
8
+ * @default "horizontal"
9
+ */
10
+ orientation?: "horizontal" | "vertical";
11
+ };
12
+
13
+ export const buttonGroup = tv({
14
+ base: [
15
+ "flex w-fit items-stretch",
16
+ "[&>*]:focus-visible:relative [&>*]:focus-visible:z-10",
17
+ "has-[>[data-slot=button-group]]:gap-2 [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
18
+ ],
19
+ variants: {
20
+ orientation: {
21
+ horizontal: [
22
+ "[&>*:not(:first-child)]:rounded-l-none",
23
+ "[&>*:not(:first-child)]:border-l-0",
24
+ "[&>*:not(:last-child):not(:has(+_script:last-child))]:rounded-r-none",
25
+ "[&>*:not(:first-child)_>_[data-as-child]_>_*]:rounded-l-none",
26
+ "[&>*:not(:first-child)_>_[data-as-child]_>_*]:border-l-0",
27
+ "[&>*:not(:last-child):not(:has(+_script:last-child))_>_[data-as-child]_>_*]:rounded-r-none",
28
+ "[&>*:not(:first-child)_>_[data-slot=select-trigger]]:rounded-l-none",
29
+ "[&>*:not(:first-child)_>_[data-slot=select-trigger]]:border-l-0",
30
+ "[&>*:not(:last-child):not(:has(+_script:last-child))_>_[data-slot=select-trigger]]:rounded-r-none",
31
+ ],
32
+ vertical: [
33
+ "flex-col",
34
+ "[&>*:not(:first-child)]:rounded-t-none",
35
+ "[&>*:not(:first-child)]:border-t-0",
36
+ "[&>*:not(:last-child):not(:has(+_script:last-child))]:rounded-b-none",
37
+ "[&>*:not(:first-child)_>_[data-as-child]_>_*]:rounded-t-none",
38
+ "[&>*:not(:first-child)_>_[data-as-child]_>_*]:border-t-0",
39
+ "[&>*:not(:last-child):not(:has(+_script:last-child))_>_[data-as-child]_>_*]:rounded-b-none",
40
+ "[&>*:not(:first-child)_>_[data-slot=select-trigger]]:rounded-t-none",
41
+ "[&>*:not(:first-child)_>_[data-slot=select-trigger]]:border-t-0",
42
+ "[&>*:not(:last-child):not(:has(+_script:last-child))_>_[data-slot=select-trigger]]:rounded-b-none",
43
+ ],
44
+ },
45
+ },
46
+ defaultVariants: {
47
+ orientation: "horizontal",
48
+ },
49
+ });
50
+
51
+ const { class: className, orientation = "horizontal", ...rest } = Astro.props;
52
+ ---
53
+
54
+ <div
55
+ role="group"
56
+ data-slot="button-group"
57
+ data-orientation={orientation}
58
+ class={buttonGroup({ orientation, class: className })}
59
+ {...rest}
60
+ >
61
+ <slot />
62
+ </div>
@@ -0,0 +1,27 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+ import { tv } from "tailwind-variants";
4
+
5
+ import { Separator } from "@/components/starwind/separator";
6
+
7
+ type Props = HTMLAttributes<"div"> & {
8
+ /**
9
+ * The orientation of the separator.
10
+ * @default "vertical"
11
+ */
12
+ orientation?: "horizontal" | "vertical";
13
+ };
14
+
15
+ export const buttonGroupSeparator = tv({
16
+ base: ["bg-input relative m-0! self-stretch data-[orientation=vertical]:h-auto"],
17
+ });
18
+
19
+ const { class: className, orientation = "vertical", ...rest } = Astro.props;
20
+ ---
21
+
22
+ <Separator
23
+ data-slot="button-group-separator"
24
+ orientation={orientation}
25
+ class={buttonGroupSeparator({ class: className })}
26
+ {...rest}
27
+ />
@@ -0,0 +1,19 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+ import { tv } from "tailwind-variants";
4
+
5
+ type Props = HTMLAttributes<"div">;
6
+
7
+ export const buttonGroupText = tv({
8
+ base: [
9
+ "bg-muted flex items-center gap-2 rounded-md border px-4 text-sm font-medium shadow-xs",
10
+ "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
11
+ ],
12
+ });
13
+
14
+ const { class: className, ...rest } = Astro.props;
15
+ ---
16
+
17
+ <div class:list={[buttonGroupText(), className]} data-slot="button-group-text" {...rest}>
18
+ <slot />
19
+ </div>
@@ -0,0 +1,17 @@
1
+ import ButtonGroup, { buttonGroup } from "./ButtonGroup.astro";
2
+ import ButtonGroupSeparator, { buttonGroupSeparator } from "./ButtonGroupSeparator.astro";
3
+ import ButtonGroupText, { buttonGroupText } from "./ButtonGroupText.astro";
4
+
5
+ const ButtonGroupVariants = {
6
+ buttonGroup,
7
+ buttonGroupSeparator,
8
+ buttonGroupText,
9
+ };
10
+
11
+ export { ButtonGroup, ButtonGroupSeparator, ButtonGroupText, ButtonGroupVariants };
12
+
13
+ export default {
14
+ Root: ButtonGroup,
15
+ Separator: ButtonGroupSeparator,
16
+ Text: ButtonGroupText,
17
+ };
@@ -30,7 +30,7 @@ if (Astro.slots.has("default")) {
30
30
  {
31
31
  asChild && hasChildren ? (
32
32
  <div
33
- class={`starwind-dropdown-trigger ${className}`}
33
+ class:list={["starwind-dropdown-trigger", className]}
34
34
  data-slot="dropdown-trigger"
35
35
  data-as-child
36
36
  >
@@ -1,18 +1,28 @@
1
1
  ---
2
2
  import Dots from "@tabler/icons/outline/dots.svg";
3
3
  import type { HTMLAttributes } from "astro/types";
4
- import { tv } from "tailwind-variants";
4
+ import { tv, type VariantProps } from "tailwind-variants";
5
5
 
6
- type Props = HTMLAttributes<"span">;
6
+ type Props = HTMLAttributes<"span"> & VariantProps<typeof paginationEllipsis>;
7
7
 
8
- export const paginationEllipsis = tv({ base: "flex h-9 w-9 items-center justify-center" });
8
+ export const paginationEllipsis = tv({
9
+ base: "flex items-center justify-center",
10
+ variants: {
11
+ size: {
12
+ "icon-sm": "size-9",
13
+ icon: "size-11",
14
+ "icon-lg": "size-12",
15
+ },
16
+ },
17
+ defaultVariants: { size: "icon" },
18
+ });
9
19
 
10
- const { class: className, ...rest } = Astro.props;
20
+ const { class: className, size, ...rest } = Astro.props;
11
21
  ---
12
22
 
13
23
  <span
14
24
  aria-hidden
15
- class={paginationEllipsis({ class: className })}
25
+ class={paginationEllipsis({ size, class: className })}
16
26
  data-slot="pagination-ellipsis"
17
27
  {...rest}
18
28
  >
@@ -1,11 +1,15 @@
1
1
  ---
2
2
  import ChevronRight from "@tabler/icons/outline/chevron-right.svg";
3
3
  import type { HTMLAttributes } from "astro/types";
4
- import { tv } from "tailwind-variants";
4
+ import { tv, type VariantProps } from "tailwind-variants";
5
+
6
+ import { ButtonVariants } from "@/components/starwind/button";
5
7
 
6
8
  import PaginationLink from "./PaginationLink.astro";
7
9
 
8
- type Props = HTMLAttributes<"a"> & { size?: "sm" | "md" | "lg" | "icon" };
10
+ type Props = HTMLAttributes<"a"> & {
11
+ size?: VariantProps<typeof ButtonVariants.button>["size"];
12
+ };
9
13
 
10
14
  export const paginationNext = tv({ base: "group gap-1" });
11
15
 
@@ -1,11 +1,15 @@
1
1
  ---
2
2
  import ChevronLeft from "@tabler/icons/outline/chevron-left.svg";
3
3
  import type { HTMLAttributes } from "astro/types";
4
- import { tv } from "tailwind-variants";
4
+ import { tv, type VariantProps } from "tailwind-variants";
5
+
6
+ import { ButtonVariants } from "@/components/starwind/button";
5
7
 
6
8
  import PaginationLink from "./PaginationLink.astro";
7
9
 
8
- type Props = HTMLAttributes<"a"> & { size?: "sm" | "md" | "lg" | "icon" };
10
+ type Props = HTMLAttributes<"a"> & {
11
+ size?: VariantProps<typeof ButtonVariants.button>["size"];
12
+ };
9
13
 
10
14
  export const paginationPrevious = tv({ base: "group gap-1" });
11
15
 
@@ -34,8 +34,11 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
34
34
  private select: HTMLElement;
35
35
  private trigger: HTMLButtonElement | null;
36
36
  private content: HTMLElement | null;
37
+ private searchInput: HTMLInputElement | null = null;
38
+ private emptyElement: HTMLElement | null = null;
37
39
  private isOpen: boolean = false;
38
40
  private selectedItem: HTMLElement | null = null;
41
+ private activeItem: HTMLElement | null = null;
39
42
  private animationDuration = 150;
40
43
  private typeaheadTimerRef: number | null = null;
41
44
  private typeaheadSearch = "";
@@ -45,6 +48,8 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
45
48
  this.select = select;
46
49
  this.trigger = select.querySelector(".starwind-select-trigger");
47
50
  this.content = select.querySelector(".starwind-select-content");
51
+ this.searchInput = select.querySelector('[data-slot="select-search"]');
52
+ this.emptyElement = select.querySelector('[data-slot="select-empty"]');
48
53
 
49
54
  if (!this.trigger || !this.content) return;
50
55
 
@@ -118,11 +123,32 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
118
123
  // Set up additional ARIA attributes
119
124
  this.trigger.setAttribute("aria-controls", this.content.id);
120
125
  this.content.setAttribute("aria-labelledby", this.trigger.id);
126
+
127
+ // If search input exists, add IDs to all items for aria-activedescendant
128
+ if (this.searchInput) {
129
+ if (!this.searchInput.id) {
130
+ this.searchInput.id = `starwind-select${selectIdx}-search`;
131
+ }
132
+ // Link search input to the listbox it filters
133
+ this.searchInput.setAttribute("aria-controls", this.content.id);
134
+
135
+ const items = this.content.querySelectorAll('[role="option"]');
136
+ items.forEach((item, index) => {
137
+ if (!item.id) {
138
+ item.id = `starwind-select${selectIdx}-option${index}`;
139
+ }
140
+ });
141
+ }
121
142
  }
122
143
 
123
144
  private setupEvents() {
124
145
  if (!this.trigger || !this.content) return;
125
146
 
147
+ // Handle search input if it exists
148
+ if (this.searchInput) {
149
+ this.setupSearchInput();
150
+ }
151
+
126
152
  // Handle pointerdown
127
153
  this.trigger.addEventListener("pointerdown", (e) => {
128
154
  // prevent implicit pointer capture
@@ -164,11 +190,18 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
164
190
 
165
191
  // Handle keyboard navigation inside select content
166
192
  this.content.addEventListener("keydown", (e) => {
193
+ // Check if the event originated from the search input
194
+ const isFromSearchInput = e.target === this.searchInput;
195
+
167
196
  if (e.key === "Enter" || e.key === " ") {
168
- // set element based on current focused element
169
- const activeElement = document.activeElement;
170
- this.returnFocusOnClose = true;
171
- this.handleSelection(activeElement as HTMLElement);
197
+ // Only handle selection if not typing in search input
198
+ if (!isFromSearchInput) {
199
+ // set element based on current focused element
200
+ const activeElement = document.activeElement;
201
+ this.returnFocusOnClose = true;
202
+ this.handleSelection(activeElement as HTMLElement);
203
+ }
204
+ // If from search input, don't handle it here - let the input handle it naturally
172
205
  } else if (e.key === "Escape" && this.isOpen) {
173
206
  this.returnFocusOnClose = true;
174
207
  this.closeSelect();
@@ -188,7 +221,7 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
188
221
  // select should not be navigated using tab key so we prevent it
189
222
  if (e.key === "Tab") e.preventDefault();
190
223
 
191
- if (!isModifierKey && e.key.length === 1) {
224
+ if (!isModifierKey && e.key.length === 1 && !this.searchInput) {
192
225
  this.handleTypeahead(e.key);
193
226
  }
194
227
  }
@@ -199,7 +232,7 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
199
232
  const target = e.target as HTMLElement;
200
233
  const option = target.closest('[role="option"]');
201
234
  if (option && option instanceof HTMLElement && this.isOpen === true) {
202
- option.focus();
235
+ this.setActiveItem(option);
203
236
  }
204
237
  });
205
238
 
@@ -259,50 +292,181 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
259
292
  });
260
293
  }
261
294
 
262
- private handleNavigationKeys(e: KeyboardEvent) {
295
+ private setActiveItem(item: HTMLElement | null) {
263
296
  if (!this.content) return;
264
- const items = this.content.querySelectorAll('[role="option"]');
265
297
 
266
- // current, or first item, is focused upon opening the select
267
- const activeElement = document.activeElement;
268
- const currentIndex = Array.from(items).indexOf(activeElement as HTMLElement);
269
- if (e.key === "Home") {
270
- const firstEnabledItem = Array.from(items).find(
271
- (item) => item.getAttribute("data-disabled") !== "true",
272
- ) as HTMLElement;
273
- if (firstEnabledItem) {
274
- firstEnabledItem.focus();
298
+ // Remove active state from previous item
299
+ if (this.activeItem) {
300
+ this.activeItem.removeAttribute("data-active");
301
+ }
302
+
303
+ // Set new active item
304
+ this.activeItem = item;
305
+ if (item) {
306
+ item.setAttribute("data-active", "true");
307
+
308
+ // Scroll item into view if needed
309
+ item.scrollIntoView({ block: "nearest" });
310
+
311
+ // For search mode, set aria-activedescendant for assistive technologies
312
+ if (this.searchInput) {
313
+ // Item should already have an ID from setupAccessibility
314
+ this.searchInput.setAttribute("aria-activedescendant", item.id);
315
+ } else {
316
+ // For non-search mode, set focus for keyboard accessibility
317
+ item.focus();
275
318
  }
276
- return;
319
+ } else if (this.searchInput) {
320
+ // Clear aria-activedescendant when no item is active
321
+ this.searchInput.removeAttribute("aria-activedescendant");
277
322
  }
278
- if (e.key === "End") {
279
- const lastEnabledItem = Array.from(items)
280
- .reverse()
281
- .find((item) => item.getAttribute("data-disabled") !== "true") as HTMLElement;
282
- if (lastEnabledItem) {
283
- lastEnabledItem.focus();
323
+ }
324
+
325
+ private findNavigableItem(
326
+ items: NodeListOf<Element>,
327
+ startIndex: number,
328
+ direction: "forward" | "backward",
329
+ ): HTMLElement | null {
330
+ const step = direction === "forward" ? 1 : -1;
331
+ const end = direction === "forward" ? items.length : -1;
332
+
333
+ for (let i = startIndex; i !== end; i += step) {
334
+ const item = items[i] as HTMLElement;
335
+ if (
336
+ item.getAttribute("data-disabled") !== "true" &&
337
+ item.getAttribute("data-filtered") !== "true"
338
+ ) {
339
+ return item;
284
340
  }
285
- return;
286
341
  }
287
- if (e.key === "ArrowUp" && currentIndex > 0) {
288
- for (let i = currentIndex - 1; i >= 0; i--) {
289
- const item = items[i] as HTMLElement;
290
- if (item.getAttribute("data-disabled") !== "true") {
291
- item.focus();
292
- break;
342
+ return null;
343
+ }
344
+
345
+ private handleNavigationKeys(e: KeyboardEvent) {
346
+ if (!this.content) return;
347
+ const items = this.content.querySelectorAll('[role="option"]');
348
+ const currentIndex = Array.from(items).indexOf(this.activeItem as HTMLElement);
349
+
350
+ let targetItem: HTMLElement | null = null;
351
+
352
+ switch (e.key) {
353
+ case "Home":
354
+ targetItem = this.findNavigableItem(items, 0, "forward");
355
+ break;
356
+
357
+ case "End":
358
+ targetItem = this.findNavigableItem(items, items.length - 1, "backward");
359
+ break;
360
+
361
+ case "ArrowUp":
362
+ if (currentIndex > 0) {
363
+ targetItem = this.findNavigableItem(items, currentIndex - 1, "backward");
364
+ }
365
+ break;
366
+
367
+ case "ArrowDown":
368
+ if (currentIndex < items.length - 1) {
369
+ targetItem = this.findNavigableItem(items, currentIndex + 1, "forward");
370
+ }
371
+ break;
372
+ }
373
+
374
+ if (targetItem) {
375
+ this.setActiveItem(targetItem);
376
+ }
377
+ }
378
+
379
+ private setupSearchInput() {
380
+ if (!this.searchInput || !this.content) return;
381
+
382
+ this.searchInput.addEventListener("input", (e) => {
383
+ const target = e.target as HTMLInputElement;
384
+ const searchValue = target.value.toLowerCase().trim();
385
+ this.filterItems(searchValue);
386
+ });
387
+
388
+ // Handle keyboard navigation from search input
389
+ this.searchInput.addEventListener("keydown", (e) => {
390
+ if (e.key === "Escape") {
391
+ e.stopPropagation();
392
+ this.returnFocusOnClose = true;
393
+ this.closeSelect();
394
+ }
395
+ // Allow arrow keys to navigate to items
396
+ else if (["ArrowUp", "ArrowDown", "Home", "End"].includes(e.key)) {
397
+ e.preventDefault();
398
+ e.stopPropagation();
399
+ this.handleNavigationKeys(e);
400
+ }
401
+ // Handle Enter to select the active or first visible item
402
+ else if (e.key === "Enter") {
403
+ e.preventDefault();
404
+ const itemToSelect = this.activeItem || this.getFirstVisibleItem();
405
+ if (itemToSelect) {
406
+ this.returnFocusOnClose = true;
407
+ this.handleSelection(itemToSelect);
293
408
  }
294
409
  }
295
- return;
410
+ // Prevent space from scrolling the page, but allow it to be typed
411
+ else if (e.key === " ") {
412
+ e.stopPropagation();
413
+ }
414
+ });
415
+ }
416
+
417
+ private getFirstVisibleItem(): HTMLElement | null {
418
+ if (!this.content) return null;
419
+
420
+ const items = this.content.querySelectorAll('[role="option"]');
421
+ for (const item of items) {
422
+ if (
423
+ item.getAttribute("data-disabled") !== "true" &&
424
+ item.getAttribute("data-filtered") !== "true"
425
+ ) {
426
+ return item as HTMLElement;
427
+ }
296
428
  }
297
- if (e.key === "ArrowDown" && currentIndex < items.length - 1) {
298
- for (let i = currentIndex + 1; i < items.length; i++) {
299
- const item = items[i] as HTMLElement;
300
- if (item.getAttribute("data-disabled") !== "true") {
301
- item.focus();
302
- break;
429
+ return null;
430
+ }
431
+
432
+ private filterItems(searchValue: string) {
433
+ if (!this.content) return;
434
+
435
+ const items = this.content.querySelectorAll('[role="option"]');
436
+ let visibleCount = 0;
437
+ let firstVisibleItem: HTMLElement | null = null;
438
+
439
+ items.forEach((item) => {
440
+ const itemText = item.textContent?.toLowerCase().trim() || "";
441
+ const matches = itemText.includes(searchValue);
442
+
443
+ if (matches || searchValue === "") {
444
+ item.classList.remove("starwind-sr-only");
445
+ item.removeAttribute("data-filtered");
446
+ visibleCount++;
447
+ if (!firstVisibleItem) {
448
+ firstVisibleItem = item as HTMLElement;
303
449
  }
450
+ } else {
451
+ item.classList.add("starwind-sr-only");
452
+ item.setAttribute("data-filtered", "true");
453
+ }
454
+ });
455
+
456
+ // Update active item to first visible item after filtering
457
+ if (this.searchInput && firstVisibleItem) {
458
+ this.setActiveItem(firstVisibleItem);
459
+ } else if (this.searchInput && visibleCount === 0) {
460
+ this.setActiveItem(null);
461
+ }
462
+
463
+ // Show/hide empty message
464
+ if (this.emptyElement) {
465
+ if (visibleCount === 0 && searchValue !== "") {
466
+ this.emptyElement.classList.remove("hidden");
467
+ } else {
468
+ this.emptyElement.classList.add("hidden");
304
469
  }
305
- return;
306
470
  }
307
471
  }
308
472
 
@@ -311,12 +475,12 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
311
475
  const search = this.typeaheadSearch + key;
312
476
  const items = this.content.querySelectorAll('[role="option"]');
313
477
 
314
- // find and focus the first matching option
478
+ // find and set active the first matching option
315
479
  const matches = Array.from(items).filter((item) =>
316
480
  item.textContent?.toLowerCase().trim().startsWith(search.toLowerCase()),
317
481
  ) as HTMLElement[];
318
482
  if (matches.length > 0) {
319
- matches[0].focus();
483
+ this.setActiveItem(matches[0]);
320
484
  }
321
485
 
322
486
  // update the typeahead search and reset the timer
@@ -361,18 +525,32 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
361
525
  this.trigger.setAttribute("aria-expanded", "true");
362
526
  this.content.style.removeProperty("display");
363
527
 
364
- // set focus on the current selected item
365
- if (this.selectedItem) {
366
- this.selectedItem.focus();
528
+ // If search input exists, focus it and clear any previous search
529
+ if (this.searchInput) {
530
+ this.searchInput.value = "";
531
+ this.filterItems("");
532
+
533
+ // Set the selected item or first item as active
534
+ const initialItem = this.selectedItem || this.getFirstVisibleItem();
535
+ if (initialItem) {
536
+ this.setActiveItem(initialItem);
537
+ }
538
+
539
+ requestAnimationFrame(() => {
540
+ this.searchInput?.focus();
541
+ });
367
542
  } else {
368
- // if no item is selected, focus on the first item
369
- const firstItem = this.content.querySelector('[role="option"]') as HTMLElement;
370
- if (firstItem) {
371
- firstItem.focus();
543
+ // set active on the current selected item
544
+ if (this.selectedItem) {
545
+ this.setActiveItem(this.selectedItem);
546
+ } else {
547
+ // if no item is selected, set active on the first item
548
+ const firstItem = this.content.querySelector('[role="option"]') as HTMLElement;
549
+ if (firstItem) {
550
+ this.setActiveItem(firstItem);
551
+ }
372
552
  }
373
553
  }
374
-
375
- this.positionContent();
376
554
  }
377
555
 
378
556
  private closeSelect() {
@@ -399,6 +577,13 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
399
577
  setTimeout(() => {
400
578
  if (!this.content) return;
401
579
  this.content.style.display = "none";
580
+
581
+ // Clear search and show all items after animation completes
582
+ if (this.searchInput) {
583
+ this.searchInput.value = "";
584
+ this.filterItems("");
585
+ this.setActiveItem(null);
586
+ }
402
587
  }, this.animationDuration);
403
588
 
404
589
  this.trigger.setAttribute("aria-expanded", "false");
@@ -478,29 +663,6 @@ const { class: className, name, defaultValue, ...rest } = Astro.props;
478
663
  this.handleSelection(item);
479
664
  }
480
665
  }
481
-
482
- /**
483
- * TODO: add position logic to avoid collisions with window boundary
484
- * It will need to switch to top or bottom depending on space available
485
- * It will also need to set the content max height so it doesn't overflow the viewport
486
- */
487
- private positionContent() {
488
- // if (!this.content || !this.trigger) return;
489
- // const triggerRect = this.trigger.getBoundingClientRect();
490
- // const contentRect = this.content.getBoundingClientRect();
491
- // const viewportHeight = window.innerHeight;
492
- // // Position the content below the trigger by default
493
- // let top = triggerRect.bottom;
494
- // // If there's not enough space below, position it above
495
- // if (top + contentRect.height > viewportHeight) {
496
- // top = triggerRect.top - contentRect.height;
497
- // }
498
- // this.content.style.position = "absolute";
499
- // this.content.style.top = `${top}px`;
500
- // this.content.style.left = `${triggerRect.left}px`;
501
- // this.content.style.width = `${triggerRect.width}px`;
502
- // this.content.style.zIndex = "50";
503
- }
504
666
  }
505
667
 
506
668
  // Store instances in a WeakMap to avoid memory leaks
@@ -23,6 +23,11 @@ type Props = HTMLAttributes<"div"> & {
23
23
  * @default 150
24
24
  */
25
25
  animationDuration?: number;
26
+ /**
27
+ * Size of the select content
28
+ * @default md
29
+ */
30
+ size?: "sm" | "md" | "lg";
26
31
  };
27
32
 
28
33
  export const selectContent = tv({
@@ -42,8 +47,13 @@ export const selectContent = tv({
42
47
  center: "left-1/2 -translate-x-1/2",
43
48
  end: "slide-in-from-right-1 slide-out-to-right-1 right-0",
44
49
  },
50
+ size: {
51
+ sm: "text-sm [&_[data-slot=select-label]]:text-xs",
52
+ md: "text-base [&_[data-slot=select-label]]:text-sm",
53
+ lg: "text-lg [&_[data-slot=select-label]]:text-base",
54
+ },
45
55
  },
46
- defaultVariants: { side: "bottom", align: "start" },
56
+ defaultVariants: { side: "bottom", align: "start", size: "md" },
47
57
  });
48
58
 
49
59
  export const selectContentInner = tv({
@@ -54,6 +64,7 @@ const {
54
64
  class: className,
55
65
  side = "bottom",
56
66
  align = "start",
67
+ size = "md",
57
68
  sideOffset = 4,
58
69
  animationDuration = 150,
59
70
  ...rest
@@ -61,7 +72,7 @@ const {
61
72
  ---
62
73
 
63
74
  <div
64
- class={selectContent({ side, align, class: className })}
75
+ class={selectContent({ side, align, size, class: className })}
65
76
  role="listbox"
66
77
  data-side={side}
67
78
  data-align={align}
@@ -16,14 +16,16 @@ type Props = HTMLAttributes<"div"> & {
16
16
 
17
17
  export const selectItem = tv({
18
18
  base: [
19
- "relative flex w-full cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 outline-none select-none",
20
- "focus:bg-accent focus:text-accent-foreground",
19
+ "relative flex w-full cursor-default items-center rounded-sm py-1.5 pr-8 pl-2 outline-none select-none",
20
+ "data-[active]:bg-accent data-[active]:text-accent-foreground",
21
21
  "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
22
22
  "not-aria-selected:[&_svg]:hidden aria-selected:[&_svg]:flex",
23
23
  ],
24
24
  });
25
25
 
26
- export const selectItemIcon = tv({ base: "absolute left-2 size-3.5 items-center justify-center" });
26
+ export const selectItemIcon = tv({
27
+ base: "absolute right-2 flex size-4 items-center justify-center",
28
+ });
27
29
 
28
30
  const { class: className, value, disabled, ...rest } = Astro.props;
29
31
  ---
@@ -38,12 +40,12 @@ const { class: className, value, disabled, ...rest } = Astro.props;
38
40
  tabindex="0"
39
41
  {...rest}
40
42
  >
43
+ <span>
44
+ <slot />
45
+ </span>
41
46
  <span class={selectItemIcon()}>
42
47
  <slot name="icon">
43
48
  <Check class="size-4" />
44
49
  </slot>
45
50
  </span>
46
- <span>
47
- <slot />
48
- </span>
49
51
  </div>
@@ -4,7 +4,7 @@ import { tv } from "tailwind-variants";
4
4
 
5
5
  type Props = HTMLAttributes<"div">;
6
6
 
7
- export const selectLabel = tv({ base: "text-muted-foreground py-1.5 pr-2 pl-8 text-sm" });
7
+ export const selectLabel = tv({ base: "text-muted-foreground py-1.5 pr-8 pl-2" });
8
8
 
9
9
  const { class: className, ...rest } = Astro.props;
10
10
  ---
@@ -0,0 +1,49 @@
1
+ ---
2
+ import Search from "@tabler/icons/outline/search.svg";
3
+ import type { HTMLAttributes } from "astro/types";
4
+ import { tv } from "tailwind-variants";
5
+
6
+ type Props = Omit<HTMLAttributes<"input">, "type" | "autocomplete"> & {
7
+ /**
8
+ * The text to display when no results are found
9
+ * @default "No results found."
10
+ */
11
+ emptyText?: string;
12
+ };
13
+
14
+ export const selectSearch = tv({
15
+ base: [
16
+ "placeholder:text-muted-foreground flex w-full border-0 bg-transparent px-0 py-2.5",
17
+ "ring-0 outline-none disabled:cursor-not-allowed disabled:opacity-50",
18
+ ],
19
+ });
20
+
21
+ const {
22
+ class: className,
23
+ placeholder = "Search...",
24
+ emptyText = "No results found.",
25
+ ...rest
26
+ } = Astro.props;
27
+ ---
28
+
29
+ <div
30
+ data-slot="select-search-wrapper"
31
+ class="-mx-1 -mt-1 mb-1 flex items-center gap-2 border-b px-3"
32
+ >
33
+ <slot name="icon">
34
+ <Search class="text-muted-foreground size-4.5 shrink-0" />
35
+ </slot>
36
+ <input
37
+ type="text"
38
+ class={selectSearch({ class: className })}
39
+ data-slot="select-search"
40
+ placeholder={placeholder}
41
+ autocomplete="off"
42
+ aria-label={placeholder}
43
+ {...rest}
44
+ />
45
+ </div>
46
+
47
+ <div class="text-muted-foreground hidden py-6 text-center text-sm" data-slot="select-empty">
48
+ <slot name="empty">{emptyText}</slot>
49
+ </div>
@@ -1,34 +1,44 @@
1
1
  ---
2
2
  import ChevronDown from "@tabler/icons/outline/chevron-down.svg";
3
3
  import type { HTMLAttributes } from "astro/types";
4
- import { tv } from "tailwind-variants";
4
+ import { tv, type VariantProps } from "tailwind-variants";
5
5
 
6
- type Props = Omit<HTMLAttributes<"button">, "role" | "type"> & {
7
- /**
8
- * The content to be rendered inside the select trigger
9
- */
10
-
11
- children: any;
12
- /**
13
- * Whether the select field is required in a form context
14
- */
15
- required?: boolean;
16
- };
6
+ type Props = Omit<HTMLAttributes<"button">, "role" | "type"> &
7
+ VariantProps<typeof selectTrigger> & {
8
+ /**
9
+ * The content to be rendered inside the select trigger
10
+ */
11
+ children: any;
12
+ /**
13
+ * Whether the select field is required in a form context
14
+ */
15
+ required?: boolean;
16
+ };
17
17
 
18
18
  export const selectTrigger = tv({
19
19
  base: [
20
20
  "starwind-select-trigger",
21
- "border-input dark:bg-input/30 text-foreground ring-offset-background flex h-11 items-center justify-between rounded-md border bg-transparent px-3 py-2 shadow-xs",
21
+ "border-input dark:bg-input/30 text-foreground ring-offset-background flex items-center justify-between gap-2 rounded-md border bg-transparent shadow-xs",
22
22
  "focus-visible:border-outline focus-visible:ring-outline/50 transition-[color,box-shadow] outline-none focus-visible:ring-3",
23
23
  "disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
24
24
  ],
25
+ variants: {
26
+ size: {
27
+ sm: "h-9 px-2 text-sm",
28
+ md: "h-11 px-3 text-base",
29
+ lg: "h-12 px-4 text-lg",
30
+ },
31
+ },
32
+ defaultVariants: {
33
+ size: "md",
34
+ },
25
35
  });
26
36
 
27
- const { class: className, required = false, ...rest } = Astro.props;
37
+ const { class: className, size, required = false, ...rest } = Astro.props;
28
38
  ---
29
39
 
30
40
  <button
31
- class={selectTrigger({ class: className })}
41
+ class={selectTrigger({ size, class: className })}
32
42
  type="button"
33
43
  role="combobox"
34
44
  aria-label="Select field"
@@ -3,6 +3,7 @@ import SelectContent, { selectContent, selectContentInner } from "./SelectConten
3
3
  import SelectGroup from "./SelectGroup.astro";
4
4
  import SelectItem, { selectItem, selectItemIcon } from "./SelectItem.astro";
5
5
  import SelectLabel, { selectLabel } from "./SelectLabel.astro";
6
+ import SelectSearch, { selectSearch } from "./SelectSearch.astro";
6
7
  import SelectSeparator, { selectSeparator } from "./SelectSeparator.astro";
7
8
  import SelectTrigger, { selectTrigger } from "./SelectTrigger.astro";
8
9
  import type { SelectChangeEvent, SelectEvent } from "./SelectTypes";
@@ -14,6 +15,7 @@ const SelectVariants = {
14
15
  selectItem,
15
16
  selectItemIcon,
16
17
  selectLabel,
18
+ selectSearch,
17
19
  selectSeparator,
18
20
  selectTrigger,
19
21
  selectValue,
@@ -27,6 +29,7 @@ export {
27
29
  SelectGroup,
28
30
  SelectItem,
29
31
  SelectLabel,
32
+ SelectSearch,
30
33
  SelectSeparator,
31
34
  SelectTrigger,
32
35
  SelectValue,
@@ -40,6 +43,7 @@ export default {
40
43
  Content: SelectContent,
41
44
  Group: SelectGroup,
42
45
  Label: SelectLabel,
46
+ Search: SelectSearch,
43
47
  Item: SelectItem,
44
48
  Separator: SelectSeparator,
45
49
  };
@@ -0,0 +1,172 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+ import { tv, type VariantProps } from "tailwind-variants";
4
+
5
+ export const toggle = tv({
6
+ base: [
7
+ "inline-flex items-center justify-center gap-2 rounded-md font-medium whitespace-nowrap",
8
+ "disabled:pointer-events-none disabled:opacity-50",
9
+ "data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
10
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
11
+ "focus-visible:border-outline focus-visible:ring-outline/50 focus-visible:ring-3",
12
+ "transition-colors outline-none",
13
+ "aria-invalid:ring-error/20 dark:aria-invalid:ring-error/40 aria-invalid:border-error",
14
+ ],
15
+ variants: {
16
+ variant: {
17
+ default: "hover:bg-muted hover:text-muted-foreground bg-transparent",
18
+ outline:
19
+ "border-input hover:bg-accent hover:text-accent-foreground border bg-transparent shadow-xs",
20
+ },
21
+ size: {
22
+ sm: "h-9 min-w-9 px-2 text-sm",
23
+ md: "h-11 min-w-11 px-2.5 text-base",
24
+ lg: "h-12 min-w-12 px-3 text-lg",
25
+ },
26
+ },
27
+ defaultVariants: {
28
+ variant: "default",
29
+ size: "md",
30
+ },
31
+ });
32
+
33
+ type Props = VariantProps<typeof toggle> &
34
+ HTMLAttributes<"button"> & {
35
+ /**
36
+ * The pressed state of the toggle when initially rendered
37
+ */
38
+ defaultPressed?: boolean;
39
+ /**
40
+ * Optional sync group name. When set, all toggles with the same sync group will mirror each other's state
41
+ */
42
+ syncGroup?: string;
43
+ };
44
+
45
+ const { class: className, defaultPressed = false, syncGroup, variant, size, ...rest } = Astro.props;
46
+
47
+ const dataState = defaultPressed ? "on" : "off";
48
+ ---
49
+
50
+ <button
51
+ type="button"
52
+ class={toggle({ variant, size, class: `starwind-toggle ${className || ""}` })}
53
+ data-slot="toggle"
54
+ data-state={dataState}
55
+ data-sync-group={syncGroup}
56
+ aria-pressed={defaultPressed}
57
+ {...rest}
58
+ >
59
+ <slot />
60
+ </button>
61
+
62
+ <script>
63
+ import type { ToggleChangeEvent, ToggleSyncEvent } from "./ToggleTypes";
64
+
65
+ class ToggleHandler {
66
+ private toggle: HTMLButtonElement;
67
+ private syncGroup?: string;
68
+
69
+ constructor(toggle: HTMLButtonElement, idx: number) {
70
+ this.toggle = toggle;
71
+ this.syncGroup = toggle.dataset.syncGroup;
72
+
73
+ if (!this.toggle.id) {
74
+ this.toggle.id = `starwind-toggle${idx}`;
75
+ }
76
+
77
+ this.setupEventListeners();
78
+
79
+ if (this.syncGroup) {
80
+ this.setupSyncListener();
81
+ }
82
+ }
83
+
84
+ private setupEventListeners(): void {
85
+ this.toggle.addEventListener("click", () => this.handleToggle());
86
+ this.toggle.addEventListener("keydown", (event) => this.handleKeyDown(event));
87
+ }
88
+
89
+ private setupSyncListener(): void {
90
+ if (!this.syncGroup) return;
91
+
92
+ document.addEventListener(`starwind-toggle-sync:${this.syncGroup}`, ((e: ToggleSyncEvent) => {
93
+ // Don't sync if this toggle triggered the event
94
+ if (e.detail.sourceId === this.toggle.id) return;
95
+
96
+ const newPressed = e.detail.pressed;
97
+ this.updateState(newPressed, false); // false = don't dispatch sync event
98
+ }) as EventListener);
99
+ }
100
+
101
+ private handleToggle(): void {
102
+ if (this.isDisabled()) return;
103
+
104
+ const isPressed = this.toggle.getAttribute("aria-pressed") === "true";
105
+ const newPressed = !isPressed;
106
+
107
+ this.updateState(newPressed, true);
108
+ }
109
+
110
+ private handleKeyDown(event: KeyboardEvent): void {
111
+ if (this.isDisabled()) return;
112
+
113
+ if (event.key === " " || event.key === "Enter") {
114
+ event.preventDefault();
115
+ this.handleToggle();
116
+ }
117
+ }
118
+
119
+ private isDisabled(): boolean {
120
+ return this.toggle.disabled;
121
+ }
122
+
123
+ private updateState(pressed: boolean, dispatchSync: boolean): void {
124
+ const newState = pressed ? "on" : "off";
125
+
126
+ this.toggle.setAttribute("aria-pressed", pressed.toString());
127
+ this.toggle.setAttribute("data-state", newState);
128
+
129
+ // Dispatch change event (always fired for user to listen to)
130
+ const changeEvent = new CustomEvent<ToggleChangeEvent["detail"]>("starwind-toggle:change", {
131
+ detail: {
132
+ pressed,
133
+ toggleId: this.toggle.id,
134
+ syncGroup: this.syncGroup,
135
+ },
136
+ bubbles: true,
137
+ cancelable: true,
138
+ });
139
+
140
+ this.toggle.dispatchEvent(changeEvent);
141
+
142
+ // Dispatch sync event if in a sync group and requested
143
+ if (this.syncGroup && dispatchSync) {
144
+ const syncEvent = new CustomEvent<ToggleSyncEvent["detail"]>(
145
+ `starwind-toggle-sync:${this.syncGroup}`,
146
+ {
147
+ detail: {
148
+ pressed,
149
+ sourceId: this.toggle.id,
150
+ },
151
+ },
152
+ );
153
+
154
+ document.dispatchEvent(syncEvent);
155
+ }
156
+ }
157
+ }
158
+
159
+ // Store instances in a WeakMap to avoid memory leaks
160
+ const toggleInstances = new WeakMap<HTMLElement, ToggleHandler>();
161
+
162
+ const setupToggles = () => {
163
+ document.querySelectorAll<HTMLButtonElement>(".starwind-toggle").forEach((toggle, idx) => {
164
+ if (!toggleInstances.has(toggle)) {
165
+ toggleInstances.set(toggle, new ToggleHandler(toggle, idx));
166
+ }
167
+ });
168
+ };
169
+
170
+ setupToggles();
171
+ document.addEventListener("astro:after-swap", setupToggles);
172
+ </script>
@@ -0,0 +1,14 @@
1
+ export interface ToggleChangeEvent extends CustomEvent {
2
+ detail: {
3
+ pressed: boolean;
4
+ toggleId: string;
5
+ syncGroup?: string;
6
+ };
7
+ }
8
+
9
+ export interface ToggleSyncEvent extends CustomEvent {
10
+ detail: {
11
+ pressed: boolean;
12
+ sourceId: string;
13
+ };
14
+ }
@@ -0,0 +1,8 @@
1
+ import Toggle, { toggle } from "./Toggle.astro";
2
+ import type { ToggleChangeEvent, ToggleSyncEvent } from "./ToggleTypes";
3
+
4
+ const ToggleVariants = { toggle };
5
+
6
+ export { Toggle, type ToggleChangeEvent, type ToggleSyncEvent, ToggleVariants };
7
+
8
+ export default Toggle;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starwind-ui/core",
3
- "version": "1.11.2",
3
+ "version": "1.12.0",
4
4
  "description": "Starwind UI core components and registry",
5
5
  "license": "MIT",
6
6
  "author": {