Search

Ctrl + K

MobileMockup

This document outlines the steps to create MobileMockup component styled withTailwind CSS and using some npm dependency libraries.

Prerequisites

  • 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.
Step 1: Create TypeScript types
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
Step 2: Create MobileMockup component
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