pane stack: each pane is now its own card

move card styling (bg, radius, shadow, border) from sidebar
container to individual panes so they visually stack as
separate cards. behind pane peeks out from the left.
This commit is contained in:
Justin Edmund 2025-12-03 17:38:53 -08:00
parent df045ecd2b
commit f23779b664
2 changed files with 71 additions and 15 deletions

View file

@ -48,6 +48,21 @@
function isPopping(index: number): boolean {
return isAnimating && animationDirection === 'pop' && index === panes.length - 1
}
// Determine if a pane is becoming active (the one behind a popping pane)
function isBecomingActive(index: number): boolean {
return isAnimating && animationDirection === 'pop' && index === panes.length - 2
}
// Determine the visual depth of a pane (0 = active, 1 = one behind, 2+ = hidden)
function getDepth(index: number): number {
return panes.length - 1 - index
}
// Panes more than 1 level deep should be hidden
function isHidden(index: number): boolean {
return getDepth(index) > 1
}
</script>
<div class="pane-stack">
@ -56,13 +71,17 @@
{@const isBehind = index < panes.length - 1}
{@const showBackButton = index > 0 || pane.onback || onClose}
{@const PaneComponent = pane.component}
{@const depth = getDepth(index)}
<div
class="pane"
class:is-active={isActive && !isPopping(index)}
class:is-behind={isBehind || isPopping(index)}
class:is-active={(isActive && !isPopping(index)) || isBecomingActive(index)}
class:is-behind={isBehind && !isPopping(index) && !isBecomingActive(index)}
class:is-pushing={isPushing(index)}
class:is-popping={isPopping(index)}
class:is-hidden={isHidden(index)}
class:scrollable={pane.scrollable !== false}
style:--pane-depth={depth}
>
<SidebarHeader title={pane.title}>
{#snippet leftAccessory()}
@ -103,12 +122,15 @@
<style lang="scss">
@use '$src/themes/spacing' as *;
@use '$src/themes/effects' as *;
@use '$src/themes/layout' as *;
// Stacking configuration
$pane-peek-offset: $unit-3x; // How much the behind pane peeks out to the left
.pane-stack {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
.pane {
@ -116,7 +138,14 @@
inset: 0;
display: flex;
flex-direction: column;
// Each pane is its own card
background: var(--sidebar-bg);
border-radius: $page-corner;
box-shadow: $page-elevation;
border: 1px solid rgba(0, 0, 0, 0.14);
overflow: hidden;
transition:
transform $duration-slide ease-out,
opacity $duration-slide ease-out;
@ -126,29 +155,42 @@
&.is-active {
transform: translateX(0) scale(1);
z-index: 10;
opacity: 1;
.pane-content {
opacity: 1;
transition: opacity $duration-slide ease-out;
}
}
// Behind pane (scaled down and shifted left)
// Behind pane (shifted left to peek out)
&.is-behind {
transform: translateX(-20px) scale(0.9);
z-index: 1;
transform: translateX(-$pane-peek-offset) scale(0.98);
z-index: 5;
opacity: 1;
.pane-content {
opacity: 0;
pointer-events: none;
transition: opacity $duration-slide ease-out;
}
}
// Hidden panes (2+ levels deep)
&.is-hidden {
opacity: 0;
pointer-events: none;
z-index: 0;
}
// Pushing animation (new pane entering from right)
&.is-pushing {
animation: pane-enter $duration-slide ease-out forwards;
}
// Popping animation (pane exiting to the right)
&.is-popping {
animation: pane-exit $duration-slide ease-out forwards;
z-index: 10; // Keep on top during exit
}
}
@keyframes pane-enter {
@ -156,7 +198,16 @@
transform: translateX(100%);
}
to {
transform: translateX(0) scale(1);
transform: translateX(0);
}
}
@keyframes pane-exit {
from {
transform: translateX(0);
}
to {
transform: translateX(100%);
}
}
@ -165,6 +216,7 @@
overflow: hidden;
display: flex;
flex-direction: column;
transition: opacity $duration-slide ease-out;
// Scrollable pane content
.pane.scrollable & {

View file

@ -43,21 +43,19 @@
right: $unit-2x;
height: calc(100vh - #{$unit-2x} - #{$unit-2x}); // 100vh minus top and bottom insets
box-sizing: border-box;
background: var(--sidebar-bg);
// No background - individual panes have their own card styling
display: flex;
flex-direction: column;
border-radius: $page-corner;
flex-shrink: 0;
width: var(--sidebar-width);
overflow: hidden;
transform: translateX(100%);
overflow: visible; // Allow panes to show stacking effect
transform: translateX(calc(100% + #{$unit-2x}));
opacity: 0;
transition:
transform $duration-slide ease-in-out,
opacity $duration-slide ease-in-out;
z-index: 50;
box-shadow: $page-elevation;
border: 1px solid rgba(0, 0, 0, 0.14);
// No shadow/border - individual panes have their own
&.open {
transform: translateX(0);
@ -70,6 +68,12 @@
display: flex;
flex-direction: column;
// Legacy content needs its own card styling
background: var(--sidebar-bg);
border-radius: $page-corner;
box-shadow: $page-elevation;
border: 1px solid rgba(0, 0, 0, 0.14);
// When scrollable, enable scrolling with nice scrollbars
&.scrollable {
overflow-y: auto;