diff --git a/apps/web/src/timeline/components/__tests__/theme.test.ts b/apps/web/src/timeline/components/__tests__/theme.test.ts new file mode 100644 index 000000000..47b909cf2 --- /dev/null +++ b/apps/web/src/timeline/components/__tests__/theme.test.ts @@ -0,0 +1,64 @@ +import { describe, expect, test } from "bun:test"; +import { + getTimelineElementClassName, + getTimelineElementClassNameForElement, + TIMELINE_ELEMENT_THEME, + TIMELINE_TRACK_THEME, +} from "../theme"; + +describe("getTimelineElementClassName", () => { + test("returns the configured class for each track type", () => { + expect(getTimelineElementClassName({ type: "audio" })).toBe( + TIMELINE_TRACK_THEME.audio.elementClassName.trim(), + ); + expect(getTimelineElementClassName({ type: "text" })).toBe( + TIMELINE_TRACK_THEME.text.elementClassName.trim(), + ); + }); +}); + +describe("getTimelineElementClassNameForElement", () => { + test("uses the element-level theme when one is defined", () => { + expect( + getTimelineElementClassNameForElement({ + elementType: "video", + trackType: "video", + }), + ).toBe(TIMELINE_ELEMENT_THEME.video?.elementClassName.trim()); + }); + + test("distinguishes video and image even though they share the video track", () => { + const videoClass = getTimelineElementClassNameForElement({ + elementType: "video", + trackType: "video", + }); + const imageClass = getTimelineElementClassNameForElement({ + elementType: "image", + trackType: "video", + }); + expect(videoClass).not.toBe(imageClass); + }); + + test("falls back to the track-level theme when an element type has no override", () => { + expect( + getTimelineElementClassNameForElement({ + elementType: "audio", + trackType: "audio", + }), + ).toBe(TIMELINE_TRACK_THEME.audio.elementClassName.trim()); + + expect( + getTimelineElementClassNameForElement({ + elementType: "text", + trackType: "text", + }), + ).toBe(TIMELINE_TRACK_THEME.text.elementClassName.trim()); + + expect( + getTimelineElementClassNameForElement({ + elementType: "sticker", + trackType: "graphic", + }), + ).toBe(TIMELINE_TRACK_THEME.graphic.elementClassName.trim()); + }); +}); diff --git a/apps/web/src/timeline/components/theme.ts b/apps/web/src/timeline/components/theme.ts index 9f93dc16c..5f0ca8f40 100644 --- a/apps/web/src/timeline/components/theme.ts +++ b/apps/web/src/timeline/components/theme.ts @@ -1,4 +1,4 @@ -import type { TrackType } from "@/timeline"; +import type { ElementType, TrackType } from "@/timeline"; export const TIMELINE_AUDIO_WAVEFORM_COLOR = "rgba(255, 255, 255, 0.7)"; @@ -19,6 +19,22 @@ export const TIMELINE_TRACK_THEME: Record< effect: { elementClassName: "bg-[#5d93ba]" }, } as const; +/** + * Per-element-type background tint applied to clips on the timeline. + * + * Falls back to the track-level theme when an element type has no + * dedicated entry. Used so that clips on the same track (e.g. video + * and image, which both live on the "video" track) can still be + * visually distinguished from each other and from the timeline + * background. + */ +export const TIMELINE_ELEMENT_THEME: Partial< + Record +> = { + video: { elementClassName: "bg-sky-500/20" }, + image: { elementClassName: "bg-amber-500/20" }, +} as const; + export const SELECTED_TRACK_ROW_CLASS = "bg-accent/50"; export const DEFAULT_TIMELINE_BOOKMARK_COLOR = "#009dff"; @@ -29,3 +45,17 @@ export function getTimelineElementClassName({ }): string { return TIMELINE_TRACK_THEME[type].elementClassName.trim(); } + +export function getTimelineElementClassNameForElement({ + elementType, + trackType, +}: { + elementType: ElementType; + trackType: TrackType; +}): string { + const elementTheme = TIMELINE_ELEMENT_THEME[elementType]; + if (elementTheme) { + return elementTheme.elementClassName.trim(); + } + return TIMELINE_TRACK_THEME[trackType].elementClassName.trim(); +} diff --git a/apps/web/src/timeline/components/timeline-element.tsx b/apps/web/src/timeline/components/timeline-element.tsx index 1b549a150..48083c9a4 100644 --- a/apps/web/src/timeline/components/timeline-element.tsx +++ b/apps/web/src/timeline/components/timeline-element.tsx @@ -23,7 +23,11 @@ import { timelineTimeToSnappedPixels, } from "@/timeline"; import { getTrackHeight } from "./track-layout"; -import { getTimelineElementClassName, TIMELINE_TRACK_THEME } from "./theme"; +import { + getTimelineElementClassName, + getTimelineElementClassNameForElement, + TIMELINE_TRACK_THEME, +} from "./theme"; import { ContextMenu, ContextMenuContent, @@ -585,8 +589,9 @@ function ElementInner({