Saltar al contenido
ssntl/ui

Frame

Un marco de proporción fija para que todas las imágenes de un conjunto se alineen, recortadas o contenidas.

  • Wall clockQ 340
  • Ceramic cupQ 95
  • Day backpackQ 520

Instalación

$ pnpm dlx shadcn@latest add @ssntl/frame

Uso

tsx
import { Frame } from "@/components/ui/frame"

<Frame ratio="landscape">
  <img src="/taza.webp" srcSet="/taza.webp 1x, /taza@2x.webp 2x" alt="Taza de cerámica" width={640} height={480} />
</Frame>
<Frame fit="contain"><img src="/reloj.webp" alt="" /></Frame>

Reglas

Lo que este componente espera de ti, tal como se instala en components/ui/frame.md junto al código. El archivo completo agrega cuándo usarlo, teclado, los fundamentos de UI que aplica y cada regla compartida (en inglés).

components/ui/frame.md
## Reglas de Frame

- **Marcos consistentes.** Cada imagen de un conjunto toma la misma proporción (square, portrait, landscape, wide, video) y se recorta para encajar.
- **Calidad de foto.** Sirve el doble del tamaño mostrado (`srcSet` 2x o sizes) y nunca estires una imagen más allá de su original; define width y height para que nada salte.
- `fit="contain"` para objetos recortados sobre fondo transparente: se muestran completos, con espacio alrededor.
- Un contorno de 1px al 10% mantiene el borde de una foto clara sobre cualquier superficie.
Abrir el archivo de reglas completo (se abre en otra pestaña)

Código

components/ui/frame.tsx
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

// A fixed-ratio frame for images in a set: products, galleries, covers.
// - Consistent frames: every image in a set takes the same ratio and is
//   cropped to fit (object-cover), so a row never goes ragged.
// - Photo quality: give the image twice its displayed size (srcSet 2x, or
//   sizes with a width list) and never stretch it past its source.
// - A 1px pure black (white in dark) outline at 10% keeps a light photo's
//   edge on any surface.
const frameVariants = cva(
  "relative overflow-hidden bg-muted outline -outline-offset-1 outline-black/10 dark:outline-white/10 [&_img]:size-full [&_img]:object-cover",
  {
    variants: {
      ratio: {
        square: "aspect-square",
        portrait: "aspect-3/4",
        landscape: "aspect-4/3",
        wide: "aspect-16/10",
        video: "aspect-video",
      },
      fit: {
        cover: "",
        // Cut-out objects on a transparent ground: shown whole, never cropped.
        contain: "[&_img]:object-contain [&_img]:p-4",
      },
    },
    defaultVariants: { ratio: "square", fit: "cover" },
  }
)

function Frame({
  ratio,
  fit,
  className,
  ...props
}: React.ComponentProps<"div"> & VariantProps<typeof frameVariants>) {
  return (
    <div
      data-slot="frame"
      className={cn("rounded-md", frameVariants({ ratio, fit, className }))}
      {...props}
    />
  )
}

export { Frame, frameVariants }