summaryrefslogtreecommitdiff
path: root/apps/web/src/components/ui/input-with-back.tsx
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2025-08-15 02:12:16 +0200
committerMaze Winther <mazewinther@gmail.com>2025-08-15 02:12:16 +0200
commit7f91c9ca00cd51bea1cd1e97eeb3c58b7e7a1bae (patch)
tree25c6cddf67e3227d99c2c28f71496851962f1f45 /apps/web/src/components/ui/input-with-back.tsx
parent5e8a489a147c148b6495a6a25fb4dd9725e6fca6 (diff)
parentc3f3345d7b1ac820b411a5adbca3172378642953 (diff)
downloadopencut-7f91c9ca00cd51bea1cd1e97eeb3c58b7e7a1bae.tar.gz
Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging
Diffstat (limited to 'apps/web/src/components/ui/input-with-back.tsx')
-rw-r--r--apps/web/src/components/ui/input-with-back.tsx86
1 files changed, 86 insertions, 0 deletions
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>
+ );
+}