feat: add toast component
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { toasts } from './toast.svelte';
|
||||
|
||||
const typeClasses: Record<string, string> = {
|
||||
success: 'border-l-green-500',
|
||||
error: 'border-l-red-500',
|
||||
info: 'border-l-active'
|
||||
};
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="fixed bottom-4 right-4 flex flex-col gap-2 z-[9999] pointer-events-none"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-atomic="false"
|
||||
>
|
||||
{#each toasts.value as item (item.id)}
|
||||
<div
|
||||
class="
|
||||
bg-layer-2 text-text border border-outline rounded
|
||||
px-3.5 py-2 text-sm min-w-[180px] max-w-xs
|
||||
border-l-3 {typeClasses[item.type] ?? 'border-l-outline'}
|
||||
animate-[slide-in_0.18s_ease]
|
||||
"
|
||||
>
|
||||
{item.message}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes slide-in {
|
||||
from { opacity: 0; transform: translateX(12px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
</style>
|
||||
@@ -2,14 +2,20 @@ export { default as Input } from './Input.svelte';
|
||||
export { default as InputCheckbox } from './inputs/InputCheckbox.svelte';
|
||||
export { default as InputColor } from './inputs/InputColor.svelte';
|
||||
export { default as InputNumber } from './inputs/InputNumber.svelte';
|
||||
export { default as InputSearch } from './inputs/InputSearch.svelte';
|
||||
export { default as InputSelect } from './inputs/InputSelect.svelte';
|
||||
export { default as InputShape } from './inputs/InputShape.svelte';
|
||||
export { default as InputVec3 } from './inputs/InputVec3.svelte';
|
||||
export { default as SocketTable } from './inputs/SocketTable.svelte';
|
||||
|
||||
export { default as Button } from './Button.svelte';
|
||||
export { default as Details } from './Details.svelte';
|
||||
export { default as JsonViewer } from './JsonViewer.svelte';
|
||||
export { default as ShortCut } from './ShortCut.svelte';
|
||||
export { default as Spinner } from './Spinner.svelte';
|
||||
export { default as Toast } from './Toast.svelte';
|
||||
export { toast } from './toast.svelte';
|
||||
export { default as ConfirmDialog } from './ConfirmDialog.svelte';
|
||||
|
||||
import Input from './Input.svelte';
|
||||
export default Input;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
export type ToastType = 'info' | 'success' | 'error';
|
||||
|
||||
export type ToastItem = {
|
||||
id: number;
|
||||
message: string;
|
||||
type: ToastType;
|
||||
};
|
||||
|
||||
let _toasts = $state<ToastItem[]>([]);
|
||||
let _nextId = 0;
|
||||
|
||||
export const toasts = {
|
||||
get value() {
|
||||
return _toasts;
|
||||
}
|
||||
};
|
||||
|
||||
export function toast(message: string, type: ToastType = 'info', duration = 3000) {
|
||||
const id = _nextId++;
|
||||
_toasts.push({ id, message, type });
|
||||
setTimeout(() => {
|
||||
_toasts = _toasts.filter((t) => t.id !== id);
|
||||
}, duration);
|
||||
}
|
||||
@@ -2,15 +2,21 @@
|
||||
import type { NodeInput } from '@nodarium/types';
|
||||
import '$lib/app.css';
|
||||
import {
|
||||
Button,
|
||||
ConfirmDialog,
|
||||
Details,
|
||||
InputCheckbox,
|
||||
InputColor,
|
||||
InputNumber,
|
||||
InputSearch,
|
||||
InputSelect,
|
||||
InputShape,
|
||||
InputVec3,
|
||||
JsonViewer,
|
||||
ShortCut
|
||||
ShortCut,
|
||||
Spinner,
|
||||
Toast,
|
||||
toast
|
||||
} from '$lib';
|
||||
import SocketTable from '$lib/inputs/SocketTable.svelte';
|
||||
import Section from './Section.svelte';
|
||||
@@ -68,6 +74,7 @@
|
||||
|
||||
let points = $state([]);
|
||||
let theme = $state('dark');
|
||||
let confirmOpen = $state(false);
|
||||
</script>
|
||||
|
||||
<main class="flex flex-col gap-8 py-8">
|
||||
@@ -76,6 +83,17 @@
|
||||
<ThemeSelector bind:theme />
|
||||
</div>
|
||||
|
||||
<Section title="Button">
|
||||
<div class="flex flex-wrap gap-3 items-center">
|
||||
<Button>Default</Button>
|
||||
<Button variant="primary">Primary</Button>
|
||||
<Button variant="destructive">Destructive</Button>
|
||||
<Button variant="ghost">Ghost</Button>
|
||||
<Button disabled>Disabled</Button>
|
||||
<Button size="sm">Small</Button>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="InputNumber">
|
||||
<Theme />
|
||||
</Section>
|
||||
@@ -95,6 +113,13 @@
|
||||
<InputVec3 bind:value={vecValue} />
|
||||
</Section>
|
||||
|
||||
<Section title="InputSearch" value={options[selectValue]}>
|
||||
<div class="flex flex-col gap-2">
|
||||
<p>Searchable select — type to filter</p>
|
||||
<InputSearch bind:value={selectValue} {options} />
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Select">
|
||||
<p>
|
||||
Select with simple values
|
||||
@@ -148,12 +173,12 @@
|
||||
|
||||
<Section title="JsonViewer">
|
||||
{#snippet header()}
|
||||
<button
|
||||
<Button
|
||||
onclick={() => randomlyUpdateJson()}
|
||||
class="-mt-1 bg-layer-2 p-1 px-2 rounded-sm cursor-pointer"
|
||||
>
|
||||
update
|
||||
</button>
|
||||
</Button>
|
||||
{/snippet}
|
||||
<div class="w-64 bg-layer-1 p-2 rounded">
|
||||
<JsonViewer
|
||||
@@ -182,8 +207,46 @@
|
||||
<ShortCut alt ctrl key="delete" />
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Spinner">
|
||||
<div class="flex gap-6 items-center">
|
||||
<Spinner size={16} />
|
||||
<Spinner size={24} />
|
||||
<Spinner size={36} />
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Toast">
|
||||
<div class="flex gap-3">
|
||||
<Button onclick={() => toast('Project saved successfully', 'success')}>
|
||||
Success toast
|
||||
</Button>
|
||||
<Button onclick={() => toast('Something went wrong', 'error')}>
|
||||
Error toast
|
||||
</Button>
|
||||
<Button onclick={() => toast('Graph is executing…', 'info')}>
|
||||
Info toast
|
||||
</Button>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="ConfirmDialog">
|
||||
<Button onclick={() => (confirmOpen = true)}>
|
||||
Open dialog
|
||||
</Button>
|
||||
<ConfirmDialog
|
||||
bind:open={confirmOpen}
|
||||
title="Delete project?"
|
||||
message="This action cannot be undone. The project and all its data will be permanently removed."
|
||||
confirmLabel="Delete"
|
||||
cancelLabel="Cancel"
|
||||
onconfirm={() => toast('Project deleted', 'error')}
|
||||
/>
|
||||
</Section>
|
||||
</main>
|
||||
|
||||
<Toast />
|
||||
|
||||
<style>
|
||||
main {
|
||||
max-width: 800px;
|
||||
|
||||
Reference in New Issue
Block a user