summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/media-panel/tabbar.tsx
blob: 5079324378e44b40e849a46cbe5e021a1b5cfa7d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"use client";

import { cn } from "@/lib/utils";
import { Tab, tabs, useMediaPanelStore } from "./store";
import { Button } from "@/components/ui/button";
import { ChevronRight, ChevronLeft } from "lucide-react";
import { useRef, useState, useEffect } from "react";

export function TabBar() {
	const { activeTab, setActiveTab } = useMediaPanelStore();
	const scrollContainerRef = useRef<HTMLDivElement>(null);
	const [isAtEnd, setIsAtEnd] = useState(false);
	const [isAtStart, setIsAtStart] = useState(true);

	const scrollToEnd = () => {
		if (scrollContainerRef.current) {
			scrollContainerRef.current.scrollTo({
				left: scrollContainerRef.current.scrollWidth,
			});
			setIsAtEnd(true);
			setIsAtStart(false);
		}
	};

	const scrollToStart = () => {
		if (scrollContainerRef.current) {
			scrollContainerRef.current.scrollTo({
				left: 0,
			});
			setIsAtStart(true);
			setIsAtEnd(false);
		}
	};

	const checkScrollPosition = () => {
		if (scrollContainerRef.current) {
			const { scrollLeft, scrollWidth, clientWidth } =
				scrollContainerRef.current;
			const isAtEndNow = scrollLeft + clientWidth >= scrollWidth - 1;
			const isAtStartNow = scrollLeft <= 1;
			setIsAtEnd(isAtEndNow);
			setIsAtStart(isAtStartNow);
		}
	};

	// We're using useEffect because we need to sync with external DOM scroll events
	useEffect(() => {
		const container = scrollContainerRef.current;
		if (!container) return;

		checkScrollPosition();
		container.addEventListener("scroll", checkScrollPosition);

		const resizeObserver = new ResizeObserver(checkScrollPosition);
		resizeObserver.observe(container);

		return () => {
			container.removeEventListener("scroll", checkScrollPosition);
			resizeObserver.disconnect();
		};
	}, []);

	return (
		<div className="flex">
			<ScrollButton
				direction="left"
				onClick={scrollToStart}
				isVisible={!isAtStart}
			/>
			<div
				ref={scrollContainerRef}
				className="h-12 bg-panel-accent px-3 flex justify-start items-center gap-5 overflow-x-auto scrollbar-x-hidden relative w-full"
			>
				{(Object.keys(tabs) as Tab[]).map((tabKey) => {
					const tab = tabs[tabKey];
					return (
						<div
							className={cn(
								"flex flex-col gap-0.5 items-center cursor-pointer",
								activeTab === tabKey ? "text-primary" : "text-muted-foreground",
							)}
							onClick={() => setActiveTab(tabKey)}
							key={tabKey}
						>
							<tab.icon className="!size-[1.1rem]" />
							<span className="text-[0.65rem]">{tab.label}</span>
						</div>
					);
				})}
			</div>
			<ScrollButton
				direction="right"
				onClick={scrollToEnd}
				isVisible={!isAtEnd}
			/>
		</div>
	);
}

function ScrollButton({
	direction,
	onClick,
	isVisible,
}: {
	direction: "left" | "right";
	onClick: () => void;
	isVisible: boolean;
}) {
	if (!isVisible) return null;

	const Icon = direction === "left" ? ChevronLeft : ChevronRight;

	return (
		<div className="bg-panel-accent w-12 h-full flex items-center justify-center">
			<Button
				size="icon"
				className="rounded-[0.4rem] w-4 h-7 !bg-foreground/10"
				onClick={onClick}
			>
				<Icon className="!size-4 text-foreground" />
			</Button>
		</div>
	);
}