@starwind-ui/core 1.12.3 → 1.13.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 +16 -12
- package/dist/index.js.map +1 -1
- package/dist/src/components/accordion/Accordion.astro +10 -3
- package/dist/src/components/alert/AlertTitle.astro +1 -1
- package/dist/src/components/alert-dialog/AlertDialog.astro +4 -2
- package/dist/src/components/alert-dialog/AlertDialogTitle.astro +1 -1
- package/dist/src/components/badge/Badge.astro +1 -3
- package/dist/src/components/button/Button.astro +2 -1
- package/dist/src/components/dialog/Dialog.astro +101 -9
- package/dist/src/components/dialog/DialogContent.astro +13 -2
- package/dist/src/components/dialog/DialogTitle.astro +3 -1
- package/dist/src/components/dropdown/Dropdown.astro +4 -2
- package/dist/src/components/dropzone/Dropzone.astro +5 -3
- package/dist/src/components/image/Image.astro +24 -0
- package/dist/src/components/image/index.ts +9 -0
- package/dist/src/components/progress/Progress.astro +1 -0
- package/dist/src/components/radio-group/RadioGroup.astro +7 -2
- package/dist/src/components/select/Select.astro +4 -2
- package/dist/src/components/sheet/SheetTitle.astro +1 -1
- package/dist/src/components/slider/Slider.astro +411 -0
- package/dist/src/components/slider/index.ts +9 -0
- package/dist/src/components/switch/Switch.astro +1 -0
- package/dist/src/components/tabs/Tabs.astro +4 -2
- package/dist/src/components/toast/ToastDescription.astro +21 -0
- package/dist/src/components/toast/ToastItem.astro +54 -0
- package/dist/src/components/toast/ToastTemplate.astro +25 -0
- package/dist/src/components/toast/ToastTitle.astro +57 -0
- package/dist/src/components/toast/Toaster.astro +982 -0
- package/dist/src/components/toast/index.ts +29 -0
- package/dist/src/components/toast/toast-manager.ts +216 -0
- package/dist/src/components/toggle/Toggle.astro +4 -2
- package/dist/src/components/tooltip/Tooltip.astro +4 -2
- package/dist/src/components/tooltip/TooltipContent.astro +1 -1
- package/dist/src/components/video/Video.astro +120 -0
- package/dist/src/components/video/index.ts +9 -0
- package/package.json +8 -6
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
export const slider = tv({
|
|
6
|
+
slots: {
|
|
7
|
+
root: "starwind-slider relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:w-auto",
|
|
8
|
+
control:
|
|
9
|
+
"starwind-slider-control relative w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-auto",
|
|
10
|
+
track:
|
|
11
|
+
"starwind-slider-track bg-muted relative overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5",
|
|
12
|
+
range:
|
|
13
|
+
"starwind-slider-range absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full",
|
|
14
|
+
thumb:
|
|
15
|
+
"starwind-slider-thumb absolute block size-4 shrink-0 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50 data-[orientation=horizontal]:top-1/2 data-[orientation=horizontal]:-translate-x-1/2 data-[orientation=horizontal]:-translate-y-1/2 data-[orientation=vertical]:left-1/2 data-[orientation=vertical]:-translate-x-1/2 data-[orientation=vertical]:translate-y-1/2",
|
|
16
|
+
},
|
|
17
|
+
variants: {
|
|
18
|
+
variant: {
|
|
19
|
+
default: {
|
|
20
|
+
range: "bg-foreground",
|
|
21
|
+
thumb: "border-foreground ring-outline/50",
|
|
22
|
+
},
|
|
23
|
+
primary: {
|
|
24
|
+
range: "bg-primary",
|
|
25
|
+
thumb: "border-primary ring-primary/50",
|
|
26
|
+
},
|
|
27
|
+
secondary: {
|
|
28
|
+
range: "bg-secondary",
|
|
29
|
+
thumb: "border-secondary ring-secondary/50",
|
|
30
|
+
},
|
|
31
|
+
info: {
|
|
32
|
+
range: "bg-info",
|
|
33
|
+
thumb: "border-info ring-info/50",
|
|
34
|
+
},
|
|
35
|
+
success: {
|
|
36
|
+
range: "bg-success",
|
|
37
|
+
thumb: "border-success ring-success/50",
|
|
38
|
+
},
|
|
39
|
+
warning: {
|
|
40
|
+
range: "bg-warning",
|
|
41
|
+
thumb: "border-warning ring-warning/50",
|
|
42
|
+
},
|
|
43
|
+
error: {
|
|
44
|
+
range: "bg-error",
|
|
45
|
+
thumb: "border-error ring-error/50",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
defaultVariants: {
|
|
50
|
+
variant: "default",
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
type Variant = "default" | "primary" | "secondary" | "info" | "success" | "warning" | "error";
|
|
55
|
+
|
|
56
|
+
type Props = HTMLAttributes<"div"> & {
|
|
57
|
+
defaultValue?: number | number[];
|
|
58
|
+
value?: number | number[];
|
|
59
|
+
min?: number;
|
|
60
|
+
max?: number;
|
|
61
|
+
step?: number;
|
|
62
|
+
largeStep?: number;
|
|
63
|
+
orientation?: "horizontal" | "vertical";
|
|
64
|
+
disabled?: boolean;
|
|
65
|
+
name?: string;
|
|
66
|
+
variant?: Variant;
|
|
67
|
+
"aria-label"?: string;
|
|
68
|
+
"aria-labelledby"?: string;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const {
|
|
72
|
+
class: className,
|
|
73
|
+
defaultValue = 0,
|
|
74
|
+
value,
|
|
75
|
+
min = 0,
|
|
76
|
+
max = 100,
|
|
77
|
+
step = 1,
|
|
78
|
+
largeStep = 10,
|
|
79
|
+
orientation = "horizontal",
|
|
80
|
+
disabled = false,
|
|
81
|
+
name,
|
|
82
|
+
variant = "default",
|
|
83
|
+
"aria-label": ariaLabel,
|
|
84
|
+
"aria-labelledby": ariaLabelledby,
|
|
85
|
+
...rest
|
|
86
|
+
} = Astro.props;
|
|
87
|
+
|
|
88
|
+
const { root, control, track, range, thumb } = slider({ variant });
|
|
89
|
+
|
|
90
|
+
const initialValue = value ?? defaultValue;
|
|
91
|
+
const values = Array.isArray(initialValue) ? initialValue : [initialValue];
|
|
92
|
+
const isRange = values.length > 1;
|
|
93
|
+
|
|
94
|
+
function getPercentage(val: number): number {
|
|
95
|
+
return ((val - min) / (max - min)) * 100;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const rangeStart = isRange ? getPercentage(Math.min(...values)) : 0;
|
|
99
|
+
const rangeEnd = isRange ? getPercentage(Math.max(...values)) : getPercentage(values[0]);
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
<div
|
|
103
|
+
class={root({ class: className })}
|
|
104
|
+
data-slot="slider"
|
|
105
|
+
data-orientation={orientation}
|
|
106
|
+
data-disabled={disabled ? "" : undefined}
|
|
107
|
+
role="group"
|
|
108
|
+
aria-labelledby={ariaLabelledby}
|
|
109
|
+
data-min={min}
|
|
110
|
+
data-max={max}
|
|
111
|
+
data-step={step}
|
|
112
|
+
data-large-step={largeStep}
|
|
113
|
+
{...rest}
|
|
114
|
+
>
|
|
115
|
+
<div class={control()} data-slot="slider-control" data-orientation={orientation}>
|
|
116
|
+
<div class={track()} data-slot="slider-track" data-orientation={orientation}>
|
|
117
|
+
<div
|
|
118
|
+
class={range()}
|
|
119
|
+
data-slot="slider-range"
|
|
120
|
+
data-orientation={orientation}
|
|
121
|
+
style={orientation === "horizontal"
|
|
122
|
+
? `left: ${rangeStart}%; width: ${rangeEnd - rangeStart}%`
|
|
123
|
+
: `bottom: ${rangeStart}%; height: ${rangeEnd - rangeStart}%`}
|
|
124
|
+
>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
{
|
|
128
|
+
values.map((val, index) => (
|
|
129
|
+
<div
|
|
130
|
+
class={thumb()}
|
|
131
|
+
data-slot="slider-thumb"
|
|
132
|
+
data-index={index}
|
|
133
|
+
data-orientation={orientation}
|
|
134
|
+
style={
|
|
135
|
+
orientation === "horizontal"
|
|
136
|
+
? `left: ${getPercentage(val)}%`
|
|
137
|
+
: `bottom: ${getPercentage(val)}%`
|
|
138
|
+
}
|
|
139
|
+
tabindex={disabled ? -1 : 0}
|
|
140
|
+
role="slider"
|
|
141
|
+
aria-label={ariaLabel}
|
|
142
|
+
aria-valuemin={min}
|
|
143
|
+
aria-valuemax={max}
|
|
144
|
+
aria-valuenow={val}
|
|
145
|
+
aria-orientation={orientation}
|
|
146
|
+
aria-disabled={disabled}
|
|
147
|
+
>
|
|
148
|
+
<input
|
|
149
|
+
type="range"
|
|
150
|
+
min={min}
|
|
151
|
+
max={max}
|
|
152
|
+
step={step}
|
|
153
|
+
value={val}
|
|
154
|
+
name={name ? (isRange ? `${name}[${index}]` : name) : undefined}
|
|
155
|
+
disabled={disabled}
|
|
156
|
+
tabindex={-1}
|
|
157
|
+
aria-hidden="true"
|
|
158
|
+
class="sr-only"
|
|
159
|
+
/>
|
|
160
|
+
</div>
|
|
161
|
+
))
|
|
162
|
+
}
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<script>
|
|
167
|
+
class StarwindSlider {
|
|
168
|
+
private root: HTMLElement;
|
|
169
|
+
private track: HTMLElement;
|
|
170
|
+
private range: HTMLElement;
|
|
171
|
+
private thumbs: HTMLElement[];
|
|
172
|
+
private inputs: HTMLInputElement[];
|
|
173
|
+
private min: number;
|
|
174
|
+
private max: number;
|
|
175
|
+
private step: number;
|
|
176
|
+
private largeStep: number;
|
|
177
|
+
private orientation: "horizontal" | "vertical";
|
|
178
|
+
private disabled: boolean;
|
|
179
|
+
private values: number[];
|
|
180
|
+
private activeThumbIndex: number = -1;
|
|
181
|
+
private dragging: boolean = false;
|
|
182
|
+
|
|
183
|
+
constructor(root: HTMLElement) {
|
|
184
|
+
this.root = root;
|
|
185
|
+
this.track = root.querySelector('[data-slot="slider-track"]')!;
|
|
186
|
+
this.range = root.querySelector('[data-slot="slider-range"]')!;
|
|
187
|
+
this.thumbs = Array.from(root.querySelectorAll('[data-slot="slider-thumb"]'));
|
|
188
|
+
this.inputs = Array.from(root.querySelectorAll('input[type="range"]'));
|
|
189
|
+
|
|
190
|
+
this.min = Number(root.dataset.min) || 0;
|
|
191
|
+
this.max = Number(root.dataset.max) || 100;
|
|
192
|
+
this.step = Number(root.dataset.step) || 1;
|
|
193
|
+
this.largeStep = Number(root.dataset.largeStep) || 10;
|
|
194
|
+
this.orientation = (root.dataset.orientation as "horizontal" | "vertical") || "horizontal";
|
|
195
|
+
this.disabled = root.hasAttribute("data-disabled");
|
|
196
|
+
|
|
197
|
+
this.values = this.inputs.map((input) => Number(input.value));
|
|
198
|
+
|
|
199
|
+
this.init();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private init(): void {
|
|
203
|
+
if (this.disabled) return;
|
|
204
|
+
|
|
205
|
+
this.thumbs.forEach((thumb, index) => {
|
|
206
|
+
thumb.addEventListener("pointerdown", (e) => this.handleThumbPointerDown(e, index));
|
|
207
|
+
thumb.addEventListener("keydown", (e) => this.handleKeyDown(e, index));
|
|
208
|
+
thumb.addEventListener("focus", () => this.handleFocus(index));
|
|
209
|
+
thumb.addEventListener("blur", () => this.handleBlur());
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
this.track.addEventListener("pointerdown", (e) => this.handleTrackClick(e));
|
|
213
|
+
|
|
214
|
+
document.addEventListener("pointermove", (e) => this.handlePointerMove(e));
|
|
215
|
+
document.addEventListener("pointerup", () => this.handlePointerUp());
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private getPercentage(val: number): number {
|
|
219
|
+
return ((val - this.min) / (this.max - this.min)) * 100;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private getValueFromPosition(clientX: number, clientY: number): number {
|
|
223
|
+
const rect = this.track.getBoundingClientRect();
|
|
224
|
+
let percentage: number;
|
|
225
|
+
|
|
226
|
+
if (this.orientation === "horizontal") {
|
|
227
|
+
percentage = (clientX - rect.left) / rect.width;
|
|
228
|
+
} else {
|
|
229
|
+
percentage = 1 - (clientY - rect.top) / rect.height;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
percentage = Math.max(0, Math.min(1, percentage));
|
|
233
|
+
const rawValue = this.min + percentage * (this.max - this.min);
|
|
234
|
+
return this.snapToStep(rawValue);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private snapToStep(value: number): number {
|
|
238
|
+
const snapped = Math.round((value - this.min) / this.step) * this.step + this.min;
|
|
239
|
+
return Math.max(this.min, Math.min(this.max, snapped));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private updateValue(index: number, newValue: number): void {
|
|
243
|
+
const isRange = this.values.length > 1;
|
|
244
|
+
|
|
245
|
+
if (isRange) {
|
|
246
|
+
if (index === 0 && newValue > this.values[1]) {
|
|
247
|
+
newValue = this.values[1];
|
|
248
|
+
} else if (index === 1 && newValue < this.values[0]) {
|
|
249
|
+
newValue = this.values[0];
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
this.values[index] = newValue;
|
|
254
|
+
this.inputs[index].value = String(newValue);
|
|
255
|
+
|
|
256
|
+
this.updateVisuals();
|
|
257
|
+
this.dispatchChangeEvent();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private updateVisuals(): void {
|
|
261
|
+
const isRange = this.values.length > 1;
|
|
262
|
+
const rangeStart = isRange ? this.getPercentage(Math.min(...this.values)) : 0;
|
|
263
|
+
const rangeEnd = isRange
|
|
264
|
+
? this.getPercentage(Math.max(...this.values))
|
|
265
|
+
: this.getPercentage(this.values[0]);
|
|
266
|
+
|
|
267
|
+
if (this.orientation === "horizontal") {
|
|
268
|
+
this.range.style.left = `${rangeStart}%`;
|
|
269
|
+
this.range.style.width = `${rangeEnd - rangeStart}%`;
|
|
270
|
+
} else {
|
|
271
|
+
this.range.style.bottom = `${rangeStart}%`;
|
|
272
|
+
this.range.style.height = `${rangeEnd - rangeStart}%`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
this.thumbs.forEach((thumb, index) => {
|
|
276
|
+
const percentage = this.getPercentage(this.values[index]);
|
|
277
|
+
if (this.orientation === "horizontal") {
|
|
278
|
+
thumb.style.left = `${percentage}%`;
|
|
279
|
+
} else {
|
|
280
|
+
thumb.style.bottom = `${percentage}%`;
|
|
281
|
+
}
|
|
282
|
+
thumb.setAttribute("aria-valuenow", String(this.values[index]));
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private dispatchChangeEvent(): void {
|
|
287
|
+
const value = this.values.length === 1 ? this.values[0] : [...this.values];
|
|
288
|
+
this.root.dispatchEvent(
|
|
289
|
+
new CustomEvent("slider-change", {
|
|
290
|
+
detail: { value },
|
|
291
|
+
bubbles: true,
|
|
292
|
+
}),
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private handleThumbPointerDown(e: PointerEvent, index: number): void {
|
|
297
|
+
if (this.disabled) return;
|
|
298
|
+
e.preventDefault();
|
|
299
|
+
this.activeThumbIndex = index;
|
|
300
|
+
this.dragging = true;
|
|
301
|
+
this.root.setAttribute("data-dragging", "");
|
|
302
|
+
this.thumbs[index].setAttribute("data-dragging", "");
|
|
303
|
+
this.thumbs[index].focus();
|
|
304
|
+
(e.target as HTMLElement).setPointerCapture(e.pointerId);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private handlePointerMove(e: PointerEvent): void {
|
|
308
|
+
if (!this.dragging || this.activeThumbIndex === -1) return;
|
|
309
|
+
const newValue = this.getValueFromPosition(e.clientX, e.clientY);
|
|
310
|
+
this.updateValue(this.activeThumbIndex, newValue);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private handlePointerUp(): void {
|
|
314
|
+
if (!this.dragging) return;
|
|
315
|
+
this.dragging = false;
|
|
316
|
+
this.root.removeAttribute("data-dragging");
|
|
317
|
+
this.thumbs.forEach((thumb) => thumb.removeAttribute("data-dragging"));
|
|
318
|
+
|
|
319
|
+
const value = this.values.length === 1 ? this.values[0] : [...this.values];
|
|
320
|
+
this.root.dispatchEvent(
|
|
321
|
+
new CustomEvent("slider-commit", {
|
|
322
|
+
detail: { value },
|
|
323
|
+
bubbles: true,
|
|
324
|
+
}),
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
this.activeThumbIndex = -1;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private handleTrackClick(e: PointerEvent): void {
|
|
331
|
+
if (this.disabled) return;
|
|
332
|
+
const newValue = this.getValueFromPosition(e.clientX, e.clientY);
|
|
333
|
+
|
|
334
|
+
let closestIndex = 0;
|
|
335
|
+
if (this.values.length > 1) {
|
|
336
|
+
const distances = this.values.map((v) => Math.abs(v - newValue));
|
|
337
|
+
closestIndex = distances.indexOf(Math.min(...distances));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
this.updateValue(closestIndex, newValue);
|
|
341
|
+
this.thumbs[closestIndex].focus();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private handleKeyDown(e: KeyboardEvent, index: number): void {
|
|
345
|
+
if (this.disabled) return;
|
|
346
|
+
|
|
347
|
+
let newValue = this.values[index];
|
|
348
|
+
const isHorizontal = this.orientation === "horizontal";
|
|
349
|
+
|
|
350
|
+
switch (e.key) {
|
|
351
|
+
case "ArrowRight":
|
|
352
|
+
case "ArrowUp":
|
|
353
|
+
e.preventDefault();
|
|
354
|
+
newValue += isHorizontal === (e.key === "ArrowRight") ? this.step : -this.step;
|
|
355
|
+
if (e.shiftKey) newValue = this.values[index] + this.largeStep;
|
|
356
|
+
break;
|
|
357
|
+
case "ArrowLeft":
|
|
358
|
+
case "ArrowDown":
|
|
359
|
+
e.preventDefault();
|
|
360
|
+
newValue -= isHorizontal === (e.key === "ArrowLeft") ? this.step : -this.step;
|
|
361
|
+
if (e.shiftKey) newValue = this.values[index] - this.largeStep;
|
|
362
|
+
break;
|
|
363
|
+
case "PageUp":
|
|
364
|
+
e.preventDefault();
|
|
365
|
+
newValue += this.largeStep;
|
|
366
|
+
break;
|
|
367
|
+
case "PageDown":
|
|
368
|
+
e.preventDefault();
|
|
369
|
+
newValue -= this.largeStep;
|
|
370
|
+
break;
|
|
371
|
+
case "Home":
|
|
372
|
+
e.preventDefault();
|
|
373
|
+
newValue = this.min;
|
|
374
|
+
break;
|
|
375
|
+
case "End":
|
|
376
|
+
e.preventDefault();
|
|
377
|
+
newValue = this.max;
|
|
378
|
+
break;
|
|
379
|
+
default:
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
newValue = this.snapToStep(newValue);
|
|
384
|
+
this.updateValue(index, newValue);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private handleFocus(index: number): void {
|
|
388
|
+
this.root.setAttribute("data-focused", "");
|
|
389
|
+
this.thumbs[index].setAttribute("data-focused", "");
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private handleBlur(): void {
|
|
393
|
+
this.root.removeAttribute("data-focused");
|
|
394
|
+
this.thumbs.forEach((thumb) => thumb.removeAttribute("data-focused"));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const sliderInstances = new WeakMap<HTMLElement, StarwindSlider>();
|
|
399
|
+
|
|
400
|
+
function initSliders(): void {
|
|
401
|
+
document.querySelectorAll<HTMLElement>('[data-slot="slider"]').forEach((slider) => {
|
|
402
|
+
if (!sliderInstances.has(slider)) {
|
|
403
|
+
sliderInstances.set(slider, new StarwindSlider(slider));
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
initSliders();
|
|
409
|
+
document.addEventListener("astro:after-swap", initSliders);
|
|
410
|
+
document.addEventListener("starwind:init", initSliders);
|
|
411
|
+
</script>
|
|
@@ -252,18 +252,20 @@ const { defaultValue, syncKey, class: className, ...rest } = Astro.props;
|
|
|
252
252
|
|
|
253
253
|
// Store instances in a WeakMap to avoid memory leaks
|
|
254
254
|
const tabInstances = new WeakMap<HTMLElement, TabsHandler>();
|
|
255
|
+
let tabCounter = 0;
|
|
255
256
|
|
|
256
257
|
const setupTabs = () => {
|
|
257
258
|
// First handle top-level tabs
|
|
258
|
-
document.querySelectorAll<HTMLElement>(".starwind-tabs").forEach((tabs
|
|
259
|
+
document.querySelectorAll<HTMLElement>(".starwind-tabs").forEach((tabs) => {
|
|
259
260
|
// Skip tabs that are nested within other tab contents
|
|
260
261
|
const isNested = !!tabs.closest("[data-tabs-content]");
|
|
261
262
|
if (!isNested && !tabInstances.has(tabs)) {
|
|
262
|
-
tabInstances.set(tabs, new TabsHandler(tabs,
|
|
263
|
+
tabInstances.set(tabs, new TabsHandler(tabs, tabCounter++));
|
|
263
264
|
}
|
|
264
265
|
});
|
|
265
266
|
};
|
|
266
267
|
|
|
267
268
|
setupTabs();
|
|
268
269
|
document.addEventListener("astro:after-swap", setupTabs);
|
|
270
|
+
document.addEventListener("starwind:init", setupTabs);
|
|
269
271
|
</script>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from "astro/types";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
export const toastDescription = tv({
|
|
6
|
+
base: "starwind-toast-description text-muted-foreground text-sm",
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
type Props = HTMLAttributes<"div">;
|
|
10
|
+
|
|
11
|
+
const { class: className, ...rest } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<div
|
|
15
|
+
class={toastDescription({ class: className })}
|
|
16
|
+
data-slot="toast-description"
|
|
17
|
+
data-toast-description
|
|
18
|
+
{...rest}
|
|
19
|
+
>
|
|
20
|
+
<slot />
|
|
21
|
+
</div>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
import X from "@tabler/icons/outline/x.svg";
|
|
3
|
+
import type { HTMLAttributes } from "astro/types";
|
|
4
|
+
import { tv } from "tailwind-variants";
|
|
5
|
+
|
|
6
|
+
export const toastItem = tv({
|
|
7
|
+
base: "starwind-toast bg-popover text-popover-foreground pointer-events-auto absolute inset-x-0 bottom-0 flex w-full origin-bottom flex-col gap-1 overflow-hidden rounded-lg border bg-clip-padding p-4 pr-10 shadow-lg transition-[transform,opacity] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] data-limited:pointer-events-none data-limited:opacity-0",
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "border-border",
|
|
11
|
+
success: "border-success/80",
|
|
12
|
+
error: "border-error/80",
|
|
13
|
+
warning: "border-warning/80",
|
|
14
|
+
info: "border-info/80",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
defaultVariants: {
|
|
18
|
+
variant: "default",
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
type Variant = "default" | "success" | "error" | "warning" | "info";
|
|
23
|
+
|
|
24
|
+
type Props = HTMLAttributes<"div"> & {
|
|
25
|
+
variant?: Variant;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const { class: className, variant = "default", ...rest } = Astro.props;
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
<div
|
|
32
|
+
class={toastItem({ variant, class: className })}
|
|
33
|
+
data-slot="toast"
|
|
34
|
+
data-variant={variant}
|
|
35
|
+
data-state="open"
|
|
36
|
+
role="dialog"
|
|
37
|
+
aria-modal="false"
|
|
38
|
+
{...rest}
|
|
39
|
+
>
|
|
40
|
+
<div
|
|
41
|
+
class="starwind-toast-content grid gap-1 transition-opacity duration-200 data-behind:opacity-0 data-expanded:opacity-100"
|
|
42
|
+
data-slot="toast-content"
|
|
43
|
+
>
|
|
44
|
+
<slot />
|
|
45
|
+
</div>
|
|
46
|
+
<button
|
|
47
|
+
type="button"
|
|
48
|
+
class="starwind-toast-close text-muted-foreground hover:text-foreground absolute top-2 right-2 rounded-md p-1 opacity-70 transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:outline-none"
|
|
49
|
+
data-slot="toast-close"
|
|
50
|
+
aria-label="Close notification"
|
|
51
|
+
>
|
|
52
|
+
<X class="size-4" />
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
import ToastDescription from "./ToastDescription.astro";
|
|
3
|
+
import ToastItem from "./ToastItem.astro";
|
|
4
|
+
import ToastTitle from "./ToastTitle.astro";
|
|
5
|
+
|
|
6
|
+
type Variant = "default" | "success" | "error" | "warning" | "info" | "loading";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
variant: Variant;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { variant } = Astro.props;
|
|
13
|
+
|
|
14
|
+
// Loading variant uses default item styling but loading title styling
|
|
15
|
+
const itemVariant = variant === "loading" ? "default" : variant;
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<template data-toast-template={variant}>
|
|
19
|
+
<slot>
|
|
20
|
+
<ToastItem variant={itemVariant}>
|
|
21
|
+
<ToastTitle variant={variant}>Title</ToastTitle>
|
|
22
|
+
<ToastDescription>Description</ToastDescription>
|
|
23
|
+
</ToastItem>
|
|
24
|
+
</slot>
|
|
25
|
+
</template>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
import WarningIcon from "@tabler/icons/outline/alert-triangle.svg";
|
|
3
|
+
import SuccessIcon from "@tabler/icons/outline/circle-check.svg";
|
|
4
|
+
import ErrorIcon from "@tabler/icons/outline/circle-x.svg";
|
|
5
|
+
import InfoIcon from "@tabler/icons/outline/info-circle.svg";
|
|
6
|
+
import LoaderIcon from "@tabler/icons/outline/loader-2.svg";
|
|
7
|
+
import type { HTMLAttributes } from "astro/types";
|
|
8
|
+
import { tv } from "tailwind-variants";
|
|
9
|
+
|
|
10
|
+
export const toastTitle = tv({
|
|
11
|
+
base: "starwind-toast-title flex items-center gap-1 text-sm font-semibold [&_svg]:size-4",
|
|
12
|
+
variants: {
|
|
13
|
+
variant: {
|
|
14
|
+
default: "",
|
|
15
|
+
success: "[&_svg]:text-success",
|
|
16
|
+
error: "[&_svg]:text-error",
|
|
17
|
+
warning: "[&_svg]:text-warning",
|
|
18
|
+
info: "[&_svg]:text-info",
|
|
19
|
+
loading: "[&_svg]:text-muted-foreground",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
variant: "default",
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
type Variant = "default" | "success" | "error" | "warning" | "info" | "loading";
|
|
28
|
+
|
|
29
|
+
type Props = HTMLAttributes<"div"> & {
|
|
30
|
+
variant?: Variant;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const { class: className, variant = "default", ...rest } = Astro.props;
|
|
34
|
+
|
|
35
|
+
const icons = {
|
|
36
|
+
default: null,
|
|
37
|
+
success: SuccessIcon,
|
|
38
|
+
error: ErrorIcon,
|
|
39
|
+
warning: WarningIcon,
|
|
40
|
+
info: InfoIcon,
|
|
41
|
+
loading: LoaderIcon,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const Icon = icons[variant];
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
<div
|
|
48
|
+
class={toastTitle({ variant, class: className })}
|
|
49
|
+
data-slot="toast-title"
|
|
50
|
+
data-toast-title
|
|
51
|
+
{...rest}
|
|
52
|
+
>
|
|
53
|
+
<slot name="icon">
|
|
54
|
+
{Icon && (variant === "loading" ? <Icon class="animate-spin" /> : <Icon />)}
|
|
55
|
+
</slot>
|
|
56
|
+
<span data-toast-title-text><slot /></span>
|
|
57
|
+
</div>
|