Skip to content

Commit 2e295c3

Browse files
authored
docs: toast stack vertical (#1832)
* docs: toast example stack up vertical * fix: remove unnecessary code * feat: add ToastContainer * fix: change to ReturnType * docs: example update * docs: add visible prop to ToastContainer ex * docs: annotate the map as Record<> * fix: add theme, and theme.ts * docs: add Code of conduct * fix: extends and add ...restProps
1 parent 5ba079f commit 2e295c3

File tree

11 files changed

+300
-9
lines changed

11 files changed

+300
-9
lines changed

CODE_OF_CONDUCT.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Contributor Code of Conduct
2+
3+
We’re committed to maintaining a welcoming, respectful, and collaborative environment for everyone contributing to this project.
4+
5+
## Guidelines
6+
7+
- Be kind, constructive, and professional in all discussions.
8+
- Use **public project channels** (issues, pull requests, discussions) for feedback or questions.
9+
- **Do not send personal emails or direct messages** to maintainers for criticism, complaints, or disputes.
10+
- Harassment, personal attacks, or abusive communication — whether public or private — will not be tolerated.
11+
- Maintainers may remove or block contributors who violate these guidelines.
12+
13+
## Scope
14+
15+
This Code of Conduct applies in all project spaces and communication channels related to the project, both public and private.
16+
17+
## Contact
18+
19+
If you experience or witness unacceptable behavior, please report it through the project’s **GitHub issues or discussions**, not via personal email.
20+
21+
---
22+
23+
_Adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1._

CONTRIBUTING.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22

33
Here are some guidelines we'd like you to follow before submitting a PR.
44

5+
## Respectful Communication
6+
7+
We welcome feedback and discussions about the project, but all communication must remain respectful and professional.
8+
9+
- Project-related discussions should take place in public channels such as GitHub issues, discussions, or pull requests.
10+
11+
- Personal emails, DMs, or other private contact should not be used to criticize, pressure, or personally address maintainers or contributors.
12+
13+
- If you have feedback, please share it constructively and publicly, so others can participate and learn.
14+
15+
- Harassment, personal attacks, or abusive messages — whether public or private — will not be tolerated and may result in being blocked or reported.
16+
17+
Maintainers reserve the right to decide when communication crosses the line and to take appropriate action.
18+
519
## Frontend pages
620

721
<p><a href="https://tailwindcss.com/docs/content-configuration#dynamic-class-names">Tailwind warns</a> that you don't construct class names dynamically. Instead of this:</p>

src/lib/theme/themes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export { stepper, progressStepper, verticalStepper, detailedStepper, breadcrumbS
3636
export { table, tableBodyCell, tableBodyRow, tableHead, tableHeadCell, tableSearch } from "../table";
3737
export { tabs, tabItem } from "../tabs";
3838
export { timeline, activity, activityItem, group, groupItem, timelineItem } from "../timeline";
39-
export { toast } from "../toast";
39+
export { toast, toastContainer } from "../toast";
4040
export { toolbar, toolbarButton, toolbarGroup } from "../toolbar";
4141
export { tooltip } from "../tooltip";
4242
// export {utils} from "../utils";
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script lang="ts">
2+
import type { ToastContainerProps } from "$lib/types";
3+
import clsx from "clsx";
4+
import { toastContainer } from "./theme";
5+
import { getTheme } from "$lib/theme/themeUtils";
6+
7+
let { children, position = "top-right", class: className, ...restProps }: ToastContainerProps = $props();
8+
9+
const theme = getTheme("toastContainer");
10+
11+
const positionClasses = {
12+
"top-left": "top-4 left-4",
13+
"top-right": "top-4 right-4",
14+
"bottom-left": "bottom-4 left-4",
15+
"bottom-right": "bottom-4 right-4"
16+
};
17+
18+
const base = toastContainer({class: clsx(positionClasses[position], theme, className)})
19+
20+
</script>
21+
22+
<div {...restProps} class={base}>
23+
{@render children()}
24+
</div>

src/lib/toast/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { default as Toast } from "./Toast.svelte";
2-
export { toast } from "./theme";
2+
export { default as ToastContainer } from "./ToastContainer.svelte";
3+
export { toast, toastContainer } from "./theme";

src/lib/toast/theme.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,7 @@ export const toast = tv({
104104
}
105105
}
106106
});
107+
108+
export const toastContainer = tv({
109+
base:"fixed z-50 space-y-3"
110+
})

src/lib/types.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ export interface DrawerProps extends DrawerVariants, Omit<DialogProps, "hidden"
623623
offset?: string;
624624
}
625625

626-
export interface DrawerHandleProps extends DrawerHandleVariants, HTMLButtonAttributes {}
626+
export interface DrawerHandleProps extends DrawerHandleVariants, HTMLButtonAttributes { }
627627

628628
export interface DrawerheadProps extends DrawerheadVariants, HTMLButtonAttributes {
629629
closeIcon?: Snippet;
@@ -782,7 +782,7 @@ export interface FloatingLabelInputProps extends FloatingLabelInputVaratiants, O
782782
}
783783

784784
// helper
785-
export interface HelperProps extends HelperVariants, Omit<HTMLAttributes<HTMLParagraphElement>, "color"> {}
785+
export interface HelperProps extends HelperVariants, Omit<HTMLAttributes<HTMLParagraphElement>, "color"> { }
786786

787787
// input
788788
export type InputValue = string | number | string[] | undefined;
@@ -978,7 +978,7 @@ export interface TimepickerProps {
978978
timeIntervals?: string[];
979979
columns?: ColumnCount;
980980
// Callback props instead of events
981-
onselect?: (data: { time: string; endTime: string; [key: string]: string }) => void;
981+
onselect?: (data: { time: string; endTime: string;[key: string]: string }) => void;
982982
}
983983

984984
// textarea
@@ -1167,7 +1167,7 @@ export interface ToolbarProps extends ToolbarVariants, Omit<HTMLAttributes<HTMLD
11671167
end?: Snippet;
11681168
}
11691169

1170-
export interface ToolbarGroupProps extends ToolbarGroupVariants, HTMLAttributes<HTMLDivElement> {}
1170+
export interface ToolbarGroupProps extends ToolbarGroupVariants, HTMLAttributes<HTMLDivElement> { }
11711171

11721172
export type ToolbarButtonProps = ToolbarButtonVariants &
11731173
AnchorButtonAttributes & {
@@ -1484,7 +1484,7 @@ export interface SkeletonProps extends SkeletonVariants, HTMLAttributes<HTMLDivE
14841484
size?: SkeletonVariants["size"];
14851485
}
14861486

1487-
export interface TestimonialPlaceholderProps extends TestimonialPlaceholderVariants, HTMLAttributes<HTMLDivElement> {}
1487+
export interface TestimonialPlaceholderProps extends TestimonialPlaceholderVariants, HTMLAttributes<HTMLDivElement> { }
14881488

14891489
export interface TextPlaceholderProps extends TextPlaceholderVariants, HTMLAttributes<HTMLDivElement> {
14901490
size?: TextPlaceholderVariants["size"];
@@ -1494,7 +1494,7 @@ export interface VideoPlaceholderProps extends VideoPlaceholderVariants, HTMLAtt
14941494
size?: VideoPlaceholderVariants["size"];
14951495
}
14961496

1497-
export interface WidgetPlaceholderProps extends WidgetPlaceholderVariants, HTMLAttributes<HTMLDivElement> {}
1497+
export interface WidgetPlaceholderProps extends WidgetPlaceholderVariants, HTMLAttributes<HTMLDivElement> { }
14981498

14991499
// speeddial
15001500
export interface SpeedCtxType {
@@ -1888,8 +1888,14 @@ export interface ToastProps extends ToastVaraints, HTMLAttributes<HTMLDivElement
18881888
class?: string;
18891889
}
18901890

1891+
export interface ToastContainerProps extends HTMLAttributes<HTMLDivElement> {
1892+
children: Snippet;
1893+
position?: ToastVaraints["position"];
1894+
class?: ClassValue | null;
1895+
}
1896+
18911897
// tooltip
1892-
export interface TooltipProps extends TooltipVariants, PopperProps {}
1898+
export interface TooltipProps extends TooltipVariants, PopperProps { }
18931899

18941900
// typography
18951901
// anchor
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<script lang="ts">
2+
import { Toast, P, Button, Heading } from "flowbite-svelte";
3+
import { fly } from "svelte/transition";
4+
5+
type ToastColor = "green" | "red" | "yellow" | "blue";
6+
7+
interface ToastItem {
8+
id: number;
9+
message: string;
10+
color: ToastColor;
11+
timeoutId?: NodeJS.Timeout | number;
12+
}
13+
14+
let toasts = $state<ToastItem[]>([]);
15+
let nextId = $state(1);
16+
17+
const messages = {
18+
green: "Successfully saved!",
19+
blue: "New message received",
20+
yellow: "Please review your changes",
21+
red: "Operation failed"
22+
};
23+
24+
function addToast(color?: ToastColor) {
25+
const selectedColor = color || (["green", "blue", "yellow", "red"][Math.floor(Math.random() * 4)] as ToastColor);
26+
const newToast: ToastItem = {
27+
id: nextId,
28+
message: messages[selectedColor],
29+
color: selectedColor
30+
};
31+
32+
toasts = [...toasts, newToast];
33+
nextId++;
34+
35+
// Auto-dismiss after 5 seconds
36+
const timeoutId = setTimeout(() => {
37+
dismissToast(newToast.id);
38+
}, 5000);
39+
// Store timeout ID for cleanup
40+
toasts = toasts.map((t) => (t.id === newToast.id ? { ...t, timeoutId } : t));
41+
}
42+
43+
function dismissToast(id: number) {
44+
// Clear timeout if it exists
45+
const toast = toasts.find((t) => t.id === id);
46+
if (toast?.timeoutId) {
47+
clearTimeout(toast.timeoutId);
48+
}
49+
toasts = toasts.filter((toast) => toast.id !== id);
50+
}
51+
52+
function handleClose(id: number) {
53+
return () => {
54+
dismissToast(id);
55+
};
56+
}
57+
</script>
58+
59+
<div class="relative min-h-screen p-8">
60+
<div class="z-50 space-y-3" style="position: absolute; top: 1rem; right: 1rem;">
61+
{#each toasts as toast (toast.id)}
62+
<Toast color={toast.color} dismissable={true} transition={fly} params={{ x: 400, duration: 300 }} class="w-64" onclose={handleClose(toast.id)}>
63+
{toast.message}
64+
</Toast>
65+
{/each}
66+
</div>
67+
68+
<div class="mx-auto max-w-2xl">
69+
<div class="rounded-xl p-8 shadow-lg">
70+
<div class="mb-6 flex items-center gap-3">
71+
<svg class="h-8 w-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
72+
<path
73+
stroke-linecap="round"
74+
stroke-linejoin="round"
75+
stroke-width="2"
76+
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"
77+
/>
78+
</svg>
79+
<Heading tag="h1" class="text-3xl">Toast Notifications Demo</Heading>
80+
</div>
81+
<P class="mb-8">Click the buttons below to trigger toast notifications. Each toast will appear in the top-right corner and automatically dismiss after 5 seconds.</P>
82+
<div class="space-y-4">
83+
<div class="grid grid-cols-2 gap-3">
84+
<Button onclick={() => addToast("green")} color="green">Success Toast</Button>
85+
<Button onclick={() => addToast("blue")} color="blue">Info Toast</Button>
86+
<Button onclick={() => addToast("yellow")} color="yellow">Warning Toast</Button>
87+
<Button onclick={() => addToast("red")} color="red">Error Toast</Button>
88+
</div>
89+
<Button onclick={() => addToast()} color="dark" class="w-full">Random Toast</Button>
90+
</div>
91+
</div>
92+
</div>
93+
</div>
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<script lang="ts">
2+
import { Toast, ToastContainer, P, Button, Heading } from "flowbite-svelte";
3+
import { fly } from "svelte/transition";
4+
import { onDestroy } from "svelte";
5+
6+
type ToastColor = "green" | "red" | "yellow" | "blue";
7+
8+
interface ToastItem {
9+
id: number;
10+
message: string;
11+
color: ToastColor;
12+
timeoutId?: ReturnType<typeof setTimeout>;
13+
visible: boolean;
14+
}
15+
16+
let toasts = $state<ToastItem[]>([]);
17+
let nextId = $state(1);
18+
19+
const messages: Record<ToastColor, string> = {
20+
green: "Successfully saved!",
21+
blue: "New message received",
22+
yellow: "Please review your changes",
23+
red: "Operation failed"
24+
};
25+
26+
function addToast(color?: ToastColor) {
27+
const selectedColor = color || (["green", "blue", "yellow", "red"][Math.floor(Math.random() * 4)] as ToastColor);
28+
const newToast: ToastItem = {
29+
id: nextId,
30+
message: messages[selectedColor],
31+
color: selectedColor,
32+
visible: true
33+
};
34+
35+
// Auto-dismiss after 5 seconds
36+
const timeoutId = setTimeout(() => {
37+
dismissToast(newToast.id);
38+
}, 5000);
39+
newToast.timeoutId = timeoutId;
40+
41+
toasts = [...toasts, newToast];
42+
nextId++;
43+
}
44+
45+
function dismissToast(id: number) {
46+
// Clear timeout if it exists
47+
const toast = toasts.find((t) => t.id === id);
48+
if (toast?.timeoutId) {
49+
clearTimeout(toast.timeoutId);
50+
}
51+
52+
// Set visible to false to trigger outro transition
53+
toasts = toasts.map((t) => (t.id === id ? { ...t, visible: false } : t));
54+
55+
setTimeout(() => {
56+
toasts = toasts.filter((t) => t.id !== id);
57+
}, 300); // Slightly longer than transition duration
58+
}
59+
60+
function handleClose(id: number) {
61+
return () => {
62+
dismissToast(id);
63+
};
64+
}
65+
66+
onDestroy(() => {
67+
// Clear all pending timeouts on unmount
68+
toasts.forEach((toast) => {
69+
if (toast.timeoutId) {
70+
clearTimeout(toast.timeoutId);
71+
}
72+
});
73+
});
74+
</script>
75+
76+
<ToastContainer position="top-right">
77+
{#each toasts as toast (toast.id)}
78+
<Toast
79+
color={toast.color}
80+
dismissable={true}
81+
transition={fly}
82+
params={{ x: 200, duration: 800 }}
83+
class="w-64"
84+
onclose={handleClose(toast.id)}
85+
bind:toastStatus={toast.visible}
86+
>
87+
{toast.message}
88+
</Toast>
89+
{/each}
90+
</ToastContainer>
91+
92+
<div class="relative min-h-screen p-8">
93+
<div class="mx-auto max-w-2xl">
94+
<div class="rounded-xl p-8 shadow-lg">
95+
<div class="mb-6 flex items-center gap-3">
96+
<svg class="h-8 w-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
97+
<path
98+
stroke-linecap="round"
99+
stroke-linejoin="round"
100+
stroke-width="2"
101+
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"
102+
/>
103+
</svg>
104+
<Heading tag="h1" class="text-3xl">Toast Notifications Demo</Heading>
105+
</div>
106+
<P class="mb-8">Click the buttons below to trigger toast notifications. Each toast will appear in the top-right corner and automatically dismiss after 5 seconds.</P>
107+
<div class="space-y-4">
108+
<div class="grid grid-cols-2 gap-3">
109+
<Button onclick={() => addToast("green")} color="green">Success Toast</Button>
110+
<Button onclick={() => addToast("blue")} color="blue">Info Toast</Button>
111+
<Button onclick={() => addToast("yellow")} color="yellow">Warning Toast</Button>
112+
<Button onclick={() => addToast("red")} color="red">Error Toast</Button>
113+
</div>
114+
<Button onclick={() => addToast()} color="dark" class="w-full">Random Toast</Button>
115+
</div>
116+
</div>
117+
</div>
118+
</div>

src/routes/docs-examples/components/toast/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ export { default as Positioning } from "./Positioning.svelte";
1010
export { default as Push } from "./Push.svelte";
1111
export { default as Simple } from "./Simple.svelte";
1212
export { default as Toast } from "./Toast.svelte";
13+
export { default as ToastContainerEx } from "./ToastContainerEx.svelte";
1314
export { default as Transitions } from "./Transitions.svelte";
1415
export { default as Undo } from "./Undo.svelte";

0 commit comments

Comments
 (0)