Snackbar 10.18.1
The snackbar component is a non-disruptive message that appears on the interface to provide quick, at-a-glance feedback on the outcome of an action.
Based on Radix.
Anatomy
<Snackbar> <Snackbar.Icon>...</Snackbar.Icon> <Snackbar.Content> <Snackbar.Header>...</Snackbar.Header> <Snackbar.Message>...</Snackbar.Message> </Snackbar.Content> <Snackbar.Close /> </Snackbar>
Default
"use client";
import { Snackbar, Button } from "@heathmont/moon-core-tw";
import { useState, useCallback } from "react";
const Default = () => {
const [snackbar, setSnackbar] = useState("");
const openSnackbarHandler = useCallback(
(type: string) => {
if (snackbar) {
setSnackbar("");
setTimeout(() => {
setSnackbar(type);
}, 400);
} else {
setSnackbar(type);
}
},
[snackbar],
);
return (
<div>
<Button variant="outline" onClick={() => openSnackbarHandler("default")}>
Default
</Button>
<Snackbar isOpen={snackbar === "default"} onOpenChange={setSnackbar}>
<Snackbar.Message>Snackbar message</Snackbar.Message>
</Snackbar>
</div>
);
};
export default Default;
Positions
Using the position
property allows you to position <Snackbar />
in six different locations where it is the most effective: top-left
, top-center
, top-right
, bottom-left
, bottom-center
, and bottom-right
. If not specified, top-right
is the default position.
"use client";
import { Snackbar, Button } from "@heathmont/moon-core-tw";
import { useCallback, useState } from "react";
const Positions = () => {
const [snackbar, setSnackbar] = useState("");
const openSnackbarHandler = useCallback(
(type: string) => {
if (snackbar) {
setSnackbar("");
setTimeout(() => {
setSnackbar(type);
}, 400);
} else {
setSnackbar(type);
}
},
[snackbar],
);
return (
<>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("top-left")}
>
Top left
</Button>
<Snackbar
isOpen={snackbar === "top-left"}
onOpenChange={setSnackbar}
position="top-left"
>
<Snackbar.Message>
Snackbar at top-left screen corner
</Snackbar.Message>
</Snackbar>
</div>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("top-center")}
>
Top center
</Button>
<Snackbar
isOpen={snackbar === "top-center"}
onOpenChange={setSnackbar}
position="top-center"
>
<Snackbar.Message>
Snackbar at top-center screen edge
</Snackbar.Message>
</Snackbar>
</div>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("top-right")}
>
Top right
</Button>
<Snackbar isOpen={snackbar === "top-right"} onOpenChange={setSnackbar}>
<Snackbar.Message>
Snackbar at top-right screen corner
</Snackbar.Message>
</Snackbar>
</div>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("bottom-left")}
>
Bottom left
</Button>
<Snackbar
isOpen={snackbar === "bottom-left"}
onOpenChange={setSnackbar}
position="bottom-left"
>
<Snackbar.Message>
Snackbar at bottom-left screen corner
</Snackbar.Message>
</Snackbar>
</div>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("bottom-center")}
>
Bottom center
</Button>
<Snackbar
isOpen={snackbar === "bottom-center"}
onOpenChange={setSnackbar}
position="bottom-center"
>
<Snackbar.Message>
Snackbar at bottom-center screen edge
</Snackbar.Message>
</Snackbar>
</div>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("bottom-right")}
>
Bottom right
</Button>
<Snackbar
isOpen={snackbar === "bottom-right"}
onOpenChange={setSnackbar}
position="bottom-right"
>
<Snackbar.Message>
Snackbar at bottom-right screen corner
</Snackbar.Message>
</Snackbar>
</div>
</>
);
};
export default Positions;
Options
As a compound component, <Snackbar />
allows you to tailor its content according to your preferences. Utilize one or several <Snackbar.Message />
sub-components for a single-line or multiline option. Sub-elements like <Snackbar.Header />
, <Snackbar.Close />
, and <Snackbar.Icon />
add their respective content. Simply follow the component structure illustrated in the anatomy
snippet above and combine the content as needed.
"use client";
import { Snackbar, Button } from "@heathmont/moon-core-tw";
import { OtherFrame } from "@heathmont/moon-icons-tw";
import { useCallback, useState } from "react";
const Options = () => {
const [snackbar, setSnackbar] = useState("");
const openSnackbarHandler = useCallback(
(type: string) => {
if (snackbar) {
setSnackbar("");
setTimeout(() => {
setSnackbar(type);
}, 400);
} else {
setSnackbar(type);
}
},
[snackbar],
);
return (
<>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("multiline")}
>
Multi Line
</Button>
<Snackbar isOpen={snackbar === "multiline"} onOpenChange={setSnackbar}>
<Snackbar.Content>
<Snackbar.Message>Snackbar message.</Snackbar.Message>
<Snackbar.Message>
Snackbar the second line message
</Snackbar.Message>
</Snackbar.Content>
</Snackbar>
</div>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("with-header")}
>
With Header
</Button>
<Snackbar
isOpen={snackbar === "with-header"}
onOpenChange={setSnackbar}
>
<Snackbar.Content>
<Snackbar.Header>Info</Snackbar.Header>
<Snackbar.Message>Snackbar message</Snackbar.Message>
</Snackbar.Content>
</Snackbar>
</div>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("with-close")}
>
With Close
</Button>
<Snackbar isOpen={snackbar === "with-close"} onOpenChange={setSnackbar}>
<Snackbar.Message>Snackbar message</Snackbar.Message>
<Snackbar.Close />
</Snackbar>
</div>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("with-icon")}
>
With Icon
</Button>
<Snackbar isOpen={snackbar === "with-icon"} onOpenChange={setSnackbar}>
<Snackbar.Icon>
<OtherFrame />
</Snackbar.Icon>
<Snackbar.Message>Snackbar message</Snackbar.Message>
</Snackbar>
</div>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("multiline-with-icon")}
>
Multi Line with Icon
</Button>
<Snackbar
isOpen={snackbar === "multiline-with-icon"}
onOpenChange={setSnackbar}
>
<Snackbar.Icon className="h-10">
<OtherFrame />
</Snackbar.Icon>
<Snackbar.Content>
<Snackbar.Message>Snackbar message.</Snackbar.Message>
<Snackbar.Message>
Snackbar the second line message
</Snackbar.Message>
</Snackbar.Content>
</Snackbar>
</div>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("with-close-and-icon")}
>
With Close and Icon
</Button>
<Snackbar
isOpen={snackbar === "with-close-and-icon"}
onOpenChange={setSnackbar}
>
<Snackbar.Icon>
<OtherFrame />
</Snackbar.Icon>
<Snackbar.Message>Snackbar message</Snackbar.Message>
<Snackbar.Close />
</Snackbar>
</div>
</>
);
};
export default Options;
Semantic types
To apply semantic styles like success
, warning
or error
to the <Snackbar />
, utilize CSS classes that correspond with the desired appearance.
"use client";
import { Snackbar, Button } from "@heathmont/moon-core-tw";
import { OtherFrame } from "@heathmont/moon-icons-tw";
import { useCallback, useState } from "react";
const SemanticTypes = () => {
const [snackbar, setSnackbar] = useState("");
const openSnackbarHandler = useCallback(
(type: string) => {
if (snackbar) {
setSnackbar("");
setTimeout(() => {
setSnackbar(type);
}, 400);
} else {
setSnackbar(type);
}
},
[snackbar],
);
return (
<>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("success")}
>
Success
</Button>
<Snackbar isOpen={snackbar === "success"} onOpenChange={setSnackbar}>
<Snackbar.Icon className="h-10 bg-roshi-10 text-roshi">
<OtherFrame />
</Snackbar.Icon>
<Snackbar.Content>
<Snackbar.Header>Success</Snackbar.Header>
<Snackbar.Message>Snackbar message</Snackbar.Message>
</Snackbar.Content>
<Snackbar.Close />
</Snackbar>
</div>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("warning")}
>
Warning
</Button>
<Snackbar isOpen={snackbar === "warning"} onOpenChange={setSnackbar}>
<Snackbar.Icon className="h-10 bg-krillin-10 text-krillin">
<OtherFrame />
</Snackbar.Icon>
<Snackbar.Content>
<Snackbar.Header>Warning</Snackbar.Header>
<Snackbar.Message>Snackbar message</Snackbar.Message>
</Snackbar.Content>
<Snackbar.Close />
</Snackbar>
</div>
<div>
<Button variant="outline" onClick={() => openSnackbarHandler("error")}>
Error
</Button>
<Snackbar isOpen={snackbar === "error"} onOpenChange={setSnackbar}>
<Snackbar.Icon className="h-10 bg-chichi-10 text-chichi">
<OtherFrame />
</Snackbar.Icon>
<Snackbar.Content>
<Snackbar.Header>Error</Snackbar.Header>
<Snackbar.Message>Snackbar message</Snackbar.Message>
</Snackbar.Content>
<Snackbar.Close />
</Snackbar>
</div>
</>
);
};
export default SemanticTypes;
Auto close
The autoClose
property allows you to adjust the duration of the <Snackbar />
visibility. If not defined, the default duration is 5000ms
.
"use client";
import { Snackbar, Button } from "@heathmont/moon-core-tw";
import { useState, useCallback } from "react";
const AutoClose = () => {
const [snackbar, setSnackbar] = useState("");
const openSnackbarHandler = useCallback(
(type: string) => {
if (snackbar) {
setSnackbar("");
setTimeout(() => {
setSnackbar(type);
}, 400);
} else {
setSnackbar(type);
}
},
[snackbar],
);
return (
<>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("one-sec")}
>
Close after 1 sec
</Button>
<Snackbar
isOpen={snackbar === "one-sec"}
onOpenChange={setSnackbar}
autoClose={1000}
>
<Snackbar.Message>Snackbar message</Snackbar.Message>
</Snackbar>
</div>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("three-sec")}
>
Close after 3 sec
</Button>
<Snackbar
isOpen={snackbar === "three-sec"}
onOpenChange={setSnackbar}
autoClose={3000}
>
<Snackbar.Message>Snackbar message</Snackbar.Message>
</Snackbar>
</div>
</>
);
};
export default AutoClose;
Customization
In case you need to add some extra styling to the <Snackbar/>
component, you can easily use the className
property. It applies not only to the <Snackbar />
component itself but also to all its sub-components - <Snackbar.Icon />
, <Snackbar.Close />
, <Snackbar.Content />
, <Snackbar.Header />
and <Snackbar.Message />
.
"use client";
import { Snackbar, Button } from "@heathmont/moon-core-tw";
import { useState, useCallback } from "react";
const Customization = () => {
const [snackbar, setSnackbar] = useState("");
const openSnackbarHandler = useCallback(
(type: string) => {
if (snackbar) {
setSnackbar("");
setTimeout(() => {
setSnackbar(type);
}, 400);
} else {
setSnackbar(type);
}
},
[snackbar],
);
return (
<>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("border-radius")}
>
Border radius
</Button>
<Snackbar
isOpen={snackbar === "border-radius"}
onOpenChange={setSnackbar}
className="rounded-none"
>
<Snackbar.Message>Custom border radius</Snackbar.Message>
</Snackbar>
</div>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("background")}
>
Background
</Button>
<Snackbar
isOpen={snackbar === "background"}
onOpenChange={setSnackbar}
className="bg-roshi"
>
<Snackbar.Message>Custom background color</Snackbar.Message>
</Snackbar>
</div>
<div>
<Button variant="outline" onClick={() => openSnackbarHandler("width")}>
Width
</Button>
<Snackbar
isOpen={snackbar === "width"}
onOpenChange={setSnackbar}
className="w-72"
>
<Snackbar.Message>Custom width</Snackbar.Message>
</Snackbar>
</div>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("always-light")}
>
Always light
</Button>
<Snackbar
isOpen={snackbar === "always-light"}
onOpenChange={setSnackbar}
className="theme-moon-light"
>
<Snackbar.Message>Applying light theme</Snackbar.Message>
</Snackbar>
</div>
<div>
<Button
variant="outline"
onClick={() => openSnackbarHandler("always-dark")}
>
Always dark
</Button>
<Snackbar
isOpen={snackbar === "always-dark"}
onOpenChange={setSnackbar}
className="theme-moon-dark"
>
<Snackbar.Message>Applying dark theme</Snackbar.Message>
</Snackbar>
</div>
<div>
<Button variant="outline" onClick={() => openSnackbarHandler("fonts")}>
Fonts
</Button>
<Snackbar isOpen={snackbar === "fonts"} onOpenChange={setSnackbar}>
<Snackbar.Content>
<Snackbar.Message className="text-moon-24 text-chichi">
Custom fonts.
</Snackbar.Message>
<Snackbar.Message className="text-moon-12 font-medium">
Even more custom fonts.
</Snackbar.Message>
</Snackbar.Content>
</Snackbar>
</div>
</>
);
};
export default Customization;
Snackbar Queue
This example demonstrates the use of the <Snackbar />
component for managing queued notifications.
"use client";
import { useState, useCallback } from "react";
import { Snackbar, Button } from "@heathmont/moon-core-tw";
type NotificationType = {
message: string;
type: string;
};
type QueueType = [
list: NotificationType[],
options: {
dequeue: () => NotificationType | undefined;
enqueue: (item: NotificationType) => number;
length: number;
peek: () => NotificationType | undefined;
},
];
const useQueueState = (initialList: NotificationType[]): QueueType => {
const [list, setList] = useState<NotificationType[]>([...initialList]);
const enqueue = useCallback(
(item: NotificationType) => {
const newList = [...list, item];
setList(newList);
return newList.length;
},
[list],
);
const dequeue = useCallback(() => {
if (list.length > 0) {
const firstItem = list[0];
setList([...list.slice(1)]);
return firstItem;
}
return undefined;
}, [list]);
const peek = useCallback(() => {
if (list.length > 0) {
return list[0];
}
return undefined;
}, [list]);
const options = {
dequeue,
enqueue,
length: list.length,
peek,
};
return [list, options];
};
const delay = (delayMs: number) =>
new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, delayMs);
});
const SnackbarQueue = () => {
const [list, options] = useQueueState([] as NotificationType[]);
const [isAnimated, setIsAnimated] = useState(false);
const onOpenChange = async () => {
setIsAnimated(true);
await delay(100);
setIsAnimated(false);
options.dequeue();
};
const openSnackbarHandler = useCallback(
(notification: NotificationType) => {
options.enqueue(notification);
},
[options],
);
const isOpen = options?.length > 0 && !isAnimated;
return (
<div>
<Button
variant="outline"
onClick={() =>
openSnackbarHandler({
message: `Number ${options.length + 1} Snackbar`,
type: "info",
})
}
>
Queue Snackbar
</Button>
<Snackbar isOpen={isOpen} onOpenChange={onOpenChange}>
<Snackbar.Message>{options?.peek()?.message}</Snackbar.Message>
<Snackbar.Close />
</Snackbar>
</div>
);
};
export default SnackbarQueue;
Snackbar
These are props specific to the Snackbar component:
Name | Type | Default |
---|---|---|
autoClose | number | 5000 |
children | "React.ReactNode" | - |
className | string | - |
isOpen* | boolean | - |
position | enum | top-right |
onOpenChange* | "() => void" | - |
Properties indicated with * are required.
Snackbar.Content
These are props specific to the Snackbar.Content component:
Name | Type | Default |
---|---|---|
children | "React.ReactNode" | - |
className | string | - |
Snackbar.Header
These are props specific to the Snackbar.Header component:
Name | Type | Default |
---|---|---|
children | "React.ReactNode" | - |
className | string | - |
Snackbar.Message
These are props specific to the Snackbar.Message component:
Name | Type | Default |
---|---|---|
children | "React.ReactNode" | - |
className | string | - |
Snackbar.Icon
These are props specific to the Snackbar.Icon component:
Name | Type | Default |
---|---|---|
children | "React.ReactNode" | - |
className | string | - |
Snackbar.Close
These are props specific to the Snackbar.Close component:
Name | Type | Default |
---|---|---|
ariaLabel | string | Close |
className | string | - |