|
| 1 | +import { useRef, useState } from "react"; |
1 | 2 | import { SidebarProvider, SidebarInset, SidebarTrigger, useSidebar } from "@/components/sidebar"; |
2 | 3 | import { AppShellOutlet } from "@/components/content"; |
3 | 4 | import { AppearanceSwitcher } from "@/components/appearance-switcher"; |
@@ -68,6 +69,17 @@ const HidableSidebarTrigger = () => { |
68 | 69 | export const SidebarLayout = (props: SidebarLayoutProps) => { |
69 | 70 | const Children = props.children ? props.children({ Outlet: AppShellOutlet }) : null; |
70 | 71 |
|
| 72 | + // Toggle a scroll-fade under the pinned breadcrumb once the content area |
| 73 | + // has scrolled, so content dissolves into the header instead of cutting off |
| 74 | + // abruptly. State only flips when crossing 0, so this doesn't re-render on |
| 75 | + // every scroll frame. |
| 76 | + const scrollRef = useRef<HTMLDivElement>(null); |
| 77 | + const [scrolled, setScrolled] = useState(false); |
| 78 | + const handleScroll = () => { |
| 79 | + const next = (scrollRef.current?.scrollTop ?? 0) > 0; |
| 80 | + setScrolled((prev) => (prev === next ? prev : next)); |
| 81 | + }; |
| 82 | + |
71 | 83 | return ( |
72 | 84 | <SidebarProvider |
73 | 85 | defaultOpen={props.defaultOpen} |
@@ -100,11 +112,22 @@ export const SidebarLayout = (props: SidebarLayoutProps) => { |
100 | 112 | margin / padding pair mirrors SidebarInset's own responsive |
101 | 113 | padding (px-4, → px-8 at md when the sidebar is the inset |
102 | 114 | variant). */} |
| 115 | + {/* Scroll-fade: once scrolled, a mask fades the top edge of the |
| 116 | + content to transparent so it dissolves into the pinned breadcrumb |
| 117 | + rather than cutting off. Masking (rather than overlaying a matching |
| 118 | + colour) reveals the real page backdrop — which is a theme gradient |
| 119 | + with `background-attachment: fixed`, so no single colour could |
| 120 | + match it — making the fade seamless on every theme. Only applied |
| 121 | + when scrolled, so content isn't faded at rest. */} |
103 | 122 | <div |
| 123 | + ref={scrollRef} |
| 124 | + onScroll={handleScroll} |
| 125 | + data-scrolled={scrolled ? "" : undefined} |
104 | 126 | className={cn( |
105 | 127 | "astw:flex astw:flex-col astw:gap-4 astw:flex-1 astw:min-h-0 astw:overflow-y-auto", |
106 | 128 | "astw:-mr-4 astw:pr-4", |
107 | 129 | "astw:md:group-has-data-[variant=inset]/sidebar-wrapper:-mr-8 astw:md:group-has-data-[variant=inset]/sidebar-wrapper:pr-8", |
| 130 | + "astw:data-[scrolled]:[mask-image:linear-gradient(to_bottom,transparent,black_2rem)]", |
108 | 131 | )} |
109 | 132 | > |
110 | 133 | {Children ?? <AppShellOutlet />} |
|
0 commit comments