clean(desktop): tighten hover-reveal pane code
KISS pass — flatten the translate ternary, derive a single `revealed`, inline the edge style, drop the redundant set-guard, and trim comments to the house one-liner style. No behavior change.
This commit is contained in:
@@ -248,8 +248,7 @@ export function ChatSidebar({
|
|||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const s = t.sidebar
|
const s = t.sidebar
|
||||||
const sidebarOpen = useStore($sidebarOpen)
|
const sidebarOpen = useStore($sidebarOpen)
|
||||||
// When collapsed but hover-revealed (floated over content), render the full
|
// Collapsed-but-hover-revealed → render the full sidebar, not just the nav rail.
|
||||||
// sidebar — search field, pinned + recents — not just the nav rail.
|
|
||||||
const sidebarRevealed = useStore($sidebarRevealed)
|
const sidebarRevealed = useStore($sidebarRevealed)
|
||||||
const contentVisible = sidebarOpen || sidebarRevealed
|
const contentVisible = sidebarOpen || sidebarRevealed
|
||||||
const panesFlipped = useStore($panesFlipped)
|
const panesFlipped = useStore($panesFlipped)
|
||||||
@@ -586,10 +585,7 @@ export function ChatSidebar({
|
|||||||
sidebarOpen
|
sidebarOpen
|
||||||
? 'border-(--sidebar-edge-border) bg-(--ui-sidebar-surface-background) opacity-100'
|
? 'border-(--sidebar-edge-border) bg-(--ui-sidebar-surface-background) opacity-100'
|
||||||
: 'pointer-events-none border-transparent bg-transparent opacity-0',
|
: 'pointer-events-none border-transparent bg-transparent opacity-0',
|
||||||
// Hover-reveal overlay: when collapsed, the PaneShell floats this
|
// While floated by PaneShell's hover-reveal, force visible + interactive.
|
||||||
// sidebar over the content and marks the wrapper `data-pane-hover-reveal`.
|
|
||||||
// Force it fully visible + interactive while revealed, regardless of the
|
|
||||||
// collapsed (sidebarOpen=false) styling above.
|
|
||||||
'in-data-[pane-hover-reveal=open]:pointer-events-auto in-data-[pane-hover-reveal=open]:border-(--sidebar-edge-border) in-data-[pane-hover-reveal=open]:bg-(--ui-sidebar-surface-background) in-data-[pane-hover-reveal=open]:opacity-100'
|
'in-data-[pane-hover-reveal=open]:pointer-events-auto in-data-[pane-hover-reveal=open]:border-(--sidebar-edge-border) in-data-[pane-hover-reveal=open]:bg-(--ui-sidebar-surface-background) in-data-[pane-hover-reveal=open]:opacity-100'
|
||||||
)}
|
)}
|
||||||
collapsible="none"
|
collapsible="none"
|
||||||
|
|||||||
@@ -32,12 +32,7 @@ export interface PaneProps {
|
|||||||
defaultOpen?: boolean
|
defaultOpen?: boolean
|
||||||
/** Forces the pane closed (track→0, aria-hidden) without writing to the store — for transient route gates. */
|
/** Forces the pane closed (track→0, aria-hidden) without writing to the store — for transient route gates. */
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
/**
|
/** When collapsed, float the contents over the main column on hover/focus instead of hiding them (track stays 0px). */
|
||||||
* When the pane is collapsed, reveal its contents as a fixed overlay on hover
|
|
||||||
* (or keyboard focus) instead of leaving it fully hidden. The overlay floats
|
|
||||||
* over the main content — it does not reserve grid space, so the collapsed
|
|
||||||
* track stays at 0px.
|
|
||||||
*/
|
|
||||||
hoverReveal?: boolean
|
hoverReveal?: boolean
|
||||||
/** Called with the reveal state whenever a collapsed hoverReveal pane floats in/out. */
|
/** Called with the reveal state whenever a collapsed hoverReveal pane floats in/out. */
|
||||||
onHoverRevealChange?: (revealed: boolean) => void
|
onHoverRevealChange?: (revealed: boolean) => void
|
||||||
@@ -234,28 +229,23 @@ export function Pane({
|
|||||||
const side = slot?.side ?? 'left'
|
const side = slot?.side ?? 'left'
|
||||||
|
|
||||||
// Collapsed + hoverReveal: float the pane contents over the main column on
|
// Collapsed + hoverReveal: float the pane contents over the main column on
|
||||||
// hover/focus instead of leaving them fully hidden. Honors any persisted
|
// hover/focus instead of hiding them. Honors any persisted resize width.
|
||||||
// resize width so the overlay matches the pane's expanded size.
|
|
||||||
const overlayActive = !open && hoverReveal && !disabled
|
const overlayActive = !open && hoverReveal && !disabled
|
||||||
|
const override = resizable ? paneStates[id]?.widthOverride : undefined
|
||||||
|
const overlayWidth = override !== undefined ? `${override}px` : widthToCss(width, DEFAULT_WIDTH)
|
||||||
|
const revealed = overlayActive && hoverRevealed
|
||||||
|
|
||||||
const overlayWidth =
|
// Reset stale reveal state when the track reopens/disables, and surface the
|
||||||
resizable && paneStates[id]?.widthOverride !== undefined
|
// effective state so consumers can render full content while floated.
|
||||||
? `${paneStates[id]?.widthOverride}px`
|
|
||||||
: widthToCss(width, DEFAULT_WIDTH)
|
|
||||||
|
|
||||||
// Collapse the reveal whenever the pane reopens or gets disabled so it never
|
|
||||||
// sticks "open" underneath the now-expanded track.
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!overlayActive && hoverRevealed) {
|
if (!overlayActive) {
|
||||||
setHoverRevealed(false)
|
setHoverRevealed(false)
|
||||||
}
|
}
|
||||||
}, [overlayActive, hoverRevealed])
|
}, [overlayActive])
|
||||||
|
|
||||||
// Surface the effective reveal state to consumers (e.g. so the sidebar can
|
|
||||||
// render its full content while floated, not just while the track is open).
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onHoverRevealChange?.(overlayActive && hoverRevealed)
|
onHoverRevealChange?.(revealed)
|
||||||
}, [onHoverRevealChange, overlayActive, hoverRevealed])
|
}, [onHoverRevealChange, revealed])
|
||||||
|
|
||||||
const startResize = useCallback(
|
const startResize = useCallback(
|
||||||
(event: ReactPointerEvent<HTMLDivElement>) => {
|
(event: ReactPointerEvent<HTMLDivElement>) => {
|
||||||
@@ -312,54 +302,44 @@ export function Pane({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collapsed hover-reveal track: keep the grid cell at 0px (no reserved space)
|
// Collapsed hover-reveal track: grid cell stays 0px (no reserved space) but
|
||||||
// but don't clip — the trigger strip and the floating overlay both escape the
|
// unclipped — the hot-zone and floating panel escape it via absolute
|
||||||
// zero-width box via absolute positioning, so the panel renders over the main
|
// positioning, rendering over the main content instead of pushing it.
|
||||||
// content rather than pushing it.
|
|
||||||
if (overlayActive) {
|
if (overlayActive) {
|
||||||
const revealEdge = slot.side === 'left' ? { left: 0 } : { right: 0 }
|
const left = slot.side === 'left'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn('pointer-events-none relative row-start-1 min-w-0', className)}
|
className={cn('pointer-events-none relative row-start-1 min-w-0', className)}
|
||||||
data-pane-hover-reveal={hoverRevealed ? 'open' : 'closed'}
|
data-pane-hover-reveal={revealed ? 'open' : 'closed'}
|
||||||
data-pane-id={id}
|
data-pane-id={id}
|
||||||
data-pane-open="false"
|
data-pane-open="false"
|
||||||
data-pane-side={slot.side}
|
data-pane-side={slot.side}
|
||||||
ref={paneRef}
|
ref={paneRef}
|
||||||
style={{ gridColumn: `${slot.column} / ${slot.column + 1}` }}
|
style={{ gridColumn: `${slot.column} / ${slot.column + 1}` }}
|
||||||
>
|
>
|
||||||
{/* Edge hot-zone: hovering it (or moving onto the revealed overlay)
|
{/* Invisible edge hot-zone — hovering/focusing it floats the panel in. */}
|
||||||
keeps the panel open. Sits above main content but is invisible. */}
|
|
||||||
<button
|
<button
|
||||||
aria-expanded={hoverRevealed}
|
aria-expanded={revealed}
|
||||||
aria-label={`Reveal ${id}`}
|
aria-label={`Reveal ${id}`}
|
||||||
className={cn(
|
className={cn(
|
||||||
'pointer-events-auto absolute inset-y-0 z-30 w-3 cursor-pointer [-webkit-app-region:no-drag]',
|
'pointer-events-auto absolute inset-y-0 z-30 w-3 cursor-pointer [-webkit-app-region:no-drag]',
|
||||||
slot.side === 'left' ? 'left-0' : 'right-0'
|
left ? 'left-0' : 'right-0'
|
||||||
)}
|
)}
|
||||||
onFocus={() => setHoverRevealed(true)}
|
onFocus={() => setHoverRevealed(true)}
|
||||||
onPointerEnter={() => setHoverRevealed(true)}
|
onPointerEnter={() => setHoverRevealed(true)}
|
||||||
type="button"
|
type="button"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Floating panel — fixed-height, absolutely positioned over the main
|
{/* Floating panel — full-height, anchored to the edge, slid off until revealed. */}
|
||||||
column. Slides in from the edge; hidden (translated off-edge) until
|
|
||||||
hovered/focused. */}
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'pointer-events-auto absolute inset-y-0 z-30 overflow-hidden transition-transform duration-200 ease-out',
|
'pointer-events-auto absolute inset-y-0 z-30 overflow-hidden transition-transform duration-200 ease-out',
|
||||||
slot.side === 'left'
|
revealed ? 'translate-x-0' : left ? '-translate-x-[calc(100%+1rem)]' : 'translate-x-[calc(100%+1rem)]'
|
||||||
? hoverRevealed
|
|
||||||
? 'translate-x-0'
|
|
||||||
: '-translate-x-[calc(100%+1rem)]'
|
|
||||||
: hoverRevealed
|
|
||||||
? 'translate-x-0'
|
|
||||||
: 'translate-x-[calc(100%+1rem)]'
|
|
||||||
)}
|
)}
|
||||||
onPointerEnter={() => setHoverRevealed(true)}
|
onPointerEnter={() => setHoverRevealed(true)}
|
||||||
onPointerLeave={() => setHoverRevealed(false)}
|
onPointerLeave={() => setHoverRevealed(false)}
|
||||||
style={{ ...revealEdge, width: overlayWidth }}
|
style={{ [left ? 'left' : 'right']: 0, width: overlayWidth }}
|
||||||
>
|
>
|
||||||
<div className="flex h-full w-full flex-col">{children}</div>
|
<div className="flex h-full w-full flex-col">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -55,8 +55,7 @@ export const $sidebarWidth: ReadableAtom<number> = computed($paneStates, states
|
|||||||
export const $pinnedSessionIds = atom(storedStringArray(SIDEBAR_PINNED_STORAGE_KEY))
|
export const $pinnedSessionIds = atom(storedStringArray(SIDEBAR_PINNED_STORAGE_KEY))
|
||||||
export const $sidebarPinsOpen = atom(true)
|
export const $sidebarPinsOpen = atom(true)
|
||||||
// Set by the PaneShell hover-reveal overlay while the collapsed sidebar is
|
// Set by the PaneShell hover-reveal overlay while the collapsed sidebar is
|
||||||
// floated over content. ChatSidebar treats `sidebarOpen || sidebarRevealed` as
|
// floated over content; ChatSidebar gates its rows on `sidebarOpen || this`.
|
||||||
// "show my full self" so session rows render in the overlay too.
|
|
||||||
export const $sidebarRevealed = atom(false)
|
export const $sidebarRevealed = atom(false)
|
||||||
export const $sidebarRecentsOpen = atom(true)
|
export const $sidebarRecentsOpen = atom(true)
|
||||||
// Cron-job sessions live in their own section below recents, collapsed by
|
// Cron-job sessions live in their own section below recents, collapsed by
|
||||||
@@ -121,9 +120,7 @@ export function setSidebarPinsOpen(open: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function setSidebarRevealed(revealed: boolean) {
|
export function setSidebarRevealed(revealed: boolean) {
|
||||||
if ($sidebarRevealed.get() !== revealed) {
|
$sidebarRevealed.set(revealed)
|
||||||
$sidebarRevealed.set(revealed)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setSidebarRecentsOpen(open: boolean) {
|
export function setSidebarRecentsOpen(open: boolean) {
|
||||||
|
|||||||
Reference in New Issue
Block a user