Discover Button

discover-button.tsx
"use client";

import { useState } from "react";
import { motion } from "motion/react";

import {
  Search01Icon,
  FavouriteIcon,
  Fire02Icon,
  MultiplicationSignIcon,
} from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";

// Change Here
const TABS = [
  {
    id: "popular",
    label: "Popular",
    icon: Fire02Icon,
    color: "text-red-500",
    fill: "fill-red-500",
    bg: "bg-red-50",
  },
  {
    id: "favorites",
    label: "Favorites",
    icon: FavouriteIcon,
    color: "text-gray-900",
    fill: "fill-gray-900",
    bg: "bg-gray-100",
  },
] as const;

export default function DiscoverButton() {
  const [activeTab, setActiveTab] = useState<(typeof TABS)[number]["id"]>(
    TABS[0].id
  );
  const [isSearchExpanded, setIsSearchExpanded] = useState(false);

  return (
    <div className="flex items-center gap-3 p-2 h-full ">
      {/* Search Button / Input */}
      <motion.div
        layout
        transition={{
          type: "spring",
          damping: 20,
          stiffness: 230,
          mass: 1.2,
        }}
        onClick={() => !isSearchExpanded && setIsSearchExpanded(true)}
        className={`flex items-center bg-white rounded-[3rem] shadow-lg cursor-pointer h-[60px] overflow-hidden relative px-[1.125rem]     ${
          isSearchExpanded ? "flex-1" : ""
        }`}
      >
        <div className="shrink-0">
          <HugeiconsIcon
            icon={Search01Icon}
            className="w-6 h-6 text-gray-800"
          />
        </div>

        <motion.div
          initial={false}
          animate={{
            width: isSearchExpanded ? "auto" : "0px",
            opacity: isSearchExpanded ? 1 : 0,
            filter: isSearchExpanded ? "blur(0px)" : "blur(4px)",
            marginLeft: isSearchExpanded ? "12px" : "0px",
          }}
          transition={{
            type: "spring",
            damping: 20,
            stiffness: 230,
            mass: 1.2,
          }}
          className="overflow-hidden -mb-0.5 flex items-center"
        >
          <input
            type="text"
            placeholder="Search"
            className="border-0 outline-none bg-transparent text-lg focus-visible:ring-0 focus-visible:ring-offset-0 w-full"
            onClick={(e) => e.stopPropagation()}
          />
        </motion.div>
      </motion.div>

      {/* Tab Container / Close Button */}
      <motion.div
        layout
        transition={{
          type: "spring",
          damping: 20,
          stiffness: 230,
          mass: 1.2,
        }}
        className={`flex items-center bg-white rounded-[3rem] shadow-lg h-[60px] overflow-hidden relative `}
      >
        {/* Wrapper to control clipping - clips from right side */}
        <motion.div
          initial={false}
          animate={{
            width: isSearchExpanded ? "60px" : "auto",
          }}
          transition={{
            type: "spring",
            damping: 20,
            stiffness: 230,
            mass: 1.2,
          }}
          className="overflow-hidden relative h-full flex items-center"
        >
          {/* Tabs Group - stays in place, gets clipped */}
          <motion.div
            initial={false}
            animate={{
              opacity: isSearchExpanded ? 0 : 1,
              filter: isSearchExpanded ? "blur(4px)" : "blur(0px)",
              width: "auto",
            }}
            transition={{
              duration: 0.2,
            }}
            className={`flex items-center  whitespace-nowrap `}
          >
            <div className="flex items-center gap-2 px-[6px]">
              {TABS.map((tab) => (
                <button
                  key={tab.id}
                  onClick={() => setActiveTab(tab.id)}
                  className={`flex items-center gap-2 px-6 py-3 rounded-[3rem] transition-colors relative ${
                    activeTab === tab.id ? tab.color : "text-gray-700"
                  }`}
                >
                  {activeTab === tab.id && (
                    <motion.span
                      layoutId="bubble"
                      className={`absolute inset-0 z-0 ${tab.bg}`}
                      style={{ borderRadius: 9999 }}
                      transition={{
                        type: "spring",
                        bounce: 0.19,
                        duration: 0.4,
                      }}
                    />
                  )}
                  <HugeiconsIcon
                    icon={tab.icon}
                    className={`w-5 h-5 relative z-10 ${
                      activeTab === tab.id ? tab.fill : ""
                    }`}
                  />
                  <span className="font-semibold font-mono uppercase relative z-10">
                    {tab.label}
                  </span>
                </button>
              ))}
            </div>
          </motion.div>

          {/* Close Button - positioned absolutely on top */}
          <motion.div
            initial={false}
            animate={{
              opacity: isSearchExpanded ? 1 : 0,
              filter: isSearchExpanded ? "blur(0px)" : "blur(4px)",
            }}
            transition={{
              duration: 0.2,
            }}
            className="absolute inset-0 flex items-center justify-center"
            style={{ pointerEvents: isSearchExpanded ? "auto" : "none" }}
          >
            <button
              onClick={() => setIsSearchExpanded(false)}
              className="shrink-0 cursor-pointer"
            >
              <HugeiconsIcon
                icon={MultiplicationSignIcon}
                className="w-6 h-6 text-gray-800"
              />
            </button>
          </motion.div>
        </motion.div>
      </motion.div>
    </div>
  );
}

Installation

npx shadcn@latest add "https://uselayouts.com/r/discover-button.json"

Install dependencies

npm install motion @hugeicons/react @hugeicons/core-free-icons

Copy the code

Copy the code from the Code tab above into components/discover-button.tsx.

Update imports

Update the imports to match your project structure.

Usage

Customizing Content

Adjust the navigation items by modifying the TABS array at the top of the file:

// Change Here
const TABS = [
  { id: "popular", label: "Popular", icon: Fire02Icon, color: "text-red-500", ... },
  { id: "favorites", label: "Favorites", icon: FavouriteIcon, color: "text-gray-900", ... },
  // ...
];
import DiscoverButton from "@/components/discover-button";

export default function Page() {
  return <DiscoverButton />;
}

Features

  • Expandable Search: Button expands into a fully functional search bar on click.
  • Category Filters: Integrated tab navigation for quick category filtering.
  • Compact Design: efficient use of screen space by hiding complex controls until needed.
  • Focus Management: Automatically handles focus states for search input.
  • Keyboard Accessible: Fully navigable via keyboard.