From 1a530aeaa2d61b34d9721a555b71631c7101c58e Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Sat, 2 May 2026 22:52:09 +0200 Subject: [PATCH] fix(theme): kill initial white frame + theme-aware skeleton tiles (#358 follow-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two further fixes for the gallery loading sequence shown in @Rekoo-PS's frame breakdown on issue #358 — both about colours that didn't track the active theme. 1. Initial white frame (frame f1) The pre-React bootstrap script in #359 sets the cached background on documentElement, but the browser may paint the very first frame *before* that -
diff --git a/frontend/src/components/common/Skeleton.tsx b/frontend/src/components/common/Skeleton.tsx index b9cdf680..939cecbe 100644 --- a/frontend/src/components/common/Skeleton.tsx +++ b/frontend/src/components/common/Skeleton.tsx @@ -16,8 +16,6 @@ export const Skeleton: React.FC = ({ height, animation = 'pulse' }) => { - const baseClasses = 'bg-neutral-200'; - const animationClasses = { pulse: 'animate-pulse', wave: 'animate-shimmer', @@ -30,14 +28,21 @@ export const Skeleton: React.FC = ({ rectangular: 'rounded-lg' }; - const style: React.CSSProperties = {}; + // Theme-aware placeholder colour. Without this the skeleton tiles + // rendered as bright bg-neutral-200 light grey on dark gallery + // themes — the "most annoying" frame in #358's screenshots. Using + // var(--color-surface-border) tracks whatever shade ThemeContext + // resolves for the current colour mode (light: #e5e5e5, dark: + // #2e2e2e by default; per-event themes can override). + const style: React.CSSProperties = { + backgroundColor: 'var(--color-surface-border, #e5e5e5)', + }; if (width) style.width = typeof width === 'number' ? `${width}px` : width; if (height) style.height = typeof height === 'number' ? `${height}px` : height; return (
= ({ ); }; +// Theme-aware container surface — same reasoning as the Skeleton +// itself. Reads var(--color-surface) so the card sits on the right +// background regardless of the active theme's colour mode. +const SURFACE_STYLE: React.CSSProperties = { + backgroundColor: 'var(--color-surface, #ffffff)', +}; + // Common skeleton patterns export const SkeletonCard: React.FC<{ className?: string }> = ({ className }) => ( -
+
@@ -86,12 +98,12 @@ export const SkeletonCard: React.FC<{ className?: string }> = ({ className }) =>
); -export const SkeletonTable: React.FC<{ rows?: number; className?: string }> = ({ - rows = 5, - className +export const SkeletonTable: React.FC<{ rows?: number; className?: string }> = ({ + rows = 5, + className }) => ( -
-
+
+
@@ -99,7 +111,7 @@ export const SkeletonTable: React.FC<{ rows?: number; className?: string }> = ({
-
+
{Array.from({ length: rows }).map((_, index) => (
diff --git a/frontend/src/components/common/__tests__/Skeleton.test.tsx b/frontend/src/components/common/__tests__/Skeleton.test.tsx new file mode 100644 index 00000000..08720b61 --- /dev/null +++ b/frontend/src/components/common/__tests__/Skeleton.test.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { describe, it, expect } from 'vitest'; +import { Skeleton, SkeletonGalleryGrid, SkeletonCard } from '../Skeleton'; + +/** + * Regression for #358. The Skeleton placeholders used to hard-code + * `bg-neutral-200`, which rendered as bright light grey on dark + * gallery themes (Rekoo-PS's "most annoying" frame). They must instead + * use the active theme's surface-border colour so the placeholders + * track whatever the theme defines for both light and dark modes. + */ +describe('Skeleton — theme-aware colour', () => { + it('uses var(--color-surface-border) for the placeholder background', () => { + const { container } = render(); + const div = container.querySelector('div'); + expect(div).not.toBeNull(); + expect(div!.style.backgroundColor).toBe('var(--color-surface-border, #e5e5e5)'); + }); + + it('does NOT add the legacy hard-coded bg-neutral-200 class', () => { + const { container } = render(); + const div = container.querySelector('div'); + expect(div!.className).not.toMatch(/bg-neutral-200/); + }); + + it('SkeletonGalleryGrid tiles inherit the theme colour', () => { + const { container } = render(); + // Tiles are the Skeleton components — direct children of the + // gallery-grid wrapper. They carry aria-busy="true" while the + // wrapper does not, which is the cleanest way to select them. + const tiles = container.querySelectorAll('[aria-busy="true"]'); + expect(tiles.length).toBe(3); + tiles.forEach((tile) => { + expect((tile as HTMLElement).style.backgroundColor).toBe( + 'var(--color-surface-border, #e5e5e5)' + ); + }); + }); + + it('SkeletonCard surface uses var(--color-surface)', () => { + const { container } = render(); + const card = container.firstElementChild as HTMLElement; + expect(card).not.toBeNull(); + expect(card.style.backgroundColor).toBe('var(--color-surface, #ffffff)'); + // Sanity: should not retain the old bg-white class either + expect(card.className).not.toMatch(/bg-white/); + }); +});