@starwind-ui/core 0.0.1 → 0.1.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 +86 -2
- package/dist/index.js.map +1 -1
- package/dist/src/components/accordion/Accordion.astro +248 -0
- package/dist/src/components/accordion/AccordionContent.astro +28 -0
- package/dist/src/components/accordion/AccordionItem.astro +25 -0
- package/dist/src/components/accordion/AccordionTrigger.astro +26 -0
- package/dist/src/components/accordion/index.ts +13 -0
- package/dist/src/components/alert/Alert.astro +44 -0
- package/dist/src/components/alert/AlertDescription.astro +11 -0
- package/dist/src/components/alert/AlertTitle.astro +17 -0
- package/dist/src/components/alert/index.ts +11 -0
- package/dist/src/components/avatar/Avatar.astro +44 -0
- package/dist/src/components/avatar/AvatarFallback.astro +16 -0
- package/dist/src/components/avatar/AvatarImage.astro +48 -0
- package/dist/src/components/avatar/index.ts +11 -0
- package/dist/src/components/card/Card.astro +14 -0
- package/dist/src/components/card/CardContent.astro +11 -0
- package/dist/src/components/card/CardDescription.astro +11 -0
- package/dist/src/components/card/CardFooter.astro +11 -0
- package/dist/src/components/card/CardHeader.astro +11 -0
- package/dist/src/components/card/CardTitle.astro +11 -0
- package/dist/src/components/card/index.ts +17 -0
- package/dist/src/components/checkbox/Checkbox.astro +105 -0
- package/dist/src/components/checkbox/index.ts +5 -0
- package/dist/src/components/dialog/Dialog.astro +175 -0
- package/dist/src/components/dialog/DialogClose.astro +30 -0
- package/dist/src/components/dialog/DialogContent.astro +57 -0
- package/dist/src/components/dialog/DialogDescription.astro +11 -0
- package/dist/src/components/dialog/DialogFooter.astro +11 -0
- package/dist/src/components/dialog/DialogHeader.astro +11 -0
- package/dist/src/components/dialog/DialogTitle.astro +16 -0
- package/dist/src/components/dialog/DialogTrigger.astro +35 -0
- package/dist/src/components/dialog/index.ts +30 -0
- package/dist/src/components/input/Input.astro +26 -0
- package/dist/src/components/input/index.ts +5 -0
- package/dist/src/components/label/Label.astro +25 -0
- package/dist/src/components/label/index.ts +5 -0
- package/dist/src/components/pagination/Pagination.astro +18 -0
- package/dist/src/components/pagination/PaginationContent.astro +13 -0
- package/dist/src/components/pagination/PaginationEllipsis.astro +15 -0
- package/dist/src/components/pagination/PaginationItem.astro +13 -0
- package/dist/src/components/pagination/PaginationLink.astro +50 -0
- package/dist/src/components/pagination/PaginationNext.astro +23 -0
- package/dist/src/components/pagination/PaginationPrevious.astro +23 -0
- package/dist/src/components/pagination/index.ts +27 -0
- package/dist/src/components/select/Select.astro +452 -0
- package/dist/src/components/select/SelectContent.astro +57 -0
- package/dist/src/components/select/SelectGroup.astro +10 -0
- package/dist/src/components/select/SelectItem.astro +41 -0
- package/dist/src/components/select/SelectLabel.astro +11 -0
- package/dist/src/components/select/SelectSeparator.astro +9 -0
- package/dist/src/components/select/SelectTrigger.astro +40 -0
- package/dist/src/components/select/SelectTypes.ts +7 -0
- package/dist/src/components/select/SelectValue.astro +16 -0
- package/dist/src/components/select/index.ts +30 -0
- package/dist/src/components/switch/Switch.astro +189 -0
- package/dist/src/components/switch/SwitchTypes.ts +6 -0
- package/dist/src/components/switch/index.ts +6 -0
- package/dist/src/components/tabs/Tabs.astro +246 -0
- package/dist/src/components/tabs/TabsContent.astro +22 -0
- package/dist/src/components/tabs/TabsList.astro +19 -0
- package/dist/src/components/tabs/TabsTrigger.astro +27 -0
- package/dist/src/components/tabs/index.ts +13 -0
- package/dist/src/components/textarea/Textarea.astro +25 -0
- package/dist/src/components/textarea/index.ts +5 -0
- package/dist/src/components/tooltip/Tooltip.astro +233 -0
- package/dist/src/components/tooltip/TooltipContent.astro +76 -0
- package/dist/src/components/tooltip/TooltipTrigger.astro +11 -0
- package/dist/src/components/tooltip/index.ts +11 -0
- package/package.json +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
|
|
4
|
+
type Props = HTMLAttributes<"textarea"> & {
|
|
5
|
+
size?: "sm" | "md" | "lg";
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const { size = "md", class: className, ...rest } = Astro.props;
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<textarea
|
|
12
|
+
class:list={[
|
|
13
|
+
"border-input bg-background text-foreground ring-offset-background min-h-10 w-full rounded-md border",
|
|
14
|
+
"focus:outline-outline focus:ring-0 focus:outline-2 focus:outline-offset-2",
|
|
15
|
+
"file:text-foreground file:border-0 file:bg-transparent file:text-sm file:font-medium",
|
|
16
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
17
|
+
"peer placeholder:text-muted-foreground",
|
|
18
|
+
{
|
|
19
|
+
"min-h-9 px-2 py-1 text-sm": size === "sm",
|
|
20
|
+
"min-h-10 px-3 py-2 text-base": size === "md",
|
|
21
|
+
"min-h-12 px-4 py-3 text-lg": size === "lg",
|
|
22
|
+
},
|
|
23
|
+
className,
|
|
24
|
+
]}
|
|
25
|
+
{...rest}></textarea>
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
|
|
4
|
+
type Props = HTMLAttributes<"div"> & {
|
|
5
|
+
/**
|
|
6
|
+
* Time in milliseconds to wait before showing the tooltip
|
|
7
|
+
*/
|
|
8
|
+
openDelay?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Time in milliseconds to wait before hiding the tooltip
|
|
11
|
+
*/
|
|
12
|
+
closeDelay?: number;
|
|
13
|
+
/**
|
|
14
|
+
* When true, prevents the tooltip from staying open when hovering over its content
|
|
15
|
+
*/
|
|
16
|
+
disableHoverableContent?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
openDelay = 200,
|
|
21
|
+
closeDelay = 200,
|
|
22
|
+
disableHoverableContent = false,
|
|
23
|
+
class: className,
|
|
24
|
+
} = Astro.props;
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
<div
|
|
28
|
+
class:list={["starwind-tooltip relative inline-block", className]}
|
|
29
|
+
data-state="closed"
|
|
30
|
+
data-open-delay={openDelay}
|
|
31
|
+
data-close-delay={closeDelay}
|
|
32
|
+
{...!disableHoverableContent && { "data-content-hoverable": "" }}
|
|
33
|
+
>
|
|
34
|
+
<slot />
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<script>
|
|
38
|
+
class TooltipHandler {
|
|
39
|
+
private tooltip: HTMLElement;
|
|
40
|
+
private trigger: HTMLElement | null;
|
|
41
|
+
private content: HTMLElement | null;
|
|
42
|
+
private openTimerRef: number | null = null;
|
|
43
|
+
private closeTimerRef: number | null = null;
|
|
44
|
+
private contentId: string;
|
|
45
|
+
private animationDuration = 150;
|
|
46
|
+
|
|
47
|
+
constructor(tooltip: HTMLElement, idx: number) {
|
|
48
|
+
this.contentId = `starwind-tooltip${idx}`;
|
|
49
|
+
this.tooltip = tooltip;
|
|
50
|
+
this.content = tooltip.querySelector(".starwind-tooltip-content");
|
|
51
|
+
|
|
52
|
+
// if tooltip.firstElementChild is this.content, then get the next element
|
|
53
|
+
this.trigger = tooltip.firstElementChild as HTMLElement;
|
|
54
|
+
if (this.trigger === this.content) {
|
|
55
|
+
this.trigger = this.trigger.nextElementSibling as HTMLElement;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.trigger.classList.add("starwind-tooltip-trigger");
|
|
59
|
+
|
|
60
|
+
if (!this.trigger || !this.content) return;
|
|
61
|
+
|
|
62
|
+
// animationDuration is set with inline styles through passed prop to TooltipContent
|
|
63
|
+
const animationDurationString = this.content.style.animationDuration;
|
|
64
|
+
if (animationDurationString.endsWith("ms")) {
|
|
65
|
+
this.animationDuration = parseFloat(animationDurationString);
|
|
66
|
+
} else if (animationDurationString.endsWith("s")) {
|
|
67
|
+
// using something like @playform/compress might optimize to use "s" instead of "ms"
|
|
68
|
+
this.animationDuration = parseFloat(animationDurationString) * 1000;
|
|
69
|
+
}
|
|
70
|
+
this.init();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private init() {
|
|
74
|
+
this.setupAccessibility();
|
|
75
|
+
this.setupEvents();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private setupAccessibility() {
|
|
79
|
+
if (!this.trigger || !this.content) return;
|
|
80
|
+
this.trigger.setAttribute("aria-describedby", this.contentId);
|
|
81
|
+
this.content.id = this.contentId;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private setupEvents() {
|
|
85
|
+
if (!this.trigger || !this.content) return;
|
|
86
|
+
|
|
87
|
+
// Trigger events
|
|
88
|
+
this.trigger.addEventListener("mouseenter", () => this.show());
|
|
89
|
+
this.trigger.addEventListener("mouseleave", () => this.hide());
|
|
90
|
+
this.trigger.addEventListener("focus", () => this.show(true));
|
|
91
|
+
this.trigger.addEventListener("blur", () => this.hide(true));
|
|
92
|
+
|
|
93
|
+
// Content events
|
|
94
|
+
if (this.tooltip.hasAttribute("data-content-hoverable")) {
|
|
95
|
+
this.content.addEventListener("mouseenter", () => this.show());
|
|
96
|
+
this.content.addEventListener("mouseleave", () => this.hide());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// if data-avoid-collisions exists, add resize listener to reset any translations
|
|
100
|
+
if (this.content.hasAttribute("data-avoid-collisions")) {
|
|
101
|
+
window.addEventListener(
|
|
102
|
+
"resize",
|
|
103
|
+
() => {
|
|
104
|
+
if (!this.content) return;
|
|
105
|
+
this.content.style.transform = "";
|
|
106
|
+
},
|
|
107
|
+
{ passive: true },
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Document events
|
|
112
|
+
document.addEventListener("keydown", (e) => {
|
|
113
|
+
if (e.key === "Escape" && this.tooltip.getAttribute("data-state") === "open") {
|
|
114
|
+
this.hide(true);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
document.addEventListener("click", (e) => {
|
|
119
|
+
if (
|
|
120
|
+
!this.tooltip.contains(e.target as Node) &&
|
|
121
|
+
this.tooltip.getAttribute("data-state") === "open"
|
|
122
|
+
) {
|
|
123
|
+
this.hide(true);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private show(immediate: boolean = false) {
|
|
129
|
+
if (!this.content || !this.trigger) return;
|
|
130
|
+
if (immediate) {
|
|
131
|
+
this.tooltip.setAttribute("data-state", "open");
|
|
132
|
+
this.content.setAttribute("data-state", "open");
|
|
133
|
+
this.content.style.display = "block";
|
|
134
|
+
this.checkBoundary(this.content);
|
|
135
|
+
this.clearOpenTimer();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.clearCloseTimer();
|
|
140
|
+
|
|
141
|
+
const delay = parseInt(this.tooltip.getAttribute("data-open-delay") || "700");
|
|
142
|
+
this.openTimerRef = window.setTimeout(() => {
|
|
143
|
+
if (!this.content || !this.trigger) return;
|
|
144
|
+
this.tooltip.setAttribute("data-state", "open");
|
|
145
|
+
this.content.setAttribute("data-state", "open");
|
|
146
|
+
this.content.style.display = "block";
|
|
147
|
+
this.checkBoundary(this.content);
|
|
148
|
+
this.openTimerRef = null;
|
|
149
|
+
}, delay);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private hide(immediate: boolean = false) {
|
|
153
|
+
if (!this.content || !this.trigger) return;
|
|
154
|
+
this.clearOpenTimer();
|
|
155
|
+
|
|
156
|
+
if (immediate) {
|
|
157
|
+
this.clearCloseTimer();
|
|
158
|
+
this.tooltip.setAttribute("data-state", "closed");
|
|
159
|
+
setTimeout(() => {
|
|
160
|
+
if (!this.content) return;
|
|
161
|
+
this.content.style.display = "none";
|
|
162
|
+
}, this.animationDuration - 10);
|
|
163
|
+
this.content.setAttribute("data-state", "closed");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this.closeTimerRef = window.setTimeout(
|
|
168
|
+
() => {
|
|
169
|
+
this.tooltip.setAttribute("data-state", "closed");
|
|
170
|
+
setTimeout(() => {
|
|
171
|
+
if (!this.content) return;
|
|
172
|
+
this.content.style.display = "none";
|
|
173
|
+
}, this.animationDuration - 10);
|
|
174
|
+
if (!this.content) return;
|
|
175
|
+
this.content.setAttribute("data-state", "closed");
|
|
176
|
+
this.closeTimerRef = null;
|
|
177
|
+
},
|
|
178
|
+
parseInt(this.tooltip.getAttribute("data-close-delay") || "300"),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private checkBoundary(tooltipElement: HTMLElement) {
|
|
183
|
+
if (!tooltipElement) return;
|
|
184
|
+
|
|
185
|
+
// if data-avoid-collisions does not exist, return
|
|
186
|
+
if (!tooltipElement.hasAttribute("data-avoid-collisions")) return;
|
|
187
|
+
|
|
188
|
+
const viewportWidth = window.innerWidth;
|
|
189
|
+
const tooltipRect = tooltipElement.getBoundingClientRect();
|
|
190
|
+
const padding = 16; // Add some padding from viewport edges
|
|
191
|
+
|
|
192
|
+
// Check if tooltip extends beyond right edge of viewport
|
|
193
|
+
if (tooltipRect.right > viewportWidth - padding) {
|
|
194
|
+
const overflow = tooltipRect.right - (viewportWidth - padding);
|
|
195
|
+
tooltipElement.style.transform = `translateX(-${overflow + padding}px)`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Check if tooltip extends beyond left edge of viewport
|
|
199
|
+
if (tooltipRect.left < padding) {
|
|
200
|
+
const overflow = padding - tooltipRect.left;
|
|
201
|
+
tooltipElement.style.transform = `translateX(${overflow + padding}px)`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private clearOpenTimer() {
|
|
206
|
+
if (this.openTimerRef) {
|
|
207
|
+
window.clearTimeout(this.openTimerRef);
|
|
208
|
+
this.openTimerRef = null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private clearCloseTimer() {
|
|
213
|
+
if (this.closeTimerRef) {
|
|
214
|
+
window.clearTimeout(this.closeTimerRef);
|
|
215
|
+
this.closeTimerRef = null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Store instances in a WeakMap to avoid memory leaks
|
|
221
|
+
const tooltipInstances = new WeakMap<HTMLElement, TooltipHandler>();
|
|
222
|
+
|
|
223
|
+
const setupTooltips = () => {
|
|
224
|
+
document.querySelectorAll<HTMLElement>(".starwind-tooltip").forEach((tooltip, idx) => {
|
|
225
|
+
if (!tooltipInstances.has(tooltip)) {
|
|
226
|
+
tooltipInstances.set(tooltip, new TooltipHandler(tooltip, idx));
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
setupTooltips();
|
|
232
|
+
document.addEventListener("astro:after-swap", setupTooltips);
|
|
233
|
+
</script>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
|
|
4
|
+
type Props = HTMLAttributes<"div"> & {
|
|
5
|
+
/**
|
|
6
|
+
* Side of the tooltip
|
|
7
|
+
* @default top
|
|
8
|
+
*/
|
|
9
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
10
|
+
/**
|
|
11
|
+
* Alignment of the tooltip
|
|
12
|
+
* @default center
|
|
13
|
+
*/
|
|
14
|
+
align?: "start" | "center" | "end";
|
|
15
|
+
/**
|
|
16
|
+
* Offset distance in pixels
|
|
17
|
+
* @default 4
|
|
18
|
+
*/
|
|
19
|
+
sideOffset?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Prevent the tooltip from colliding with other elements
|
|
22
|
+
* @default true
|
|
23
|
+
*/
|
|
24
|
+
avoidCollisions?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Open and close animation duration in milliseconds
|
|
27
|
+
* @default 150
|
|
28
|
+
*/
|
|
29
|
+
animationDuration?: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const {
|
|
33
|
+
side = "top",
|
|
34
|
+
align = "center",
|
|
35
|
+
sideOffset = 4,
|
|
36
|
+
avoidCollisions = true,
|
|
37
|
+
animationDuration = 150,
|
|
38
|
+
class: className,
|
|
39
|
+
} = Astro.props;
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
<div
|
|
43
|
+
class:list={[
|
|
44
|
+
"starwind-tooltip-content",
|
|
45
|
+
"absolute z-50 hidden px-3 py-1.5 whitespace-nowrap shadow-sm",
|
|
46
|
+
"bg-popover text-popover-foreground rounded-md border",
|
|
47
|
+
"animate-in fade-in-0 zoom-in-95",
|
|
48
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
49
|
+
{
|
|
50
|
+
"slide-in-from-right-2 right-(--tooltip-offset)": side === "left",
|
|
51
|
+
"slide-in-from-left-2 left-(--tooltip-offset)": side === "right",
|
|
52
|
+
"slide-in-from-top-2 top-(--tooltip-offset)": side === "bottom",
|
|
53
|
+
"slide-in-from-bottom-2 bottom-(--tooltip-offset)": side === "top",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"left-[50%] translate-x-[-50%]": align === "center" && (side === "top" || side === "bottom"),
|
|
57
|
+
"top-[50%] translate-y-[-50%]": align === "center" && (side === "left" || side === "right"),
|
|
58
|
+
"left-0": align === "start" && (side === "top" || side === "bottom"),
|
|
59
|
+
"right-0": align === "end" && (side === "top" || side === "bottom"),
|
|
60
|
+
"top-0": align === "start" && (side === "left" || side === "right"),
|
|
61
|
+
"bottom-0": align === "end" && (side === "left" || side === "right"),
|
|
62
|
+
},
|
|
63
|
+
className,
|
|
64
|
+
]}
|
|
65
|
+
data-state="closed"
|
|
66
|
+
data-side={side}
|
|
67
|
+
data-align={align}
|
|
68
|
+
{...avoidCollisions && { "data-avoid-collisions": "" }}
|
|
69
|
+
role="tooltip"
|
|
70
|
+
style={{
|
|
71
|
+
"--tooltip-offset": `calc(100% + ${sideOffset}px)`,
|
|
72
|
+
animationDuration: `${animationDuration}ms`,
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<slot> My tooltip! </slot>
|
|
76
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import Tooltip from "./Tooltip.astro";
|
|
2
|
+
import TooltipContent from "./TooltipContent.astro";
|
|
3
|
+
import TooltipTrigger from "./TooltipTrigger.astro";
|
|
4
|
+
|
|
5
|
+
export { Tooltip, TooltipContent, TooltipTrigger };
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
Root: Tooltip,
|
|
9
|
+
Trigger: TooltipTrigger,
|
|
10
|
+
Content: TooltipContent,
|
|
11
|
+
};
|