feat: fit ai chat trigger to center of page

- Updated the CSS classes for the AI assistant bar to adjust positioning based on the new side panel width variable.
- Introduced a new `onWidthPreview` prop in the SidePanelRail component to handle live resizing and update the side panel width dynamically.
- Added width state management in the SidePanelStore to ensure proper width handling during panel interactions.
- Improved layout handling in the course layout component to accommodate the new side panel width adjustments.
This commit is contained in:
Rotimi Best
2026-06-07 23:21:19 +01:00
parent 4eee866e85
commit 03dcf812a5
4 changed files with 28 additions and 12 deletions
@@ -114,7 +114,7 @@
{#if expanded} {#if expanded}
<div <div
class="ui:bg-background ui:border-border fixed right-0 bottom-0 left-0 z-40 border-t py-3 shadow-sm md:left-[var(--sidebar-width,0px)]" class="ui:bg-background ui:border-border fixed right-0 bottom-0 left-0 z-40 border-t py-3 shadow-sm md:right-[var(--side-panel-width,0px)] md:left-[var(--sidebar-width,0px)]"
in:fly={expandIn} in:fly={expandIn}
out:fly={expandOut} out:fly={expandOut}
> >
@@ -135,7 +135,7 @@
</div> </div>
{:else} {:else}
<div <div
class="pointer-events-none fixed right-0 bottom-4 left-0 z-40 md:left-[var(--sidebar-width,0px)]" class="pointer-events-none fixed right-0 bottom-4 left-0 z-40 md:right-[var(--side-panel-width,0px)] md:left-[var(--sidebar-width,0px)]"
in:fade={collapseIn} in:fade={collapseIn}
out:fade={collapseOut} out:fade={collapseOut}
> >
@@ -5,7 +5,16 @@
import { t } from '$lib/utils/functions/translations'; import { t } from '$lib/utils/functions/translations';
import { sidePanel } from './utils/store.svelte'; import { sidePanel } from './utils/store.svelte';
let railShellElement: HTMLDivElement | null = $state(null); interface Props {
/**
* Live-resize hook. The host writes `--side-panel-width` directly to the
* shared shell element so the panel and fixed siblings (e.g. the Ask AI
* bar) follow the drag without a per-frame reactivity round-trip.
*/
onWidthPreview?: (width: number) => void;
}
let { onWidthPreview }: Props = $props();
let railWidth = $state(0); let railWidth = $state(0);
let isWidthLoaded = $state(false); let isWidthLoaded = $state(false);
@@ -65,6 +74,12 @@
isWidthLoaded = true; isWidthLoaded = true;
}); });
// Publish the effective width so the shared ancestor can expose it as a CSS
// variable that fixed siblings (e.g. the Ask AI bar) can offset against.
$effect(() => {
sidePanel.width = activeDefinition ? railWidth : 0;
});
$effect(() => { $effect(() => {
if (!isWidthLoaded) return; if (!isWidthLoaded) return;
@@ -100,7 +115,7 @@
startWidth, startWidth,
resolveWidth: ({ startWidth, deltaX }) => clampWidth(startWidth - deltaX), resolveWidth: ({ startWidth, deltaX }) => clampWidth(startWidth - deltaX),
onPreview: (width) => { onPreview: (width) => {
railShellElement?.style.setProperty('--side-panel-width', `${width}px`); onWidthPreview?.(width);
}, },
onCommit: ({ width }) => { onCommit: ({ width }) => {
railWidth = width; railWidth = width;
@@ -129,12 +144,7 @@
{#if activeDefinition} {#if activeDefinition}
{@const def = activeDefinition} {@const def = activeDefinition}
<div <div data-side-panel-resizing={isRailResizing} class="contents">
bind:this={railShellElement}
data-side-panel-resizing={isRailResizing}
class="contents"
style={`--side-panel-width: ${railWidth}px;`}
>
<div class="hidden shrink-0 md:block md:w-(--side-panel-width)"></div> <div class="hidden shrink-0 md:block md:w-(--side-panel-width)"></div>
<aside <aside
@@ -5,6 +5,7 @@ class SidePanelStore {
activePanelId = $state<string | null>(null); activePanelId = $state<string | null>(null);
panelProps = $state<Record<string, unknown>>({}); panelProps = $state<Record<string, unknown>>({});
width = $state(0);
register(definition: SidePanelDefinition) { register(definition: SidePanelDefinition) {
this.definitions.set(definition.id, definition); this.definitions.set(definition.id, definition);
@@ -48,6 +49,7 @@ class SidePanelStore {
close() { close() {
this.activePanelId = null; this.activePanelId = null;
this.panelProps = {}; this.panelProps = {};
this.width = 0;
} }
closeIfScope(scope: SidePanelScope) { closeIfScope(scope: SidePanelScope) {
@@ -122,6 +122,10 @@
sidebarProviderElement?.style.setProperty('--sidebar-width', `${nextWidth}px`); sidebarProviderElement?.style.setProperty('--sidebar-width', `${nextWidth}px`);
} }
function handleSidePanelWidthPreview(width: number) {
sidebarProviderElement?.style.setProperty('--side-panel-width', `${width}px`);
}
onMount(() => { onMount(() => {
try { try {
const storedWidth = Number(localStorage.getItem(COURSE_SIDEBAR_STORAGE_KEY)); const storedWidth = Number(localStorage.getItem(COURSE_SIDEBAR_STORAGE_KEY));
@@ -185,7 +189,7 @@
<Sidebar.Provider <Sidebar.Provider
bind:ref={sidebarProviderElement} bind:ref={sidebarProviderElement}
data-sveltekit-preload-data="off" data-sveltekit-preload-data="off"
style={`--sidebar-width: ${sidebarWidth}px;`} style={`--sidebar-width: ${sidebarWidth}px; --side-panel-width: ${sidePanel.width}px;`}
> >
<CourseSidebar <CourseSidebar
{path} {path}
@@ -224,5 +228,5 @@
{/if} {/if}
</Sidebar.Inset> </Sidebar.Inset>
<SidePanelRail /> <SidePanelRail onWidthPreview={handleSidePanelWidthPreview} />
</Sidebar.Provider> </Sidebar.Provider>