Domphy

safari

A Device Mocks block/component from Magic UI — clean-room reimplemented for Domphy (see methodology). Call safari() with no arguments for a working demo, or edit the code below live.

Implementation notes

Full DOM/CSS implementation (flex column: toolbar + screen), not an SVG frame — chosen to keep typography on real DOM text nodes (small() patch for the address-bar URL) and to sidestep foreignObject entirely, in the same spirit as the spec's own note about avoiding SVG-masked video on Safari/iOS. Aspect ratio locked to 1203/753 via CSS aspect-ratio at width% (wrapper-driven sizing, per spec). Traffic-light dots use the same 'fill glyph, not backgroundColor' idiom already established by this package's terminal.ts. 'simple' mode drops the traffic lights but keeps the address-bar pill (docs text was ambiguous on exactly what 'simple' strips — implementer judgment call, noted in code comments). Exact toolbar-gray/border hex values were low-confidence per the research note, so approximated with themed edge-anchor tones (shift-0/shift-2/shift-4) instead of literal colors — passes @domphy/doctor with zero diagnostics (verified via direct diagnose() call against 4 prop variants).

Status: ported · Reference: Magic UI original

// magicui "Safari" — clean-room reimplementation from the public
// behavior/visual spec only (no upstream source viewed or copied). A framed
// browser-window mockup styled like macOS Safari, used to showcase a
// screenshot, image, or video inside a realistic browser chrome. Purely a
// static presentational frame — it just contains whatever media is passed in.

import type { DomphyElement, Listener, StyleObject } from "@domphy/core";
import { small } from "@domphy/ui";
import { type ThemeColor, themeColor, themeSpacing } from "@domphy/theme";

export type SafariMode = "default" | "simple";

export interface SafariProps {
  /** URL text shown centered in the pill-shaped address bar. Defaults to `"domphy.com"`. */
  url?: string;
  /** Static screenshot displayed in the screen area. */
  imageSrc?: string;
  /** Video displayed in the screen area, rendered as a DOM overlay (not an SVG mask) to
   * avoid a known Safari/iOS video-clipping bug when video sits inside a masked SVG tree. */
  videoSrc?: string;
  /** "default" shows the full toolbar (traffic lights + address bar); "simple" strips it
   * down to just the address bar. Defaults to "default". */
  mode?: SafariMode;
  style?: StyleObject;
}

const TRAFFIC_LIGHTS: Array<{ key: string; color: ThemeColor }> = [
  { key: "close", color: "danger" },
  { key: "minimize", color: "warning" },
  { key: "zoom", color: "success" },
];

// A solid-filled circular glyph, not a themed "surface" — painted via `fill:
// currentColor` + a fixed-shift `color` (same idiom as icon()/badge()'s own
// fixed-shift `color`) rather than `backgroundColor`, so it reads as a vivid
// traffic-light dot without tripping the tone-background-inherit surface rule.
function trafficLightDot(color: ThemeColor, key: string): DomphyElement<"span"> {
  return {
    span: [
      {
        svg: [{ circle: null, cx: "12", cy: "12", r: "12" }],
        viewBox: "0 0 24 24",
        fill: "currentColor",
        role: "img",
        ariaHidden: "true",
        style: { width: "100%", height: "100%" },
      } as DomphyElement<"svg">,
    ],
    _key: key,
    style: {
      display: "inline-block",
      width: themeSpacing(3),
      height: themeSpacing(3),
      color: (listener: Listener) => themeColor(listener, "shift-9", color),
    },
  };
}

/** The screen-area media layer: a video overlay wins over a static image; renders nothing
 * (bare frame) when neither is supplied. */
function screenMedia(imageSrc: string | undefined, videoSrc: string | undefined, label: string): DomphyElement | null {
  if (videoSrc) {
    return {
      video: null,
      src: videoSrc,
      autoPlay: true,
      loop: true,
      muted: true,
      playsInline: true,
      "aria-label": label,
      style: {
        position: "absolute",
        inset: 0,
        width: "100%",
        height: "100%",
        display: "block",
        objectFit: "cover",
      },
    } as DomphyElement;
  }
  if (imageSrc) {
    return {
      img: null,
      src: imageSrc,
      alt: label,
      style: {
        position: "absolute",
        inset: 0,
        width: "100%",
        height: "100%",
        display: "block",
        objectFit: "cover",
      },
    } as DomphyElement;
  }
  return null;
}

/**
 * A macOS-Safari-styled browser-window frame (fixed 1203:753 aspect ratio) that shows a
 * screenshot or video inside its screen area. Static presentational mockup — no built-in
 * interactivity. Call with no arguments for a working demo (empty frame, "domphy.com" in
 * the address bar).
 */
function safari(props: SafariProps = {}): DomphyElement<"div"> {
  const url = props.url ?? "domphy.com";
  const mode = props.mode ?? "default";
  const media = screenMedia(props.imageSrc, props.videoSrc, `Preview of ${url}`);

  const addressBar: DomphyElement = {
    div: [{ small: url, $: [small()] }],
    // `dataTone` anchors are absolute (each is computed from the base tone, not nested
    // relative to an ancestor's own anchor), so pairing this "shift-0" with the toolbar's
    // "shift-2" gives the pill a genuinely distinct — slightly lighter — surface than the
    // bar around it, matching a real address bar's subtle contrast.
    dataTone: "shift-0",
    style: {
      width: "60%",
      display: "flex",
      alignItems: "center",
      justifyContent: "center",
      height: themeSpacing(6),
      borderRadius: themeSpacing(6),
      overflow: "hidden",
      paddingInline: themeSpacing(4),
      backgroundColor: (listener: Listener) => themeColor(listener, "inherit"),
      color: (listener: Listener) => themeColor(listener, "shift-10"),
    },
  };

  const toolbar: DomphyElement = {
    div:
      mode === "default"
        ? [
            {
              div: TRAFFIC_LIGHTS.map((dot) => trafficLightDot(dot.color, dot.key)),
              style: { display: "flex", alignItems: "center", gap: themeSpacing(2), flex: "1 0 0" },
            } as DomphyElement,
            addressBar,
            { div: null, style: { flex: "1 0 0" } } as DomphyElement,
          ]
        : [addressBar],
    dataTone: "shift-2",
    style: {
      flex: "0 0 auto",
      display: "flex",
      alignItems: "center",
      justifyContent: "center",
      gap: themeSpacing(3),
      paddingInline: themeSpacing(4),
      paddingBlock: themeSpacing(3),
      backgroundColor: (listener: Listener) => themeColor(listener, "inherit"),
      color: (listener: Listener) => themeColor(listener, "shift-9"),
    },
  };

  const screen: DomphyElement = {
    div: media ? [media] : null,
    ariaHidden: "true",
    dataTone: "shift-0",
    style: {
      flex: "1 1 auto",
      position: "relative",
      overflow: "hidden",
      backgroundColor: (listener: Listener) => themeColor(listener, "inherit"),
      color: (listener: Listener) => themeColor(listener, "shift-9"),
    },
  };

  return {
    div: [toolbar, screen],
    role: "img",
    ariaLabel: `Browser window showing ${url}`,
    style: {
      position: "relative",
      display: "flex",
      flexDirection: "column",
      width: "100%",
      aspectRatio: "1203 / 753",
      overflow: "hidden",
      borderRadius: themeSpacing(4),
      border: (listener: Listener) => `1px solid ${themeColor(listener, "shift-4")}`,
      color: (listener: Listener) => themeColor(listener, "shift-9"),
      ...(props.style ?? {}),
    },
  };
}

export { safari };

← Back to Magic UI catalog