feat: enhance table scrolling and eliminate initial theme flash

This commit is contained in:
2026-01-29 01:18:02 -06:00
parent 841191c44d
commit 57b5cafb27
4 changed files with 82 additions and 7 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html lang="en" class="no-transition">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
+36 -3
View File
@@ -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({
</div>
<!-- Table with context menu on header -->
<div class="overflow-x-auto">
<div bind:this={tableWrapper} class="overflow-x-auto">
<ContextMenu.Root>
<ContextMenu.Trigger class="contents">
<table class="w-full border-collapse text-sm">
<table class="w-full min-w-[640px] border-collapse text-sm">
<thead>
{#each table.getHeaderGroups() as headerGroup}
<tr class="border-b border-border text-left text-muted-foreground">
@@ -372,7 +403,7 @@ const table = createSvelteTable({
<span class="font-semibold">{course.subject} {course.courseNumber}</span>{#if course.sequenceNumber}<span class="text-muted-foreground">-{course.sequenceNumber}</span>{/if}
</td>
{:else if colId === "title"}
<td class="py-2 px-2 font-medium">{course.title}</td>
<td class="py-2 px-2 font-medium max-w-[200px] truncate" title={course.title}>{course.title}</td>
{:else if colId === "instructor"}
<td class="py-2 px-2 whitespace-nowrap">
{primaryInstructorDisplay(course)}
@@ -423,7 +454,9 @@ const table = createSvelteTable({
{#if expandedCrn === course.crn}
<tr>
<td colspan={visibleColumnIds.length} class="p-0">
<div transition:slide={{ duration: 200 }}>
<CourseDetail {course} />
</div>
</td>
</tr>
{/if}
+6
View File
@@ -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",
+38 -2
View File
@@ -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% {