This document outlines the steps to create MobileMockup
component styled withTailwind CSS and using some npm dependency libraries.
Node.js
and npm
installed on your machine.Tailwind CSS
installed in your project.CVA(class-variance-authority)
is a utility for managing CSS class names based on various conditions.clsx
is a tiny utility for constructing className strings conditionally.1// mobile-mockup.types.ts
2type Size = "small" | "medium" | "large";
3
4type EdgeSize = "small" | "large";
5
6type EdgeRounded = "small" | "large" | "none";
7
8type TopVariant =
9 | "pill"
10 | "small_ears"
11 | "large_ears"
12 | "one_dot_center"
13 | "two_dot_center"
14 | "one_dot_left"
15 | "drop_of_water"
16 | "invisible";
17
18type BottomVariant = "home_bar" | "navigate_shape" | "navigate_icon" | "none";
19
20export interface MobileMockupProps {
21 showBackground?: boolean;
22 size?: Size;
23 edgeSize?: EdgeSize;
24 edgeRounded?: EdgeRounded;
25 topVariant?: TopVariant;
26 bottomVariant?: BottomVariant;
27 className?: string;
28 contentClassName?: string;
29 children?: React.ReactNode;
30}
31
1// mobile-mockup.component.tsx
2import clsx from "clsx";
3import { ChevronLeftIcon, MenuIcon, SquircleIcon } from "lucide-react";
4import { MobileMockupProps } from "./mobile-mockup.types";
5
6const MobileMockup: React.FC<MobileMockupProps> = ({
7 showBackground,
8 size = "medium",
9 edgeSize = "small",
10 edgeRounded = "small",
11 topVariant = "invisible",
12 bottomVariant = "none",
13 className,
14 contentClassName,
15 children,
16}) => {
17 return (
18 <div
19 className={clsx(
20 "relative h-[78em] w-[36em] bg-neutral-50",
21 {
22 "shadow-[0em_0em_0em_0.9em_#1f1f1f,_0em_0em_0em_1.1em_#191919,_0em_0em_0em_1.4em_#111]":
23 edgeSize === "small",
24 "shadow-[0em_0em_0em_1.1em_#1f1f1f,_0em_0em_0em_1.3em_#191919,_0em_0em_0em_2em_#111]":
25 edgeSize === "large",
26 },
27 {
28 "rounded-[3em]": edgeRounded === "small",
29 "rounded-[4em]": edgeRounded === "large",
30 },
31 {
32 "text-[8px]": size === "small",
33 "text-[9px]": size === "medium",
34 "text-[10px]": size === "large",
35 },
36 {
37 "bg-[linear-gradient(60deg,#7371ee_1%,#a1d9d6_100%)]": showBackground,
38 },
39 className,
40 )}
41 >
42 <div
43 className={clsx(
44 "absolute inset-0 flex items-center justify-center",
45 contentClassName,
46 )}
47 >
48 {children}
49 </div>
50 {bottomVariant === "home_bar" ? (
51 <div
52 id="home_indicator"
53 className={clsx(
54 "absolute bottom-[0.7em] left-1/2 h-[0.4em] w-[14em] -translate-x-1/2 rounded-[1em]",
55 showBackground ? "bg-neutral-200" : "bg-neutral-500",
56 )}
57 />
58 ) : bottomVariant === "navigate_shape" ? (
59 <div className="absolute inset-x-0 bottom-[1.5em] flex items-center justify-evenly">
60 <div
61 className={clsx(
62 "h-[1.6em] w-[1.6em] rounded-[0.3em]",
63 showBackground ? "bg-neutral-200" : "bg-neutral-500",
64 )}
65 />
66 <div
67 className={clsx(
68 "h-[1.6em] w-[1.6em] rounded-full",
69 showBackground ? "bg-neutral-200" : "bg-neutral-500",
70 )}
71 />
72 <div
73 className={clsx(
74 "h-0 w-0 border-y-[0.8em] border-r-[1.6em] border-y-transparent",
75 showBackground ? "border-r-neutral-200" : "border-r-neutral-500",
76 )}
77 />
78 </div>
79 ) : bottomVariant === "navigate_icon" ? (
80 <div className="absolute inset-x-0 bottom-[1.5em] flex items-center justify-evenly">
81 <MenuIcon
82 className={clsx(
83 "h-[1.6em] w-[1.6em]",
84 showBackground ? "text-neutral-200" : "text-neutral-500",
85 )}
86 />
87 <SquircleIcon
88 className={clsx(
89 "h-[1.6em] w-[1.6em]",
90 showBackground ? "text-neutral-200" : "text-neutral-500",
91 )}
92 />
93 <ChevronLeftIcon
94 className={clsx(
95 "h-[1.6em] w-[1.6em]",
96 showBackground ? "text-neutral-200" : "text-neutral-500",
97 )}
98 />
99 </div>
100 ) : null}
101 {topVariant !== "invisible" && (
102 <div
103 id="front_top_frame"
104 className={clsx("absolute left-1/2 -translate-x-1/2 bg-[#1f1f1f]", {
105 "top-0 h-[3em] w-[56%] rounded-b-[4em]":
106 topVariant === "large_ears",
107 "top-0 h-[3em] w-[40%] rounded-b-[2em]":
108 topVariant === "small_ears",
109 "top-[0.8em] h-[3em] w-[30%] rounded-[2em]": topVariant === "pill",
110 "top-0 h-[2em] w-[3em] rounded-b-full":
111 topVariant === "drop_of_water",
112 })}
113 />
114 )}
115 {(topVariant === "small_ears" || topVariant === "large_ears") && (
116 <div
117 id="speaker"
118 className={clsx(
119 "absolute left-1/2 top-0 h-[0.8em] w-[15%] -translate-x-1/2 translate-y-[0.6em] rounded-[0.8em] bg-[#101010] shadow-[inset_0em_-0.3em_0.3em_0em_rgba(256,256,256,0.2)]",
120 )}
121 />
122 )}
123 {topVariant !== "invisible" && (
124 <>
125 <div
126 id="camera"
127 className={clsx(
128 "absolute rounded-[1.2em] bg-[#101010] shadow-[inset_0em_-0.3em_0.2em_0em_rgba(256,256,256,0.2)]",
129 "flex items-center justify-center",
130 {
131 "translate-y-[0.4em]": topVariant !== "drop_of_water",
132 },
133 {
134 "left-1/2 top-0 h-[1.2em] w-[1.2em] translate-x-[4em]":
135 topVariant === "small_ears" || topVariant === "large_ears",
136 "left-1/2 top-[1.3em] h-[1.2em] w-[1.2em] translate-x-[3em]":
137 topVariant === "pill",
138 "left-1/2 top-[0.4em] h-[1.6em] w-[1.6em] -translate-x-1/2":
139 topVariant === "one_dot_center",
140 "left-1/2 top-[0.4em] h-[1.6em] w-[1.6em] translate-x-[0.2em]":
141 topVariant === "two_dot_center",
142 "left-[1.8em] top-[0.8em] h-[1.6em] w-[1.6em]":
143 topVariant === "one_dot_left",
144 "left-1/2 top-0 h-[1.6em] w-[1.6em] -translate-x-1/2":
145 topVariant === "drop_of_water",
146 },
147 )}
148 >
149 <div
150 className={clsx(
151 "rounded-full bg-[#2d4d76] shadow-[inset_0em_-0.2em_0.2em_rgba(0,0,0,0.5)]",
152 topVariant === "one_dot_center"
153 ? "h-[0.8em] w-[0.8em]"
154 : "h-[0.6em] w-[0.6em]",
155 )}
156 />
157 </div>
158 {topVariant === "two_dot_center" && (
159 <div
160 id="sub_camera"
161 className={clsx(
162 "absolute top-0 translate-y-[0.4em] rounded-[1.2em] bg-[#101010] shadow-[inset_0em_-0.3em_0.2em_0em_rgba(256,256,256,0.2)]",
163 "left-1/2 top-[0.4em] h-[1.6em] w-[1.6em] -translate-x-[1.8em]",
164 "flex items-center justify-center",
165 )}
166 >
167 <div className="h-[0.8em] w-[0.8em] rounded-full bg-[#2d4d76] shadow-[inset_0em_-0.2em_0.2em_rgba(0,0,0,0.5)]" />
168 </div>
169 )}
170 </>
171 )}
172 </div>
173 );
174};
175
176export default MobileMockup;
177