Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion components/ui/toast/ToastViewport.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,28 @@ const delegatedProps = reactiveOmit(props, "class")
</script>

<template>
<ToastViewport v-bind="delegatedProps" :class="cn('fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]', props.class)" />
<ToastViewport v-bind="delegatedProps" :class="cn('toast-viewport fixed top-0 z-40 flex max-h-screen w-full flex-col-reverse p-4 sm:top-auto sm:flex-col md:max-w-[420px]', props.class)" />
</template>

<style>
/* Bottom-left of the content column: clear of the right hub, above the admin
dock and the floating save bar. */
@media (min-width: 640px) {
.toast-viewport {
left: var(--main-content-left, 0px);
bottom: var(--main-bottom-dock-height, 0px);
}

body:has(> div > .save-bar) .toast-viewport {
bottom: calc(var(--main-bottom-dock-height, 0px) + 4.5rem);
}
}

/* z-40 keeps a toast under menus, popovers and selects (z-50). A modal's dim
overlay would bury it there, so it rises above the overlay while one is
mounted. Popovers are role="dialog" too; data-side tells them apart. */
body:has([role="dialog"]:not([data-side]), [role="alertdialog"])
.toast-viewport {
z-index: 100;
}
</style>
2 changes: 1 addition & 1 deletion components/ui/toast/Toaster.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function copyText(toast: { title?: unknown; description?: unknown }): string {
</script>

<template>
<ToastProvider>
<ToastProvider swipe-direction="left">
<Toast v-for="toast in toasts" :key="toast.id" v-bind="toast" :class="'pr-12'">
<div class="grid gap-1">
<ToastTitle v-if="toast.title" class="select-text break-words">
Expand Down
2 changes: 1 addition & 1 deletion components/ui/toast/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type { VariantProps } from "class-variance-authority"
import { cva } from "class-variance-authority"

export const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--reka-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--reka-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--reka-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--reka-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-left-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
{
variants: {
variant: {
Expand Down
18 changes: 10 additions & 8 deletions layouts/components/MainContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,31 @@ const { rightSidebarOpen, setRightSidebarOpen } = useRightSidebar();
// Publish the height of the bottom dock (e.g. MatchAdminBottomBar teleports
// into #main-bottom-dock) as a CSS variable so fixed bottom chrome — like the
// floating SettingsSaveBar — can lift itself above it. Defaults to 0 when the
// dock is empty / on pages without one.
// dock is empty / on pages without one. Its left edge is the content column's
// (past the left nav), which is where toasts line up.
const bottomDock = ref<HTMLElement | null>(null);
let dockRO: ResizeObserver | null = null;

function publishDockHeight() {
function publishDockBox() {
const h = bottomDock.value?.offsetHeight ?? 0;
document.documentElement.style.setProperty(
"--main-bottom-dock-height",
`${h}px`,
);
const left = bottomDock.value?.getBoundingClientRect().left ?? 0;
const root = document.documentElement.style;
root.setProperty("--main-bottom-dock-height", `${h}px`);
root.setProperty("--main-content-left", `${left}px`);
}

onMounted(() => {
publishDockHeight();
publishDockBox();
if (typeof ResizeObserver !== "undefined" && bottomDock.value) {
dockRO = new ResizeObserver(() => publishDockHeight());
dockRO = new ResizeObserver(() => publishDockBox());
dockRO.observe(bottomDock.value);
}
});

onBeforeUnmount(() => {
dockRO?.disconnect();
document.documentElement.style.removeProperty("--main-bottom-dock-height");
document.documentElement.style.removeProperty("--main-content-left");
});

// Inject values from default.vue
Expand Down
Loading