summaryrefslogtreecommitdiff
path: root/apps/web/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/components')
-rw-r--r--apps/web/src/components/background-settings.tsx8
-rw-r--r--apps/web/src/components/delete-project-dialog.tsx2
-rw-r--r--apps/web/src/components/editor/audio-waveform.tsx85
-rw-r--r--apps/web/src/components/editor/media-panel/tabbar.tsx6
-rw-r--r--apps/web/src/components/editor/media-panel/views/media.tsx2
-rw-r--r--apps/web/src/components/editor/preview-panel.tsx129
-rw-r--r--apps/web/src/components/editor/properties-panel/index.tsx2
-rw-r--r--apps/web/src/components/editor/properties-panel/text-properties.tsx207
-rw-r--r--apps/web/src/components/editor/selection-box.tsx5
-rw-r--r--apps/web/src/components/editor/snap-indicator.tsx2
-rw-r--r--apps/web/src/components/editor/timeline/index.tsx83
-rw-r--r--apps/web/src/components/editor/timeline/timeline-element.tsx53
-rw-r--r--apps/web/src/components/editor/timeline/timeline-playhead.tsx4
-rw-r--r--apps/web/src/components/keyboard-shortcuts-help.tsx2
-rw-r--r--apps/web/src/components/landing/handlebars.tsx6
-rw-r--r--apps/web/src/components/landing/hero.tsx2
-rw-r--r--apps/web/src/components/onboarding.tsx2
-rw-r--r--apps/web/src/components/ui/badge.tsx6
-rw-r--r--apps/web/src/components/ui/button.tsx13
-rw-r--r--apps/web/src/components/ui/chart.tsx6
-rw-r--r--apps/web/src/components/ui/checkbox.tsx2
-rw-r--r--apps/web/src/components/ui/command.tsx4
-rw-r--r--apps/web/src/components/ui/context-menu.tsx6
-rw-r--r--apps/web/src/components/ui/dialog.tsx6
-rw-r--r--apps/web/src/components/ui/draggable-item.tsx6
-rw-r--r--apps/web/src/components/ui/dropdown-menu.tsx6
-rw-r--r--apps/web/src/components/ui/editable-timecode.tsx137
-rw-r--r--apps/web/src/components/ui/hover-card.tsx2
-rw-r--r--apps/web/src/components/ui/input-otp.tsx4
-rw-r--r--apps/web/src/components/ui/input.tsx2
-rw-r--r--apps/web/src/components/ui/menubar.tsx16
-rw-r--r--apps/web/src/components/ui/navigation-menu.tsx8
-rw-r--r--apps/web/src/components/ui/popover.tsx2
-rw-r--r--apps/web/src/components/ui/radio-group.tsx2
-rw-r--r--apps/web/src/components/ui/resizable.tsx2
-rw-r--r--apps/web/src/components/ui/scroll-area.tsx4
-rw-r--r--apps/web/src/components/ui/select.tsx8
-rw-r--r--apps/web/src/components/ui/separator.tsx2
-rw-r--r--apps/web/src/components/ui/sheet.tsx2
-rw-r--r--apps/web/src/components/ui/sidebar.tsx46
-rw-r--r--apps/web/src/components/ui/slider.tsx2
-rw-r--r--apps/web/src/components/ui/sponsor-button.tsx2
-rw-r--r--apps/web/src/components/ui/switch.tsx2
-rw-r--r--apps/web/src/components/ui/table.tsx2
-rw-r--r--apps/web/src/components/ui/tabs.tsx4
-rw-r--r--apps/web/src/components/ui/textarea.tsx2
-rw-r--r--apps/web/src/components/ui/toast.tsx8
-rw-r--r--apps/web/src/components/ui/toggle.tsx4
48 files changed, 718 insertions, 200 deletions
diff --git a/apps/web/src/components/background-settings.tsx b/apps/web/src/components/background-settings.tsx
index a640dc4c..7e27e574 100644
--- a/apps/web/src/components/background-settings.tsx
+++ b/apps/web/src/components/background-settings.tsx
@@ -40,12 +40,12 @@ export function BackgroundSettings() {
<Button
variant="text"
size="icon"
- className="!size-4 border border-muted-foreground"
+ className="size-4! border border-muted-foreground"
>
- <BackgroundIcon className="!size-3" />
+ <BackgroundIcon className="size-3!" />
</Button>
</PopoverTrigger>
- <PopoverContent className="flex flex-col items-start w-[20rem] h-[16rem] overflow-hidden p-0">
+ <PopoverContent className="flex flex-col items-start w-[20rem] h-64 overflow-hidden p-0">
<div className="flex items-center justify-between w-full gap-2 z-10 bg-popover p-3">
<h2 className="text-sm">Background</h2>
<div className="flex items-center gap-2 text-sm">
@@ -100,7 +100,7 @@ function ColorView({
}) {
return (
<div className="w-full h-full">
- <div className="absolute top-8 left-0 w-[calc(100%-1rem)] h-12 bg-gradient-to-b from-popover to-transparent pointer-events-none" />
+ <div className="absolute top-8 left-0 w-[calc(100%-1rem)] h-12 bg-linear-to-b from-popover to-transparent pointer-events-none" />
<div className="grid grid-cols-4 gap-2 w-full h-full p-3 pt-0 overflow-auto">
<div className="w-full aspect-square rounded-sm cursor-pointer border border-foreground/15 hover:border-primary flex items-center justify-center">
<PipetteIcon className="size-4" />
diff --git a/apps/web/src/components/delete-project-dialog.tsx b/apps/web/src/components/delete-project-dialog.tsx
index e2b32dda..e8f8703c 100644
--- a/apps/web/src/components/delete-project-dialog.tsx
+++ b/apps/web/src/components/delete-project-dialog.tsx
@@ -49,7 +49,7 @@ export function DeleteProjectDialog({
</DialogHeader>
<DialogFooter>
<Button
- variant="outline"
+ variant="text"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
diff --git a/apps/web/src/components/editor/audio-waveform.tsx b/apps/web/src/components/editor/audio-waveform.tsx
index a673f995..372a31c5 100644
--- a/apps/web/src/components/editor/audio-waveform.tsx
+++ b/apps/web/src/components/editor/audio-waveform.tsx
@@ -19,22 +19,21 @@ const AudioWaveform: React.FC<AudioWaveformProps> = ({
useEffect(() => {
let mounted = true;
-
+ let ws = wavesurfer.current;
+
const initWaveSurfer = async () => {
if (!waveformRef.current || !audioUrl) return;
try {
- // Clean up any existing instance
- if (wavesurfer.current) {
- try {
- wavesurfer.current.destroy();
- } catch (e) {
- // Silently ignore destroy errors
- }
+ // Clear any existing instance safely
+ if (ws) {
+ // Instead of immediately destroying, just set to null
+ // We'll destroy it outside this function
wavesurfer.current = null;
}
- wavesurfer.current = WaveSurfer.create({
+ // Create a fresh instance
+ const newWaveSurfer = WaveSurfer.create({
container: waveformRef.current,
waveColor: "rgba(255, 255, 255, 0.6)",
progressColor: "rgba(255, 255, 255, 0.9)",
@@ -46,15 +45,28 @@ const AudioWaveform: React.FC<AudioWaveformProps> = ({
interact: false,
});
+ // Assign to ref only if component is still mounted
+ if (mounted) {
+ wavesurfer.current = newWaveSurfer;
+ } else {
+ // Component unmounted during initialization, clean up
+ try {
+ newWaveSurfer.destroy();
+ } catch (e) {
+ // Ignore destroy errors
+ }
+ return;
+ }
+
// Event listeners
- wavesurfer.current.on("ready", () => {
+ newWaveSurfer.on("ready", () => {
if (mounted) {
setIsLoading(false);
setError(false);
}
});
- wavesurfer.current.on("error", (err) => {
+ newWaveSurfer.on("error", (err) => {
console.error("WaveSurfer error:", err);
if (mounted) {
setError(true);
@@ -62,7 +74,7 @@ const AudioWaveform: React.FC<AudioWaveformProps> = ({
}
});
- await wavesurfer.current.load(audioUrl);
+ await newWaveSurfer.load(audioUrl);
} catch (err) {
console.error("Failed to initialize WaveSurfer:", err);
if (mounted) {
@@ -72,17 +84,50 @@ const AudioWaveform: React.FC<AudioWaveformProps> = ({
}
};
- initWaveSurfer();
-
- return () => {
- mounted = false;
- if (wavesurfer.current) {
+ // First safely destroy previous instance if it exists
+ if (ws) {
+ // Use this pattern to safely destroy the previous instance
+ const wsToDestroy = ws;
+ // Detach from ref immediately
+ wavesurfer.current = null;
+
+ // Wait a tick to destroy so any pending operations can complete
+ requestAnimationFrame(() => {
try {
- wavesurfer.current.destroy();
+ wsToDestroy.destroy();
} catch (e) {
- // Silently ignore destroy errors
+ // Ignore errors during destroy
+ }
+ // Only initialize new instance after destroying the old one
+ if (mounted) {
+ initWaveSurfer();
}
- wavesurfer.current = null;
+ });
+ } else {
+ // No previous instance to clean up, initialize directly
+ initWaveSurfer();
+ }
+
+ return () => {
+ // Mark component as unmounted
+ mounted = false;
+
+ // Store reference to current wavesurfer instance
+ const wsToDestroy = wavesurfer.current;
+
+ // Immediately clear the ref to prevent accessing it after unmount
+ wavesurfer.current = null;
+
+ // If we have an instance to clean up, do it safely
+ if (wsToDestroy) {
+ // Delay destruction to avoid race conditions
+ requestAnimationFrame(() => {
+ try {
+ wsToDestroy.destroy();
+ } catch (e) {
+ // Ignore destroy errors - they're expected
+ }
+ });
}
};
}, [audioUrl, height]);
diff --git a/apps/web/src/components/editor/media-panel/tabbar.tsx b/apps/web/src/components/editor/media-panel/tabbar.tsx
index bce84a46..92482559 100644
--- a/apps/web/src/components/editor/media-panel/tabbar.tsx
+++ b/apps/web/src/components/editor/media-panel/tabbar.tsx
@@ -82,7 +82,7 @@ export function TabBar() {
onClick={() => setActiveTab(tabKey)}
key={tabKey}
>
- <tab.icon className="!size-[1.1rem]" />
+ <tab.icon className="size-[1.1rem]!" />
<span className="text-[0.65rem]">{tab.label}</span>
</div>
);
@@ -114,10 +114,10 @@ function ScrollButton({
<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"
+ className="rounded-[0.4rem] w-4 h-7 bg-foreground/10!"
onClick={onClick}
>
- <Icon className="!size-4 text-foreground" />
+ <Icon className="size-4! text-foreground" />
</Button>
</div>
);
diff --git a/apps/web/src/components/editor/media-panel/views/media.tsx b/apps/web/src/components/editor/media-panel/views/media.tsx
index f218dc14..d1533736 100644
--- a/apps/web/src/components/editor/media-panel/views/media.tsx
+++ b/apps/web/src/components/editor/media-panel/views/media.tsx
@@ -169,7 +169,7 @@ export function MediaView() {
if (item.type === "audio") {
return (
- <div className="w-full h-full bg-gradient-to-br from-green-500/20 to-emerald-500/20 flex flex-col items-center justify-center text-muted-foreground rounded border border-green-500/20">
+ <div className="w-full h-full bg-linear-to-br from-green-500/20 to-emerald-500/20 flex flex-col items-center justify-center text-muted-foreground rounded border border-green-500/20">
<Music className="h-6 w-6 mb-1" />
<span className="text-xs">Audio</span>
{item.duration && (
diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx
index 6cb06061..c6f4e159 100644
--- a/apps/web/src/components/editor/preview-panel.tsx
+++ b/apps/web/src/components/editor/preview-panel.tsx
@@ -20,6 +20,7 @@ import { Play, Pause, Expand, SkipBack, SkipForward } from "lucide-react";
import { useState, useRef, useEffect, useCallback } from "react";
import { cn } from "@/lib/utils";
import { formatTimeCode } from "@/lib/time";
+import { EditableTimecode } from "@/components/ui/editable-timecode";
import { FONT_CLASS_MAP } from "@/lib/font-config";
import { BackgroundSettings } from "../background-settings";
import { useProjectStore } from "@/stores/project-store";
@@ -233,6 +234,7 @@ export function PreviewPanel() {
tracks.forEach((track) => {
track.elements.forEach((element) => {
+ if (element.hidden) return;
const elementStart = element.startTime;
const elementEnd =
element.startTime +
@@ -357,20 +359,18 @@ export function PreviewPanel() {
style={{
left: `${
50 +
- (
- (dragState.isDragging && dragState.elementId === element.id
- ? dragState.currentX
- : element.x) / canvasSize.width
- ) *
+ ((dragState.isDragging && dragState.elementId === element.id
+ ? dragState.currentX
+ : element.x) /
+ canvasSize.width) *
100
}%`,
top: `${
50 +
- (
- (dragState.isDragging && dragState.elementId === element.id
- ? dragState.currentY
- : element.y) / canvasSize.height
- ) *
+ ((dragState.isDragging && dragState.elementId === element.id
+ ? dragState.currentY
+ : element.y) /
+ canvasSize.height) *
100
}%`,
transform: `translate(-50%, -50%) rotate(${element.rotation}deg) scale(${scaleRatio})`,
@@ -408,7 +408,7 @@ export function PreviewPanel() {
return (
<div
key={element.id}
- className="absolute inset-0 bg-gradient-to-br from-blue-500/20 to-purple-500/20 flex items-center justify-center"
+ className="absolute inset-0 bg-linear-to-br from-blue-500/20 to-purple-500/20 flex items-center justify-center"
>
<div className="text-center">
<div className="text-2xl mb-2">🎬</div>
@@ -564,7 +564,7 @@ function FullscreenToolbar({
toggle: () => void;
getTotalDuration: () => number;
}) {
- const { isPlaying } = usePlaybackStore();
+ const { isPlaying, seek } = usePlaybackStore();
const { activeProject } = useProjectStore();
const [isDragging, setIsDragging] = useState(false);
@@ -624,9 +624,15 @@ function FullscreenToolbar({
className="flex items-center gap-2 p-1 pt-2 w-full text-white"
>
<div className="flex items-center gap-1 text-[0.70rem] tabular-nums text-white/90">
- <span className="text-primary">
- {formatTimeCode(currentTime, "HH:MM:SS:FF", activeProject?.fps || 30)}
- </span>
+ <EditableTimecode
+ time={currentTime}
+ duration={totalDuration}
+ format="HH:MM:SS:FF"
+ fps={activeProject?.fps || 30}
+ onTimeChange={seek}
+ disabled={!hasAnyElements}
+ className="text-white/90 hover:bg-white/10"
+ />
<span className="opacity-50">/</span>
<span>
{formatTimeCode(
@@ -691,7 +697,7 @@ function FullscreenToolbar({
style={{ width: `${progress}%` }}
/>
<div
- className="absolute top-1/2 w-3 h-3 rounded-full -translate-y-1/2 -translate-x-1/2 shadow-sm bg-white border border-black/20"
+ className="absolute top-1/2 w-3 h-3 rounded-full -translate-y-1/2 -translate-x-1/2 shadow-xs bg-white border border-black/20"
style={{ left: `${progress}%` }}
/>
</div>
@@ -700,11 +706,11 @@ function FullscreenToolbar({
<Button
variant="text"
size="icon"
- className="!size-4 text-white/80 hover:text-white"
+ className="size-4! text-white/80 hover:text-white"
onClick={onToggleExpanded}
title="Exit fullscreen (Esc)"
>
- <Expand className="!size-4" />
+ <Expand className="size-4!" />
</Button>
</div>
);
@@ -738,7 +744,7 @@ function FullscreenPreview({
getTotalDuration: () => number;
}) {
return (
- <div className="fixed inset-0 z-[9999] flex flex-col">
+ <div className="fixed inset-0 z-9999 flex flex-col">
<div className="flex-1 flex items-center justify-center bg-background">
<div
className="relative overflow-hidden border border-border m-3"
@@ -801,7 +807,7 @@ function PreviewToolbar({
toggle: () => void;
getTotalDuration: () => number;
}) {
- const { isPlaying } = usePlaybackStore();
+ const { isPlaying, seek } = usePlaybackStore();
const { setCanvasSize, setCanvasSizeToOriginal } = useEditorStore();
const { activeProject } = useProjectStore();
const {
@@ -821,6 +827,18 @@ function PreviewToolbar({
setCanvasSizeToOriginal(aspectRatio);
};
+ const totalDuration = getTotalDuration();
+
+ const skipBackward = () => {
+ const newTime = Math.max(0, currentTime - 1);
+ setCurrentTime(newTime);
+ };
+
+ const skipForward = () => {
+ const newTime = Math.min(totalDuration, currentTime + 1);
+ setCurrentTime(newTime);
+ };
+
if (isExpanded) {
return (
<FullscreenToolbar
@@ -844,17 +862,18 @@ function PreviewToolbar({
<div>
<p
className={cn(
- "text-[0.75rem] text-muted-foreground flex items-center gap-1",
+ "text-[0.75rem] text-muted-foreground flex items-center gap-1 w-[10rem]",
!hasAnyElements && "opacity-50"
)}
>
- <span className="text-primary tabular-nums">
- {formatTimeCode(
- currentTime,
- "HH:MM:SS:FF",
- activeProject?.fps || 30
- )}
- </span>
+ <EditableTimecode
+ time={currentTime}
+ duration={getTotalDuration()}
+ format="HH:MM:SS:FF"
+ fps={activeProject?.fps || 30}
+ onTimeChange={seek}
+ disabled={!hasAnyElements}
+ />
<span className="opacity-50">/</span>
<span className="tabular-nums">
{formatTimeCode(
@@ -865,26 +884,48 @@ function PreviewToolbar({
</span>
</p>
</div>
- <Button
- variant="text"
- size="icon"
- onClick={toggle}
- disabled={!hasAnyElements}
- className="h-auto p-0"
- >
- {isPlaying ? (
- <Pause className="h-3 w-3" />
- ) : (
- <Play className="h-3 w-3" />
- )}
- </Button>
+ <div className="flex items-center gap-1">
+ <Button
+ variant="text"
+ size="icon"
+ onClick={skipBackward}
+ disabled={!hasAnyElements}
+ className="h-auto p-0 text-white hover:text-white/80"
+ title="Skip backward 1s"
+ >
+ <SkipBack className="h-3 w-3" />
+ </Button>
+ <Button
+ variant="text"
+ size="icon"
+ onClick={toggle}
+ disabled={!hasAnyElements}
+ className="h-auto p-0 text-white hover:text-white/80"
+ >
+ {isPlaying ? (
+ <Pause className="h-3 w-3" />
+ ) : (
+ <Play className="h-3 w-3" />
+ )}
+ </Button>
+ <Button
+ variant="text"
+ size="icon"
+ onClick={skipForward}
+ disabled={!hasAnyElements}
+ className="h-auto p-0 text-white hover:text-white/80"
+ title="Skip forward 1s"
+ >
+ <SkipForward className="h-3 w-3" />
+ </Button>
+ </div>
<div className="flex items-center gap-3">
<BackgroundSettings />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="sm"
- className="!bg-panel-accent text-foreground/85 text-[0.70rem] h-4 rounded-none border border-muted-foreground px-0.5 py-0 font-light"
+ className="bg-panel-accent! text-foreground/85 text-[0.70rem] h-4 rounded-none border border-muted-foreground px-0.5 py-0 font-light"
disabled={!hasAnyElements}
>
{getDisplayName()}
@@ -915,11 +956,11 @@ function PreviewToolbar({
<Button
variant="text"
size="icon"
- className="!size-4 text-muted-foreground"
+ className="size-4! text-muted-foreground"
onClick={onToggleExpanded}
title="Enter fullscreen"
>
- <Expand className="!size-4" />
+ <Expand className="size-4!" />
</Button>
</div>
</div>
diff --git a/apps/web/src/components/editor/properties-panel/index.tsx b/apps/web/src/components/editor/properties-panel/index.tsx
index 720c4a26..f1b3f6a3 100644
--- a/apps/web/src/components/editor/properties-panel/index.tsx
+++ b/apps/web/src/components/editor/properties-panel/index.tsx
@@ -67,7 +67,7 @@ export function PropertiesPanel() {
<div className="flex flex-col gap-1">
<Label className="text-xs text-muted-foreground">Frame rate:</Label>
<Select
- value={(activeProject?.fps || 30).toString()}
+ value={(activeProject?.fps || "N/A").toString()}
onValueChange={handleFpsChange}
>
<SelectTrigger className="w-32 h-6 text-xs">
diff --git a/apps/web/src/components/editor/properties-panel/text-properties.tsx b/apps/web/src/components/editor/properties-panel/text-properties.tsx
index e147ea65..a06624eb 100644
--- a/apps/web/src/components/editor/properties-panel/text-properties.tsx
+++ b/apps/web/src/components/editor/properties-panel/text-properties.tsx
@@ -5,6 +5,8 @@ import { TextElement } from "@/types/timeline";
import { useTimelineStore } from "@/stores/timeline-store";
import { Slider } from "@/components/ui/slider";
import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { useState } from "react";
import {
PropertyItem,
PropertyItemLabel,
@@ -20,12 +22,76 @@ export function TextProperties({
}) {
const { updateTextElement } = useTimelineStore();
+ // Local state for input values to allow temporary empty/invalid states
+ const [fontSizeInput, setFontSizeInput] = useState(
+ element.fontSize.toString()
+ );
+ const [opacityInput, setOpacityInput] = useState(
+ Math.round(element.opacity * 100).toString()
+ );
+
+ const parseAndValidateNumber = (
+ value: string,
+ min: number,
+ max: number,
+ fallback: number
+ ): number => {
+ const parsed = parseInt(value, 10);
+ if (isNaN(parsed)) return fallback;
+ return Math.max(min, Math.min(max, parsed));
+ };
+
+ const handleFontSizeChange = (value: string) => {
+ setFontSizeInput(value);
+
+ if (value.trim() !== "") {
+ const fontSize = parseAndValidateNumber(value, 8, 300, element.fontSize);
+ updateTextElement(trackId, element.id, { fontSize });
+ }
+ };
+
+ const handleFontSizeBlur = () => {
+ const fontSize = parseAndValidateNumber(
+ fontSizeInput,
+ 8,
+ 300,
+ element.fontSize
+ );
+ setFontSizeInput(fontSize.toString());
+ updateTextElement(trackId, element.id, { fontSize });
+ };
+
+ const handleOpacityChange = (value: string) => {
+ setOpacityInput(value);
+
+ if (value.trim() !== "") {
+ const opacityPercent = parseAndValidateNumber(
+ value,
+ 0,
+ 100,
+ Math.round(element.opacity * 100)
+ );
+ updateTextElement(trackId, element.id, { opacity: opacityPercent / 100 });
+ }
+ };
+
+ const handleOpacityBlur = () => {
+ const opacityPercent = parseAndValidateNumber(
+ opacityInput,
+ 0,
+ 100,
+ Math.round(element.opacity * 100)
+ );
+ setOpacityInput(opacityPercent.toString());
+ updateTextElement(trackId, element.id, { opacity: opacityPercent / 100 });
+ };
+
return (
<div className="space-y-6 p-5">
<Textarea
placeholder="Name"
defaultValue={element.content}
- className="min-h-[4.5rem] resize-none bg-background/50"
+ className="min-h-18 resize-none bg-background/50"
onChange={(e) =>
updateTextElement(trackId, element.id, { content: e.target.value })
}
@@ -42,27 +108,96 @@ export function TextProperties({
</PropertyItemValue>
</PropertyItem>
<PropertyItem direction="column">
+ <PropertyItem direction="row">
+ <PropertyItemLabel>Style</PropertyItemLabel>
+ <PropertyItemValue>
+ <div className="flex items-center gap-2">
+ <Button
+ variant={element.fontWeight === "bold" ? "default" : "outline"}
+ size="sm"
+ onClick={() =>
+ updateTextElement(trackId, element.id, {
+ fontWeight:
+ element.fontWeight === "bold" ? "normal" : "bold",
+ })
+ }
+ className="h-8 px-3 font-bold"
+ >
+ B
+ </Button>
+ <Button
+ variant={element.fontStyle === "italic" ? "default" : "outline"}
+ size="sm"
+ onClick={() =>
+ updateTextElement(trackId, element.id, {
+ fontStyle:
+ element.fontStyle === "italic" ? "normal" : "italic",
+ })
+ }
+ className="h-8 px-3 italic"
+ >
+ I
+ </Button>
+ <Button
+ variant={
+ element.textDecoration === "underline" ? "default" : "outline"
+ }
+ size="sm"
+ onClick={() =>
+ updateTextElement(trackId, element.id, {
+ textDecoration:
+ element.textDecoration === "underline"
+ ? "none"
+ : "underline",
+ })
+ }
+ className="h-8 px-3 underline"
+ >
+ U
+ </Button>
+ <Button
+ variant={
+ element.textDecoration === "line-through"
+ ? "default"
+ : "outline"
+ }
+ size="sm"
+ onClick={() =>
+ updateTextElement(trackId, element.id, {
+ textDecoration:
+ element.textDecoration === "line-through"
+ ? "none"
+ : "line-through",
+ })
+ }
+ className="h-8 px-3 line-through"
+ >
+ S
+ </Button>
+ </div>
+ </PropertyItemValue>
+ </PropertyItem>
<PropertyItemLabel>Font size</PropertyItemLabel>
<PropertyItemValue>
<div className="flex items-center gap-2">
<Slider
- defaultValue={[element.fontSize]}
+ value={[element.fontSize]}
min={8}
max={300}
step={1}
- onValueChange={([value]) =>
- updateTextElement(trackId, element.id, { fontSize: value })
- }
+ onValueChange={([value]) => {
+ updateTextElement(trackId, element.id, { fontSize: value });
+ setFontSizeInput(value.toString());
+ }}
className="w-full"
/>
<Input
type="number"
- value={element.fontSize}
- onChange={(e) =>
- updateTextElement(trackId, element.id, {
- fontSize: parseInt(e.target.value),
- })
- }
+ value={fontSizeInput}
+ min={8}
+ max={300}
+ onChange={(e) => handleFontSizeChange(e.target.value)}
+ onBlur={handleFontSizeBlur}
className="w-12 !text-xs h-7 rounded-sm text-center
[appearance:textfield]
[&::-webkit-outer-spin-button]:appearance-none
@@ -85,6 +220,56 @@ export function TextProperties({
/>
</PropertyItemValue>
</PropertyItem>
+ <PropertyItem direction="row">
+ <PropertyItemLabel>Background</PropertyItemLabel>
+ <PropertyItemValue>
+ <Input
+ type="color"
+ value={
+ element.backgroundColor === "transparent"
+ ? "#000000"
+ : element.backgroundColor || "#000000"
+ }
+ onChange={(e) => {
+ const backgroundColor = e.target.value;
+ updateTextElement(trackId, element.id, { backgroundColor });
+ }}
+ className="w-full cursor-pointer rounded-full"
+ />
+ </PropertyItemValue>
+ </PropertyItem>
+ <PropertyItem direction="column">
+ <PropertyItemLabel>Opacity</PropertyItemLabel>
+ <PropertyItemValue>
+ <div className="flex items-center gap-2">
+ <Slider
+ value={[element.opacity * 100]}
+ min={0}
+ max={100}
+ step={1}
+ onValueChange={([value]) => {
+ updateTextElement(trackId, element.id, {
+ opacity: value / 100,
+ });
+ setOpacityInput(value.toString());
+ }}
+ className="w-full"
+ />
+ <Input
+ type="number"
+ value={opacityInput}
+ min={0}
+ max={100}
+ onChange={(e) => handleOpacityChange(e.target.value)}
+ onBlur={handleOpacityBlur}
+ className="w-12 !text-xs h-7 rounded-sm text-center
+ [appearance:textfield]
+ [&::-webkit-outer-spin-button]:appearance-none
+ [&::-webkit-inner-spin-button]:appearance-none"
+ />
+ </div>
+ </PropertyItemValue>
+ </PropertyItem>
</div>
);
}
diff --git a/apps/web/src/components/editor/selection-box.tsx b/apps/web/src/components/editor/selection-box.tsx
index 0a97c6f8..ca9d799f 100644
--- a/apps/web/src/components/editor/selection-box.tsx
+++ b/apps/web/src/components/editor/selection-box.tsx
@@ -49,10 +49,7 @@ export function SelectionBox({
return (
<div
ref={selectionBoxRef}
- className="absolute pointer-events-none z-50"
- style={{
- backgroundColor: "hsl(var(--foreground) / 0.1)",
- }}
+ className="absolute pointer-events-none z-50 bg-foreground/10"
/>
);
}
diff --git a/apps/web/src/components/editor/snap-indicator.tsx b/apps/web/src/components/editor/snap-indicator.tsx
index 053d119e..0a51e107 100644
--- a/apps/web/src/components/editor/snap-indicator.tsx
+++ b/apps/web/src/components/editor/snap-indicator.tsx
@@ -65,7 +65,7 @@ export function SnapIndicator({
return (
<div
- className="absolute pointer-events-none z-[90]"
+ className="absolute pointer-events-none z-90"
style={{
left: `${leftPosition}px`,
top: 0,
diff --git a/apps/web/src/components/editor/timeline/index.tsx b/apps/web/src/components/editor/timeline/index.tsx
index 7943af06..85d6a11e 100644
--- a/apps/web/src/components/editor/timeline/index.tsx
+++ b/apps/web/src/components/editor/timeline/index.tsx
@@ -19,6 +19,7 @@ import {
Link,
ZoomIn,
ZoomOut,
+ Bookmark,
} from "lucide-react";
import {
Tooltip,
@@ -543,7 +544,7 @@ export function Timeline() {
{/* Timeline Header with Ruler */}
<div className="flex bg-panel sticky top-0 z-10">
{/* Track Labels Header */}
- <div className="w-48 flex-shrink-0 bg-panel border-r flex items-center justify-between px-3 py-2">
+ <div className="w-48 shrink-0 bg-panel border-r flex items-center justify-between px-3 py-2">
{/* Empty space */}
<span className="text-sm font-medium text-muted-foreground opacity-0">
.
@@ -552,7 +553,7 @@ export function Timeline() {
{/* Timeline Ruler */}
<div
- className="flex-1 relative overflow-hidden h-4"
+ className="flex-1 relative overflow-hidden h-10"
onWheel={(e) => {
// Check if this is horizontal scrolling - if so, don't handle it here
if (e.shiftKey || Math.abs(e.deltaX) > Math.abs(e.deltaY)) {
@@ -567,7 +568,7 @@ export function Timeline() {
<ScrollArea className="w-full" ref={rulerScrollRef}>
<div
ref={rulerRef}
- className="relative h-4 select-none cursor-default"
+ className="relative h-10 select-none cursor-default"
style={{
width: `${dynamicTimelineWidth}px`,
}}
@@ -601,7 +602,7 @@ export function Timeline() {
return (
<div
key={i}
- className={`absolute top-0 bottom-0 ${
+ className={`absolute top-0 h-4 ${
isMainMarker
? "border-l border-muted-foreground/40"
: "border-l border-muted-foreground/20"
@@ -651,6 +652,30 @@ export function Timeline() {
);
}).filter(Boolean);
})()}
+
+ {/* Bookmark markers */}
+ {(() => {
+ const { activeProject } = useProjectStore.getState();
+ if (!activeProject?.bookmarks?.length) return null;
+
+ return activeProject.bookmarks.map((bookmarkTime, i) => (
+ <div
+ key={`bookmark-${i}`}
+ className="absolute top-0 h-10 w-0.5 !bg-primary cursor-pointer"
+ style={{
+ left: `${bookmarkTime * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel}px`,
+ }}
+ onClick={(e) => {
+ e.stopPropagation();
+ usePlaybackStore.getState().seek(bookmarkTime);
+ }}
+ >
+ <div className="absolute top-[-1px] left-[-5px] text-primary">
+ <Bookmark className="h-3 w-3 fill-primary" />
+ </div>
+ </div>
+ ));
+ })()}
</div>
</ScrollArea>
</div>
@@ -662,7 +687,7 @@ export function Timeline() {
{tracks.length > 0 && (
<div
ref={trackLabelsRef}
- className="w-48 flex-shrink-0 border-r border-black overflow-y-auto z-[200] bg-panel"
+ className="w-48 shrink-0 border-r border-black overflow-y-auto z-200 bg-panel"
data-track-labels
>
<ScrollArea className="w-full h-full" ref={trackLabelsScrollRef}>
@@ -677,7 +702,7 @@ export function Timeline() {
<TrackIcon track={track} />
</div>
{track.muted && (
- <span className="ml-2 text-xs text-red-500 font-semibold flex-shrink-0">
+ <span className="ml-2 text-xs text-red-500 font-semibold shrink-0">
Muted
</span>
)}
@@ -761,7 +786,7 @@ export function Timeline() {
/>
</div>
</ContextMenuTrigger>
- <ContextMenuContent className="z-[200]">
+ <ContextMenuContent className="z-200">
<ContextMenuItem
onClick={(e) => {
e.stopPropagation();
@@ -773,6 +798,23 @@ export function Timeline() {
<ContextMenuItem onClick={(e) => e.stopPropagation()}>
Track settings (soon)
</ContextMenuItem>
+ {activeProject?.bookmarks?.length && activeProject.bookmarks.length > 0 && (
+ <>
+ <ContextMenuItem disabled>Bookmarks</ContextMenuItem>
+ {activeProject.bookmarks.map((bookmarkTime, i) => (
+ <ContextMenuItem
+ key={`bookmark-menu-${i}`}
+ onClick={(e) => {
+ e.stopPropagation();
+ seek(bookmarkTime);
+ }}
+ >
+ <Bookmark className="h-3 w-3 mr-2 inline-block" />
+ {bookmarkTime.toFixed(1)}s
+ </ContextMenuItem>
+ ))}
+ </>
+ )}
</ContextMenuContent>
</ContextMenu>
))}
@@ -791,13 +833,13 @@ function TrackIcon({ track }: { track: TimelineTrack }) {
return (
<>
{track.type === "media" && (
- <Video className="w-4 h-4 flex-shrink-0 text-muted-foreground" />
+ <Video className="w-4 h-4 shrink-0 text-muted-foreground" />
)}
{track.type === "text" && (
- <TypeIcon className="w-4 h-4 flex-shrink-0 text-muted-foreground" />
+ <TypeIcon className="w-4 h-4 shrink-0 text-muted-foreground" />
)}
{track.type === "audio" && (
- <Music className="w-4 h-4 flex-shrink-0 text-muted-foreground" />
+ <Music className="w-4 h-4 shrink-0 text-muted-foreground" />
)}
</>
);
@@ -828,6 +870,7 @@ function TimelineToolbar({
toggleRippleEditing,
} = useTimelineStore();
const { currentTime, duration, isPlaying, toggle } = usePlaybackStore();
+ const { toggleBookmark, isBookmarked } = useProjectStore();
// Action handlers
const handleSplitSelected = () => {
@@ -957,6 +1000,13 @@ function TimelineToolbar({
const handleZoomSliderChange = (values: number[]) => {
setZoomLevel(values[0]);
};
+
+ const handleToggleBookmark = async () => {
+ await toggleBookmark(currentTime);
+ };
+
+ // Check if the current time is bookmarked
+ const currentBookmarked = isBookmarked(currentTime);
return (
<div className="border-b flex items-center justify-between px-2 py-1">
<div className="flex items-center gap-1 w-full">
@@ -1088,6 +1138,17 @@ function TimelineToolbar({
</TooltipTrigger>
<TooltipContent>Delete element (Delete)</TooltipContent>
</Tooltip>
+ <div className="w-px h-6 bg-border mx-1" />
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <Button variant="text" size="icon" onClick={handleToggleBookmark}>
+ <Bookmark className={`h-4 w-4 ${currentBookmarked ? "fill-primary text-primary" : ""}`} />
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent>
+ {currentBookmarked ? "Remove bookmark" : "Add bookmark"}
+ </TooltipContent>
+ </Tooltip>
</TooltipProvider>
</div>
<div className="flex items-center gap-1">
@@ -1121,6 +1182,8 @@ function TimelineToolbar({
</TooltipContent>
</Tooltip>
</TooltipProvider>
+
+ <div className="h-6 w-px bg-border mx-1" />
<div className="flex items-center gap-1">
<Button variant="text" size="icon" onClick={handleZoomOut}>
<ZoomOut className="h-4 w-4" />
diff --git a/apps/web/src/components/editor/timeline/timeline-element.tsx b/apps/web/src/components/editor/timeline/timeline-element.tsx
index f581bbf0..c7ed833e 100644
--- a/apps/web/src/components/editor/timeline/timeline-element.tsx
+++ b/apps/web/src/components/editor/timeline/timeline-element.tsx
@@ -13,6 +13,10 @@ import {
Type,
Copy,
RefreshCw,
+ EyeOff,
+ Eye,
+ Volume2,
+ VolumeX,
} from "lucide-react";
import { useMediaStore } from "@/stores/media-store";
import { useTimelineStore } from "@/stores/timeline-store";
@@ -66,11 +70,18 @@ export function TimelineElement({
addElementToTrack,
replaceElementMedia,
rippleEditingEnabled,
+ toggleElementHidden,
} = useTimelineStore();
const { currentTime } = usePlaybackStore();
const [elementMenuOpen, setElementMenuOpen] = useState(false);
+ const mediaItem =
+ element.type === "media"
+ ? mediaItems.find((item) => item.id === element.mediaId)
+ : null;
+ const isAudio = mediaItem?.type === "audio";
+
const {
resizing,
isResizing,
@@ -141,6 +152,11 @@ export function TimelineElement({
}
};
+ const handleToggleElementHidden = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ toggleElementHidden(track.id, element.id);
+ };
+
const handleReplaceClip = (e: React.MouseEvent) => {
e.stopPropagation();
if (element.type !== "media") {
@@ -332,7 +348,7 @@ export function TimelineElement({
track.type
)} ${isSelected ? "border-b-[0.5px] border-t-[0.5px] border-foreground" : ""} ${
isBeingDragged ? "z-50" : "z-10"
- }`}
+ } ${element.hidden ? "opacity-50" : ""}`}
onClick={(e) => onElementClick && onElementClick(e, element)}
onMouseDown={handleElementMouseDown}
onContextMenu={(e) =>
@@ -343,6 +359,16 @@ export function TimelineElement({
{renderElementContent()}
</div>
+ {element.hidden && (
+ <div className="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center pointer-events-none">
+ {isAudio ? (
+ <VolumeX className="h-6 w-6 text-white" />
+ ) : (
+ <EyeOff className="h-6 w-6 text-white" />
+ )}
+ </div>
+ )}
+
{isSelected && (
<>
<div
@@ -358,11 +384,34 @@ export function TimelineElement({
</div>
</div>
</ContextMenuTrigger>
- <ContextMenuContent className="z-[200]">
+ <ContextMenuContent className="z-200">
<ContextMenuItem onClick={handleElementSplitContext}>
<Scissors className="h-4 w-4 mr-2" />
Split at playhead
</ContextMenuItem>
+ <ContextMenuItem onClick={handleToggleElementHidden}>
+ {isAudio ? (
+ element.hidden ? (
+ <Volume2 className="h-4 w-4 mr-2" />
+ ) : (
+ <VolumeX className="h-4 w-4 mr-2" />
+ )
+ ) : element.hidden ? (
+ <Eye className="h-4 w-4 mr-2" />
+ ) : (
+ <EyeOff className="h-4 w-4 mr-2" />
+ )}
+ <span>
+ {isAudio
+ ? element.hidden
+ ? "Unmute"
+ : "Mute"
+ : element.hidden
+ ? "Show"
+ : "Hide"}{" "}
+ {element.type === "text" ? "text" : "clip"}
+ </span>
+ </ContextMenuItem>
<ContextMenuItem onClick={handleElementDuplicateContext}>
<Copy className="h-4 w-4 mr-2" />
Duplicate {element.type === "text" ? "text" : "clip"}
diff --git a/apps/web/src/components/editor/timeline/timeline-playhead.tsx b/apps/web/src/components/editor/timeline/timeline-playhead.tsx
index b33166c0..ea70419b 100644
--- a/apps/web/src/components/editor/timeline/timeline-playhead.tsx
+++ b/apps/web/src/components/editor/timeline/timeline-playhead.tsx
@@ -126,7 +126,7 @@ export function TimelinePlayhead({
return (
<div
ref={playheadRef}
- className="absolute pointer-events-auto z-[150]"
+ className="absolute pointer-events-auto z-150"
style={{
left: `${leftPosition}px`,
top: 0,
@@ -142,7 +142,7 @@ export function TimelinePlayhead({
{/* Playhead dot indicator at the top (in ruler area) */}
<div
- className={`absolute top-1 left-1/2 transform -translate-x-1/2 w-3 h-3 rounded-full border-2 shadow-sm ${isSnappingToPlayhead ? "bg-primary border-primary" : "bg-foreground border-foreground"}`}
+ className={`absolute top-1 left-1/2 transform -translate-x-1/2 w-3 h-3 rounded-full border-2 shadow-xs ${isSnappingToPlayhead ? "bg-primary border-primary" : "bg-foreground border-foreground"}`}
/>
</div>
);
diff --git a/apps/web/src/components/keyboard-shortcuts-help.tsx b/apps/web/src/components/keyboard-shortcuts-help.tsx
index 96d4705f..15b1e1d8 100644
--- a/apps/web/src/components/keyboard-shortcuts-help.tsx
+++ b/apps/web/src/components/keyboard-shortcuts-help.tsx
@@ -93,7 +93,7 @@ const EditableShortcutKey = ({
<Button
variant="text"
size="sm"
- className={`inline-flex font-sans text-xs rounded px-2 min-w-[1.5rem] min-h-[1.5rem] leading-none items-center justify-center shadow-sm border mr-1 cursor-pointer hover:bg-opacity-80 ${
+ className={`inline-flex font-sans text-xs rounded px-2 min-w-6 min-h-6 leading-none items-center justify-center shadow-xs border mr-1 cursor-pointer hover:bg-opacity-80 ${
isRecording
? "border-primary bg-primary/10"
: "border-white/10 bg-black/20"
diff --git a/apps/web/src/components/landing/handlebars.tsx b/apps/web/src/components/landing/handlebars.tsx
index e87d50e9..3ed39a9f 100644
--- a/apps/web/src/components/landing/handlebars.tsx
+++ b/apps/web/src/components/landing/handlebars.tsx
@@ -46,9 +46,9 @@ export function Handlebars({ children }: HandlebarsProps) {
const rightGradient = useTransform(rightHandleX, [0, width + 10], [0, 100]);
return (
- <div className="flex justify-center gap-4 leading-[4rem] mt-0 md:mt-2">
- <div ref={containerRef} className="relative -rotate-[2.76deg] mt-2">
- <div className="absolute inset-0 w-full h-full rounded-2xl border border-yellow-500 flex justify-between z-[1]">
+ <div className="flex justify-center gap-4 leading-16">
+ <div ref={containerRef} className="relative -rotate-[2.76deg] mt-0.5">
+ <div className="absolute inset-0 w-full h-full rounded-2xl border border-yellow-500 flex justify-between z-1">
<motion.div
className="absolute z-10 left-0 h-full border border-yellow-500 w-7 rounded-full bg-accent flex items-center justify-center select-none"
style={{
diff --git a/apps/web/src/components/landing/hero.tsx b/apps/web/src/components/landing/hero.tsx
index dba958ac..2c6a130e 100644
--- a/apps/web/src/components/landing/hero.tsx
+++ b/apps/web/src/components/landing/hero.tsx
@@ -30,7 +30,7 @@ export function Hero() {
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.6, duration: 0.8 }}
- className="mb-8 flex justify-center"
+ className="mb-4 flex justify-center"
>
<SponsorButton
href="https://vercel.com/home?utm_source=opencut"
diff --git a/apps/web/src/components/onboarding.tsx b/apps/web/src/components/onboarding.tsx
index d72ac0f6..36ae68d9 100644
--- a/apps/web/src/components/onboarding.tsx
+++ b/apps/web/src/components/onboarding.tsx
@@ -79,7 +79,7 @@ export function Onboarding() {
return (
<Dialog open={isOpen} onOpenChange={handleClose}>
- <DialogContent className="sm:max-w-[425px] !outline-none">
+ <DialogContent className="sm:max-w-[425px] outline-hidden!">
<DialogTitle>
<span className="sr-only">{getStepTitle()}</span>
</DialogTitle>
diff --git a/apps/web/src/components/ui/badge.tsx b/apps/web/src/components/ui/badge.tsx
index e37530f4..02b8532b 100644
--- a/apps/web/src/components/ui/badge.tsx
+++ b/apps/web/src/components/ui/badge.tsx
@@ -4,16 +4,16 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "../../lib/utils";
const badgeVariants = cva(
- "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+ "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
- "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
+ "border-transparent bg-primary text-primary-foreground shadow-sm hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
- "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
+ "border-transparent bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/80",
outline: "text-foreground",
},
},
diff --git a/apps/web/src/components/ui/button.tsx b/apps/web/src/components/ui/button.tsx
index 5902af65..e5c5d4cc 100644
--- a/apps/web/src/components/ui/button.tsx
+++ b/apps/web/src/components/ui/button.tsx
@@ -5,19 +5,20 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "../../lib/utils";
const buttonVariants = cva(
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ "inline-flex items-center cursor-pointer justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
- default: "bg-foreground text-background shadow hover:bg-foreground/90",
+ default:
+ "bg-foreground text-background shadow-sm hover:bg-foreground/90",
primary:
- "bg-primary text-primary-foreground shadow hover:bg-primary/90",
+ "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90",
destructive:
- "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
+ "bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90",
outline:
- "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
+ "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
secondary:
- "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
+ "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
text: "bg-transparent p-0 rounded-none hover:text-muted-foreground", // Instead of ghost (matches app better)
link: "text-primary underline-offset-4 hover:underline",
},
diff --git a/apps/web/src/components/ui/chart.tsx b/apps/web/src/components/ui/chart.tsx
index 82d77752..a469c87a 100644
--- a/apps/web/src/components/ui/chart.tsx
+++ b/apps/web/src/components/ui/chart.tsx
@@ -57,7 +57,7 @@ const ChartContainer = React.forwardRef<
data-chart={chartId}
ref={ref}
className={cn(
- "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
+ "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden",
className
)}
{...props}
@@ -184,7 +184,7 @@ const ChartTooltipContent = React.forwardRef<
<div
ref={ref}
className={cn(
- "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
+ "grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
className
)}
>
@@ -213,7 +213,7 @@ const ChartTooltipContent = React.forwardRef<
!hideIndicator && (
<div
className={cn(
- "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
+ "shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
{
"h-2.5 w-2.5": indicator === "dot",
"w-1": indicator === "line",
diff --git a/apps/web/src/components/ui/checkbox.tsx b/apps/web/src/components/ui/checkbox.tsx
index 2b74bc52..5e429994 100644
--- a/apps/web/src/components/ui/checkbox.tsx
+++ b/apps/web/src/components/ui/checkbox.tsx
@@ -13,7 +13,7 @@ const Checkbox = React.forwardRef<
<CheckboxPrimitive.Root
ref={ref}
className={cn(
- "peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow-sm focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
diff --git a/apps/web/src/components/ui/command.tsx b/apps/web/src/components/ui/command.tsx
index 11b27041..887c1ab7 100644
--- a/apps/web/src/components/ui/command.tsx
+++ b/apps/web/src/components/ui/command.tsx
@@ -44,7 +44,7 @@ const CommandInput = React.forwardRef<
<CommandPrimitive.Input
ref={ref}
className={cn(
- "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
+ "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
@@ -115,7 +115,7 @@ const CommandItem = React.forwardRef<
<CommandPrimitive.Item
ref={ref}
className={cn(
- "relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ "relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-hidden data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
className
)}
{...props}
diff --git a/apps/web/src/components/ui/context-menu.tsx b/apps/web/src/components/ui/context-menu.tsx
index 7aa938f6..a1a28fa6 100644
--- a/apps/web/src/components/ui/context-menu.tsx
+++ b/apps/web/src/components/ui/context-menu.tsx
@@ -20,7 +20,7 @@ const ContextMenuSub = ContextMenuPrimitive.Sub;
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
const contextMenuItemVariants = cva(
- "relative flex cursor-pointer select-none items-center gap-2 px-2 py-1.5 text-sm outline-none transition-opacity data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
+ "relative flex cursor-pointer select-none items-center gap-2 px-2 py-1.5 text-sm outline-hidden transition-opacity data-disabled:pointer-events-none data-disabled:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
{
variants: {
variant: {
@@ -64,7 +64,7 @@ const ContextMenuSubContent = React.forwardRef<
<ContextMenuPrimitive.SubContent
ref={ref}
className={cn(
- "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ "z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
@@ -80,7 +80,7 @@ const ContextMenuContent = React.forwardRef<
<ContextMenuPrimitive.Content
ref={ref}
className={cn(
- "z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 bg-popover text-popover-foreground shadow-md",
+ "z-50 min-w-32 overflow-hidden rounded-md border p-1 bg-popover text-popover-foreground shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
diff --git a/apps/web/src/components/ui/dialog.tsx b/apps/web/src/components/ui/dialog.tsx
index 35843a5f..2d98285b 100644
--- a/apps/web/src/components/ui/dialog.tsx
+++ b/apps/web/src/components/ui/dialog.tsx
@@ -22,7 +22,7 @@ const DialogOverlay = React.forwardRef<
<DialogPrimitive.Overlay
ref={ref}
className={cn(
- "fixed inset-0 z-[100] bg-black/20 backdrop-blur-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
+ "fixed inset-0 z-100 bg-black/20 backdrop-blur-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
@@ -39,7 +39,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
- "fixed left-[50%] top-[50%] z-[150] grid w-[calc(100%-2rem)] max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-popover shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-lg",
+ "fixed left-[50%] top-[50%] z-150 grid w-[calc(100%-2rem)] max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-popover shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 rounded-lg",
className
)}
onCloseAutoFocus={(e) => {
@@ -55,7 +55,7 @@ const DialogContent = React.forwardRef<
<ScrollArea className="max-h-[75vh]">
<div className="p-6 space-y-4">{children}</div>
</ScrollArea>
- <DialogPrimitive.Close className="absolute right-4 top-4 opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
+ <DialogPrimitive.Close className="absolute right-4 top-4 opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
diff --git a/apps/web/src/components/ui/draggable-item.tsx b/apps/web/src/components/ui/draggable-item.tsx
index dfb4dce7..4c71e4b7 100644
--- a/apps/web/src/components/ui/draggable-item.tsx
+++ b/apps/web/src/components/ui/draggable-item.tsx
@@ -130,7 +130,7 @@ export function DraggableMediaItem({
typeof document !== "undefined" &&
createPortal(
<div
- className="fixed pointer-events-none z-[9999]"
+ className="fixed pointer-events-none z-9999"
style={{
left: dragPosition.x - 40, // Center the preview (half of 80px)
top: dragPosition.y - 40, // Center the preview (half of 80px)
@@ -139,7 +139,7 @@ export function DraggableMediaItem({
<div className="w-[80px]">
<AspectRatio
ratio={1}
- className="relative rounded-md overflow-hidden shadow-2xl ring ring-primary"
+ className="relative rounded-md overflow-hidden shadow-2xl ring-3 ring-primary"
>
<div className="w-full h-full [&_img]:w-full [&_img]:h-full [&_img]:object-cover [&_img]:rounded-none">
{preview}
@@ -179,7 +179,7 @@ function PlusButton({
}}
title={tooltipText}
>
- <Plus className="!size-3" />
+ <Plus className="size-3!" />
</Button>
);
diff --git a/apps/web/src/components/ui/dropdown-menu.tsx b/apps/web/src/components/ui/dropdown-menu.tsx
index d9962dd2..bdd86d58 100644
--- a/apps/web/src/components/ui/dropdown-menu.tsx
+++ b/apps/web/src/components/ui/dropdown-menu.tsx
@@ -20,7 +20,7 @@ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const dropdownMenuItemVariants = cva(
- "relative flex cursor-pointer select-none items-center gap-2 px-2 py-1.5 text-sm outline-none transition-opacity data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
+ "relative flex cursor-pointer select-none items-center gap-2 px-2 py-1.5 text-sm outline-hidden transition-opacity data-disabled:pointer-events-none data-disabled:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
{
variants: {
variant: {
@@ -65,7 +65,7 @@ const DropdownMenuSubContent = React.forwardRef<
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
- "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ "z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
@@ -87,7 +87,7 @@ const DropdownMenuContent = React.forwardRef<
e.preventDefault();
}}
className={cn(
- "z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 bg-popover text-popover-foreground shadow-md",
+ "z-50 min-w-32 overflow-hidden rounded-md border p-1 bg-popover text-popover-foreground shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
diff --git a/apps/web/src/components/ui/editable-timecode.tsx b/apps/web/src/components/ui/editable-timecode.tsx
new file mode 100644
index 00000000..f6d29f49
--- /dev/null
+++ b/apps/web/src/components/ui/editable-timecode.tsx
@@ -0,0 +1,137 @@
+"use client";
+
+import { useState, useRef, useEffect } from "react";
+import { cn } from "@/lib/utils";
+import { formatTimeCode, parseTimeCode } from "@/lib/time";
+
+interface EditableTimecodeProps {
+ time: number;
+ duration?: number;
+ format?: "MM:SS" | "HH:MM:SS" | "HH:MM:SS:CS" | "HH:MM:SS:FF";
+ fps?: number;
+ onTimeChange?: (time: number) => void;
+ className?: string;
+ disabled?: boolean;
+}
+
+export function EditableTimecode({
+ time,
+ duration,
+ format = "HH:MM:SS:FF",
+ fps = 30,
+ onTimeChange,
+ className,
+ disabled = false,
+}: EditableTimecodeProps) {
+ const [isEditing, setIsEditing] = useState(false);
+ const [inputValue, setInputValue] = useState("");
+ const [hasError, setHasError] = useState(false);
+ const inputRef = useRef<HTMLInputElement>(null);
+ const enterPressedRef = useRef(false);
+
+ const formattedTime = formatTimeCode(time, format, fps);
+
+ const startEditing = () => {
+ if (disabled) return;
+ setIsEditing(true);
+ setInputValue(formattedTime);
+ setHasError(false);
+ enterPressedRef.current = false;
+ };
+
+ const cancelEditing = () => {
+ setIsEditing(false);
+ setInputValue("");
+ setHasError(false);
+ enterPressedRef.current = false;
+ };
+
+ const applyEdit = () => {
+ const parsedTime = parseTimeCode(inputValue, format, fps);
+
+ if (parsedTime === null) {
+ setHasError(true);
+ return;
+ }
+
+ // Clamp time to valid range
+ const clampedTime = Math.max(
+ 0,
+ duration ? Math.min(duration, parsedTime) : parsedTime
+ );
+
+ onTimeChange?.(clampedTime);
+ setIsEditing(false);
+ setInputValue("");
+ setHasError(false);
+ enterPressedRef.current = false;
+ };
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ e.preventDefault();
+ enterPressedRef.current = true;
+ applyEdit();
+ } else if (e.key === "Escape") {
+ e.preventDefault();
+ cancelEditing();
+ }
+ };
+
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ setInputValue(e.target.value);
+ setHasError(false);
+ };
+
+ const handleBlur = () => {
+ // Only apply edit if Enter wasn't pressed (to avoid double processing)
+ if (!enterPressedRef.current && isEditing) {
+ applyEdit();
+ }
+ };
+
+ // Focus input when entering edit mode
+ useEffect(() => {
+ if (isEditing && inputRef.current) {
+ inputRef.current.focus();
+ inputRef.current.select();
+ }
+ }, [isEditing]);
+
+ if (isEditing) {
+ return (
+ <input
+ ref={inputRef}
+ type="text"
+ value={inputValue}
+ onChange={handleInputChange}
+ onKeyDown={handleKeyDown}
+ onBlur={handleBlur}
+ className={cn(
+ "text-xs font-mono bg-transparent border-none outline-none",
+ "focus:bg-background focus:border focus:border-primary focus:px-1 focus:rounded",
+ "tabular-nums text-primary",
+ hasError && "text-destructive focus:border-destructive",
+ className
+ )}
+ style={{ width: `${formattedTime.length + 1}ch` }}
+ placeholder={formattedTime}
+ />
+ );
+ }
+
+ return (
+ <span
+ onClick={startEditing}
+ className={cn(
+ "text-xs font-mono tabular-nums text-primary cursor-pointer",
+ "hover:bg-muted/50 hover:rounded px-1 -mx-1 transition-colors",
+ disabled && "cursor-default hover:bg-transparent",
+ className
+ )}
+ title={disabled ? undefined : "Click to edit time"}
+ >
+ {formattedTime}
+ </span>
+ );
+}
diff --git a/apps/web/src/components/ui/hover-card.tsx b/apps/web/src/components/ui/hover-card.tsx
index c3958fcf..92700e09 100644
--- a/apps/web/src/components/ui/hover-card.tsx
+++ b/apps/web/src/components/ui/hover-card.tsx
@@ -18,7 +18,7 @@ const HoverCardContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
className={cn(
- "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
diff --git a/apps/web/src/components/ui/input-otp.tsx b/apps/web/src/components/ui/input-otp.tsx
index 1bffc451..0d2cc002 100644
--- a/apps/web/src/components/ui/input-otp.tsx
+++ b/apps/web/src/components/ui/input-otp.tsx
@@ -13,7 +13,7 @@ const InputOTP = React.forwardRef<
<OTPInput
ref={ref}
containerClassName={cn(
- "flex items-center justify-between gap-2 w-full has-[:disabled]:opacity-50",
+ "flex items-center justify-between gap-2 w-full has-disabled:opacity-50",
containerClassName
)}
className={cn("disabled:cursor-not-allowed", className)}
@@ -45,7 +45,7 @@ const InputOTPSlot = React.forwardRef<
<div
ref={ref}
className={cn(
- "relative flex aspect-square flex-1 min-w-[36px] items-center justify-center border border-input text-lg shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
+ "relative flex aspect-square flex-1 min-w-[36px] items-center justify-center border border-input text-lg shadow-xs transition-all first:rounded-l-md first:border-l last:rounded-r-md",
isActive && "z-10 ring-1 ring-ring",
className
)}
diff --git a/apps/web/src/components/ui/input.tsx b/apps/web/src/components/ui/input.tsx
index 8fc31a58..6f32885c 100644
--- a/apps/web/src/components/ui/input.tsx
+++ b/apps/web/src/components/ui/input.tsx
@@ -23,7 +23,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input
type={inputType}
className={cn(
- "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
showPasswordToggle && "pr-10",
className
)}
diff --git a/apps/web/src/components/ui/menubar.tsx b/apps/web/src/components/ui/menubar.tsx
index 642ffa2b..96e66c8a 100644
--- a/apps/web/src/components/ui/menubar.tsx
+++ b/apps/web/src/components/ui/menubar.tsx
@@ -23,7 +23,7 @@ const Menubar = React.forwardRef<
<MenubarPrimitive.Root
ref={ref}
className={cn(
- "flex h-9 items-center space-x-1 rounded-md border bg-background p-1 shadow-sm",
+ "flex h-9 items-center space-x-1 rounded-md border bg-background p-1 shadow-xs",
className
)}
{...props}
@@ -38,7 +38,7 @@ const MenubarTrigger = React.forwardRef<
<MenubarPrimitive.Trigger
ref={ref}
className={cn(
- "flex cursor-default select-none items-center rounded-sm px-3 py-1 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
+ "flex cursor-default select-none items-center rounded-sm px-3 py-1 text-sm font-medium outline-hidden focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
className
)}
{...props}
@@ -55,7 +55,7 @@ const MenubarSubTrigger = React.forwardRef<
<MenubarPrimitive.SubTrigger
ref={ref}
className={cn(
- "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8",
className
)}
@@ -74,7 +74,7 @@ const MenubarSubContent = React.forwardRef<
<MenubarPrimitive.SubContent
ref={ref}
className={cn(
- "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ "z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
@@ -97,7 +97,7 @@ const MenubarContent = React.forwardRef<
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
- "z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ "z-50 min-w-48 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
@@ -116,7 +116,7 @@ const MenubarItem = React.forwardRef<
<MenubarPrimitive.Item
ref={ref}
className={cn(
- "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
inset && "pl-8",
className
)}
@@ -132,7 +132,7 @@ const MenubarCheckboxItem = React.forwardRef<
<MenubarPrimitive.CheckboxItem
ref={ref}
className={cn(
- "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
className
)}
checked={checked}
@@ -155,7 +155,7 @@ const MenubarRadioItem = React.forwardRef<
<MenubarPrimitive.RadioItem
ref={ref}
className={cn(
- "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
className
)}
{...props}
diff --git a/apps/web/src/components/ui/navigation-menu.tsx b/apps/web/src/components/ui/navigation-menu.tsx
index b5dd3622..288b5d88 100644
--- a/apps/web/src/components/ui/navigation-menu.tsx
+++ b/apps/web/src/components/ui/navigation-menu.tsx
@@ -41,7 +41,7 @@ NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
const NavigationMenuItem = NavigationMenuPrimitive.Item;
const navigationMenuTriggerStyle = cva(
- "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
+ "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-hidden disabled:pointer-events-none disabled:opacity-50 data-active:bg-accent/50 data-[state=open]:bg-accent/50"
);
const NavigationMenuTrigger = React.forwardRef<
@@ -55,7 +55,7 @@ const NavigationMenuTrigger = React.forwardRef<
>
{children}{" "}
<ChevronDown
- className="relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
+ className="relative top-px ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
@@ -86,7 +86,7 @@ const NavigationMenuViewport = React.forwardRef<
<div className={cn("absolute left-0 top-full flex justify-center")}>
<NavigationMenuPrimitive.Viewport
className={cn(
- "origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
+ "origin-top-center relative mt-1.5 h-(--radix-navigation-menu-viewport-height) w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-(--radix-navigation-menu-viewport-width)",
className
)}
ref={ref}
@@ -104,7 +104,7 @@ const NavigationMenuIndicator = React.forwardRef<
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
- "top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
+ "top-full z-1 flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
className
)}
{...props}
diff --git a/apps/web/src/components/ui/popover.tsx b/apps/web/src/components/ui/popover.tsx
index cc4f6508..47782a7f 100644
--- a/apps/web/src/components/ui/popover.tsx
+++ b/apps/web/src/components/ui/popover.tsx
@@ -21,7 +21,7 @@ const PopoverContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
className={cn(
- "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
diff --git a/apps/web/src/components/ui/radio-group.tsx b/apps/web/src/components/ui/radio-group.tsx
index cc07d34f..859b8b5f 100644
--- a/apps/web/src/components/ui/radio-group.tsx
+++ b/apps/web/src/components/ui/radio-group.tsx
@@ -28,7 +28,7 @@ const RadioGroupItem = React.forwardRef<
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
- "aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
+ "aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow-sm focus:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
diff --git a/apps/web/src/components/ui/resizable.tsx b/apps/web/src/components/ui/resizable.tsx
index a4443996..fc9d8b81 100644
--- a/apps/web/src/components/ui/resizable.tsx
+++ b/apps/web/src/components/ui/resizable.tsx
@@ -28,7 +28,7 @@ const ResizableHandle = ({
}) => (
<ResizablePrimitive.PanelResizeHandle
className={cn(
- "relative flex w-px items-center justify-center bg-transparent after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
+ "relative flex w-px items-center justify-center bg-transparent after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
className
)}
{...props}
diff --git a/apps/web/src/components/ui/scroll-area.tsx b/apps/web/src/components/ui/scroll-area.tsx
index 5c9db727..d5aaf2ee 100644
--- a/apps/web/src/components/ui/scroll-area.tsx
+++ b/apps/web/src/components/ui/scroll-area.tsx
@@ -38,9 +38,9 @@ const ScrollBar = React.forwardRef<
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
- "h-full w-2.5 border-l border-l-transparent p-[1px]",
+ "h-full w-2.5 border-l border-l-transparent p-px",
orientation === "horizontal" &&
- "h-2.5 flex-col border-t border-t-transparent p-[1px]",
+ "h-2.5 flex-col border-t border-t-transparent p-px",
className
)}
{...props}
diff --git a/apps/web/src/components/ui/select.tsx b/apps/web/src/components/ui/select.tsx
index 911b3e5d..ce4f340d 100644
--- a/apps/web/src/components/ui/select.tsx
+++ b/apps/web/src/components/ui/select.tsx
@@ -14,7 +14,7 @@ const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value;
const selectItemVariants = cva(
- "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none transition-opacity data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-hidden transition-opacity data-disabled:pointer-events-none data-disabled:opacity-50",
{
variants: {
variant: {
@@ -35,7 +35,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
- "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
+ "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs ring-offset-background placeholder:text-muted-foreground focus:outline-hidden focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
@@ -91,7 +91,7 @@ const SelectContent = React.forwardRef<
<SelectPrimitive.Content
ref={ref}
className={cn(
- "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ "relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
@@ -108,7 +108,7 @@ const SelectContent = React.forwardRef<
className={cn(
"p-1",
position === "popper" &&
- "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
+ "h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)"
)}
>
{children}
diff --git a/apps/web/src/components/ui/separator.tsx b/apps/web/src/components/ui/separator.tsx
index 5b6774dc..f0040a52 100644
--- a/apps/web/src/components/ui/separator.tsx
+++ b/apps/web/src/components/ui/separator.tsx
@@ -19,7 +19,7 @@ const Separator = React.forwardRef<
orientation={orientation}
className={cn(
"shrink-0 bg-border",
- orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
+ orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
className
)}
{...props}
diff --git a/apps/web/src/components/ui/sheet.tsx b/apps/web/src/components/ui/sheet.tsx
index 6aa092dc..2c5522c1 100644
--- a/apps/web/src/components/ui/sheet.tsx
+++ b/apps/web/src/components/ui/sheet.tsx
@@ -64,7 +64,7 @@ const SheetContent = React.forwardRef<
className={cn(sheetVariants({ side }), className)}
{...props}
>
- <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
+ <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
diff --git a/apps/web/src/components/ui/sidebar.tsx b/apps/web/src/components/ui/sidebar.tsx
index c1583503..819116c3 100644
--- a/apps/web/src/components/ui/sidebar.tsx
+++ b/apps/web/src/components/ui/sidebar.tsx
@@ -141,7 +141,7 @@ const SidebarProvider = React.forwardRef<
} as React.CSSProperties
}
className={cn(
- "group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
+ "group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar",
className
)}
ref={ref}
@@ -181,7 +181,7 @@ const Sidebar = React.forwardRef<
return (
<div
className={cn(
- "flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground",
+ "flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground",
className
)}
ref={ref}
@@ -198,7 +198,7 @@ const Sidebar = React.forwardRef<
<SheetContent
data-sidebar="sidebar"
data-mobile="true"
- className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
+ className="w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
style={
{
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
@@ -225,30 +225,30 @@ const Sidebar = React.forwardRef<
{/* This is what handles the sidebar gap on desktop */}
<div
className={cn(
- "duration-200 relative h-full w-[--sidebar-width] bg-transparent transition-[width] ease-linear",
+ "duration-200 relative h-full w-(--sidebar-width) bg-transparent transition-[width] ease-linear",
"group-data-[collapsible=offcanvas]:w-0",
"group-data-[side=right]:rotate-180",
variant === "floating" || variant === "inset"
- ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
- : "group-data-[collapsible=icon]:w-[--sidebar-width-icon]"
+ ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
+ : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"
)}
/>
<div
className={cn(
- "duration-200 fixed h-[calc(100vh-4rem)] top-16 z-10 hidden w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex",
+ "duration-200 fixed h-[calc(100vh-4rem)] top-16 z-10 hidden w-(--sidebar-width) transition-[left,right,width] ease-linear md:flex",
side === "left"
? "left-0 border-r group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
: "right-0 border-l group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
variant === "floating" || variant === "inset"
- ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
- : "group-data-[collapsible=icon]:w-[--sidebar-width-icon]",
+ ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
+ : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)",
className
)}
{...props}
>
<div
data-sidebar="sidebar"
- className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow"
+ className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow-sm"
>
{children}
</div>
@@ -301,9 +301,9 @@ const SidebarRail = React.forwardRef<
title="Toggle Sidebar"
className={cn(
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
- "[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
+ "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
- "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar",
+ "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar",
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
className
@@ -323,7 +323,7 @@ const SidebarInset = React.forwardRef<
ref={ref}
className={cn(
"relative flex min-h-svh flex-1 flex-col bg-background",
- "peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
+ "peer-data-[variant=inset]:min-h-[calc(100svh-(--spacing(4)))] md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm",
className
)}
{...props}
@@ -439,7 +439,7 @@ const SidebarGroupLabel = React.forwardRef<
ref={ref as any}
data-sidebar="group-label"
className={cn(
- "duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
+ "duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-hidden ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className
)}
@@ -460,9 +460,9 @@ const SidebarGroupAction = React.forwardRef<
ref={ref as any}
data-sidebar="group-action"
className={cn(
- "absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
+ "absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
- "after:absolute after:-inset-2 after:md:hidden",
+ "after:absolute after:-inset-2 md:after:hidden",
"group-data-[collapsible=icon]:hidden",
className
)}
@@ -512,18 +512,18 @@ const SidebarMenuItem = React.forwardRef<
SidebarMenuItem.displayName = "SidebarMenuItem";
const sidebarMenuButtonVariants = cva(
- "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
+ "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
{
variants: {
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline:
- "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
+ "bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]",
},
size: {
default: "h-8 text-sm",
sm: "h-7 text-xs",
- lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
+ lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
},
},
defaultVariants: {
@@ -606,9 +606,9 @@ const SidebarMenuAction = React.forwardRef<
ref={ref as any}
data-sidebar="menu-action"
className={cn(
- "absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
+ "absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
- "after:absolute after:-inset-2 after:md:hidden",
+ "after:absolute after:-inset-2 md:after:hidden",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
@@ -669,7 +669,7 @@ const SidebarMenuSkeleton = React.forwardRef<
/>
)}
<Skeleton
- className="h-4 flex-1 max-w-[--skeleton-width]"
+ className="h-4 flex-1 max-w-(--skeleton-width)"
data-sidebar="menu-skeleton-text"
style={
{
@@ -722,7 +722,7 @@ const SidebarMenuSubButton = React.forwardRef<
data-size={size}
data-active={isActive}
className={cn(
- "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
+ "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
size === "sm" && "text-xs",
size === "md" && "text-sm",
diff --git a/apps/web/src/components/ui/slider.tsx b/apps/web/src/components/ui/slider.tsx
index 4789ae17..d4afdd06 100644
--- a/apps/web/src/components/ui/slider.tsx
+++ b/apps/web/src/components/ui/slider.tsx
@@ -20,7 +20,7 @@ const Slider = React.forwardRef<
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
<SliderPrimitive.Range className="absolute h-full bg-primary" />
</SliderPrimitive.Track>
- <SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
+ <SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow-sm transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
</SliderPrimitive.Root>
));
Slider.displayName = SliderPrimitive.Root.displayName;
diff --git a/apps/web/src/components/ui/sponsor-button.tsx b/apps/web/src/components/ui/sponsor-button.tsx
index a060e428..9dc8e966 100644
--- a/apps/web/src/components/ui/sponsor-button.tsx
+++ b/apps/web/src/components/ui/sponsor-button.tsx
@@ -19,7 +19,7 @@ export function SponsorButton({
href={href}
target="_blank"
rel="noopener noreferrer"
- className={`inline-flex items-center gap-2 px-3 py-2 rounded-full border border-white/10 bg-white/5 backdrop-blur-sm hover:bg-white/10 hover:border-white/20 transition-all duration-200 group shadow-lg ${className}`}
+ className={`inline-flex items-center gap-2 px-3 py-2 rounded-full border border-white/10 bg-white/5 backdrop-blur-xs hover:bg-white/10 hover:border-white/20 transition-all duration-200 group shadow-lg ${className}`}
>
<span className="text-xs font-medium text-zinc-400 group-hover:text-zinc-300 transition-colors">
Sponsored by
diff --git a/apps/web/src/components/ui/switch.tsx b/apps/web/src/components/ui/switch.tsx
index ba324f1d..f210afdf 100644
--- a/apps/web/src/components/ui/switch.tsx
+++ b/apps/web/src/components/ui/switch.tsx
@@ -11,7 +11,7 @@ const Switch = React.forwardRef<
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
- "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
+ "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-xs transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className
)}
{...props}
diff --git a/apps/web/src/components/ui/table.tsx b/apps/web/src/components/ui/table.tsx
index 6b83618c..7481cabb 100644
--- a/apps/web/src/components/ui/table.tsx
+++ b/apps/web/src/components/ui/table.tsx
@@ -43,7 +43,7 @@ const TableFooter = React.forwardRef<
<tfoot
ref={ref}
className={cn(
- "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
+ "border-t bg-muted/50 font-medium last:[&>tr]:border-b-0",
className
)}
{...props}
diff --git a/apps/web/src/components/ui/tabs.tsx b/apps/web/src/components/ui/tabs.tsx
index a51a28a2..e5384daf 100644
--- a/apps/web/src/components/ui/tabs.tsx
+++ b/apps/web/src/components/ui/tabs.tsx
@@ -29,7 +29,7 @@ const TabsTrigger = React.forwardRef<
<TabsPrimitive.Trigger
ref={ref}
className={cn(
- "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
className
)}
{...props}
@@ -44,7 +44,7 @@ const TabsContent = React.forwardRef<
<TabsPrimitive.Content
ref={ref}
className={cn(
- "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
+ "mt-2 ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
diff --git a/apps/web/src/components/ui/textarea.tsx b/apps/web/src/components/ui/textarea.tsx
index 64f81ce6..b9004a34 100644
--- a/apps/web/src/components/ui/textarea.tsx
+++ b/apps/web/src/components/ui/textarea.tsx
@@ -9,7 +9,7 @@ const Textarea = React.forwardRef<
return (
<textarea
className={cn(
- "flex min-h-[60px] w-full rounded-md border border-input bg-background px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-xs",
+ "flex min-h-[60px] w-full rounded-md border border-input bg-background px-3 py-2 text-base shadow-xs placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-0 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-xs",
className
)}
ref={ref}
diff --git a/apps/web/src/components/ui/toast.tsx b/apps/web/src/components/ui/toast.tsx
index bf89e350..bd733f63 100644
--- a/apps/web/src/components/ui/toast.tsx
+++ b/apps/web/src/components/ui/toast.tsx
@@ -16,7 +16,7 @@ const ToastViewport = React.forwardRef<
<ToastPrimitives.Viewport
ref={ref}
className={cn(
- "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
+ "fixed top-0 z-100 flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
className
)}
{...props}
@@ -25,7 +25,7 @@ const ToastViewport = React.forwardRef<
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
const toastVariants = cva(
- "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
+ "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-(--radix-toast-swipe-end-x) data-[swipe=move]:translate-x-(--radix-toast-swipe-move-x) data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full sm:data-[state=open]:slide-in-from-bottom-full",
{
variants: {
variant: {
@@ -62,7 +62,7 @@ const ToastAction = React.forwardRef<
<ToastPrimitives.Action
ref={ref}
className={cn(
- "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus-visible:outline-none focus-visible:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus-visible::ring-destructive",
+ "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus-visible:outline-hidden focus-visible:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 hover:group-[.destructive]:border-destructive/30 hover:group-[.destructive]:bg-destructive hover:group-[.destructive]:text-destructive-foreground group-[.destructive]:focus-visible::ring-destructive",
className
)}
{...props}
@@ -77,7 +77,7 @@ const ToastClose = React.forwardRef<
<ToastPrimitives.Close
ref={ref}
className={cn(
- "absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus-visible::ring-1 group-hover:opacity-100 group-[.destructive]:text-muted-foreground group-[.destructive]:hover:text-red-50 group-[.destructive]:focus-visible:ring-red-400 group-[.destructive]:focus-visible:ring-offset-red-600",
+ "absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-hidden focus-visible::ring-1 group-hover:opacity-100 group-[.destructive]:text-muted-foreground hover:group-[.destructive]:text-red-50 focus-visible:group-[.destructive]:ring-red-400 focus-visible:group-[.destructive]:ring-offset-red-600",
className
)}
toast-close=""
diff --git a/apps/web/src/components/ui/toggle.tsx b/apps/web/src/components/ui/toggle.tsx
index a4bfda42..460aa9f2 100644
--- a/apps/web/src/components/ui/toggle.tsx
+++ b/apps/web/src/components/ui/toggle.tsx
@@ -7,13 +7,13 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "../../lib/utils";
const toggleVariants = cva(
- "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-transparent",
outline:
- "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
+ "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-9 px-2 min-w-9",