BasecoatUI components for htmy.

Toast

Example:

Permanent toast

No icons, no actions.


Code example:

from htmy import ComponentType, html

from htmui.basecoat.toast import toast, toast_content, toaster

_toaster_id = "toaster"


def example() -> ComponentType:
    return html.div(
        html.div(
            html.button(
                "Trigger notification.",
                class_="btn",
                data_variant="outline",
                onclick=(
                    f"document.getElementById('{_toaster_id}').toast({{"
                    "category: 'success',"
                    "title: 'Success',"
                    "description: 'Toast triggered by a button click.',"
                    "cancel: { label: 'Dismiss' }"
                    "})"
                ),
            ),
            class_="grow",
        ),
        toaster(
            toast(
                toast_content(
                    "No icons, no actions.",
                    title="Permanent toast",
                ),
                category="success",
                duration=-1,
            ),
            id=_toaster_id,
            align="center",
            class_="min-w-2xs md:min-w-sm",
            style="position: relative",  # Override fixed placement for this example.
        ),
        class_="flex flex-col items-center",
    )

Component implementation:

For more details, see the BasecoatUI documentation.

from typing import Literal

from htmy import ComponentType, Fragment, PropertyValue, SafeStr, html, join_classes

from .typing import Align

__version__ = "0.2.0"
__framework__ = "BasecoatUI"
__framework_version__ = "1"
__framework_url__ = "https://basecoatui.com/components/toast/"

ToastCategory = Literal["success", "info", "warning", "error"]

js = SafeStr(
    '<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1/dist/js/toast.min.js" defer></script>'
)


def toaster(
    *children: ComponentType,
    id: str,
    class_: str | None = None,
    align: Align | None = None,
    **kwargs: PropertyValue,
) -> ComponentType:
    """
    Toast container.

    Arguments:
        *children: Initial toasts rendered inside the container.
        id: The unique identifier of the component.
        class_: Extra CSS classes for the container.
        align: Horizontal alignment of the toaster.
        **kwargs: Extra attributes for the root element.
    """
    if align is not None:
        kwargs["data-align"] = align

    return html.div(*children, class_=join_classes("toaster", class_), id=id, **kwargs)


def toast(
    *children: ComponentType,
    category: ToastCategory | None = None,
    class_: str | None = None,
    duration: int | None = None,
    **kwargs: PropertyValue,
) -> ComponentType:
    """
    Toast item.

    Arguments:
        *children: Toast content. Use `toast_content()` for the standard toast layout.
        category: Semantic category that controls styling.
        class_: Optional CSS classes for the toast.
        duration: Timeout in milliseconds. Use `-1` to keep the toast open.
        **kwargs: Extra attributes for the root element.
    """
    if category is not None:
        kwargs["data-category"] = category
    if duration is not None:
        kwargs["data-duration"] = duration

    return html.div(
        html.div(*children, class_="toast-content"),
        class_=join_classes("toast", class_),
        role="alert" if category == "error" else "status",
        aria_atomic="true",
        aria_hidden="false",
        **kwargs,
    )


def toast_content(
    description: ComponentType,
    *,
    title: ComponentType | None = None,
    icon: ComponentType | None = None,
    footer: ComponentType | None = None,
) -> ComponentType:
    """
    Standard toast content layout.

    Arguments:
        description: Toast description.
        title: Optional toast title.
        icon: Optional icon.
        footer: Optional footer element, typically an `html.footer(...)`.
    """
    return Fragment(
        icon,
        html.section(
            None if title is None else html.h2(title),
            html.p(description),
        ),
        footer,
    )