Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions apps/web/src/timeline/components/__tests__/theme.test.ts
Original file line number Diff line number Diff line change
@@ -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());
});
});
32 changes: 31 additions & 1 deletion apps/web/src/timeline/components/theme.ts
Original file line number Diff line number Diff line change
@@ -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)";

Expand All @@ -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<ElementType, { elementClassName: string }>
> = {
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";

Expand All @@ -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();
}
11 changes: 8 additions & 3 deletions apps/web/src/timeline/components/timeline-element.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -585,8 +589,9 @@ function ElementInner({
<div
className={cn(
"flex shrink-0 items-center overflow-hidden",
getTimelineElementClassName({
type: getTrackTypeForElementType({
getTimelineElementClassNameForElement({
elementType: element.type,
trackType: getTrackTypeForElementType({
elementType: element.type,
}),
}),
Expand Down