mirror of
https://github.com/open-webui/open-webui.git
synced 2026-11-04 07:47:10 +00:00
fix(ui): keep toasts clear of the right hand panes
Toasts are anchored to the top right of the viewport, so whenever a right hand pane is open they render on top of it, covering its header, its close button and the first row of controls. Reaching a control meant waiting for the toast to expire or dismissing it first. Each right hand pane now publishes its rendered width to --right-pane-width on the document element, following the same convention the sidebar already uses for --sidebar-width, and the toast container is inset by that amount. The three panes are the chat controls pane, the channel thread pane and the notes panel. The width comes from a ResizeObserver on the pane element itself, so it tracks resizer drags and window resizes, reads 0 while a pane is collapsed, and is cleared when the pane goes away. Only the most recent pane owns the property, so navigating between routes cannot leave a stale inset behind. Toast position is unchanged. With no pane open the rule resolves to the same 32px offset svelte-sonner already applies, and it is gated above 600px so the library's full width mobile layout is untouched. If a pane is ever wide enough that a toast cannot fit beside it, the inset stops growing so the toast stays fully on screen instead of sliding off the left edge.
This commit is contained in:
parent
8faaf2cd1e
commit
77aab7d49d
5 changed files with 86 additions and 1 deletions
12
src/app.css
12
src/app.css
|
|
@ -918,3 +918,15 @@ html.high-contrast:not(.dark) .shimmer {
|
|||
color: var(--color-gray-700);
|
||||
-webkit-text-fill-color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
@media (min-width: 601px) {
|
||||
[data-sonner-toaster][data-x-position='right'] {
|
||||
right: max(
|
||||
env(safe-area-inset-right),
|
||||
min(
|
||||
calc(var(--offset) + var(--right-pane-width, 0px)),
|
||||
calc(100vw - var(--width) - var(--offset))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
user
|
||||
} from '$lib/stores';
|
||||
import { getChannelById, getChannelMessages, sendMessage } from '$lib/apis/channels';
|
||||
import { trackRightPaneWidth } from '$lib/utils/rightPane';
|
||||
|
||||
import Messages from './Messages.svelte';
|
||||
import MessageInput from './MessageInput.svelte';
|
||||
|
|
@ -266,6 +267,16 @@
|
|||
let mediaQuery;
|
||||
let largeScreen = false;
|
||||
|
||||
let threadPaneEl: HTMLElement | null = null;
|
||||
let untrackPaneWidth: (() => void) | null = null;
|
||||
|
||||
const trackPaneWidth = (el: HTMLElement | null) => {
|
||||
untrackPaneWidth?.();
|
||||
untrackPaneWidth = el ? trackRightPaneWidth(el) : null;
|
||||
};
|
||||
|
||||
$: trackPaneWidth(threadPaneEl);
|
||||
|
||||
onMount(() => {
|
||||
if ($chatId) {
|
||||
chatId.set('');
|
||||
|
|
@ -292,6 +303,8 @@
|
|||
updateLastReadAt(id);
|
||||
_channelId.set(null);
|
||||
$socket?.off('events:channel', channelEventHandler);
|
||||
untrackPaneWidth?.();
|
||||
untrackPaneWidth = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -439,7 +452,7 @@
|
|||
/>
|
||||
</PaneResizer>
|
||||
|
||||
<Pane defaultSize={50} minSize={30} class="h-full w-full">
|
||||
<Pane bind:el={threadPaneEl} defaultSize={50} minSize={30} class="h-full w-full">
|
||||
<div class="h-full w-full shadow-xl">
|
||||
<Thread
|
||||
{threadId}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
} from '$lib/stores';
|
||||
|
||||
import { uploadFile } from '$lib/apis/files';
|
||||
import { trackRightPaneWidth } from '$lib/utils/rightPane';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import Controls from './Controls/Controls.svelte';
|
||||
|
|
@ -62,6 +63,16 @@
|
|||
let minSize = 0;
|
||||
let paneReady = false;
|
||||
|
||||
let paneEl: HTMLElement | null = null;
|
||||
let untrackPaneWidth: (() => void) | null = null;
|
||||
|
||||
const trackPaneWidth = (el: HTMLElement | null) => {
|
||||
untrackPaneWidth?.();
|
||||
untrackPaneWidth = el ? trackRightPaneWidth(el) : null;
|
||||
};
|
||||
|
||||
$: trackPaneWidth(paneEl);
|
||||
|
||||
// Tab state for Controls+Files panel
|
||||
let activeTab = savedTab;
|
||||
// svelte-ignore reactive_declaration_module_script_dependency
|
||||
|
|
@ -251,6 +262,8 @@
|
|||
isDestroyed = true;
|
||||
paneReady = false;
|
||||
resizeObserver?.disconnect();
|
||||
untrackPaneWidth?.();
|
||||
untrackPaneWidth = null;
|
||||
if (!largeScreen) {
|
||||
showControls.set(false);
|
||||
}
|
||||
|
|
@ -401,6 +414,7 @@
|
|||
|
||||
<Pane
|
||||
bind:pane
|
||||
bind:el={paneEl}
|
||||
defaultSize={0}
|
||||
onResize={(size) => {
|
||||
if ($showControls && pane.isExpanded()) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { Pane, PaneResizer } from 'paneforge';
|
||||
|
||||
import Drawer from '../common/Drawer.svelte';
|
||||
import { trackRightPaneWidth } from '$lib/utils/rightPane';
|
||||
|
||||
export let show = false;
|
||||
export let pane = null;
|
||||
|
|
@ -14,6 +15,16 @@
|
|||
|
||||
let minSize = 0;
|
||||
|
||||
let paneEl: HTMLElement | null = null;
|
||||
let untrackPaneWidth: (() => void) | null = null;
|
||||
|
||||
const trackPaneWidth = (el: HTMLElement | null) => {
|
||||
untrackPaneWidth?.();
|
||||
untrackPaneWidth = el ? trackRightPaneWidth(el) : null;
|
||||
};
|
||||
|
||||
$: trackPaneWidth(paneEl);
|
||||
|
||||
const handleMediaQuery = async (e) => {
|
||||
if (e.matches) {
|
||||
largeScreen = true;
|
||||
|
|
@ -58,6 +69,8 @@
|
|||
|
||||
onDestroy(() => {
|
||||
mediaQuery.removeEventListener('change', handleMediaQuery);
|
||||
untrackPaneWidth?.();
|
||||
untrackPaneWidth = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -86,6 +99,7 @@
|
|||
|
||||
<Pane
|
||||
bind:pane
|
||||
bind:el={paneEl}
|
||||
defaultSize={Math.max(20, minSize)}
|
||||
{minSize}
|
||||
onCollapse={() => {
|
||||
|
|
|
|||
32
src/lib/utils/rightPane.ts
Normal file
32
src/lib/utils/rightPane.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
const CSS_VARIABLE = '--right-pane-width';
|
||||
|
||||
let owner: HTMLElement | null = null;
|
||||
|
||||
export const trackRightPaneWidth = (el: HTMLElement) => {
|
||||
owner = el;
|
||||
|
||||
const publish = (width: number) => {
|
||||
if (owner !== el) {
|
||||
return;
|
||||
}
|
||||
document.documentElement.style.setProperty(CSS_VARIABLE, `${width}px`);
|
||||
};
|
||||
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
publish(entry.borderBoxSize?.[0]?.inlineSize ?? entry.contentRect.width);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(el);
|
||||
publish(el.offsetWidth);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
|
||||
if (owner === el) {
|
||||
owner = null;
|
||||
document.documentElement.style.removeProperty(CSS_VARIABLE);
|
||||
}
|
||||
};
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue