{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "screen-frame",
  "title": "Screen Frame",
  "description": "A window, a phone or a plain hairline around a screenshot or live interface, with the real address in the bar and no decorative traffic lights.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/screen-frame.tsx",
      "content": "import type * as React from \"react\"\nimport { useRef, useState } from \"react\"\nimport { motion, useInView, useReducedMotion } from \"motion/react\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\nimport { SPRING_DEFAULT } from \"@/registry/miseui/lib/motion\"\n\ntype ScreenFrameVariant = \"browser\" | \"phone\" | \"plain\"\ntype ScreenFrameEffect = \"settle\"\n\nconst SETTLE_TRANSITION = { ...SPRING_DEFAULT, duration: 0.8 }\nconst VIEW_MARGIN = \"0px 0px -10% 0px\"\n/** Black, not the foreground colour: a foreground shadow in dark mode would glow. */\nconst SHADOW = \"shadow-[0_24px_48px_-24px_rgb(0_0_0/0.45)]\"\n\nconst FRAME_CLASS: Record<ScreenFrameVariant, string> = {\n  browser: cn(\"overflow-hidden rounded-lg border bg-card\", SHADOW),\n  phone: cn(\"mx-auto w-full max-w-60 rounded-[2rem] border bg-card p-2\", SHADOW),\n  plain: cn(\"overflow-hidden rounded-md ring-1 ring-border\", SHADOW),\n}\n\nconst SCREEN_CLASS: Record<ScreenFrameVariant, string> = {\n  browser: \"aspect-video\",\n  phone: \"aspect-[9/19.5] overflow-hidden rounded-[1.5rem]\",\n  plain: \"aspect-video\",\n}\n\n/** What people read in an address bar: the host and path, without the protocol. */\nfunction displayAddress(url: string) {\n  return url.replace(/^[a-z][a-z0-9+.-]*:\\/\\//i, \"\").replace(/\\/$/, \"\")\n}\n\nfunction ScreenImage({ src, alt, eager }: { src: string; alt: string; eager: boolean }) {\n  const [failed, setFailed] = useState(false)\n  if (failed) {\n    return <p data-slot=\"screen-frame-fallback\" className=\"grid size-full place-items-center p-4 text-center text-[13px] leading-5 text-muted-foreground\">{alt}</p>\n  }\n  return <img src={src} alt={alt} loading={eager ? \"eager\" : \"lazy\"} decoding=\"async\" className=\"block size-full object-cover\" onError={() => setFailed(true)} />\n}\n\ninterface ChromeProps {\n  variant: ScreenFrameVariant\n  url?: string\n  screenClassName?: string\n  children: React.ReactNode\n}\n\nfunction FrameChrome({ variant, url, screenClassName, children }: ChromeProps) {\n  return (\n    <>\n      {/* The real address and no decorative traffic lights: they say nothing and date a screenshot. */}\n      {variant === \"browser\" && url ? (\n        <p data-slot=\"screen-frame-address\" className=\"truncate border-b px-3 py-1 font-mono text-xs leading-5 text-muted-foreground\">\n          {displayAddress(url)}\n        </p>\n      ) : null}\n      <div data-slot=\"screen-frame-screen\" className={cn(\"min-w-0 bg-background\", SCREEN_CLASS[variant], screenClassName)}>\n        {children}\n      </div>\n    </>\n  )\n}\n\ntype ScreenFrameProps = Omit<React.ComponentProps<\"figure\">, \"children\"> & {\n  /** A screenshot. Leave it out and pass `children` to frame live interface. */\n  src?: string\n  alt?: string\n  /** Shown in the `browser` bar. */\n  url?: string\n  /** A line under the frame. */\n  caption?: React.ReactNode\n  /** Loads straight away, for the first screen of a page. */\n  eager?: boolean\n  /** Changes the screen, such as its aspect ratio. */\n  screenClassName?: string\n  /** `browser` a window with its address, `phone` a slim device, `plain` only a hairline and a shadow. */\n  variant?: ScreenFrameVariant\n  /** `settle` lays the frame flat as it comes into view, from a slight tilt. */\n  effect?: ScreenFrameEffect\n  children?: React.ReactNode\n}\n\nfunction ScreenFrame({ src, alt = \"\", url, caption, eager = false, screenClassName, variant = \"browser\", effect, className, children, ...props }: ScreenFrameProps) {\n  const reduceMotion = useReducedMotion() ?? false\n  const ref = useRef<HTMLDivElement>(null)\n  const seen = useInView(ref, { once: true, margin: VIEW_MARGIN })\n  const settling = effect === \"settle\" && !reduceMotion\n  const tilted = { rotateX: 10, scale: 0.97, transformPerspective: 1200 }\n  const flat = { rotateX: 0, scale: 1, transformPerspective: 1200 }\n  const screen = children ?? (src ? <ScreenImage src={src} alt={alt} eager={eager} /> : null)\n  return (\n    <figure data-slot=\"screen-frame\" data-variant={variant} data-effect={effect} className={cn(\"grid min-w-0 grid-cols-[minmax(0,1fr)] gap-2\", className)} {...props}>\n      <motion.div\n        ref={ref}\n        data-slot=\"screen-frame-body\"\n        className={FRAME_CLASS[variant]}\n        initial={settling ? tilted : false}\n        animate={settling ? (seen ? flat : tilted) : undefined}\n        transition={SETTLE_TRANSITION}\n      >\n        <FrameChrome variant={variant} url={url} screenClassName={screenClassName}>\n          {screen}\n        </FrameChrome>\n      </motion.div>\n      {caption ? <figcaption className=\"text-[13px] leading-5 text-muted-foreground\">{caption}</figcaption> : null}\n    </figure>\n  )\n}\n\nexport { ScreenFrame, displayAddress, type ScreenFrameProps, type ScreenFrameVariant, type ScreenFrameEffect }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "display"
  ],
  "type": "registry:ui"
}