Mega push vol2

This commit is contained in:
AlacrisDevs
2026-02-05 01:36:06 +02:00
parent 1534e1f0af
commit 9af0ef5307
17 changed files with 1056 additions and 189 deletions

View File

@@ -1,28 +1,47 @@
<script lang="ts">
import type { CalendarEvent } from '$lib/supabase/types';
import { getMonthDays, isSameDay } from '$lib/api/calendar';
import type { CalendarEvent } from "$lib/supabase/types";
import { getMonthDays, isSameDay } from "$lib/api/calendar";
type ViewType = "month" | "week" | "day";
interface Props {
events: CalendarEvent[];
onDateClick?: (date: Date) => void;
onEventClick?: (event: CalendarEvent) => void;
initialView?: ViewType;
}
let { events, onDateClick, onEventClick }: Props = $props();
let {
events,
onDateClick,
onEventClick,
initialView = "month",
}: Props = $props();
let currentDate = $state(new Date());
let currentView = $state<ViewType>(initialView);
const today = new Date();
const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const days = $derived(getMonthDays(currentDate.getFullYear(), currentDate.getMonth()));
const days = $derived(
getMonthDays(currentDate.getFullYear(), currentDate.getMonth()),
);
function prevMonth() {
currentDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1);
currentDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() - 1,
1,
);
}
function nextMonth() {
currentDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1);
currentDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() + 1,
1,
);
}
function goToToday() {
@@ -41,14 +60,109 @@
}
const monthYear = $derived(
currentDate.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })
currentDate.toLocaleDateString("en-US", {
month: "long",
year: "numeric",
}),
);
// Get week days for week view
function getWeekDays(date: Date): Date[] {
const startOfWeek = new Date(date);
startOfWeek.setDate(date.getDate() - date.getDay());
return Array.from({ length: 7 }, (_, i) => {
const d = new Date(startOfWeek);
d.setDate(startOfWeek.getDate() + i);
return d;
});
}
const weekDates = $derived(getWeekDays(currentDate));
// Navigation functions for different views
function prev() {
if (currentView === "month") {
currentDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() - 1,
1,
);
} else if (currentView === "week") {
currentDate = new Date(
currentDate.getTime() - 7 * 24 * 60 * 60 * 1000,
);
} else {
currentDate = new Date(currentDate.getTime() - 24 * 60 * 60 * 1000);
}
}
function next() {
if (currentView === "month") {
currentDate = new Date(
currentDate.getFullYear(),
currentDate.getMonth() + 1,
1,
);
} else if (currentView === "week") {
currentDate = new Date(
currentDate.getTime() + 7 * 24 * 60 * 60 * 1000,
);
} else {
currentDate = new Date(currentDate.getTime() + 24 * 60 * 60 * 1000);
}
}
const headerTitle = $derived(() => {
if (currentView === "day") {
return currentDate.toLocaleDateString("en-US", {
weekday: "long",
month: "long",
day: "numeric",
year: "numeric",
});
} else if (currentView === "week") {
const start = weekDates[0];
const end = weekDates[6];
return `${start.toLocaleDateString("en-US", { month: "short", day: "numeric" })} - ${end.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}`;
}
return monthYear;
});
</script>
<div class="bg-surface rounded-xl p-4">
<div class="flex items-center justify-between mb-4">
<h2 class="text-xl font-semibold text-light">{monthYear}</h2>
<h2 class="text-xl font-semibold text-light">{headerTitle()}</h2>
<div class="flex items-center gap-2">
<!-- View Switcher -->
<div class="flex bg-dark rounded-lg p-0.5">
<button
class="px-3 py-1 text-sm rounded-md transition-colors {currentView ===
'day'
? 'bg-primary text-white'
: 'text-light/60 hover:text-light'}"
onclick={() => (currentView = "day")}
>
Day
</button>
<button
class="px-3 py-1 text-sm rounded-md transition-colors {currentView ===
'week'
? 'bg-primary text-white'
: 'text-light/60 hover:text-light'}"
onclick={() => (currentView = "week")}
>
Week
</button>
<button
class="px-3 py-1 text-sm rounded-md transition-colors {currentView ===
'month'
? 'bg-primary text-white'
: 'text-light/60 hover:text-light'}"
onclick={() => (currentView = "month")}
>
Month
</button>
</div>
<button
class="px-3 py-1.5 text-sm text-light/60 hover:text-light hover:bg-light/10 rounded-lg transition-colors"
onclick={goToToday}
@@ -57,68 +171,183 @@
</button>
<button
class="p-2 text-light/60 hover:text-light hover:bg-light/10 rounded-lg transition-colors"
onclick={prevMonth}
aria-label="Previous month"
onclick={prev}
aria-label="Previous"
>
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<svg
class="w-5 h-5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="m15 18-6-6 6-6" />
</svg>
</button>
<button
class="p-2 text-light/60 hover:text-light hover:bg-light/10 rounded-lg transition-colors"
onclick={nextMonth}
aria-label="Next month"
onclick={next}
aria-label="Next"
>
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<svg
class="w-5 h-5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="m9 18 6-6-6-6" />
</svg>
</button>
</div>
</div>
<div class="grid grid-cols-7 gap-px bg-light/10 rounded-lg overflow-hidden">
{#each weekDays as day}
<div class="bg-dark px-2 py-2 text-center text-sm font-medium text-light/50">
{day}
</div>
{/each}
{#each days as day}
{@const dayEvents = getEventsForDay(day)}
{@const isToday = isSameDay(day, today)}
{@const inMonth = isCurrentMonth(day)}
<button
class="bg-dark min-h-[80px] p-1 text-left transition-colors hover:bg-light/5"
class:opacity-40={!inMonth}
onclick={() => onDateClick?.(day)}
>
<div class="flex items-center justify-center w-7 h-7 mb-1">
<span
class="text-sm {isToday
? 'bg-primary text-white rounded-full w-7 h-7 flex items-center justify-center'
: 'text-light/80'}"
>
{day.getDate()}
</span>
<!-- Month View -->
{#if currentView === "month"}
<div
class="grid grid-cols-7 gap-px bg-light/10 rounded-lg overflow-hidden"
>
{#each weekDays as day}
<div
class="bg-dark px-2 py-2 text-center text-sm font-medium text-light/50"
>
{day}
</div>
<div class="space-y-0.5">
{#each dayEvents.slice(0, 3) as event}
<button
class="w-full text-xs px-1 py-0.5 rounded truncate text-left"
style="background-color: {event.color ?? '#6366f1'}20; color: {event.color ?? '#6366f1'}"
onclick={(e) => {
e.stopPropagation();
onEventClick?.(event);
}}
{/each}
{#each days as day}
{@const dayEvents = getEventsForDay(day)}
{@const isToday = isSameDay(day, today)}
{@const inMonth = isCurrentMonth(day)}
<button
class="bg-dark min-h-[80px] p-1 text-left transition-colors hover:bg-light/5"
class:opacity-40={!inMonth}
onclick={() => onDateClick?.(day)}
>
<div class="flex items-center justify-center w-7 h-7 mb-1">
<span
class="text-sm {isToday
? 'bg-primary text-white rounded-full w-7 h-7 flex items-center justify-center'
: 'text-light/80'}"
>
{event.title}
{day.getDate()}
</span>
</div>
<div class="space-y-0.5">
{#each dayEvents.slice(0, 3) as event}
<button
class="w-full text-xs px-1 py-0.5 rounded truncate text-left"
style="background-color: {event.color ??
'#6366f1'}20; color: {event.color ??
'#6366f1'}"
onclick={(e) => {
e.stopPropagation();
onEventClick?.(event);
}}
>
{event.title}
</button>
{/each}
{#if dayEvents.length > 3}
<p class="text-xs text-light/40 px-1">
+{dayEvents.length - 3} more
</p>
{/if}
</div>
</button>
{/each}
</div>
{/if}
<!-- Week View -->
{#if currentView === "week"}
<div
class="grid grid-cols-7 gap-px bg-light/10 rounded-lg overflow-hidden"
>
{#each weekDates as day}
{@const dayEvents = getEventsForDay(day)}
{@const isToday = isSameDay(day, today)}
<div class="bg-dark">
<div class="px-2 py-2 text-center border-b border-light/10">
<div class="text-xs text-light/50">
{weekDays[day.getDay()]}
</div>
<div
class="text-lg font-medium {isToday
? 'text-primary'
: 'text-light'}"
>
{day.getDate()}
</div>
</div>
<div class="min-h-[300px] p-1 space-y-1">
{#each dayEvents as event}
<button
class="w-full text-xs px-2 py-1.5 rounded text-left"
style="background-color: {event.color ??
'#6366f1'}20; color: {event.color ??
'#6366f1'}"
onclick={() => onEventClick?.(event)}
>
<div class="font-medium truncate">
{event.title}
</div>
<div class="text-[10px] opacity-70">
{new Date(
event.start_time,
).toLocaleTimeString("en-US", {
hour: "numeric",
minute: "2-digit",
})}
</div>
</button>
{/each}
</div>
</div>
{/each}
</div>
{/if}
<!-- Day View -->
{#if currentView === "day"}
{@const dayEvents = getEventsForDay(currentDate)}
<div class="bg-dark rounded-lg p-4 min-h-[400px]">
{#if dayEvents.length === 0}
<div class="text-center text-light/40 py-12">
<p>No events for this day</p>
</div>
{:else}
<div class="space-y-2">
{#each dayEvents as event}
<button
class="w-full text-left p-3 rounded-lg transition-colors hover:opacity-80"
style="background-color: {event.color ??
'#6366f1'}20; border-left: 3px solid {event.color ??
'#6366f1'}"
onclick={() => onEventClick?.(event)}
>
<div class="font-medium text-light">
{event.title}
</div>
<div class="text-sm text-light/60 mt-1">
{new Date(event.start_time).toLocaleTimeString(
"en-US",
{ hour: "numeric", minute: "2-digit" },
)}
- {new Date(event.end_time).toLocaleTimeString(
"en-US",
{ hour: "numeric", minute: "2-digit" },
)}
</div>
{#if event.description}
<div class="text-sm text-light/50 mt-2">
{event.description}
</div>
{/if}
</button>
{/each}
{#if dayEvents.length > 3}
<p class="text-xs text-light/40 px-1">+{dayEvents.length - 3} more</p>
{/if}
</div>
</button>
{/each}
</div>
{/if}
</div>
{/if}
</div>

View File

@@ -28,18 +28,36 @@
let element: HTMLDivElement;
let editor: Editor | null = $state(null);
let saveStatus = $state<"idle" | "saving" | "saved" | "error">("idle");
let saveTimeout: ReturnType<typeof setTimeout> | null = null;
let statusTimeout: ReturnType<typeof setTimeout> | null = null;
function triggerAutoSave() {
if (saveTimeout) clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => {
if (editor && onSave) {
onSave(editor.getJSON());
}
saveStatus = "idle";
saveTimeout = setTimeout(async () => {
await saveNow();
}, 1000); // Auto-save after 1 second of inactivity
}
async function saveNow() {
if (editor && onSave) {
saveStatus = "saving";
try {
await onSave(editor.getJSON());
saveStatus = "saved";
// Reset status after 2 seconds
if (statusTimeout) clearTimeout(statusTimeout);
statusTimeout = setTimeout(() => {
saveStatus = "idle";
}, 2000);
} catch {
saveStatus = "error";
}
}
}
onMount(() => {
editor = new Editor({
element,
@@ -69,6 +87,7 @@
onDestroy(() => {
if (saveTimeout) clearTimeout(saveTimeout);
if (statusTimeout) clearTimeout(statusTimeout);
editor?.destroy();
});
@@ -110,6 +129,67 @@
<div
class="flex items-center gap-1 px-2 py-1.5 border-b border-light/10 bg-dark/50"
>
<!-- Save Button -->
<button
class="flex items-center gap-1.5 px-2 py-1 mr-2 text-xs rounded hover:bg-light/10 transition-colors {saveStatus ===
'saving'
? 'text-warning'
: saveStatus === 'saved'
? 'text-success'
: saveStatus === 'error'
? 'text-error'
: 'text-light/60 hover:text-light'}"
onclick={() => saveNow()}
disabled={saveStatus === "saving"}
title="Save (Ctrl+S)"
>
{#if saveStatus === "saving"}
<div
class="w-2 h-2 rounded-full bg-warning animate-pulse"
></div>
<span>Saving...</span>
{:else if saveStatus === "saved"}
<svg
class="w-3.5 h-3.5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polyline points="20,6 9,17 4,12" />
</svg>
<span>Saved</span>
{:else if saveStatus === "error"}
<svg
class="w-3.5 h-3.5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="12" cy="12" r="10" />
<line x1="15" y1="9" x2="9" y2="15" />
<line x1="9" y1="9" x2="15" y2="15" />
</svg>
<span>Error</span>
{:else}
<svg
class="w-3.5 h-3.5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"
/>
<polyline points="17,21 17,13 7,13 7,21" />
<polyline points="7,3 7,8 15,8" />
</svg>
<span>Save</span>
{/if}
</button>
<div class="w-px h-5 bg-light/20 mr-1"></div>
<button
class="p-1.5 rounded hover:bg-light/10 text-light/60 hover:text-light transition-colors"
onclick={() => editor?.chain().focus().toggleBold().run()}

View File

@@ -5,6 +5,7 @@
items: DocumentWithChildren[];
selectedId?: string | null;
onSelect: (doc: DocumentWithChildren) => void;
onDoubleClick?: (doc: DocumentWithChildren) => void;
onAdd?: (parentId: string | null) => void;
onMove?: (docId: string, newParentId: string | null) => void;
onEdit?: (doc: DocumentWithChildren) => void;
@@ -16,6 +17,7 @@
items,
selectedId = null,
onSelect,
onDoubleClick,
onAdd,
onMove,
onEdit,
@@ -92,6 +94,7 @@
: 'text-light/80 hover:bg-light/5'}
{dragOverId === item.id ? 'ring-2 ring-primary bg-primary/10' : ''}"
onclick={() => handleSelect(item)}
ondblclick={() => onDoubleClick?.(item)}
draggable="true"
ondragstart={(e) => handleDragStart(e, item)}
ondragover={(e) =>

View File

@@ -1,14 +1,19 @@
<script lang="ts">
import type { ColumnWithCards } from '$lib/api/kanban';
import type { KanbanCard } from '$lib/supabase/types';
import { Button, Card, Badge } from '$lib/components/ui';
import type { ColumnWithCards } from "$lib/api/kanban";
import type { KanbanCard } from "$lib/supabase/types";
import { Button, Card, Badge } from "$lib/components/ui";
interface Props {
columns: ColumnWithCards[];
onCardClick?: (card: KanbanCard) => void;
onCardMove?: (cardId: string, toColumnId: string, toPosition: number) => void;
onCardMove?: (
cardId: string,
toColumnId: string,
toPosition: number,
) => void;
onAddCard?: (columnId: string) => void;
onAddColumn?: () => void;
onDeleteCard?: (cardId: string) => void;
canEdit?: boolean;
}
@@ -18,17 +23,25 @@
onCardMove,
onAddCard,
onAddColumn,
canEdit = true
onDeleteCard,
canEdit = true,
}: Props = $props();
function handleDeleteCard(e: MouseEvent, cardId: string) {
e.stopPropagation();
if (confirm("Are you sure you want to delete this task?")) {
onDeleteCard?.(cardId);
}
}
let draggedCard = $state<KanbanCard | null>(null);
let dragOverColumn = $state<string | null>(null);
function handleDragStart(e: DragEvent, card: KanbanCard) {
draggedCard = card;
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', card.id);
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("text/plain", card.id);
}
}
@@ -54,37 +67,40 @@
}
function formatDueDate(dateStr: string | null): string {
if (!dateStr) return '';
if (!dateStr) return "";
const date = new Date(dateStr);
const now = new Date();
const diff = date.getTime() - now.getTime();
const days = Math.ceil(diff / (1000 * 60 * 60 * 24));
if (days < 0) return 'Overdue';
if (days === 0) return 'Today';
if (days === 1) return 'Tomorrow';
if (days < 0) return "Overdue";
if (days === 0) return "Today";
if (days === 1) return "Tomorrow";
return date.toLocaleDateString();
}
function getDueDateColor(dateStr: string | null): 'error' | 'warning' | 'default' {
if (!dateStr) return 'default';
function getDueDateColor(
dateStr: string | null,
): "error" | "warning" | "default" {
if (!dateStr) return "default";
const date = new Date(dateStr);
const now = new Date();
const diff = date.getTime() - now.getTime();
const days = Math.ceil(diff / (1000 * 60 * 60 * 24));
if (days < 0) return 'error';
if (days <= 2) return 'warning';
return 'default';
if (days < 0) return "error";
if (days <= 2) return "warning";
return "default";
}
</script>
<div class="flex gap-4 overflow-x-auto pb-4 min-h-[500px]">
<div class="flex gap-4 overflow-x-auto pb-4 min-h-[500px] scrollbar-visible">
{#each columns as column}
<div
class="flex-shrink-0 w-72 bg-surface rounded-xl p-3 flex flex-col max-h-[calc(100vh-200px)]"
class:ring-2={dragOverColumn === column.id}
class:ring-primary={dragOverColumn === column.id}
class="flex-shrink-0 w-72 bg-surface/80 backdrop-blur-sm rounded-xl p-3 flex flex-col max-h-[calc(100vh-200px)] border border-light/10 shadow-lg {dragOverColumn ===
column.id
? 'ring-2 ring-primary bg-primary/5'
: ''}"
ondragover={(e) => handleDragOver(e, column.id)}
ondragleave={handleDragLeave}
ondrop={(e) => handleDrop(e, column.id)}
@@ -93,37 +109,71 @@
<div class="flex items-center justify-between mb-3 px-1">
<h3 class="font-medium text-light flex items-center gap-2">
{column.name}
<span class="text-xs text-light/50 bg-light/10 px-1.5 py-0.5 rounded">
<span
class="text-xs text-light/50 bg-light/10 px-1.5 py-0.5 rounded"
>
{column.cards.length}
</span>
</h3>
{#if column.color}
<div class="w-3 h-3 rounded-full" style="background-color: {column.color}"></div>
<div
class="w-3 h-3 rounded-full"
style="background-color: {column.color}"
></div>
{/if}
</div>
<div class="flex-1 overflow-y-auto space-y-2">
{#each column.cards as card}
<div
class="bg-dark rounded-lg p-3 cursor-pointer hover:ring-1 hover:ring-light/20 transition-all"
class="group bg-dark rounded-lg p-3 cursor-pointer hover:ring-1 hover:ring-light/20 transition-all relative"
class:opacity-50={draggedCard?.id === card.id}
draggable={canEdit}
ondragstart={(e) => handleDragStart(e, card)}
onclick={() => onCardClick?.(card)}
onkeydown={(e) => e.key === 'Enter' && onCardClick?.(card)}
onkeydown={(e) =>
e.key === "Enter" && onCardClick?.(card)}
role="listitem"
tabindex="0"
>
{#if card.color}
<div class="w-full h-1 rounded-full mb-2" style="background-color: {card.color}"></div>
{#if canEdit}
<button
class="absolute top-2 right-2 p-1 rounded opacity-0 group-hover:opacity-100 hover:bg-error/20 text-light/40 hover:text-error transition-all"
onclick={(e) => handleDeleteCard(e, card.id)}
title="Delete task"
>
<svg
class="w-3.5 h-3.5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polyline points="3,6 5,6 21,6" />
<path
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
/>
</svg>
</button>
{/if}
<p class="text-sm text-light">{card.title}</p>
{#if card.color}
<div
class="w-full h-1 rounded-full mb-2"
style="background-color: {card.color}"
></div>
{/if}
<p class="text-sm text-light pr-6">{card.title}</p>
{#if card.description}
<p class="text-xs text-light/50 mt-1 line-clamp-2">{card.description}</p>
<p class="text-xs text-light/50 mt-1 line-clamp-2">
{card.description}
</p>
{/if}
{#if card.due_date}
<div class="mt-2">
<Badge size="sm" variant={getDueDateColor(card.due_date)}>
<Badge
size="sm"
variant={getDueDateColor(card.due_date)}
>
{formatDueDate(card.due_date)}
</Badge>
</div>
@@ -137,7 +187,13 @@
class="mt-2 w-full py-2 text-sm text-light/50 hover:text-light hover:bg-light/5 rounded-lg transition-colors flex items-center justify-center gap-1"
onclick={() => onAddCard?.(column.id)}
>
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<svg
class="w-4 h-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
@@ -152,7 +208,13 @@
class="flex-shrink-0 w-72 h-12 bg-light/5 hover:bg-light/10 rounded-xl flex items-center justify-center gap-2 text-light/50 hover:text-light transition-colors"
onclick={() => onAddColumn?.()}
>
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<svg
class="w-5 h-5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
@@ -160,3 +222,24 @@
</button>
{/if}
</div>
<style>
.scrollbar-visible {
scrollbar-width: thin;
scrollbar-color: rgba(229, 230, 240, 0.3) transparent;
}
.scrollbar-visible::-webkit-scrollbar {
height: 8px;
}
.scrollbar-visible::-webkit-scrollbar-track {
background: rgba(229, 230, 240, 0.1);
border-radius: 4px;
}
.scrollbar-visible::-webkit-scrollbar-thumb {
background: rgba(229, 230, 240, 0.3);
border-radius: 4px;
}
.scrollbar-visible::-webkit-scrollbar-thumb:hover {
background: rgba(229, 230, 240, 0.5);
}
</style>

View File

@@ -0,0 +1,14 @@
<script lang="ts">
import { toasts } from '$lib/stores/toast';
import Toast from './Toast.svelte';
</script>
<div class="fixed bottom-4 right-4 z-50 flex flex-col gap-2 max-w-sm">
{#each $toasts as toast (toast.id)}
<Toast
variant={toast.variant}
message={toast.message}
onClose={() => toasts.remove(toast.id)}
/>
{/each}
</div>

View File

@@ -9,3 +9,4 @@ export { default as Modal } from './Modal.svelte';
export { default as Spinner } from './Spinner.svelte';
export { default as Toggle } from './Toggle.svelte';
export { default as Toast } from './Toast.svelte';
export { default as ToastContainer } from './ToastContainer.svelte';