Shimmer

A flashy, animated text input with Mystral-style shimmer that cycles through gold-to-red colors while showing dynamic “thinking → generating → done” states inspired by Mistral Ai.

Loading demo...

default.tsx
"use client";
 
import React, { useMemo, type JSX } from "react";
import { AnimatePresence, motion } from "motion/react";
import { cn } from "@/lib/utils";
 
export type TextShimmerProps = {
  children: React.ReactNode;
  as?: React.ElementType;
  className?: string;
  duration?: number;
  spread?: number;
};
 
function TextShimmerComponent({
  children,
  as: Component = "span",
  className,
  duration = 2,
  spread = 2,
}: TextShimmerProps) {
  const MotionComponent = motion.create(
    Component as keyof JSX.IntrinsicElements,
  );
 
  const dynamicSpread = useMemo(() => {
    const length =
      typeof children === "string"
        ? children.length
        : React.Children.count(children);
    return length * spread;
  }, [children, spread]);
 
  return (
    <MotionComponent
      className={cn(
        "relative inline-block bg-size-[300%_100%,auto] bg-clip-text",
        "text-transparent [--base-color:#FFD700]",
        "[background-repeat:no-repeat,padding-box]",
        "[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),#FFD700,#FFAF00,#FF8205,#FA500F,#E10500,#0000_calc(50%+var(--spread)))]",
        className,
      )}
      key={String(children)}
      initial={{ backgroundPosition: "100% center" }}
      animate={{ backgroundPosition: "0% center" }}
      transition={{
        repeat: Infinity,
        duration,
        ease: "linear",
      }}
      style={
        {
          "--spread": `${dynamicSpread}px`,
          backgroundImage:
            "var(--bg), linear-gradient(var(--base-color), var(--base-color))",
        } as React.CSSProperties
      }
    >
      {children}
    </MotionComponent>
  );
}
 
const TextShimmer = React.memo(TextShimmerComponent);
 
type State = "thinking" | "generating" | "done";
 
const states: Record<State, string> = {
  thinking: "Thinking...",
  generating: "Generating...",
  done: "Done",
};
 
const MystralShimmer = () => {
  const [value, setValue] = React.useState("");
  const [state, setState] = React.useState<State>("thinking");
  const [loading, setLoading] = React.useState(false);
 
  const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setLoading(true);
    setValue("");
    setState("thinking");
 
    setTimeout(() => {
      setState("generating");
 
      setTimeout(() => {
        setState("done");
        setLoading(false);
      }, 4000);
    }, 4000);
  };
  // mystral shimmer colors FFD700 FFAF00 FF8205 FA500F E10500
  return (
    <div className="size-full bg-background flex items-start justify-end overflow-visible">
      <div className="border rounded-bl-lg size-[80%] border-r-0 bg-muted border-t-0 p-2 pr-0 flex flex-col justify-end">
        <form onSubmit={onSubmit} className="h-10 w-full relative flex">
          <div className="size-full border-r-0 border overflow-hidden rounded-l-md flex">
            <input
              type="text"
              value={value}
              onChange={(e) => setValue(e.target.value)}
              className="flex-1 px-4 bg-background z-10 outline-none text-muted-foreground disabled:cursor-not-allowed"
              placeholder="> Ask anything..."
              autoFocus
              disabled={loading}
            />
 
            <AnimatePresence initial={false}>
              {loading && (
                <motion.div
                  className="absolute left-4 bottom-full overflow-visible"
                  initial={{ opacity: 0, y: 8 }}
                  animate={{ opacity: 1, y: -8 }}
                  exit={{ opacity: 0, y: 4 }}
                  transition={{ duration: 0.3 }}
                >
                  <AnimatePresence mode="popLayout">
                    <motion.div
                      key={state}
                      initial={{ opacity: 0, y: 6 }}
                      animate={{ opacity: 1, y: 0 }}
                      exit={{ opacity: 0, y: -6 }}
                      transition={{ duration: 0.25, ease: "easeOut" }}
                    >
                      <TextShimmer>
                        {states[state].split("").map((char, index) => (
                          <motion.span
                            key={`${state}-${index}`}
                            initial={{ opacity: 0, y: 6 }}
                            animate={{ opacity: 1, y: 0 }}
                            exit={{ opacity: 0, y: -6 }}
                            transition={{
                              duration: 0.2,
                              delay: index * 0.02,
                            }}
                            className="inline-block"
                          >
                            {char}
                          </motion.span>
                        ))}
                      </TextShimmer>
                    </motion.div>
                  </AnimatePresence>
                </motion.div>
              )}
            </AnimatePresence>
          </div>
        </form>
      </div>
    </div>
  );
};
 
export default MystralShimmer;