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 { function isPopping(index: number): boolean {
return isAnimating && animationDirection === 'pop' && index === panes.length - 1 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> </script>
<div class="pane-stack"> <div class="pane-stack">
@ -56,13 +71,17 @@
{@const isBehind = index < panes.length - 1} {@const isBehind = index < panes.length - 1}
{@const showBackButton = index > 0 || pane.onback || onClose} {@const showBackButton = index > 0 || pane.onback || onClose}
{@const PaneComponent = pane.component} {@const PaneComponent = pane.component}
{@const depth = getDepth(index)}
<div <div
class="pane" class="pane"
class:is-active={isActive && !isPopping(index)} class:is-active={(isActive && !isPopping(index)) || isBecomingActive(index)}
class:is-behind={isBehind || isPopping(index)} class:is-behind={isBehind && !isPopping(index) && !isBecomingActive(index)}
class:is-pushing={isPushing(index)} class:is-pushing={isPushing(index)}
class:is-popping={isPopping(index)}
class:is-hidden={isHidden(index)}
class:scrollable={pane.scrollable !== false} class:scrollable={pane.scrollable !== false}
style:--pane-depth={depth}
> >
<SidebarHeader title={pane.title}> <SidebarHeader title={pane.title}>
{#snippet leftAccessory()} {#snippet leftAccessory()}
@ -103,12 +122,15 @@
<style lang="scss"> <style lang="scss">
@use '$src/themes/spacing' as *; @use '$src/themes/spacing' as *;
@use '$src/themes/effects' 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 { .pane-stack {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden;
} }
.pane { .pane {
@ -116,7 +138,14 @@
inset: 0; inset: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
// Each pane is its own card
background: var(--sidebar-bg); background: var(--sidebar-bg);
border-radius: $page-corner;
box-shadow: $page-elevation;
border: 1px solid rgba(0, 0, 0, 0.14);
overflow: hidden;
transition: transition:
transform $duration-slide ease-out, transform $duration-slide ease-out,
opacity $duration-slide ease-out; opacity $duration-slide ease-out;
@ -126,29 +155,42 @@
&.is-active { &.is-active {
transform: translateX(0) scale(1); transform: translateX(0) scale(1);
z-index: 10; z-index: 10;
opacity: 1;
.pane-content { .pane-content {
opacity: 1; opacity: 1;
transition: opacity $duration-slide ease-out;
} }
} }
// Behind pane (scaled down and shifted left) // Behind pane (shifted left to peek out)
&.is-behind { &.is-behind {
transform: translateX(-20px) scale(0.9); transform: translateX(-$pane-peek-offset) scale(0.98);
z-index: 1; z-index: 5;
opacity: 1;
.pane-content { .pane-content {
opacity: 0; opacity: 0;
pointer-events: none; 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) // Pushing animation (new pane entering from right)
&.is-pushing { &.is-pushing {
animation: pane-enter $duration-slide ease-out forwards; 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 { @keyframes pane-enter {
@ -156,7 +198,16 @@
transform: translateX(100%); transform: translateX(100%);
} }
to { 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; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
transition: opacity $duration-slide ease-out;
// Scrollable pane content // Scrollable pane content
.pane.scrollable & { .pane.scrollable & {

View file

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