From 57b5cafb27a891d2f5a5105145cc60374dc4476a Mon Sep 17 00:00:00 2001 From: Xevion Date: Thu, 29 Jan 2026 01:18:02 -0600 Subject: [PATCH] feat: enhance table scrolling and eliminate initial theme flash --- web/src/app.html | 2 +- web/src/lib/components/CourseTable.svelte | 41 ++++++++++++++++++++--- web/src/routes/+layout.svelte | 6 ++++ web/src/routes/layout.css | 40 ++++++++++++++++++++-- 4 files changed, 82 insertions(+), 7 deletions(-) diff --git a/web/src/app.html b/web/src/app.html index e4ce37a..e3aee7b 100644 --- a/web/src/app.html +++ b/web/src/app.html @@ -1,5 +1,5 @@ - + diff --git a/web/src/lib/components/CourseTable.svelte b/web/src/lib/components/CourseTable.svelte index a5ec02e..b748e8f 100644 --- a/web/src/lib/components/CourseTable.svelte +++ b/web/src/lib/components/CourseTable.svelte @@ -10,6 +10,10 @@ import { isTimeTBA, } from "$lib/course"; import CourseDetail from "./CourseDetail.svelte"; +import { slide } from "svelte/transition"; +import { onMount } from "svelte"; +import { OverlayScrollbars } from "overlayscrollbars"; +import { themeStore } from "$lib/stores/theme.svelte"; import { createSvelteTable, FlexRender } from "$lib/components/ui/data-table/index.js"; import { getCoreRowModel, @@ -38,6 +42,33 @@ let { } = $props(); let expandedCrn: string | null = $state(null); +let tableWrapper: HTMLDivElement = undefined!; + +onMount(() => { + const osInstance = OverlayScrollbars(tableWrapper, { + overflow: { x: "scroll", y: "hidden" }, + scrollbars: { + autoHide: "never", + theme: themeStore.isDark ? "os-theme-dark" : "os-theme-light", + }, + }); + + // React to theme changes + const unwatch = $effect.root(() => { + $effect(() => { + osInstance.options({ + scrollbars: { + theme: themeStore.isDark ? "os-theme-dark" : "os-theme-light", + }, + }); + }); + }); + + return () => { + unwatch(); + osInstance.destroy(); + }; +}); // Column visibility state let columnVisibility: VisibilityState = $state({}); @@ -298,10 +329,10 @@ const table = createSvelteTable({ -
+
- +
{#each table.getHeaderGroups() as headerGroup} @@ -372,7 +403,7 @@ const table = createSvelteTable({ {course.subject} {course.courseNumber}{#if course.sequenceNumber}-{course.sequenceNumber}{/if} {:else if colId === "title"} - + {:else if colId === "instructor"} {/if} diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index 057fa89..0543fdc 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -12,6 +12,12 @@ let { children } = $props(); onMount(() => { themeStore.init(); + // Enable theme transitions now that the page has rendered with the correct theme. + // Without this delay, the initial paint would animate from light to dark colors. + requestAnimationFrame(() => { + document.documentElement.classList.remove("no-transition"); + }); + const osInstance = OverlayScrollbars(document.body, { scrollbars: { autoHide: "leave", diff --git a/web/src/routes/layout.css b/web/src/routes/layout.css index 46c96c3..d7eda81 100644 --- a/web/src/routes/layout.css +++ b/web/src/routes/layout.css @@ -64,8 +64,8 @@ body { margin: 0; } -body, -body * { +html:not(.no-transition) body, +html:not(.no-transition) body * { transition: background-color 300ms, color 300ms, border-color 300ms, fill 300ms; } @@ -105,6 +105,42 @@ body::-webkit-scrollbar { display: none; } +/* Native scrollbars — theme-aware styling for inner scrollable elements */ +* { + scrollbar-width: thin; + scrollbar-color: rgba(0, 0, 0, 0.25) transparent; +} + +.dark * { + scrollbar-color: rgba(255, 255, 255, 0.3) transparent; +} + +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.25); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.35); +} + +.dark ::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.3); +} + +.dark ::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.4); +} + @keyframes pulse { 0%, 100% {
{course.title}{course.title} {primaryInstructorDisplay(course)} @@ -423,7 +454,9 @@ const table = createSvelteTable({ {#if expandedCrn === course.crn}
- +
+ +