From e2094eb4cb3a8593d03e6964346c3025ef37b06f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 03:11:19 +0000 Subject: [PATCH] Palette: Add clear button to URL input & remove unused component --- .Jules/palette.md | 10 ++ components/url-input-with-branding.tsx | 145 ------------------------- components/url-input.tsx | 23 +++- 3 files changed, 31 insertions(+), 147 deletions(-) create mode 100644 .Jules/palette.md delete mode 100644 components/url-input-with-branding.tsx diff --git a/.Jules/palette.md b/.Jules/palette.md new file mode 100644 index 0000000..a515161 --- /dev/null +++ b/.Jules/palette.md @@ -0,0 +1,10 @@ +## 2025-05-23 - [Input Clear Button Pattern] +**Learning:** Users often struggle to clear long inputs like URLs. Adding a conditional "Clear" button (X icon) that appears when text is present significantly improves usability on both desktop and mobile. +**Action:** When creating primary text inputs (search, URL), always implement a clear button that: +1. Only appears when input has value +2. Clears the state +3. Returns focus to the input element + +## 2025-05-23 - [Component Cleanup] +**Learning:** Found an unused component `UrlInputWithBranding` that was a near-duplicate of `UrlInput`. +**Action:** Before modifying a component, check for duplicates or "with branding" variants that might be unused. remove them to keep the codebase clean. diff --git a/components/url-input-with-branding.tsx b/components/url-input-with-branding.tsx deleted file mode 100644 index 73e89a8..0000000 --- a/components/url-input-with-branding.tsx +++ /dev/null @@ -1,145 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { Loader2, ArrowUp, Link as LinkIcon } from "lucide-react"; -import Image from "next/image"; -import Link from "next/link"; -import { extractVideoId } from "@/lib/utils"; -import { cn } from "@/lib/utils"; -import { Button } from "@/components/ui/button"; -import { Card } from "@/components/ui/card"; -import { ModeSelector } from "@/components/mode-selector"; -import { isGrokProviderOnClient } from "@/lib/ai-providers/client-config"; -import type { TopicGenerationMode } from "@/lib/types"; - -interface UrlInputWithBrandingProps { - onSubmit: (url: string) => void; - isLoading?: boolean; - initialUrl?: string; - mode?: TopicGenerationMode; - onModeChange?: (mode: TopicGenerationMode) => void; -} - -export function UrlInputWithBranding({ onSubmit, isLoading = false, initialUrl, mode, onModeChange }: UrlInputWithBrandingProps) { - const [url, setUrl] = useState(() => initialUrl ?? ""); - const [error, setError] = useState(""); - const [isFocused, setIsFocused] = useState(false); - const forceSmartMode = isGrokProviderOnClient(); - const showModeSelector = - !forceSmartMode && typeof onModeChange === "function"; - const modeValue: TopicGenerationMode = forceSmartMode - ? "smart" - : mode ?? "fast"; - - useEffect(() => { - if (initialUrl === undefined) return; - setUrl((current) => (current === initialUrl ? current : initialUrl)); - }, [initialUrl]); - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - setError(""); - - if (!url.trim()) { - setError("Please enter a YouTube URL"); - return; - } - - const videoId = extractVideoId(url); - if (!videoId) { - setError("Please enter a valid YouTube URL"); - return; - } - - onSubmit(url); - }; - - return ( -
-
- - {/* Top row: Branding + Input field only */} -
- {/* Left: LongCut Logo and Text */} - - LongCut logo -

LongCut

- - - {/* Vertical Separator */} -
- - {/* Middle: Input Field */} -
-
- -
- setUrl(e.target.value)} - onFocus={() => { - setIsFocused(true); - }} - onBlur={() => { - setIsFocused(false); - }} - placeholder="Paste Youtube URL link here..." - aria-label="YouTube URL" - aria-invalid={!!error} - aria-describedby={error ? "url-error" : undefined} - className="flex-1 border-0 bg-transparent text-[14px] text-[#989999] placeholder:text-[#989999] focus:outline-none min-w-0" - /> -
- - - {/* Bottom row: Mode selector (left) and submit button (right) */} -
- {showModeSelector && ( - - )} - -
- - {error && ( - - )} -
-
- ); -} diff --git a/components/url-input.tsx b/components/url-input.tsx index 616b6d1..8403749 100644 --- a/components/url-input.tsx +++ b/components/url-input.tsx @@ -1,7 +1,7 @@ "use client"; -import { useState, useEffect } from "react"; -import { Loader2, ArrowUp, Link, Sparkles } from "lucide-react"; +import { useState, useEffect, useRef } from "react"; +import { Loader2, ArrowUp, Link, Sparkles, X } from "lucide-react"; import { extractVideoId } from "@/lib/utils"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; @@ -31,6 +31,7 @@ export function UrlInput({ const [error, setError] = useState(""); const [isFocused, setIsFocused] = useState(false); const [isValidUrl, setIsValidUrl] = useState(false); + const inputRef = useRef(null); const forceSmartMode = isGrokProviderOnClient(); const showModeSelector = !forceSmartMode && typeof onModeChange === "function"; @@ -68,6 +69,13 @@ export function UrlInput({ onSubmit(url); }; + const handleClear = () => { + setUrl(""); + setIsValidUrl(false); + setError(""); + inputRef.current?.focus(); + }; + return (
@@ -84,6 +92,7 @@ export function UrlInput({
setUrl(e.target.value)} @@ -96,6 +105,16 @@ export function UrlInput({ className="flex-1 border-0 bg-transparent text-[14px] text-[#989999] placeholder:text-[#989999] focus:outline-none" disabled={isLoading} /> + {url && !isLoading && ( + + )}
{/* Bottom row: Mode selector (left) and actions (right) */}