diff options
| author | Maze Winther <mazewinther@gmail.com> | 2025-08-15 02:12:16 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2025-08-15 02:12:16 +0200 |
| commit | 7f91c9ca00cd51bea1cd1e97eeb3c58b7e7a1bae (patch) | |
| tree | 25c6cddf67e3227d99c2c28f71496851962f1f45 /apps/web/src/components/ui | |
| parent | 5e8a489a147c148b6495a6a25fb4dd9725e6fca6 (diff) | |
| parent | c3f3345d7b1ac820b411a5adbca3172378642953 (diff) | |
| download | opencut-7f91c9ca00cd51bea1cd1e97eeb3c58b7e7a1bae.tar.gz | |
Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging
Diffstat (limited to 'apps/web/src/components/ui')
| -rw-r--r-- | apps/web/src/components/ui/button.tsx | 2 | ||||
| -rw-r--r-- | apps/web/src/components/ui/draggable-item.tsx | 27 | ||||
| -rw-r--r-- | apps/web/src/components/ui/input-with-back.tsx | 86 | ||||
| -rw-r--r-- | apps/web/src/components/ui/input.tsx | 12 |
4 files changed, 110 insertions, 17 deletions
diff --git a/apps/web/src/components/ui/button.tsx b/apps/web/src/components/ui/button.tsx index 5558eee0..9096e415 100644 --- a/apps/web/src/components/ui/button.tsx +++ b/apps/web/src/components/ui/button.tsx @@ -18,7 +18,7 @@ const buttonVariants = cva( destructive: "bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90", outline: - "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground", + "border border-input bg-background shadow-xs hover:opacity-75 transition-opacity hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", text: "bg-transparent p-0 rounded-none opacity-100 hover:opacity-50 transition-opacity", // Instead of ghost (matches app better) diff --git a/apps/web/src/components/ui/draggable-item.tsx b/apps/web/src/components/ui/draggable-item.tsx index ff6766fd..92aa677b 100644 --- a/apps/web/src/components/ui/draggable-item.tsx +++ b/apps/web/src/components/ui/draggable-item.tsx @@ -25,6 +25,7 @@ export interface DraggableMediaItemProps { showLabel?: boolean; rounded?: boolean; variant?: "card" | "compact"; + isDraggable?: boolean; } export function DraggableMediaItem({ @@ -39,11 +40,14 @@ export function DraggableMediaItem({ showLabel = true, rounded = true, variant = "card", + isDraggable = true, }: DraggableMediaItemProps) { const [isDragging, setIsDragging] = useState(false); const [dragPosition, setDragPosition] = useState({ x: 0, y: 0 }); const dragRef = useRef<HTMLDivElement>(null); - const currentTime = usePlaybackStore((state) => state.currentTime); + const currentTime = isDraggable + ? usePlaybackStore((state) => state.currentTime) + : 0; const handleAddToTimeline = () => { onAddToTimeline?.(currentTime); @@ -100,11 +104,11 @@ export function DraggableMediaItem({ className={cn( "bg-panel-accent relative overflow-hidden", rounded && "rounded-md", - "[&::-webkit-drag-ghost]:opacity-0" // Webkit-specific ghost hiding + isDraggable && "[&::-webkit-drag-ghost]:opacity-0" // Webkit-specific ghost hiding )} - draggable={true} - onDragStart={handleDragStart} - onDragEnd={handleDragEnd} + draggable={isDraggable} + onDragStart={isDraggable ? handleDragStart : undefined} + onDragEnd={isDraggable ? handleDragEnd : undefined} > {preview} {!isDragging && ( @@ -132,12 +136,12 @@ export function DraggableMediaItem({ <div className={cn( "h-10 flex items-center gap-3 cursor-default w-full", - "[&::-webkit-drag-ghost]:opacity-0", + isDraggable && "[&::-webkit-drag-ghost]:opacity-0", className )} - draggable={true} - onDragStart={handleDragStart} - onDragEnd={handleDragEnd} + draggable={isDraggable} + onDragStart={isDraggable ? handleDragStart : undefined} + onDragEnd={isDraggable ? handleDragEnd : undefined} > <div className="w-6 h-6 flex-shrink-0 rounded overflow-hidden"> {preview} @@ -148,7 +152,8 @@ export function DraggableMediaItem({ )} {/* Custom drag preview */} - {isDragging && + {isDraggable && + isDragging && typeof document !== "undefined" && createPortal( <div @@ -194,7 +199,7 @@ function PlusButton({ <Button size="icon" className={cn( - "absolute bottom-2 right-2 size-4 bg-background text-foreground", + "absolute bottom-2 right-2 size-4 bg-background hover:bg-panel text-foreground", className )} onClick={(e) => { diff --git a/apps/web/src/components/ui/input-with-back.tsx b/apps/web/src/components/ui/input-with-back.tsx new file mode 100644 index 00000000..ea225c14 --- /dev/null +++ b/apps/web/src/components/ui/input-with-back.tsx @@ -0,0 +1,86 @@ +"use client";
+
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { ArrowLeft, Search } from "lucide-react";
+import { motion } from "motion/react";
+import { useState, useEffect } from "react";
+
+interface InputWithBackProps {
+ isExpanded: boolean;
+ setIsExpanded: (isExpanded: boolean) => void;
+ placeholder?: string;
+ value?: string;
+ onChange?: (value: string) => void;
+}
+
+export function InputWithBack({
+ isExpanded,
+ setIsExpanded,
+ placeholder = "Search anything",
+ value,
+ onChange,
+}: InputWithBackProps) {
+ const [containerRef, setContainerRef] = useState<HTMLDivElement | null>(null);
+ const [buttonOffset, setButtonOffset] = useState(-60);
+
+ const smoothTransition = {
+ duration: 0.35,
+ ease: [0.25, 0.1, 0.25, 1] as const,
+ };
+
+ useEffect(() => {
+ if (containerRef) {
+ const rect = containerRef.getBoundingClientRect();
+ setButtonOffset(-rect.left - 48);
+ }
+ }, [containerRef]);
+
+ return (
+ <div ref={setContainerRef} className="relative w-full">
+ <motion.div
+ className="absolute left-0 top-1/2 -translate-y-1/2 cursor-pointer hover:opacity-75 transition-opacity z-10"
+ initial={{
+ x: isExpanded ? 0 : buttonOffset,
+ opacity: isExpanded ? 1 : 0.5,
+ }}
+ animate={{
+ x: isExpanded ? 0 : buttonOffset,
+ opacity: isExpanded ? 1 : 0.5,
+ }}
+ transition={smoothTransition}
+ onClick={() => setIsExpanded(!isExpanded)}
+ >
+ <Button
+ variant="outline"
+ className="!size-9 rounded-full bg-panel-accent"
+ >
+ <ArrowLeft />
+ </Button>
+ </motion.div>
+ <div
+ className="relative flex-1"
+ style={{ marginLeft: "0px", paddingLeft: "0px" }}
+ >
+ <motion.div
+ className="relative"
+ initial={{
+ marginLeft: isExpanded ? 50 : 0,
+ }}
+ animate={{
+ marginLeft: isExpanded ? 50 : 0,
+ }}
+ transition={smoothTransition}
+ >
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
+ <Input
+ placeholder={placeholder}
+ className="pl-9 bg-panel-accent w-full"
+ value={value}
+ onChange={(e) => onChange?.(e.target.value)}
+ />
+ </motion.div>
+ </div>
+ </div>
+ );
+}
diff --git a/apps/web/src/components/ui/input.tsx b/apps/web/src/components/ui/input.tsx index 65c7660d..8f195a50 100644 --- a/apps/web/src/components/ui/input.tsx +++ b/apps/web/src/components/ui/input.tsx @@ -1,10 +1,12 @@ -import * as React from "react"; -import { Eye, EyeOff, X } from "lucide-react"; +"use client"; +import { Eye, EyeOff, X } from "lucide-react"; import { cn } from "../../lib/utils"; import { Button } from "./button"; +import { forwardRef, ComponentProps } from "react"; +import { useState } from "react"; -interface InputProps extends React.ComponentProps<"input"> { +interface InputProps extends ComponentProps<"input"> { showPassword?: boolean; onShowPasswordChange?: (show: boolean) => void; showClearIcon?: boolean; @@ -12,7 +14,7 @@ interface InputProps extends React.ComponentProps<"input"> { containerClassName?: string; } -const Input = React.forwardRef<HTMLInputElement, InputProps>( +const Input = forwardRef<HTMLInputElement, InputProps>( ( { className, @@ -29,7 +31,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>( }, ref ) => { - const [isFocused, setIsFocused] = React.useState(false); + const [isFocused, setIsFocused] = useState(false); const isPassword = type === "password"; const showPasswordToggle = isPassword && onShowPasswordChange; |
