fix(app): fade home project list edges on scroll

This commit is contained in:
David Hill
2026-09-30 13:02:30 -06:00
parent 6585bb7105
commit 270b0fec62
2 changed files with 44 additions and 36 deletions
+25 -4
View File
@@ -1,5 +1,6 @@
import { createMemo, For, type JSX, onCleanup, Show, splitProps } from "solid-js"
import { createStore } from "solid-js/store"
import { createResizeObserver } from "@solid-primitives/resize-observer"
import { Popover } from "@kobalte/core/popover"
import { DragDropProvider, PointerSensor } from "@dnd-kit/solid"
import { isSortable, useSortable } from "@dnd-kit/solid/sortable"
@@ -151,10 +152,20 @@ export function HomeProjectsView(props: HomeProjectsViewProps) {
}
function HomeProjectsPanel(props: HomeProjectsViewProps) {
const [contextMenu, setContextMenu] = createStore({ open: undefined as string | undefined })
let observer: MutationObserver | undefined
let frame = 0
const [state, setState] = createStore({ contextMenu: undefined as string | undefined })
const updateFades = (element: HTMLDivElement) => {
element.toggleAttribute("data-fade-top", element.scrollTop > 0)
element.toggleAttribute("data-fade-bottom", element.scrollTop + element.clientHeight < element.scrollHeight - 1)
}
onCleanup(() => {
observer?.disconnect()
cancelAnimationFrame(frame)
})
const contextMenuProps = {
contextMenuOpen: (id: string) => contextMenu.open === id,
onSetContextMenuOpen: (id: string, open: boolean) => setContextMenu("open", open ? id : undefined),
contextMenuOpen: (id: string) => state.contextMenu === id,
onSetContextMenuOpen: (id: string, open: boolean) => setState("contextMenu", open ? id : undefined),
}
return (
<aside
@@ -191,7 +202,17 @@ function HomeProjectsPanel(props: HomeProjectsViewProps) {
</Show>
</div>
</Show>
<ScrollView data-slot="home-projects-scroll" class="min-h-0 min-w-0 shrink">
<ScrollView
data-slot="home-projects-scroll"
class="min-h-0 min-w-0 shrink"
viewportRef={(element) => {
createResizeObserver(element, () => updateFades(element))
observer = new MutationObserver(() => updateFades(element))
observer.observe(element, { childList: true, subtree: true })
frame = requestAnimationFrame(() => updateFades(element))
}}
onScroll={(event) => updateFades(event.currentTarget)}
>
<Show when={props.dropdown && props.servers.length === 1}>
<HomeProjectNavButton
type="button"
+19 -32
View File
@@ -445,8 +445,25 @@
}
}
[data-slot="home-projects-scroll"] {
timeline-scope: --home-projects-scroll;
[data-slot="home-projects-scroll"] > .scroll-view__viewport[data-fade-top] {
-webkit-mask-image: linear-gradient(to bottom, transparent, black 16px);
mask-image: linear-gradient(to bottom, transparent, black 16px);
}
[data-slot="home-projects-scroll"] > .scroll-view__viewport[data-fade-bottom] {
-webkit-mask-image: linear-gradient(to bottom, black calc(100% - 16px), transparent);
mask-image: linear-gradient(to bottom, black calc(100% - 16px), transparent);
}
[data-slot="home-projects-scroll"] > .scroll-view__viewport[data-fade-top][data-fade-bottom] {
-webkit-mask-image: linear-gradient(
to bottom,
transparent,
black 16px,
black calc(100% - 16px),
transparent
);
mask-image: linear-gradient(to bottom, transparent, black 16px, black calc(100% - 16px), transparent);
}
[data-slot="model-selector-scroll"] {
@@ -457,8 +474,6 @@
timeline-scope: --manage-models-scroll;
}
[data-slot="home-projects-scroll"]::before,
[data-slot="home-projects-scroll"]::after,
[data-slot="model-selector-scroll"]::before,
[data-slot="model-selector-scroll"]::after,
[data-slot="manage-models-scroll"]::before {
@@ -472,11 +487,6 @@
visibility: hidden;
}
[data-slot="home-projects-scroll"]::before {
top: 0;
background: linear-gradient(to bottom, var(--v2-background-bg-base), transparent);
}
[data-slot="model-selector-scroll"]::before {
top: 0;
height: 12px;
@@ -506,11 +516,6 @@
background: linear-gradient(to bottom, var(--v2-background-bg-base), transparent);
}
[data-slot="home-projects-scroll"]::after {
bottom: 0;
background: linear-gradient(to top, var(--v2-background-bg-base), transparent);
}
[data-slot="model-selector-scroll"]::after {
bottom: 0;
height: 12px;
@@ -535,24 +540,6 @@
);
}
@supports (animation-timeline: --home-projects-scroll) and (timeline-scope: --home-projects-scroll) {
[data-slot="home-projects-scroll"] .scroll-view__viewport {
scroll-timeline: --home-projects-scroll y;
}
[data-slot="home-projects-scroll"]::before {
animation: home-projects-fade-top linear both;
animation-timeline: --home-projects-scroll;
animation-range: 0 0.1px;
}
[data-slot="home-projects-scroll"]::after {
animation: home-projects-fade-bottom linear both;
animation-timeline: --home-projects-scroll;
animation-range: calc(100% - 1.1px) calc(100% - 1px);
}
}
@supports (animation-timeline: --model-selector-scroll) and (timeline-scope: --model-selector-scroll) {
[data-slot="model-selector-scroll"] .scroll-view__viewport {
scroll-timeline: --model-selector-scroll y;