Skip to content
Merged
21 changes: 13 additions & 8 deletions apps/www/content/docs/components/iphone-15-pro.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: iPhone 15 Pro
date: 2024-09-01
description: A mockup of the iPhone 15 Pro
author: dillionverma
author: dillionverma, Yeom-JinHo
published: true
---

Expand Down Expand Up @@ -62,11 +62,16 @@ import { Iphone15Pro } from "@/components/magicui/iphone-15-pro";

## Props

| Prop | Type | Default | Description |
| ---------- | -------- | ------- | -------------------------------------- |
| `width` | `number` | `433` | The width of the iPhone 15 Pro window |
| `height` | `number` | `882` | The height of the iPhone 15 Pro window |
| `src` | `string` | `-` | The source of the image to display |
| `videoSrc` | `string` | `-` | The source of the video to display |
| Prop | Type | Default | Description |
| ----------- | --------------------- | ------- | ------------------------------------------------- |
| `src` | `string` | `-` | The source of the image to display |
| `videoSrc` | `string` | `-` | The source of the video to display |
| `className` | `string` | `-` | Additional classes applied to the wrapper `<div>` |
| `style` | `React.CSSProperties` | `-` | Inline styles applied to the wrapper `<div>` |

The `Iphone15Pro` component also accepts all properties of the `SVGElement` type.
## Notes

- Sizing: width and height props have been removed. The size is controlled by the wrapper `<div>`. The component enforces the aspect ratio 433/882.
- Masking: The screen area is only masked when media (src or videoSrc) is provided. Without media, only the frame is rendered.
- HTML Attributes: The Iphone15Pro component accepts all standard HTMLDivElement props.
- iOS Compatibility: Video is rendered as a DOM overlay instead of foreignObject to avoid Safari/iOS masking issues.
2 changes: 1 addition & 1 deletion apps/www/public/r/iphone-15-pro-demo-2.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"files": [
{
"path": "registry/example/iphone-15-pro-demo-2.tsx",
"content": "import { Iphone15Pro } from \"@/registry/magicui/iphone-15-pro\";\n\nexport default function Iphone15ProDemo() {\n return (\n <div className=\"relative\">\n <Iphone15Pro\n className=\"size-full\"\n src=\"https://via.placeholder.com/430x880\"\n />\n </div>\n );\n}\n",
"content": "import { Iphone15Pro } from \"@/registry/magicui/iphone-15-pro\";\n\nexport default function Iphone15ProDemo() {\n return (\n <div className=\"w-[434px]\">\n <Iphone15Pro src=\"https://via.placeholder.com/430x880\" />\n </div>\n );\n}\n",
"type": "registry:example"
}
]
Expand Down
2 changes: 1 addition & 1 deletion apps/www/public/r/iphone-15-pro-demo-3.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"files": [
{
"path": "registry/example/iphone-15-pro-demo-3.tsx",
"content": "import { Iphone15Pro } from \"@/registry/magicui/iphone-15-pro\";\n\nexport default function Iphone15ProDemo() {\n return (\n <div className=\"relative\">\n <Iphone15Pro\n className=\"size-full\"\n videoSrc=\"https://videos.pexels.com/video-files/8946986/8946986-uhd_1440_2732_25fps.mp4\"\n />\n </div>\n );\n}\n",
"content": "import { Iphone15Pro } from \"@/registry/magicui/iphone-15-pro\";\n\nexport default function Iphone15ProDemo() {\n return (\n <div className=\"w-[434px]\">\n <Iphone15Pro videoSrc=\"https://videos.pexels.com/video-files/8946986/8946986-uhd_1440_2732_25fps.mp4\" />\n </div>\n );\n}\n",
"type": "registry:example"
}
]
Expand Down
2 changes: 1 addition & 1 deletion apps/www/public/r/iphone-15-pro-demo.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"files": [
{
"path": "registry/example/iphone-15-pro-demo.tsx",
"content": "import { Iphone15Pro } from \"@/registry/magicui/iphone-15-pro\";\n\nexport default function Iphone15ProDemo() {\n return (\n <div className=\"relative\">\n <Iphone15Pro className=\"size-full\" />\n </div>\n );\n}\n",
"content": "import { Iphone15Pro } from \"@/registry/magicui/iphone-15-pro\";\n\nexport default function Iphone15ProDemo() {\n return (\n <div className=\"w-[434px]\">\n <Iphone15Pro />\n </div>\n );\n}\n",
"type": "registry:example"
}
]
Expand Down
2 changes: 1 addition & 1 deletion apps/www/public/r/iphone-15-pro.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"files": [
{
"path": "registry/magicui/iphone-15-pro.tsx",
"content": "import { SVGProps } from \"react\";\n\nexport interface Iphone15ProProps extends SVGProps<SVGSVGElement> {\n width?: number;\n height?: number;\n src?: string;\n videoSrc?: string;\n}\n\nexport function Iphone15Pro({\n width = 433,\n height = 882,\n src,\n videoSrc,\n ...props\n}: Iphone15ProProps) {\n return (\n <svg\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path\n d=\"M2 73C2 32.6832 34.6832 0 75 0H357C397.317 0 430 32.6832 430 73V809C430 849.317 397.317 882 357 882H75C34.6832 882 2 849.317 2 809V73Z\"\n className=\"fill-[#E5E5E5] dark:fill-[#404040]\"\n />\n <path\n d=\"M0 171C0 170.448 0.447715 170 1 170H3V204H1C0.447715 204 0 203.552 0 203V171Z\"\n className=\"fill-[#E5E5E5] dark:fill-[#404040]\"\n />\n <path\n d=\"M1 234C1 233.448 1.44772 233 2 233H3.5V300H2C1.44772 300 1 299.552 1 299V234Z\"\n className=\"fill-[#E5E5E5] dark:fill-[#404040]\"\n />\n <path\n d=\"M1 319C1 318.448 1.44772 318 2 318H3.5V385H2C1.44772 385 1 384.552 1 384V319Z\"\n className=\"fill-[#E5E5E5] dark:fill-[#404040]\"\n />\n <path\n d=\"M430 279H432C432.552 279 433 279.448 433 280V384C433 384.552 432.552 385 432 385H430V279Z\"\n className=\"fill-[#E5E5E5] dark:fill-[#404040]\"\n />\n <path\n d=\"M6 74C6 35.3401 37.3401 4 76 4H356C394.66 4 426 35.3401 426 74V808C426 846.66 394.66 878 356 878H76C37.3401 878 6 846.66 6 808V74Z\"\n className=\"fill-white dark:fill-[#262626]\"\n />\n <path\n opacity=\"0.5\"\n d=\"M174 5H258V5.5C258 6.60457 257.105 7.5 256 7.5H176C174.895 7.5 174 6.60457 174 5.5V5Z\"\n className=\"fill-[#E5E5E5] dark:fill-[#404040]\"\n />\n <path\n d=\"M21.25 75C21.25 44.2101 46.2101 19.25 77 19.25H355C385.79 19.25 410.75 44.2101 410.75 75V807C410.75 837.79 385.79 862.75 355 862.75H77C46.2101 862.75 21.25 837.79 21.25 807V75Z\"\n className=\"fill-[#E5E5E5] stroke-[#E5E5E5] stroke-[0.5] dark:fill-[#404040] dark:stroke-[#404040]\"\n />\n\n {src && (\n <image\n href={src}\n x=\"21.25\"\n y=\"19.25\"\n width=\"389.5\"\n height=\"843.5\"\n preserveAspectRatio=\"xMidYMid slice\"\n clipPath=\"url(#roundedCorners)\"\n />\n )}\n {videoSrc && (\n <foreignObject x=\"21.25\" y=\"19.25\" width=\"389.5\" height=\"843.5\">\n <video\n className=\"size-full overflow-hidden rounded-[55.75px] object-cover\"\n src={videoSrc}\n autoPlay\n loop\n muted\n playsInline\n />\n </foreignObject>\n )}\n <path\n d=\"M154 48.5C154 38.2827 162.283 30 172.5 30H259.5C269.717 30 278 38.2827 278 48.5C278 58.7173 269.717 67 259.5 67H172.5C162.283 67 154 58.7173 154 48.5Z\"\n className=\"fill-[#F5F5F5] dark:fill-[#262626]\"\n />\n <path\n d=\"M249 48.5C249 42.701 253.701 38 259.5 38C265.299 38 270 42.701 270 48.5C270 54.299 265.299 59 259.5 59C253.701 59 249 54.299 249 48.5Z\"\n className=\"fill-[#F5F5F5] dark:fill-[#262626]\"\n />\n <path\n d=\"M254 48.5C254 45.4624 256.462 43 259.5 43C262.538 43 265 45.4624 265 48.5C265 51.5376 262.538 54 259.5 54C256.462 54 254 51.5376 254 48.5Z\"\n className=\"fill-[#E5E5E5] dark:fill-[#404040]\"\n />\n <defs>\n <clipPath id=\"roundedCorners\">\n <rect\n x=\"21.25\"\n y=\"19.25\"\n width=\"389.5\"\n height=\"843.5\"\n rx=\"55.75\"\n ry=\"55.75\"\n />\n </clipPath>\n </defs>\n </svg>\n );\n}\n",
"content": "import type { HTMLAttributes } from \"react\";\n\nconst PHONE_WIDTH = 433;\nconst PHONE_HEIGHT = 882;\nconst SCREEN_X = 21.25;\nconst SCREEN_Y = 19.25;\nconst SCREEN_WIDTH = 389.5;\nconst SCREEN_HEIGHT = 843.5;\nconst SCREEN_RADIUS = 55.75;\n\n// Calculated percentages\nconst LEFT_PCT = (SCREEN_X / PHONE_WIDTH) * 100;\nconst TOP_PCT = (SCREEN_Y / PHONE_HEIGHT) * 100;\nconst WIDTH_PCT = (SCREEN_WIDTH / PHONE_WIDTH) * 100;\nconst HEIGHT_PCT = (SCREEN_HEIGHT / PHONE_HEIGHT) * 100;\nconst RADIUS_H = (SCREEN_RADIUS / SCREEN_WIDTH) * 100;\nconst RADIUS_V = (SCREEN_RADIUS / SCREEN_HEIGHT) * 100;\n\nexport interface Iphone15ProProps extends HTMLAttributes<HTMLDivElement> {\n src?: string;\n videoSrc?: string;\n}\n\nexport function Iphone15Pro({\n src,\n videoSrc,\n className,\n style,\n ...props\n}: Iphone15ProProps) {\n const hasVideo = !!videoSrc;\n const hasMedia = hasVideo || !!src;\n\n return (\n <div\n className={`relative inline-block w-full align-middle leading-none ${className}`}\n style={{\n aspectRatio: `${PHONE_WIDTH}/${PHONE_HEIGHT}`,\n ...style,\n }}\n {...props}\n >\n {hasVideo && (\n <div\n className=\"pointer-events-none absolute z-0 overflow-hidden\"\n style={{\n left: `${LEFT_PCT}%`,\n top: `${TOP_PCT}%`,\n width: `${WIDTH_PCT}%`,\n height: `${HEIGHT_PCT}%`,\n borderRadius: `${RADIUS_H}% / ${RADIUS_V}%`,\n }}\n >\n <video\n className=\"block size-full object-cover\"\n src={videoSrc}\n autoPlay\n loop\n muted\n playsInline\n preload=\"metadata\"\n />\n </div>\n )}\n\n <svg\n viewBox={`0 0 ${PHONE_WIDTH} ${PHONE_HEIGHT}`}\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className=\"absolute inset-0 size-full\"\n style={{ transform: \"translateZ(0)\" }}\n >\n <g mask={hasMedia ? \"url(#screenPunch)\" : undefined}>\n <path\n d=\"M2 73C2 32.6832 34.6832 0 75 0H357C397.317 0 430 32.6832 430 73V809C430 849.317 397.317 882 357 882H75C34.6832 882 2 849.317 2 809V73Z\"\n className=\"fill-[#E5E5E5] dark:fill-[#404040]\"\n />\n <path\n d=\"M0 171C0 170.448 0.447715 170 1 170H3V204H1C0.447715 204 0 203.552 0 203V171Z\"\n className=\"fill-[#E5E5E5] dark:fill-[#404040]\"\n />\n <path\n d=\"M1 234C1 233.448 1.44772 233 2 233H3.5V300H2C1.44772 300 1 299.552 1 299V234Z\"\n className=\"fill-[#E5E5E5] dark:fill-[#404040]\"\n />\n <path\n d=\"M1 319C1 318.448 1.44772 318 2 318H3.5V385H2C1.44772 385 1 384.552 1 384V319Z\"\n className=\"fill-[#E5E5E5] dark:fill-[#404040]\"\n />\n <path\n d=\"M430 279H432C432.552 279 433 279.448 433 280V384C433 384.552 432.552 385 432 385H430V279Z\"\n className=\"fill-[#E5E5E5] dark:fill-[#404040]\"\n />\n <path\n d=\"M6 74C6 35.3401 37.3401 4 76 4H356C394.66 4 426 35.3401 426 74V808C426 846.66 394.66 878 356 878H76C37.3401 878 6 846.66 6 808V74Z\"\n className=\"fill-white dark:fill-[#262626]\"\n />\n </g>\n\n <path\n opacity=\"0.5\"\n d=\"M174 5H258V5.5C258 6.60457 257.105 7.5 256 7.5H176C174.895 7.5 174 6.60457 174 5.5V5Z\"\n className=\"fill-[#E5E5E5] dark:fill-[#404040]\"\n />\n\n <path\n d={`M${SCREEN_X} 75C${SCREEN_X} 44.2101 46.2101 ${SCREEN_Y} 77 ${SCREEN_Y}H355C385.79 ${SCREEN_Y} 410.75 44.2101 410.75 75V807C410.75 837.79 385.79 862.75 355 862.75H77C46.2101 862.75 ${SCREEN_X} 837.79 ${SCREEN_X} 807V75Z`}\n className=\"fill-[#E5E5E5] stroke-[#E5E5E5] stroke-[0.5] dark:fill-[#404040] dark:stroke-[#404040]\"\n mask={hasMedia ? \"url(#screenPunch)\" : undefined}\n />\n\n {!hasVideo && src && (\n <image\n href={src}\n x={SCREEN_X}\n y={SCREEN_Y}\n width={SCREEN_WIDTH}\n height={SCREEN_HEIGHT}\n preserveAspectRatio=\"xMidYMid slice\"\n clipPath={hasMedia ? \"url(#roundedCorners)\" : undefined}\n />\n )}\n <path\n d=\"M154 48.5C154 38.2827 162.283 30 172.5 30H259.5C269.717 30 278 38.2827 278 48.5C278 58.7173 269.717 67 259.5 67H172.5C162.283 67 154 58.7173 154 48.5Z\"\n className=\"fill-[#F5F5F5] dark:fill-[#262626]\"\n />\n <path\n d=\"M249 48.5C249 42.701 253.701 38 259.5 38C265.299 38 270 42.701 270 48.5C270 54.299 265.299 59 259.5 59C253.701 59 249 54.299 249 48.5Z\"\n className=\"fill-[#F5F5F5] dark:fill-[#262626]\"\n />\n <path\n d=\"M254 48.5C254 45.4624 256.462 43 259.5 43C262.538 43 265 45.4624 265 48.5C265 51.5376 262.538 54 259.5 54C256.462 54 254 51.5376 254 48.5Z\"\n className=\"fill-[#E5E5E5] dark:fill-[#404040]\"\n />\n\n <defs>\n <mask id=\"screenPunch\" maskUnits=\"userSpaceOnUse\">\n <rect\n x=\"0\"\n y=\"0\"\n width={PHONE_WIDTH}\n height={PHONE_HEIGHT}\n fill=\"white\"\n />\n <rect\n x={SCREEN_X}\n y={SCREEN_Y}\n width={SCREEN_WIDTH}\n height={SCREEN_HEIGHT}\n rx={SCREEN_RADIUS}\n ry={SCREEN_RADIUS}\n fill=\"black\"\n />\n </mask>\n <clipPath id=\"roundedCorners\">\n <rect\n x={SCREEN_X}\n y={SCREEN_Y}\n width={SCREEN_WIDTH}\n height={SCREEN_HEIGHT}\n rx={SCREEN_RADIUS}\n ry={SCREEN_RADIUS}\n />\n </clipPath>\n </defs>\n </svg>\n </div>\n );\n}\n",
"type": "registry:ui"
}
]
Expand Down
7 changes: 2 additions & 5 deletions apps/www/registry/example/iphone-15-pro-demo-2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ import { Iphone15Pro } from "@/registry/magicui/iphone-15-pro";

export default function Iphone15ProDemo() {
return (
<div className="relative">
<Iphone15Pro
className="size-full"
src="https://via.placeholder.com/430x880"
/>
<div className="w-[434px]">
<Iphone15Pro src="https://via.placeholder.com/430x880" />
</div>
);
}
7 changes: 2 additions & 5 deletions apps/www/registry/example/iphone-15-pro-demo-3.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ import { Iphone15Pro } from "@/registry/magicui/iphone-15-pro";

export default function Iphone15ProDemo() {
return (
<div className="relative">
<Iphone15Pro
className="size-full"
videoSrc="https://videos.pexels.com/video-files/8946986/8946986-uhd_1440_2732_25fps.mp4"
/>
<div className="w-[434px]">
<Iphone15Pro videoSrc="https://videos.pexels.com/video-files/8946986/8946986-uhd_1440_2732_25fps.mp4" />
</div>
);
}
4 changes: 2 additions & 2 deletions apps/www/registry/example/iphone-15-pro-demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Iphone15Pro } from "@/registry/magicui/iphone-15-pro";

export default function Iphone15ProDemo() {
return (
<div className="relative">
<Iphone15Pro className="size-full" />
<div className="w-[434px]">
<Iphone15Pro />
</div>
);
}
Loading
Loading