@starwind-ui/core 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/index.js +86 -2
  2. package/dist/index.js.map +1 -1
  3. package/dist/src/components/accordion/Accordion.astro +248 -0
  4. package/dist/src/components/accordion/AccordionContent.astro +28 -0
  5. package/dist/src/components/accordion/AccordionItem.astro +25 -0
  6. package/dist/src/components/accordion/AccordionTrigger.astro +26 -0
  7. package/dist/src/components/accordion/index.ts +13 -0
  8. package/dist/src/components/alert/Alert.astro +44 -0
  9. package/dist/src/components/alert/AlertDescription.astro +11 -0
  10. package/dist/src/components/alert/AlertTitle.astro +17 -0
  11. package/dist/src/components/alert/index.ts +11 -0
  12. package/dist/src/components/avatar/Avatar.astro +44 -0
  13. package/dist/src/components/avatar/AvatarFallback.astro +16 -0
  14. package/dist/src/components/avatar/AvatarImage.astro +48 -0
  15. package/dist/src/components/avatar/index.ts +11 -0
  16. package/dist/src/components/card/Card.astro +14 -0
  17. package/dist/src/components/card/CardContent.astro +11 -0
  18. package/dist/src/components/card/CardDescription.astro +11 -0
  19. package/dist/src/components/card/CardFooter.astro +11 -0
  20. package/dist/src/components/card/CardHeader.astro +11 -0
  21. package/dist/src/components/card/CardTitle.astro +11 -0
  22. package/dist/src/components/card/index.ts +17 -0
  23. package/dist/src/components/checkbox/Checkbox.astro +105 -0
  24. package/dist/src/components/checkbox/index.ts +5 -0
  25. package/dist/src/components/dialog/Dialog.astro +175 -0
  26. package/dist/src/components/dialog/DialogClose.astro +30 -0
  27. package/dist/src/components/dialog/DialogContent.astro +57 -0
  28. package/dist/src/components/dialog/DialogDescription.astro +11 -0
  29. package/dist/src/components/dialog/DialogFooter.astro +11 -0
  30. package/dist/src/components/dialog/DialogHeader.astro +11 -0
  31. package/dist/src/components/dialog/DialogTitle.astro +16 -0
  32. package/dist/src/components/dialog/DialogTrigger.astro +35 -0
  33. package/dist/src/components/dialog/index.ts +30 -0
  34. package/dist/src/components/input/Input.astro +26 -0
  35. package/dist/src/components/input/index.ts +5 -0
  36. package/dist/src/components/label/Label.astro +25 -0
  37. package/dist/src/components/label/index.ts +5 -0
  38. package/dist/src/components/pagination/Pagination.astro +18 -0
  39. package/dist/src/components/pagination/PaginationContent.astro +13 -0
  40. package/dist/src/components/pagination/PaginationEllipsis.astro +15 -0
  41. package/dist/src/components/pagination/PaginationItem.astro +13 -0
  42. package/dist/src/components/pagination/PaginationLink.astro +50 -0
  43. package/dist/src/components/pagination/PaginationNext.astro +23 -0
  44. package/dist/src/components/pagination/PaginationPrevious.astro +23 -0
  45. package/dist/src/components/pagination/index.ts +27 -0
  46. package/dist/src/components/select/Select.astro +452 -0
  47. package/dist/src/components/select/SelectContent.astro +57 -0
  48. package/dist/src/components/select/SelectGroup.astro +10 -0
  49. package/dist/src/components/select/SelectItem.astro +41 -0
  50. package/dist/src/components/select/SelectLabel.astro +11 -0
  51. package/dist/src/components/select/SelectSeparator.astro +9 -0
  52. package/dist/src/components/select/SelectTrigger.astro +40 -0
  53. package/dist/src/components/select/SelectTypes.ts +7 -0
  54. package/dist/src/components/select/SelectValue.astro +16 -0
  55. package/dist/src/components/select/index.ts +30 -0
  56. package/dist/src/components/switch/Switch.astro +189 -0
  57. package/dist/src/components/switch/SwitchTypes.ts +6 -0
  58. package/dist/src/components/switch/index.ts +6 -0
  59. package/dist/src/components/tabs/Tabs.astro +246 -0
  60. package/dist/src/components/tabs/TabsContent.astro +22 -0
  61. package/dist/src/components/tabs/TabsList.astro +19 -0
  62. package/dist/src/components/tabs/TabsTrigger.astro +27 -0
  63. package/dist/src/components/tabs/index.ts +13 -0
  64. package/dist/src/components/textarea/Textarea.astro +25 -0
  65. package/dist/src/components/textarea/index.ts +5 -0
  66. package/dist/src/components/tooltip/Tooltip.astro +233 -0
  67. package/dist/src/components/tooltip/TooltipContent.astro +76 -0
  68. package/dist/src/components/tooltip/TooltipTrigger.astro +11 -0
  69. package/dist/src/components/tooltip/index.ts +11 -0
  70. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,16 +4,100 @@ import { fileURLToPath } from "node:url";
4
4
 
5
5
  // src/registry.json
6
6
  var registry_default = [
7
+ {
8
+ name: "accordion",
9
+ type: "component",
10
+ version: "1.0.0",
11
+ dependencies: []
12
+ },
13
+ {
14
+ name: "alert",
15
+ type: "component",
16
+ version: "1.0.0",
17
+ dependencies: []
18
+ },
19
+ {
20
+ name: "avatar",
21
+ type: "component",
22
+ version: "1.0.0",
23
+ dependencies: []
24
+ },
7
25
  {
8
26
  name: "badge",
9
27
  type: "component",
10
- version: "0.0.11",
28
+ version: "1.0.0",
11
29
  dependencies: []
12
30
  },
13
31
  {
14
32
  name: "button",
15
33
  type: "component",
16
- version: "0.0.11",
34
+ version: "1.0.0",
35
+ dependencies: []
36
+ },
37
+ {
38
+ name: "card",
39
+ type: "component",
40
+ version: "1.0.0",
41
+ dependencies: []
42
+ },
43
+ {
44
+ name: "checkbox",
45
+ type: "component",
46
+ version: "1.0.0",
47
+ dependencies: []
48
+ },
49
+ {
50
+ name: "dialog",
51
+ type: "component",
52
+ version: "1.0.0",
53
+ dependencies: []
54
+ },
55
+ {
56
+ name: "input",
57
+ type: "component",
58
+ version: "1.0.0",
59
+ dependencies: []
60
+ },
61
+ {
62
+ name: "label",
63
+ type: "component",
64
+ version: "1.0.0",
65
+ dependencies: []
66
+ },
67
+ {
68
+ name: "pagination",
69
+ type: "component",
70
+ version: "1.0.0",
71
+ dependencies: []
72
+ },
73
+ {
74
+ name: "select",
75
+ type: "component",
76
+ version: "1.0.0",
77
+ dependencies: []
78
+ },
79
+ {
80
+ name: "switch",
81
+ type: "component",
82
+ version: "1.0.0",
83
+ dependencies: []
84
+ },
85
+ {
86
+ name: "tabs",
87
+ type: "component",
88
+ version: "1.0.0",
89
+ dependencies: []
90
+ },
91
+ {
92
+ name: "textarea",
93
+ type: "component",
94
+ version: "1.0.0",
95
+ dependencies: []
96
+ },
97
+ {
98
+ name: "tooltip",
99
+ type: "component",
100
+ version: "1.0.0",
17
101
  dependencies: []
18
102
  }
19
103
  ];
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\" assert { 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 as ComponentMeta[];\n","[\n\t{\n\t\t\"name\": \"badge\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"0.0.11\",\n\t\t\"dependencies\": []\n\t},\n\t{\n\t\t\"name\": \"button\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"0.0.11\",\n\t\t\"dependencies\": []\n\t}\n]\n"],"mappings":";AAAA,SAAS,YAAY;AACrB,SAAS,qBAAqB;;;ACD9B;AAAA,EACC;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AACD;;;ADQA,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;","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\" assert { 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 as ComponentMeta[];\n","[\n\t{\n\t\t\"name\": \"accordion\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"dependencies\": []\n\t},\n\t{\n\t\t\"name\": \"alert\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"dependencies\": []\n\t},\n\t{\n\t\t\"name\": \"avatar\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"dependencies\": []\n\t},\n\t{\n\t\t\"name\": \"badge\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"dependencies\": []\n\t},\n\t{\n\t\t\"name\": \"button\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"dependencies\": []\n\t},\n\t{\n\t\t\"name\": \"card\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"dependencies\": []\n\t},\n\t{\n\t\t\"name\": \"checkbox\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"dependencies\": []\n\t},\n\t{\n\t\t\"name\": \"dialog\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"dependencies\": []\n\t},\n\t{\n\t\t\"name\": \"input\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"dependencies\": []\n\t},\n\t{\n\t\t\"name\": \"label\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"dependencies\": []\n\t},\n\t{\n\t\t\"name\": \"pagination\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"dependencies\": []\n\t},\n\t{\n\t\t\"name\": \"select\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"dependencies\": []\n\t},\n\t{\n\t\t\"name\": \"switch\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"dependencies\": []\n\t},\n\t{\n\t\t\"name\": \"tabs\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"dependencies\": []\n\t},\n\t{\n\t\t\"name\": \"textarea\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"dependencies\": []\n\t},\n\t{\n\t\t\"name\": \"tooltip\",\n\t\t\"type\": \"component\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"dependencies\": []\n\t}\n]\n"],"mappings":";AAAA,SAAS,YAAY;AACrB,SAAS,qBAAqB;;;ACD9B;AAAA,EACC;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,IACC,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,cAAgB,CAAC;AAAA,EAClB;AACD;;;AD5EA,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;","names":[]}
@@ -0,0 +1,248 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+
4
+ type Props = HTMLAttributes<"div"> & {
5
+ /**
6
+ * The type of accordion. If "single", only one item can be open at a time.
7
+ */
8
+ type?: "single" | "multiple";
9
+ /**
10
+ * The value of the item that should be open by default
11
+ */
12
+ defaultValue?: string;
13
+ };
14
+
15
+ const { type = "single", defaultValue, class: className, ...rest } = Astro.props;
16
+ ---
17
+
18
+ <div
19
+ class:list={["starwind-accordion", className]}
20
+ data-type={type}
21
+ data-value={defaultValue}
22
+ {...rest}
23
+ >
24
+ <slot />
25
+ </div>
26
+
27
+ <script>
28
+ type AccordionType = "single" | "multiple";
29
+ type AccordionState = "open" | "closed";
30
+
31
+ /** Represents a single accordion item with its associated elements */
32
+ interface AccordionItem {
33
+ element: HTMLElement;
34
+ trigger: HTMLElement;
35
+ content: HTMLElement;
36
+ value: string;
37
+ }
38
+
39
+ /**
40
+ * Handles the functionality of an accordion component.
41
+ * Supports single and multiple open items, keyboard navigation,
42
+ * and maintains ARIA accessibility standards.
43
+ */
44
+ class AccordionHandler {
45
+ private accordion: HTMLElement;
46
+ private type: AccordionType;
47
+ private items: AccordionItem[];
48
+ private itemsByValue: Map<string, AccordionItem>;
49
+ private accordionId: string;
50
+
51
+ /**
52
+ * Creates a new AccordionHandler instance
53
+ * @param accordion - The root accordion element
54
+ * @param idx - Unique index for this accordion instance
55
+ */
56
+ constructor(accordion: HTMLElement, idx: number) {
57
+ this.accordion = accordion;
58
+ this.type = (accordion.dataset.type || "single") as AccordionType;
59
+ this.accordionId = `starwind-accordion${idx}`;
60
+
61
+ // Cache all items and create lookup maps
62
+ this.items = this.initializeItems();
63
+ this.itemsByValue = new Map(this.items.map((item) => [item.value, item]));
64
+
65
+ this.setupItems();
66
+ this.setInitialState();
67
+ }
68
+
69
+ /**
70
+ * Initializes accordion items by querying the DOM and setting up data structures
71
+ * @returns Array of AccordionItem objects
72
+ */
73
+ private initializeItems(): AccordionItem[] {
74
+ return Array.from(this.accordion.querySelectorAll<HTMLElement>(".starwind-accordion-item"))
75
+ .map((element, idx) => {
76
+ const trigger = element.querySelector<HTMLElement>(".starwind-accordion-trigger");
77
+ const content = element.querySelector<HTMLElement>(".starwind-accordion-content");
78
+ const value = element.getAttribute("data-value") || String(idx);
79
+
80
+ if (!trigger || !content) return null;
81
+
82
+ return {
83
+ element,
84
+ trigger,
85
+ content,
86
+ value,
87
+ };
88
+ })
89
+ .filter((item): item is AccordionItem => item !== null);
90
+ }
91
+
92
+ /**
93
+ * Sets up initial state and event listeners for all accordion items
94
+ */
95
+ private setupItems(): void {
96
+ this.items.forEach((item, idx) => {
97
+ this.setupAccessibility(item, idx);
98
+ this.setContentHeight(item.content);
99
+ this.setupEventListeners(item);
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Sets up ARIA attributes and IDs for accessibility
105
+ * @param item - The accordion item to setup
106
+ * @param idx - Index of the item
107
+ */
108
+ private setupAccessibility(item: AccordionItem, idx: number): void {
109
+ const triggerId = `${this.accordionId}-t${idx}`;
110
+ const contentId = `${this.accordionId}-c${idx}`;
111
+
112
+ item.trigger.id = triggerId;
113
+ item.trigger.setAttribute("aria-controls", contentId);
114
+ item.trigger.setAttribute("aria-expanded", "false");
115
+
116
+ item.content.id = contentId;
117
+ item.content.setAttribute("aria-labelledby", triggerId);
118
+ item.content.setAttribute("role", "region");
119
+ }
120
+
121
+ /**
122
+ * Calculates and sets the content height CSS variable for animations
123
+ * @param content - The content element to measure
124
+ */
125
+ private setContentHeight(content: HTMLElement): void {
126
+ const contentInner = content.firstElementChild as HTMLElement;
127
+ if (contentInner) {
128
+ const height = contentInner.getBoundingClientRect().height;
129
+ content.style.setProperty("--starwind-accordion-content-height", `${height}px`);
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Sets the initial state based on the default value attribute
135
+ */
136
+ private setInitialState(): void {
137
+ const defaultValue = this.accordion.dataset.value;
138
+ if (defaultValue) {
139
+ const item = this.itemsByValue.get(defaultValue);
140
+ if (item) {
141
+ this.setItemState(item, true);
142
+ }
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Sets up click and keyboard event listeners for an accordion item
148
+ * @param item - The accordion item to setup listeners for
149
+ */
150
+ private setupEventListeners(item: AccordionItem): void {
151
+ item.trigger.addEventListener("click", () => this.handleClick(item));
152
+ item.trigger.addEventListener("keydown", (e) => this.handleKeyDown(e, item));
153
+ }
154
+
155
+ /**
156
+ * Handles click events on accordion triggers
157
+ * @param item - The clicked accordion item
158
+ */
159
+ private handleClick(item: AccordionItem): void {
160
+ const isOpen = item.element.getAttribute("data-state") === "open";
161
+ this.toggleItem(item, !isOpen);
162
+ }
163
+
164
+ /**
165
+ * Handles keyboard navigation events
166
+ * @param event - The keyboard event
167
+ * @param item - The current accordion item
168
+ */
169
+ private handleKeyDown(event: KeyboardEvent, item: AccordionItem): void {
170
+ const index = this.items.indexOf(item);
171
+
172
+ const keyActions: Record<string, () => void> = {
173
+ ArrowDown: () => this.focusItem(index + 1),
174
+ ArrowUp: () => this.focusItem(index - 1),
175
+ Home: () => this.focusItem(0),
176
+ End: () => this.focusItem(this.items.length - 1),
177
+ };
178
+
179
+ const action = keyActions[event.key];
180
+ if (action) {
181
+ event.preventDefault();
182
+ action();
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Focuses an accordion item by index with wrapping
188
+ * @param index - The target index to focus
189
+ */
190
+ private focusItem(index: number): void {
191
+ const targetIndex = (index + this.items.length) % this.items.length;
192
+ this.items[targetIndex].trigger.focus();
193
+ }
194
+
195
+ /**
196
+ * Toggles an accordion item's state
197
+ * @param item - The item to toggle
198
+ * @param shouldOpen - Whether the item should be opened
199
+ */
200
+ private toggleItem(item: AccordionItem, shouldOpen: boolean): void {
201
+ if (this.type === "single" && shouldOpen) {
202
+ // Close other items if in single mode
203
+ this.items.forEach((otherItem) => {
204
+ if (otherItem !== item && otherItem.element.getAttribute("data-state") === "open") {
205
+ this.setItemState(otherItem, false);
206
+ }
207
+ });
208
+ }
209
+
210
+ this.setItemState(item, shouldOpen);
211
+ }
212
+
213
+ /**
214
+ * Sets the state of an accordion item
215
+ * @param item - The item to update
216
+ * @param isOpen - Whether the item should be open
217
+ */
218
+ private setItemState(item: AccordionItem, isOpen: boolean): void {
219
+ const state: AccordionState = isOpen ? "open" : "closed";
220
+
221
+ if (isOpen) {
222
+ item.content.style.removeProperty("animation");
223
+ }
224
+
225
+ // Set content height variable for animations
226
+ this.setContentHeight(item.content);
227
+
228
+ item.element.setAttribute("data-state", state);
229
+ item.content.setAttribute("data-state", state);
230
+ item.trigger.setAttribute("data-state", state);
231
+ item.trigger.setAttribute("aria-expanded", isOpen.toString());
232
+ }
233
+ }
234
+
235
+ // Store instances in a WeakMap to avoid memory leaks
236
+ const accordionInstances = new WeakMap<HTMLElement, AccordionHandler>();
237
+
238
+ const setupAccordions = () => {
239
+ document.querySelectorAll<HTMLElement>(".starwind-accordion").forEach((accordion, idx) => {
240
+ if (!accordionInstances.has(accordion)) {
241
+ accordionInstances.set(accordion, new AccordionHandler(accordion, idx));
242
+ }
243
+ });
244
+ };
245
+
246
+ setupAccordions();
247
+ document.addEventListener("astro:after-swap", setupAccordions);
248
+ </script>
@@ -0,0 +1,28 @@
1
+ ---
2
+ /**
3
+ * NOTE: style="animation: none;" makes it so the close animation doesn't run on page load
4
+ * It is later removed in the Accordion.astro script
5
+ */
6
+ import type { HTMLAttributes } from "astro/types";
7
+
8
+ type Props = HTMLAttributes<"div">;
9
+
10
+ const { class: className, ...rest } = Astro.props;
11
+ ---
12
+
13
+ <div
14
+ class:list={[
15
+ "starwind-accordion-content",
16
+ "transform-gpu overflow-hidden",
17
+ "data-[state=closed]:animate-accordion-up data-[state=closed]:h-0",
18
+ "data-[state=open]:animate-accordion-down",
19
+ className,
20
+ ]}
21
+ data-state="closed"
22
+ style="animation: none;"
23
+ {...rest}
24
+ >
25
+ <div class="overflow-hidden px-5 pt-0 pb-4">
26
+ <slot />
27
+ </div>
28
+ </div>
@@ -0,0 +1,25 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+
4
+ type Props = HTMLAttributes<"div"> & {
5
+ /**
6
+ * The value of the item
7
+ */
8
+ value: string;
9
+ };
10
+
11
+ const { value, class: className, ...rest } = Astro.props;
12
+ ---
13
+
14
+ <div
15
+ class:list={[
16
+ "starwind-accordion-item",
17
+ "border-x border-b first:rounded-t-lg first:border-t last:rounded-b-lg",
18
+ className,
19
+ ]}
20
+ data-value={value}
21
+ data-state="closed"
22
+ {...rest}
23
+ >
24
+ <slot />
25
+ </div>
@@ -0,0 +1,26 @@
1
+ ---
2
+ import ChevronDown from "@tabler/icons/outline/chevron-down.svg";
3
+
4
+ import type { HTMLAttributes } from "astro/types";
5
+
6
+ type Props = HTMLAttributes<"button">;
7
+
8
+ const { class: className, ...rest } = Astro.props;
9
+ ---
10
+
11
+ <button
12
+ type="button"
13
+ class:list={[
14
+ "starwind-accordion-trigger",
15
+ "flex w-full items-center justify-between gap-4 rounded-md px-5 py-4",
16
+ "starwind-transition-colors hover:text-muted-foreground text-left font-medium",
17
+ "[&[data-state=open]>svg]:rotate-180",
18
+ "focus-visible:outline-outline focus-visible:outline-2 focus-visible:outline-offset-0",
19
+ className,
20
+ ]}
21
+ aria-expanded="false"
22
+ {...rest}
23
+ >
24
+ <slot />
25
+ <ChevronDown class="size-5 shrink-0 transition-transform duration-200" />
26
+ </button>
@@ -0,0 +1,13 @@
1
+ import Accordion from "./Accordion.astro";
2
+ import AccordionContent from "./AccordionContent.astro";
3
+ import AccordionItem from "./AccordionItem.astro";
4
+ import AccordionTrigger from "./AccordionTrigger.astro";
5
+
6
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
7
+
8
+ export default {
9
+ Root: Accordion,
10
+ Content: AccordionContent,
11
+ Item: AccordionItem,
12
+ Trigger: AccordionTrigger,
13
+ };
@@ -0,0 +1,44 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+
4
+ type Props = HTMLAttributes<"div"> & {
5
+ /**
6
+ * Sets the variant of the alert
7
+ * @default "default"
8
+ */
9
+ variant?: "default" | "primary" | "secondary" | "info" | "success" | "warning" | "error";
10
+ };
11
+
12
+ const { variant = "default", class: className, ...rest } = Astro.props;
13
+ ---
14
+
15
+ <div
16
+ class:list={[
17
+ "text-foreground relative w-full rounded-lg border p-4",
18
+ {
19
+ "bg-background [&>h5>svg]:text-foreground": variant === "default",
20
+ },
21
+ {
22
+ "border-primary bg-primary/7 [&>h5>svg]:text-primary": variant === "primary",
23
+ },
24
+ {
25
+ "border-secondary bg-secondary/7 [&>h5>svg]:text-secondary": variant === "secondary",
26
+ },
27
+ {
28
+ "border-info bg-info/7 [&>h5>svg]:text-info": variant === "info",
29
+ },
30
+ {
31
+ "border-success bg-success/7 [&>h5>svg]:text-success": variant === "success",
32
+ },
33
+ {
34
+ "border-warning bg-warning/7 [&>h5>svg]:text-warning": variant === "warning",
35
+ },
36
+ {
37
+ "border-error bg-error/7 [&>h5>svg]:text-error": variant === "error",
38
+ },
39
+ className,
40
+ ]}
41
+ {...rest}
42
+ >
43
+ <slot />
44
+ </div>
@@ -0,0 +1,11 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+
4
+ type Props = HTMLAttributes<"div">;
5
+
6
+ const { class: className, ...rest } = Astro.props;
7
+ ---
8
+
9
+ <div class:list={["[&_p]:leading-relaxed", className]} {...rest}>
10
+ <slot />
11
+ </div>
@@ -0,0 +1,17 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+
4
+ type Props = HTMLAttributes<"div">;
5
+
6
+ const { class: className, ...rest } = Astro.props;
7
+ ---
8
+
9
+ <h5
10
+ class:list={[
11
+ "mb-2 flex items-center gap-2 text-lg leading-none font-medium tracking-tight",
12
+ className,
13
+ ]}
14
+ {...rest}
15
+ >
16
+ <slot />
17
+ </h5>
@@ -0,0 +1,11 @@
1
+ import Alert from "./Alert.astro";
2
+ import AlertDescription from "./AlertDescription.astro";
3
+ import AlertTitle from "./AlertTitle.astro";
4
+
5
+ export { Alert, AlertDescription, AlertTitle };
6
+
7
+ export default {
8
+ Root: Alert,
9
+ Description: AlertDescription,
10
+ Title: AlertTitle,
11
+ };
@@ -0,0 +1,44 @@
1
+ ---
2
+ import type { HTMLAttributes } from "astro/types";
3
+
4
+ interface Props extends HTMLAttributes<"div"> {
5
+ /**
6
+ * Sets the variant of the avatar
7
+ * @default "default"
8
+ */
9
+ variant?: "default" | "primary" | "secondary" | "info" | "success" | "warning" | "error";
10
+ /**
11
+ * Sets the size of the avatar
12
+ * @default "md"
13
+ */
14
+ size?: "sm" | "md" | "lg";
15
+ }
16
+
17
+ const { variant = "default", size = "md", class: className, ...rest } = Astro.props;
18
+
19
+ const sizeClasses = {
20
+ sm: "h-8 w-8 text-xs",
21
+ md: "h-10 w-10 text-sm",
22
+ lg: "h-12 w-12 text-base",
23
+ }[size];
24
+ ---
25
+
26
+ <figure
27
+ class:list={[
28
+ "text-foreground bg-muted relative overflow-hidden rounded-full border-2",
29
+ {
30
+ "border-border": variant === "default",
31
+ "border-primary": variant === "primary",
32
+ "border-secondary": variant === "secondary",
33
+ "border-info": variant === "info",
34
+ "border-success": variant === "success",
35
+ "border-warning": variant === "warning",
36
+ "border-error": variant === "error",
37
+ },
38
+ sizeClasses,
39
+ className,
40
+ ]}
41
+ {...rest}
42
+ >
43
+ <slot />
44
+ </figure>
@@ -0,0 +1,16 @@
1
+ ---
2
+ interface Props {
3
+ class?: string;
4
+ }
5
+
6
+ const { class: className } = Astro.props;
7
+ ---
8
+
9
+ <div
10
+ class:list={[
11
+ "absolute inset-0.5 flex items-center justify-center rounded-full font-medium",
12
+ className,
13
+ ]}
14
+ >
15
+ <slot />
16
+ </div>
@@ -0,0 +1,48 @@
1
+ ---
2
+ import { Image } from "astro:assets";
3
+
4
+ type BaseProps = {
5
+ alt: string;
6
+ };
7
+
8
+ type WithSrc = BaseProps & {
9
+ src: string;
10
+ image?: never;
11
+ };
12
+
13
+ type WithImage = BaseProps & {
14
+ src?: never;
15
+ image: ImageMetadata;
16
+ };
17
+
18
+ type Props = WithSrc | WithImage;
19
+
20
+ const { src, image, alt } = Astro.props;
21
+
22
+ if (!src && !image) {
23
+ throw new Error("Either 'src' or 'image' is required for an avatar image.");
24
+ }
25
+ ---
26
+
27
+ {
28
+ src && (
29
+ <img
30
+ src={src}
31
+ alt={alt}
32
+ class="relative z-1 h-full w-full rounded-full object-cover"
33
+ width={64}
34
+ height={64}
35
+ onerror="this.style.display='none'"
36
+ />
37
+ )
38
+ }
39
+ {
40
+ image && (
41
+ <Image
42
+ src={image}
43
+ alt={alt}
44
+ class="relative z-1 h-full w-full rounded-full object-cover"
45
+ width={64}
46
+ />
47
+ )
48
+ }
@@ -0,0 +1,11 @@
1
+ import Avatar from "./Avatar.astro";
2
+ import AvatarImage from "./AvatarImage.astro";
3
+ import AvatarFallback from "./AvatarFallback.astro";
4
+
5
+ export { Avatar, AvatarImage, AvatarFallback };
6
+
7
+ export default {
8
+ Root: Avatar,
9
+ Image: AvatarImage,
10
+ Fallback: AvatarFallback,
11
+ };