From c3f3345d7b1ac820b411a5adbca3172378642953 Mon Sep 17 00:00:00 2001 From: enkei64 Date: Fri, 15 Aug 2025 10:11:21 +1000 Subject: feat: stickers panel (#539) * Stickers panel base complete * Improve dark mode stickers background for visibility * Prevent stickers from being too small or too big * Improve UI, added credit for Iconify API * Allow manually loading more collections * Add a maximum width of 200px so stickers aren't too big * cleanup * style: hover state of button * refactor: input component * fix: mark input component as client * so much stuff --------- Co-authored-by: Maze Winther --- apps/web/src/components/ui/button.tsx | 2 +- apps/web/src/components/ui/draggable-item.tsx | 27 ++++---- apps/web/src/components/ui/input-with-back.tsx | 86 ++++++++++++++++++++++++++ apps/web/src/components/ui/input.tsx | 12 ++-- 4 files changed, 110 insertions(+), 17 deletions(-) create mode 100644 apps/web/src/components/ui/input-with-back.tsx (limited to 'apps/web/src/components/ui') 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(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({
{preview} @@ -148,7 +152,8 @@ export function DraggableMediaItem({ )} {/* Custom drag preview */} - {isDragging && + {isDraggable && + isDragging && typeof document !== "undefined" && createPortal(
{ 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(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 ( +
+ setIsExpanded(!isExpanded)} + > + + +
+ + + onChange?.(e.target.value)} + /> + +
+
+ ); +} 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( +const Input = forwardRef( ( { className, @@ -29,7 +31,7 @@ const Input = React.forwardRef( }, ref ) => { - const [isFocused, setIsFocused] = React.useState(false); + const [isFocused, setIsFocused] = useState(false); const isPassword = type === "password"; const showPasswordToggle = isPassword && onShowPasswordChange; -- cgit v1.3.1