Todos os componentes

Image Metadata Preview

Image Metadata Preview é um componente de media para React e Tailwind CSS, da biblioteca SmoothUI, com licença MIT. Copia e cola no teu projeto.

SmoothUI MIT media

O que é

Image Metadata Preview é um componente de media para React e Tailwind CSS, da biblioteca SmoothUI, com licença MIT. Copia e cola no teu projeto.

Como usar

Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.

Abrir no catálogo

Cinco cópias grátis por mês. Sem cartão.

Precisa de

npm i motion lucide-react react-use-measure

Licença

Este componente vem de SmoothUI e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.

Image Metadata Preview

index.tsx
"use client";

import { ChevronUp, CircleX, Share } from "lucide-react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { useEffect, useState } from "react";
import useMeasure from "react-use-measure";

export interface ImageMetadata {
  by: string;
  created: string;
  source: string;
  updated: string;
}

export interface ImageMetadataPreviewProps {
  alt?: string;
  description?: string;
  filename?: string;
  imageSrc: string;
  metadata: ImageMetadata;
  onShare?: () => void;
}

export default function ImageMetadataPreview({
  imageSrc,
  alt = "Image preview",
  filename = "screenshot.png",
  description = "No description",
  metadata,
  onShare,
}: ImageMetadataPreviewProps) {
  const [openInfo, setopenInfo] = useState(false);
  const [isHoverDevice, setIsHoverDevice] = useState(false);
  const [elementRef, bounds] = useMeasure();
  const shouldReduceMotion = useReducedMotion();

  useEffect(() => {
    const mediaQuery = window.matchMedia("(hover: hover) and (pointer: fine)");
    setIsHoverDevice(mediaQuery.matches);

    const handleChange = (e: MediaQueryListEvent) => {
      setIsHoverDevice(e.matches);
    };

    mediaQuery.addEventListener("change", handleChange);
    return () => mediaQuery.removeEventListener("change", handleChange);
  }, []);

  const handleClickOpen = () => {
    setopenInfo((b) => !b);
  };

  const handleClickClose = () => {
    setopenInfo((b) => !b);
  };

  return (
    <div className="absolute bottom-10 flex flex-col items-center justify-center gap-4">
      <motion.div
        animate={shouldReduceMotion ? {} : { y: -bounds.height }}
        className="pointer-events-none overflow-hidden rounded-xl"
        transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.25 }}
      >
        <img
          alt={alt}
          draggable={false}
          height={437}
          src={imageSrc}
          width={300}
        />
      </motion.div>

      <div className="relative flex w-full flex-col items-center gap-4">
        <div className="relative flex w-full flex-row items-center justify-center gap-4">
          <button
            aria-label="Share"
            className={`min-h-[44px] min-w-[44px] rounded-full border bg-background p-3 transition focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${
              isHoverDevice ? "hover:bg-muted" : ""
            }`}
            disabled={!onShare}
            onClick={onShare}
            type="button"
          >
            <Share aria-hidden="true" size={16} />
          </button>
          <button
            aria-label="Connect"
            className="min-h-[44px] cursor-not-allowed rounded-full border bg-background px-4 py-3 text-sm transition focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50"
            disabled
            type="button"
          >
            Connect
          </button>
          <AnimatePresence>
            {openInfo ? null : (
              <motion.button
                animate={
                  shouldReduceMotion
                    ? { opacity: 1 }
                    : { filter: "blur(0px)", opacity: 1 }
                }
                aria-label="Open Metadata Preview"
                className={`min-h-[44px] min-w-[44px] border bg-background p-3 shadow-xs transition focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${
                  isHoverDevice ? "hover:bg-muted" : ""
                }`}
                initial={
                  shouldReduceMotion
                    ? { opacity: 1 }
                    : { filter: "blur(4px)", opacity: 0 }
                }
                onClick={handleClickOpen}
                style={{ borderRadius: 100 }}
                transition={
                  shouldReduceMotion ? { duration: 0 } : { duration: 0.2 }
                }
              >
                <ChevronUp aria-hidden="true" size={16} />
              </motion.button>
            )}
          </AnimatePresence>
        </div>
        <AnimatePresence>
          {openInfo ? (
            <motion.div
              animate={
                shouldReduceMotion
                  ? { opacity: 1 }
                  : { filter: "blur(0px)", opacity: 1 }
              }
              className="absolute bottom-0 w-full cursor-pointer gap-4 border bg-background p-5 shadow-xs"
              initial={
                shouldReduceMotion
                  ? { opacity: 1 }
                  : { filter: "blur(4px)", opacity: 0 }
              }
              onClick={handleClickClose}
              style={{ borderRadius: 20 }}
              transition={
                shouldReduceMotion
                  ? { duration: 0 }
                  : { bounce: 0, duration: 0.25, type: "spring" as const }
              }
            >
              <div className="flex flex-col items-start" ref={elementRef}>
                <div className="flex w-full flex-row items-start justify-between gap-4">
                  <div>
                    <p className="text-foreground">{filename}</p>
                    <p className="text-primary-foreground">{description}</p>
                  </div>

                  <button
                    aria-label="Close metadata preview"
                    className={`flex min-h-[44px] min-w-[44px] items-center justify-center rounded p-2 transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${
                      isHoverDevice ? "hover:bg-muted" : ""
                    }`}
                    onClick={(e) => {
                      e.stopPropagation();
                      handleClickClose();
                    }}
                    type="button"
                  >
                    <CircleX aria-hidden="true" size={16} />
                  </button>
                </div>
                <table className="flex w-full flex-col items-center gap-4 text-foreground">
                  <tbody className="w-full">
                    <tr className="flex w-full flex-row items-center gap-4">
                      <td className="w-1/2">Created</td>
                      <td className="w-1/2 text-primary-foreground">
                        {metadata.created}
                      </td>
                    </tr>
                    <tr className="flex w-full flex-row items-center gap-4">
                      <td className="w-1/2">Updated</td>
                      <td className="w-1/2 text-primary-foreground">
                        {metadata.updated}
                      </td>
                    </tr>
                    <tr className="flex w-full flex-row items-center gap-4">
                      <td className="w-1/2">By</td>
                      <td className="w-1/2">{metadata.by}</td>
                    </tr>
                    <tr className="flex w-full flex-row items-center gap-4">
                      <td className="w-1/2">Source</td>
                      <td className="w-1/2 truncate">{metadata.source}</td>
                    </tr>
                  </tbody>
                </table>
              </div>
            </motion.div>
          ) : null}
        </AnimatePresence>
      </div>
    </div>
  );
}

Componentes parecidos