mirror of
https://github.com/Xevion/banner.git
synced 2026-01-31 02:23:34 -06:00
feat: enhance table scrolling and eliminate initial theme flash
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en" class="no-transition">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ import {
|
|||||||
isTimeTBA,
|
isTimeTBA,
|
||||||
} from "$lib/course";
|
} from "$lib/course";
|
||||||
import CourseDetail from "./CourseDetail.svelte";
|
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 { createSvelteTable, FlexRender } from "$lib/components/ui/data-table/index.js";
|
||||||
import {
|
import {
|
||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
@@ -38,6 +42,33 @@ let {
|
|||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let expandedCrn: string | null = $state(null);
|
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
|
// Column visibility state
|
||||||
let columnVisibility: VisibilityState = $state({});
|
let columnVisibility: VisibilityState = $state({});
|
||||||
@@ -298,10 +329,10 @@ const table = createSvelteTable({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Table with context menu on header -->
|
<!-- Table with context menu on header -->
|
||||||
<div class="overflow-x-auto">
|
<div bind:this={tableWrapper} class="overflow-x-auto">
|
||||||
<ContextMenu.Root>
|
<ContextMenu.Root>
|
||||||
<ContextMenu.Trigger class="contents">
|
<ContextMenu.Trigger class="contents">
|
||||||
<table class="w-full border-collapse text-sm">
|
<table class="w-full min-w-[640px] border-collapse text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
{#each table.getHeaderGroups() as headerGroup}
|
{#each table.getHeaderGroups() as headerGroup}
|
||||||
<tr class="border-b border-border text-left text-muted-foreground">
|
<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}
|
<span class="font-semibold">{course.subject} {course.courseNumber}</span>{#if course.sequenceNumber}<span class="text-muted-foreground">-{course.sequenceNumber}</span>{/if}
|
||||||
</td>
|
</td>
|
||||||
{:else if colId === "title"}
|
{: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"}
|
{:else if colId === "instructor"}
|
||||||
<td class="py-2 px-2 whitespace-nowrap">
|
<td class="py-2 px-2 whitespace-nowrap">
|
||||||
{primaryInstructorDisplay(course)}
|
{primaryInstructorDisplay(course)}
|
||||||
@@ -423,7 +454,9 @@ const table = createSvelteTable({
|
|||||||
{#if expandedCrn === course.crn}
|
{#if expandedCrn === course.crn}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan={visibleColumnIds.length} class="p-0">
|
<td colspan={visibleColumnIds.length} class="p-0">
|
||||||
<CourseDetail {course} />
|
<div transition:slide={{ duration: 200 }}>
|
||||||
|
<CourseDetail {course} />
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ let { children } = $props();
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
themeStore.init();
|
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, {
|
const osInstance = OverlayScrollbars(document.body, {
|
||||||
scrollbars: {
|
scrollbars: {
|
||||||
autoHide: "leave",
|
autoHide: "leave",
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body,
|
html:not(.no-transition) body,
|
||||||
body * {
|
html:not(.no-transition) body * {
|
||||||
transition: background-color 300ms, color 300ms, border-color 300ms, fill 300ms;
|
transition: background-color 300ms, color 300ms, border-color 300ms, fill 300ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +105,42 @@ body::-webkit-scrollbar {
|
|||||||
display: none;
|
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 {
|
@keyframes pulse {
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
|
|||||||
Reference in New Issue
Block a user