├── .eslintrc.json
├── .gitignore
├── .vscode
└── settings.json
├── LICENCE.md
├── README.md
├── app
├── (auth)
│ └── login
│ │ └── page.tsx
├── (browse)
│ ├── _components
│ │ └── navbar.tsx
│ ├── browse
│ │ ├── [platform]
│ │ │ ├── [feature]
│ │ │ │ └── page.tsx
│ │ │ └── layout.tsx
│ │ └── layout.tsx
│ ├── changelog
│ │ └── page.tsx
│ ├── collections
│ │ └── page.tsx
│ ├── layout.tsx
│ ├── pricing
│ │ └── page.tsx
│ └── search
│ │ └── page.tsx
├── favicon.ico
├── globals.css
└── layout.tsx
├── components.json
├── components
├── analytics.tsx
├── button-hover-card.tsx
├── button-tag.tsx
├── cards-list.tsx
├── carousels
│ ├── carousel-buttons-tags.tsx
│ ├── carousel-card.tsx
│ └── carousel-progress-bar.tsx
├── categories-list.tsx
├── checkbox-card.tsx
├── checkbox-toolbar.tsx
├── command-content.tsx
├── context-menu-card.tsx
├── hero-homepage.tsx
├── hero-switcher.tsx
├── hover-card-content.tsx
├── hover-popover.tsx
├── icons.tsx
├── modal-provider.tsx
├── modals
│ └── search-command-dialog.tsx
├── nav-links.tsx
├── nav-menu.tsx
├── search-button-nav.tsx
├── tags-list.tsx
├── tailwind-indicator.tsx
├── theme-provider.tsx
├── theme-toggle.tsx
└── ui
│ ├── badge.tsx
│ ├── button.tsx
│ ├── carousel.tsx
│ ├── checkbox.tsx
│ ├── command.tsx
│ ├── context-menu.tsx
│ ├── dialog.tsx
│ ├── dropdown-menu.tsx
│ ├── hover-card.tsx
│ ├── popover.tsx
│ ├── scroll-area.tsx
│ ├── separator.tsx
│ ├── skeleton.tsx
│ └── tooltip.tsx
├── config
└── site.ts
├── hooks
├── use-checkbox-selection.ts
├── use-hero-selection.ts
├── use-lock-body.ts
├── use-media-query.ts
├── use-mounted.ts
└── use-search-modal.ts
├── lib
├── _data.tsx
└── utils.ts
├── next.config.js
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── public
├── fonts
│ ├── basiersquare-medium-webfont.woff2
│ ├── basiersquare-regular-webfont.woff2
│ └── basiersquare-semibold-webfont.woff2
├── images
│ ├── desktop-screen.webp
│ ├── hero-banner-1.webp
│ ├── hero-banner-2.webp
│ ├── hero-banner-3.webp
│ ├── hero-banner-4.webp
│ ├── phone-screen.webp
│ ├── review-star-dark.png
│ ├── review-star-light.png
│ └── square-logo.webp
├── next.svg
├── og.png
└── vercel.svg
├── tailwind.config.ts
└── tsconfig.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "workbench.colorCustomizations": {
3 | "activityBar.activeBackground": "#a3c7f6",
4 | "activityBar.background": "#a3c7f6",
5 | "activityBar.foreground": "#15202b",
6 | "activityBar.inactiveForeground": "#15202b99",
7 | "activityBarBadge.background": "#eb3483",
8 | "activityBarBadge.foreground": "#e7e7e7",
9 | "commandCenter.border": "#15202b99",
10 | "sash.hoverBorder": "#a3c7f6",
11 | "statusBar.background": "#74abf2",
12 | "statusBar.foreground": "#15202b",
13 | "statusBarItem.hoverBackground": "#458fee",
14 | "statusBarItem.remoteBackground": "#74abf2",
15 | "statusBarItem.remoteForeground": "#15202b",
16 | "titleBar.activeBackground": "#74abf2",
17 | "titleBar.activeForeground": "#15202b",
18 | "titleBar.inactiveBackground": "#74abf299",
19 | "titleBar.inactiveForeground": "#15202b99"
20 | },
21 | "peacock.color": "#74abf2",
22 | "typescript.tsdk": "node_modules/typescript/lib"
23 | }
24 |
--------------------------------------------------------------------------------
/LICENCE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 mickasmt
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Next Mobbin Clone
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Introduction ·
14 | Installation ·
15 | Tech Stack + Features ·
16 | Author ·
17 | Credits
18 |
19 |
20 |
21 | ## Introduction
22 |
23 | This project has been undertaken with the goal of enhancing my skills in user interface (UI) development and putting the cutting-edge features of shadcn-ui to the test.
24 |
25 | Inspired by the clean design of mobbbin.com, I am recreating its essence using the powerful Next.js 14 framework to optimize the visual experience.
26 |
27 | Components mainly used: Carousel, CommandDialog, Dropdown Menu, HoverCard, Checkbox.
28 |
29 | > [!NOTE]
30 | > This project contains UI Only - Just for testing shadcn's components.
31 |
32 | ## Installation
33 |
34 | Clone & create this repo locally with the following command:
35 |
36 | ```bash
37 | npx create-next-app my-name-project --example "https://github.com/mickasmt/next-mobbin-clone"
38 | ```
39 |
40 | 1. Install dependencies using pnpm:
41 |
42 | ```sh
43 | pnpm install
44 | ```
45 |
46 | 2. Start the development server:
47 |
48 | ```sh
49 | pnpm dev
50 | ```
51 |
52 | ## Roadmap
53 |
54 | - [x] ~Fix collision for Hover Card on Y axis in search command dialog~
55 | - [x] ~Detect keyboard for to display HoverCard in search command dialog~
56 | - [ ] Fix fade carousel after resize screen
57 | - [ ] Add framer-motion on HoverCard on carousel buttons tags
58 | - [ ] Add auth pages
59 | - [ ] Add pricing page
60 |
61 |
62 | ## Tech Stack + Features
63 |
64 | https://github.com/mickasmt/next-mobbin-clone/assets/62285783/098dc343-a3b1-4d9b-9574-7723eeba5611
65 |
66 | ### Frameworks
67 |
68 | - [Next.js](https://nextjs.org/) – React framework for building performant apps with the best developer experience
69 |
70 | ### Platforms
71 |
72 | - [Vercel](https://vercel.com/) – Easily preview & deploy changes with git
73 |
74 | ### UI
75 |
76 | - [Tailwind CSS](https://tailwindcss.com/) – Utility-first CSS framework for rapid UI development
77 | - [Shadcn/ui](https://ui.shadcn.com/) – Re-usable components built using Radix UI and Tailwind CSS
78 | - [Framer Motion](https://framer.com/motion) – Motion library for React to animate components with ease
79 | - [Lucide](https://lucide.dev/) – Beautifully simple, pixel-perfect icons
80 | - [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) – Optimize custom fonts and remove external network requests for improved performance
81 | - [`ImageResponse`](https://nextjs.org/docs/app/api-reference/functions/image-response) – Generate dynamic Open Graph images at the edge
82 |
83 | ### Code Quality
84 |
85 | - [TypeScript](https://www.typescriptlang.org/) – Static type checker for end-to-end typesafety
86 | - [Prettier](https://prettier.io/) – Opinionated code formatter for consistent code style
87 | - [ESLint](https://eslint.org/) – Pluggable linter for Next.js and TypeScript
88 |
89 | ### Miscellaneous
90 |
91 | - [Vercel Analytics](https://vercel.com/analytics) – Track unique visitors, pageviews, and more in a privacy-friendly way
92 |
93 | ## Author
94 |
95 | Created by [@miickasmt](https://twitter.com/miickasmt) in 2024, released under the [MIT license](https://github.com/shadcn/taxonomy/blob/main/LICENSE.md).
96 |
97 | ## Credits
98 |
99 | This project was inspired by the [Mobbin](https://mobbin.com/)'s website.
100 |
--------------------------------------------------------------------------------
/app/(auth)/login/page.tsx:
--------------------------------------------------------------------------------
1 | export default function LoginPage() {
2 | return (
3 |
4 | Title
5 | Auth form
6 | Images animation
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/app/(browse)/_components/navbar.tsx:
--------------------------------------------------------------------------------
1 | import { NavLinks } from "@/components/nav-links";
2 | import { NavMenu } from "@/components/nav-menu";
3 | import { SearchButtonNav } from "@/components/search-button-nav";
4 | import { Button } from "@/components/ui/button";
5 | import Link from "next/link";
6 |
7 | export function Navbar() {
8 | return (
9 |
10 |
11 |
12 |
13 | {/*
*/}
14 |
Mobbin
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | Pricing
30 |
31 |
32 | Login
33 |
34 |
35 |
36 | Try Mobbin Free
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
49 |
50 |
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/app/(browse)/browse/[platform]/[feature]/page.tsx:
--------------------------------------------------------------------------------
1 | import { CardsList } from "@/components/cards-list";
2 | import { TagsList } from "@/components/tags-list";
3 |
4 | interface FeaturePageProps {
5 | params: {
6 | platform: string;
7 | feature: string;
8 | };
9 | }
10 |
11 | export default function FeaturePage({ params }: FeaturePageProps) {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/app/(browse)/browse/[platform]/layout.tsx:
--------------------------------------------------------------------------------
1 | import { CategoriesList } from "@/components/categories-list";
2 |
3 | const PlatformLayout = ({ children }: { children: React.ReactNode }) => {
4 | return (
5 | <>
6 |
7 |
Browse
8 |
9 | {children}
10 |
11 | >
12 | );
13 | };
14 |
15 | export default PlatformLayout;
16 |
--------------------------------------------------------------------------------
/app/(browse)/browse/layout.tsx:
--------------------------------------------------------------------------------
1 | import { HerosComponent } from "@/components/hero-switcher";
2 |
3 |
4 | const BrowseLayout = ({ children }: { children: React.ReactNode }) => {
5 | return (
6 | <>
7 |
8 | {children}
9 | >
10 | );
11 | };
12 |
13 | export default BrowseLayout;
14 |
--------------------------------------------------------------------------------
/app/(browse)/changelog/page.tsx:
--------------------------------------------------------------------------------
1 | export default function ChangelogPage() {
2 | return (
3 |
4 | title
5 | blogpost with sticky
6 |
7 | )
8 | }
9 |
--------------------------------------------------------------------------------
/app/(browse)/collections/page.tsx:
--------------------------------------------------------------------------------
1 | export default function CollectionsPage() {
2 | return (
3 |
4 | title
5 | Search & new collection button
6 | Table
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/app/(browse)/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Navbar } from "./_components/navbar";
2 |
3 | const BrowseFolderLayout = ({ children }: { children: React.ReactNode }) => {
4 | return (
5 |
6 |
7 | {children}
8 |
9 | {/* */}
10 |
11 | );
12 | };
13 |
14 | export default BrowseFolderLayout;
15 |
--------------------------------------------------------------------------------
/app/(browse)/pricing/page.tsx:
--------------------------------------------------------------------------------
1 | export default function PricingPage() {
2 | return (
3 |
4 | Title & description
5 | Toggle year/month
6 | Pricing Cards
7 | Brands Marquee
8 | Compare Plans
9 | FAQs
10 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/app/(browse)/search/page.tsx:
--------------------------------------------------------------------------------
1 | export default function SearchPage() {
2 | return (
3 |
4 | back button
5 | title
6 | tags list & infos/filters
7 | Images List
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mickasmt/next-mobbin-clone/7a82cafa4eb029e9a15097d5285a4aca453952e0/app/favicon.ico
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @font-face {
6 | font-display: swap;
7 | font-family: "Basier Square";
8 | font-style: normal;
9 | font-weight: 400;
10 | src: url(/fonts/basiersquare-regular-webfont.woff2) format("woff2");
11 | }
12 |
13 | @font-face {
14 | font-display: swap;
15 | font-family: "Basier Square";
16 | font-style: normal;
17 | font-weight: 500;
18 | src: url(/fonts/basiersquare-medium-webfont.woff2) format("woff2");
19 | }
20 |
21 | @font-face {
22 | font-display: swap;
23 | font-family: "Basier Square";
24 | font-style: normal;
25 | font-weight: 600;
26 | src: url(/fonts/basiersquare-semibold-webfont.woff2) format("woff2");
27 | }
28 |
29 | @layer base {
30 | :root {
31 | --background: 0 0% 100%;
32 | --foreground: 0 0% 3.9%;
33 |
34 | --card: 0 0% 100%;
35 | --card-foreground: 0 0% 3.9%;
36 |
37 | --popover: 0 0% 100%;
38 | --popover-foreground: 0 0% 3.9%;
39 |
40 | --primary: 0 0% 9%;
41 | --primary-foreground: 0 0% 98%;
42 |
43 | --secondary: 0 0% 96.1%;
44 | --secondary-foreground: 0 0% 9%;
45 |
46 | --muted: 0 0% 96.1%;
47 | --muted-foreground: 0 0% 45.1%;
48 |
49 | --accent: 0 0% 96.1%;
50 | --accent-foreground: 0 0% 9%;
51 |
52 | --destructive: 0 84.2% 60.2%;
53 | --destructive-foreground: 0 0% 98%;
54 |
55 | --border: 0 0% 89.8%;
56 | --input: 0 0% 89.8%;
57 | --ring: 0 0% 3.9%;
58 |
59 | --radius: 0.5rem;
60 | }
61 |
62 | .dark {
63 | --background: 0 0% 3.9%;
64 | --foreground: 0 0% 98%;
65 |
66 | --card: 0 0% 3.9%;
67 | --card-foreground: 0 0% 98%;
68 |
69 | --popover: 0 0% 3.9%;
70 | --popover-foreground: 0 0% 98%;
71 |
72 | --primary: 0 0% 98%;
73 | --primary-foreground: 0 0% 9%;
74 |
75 | --secondary: 0 0% 14.9%;
76 | --secondary-foreground: 0 0% 98%;
77 |
78 | --muted: 0 0% 14.9%;
79 | --muted-foreground: 0 0% 63.9%;
80 |
81 | --accent: 0 0% 14.9%;
82 | --accent-foreground: 0 0% 98%;
83 |
84 | --destructive: 0 62.8% 30.6%;
85 | --destructive-foreground: 0 0% 98%;
86 |
87 | --border: 0 0% 14.9%;
88 | --input: 0 0% 14.9%;
89 | --ring: 0 0% 83.1%;
90 | }
91 | }
92 |
93 | @layer base {
94 | * {
95 | @apply border-border;
96 | }
97 |
98 | body {
99 | @apply bg-background text-foreground text-body;
100 | font-family: "Basier Square", sans-serif !important;
101 | }
102 |
103 | /* Mobbin styles */
104 | .marge-x {
105 | @apply px-4 min-[720px]:px-6 min-[1280px]:px-8 min-[1536px]:px-20;
106 | }
107 |
108 | .-margin-x {
109 | @apply -mx-3 px-3 md:-mx-6 md:px-6;
110 | }
111 |
112 | .text-body {
113 | @apply font-medium text-base tracking-[-.008em];
114 | }
115 |
116 | .text-heading {
117 | @apply font-semibold text-4xl tracking-[-.016em] md:text-[44px] md:leading-[48px] md:tracking-[-.024em];
118 | }
119 |
120 | .text-heading-xl {
121 | @apply font-semibold text-4xl tracking-[-.016em] md:text-[56px] md:leading-[64px] md:tracking-[-.024em];
122 | }
123 |
124 | .scrollbar-none::-webkit-scrollbar {
125 | display: none;
126 | }
127 |
128 | .scrollbar-none {
129 | -ms-overflow-style: none;
130 | scrollbar-width: none;
131 | }
132 | }
133 |
134 | /* carousel fade effect */
135 | .fade__container {
136 | transform: none !important;
137 | display: flex;
138 | position: relative;
139 | height: 100%;
140 | }
141 |
142 | .fade__slide {
143 | flex: 0 0 auto;
144 | width: 100%;
145 | height: 100%;
146 | top: 0;
147 | left: 0 !important;
148 | right: 0 !important;
149 | opacity: 0;
150 | transition: opacity 1s;
151 | }
152 |
153 | .fade__is-ready .fade__slide {
154 | position: absolute;
155 | }
156 |
157 | .fade__slide.is-snapped {
158 | opacity: 1;
159 | z-index: 1;
160 | transition: opacity 0.8s;
161 | }
162 |
163 | /* Search command dialog */
164 | .search-command [cmdk-input-wrapper] {
165 | @apply rounded-full bg-secondary text-secondary-foreground px-5 mx-3;
166 | }
167 |
168 | .search-command [cmdk-input-wrapper] svg {
169 | @apply !text-foreground !opacity-100;
170 | }
171 |
172 | .search-command [cmdk-input-wrapper] input {
173 | @apply text-sm sm:text-base;
174 | }
175 |
176 | .search-command [cmdk-list-sizer] {
177 | @apply flex flex-col gap-y-6 !size-full !max-h-full !pb-6;
178 | }
179 |
180 | .search-command [cmdk-group-heading] {
181 | @apply !text-sm !px-0 mb-2.5;
182 | }
183 |
184 | @media (min-width: 768px) {
185 | .group[data-selected=true] .md\:group-data-selected\:-translate-y-5 {
186 | @apply -translate-y-5;
187 | }
188 | }
189 |
190 | .data-selected[data-selected=true] .data-selected-bg {
191 | @apply !bg-foreground/20;
192 | }
193 |
194 | /* flex flex-col items-center gap-y-4 min-720:absolute min-720:inset-0 min-720:group-hover:-translate-y-20 min-720:group-data-selected:-translate-y-20 transition-transform duration-300 ease-out */
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Analytics } from "@/components/analytics";
2 | import { CheckboxToolbar } from "@/components/checkbox-toolbar";
3 | import { ModalProvider } from "@/components/modal-provider";
4 | import { TailwindIndicator } from "@/components/tailwind-indicator";
5 | import { ThemeProvider } from "@/components/theme-provider";
6 | import type { Metadata } from "next";
7 | import "./globals.css";
8 |
9 | export const metadata: Metadata = {
10 | title: "Next Mobbin Clone",
11 | description: "Next Mobbin Clone for testing shadcn-ui",
12 | };
13 |
14 | export default function RootLayout({
15 | children,
16 | }: {
17 | children: React.ReactNode;
18 | }) {
19 | return (
20 |
21 |
22 |
23 | {children}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "app/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
--------------------------------------------------------------------------------
/components/analytics.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { Analytics as VercelAnalytics } from "@vercel/analytics/react"
4 |
5 | export function Analytics() {
6 | return
7 | }
8 |
--------------------------------------------------------------------------------
/components/button-hover-card.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Button } from "@/components/ui/button";
4 | import {
5 | HoverCard,
6 | HoverCardContent,
7 | HoverCardTrigger,
8 | } from "@/components/ui/hover-card";
9 | import { cn } from "@/lib/utils";
10 | import { HoverCardPortal } from "@radix-ui/react-hover-card";
11 | import { AnimatePresence, MotionConfig, motion } from "framer-motion";
12 | import Image from "next/image";
13 | import { useState } from "react";
14 |
15 | const buttons = [
16 | "Artificial Intelligence",
17 | "Education",
18 | "Food & Drink",
19 | "Health & Fitness",
20 | "Lifestyle",
21 | "Entertainment",
22 | "Travel & Transportation",
23 | ];
24 |
25 | const ScreensContent = ({ name }: { name: string }) => {
26 | return (
27 |
28 |
29 | {Array.from({ length: 3 }).map((_, index) => (
30 |
31 |
39 |
40 | ))}
41 |
42 |
43 |
44 | {name} - Screens display the user’s profile information and/or
45 | account settings.
46 |
47 |
48 | );
49 | };
50 |
51 | const WebContent = ({ name }: { name: string }) => {
52 | return (
53 |
54 |
65 |
66 |
67 | {name} - Screens display the user’s profile information and/or
68 | account settings.
69 |
70 |
71 | );
72 | };
73 |
74 | export const ButtonHoverCard = () => {
75 | const [isOpen, setIsOpen] = useState(false);
76 | const [activeButton, setActiveButton] = useState("");
77 |
78 | const handleMouseEnter = (button: string) => {
79 | setActiveButton(button);
80 | setIsOpen(true);
81 | };
82 |
83 | const handleMouseLeave = () => {
84 | setActiveButton("");
85 | setIsOpen(false);
86 | };
87 |
88 | return (
89 |
95 | {buttons.map((button, index) => (
96 |
102 | handleMouseEnter(button)}
104 | asChild
105 | >
106 |
107 | {button}
108 |
109 |
110 |
111 |
112 |
113 | {isOpen ? (
114 |
126 |
148 |
149 |
150 |
151 | ) : null}
152 |
153 |
154 |
155 | ))}
156 |
157 | );
158 | };
159 |
--------------------------------------------------------------------------------
/components/button-tag.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useState } from "react";
4 | import {
5 | Popover,
6 | PopoverContent,
7 | PopoverTrigger,
8 | } from "@/components/ui/popover";
9 | import { Button } from "@/components/ui/button";
10 |
11 | interface ButtonTagProps {
12 | title: string;
13 | }
14 |
15 |
16 | export const ButtonTag = ({ title }: ButtonTagProps) => {
17 | const [open, setOpen] = useState(false);
18 |
19 | const handleMouseEnter = () => {
20 | setOpen(true);
21 | };
22 |
23 | const handleMouseLeave = () => {
24 | setOpen(false);
25 | };
26 |
27 | return (
28 |
29 | {
33 | e.preventDefault();
34 | }}
35 | asChild
36 | >
37 |
38 | {title}
39 |
40 |
41 |
47 | Place content for the popover here.
48 |
49 |
50 | );
51 | };
52 |
53 | // screens
54 | // const screens = () => (
55 | //
68 | // )
69 |
70 | // apps
71 | // {
72 | /*
73 |
74 |
75 |
76 |
77 |
78 |
79 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
92 |
93 |
94 |
95 |
96 | Business apps assist with running a business or provide a means to collaborate, edit, or share content.
97 |
98 |
99 |
100 |
101 |
102 |
103 |
*/
104 | // }
105 |
--------------------------------------------------------------------------------
/components/cards-list.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils";
2 | import { CarouselCard } from "./carousels/carousel-card";
3 | import { CheckboxCard } from "./checkbox-card";
4 |
5 | interface CardsListProps {
6 | platform: string;
7 | feature: string;
8 | }
9 |
10 | const GridCard = ({ children }: { children: React.ReactNode }) => (
11 | *:nth-child(n+5)]:hidden max-md:[&>*:nth-child(n+7)]:hidden max-xl:[&>*:nth-child(n+9)]:hidden"
15 | )}
16 | >
17 | {children}
18 |
19 | );
20 |
21 | const FeatureComponent = ({ feature }: { feature: string }) => {
22 | switch (feature) {
23 | case "apps":
24 | return ;
25 | case "screens":
26 | return ;
27 | case "ui-elements":
28 | return ;
29 | case "flows":
30 | return ;
31 | default:
32 | return null; // Vous pouvez également retourner un composant par défaut ou ne rien rendre.
33 | }
34 | };
35 |
36 | const AppsComponent = () => (
37 |
38 | {Array.from({ length: 10 }).map((_, index) => (
39 |
40 | ))}
41 |
42 | );
43 |
44 | const ScreensComponent = () => (
45 |
46 | {Array.from({ length: 10 }).map((_, index) => (
47 |
48 | ))}
49 |
50 | );
51 |
52 | const UiElementsComponent = () => (
53 |
54 | {Array.from({ length: 10 }).map((_, index) => (
55 |
56 | ))}
57 |
58 | );
59 |
60 | const FlowsComponent = () => (
61 |
62 | {Array.from({ length: 10 }).map((_, index) => (
63 |
64 | ))}
65 |
66 | );
67 |
68 | export function CardsList({ platform, feature }: CardsListProps) {
69 | return ;
70 | }
71 |
--------------------------------------------------------------------------------
/components/carousels/carousel-buttons-tags.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Carousel,
5 | CarouselContent,
6 | CarouselItem,
7 | CarouselNext,
8 | CarouselPrevious,
9 | type CarouselApi,
10 | } from "@/components/ui/carousel";
11 | import { cn } from "@/lib/utils";
12 | import React from "react";
13 |
14 | import { Button } from "@/components/ui/button";
15 | import {
16 | HoverCard,
17 | HoverCardContent,
18 | HoverCardTrigger,
19 | } from "@/components/ui/hover-card";
20 | // import { cn } from "@/lib/utils";
21 | import { ScreensContent } from "@/components/hover-card-content";
22 | import { categoriesList } from "@/lib/_data";
23 | import { HoverCardPortal } from "@radix-ui/react-hover-card";
24 |
25 | export function CarouselButtonsTags() {
26 | const [api, setApi] = React.useState();
27 | const [scrollPrev, setScrollPrev] = React.useState(false);
28 | const [scrollNext, setScrollNext] = React.useState(true);
29 |
30 | React.useEffect(() => {
31 | if (!api) {
32 | return;
33 | }
34 |
35 | api.on("select", () => {
36 | // add duration for hiding arrows after carousel slide animation
37 | setTimeout(() => {
38 | setScrollPrev(api.canScrollPrev());
39 | setScrollNext(api.canScrollNext());
40 | }, 550);
41 | });
42 | }, [api]);
43 |
44 | const [isOpen, setIsOpen] = React.useState(false);
45 | const [activeButton, setActiveButton] = React.useState("");
46 |
47 | const handleMouseEnter = (button: string) => {
48 | setActiveButton(button);
49 | setIsOpen(true);
50 | };
51 |
52 | const handleMouseLeave = () => {
53 | setActiveButton("");
54 | setIsOpen(false);
55 | };
56 |
57 | return (
58 |
69 |
70 | {/* All button */}
71 |
72 |
73 | All
74 |
75 |
76 |
77 | {/* Tags buttons */}
78 | {categoriesList.map((category) => (
79 |
83 |
89 | handleMouseEnter(category)}
91 | asChild
92 | >
93 |
94 | {category}
95 |
96 |
97 |
98 |
99 |
108 |
109 |
110 |
111 |
112 |
113 | ))}
114 |
115 |
116 |
123 |
130 |
131 | );
132 | }
133 |
--------------------------------------------------------------------------------
/components/carousels/carousel-card.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ContextMenuCard } from "@/components/context-menu-card";
4 | import {
5 | Carousel,
6 | CarouselContent,
7 | CarouselItem,
8 | CarouselNext,
9 | CarouselPrevious,
10 | type CarouselApi,
11 | } from "@/components/ui/carousel";
12 | import {
13 | DropdownMenu,
14 | DropdownMenuContent,
15 | DropdownMenuItem,
16 | DropdownMenuTrigger,
17 | } from "@/components/ui/dropdown-menu";
18 | import {
19 | Tooltip,
20 | TooltipContent,
21 | TooltipPortal,
22 | TooltipProvider,
23 | TooltipTrigger,
24 | } from "@/components/ui/tooltip";
25 |
26 | import React from "react";
27 |
28 | import Image from "next/image";
29 |
30 | import { Icons } from "@/components/icons";
31 | import { Badge } from "@/components/ui/badge";
32 | import { Button } from "@/components/ui/button";
33 | import { cn } from "@/lib/utils";
34 | import Link from "next/link";
35 | import { useState } from "react";
36 | import PhoneScreen from "../../public/images/phone-screen.webp";
37 |
38 | export function CarouselCard() {
39 | const [menuOpen, setMenuOpen] = useState(false);
40 | const [api, setApi] = React.useState();
41 | const [current, setCurrent] = React.useState(0);
42 | const [scrollPrev, setScrollPrev] = React.useState(false);
43 | const [scrollNext, setScrollNext] = React.useState(true);
44 |
45 | React.useEffect(() => {
46 | if (!api) {
47 | return;
48 | }
49 |
50 | setCurrent(api.selectedScrollSnap() + 1);
51 |
52 | api.on("select", () => {
53 | setCurrent(api.selectedScrollSnap() + 1);
54 | });
55 |
56 | api.on("select", () => {
57 | setScrollPrev(api.canScrollPrev());
58 | setScrollNext(api.canScrollNext());
59 | });
60 | }, [api]);
61 |
62 | return (
63 |
64 |
65 |
66 |
67 |
75 |
76 |
77 |
85 |
86 | {Array.from({ length: 3 }).map((_, index) => (
87 |
88 |
96 |
97 | ))}
98 |
99 |
106 |
113 |
114 |
115 |
116 |
117 | {Array.from({ length: 3 }).map((_, index) => (
118 |
122 |
123 |
129 |
130 | ))}
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | Fake Name App
141 |
142 |
143 | Fake sentence for test
144 |
145 |
146 |
147 |
153 |
154 |
155 |
156 | alert("Saved!!")}
160 | >
161 |
162 |
163 |
164 |
165 |
169 | Save to collections
170 |
171 |
172 |
173 |
174 |
175 | setMenuOpen(!menuOpen)}
178 | >
179 |
180 |
181 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
196 |
197 | Download all screens
198 |
199 | PRO
200 |
201 |
202 |
203 |
204 |
208 |
209 | Copy link app
210 |
211 |
212 |
213 |
214 |
215 |
216 |
220 | Download & Share
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 | );
230 | }
231 |
--------------------------------------------------------------------------------
/components/carousels/carousel-progress-bar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Link from "next/link";
4 | import { Button } from "@/components/ui/button";
5 | import {
6 | Carousel,
7 | CarouselContent,
8 | CarouselItem,
9 | type CarouselApi,
10 | } from "@/components/ui/carousel";
11 | import Autoplay from "embla-carousel-autoplay";
12 | import ClassNames from "embla-carousel-class-names";
13 |
14 | import Image from "next/image";
15 | import { useEffect, useState } from "react";
16 | import { cn } from "@/lib/utils";
17 | // import { useMounted } from "@/hooks/use-mounted";
18 |
19 | const slides = [
20 | {
21 | title: "Find login screen references.",
22 | imageUrl: "/images/hero-banner-1.webp",
23 | },
24 | {
25 | title: "Discover best-in-class apps.",
26 | imageUrl: "/images/hero-banner-2.webp",
27 | },
28 | {
29 | title: "Find onboarding flows.",
30 | imageUrl: "/images/hero-banner-3.webp",
31 | },
32 | {
33 | title: "Explore searchbar references.",
34 | imageUrl: "/images/hero-banner-4.webp",
35 | },
36 | ];
37 |
38 | const BlockInfos = () => (
39 |
40 |
41 |
42 | Save hours of UI & UX research with our library of 100,000+ fully
43 | searchable mobile & web screenshots.
44 |
45 |
46 | Save hours of UI & UX research by searching across our library of
47 | 100k+ mobile & web screenshots.
48 |
49 |
50 |
51 |
52 | Try Mobbin Free
53 |
54 |
55 | Learn more
56 |
57 |
58 |
59 | );
60 |
61 | const intervalAutoplay: number = 4000;
62 |
63 | export function CarouselProgressBar() {
64 | // const mounted = useMounted();
65 | const [api, setApi] = useState();
66 | const [current, setCurrent] = useState(slides.length);
67 | // FIX: Why slides.length ? The first dot don't show the animation progress bar if its 0. And current is set in useEffect after initial render
68 |
69 | const goToIndex = (index: number) => {
70 | if (!api) {
71 | return;
72 | }
73 | setCurrent(index);
74 | api?.scrollTo(index, true);
75 | };
76 |
77 | useEffect(() => {
78 | if (!api) {
79 | return;
80 | }
81 |
82 | setCurrent(api.selectedScrollSnap());
83 |
84 | api.on("select", () => {
85 | setCurrent(api.selectedScrollSnap());
86 | });
87 | }, [api]);
88 |
89 | // if (!mounted)
90 | // return (
91 | //
92 | // );
93 |
94 | return (
95 |
96 |
113 |
116 | {slides.map((item) => (
117 |
118 |
119 |
127 |
128 |
{item.title}
129 |
130 |
131 |
132 | ))}
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 | {slides.map((_, index) => (
142 |
goToIndex(index)}
145 | className="relative h-[3px] w-12 overflow-hidden rounded-full"
146 | >
147 |
148 |
162 |
163 | ))}
164 |
165 |
166 |
167 |
168 | );
169 | }
170 |
--------------------------------------------------------------------------------
/components/categories-list.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { cn } from "@/lib/utils";
4 | import Link from "next/link";
5 | import { useParams, usePathname } from "next/navigation";
6 | import { Badge } from "./ui/badge";
7 |
8 | interface LinksProps {
9 | name: string;
10 | path: string;
11 | badge?: string;
12 | }
13 |
14 | const links: LinksProps[] = [
15 | { name: "Apps", path: "/apps" },
16 | { name: "Screens", path: "/screens" },
17 | { name: "UI Elements", path: "/ui-elements" },
18 | { name: "Flows", path: "/flows", badge: "PRO" },
19 | ];
20 |
21 | export function CategoriesList() {
22 | const pathname = usePathname();
23 | const params = useParams<{ platform: string }>();
24 |
25 | return (
26 |
27 | {links.map((link) => (
28 |
38 | {link.name}
39 | {link.badge ? {link.badge} : ""}
40 |
41 | ))}
42 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/components/checkbox-card.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ContextMenuCard } from "@/components/context-menu-card";
4 | import { cn } from "@/lib/utils";
5 | import Image from "next/image";
6 | import Link from "next/link";
7 | import { useState } from "react";
8 | import PhoneScreen from "../public/images/phone-screen.webp";
9 |
10 | import { Icons } from "@/components/icons";
11 | import { Button } from "@/components/ui/button";
12 | import { Checkbox } from "@/components/ui/checkbox";
13 | import {
14 | DropdownMenu,
15 | DropdownMenuContent,
16 | DropdownMenuItem,
17 | DropdownMenuTrigger,
18 | } from "@/components/ui/dropdown-menu";
19 | import {
20 | Tooltip,
21 | TooltipContent,
22 | TooltipPortal,
23 | TooltipProvider,
24 | TooltipTrigger,
25 | } from "@/components/ui/tooltip";
26 | import { useCheckboxSelection } from "@/hooks/use-checkbox-selection";
27 |
28 | export function CheckboxCard({ id }: { id: number }) {
29 | const [menuOpen, setMenuOpen] = useState(false);
30 | const useSelection = useCheckboxSelection();
31 | const items = useSelection.selectedItems;
32 |
33 | return (
34 |
35 |
36 |
37 |
38 |
46 |
47 |
54 |
55 |
61 | useSelection.toggleItem(id)}
67 | />
68 |
69 |
70 |
76 |
77 |
83 |
84 | setMenuOpen(!menuOpen)}
87 | >
88 |
89 |
90 |
95 |
96 |
97 |
98 |
99 |
104 |
105 |
109 |
110 | Copy screen
111 |
112 |
113 |
114 |
118 |
119 | Download as PNG
120 |
121 |
122 |
123 |
127 |
128 | Copy screen link
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | Download & Share
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
154 |
155 |
161 |
162 |
163 |
164 | alert("Copied!!")}
168 | >
169 | Copy
170 |
171 |
172 |
173 |
177 | Copy screen to clipboard
178 |
179 |
180 |
181 |
182 |
183 |
184 | alert("Saved!!")}
188 | >
189 | Save
190 |
191 |
192 |
193 |
197 | Save to collection
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 | Fake Name App
211 |
212 |
213 |
214 |
215 |
216 |
217 | );
218 | }
219 |
--------------------------------------------------------------------------------
/components/checkbox-toolbar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Tooltip,
5 | TooltipContent,
6 | TooltipPortal,
7 | TooltipProvider,
8 | TooltipTrigger,
9 | } from "@/components/ui/tooltip";
10 | import { Button } from "@/components/ui/button";
11 | import { Icons } from "@/components/icons";
12 | import { Badge } from "@/components/ui/badge";
13 | import { cn } from "@/lib/utils";
14 | import { useCheckboxSelection } from "@/hooks/use-checkbox-selection";
15 |
16 | export function CheckboxToolbar() {
17 | const useSelection = useCheckboxSelection();
18 | const countItems = useSelection.selectedItems.length;
19 |
20 | return (
21 |
22 |
0
26 | ? "translate-y-0 opacity-100"
27 | : "translate-y-16 opacity-0"
28 | )}
29 | >
30 |
31 | {countItems} selected
32 |
33 |
34 |
35 |
36 |
37 |
38 | {
43 | useSelection.clearSelection()
44 | }}
45 | >
46 | Clear all
47 |
48 |
49 |
50 |
51 | Clear selection
52 |
53 |
54 |
55 |
56 |
57 |
58 | alert("Download!!")}
63 | >
64 |
65 |
66 | PRO
67 |
68 |
69 |
70 |
71 |
72 | Download as PNG
73 |
74 |
75 |
76 |
77 |
78 |
79 | alert("Copied!!")}
84 | >
85 | Save
86 |
87 |
88 |
89 |
90 | Save to collection
91 |
92 |
93 |
94 |
95 |
96 |
97 | alert("Copied!!")}
101 | >
102 | Copy to Figma
103 |
104 |
105 |
106 |
107 | Paste into Mobbin's Figma plugin
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 | );
116 | }
117 |
--------------------------------------------------------------------------------
/components/command-content.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ScreensContent } from "@/components/hover-card-content";
4 | import { Icons } from "@/components/icons";
5 | import { Button } from "@/components/ui/button";
6 | import { CommandGroup, CommandItem } from "@/components/ui/command";
7 | import {
8 | HoverCard,
9 | HoverCardContent,
10 | HoverCardTrigger,
11 | } from "@/components/ui/hover-card";
12 | import { useMediaQuery } from "@/hooks/use-media-query";
13 | import { categoriesCommand, categoriesList } from "@/lib/_data";
14 | import { cn } from "@/lib/utils";
15 | import SquareLogo from "@/public/images/square-logo.webp";
16 | import { HoverCardPortal } from "@radix-ui/react-hover-card";
17 | import Image from "next/image";
18 | import { useCallback, useEffect, useRef, useState } from "react";
19 |
20 | interface CommandCategoriesListProps {
21 | category: string;
22 | setCategory: React.Dispatch>;
23 | }
24 |
25 | export function CommandCategoriesList({
26 | category,
27 | setCategory,
28 | }: CommandCategoriesListProps) {
29 | return (
30 |
31 | {categoriesCommand.map(({ key, label, icon }) => (
32 | setCategory(key)}
40 | tabIndex={-1}
41 | >
42 |
43 | {icon}
44 |
45 | {label}
46 |
47 | ))}
48 |
49 | );
50 | }
51 |
52 | export function Trending() {
53 | return (
54 | <>
55 |
56 |
57 |
58 |
59 |
60 | >
61 | );
62 | }
63 |
64 | export function ItemsLines({ title }: { title: string }) {
65 | return (
66 |
70 |
71 | {categoriesList.map((category, index) => (
72 |
76 |
77 | {category}
78 |
79 |
80 | {(index + 1) * 17}
81 |
82 |
83 | ))}
84 |
85 |
86 | );
87 | }
88 |
89 | export function ItemsLinesHoverCard({ title }: { title: string }) {
90 | const [selectedCat, setSelectedCat] = useState("");
91 | const prevDataValueRef = useRef(null);
92 | const isLargeDesktop = useMediaQuery("(min-width: 1440px)");
93 | const customBoundary = document.querySelector("#hc-boundary");
94 |
95 | useEffect(() => {
96 | const handleMutation = (mutations: MutationRecord[]) => {
97 | mutations.forEach(() => {
98 | const selectedCommandItem = document.querySelector(
99 | ".command-item[data-selected=true]"
100 | );
101 |
102 | if (selectedCommandItem) {
103 | const dataValue = selectedCommandItem.getAttribute("data-value");
104 |
105 | if (dataValue && dataValue !== prevDataValueRef.current) {
106 | prevDataValueRef.current = dataValue;
107 | setSelectedCat(dataValue);
108 | }
109 | }
110 | });
111 | };
112 |
113 | const targetNodes = document.querySelectorAll(".command-item");
114 | const observer = new MutationObserver(handleMutation);
115 | const config = {
116 | attributes: true,
117 | attributeFilter: ["data-selected"],
118 | childList: false,
119 | subtree: false,
120 | };
121 |
122 | targetNodes.forEach((node) => {
123 | observer.observe(node, config);
124 | });
125 |
126 | return () => {
127 | observer.disconnect();
128 | };
129 | }, []);
130 |
131 | return (
132 |
136 |
137 | {categoriesList.map((category, index) => (
138 |
143 |
144 |
151 |
152 | {category}
153 |
154 |
155 | {(index + 1) * 19}
156 |
157 |
158 |
159 |
160 |
161 |
171 |
172 |
173 |
174 |
175 | ))}
176 |
177 |
178 | );
179 | }
180 |
181 | function Apps() {
182 | return (
183 |
184 |
185 | {Array.from({ length: 7 }).map((_, index) => (
186 |
190 |
191 |
192 |
200 |
201 | Name {index}
202 |
203 |
204 |
205 |
206 | ))}
207 |
208 |
209 | );
210 | }
211 |
212 | function Screens() {
213 | return (
214 |
215 |
216 | {Array.from({ length: 7 }).map((_, index) => (
217 |
221 |
222 | Screens {index}
223 |
224 |
225 |
226 | ))}
227 |
228 |
229 | );
230 | }
231 |
232 | function UiElements() {
233 | return (
234 |
235 |
236 | {Array.from({ length: 7 }).map((_, index) => (
237 |
241 |
246 | Lorem {index}
247 |
248 |
249 | ))}
250 |
251 |
252 | );
253 | }
254 |
255 | function Flows() {
256 | return (
257 |
258 |
259 | {Array.from({ length: 4 }).map((_, index) => (
260 |
264 |
265 | Text {index}
266 |
267 |
268 |
269 | ))}
270 |
271 |
272 | );
273 | }
274 |
275 | function TextInScreenshot() {
276 | return (
277 |
278 |
279 | {Array.from({ length: 5 }).map((_, index) => (
280 |
284 |
289 | Screenshot {index}
290 |
291 |
292 | ))}
293 |
294 |
295 | );
296 | }
297 |
--------------------------------------------------------------------------------
/components/context-menu-card.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ContextMenu,
3 | ContextMenuContent,
4 | ContextMenuItem,
5 | ContextMenuSeparator,
6 | ContextMenuSub,
7 | ContextMenuSubContent,
8 | ContextMenuSubTrigger,
9 | ContextMenuTrigger,
10 | } from "@/components/ui/context-menu";
11 |
12 | interface ContextMenuCardProps {
13 | children: React.ReactNode;
14 | }
15 |
16 | export function ContextMenuCard({ children }: ContextMenuCardProps) {
17 | return (
18 |
19 | {children}
20 |
21 | Open App
22 | Open App in new tab
23 |
24 |
25 | Download screens
26 |
27 | Jan 24
28 |
29 |
30 | Copy app link
31 |
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/components/hero-homepage.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import Link from "next/link";
3 | import { Badge } from "./ui/badge";
4 | import { Button } from "./ui/button";
5 |
6 | export const HeroHomepage = () => {
7 | return (
8 |
9 |
10 |
17 |
18 |
19 | NEW DROPS WEEKLY
20 |
21 |
22 | The world's largest mobile and web design library
23 |
24 |
25 |
26 | Save hours of UI & UX research with our library of 300,000+
27 | screens from the world's best designed apps.
28 |
29 |
30 |
31 |
32 |
33 | Create free account
34 |
35 |
36 | See all plans
37 |
38 |
39 |
40 |
41 |
42 | Trusted by design-forward companies
43 |
44 |
45 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
72 |
73 |
74 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
120 |
127 |
128 |
129 |
130 |
131 |
132 | );
133 | };
134 |
--------------------------------------------------------------------------------
/components/hero-switcher.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { CarouselProgressBar } from "@/components/carousels/carousel-progress-bar";
4 | import { HeroHomepage } from "@/components/hero-homepage";
5 | import { DropdownMenuCheckboxItem } from "@/components/ui/dropdown-menu";
6 | import { useHero } from "@/hooks/use-hero-selection";
7 |
8 | export function HeroCheckbox() {
9 | const { isChecked, toggleCheckbox } = useHero();
10 | return (
11 |
16 | Static Hero
17 |
18 | );
19 | }
20 |
21 | export function HerosComponent() {
22 | const { isChecked } = useHero();
23 |
24 | return <>{isChecked ? : }>;
25 | }
26 |
--------------------------------------------------------------------------------
/components/hover-card-content.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 |
3 | export const ScreensContent = ({ name }: { name: string }) => {
4 | return (
5 |
6 |
7 | {Array.from({ length: 3 }).map((_, index) => (
8 |
9 |
17 |
18 | ))}
19 |
20 |
21 |
22 | {name} - Screens display the user’s profile information and/or
23 | account settings.
24 |
25 |
26 | );
27 | };
28 |
29 | export const WebContent = ({ name }: { name: string }) => {
30 | return (
31 |
32 |
43 |
44 |
45 | {name} - Screens display the user’s profile information and/or
46 | account settings.
47 |
48 |
49 | );
50 | };
--------------------------------------------------------------------------------
/components/hover-popover.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import {
3 | Popover,
4 | PopoverContent,
5 | PopoverTrigger,
6 | } from "@/components/ui/popover";
7 |
8 | const HoverPopover = () => {
9 | const [open, setOpen] = useState(false);
10 |
11 | const handleMouseEnter = () => {
12 | setOpen(true);
13 | };
14 |
15 | const handleMouseLeave = () => {
16 | setOpen(false);
17 | };
18 |
19 | return (
20 |
21 | {
25 | e.preventDefault();
26 | }}
27 | >
28 | Open
29 |
30 |
34 | Place content for the popover here.
35 |
36 |
37 | );
38 | };
39 |
40 | export default HoverPopover;
41 |
--------------------------------------------------------------------------------
/components/icons.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ArrowRightSquare,
3 | ArrowUpRight,
4 | Bookmark,
5 | Check,
6 | ChevronLeft,
7 | ChevronRight,
8 | Copy,
9 | CreditCard,
10 | Download,
11 | File,
12 | FileText,
13 | HelpCircle,
14 | Image,
15 | Link,
16 | Laptop,
17 | LayoutGrid,
18 | Loader2,
19 | LucideProps,
20 | Moon,
21 | MoreHorizontal,
22 | Palette,
23 | Plus,
24 | Search,
25 | Settings,
26 | Settings2,
27 | Sparkles,
28 | SunMedium,
29 | Trash,
30 | User,
31 | X,
32 | type IconNode as LucideIcon,
33 | } from "lucide-react";
34 |
35 | export type Icon = LucideIcon;
36 |
37 | export const Icons = {
38 | arrowUpRight: ArrowUpRight,
39 | bookmark: Bookmark,
40 | logo: Palette,
41 | close: X,
42 | spinner: Loader2,
43 | chevronLeft: ChevronLeft,
44 | chevronRight: ChevronRight,
45 | download: Download,
46 | link: Link,
47 | trash: Trash,
48 | post: FileText,
49 | page: File,
50 | media: Image,
51 | settings: Settings,
52 | settings2: Settings2,
53 | billing: CreditCard,
54 | options: MoreHorizontal,
55 | add: Plus,
56 | search: Search,
57 | user: User,
58 | help: HelpCircle,
59 | sun: SunMedium,
60 | moon: Moon,
61 | laptop: Laptop,
62 | check: Check,
63 | copy: Copy,
64 | sparkles: Sparkles,
65 | layoutGrid: LayoutGrid,
66 | arrowRightSquare: ArrowRightSquare,
67 | gitHub: ({ ...props }: LucideProps) => (
68 |
78 |
82 |
83 | ),
84 | twitter: (props: LucideProps) => (
85 |
95 |
99 |
100 | ),
101 | menu: (props: LucideProps) => (
102 |
108 | menu icon
109 |
116 |
123 |
124 | ),
125 | };
126 |
--------------------------------------------------------------------------------
/components/modal-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { SearchCommandDialog } from "@/components/modals/search-command-dialog";
4 | import { useMounted } from "@/hooks/use-mounted";
5 |
6 | export const ModalProvider = () => {
7 | const mounted = useMounted();
8 |
9 | if (!mounted) {
10 | return null;
11 | }
12 |
13 | return (
14 | <>
15 |
16 | {/* add your own modals here... */}
17 | >
18 | );
19 | };
20 |
--------------------------------------------------------------------------------
/components/modals/search-command-dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useSearchModal } from "@/hooks/use-search-modal";
4 | import { cn } from "@/lib/utils";
5 |
6 | import {
7 | CommandDialog,
8 | CommandEmpty,
9 | CommandInput,
10 | CommandItem,
11 | CommandList,
12 | } from "@/components/ui/command";
13 |
14 | import {
15 | CommandCategoriesList,
16 | ItemsLines,
17 | ItemsLinesHoverCard,
18 | Trending,
19 | } from "@/components/command-content";
20 | import { Button } from "@/components/ui/button";
21 | import { ScrollArea } from "@/components/ui/scroll-area";
22 | import { categoriesCommand } from "@/lib/_data";
23 | import React, { useEffect } from "react";
24 |
25 | export function SearchCommandDialog() {
26 | const searchModal = useSearchModal();
27 | const [search, setSearch] = React.useState("");
28 | const [category, setCategory] = React.useState("trending");
29 |
30 | useEffect(() => {
31 | const handleKeyDown = (e: KeyboardEvent) => {
32 | if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
33 | e.preventDefault();
34 | searchModal.onOpen();
35 | }
36 |
37 | if (searchModal.isOpen) {
38 | if (e.key === "Tab") {
39 | e.preventDefault();
40 | const currentIndex = categoriesCommand.findIndex(
41 | (item) => item.key === category
42 | );
43 | const nextIndex = (currentIndex + 1) % categoriesCommand.length;
44 | setCategory(categoriesCommand[nextIndex].key);
45 | }
46 | }
47 | };
48 |
49 | document.addEventListener("keydown", handleKeyDown);
50 |
51 | return () => {
52 | document.removeEventListener("keydown", handleKeyDown);
53 | };
54 | }, [searchModal, category]);
55 |
56 | return (
57 |
67 |
68 |
75 |
76 |
81 | Cancel
82 |
83 |
84 |
85 |
86 | {!search ? (
87 | <>
88 |
92 |
93 |
94 |
95 | {category === "trending" && (
96 | <>
97 |
98 | hidden item
99 |
100 |
101 | >
102 | )}
103 |
104 | {category === "screens" && (
105 |
106 | )}
107 |
108 | {category === "ui-elements" && (
109 |
110 | )}
111 |
112 | {category === "flows" && (
113 |
114 | )}
115 |
116 |
117 | >
118 | ) : (
119 | <>
120 | search list
121 | No results found.
122 | >
123 | )}
124 |
125 |
126 | );
127 | }
128 |
--------------------------------------------------------------------------------
/components/nav-links.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Link from "next/link";
4 | import { useParams } from "next/navigation";
5 | import { Button } from "./ui/button";
6 |
7 | const links = [
8 | { name: "iOS", path: "/browse/ios" },
9 | { name: "Android", path: "/browse/android" },
10 | { name: "Web", path: "/browse/web" },
11 | ];
12 |
13 | export const NavLinks = () => {
14 | const params = useParams<{ platform: string; feature: string }>();
15 |
16 | return (
17 | <>
18 | {links.map((link) => (
19 |
20 |
28 | {link.name}
29 |
30 |
31 | ))}
32 | >
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/components/nav-menu.tsx:
--------------------------------------------------------------------------------
1 | import { Icons } from "@/components/icons";
2 | import { Button } from "@/components/ui/button";
3 | import {
4 | DropdownMenu,
5 | DropdownMenuContent,
6 | DropdownMenuItem,
7 | DropdownMenuSeparator,
8 | DropdownMenuTrigger,
9 | } from "@/components/ui/dropdown-menu";
10 | import { siteConfig } from "@/config/site";
11 | import Link from "next/link";
12 | import ThemeToggle from "./theme-toggle";
13 | import { HeroCheckbox } from "./hero-switcher";
14 |
15 | export function NavMenu() {
16 | return (
17 |
18 |
19 |
24 |
25 |
26 |
27 |
28 |
29 |
34 |
35 | Collections
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
50 | Github
51 |
52 |
53 |
54 |
55 |
60 | Twitter
61 |
62 |
63 |
64 |
65 |
70 | Portfolio
71 |
72 |
73 |
74 |
75 |
76 | );
77 | }
78 |
--------------------------------------------------------------------------------
/components/search-button-nav.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Icons } from "@/components/icons";
4 | import { Button } from "@/components/ui/button";
5 | import { useSearchModal } from "@/hooks/use-search-modal";
6 |
7 | export function SearchButtonNav() {
8 | const searchModal = useSearchModal();
9 |
10 | return (
11 |
18 |
19 |
20 |
21 |
22 | Search on iOS...
23 |
24 |
25 |
26 |
27 | ⌘
28 |
29 |
30 | K
31 |
32 |
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/components/tags-list.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { CarouselButtonsTags } from "@/components/carousels/carousel-buttons-tags";
4 | import { Icons } from "@/components/icons";
5 | import { Button } from "@/components/ui/button";
6 | import { Separator } from "@/components/ui/separator";
7 | import { useSearchModal } from "@/hooks/use-search-modal";
8 |
9 | export function TagsList() {
10 | const searchModal = useSearchModal();
11 |
12 | return (
13 |
14 |
21 |
22 |
23 | Filters
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/components/tailwind-indicator.tsx:
--------------------------------------------------------------------------------
1 | export function TailwindIndicator() {
2 | if (process.env.NODE_ENV === "production") return null;
3 |
4 | return (
5 |
6 |
xs
7 |
8 | sm
9 |
10 |
md
11 |
lg
12 |
xl
13 |
2xl
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/components/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { ThemeProvider as NextThemesProvider } from "next-themes"
5 | import type { ThemeProviderProps } from "next-themes/dist/types"
6 |
7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
8 | return {children}
9 | }
10 |
--------------------------------------------------------------------------------
/components/theme-toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { Button } from '@/components/ui/button';
4 | import { Skeleton } from '@/components/ui/skeleton';
5 | import { useMounted } from '@/hooks/use-mounted';
6 | import { Monitor, Moon, Sun } from 'lucide-react';
7 | import { useTheme } from "next-themes";
8 |
9 | export default function ThemeToggle() {
10 | const mounted = useMounted();
11 | const { theme, setTheme } = useTheme();
12 |
13 | if (!mounted) {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 | )
21 | };
22 |
23 | return (
24 |
25 | setTheme("dark")}
30 | >
31 |
32 |
33 | setTheme("light")}
38 | >
39 |
40 |
41 | setTheme("system")}
46 | >
47 |
48 |
49 |
50 | );
51 | }
--------------------------------------------------------------------------------
/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | }
24 | )
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | )
34 | }
35 |
36 | export { Badge, badgeVariants }
37 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Slot } from "@radix-ui/react-slot";
3 | import { cva, type VariantProps } from "class-variance-authority";
4 |
5 | import { cn } from "@/lib/utils";
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-2xl text-base font-medium transition-colors focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-blue-300 disabled:pointer-events-none disabled:opacity-50 cursor-pointer",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-foreground/10",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-muted-foreground hover:text-primary",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 px-3",
25 | lg: "h-11 px-4",
26 | icon: "size-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | );
35 |
36 | export interface ButtonProps extends React.ButtonHTMLAttributes,
37 | VariantProps {
38 | asChild?: boolean;
39 | }
40 |
41 | const Button = React.forwardRef(
42 | ({ className, variant, size, asChild = false, ...props }, ref) => {
43 | const Comp = asChild ? Slot : "button";
44 | return (
45 |
50 | );
51 | }
52 | );
53 | Button.displayName = "Button";
54 |
55 | export { Button, buttonVariants };
56 |
--------------------------------------------------------------------------------
/components/ui/carousel.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import {
3 | type EmblaOptionsType as CarouselOptions,
4 | type EmblaCarouselType as CarouselApi,
5 | type EmblaPluginType as CarouselPlugin,
6 | } from "embla-carousel";
7 | import useEmblaCarousel from "embla-carousel-react";
8 | import { ArrowLeft, ArrowRight } from "lucide-react";
9 |
10 | import { cn } from "@/lib/utils";
11 | import { Button } from "@/components/ui/button";
12 |
13 | type CarouselProps = {
14 | opts?: CarouselOptions;
15 | plugins?: CarouselPlugin[];
16 | orientation?: "horizontal" | "vertical";
17 | setApi?: (api: CarouselApi) => void;
18 | };
19 |
20 | type CarouselContextProps = {
21 | carouselRef: ReturnType[0];
22 | api: ReturnType[1];
23 | scrollPrev: () => void;
24 | scrollNext: () => void;
25 | canScrollPrev: boolean;
26 | canScrollNext: boolean;
27 | } & CarouselProps;
28 |
29 | const CarouselContext = React.createContext(null);
30 |
31 | function useCarousel() {
32 | const context = React.useContext(CarouselContext);
33 |
34 | if (!context) {
35 | throw new Error("useCarousel must be used within a ");
36 | }
37 |
38 | return context;
39 | }
40 |
41 | const Carousel = React.forwardRef<
42 | HTMLDivElement,
43 | React.HTMLAttributes & CarouselProps
44 | >(
45 | (
46 | {
47 | orientation = "horizontal",
48 | opts,
49 | setApi,
50 | plugins,
51 | className,
52 | children,
53 | ...props
54 | },
55 | ref
56 | ) => {
57 | const [carouselRef, api] = useEmblaCarousel(
58 | {
59 | ...opts,
60 | axis: orientation === "horizontal" ? "x" : "y",
61 | },
62 | plugins
63 | );
64 | const [canScrollPrev, setCanScrollPrev] = React.useState(false);
65 | const [canScrollNext, setCanScrollNext] = React.useState(false);
66 |
67 | const onSelect = React.useCallback((api: CarouselApi) => {
68 | if (!api) {
69 | return;
70 | }
71 |
72 | setCanScrollPrev(api.canScrollPrev());
73 | setCanScrollNext(api.canScrollNext());
74 | }, []);
75 |
76 | const scrollPrev = React.useCallback(() => {
77 | api?.scrollPrev();
78 | }, [api]);
79 |
80 | const scrollNext = React.useCallback(() => {
81 | api?.scrollNext();
82 | }, [api]);
83 |
84 | const handleKeyDown = React.useCallback(
85 | (event: React.KeyboardEvent) => {
86 | if (event.key === "ArrowLeft") {
87 | event.preventDefault();
88 | scrollPrev();
89 | } else if (event.key === "ArrowRight") {
90 | event.preventDefault();
91 | scrollNext();
92 | }
93 | },
94 | [scrollPrev, scrollNext]
95 | );
96 |
97 | React.useEffect(() => {
98 | if (!api || !setApi) {
99 | return;
100 | }
101 |
102 | setApi(api);
103 | }, [api, setApi]);
104 |
105 | React.useEffect(() => {
106 | if (!api) {
107 | return;
108 | }
109 |
110 | onSelect(api);
111 | api.on("reInit", onSelect);
112 | api.on("select", onSelect);
113 |
114 | return () => {
115 | api?.off("select", onSelect);
116 | };
117 | }, [api, onSelect]);
118 |
119 | return (
120 |
133 |
141 | {children}
142 |
143 |
144 | );
145 | }
146 | );
147 | Carousel.displayName = "Carousel";
148 |
149 | const CarouselContent = React.forwardRef<
150 | HTMLDivElement,
151 | React.HTMLAttributes
152 | >(({ className, ...props }, ref) => {
153 | const { carouselRef, orientation } = useCarousel();
154 |
155 | return (
156 |
167 | );
168 | });
169 | CarouselContent.displayName = "CarouselContent";
170 |
171 | const CarouselItem = React.forwardRef<
172 | HTMLDivElement,
173 | React.HTMLAttributes
174 | >(({ className, ...props }, ref) => {
175 | const { orientation } = useCarousel();
176 |
177 | return (
178 |
189 | );
190 | });
191 | CarouselItem.displayName = "CarouselItem";
192 |
193 | const CarouselPrevious = React.forwardRef<
194 | HTMLButtonElement,
195 | React.ComponentProps
196 | >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
197 | const { orientation, scrollPrev, canScrollPrev } = useCarousel();
198 |
199 | return (
200 |
215 |
216 | Previous slide
217 |
218 | );
219 | });
220 | CarouselPrevious.displayName = "CarouselPrevious";
221 |
222 | const CarouselNext = React.forwardRef<
223 | HTMLButtonElement,
224 | React.ComponentProps
225 | >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
226 | const { orientation, scrollNext, canScrollNext } = useCarousel();
227 |
228 | return (
229 |
244 |
245 | Next slide
246 |
247 | );
248 | });
249 | CarouselNext.displayName = "CarouselNext";
250 |
251 | export {
252 | type CarouselApi,
253 | Carousel,
254 | CarouselContent,
255 | CarouselItem,
256 | CarouselPrevious,
257 | CarouselNext,
258 | };
259 |
--------------------------------------------------------------------------------
/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5 | import { Check } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Checkbox = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
21 |
24 |
25 |
26 |
27 | ))
28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName
29 |
30 | export { Checkbox }
31 |
--------------------------------------------------------------------------------
/components/ui/command.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { type DialogProps } from "@radix-ui/react-dialog";
5 | import { Command as CommandPrimitive } from "cmdk";
6 | import { Search } from "lucide-react";
7 |
8 | import { cn } from "@/lib/utils";
9 | import { Dialog, DialogContent } from "@/components/ui/dialog";
10 |
11 | const Command = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
23 | ));
24 | Command.displayName = CommandPrimitive.displayName;
25 |
26 | interface CommandDialogProps extends DialogProps {
27 | className?: string;
28 | }
29 |
30 | const CommandDialog = ({
31 | children,
32 | className,
33 | ...props
34 | }: CommandDialogProps) => {
35 | return (
36 |
37 |
41 |
46 | {children}
47 |
48 |
49 |
50 | );
51 | };
52 |
53 | const CommandInput = React.forwardRef<
54 | React.ElementRef,
55 | React.ComponentPropsWithoutRef
56 | >(({ className, ...props }, ref) => (
57 |
58 |
59 |
67 |
68 | ));
69 |
70 | CommandInput.displayName = CommandPrimitive.Input.displayName;
71 |
72 | const CommandList = React.forwardRef<
73 | React.ElementRef,
74 | React.ComponentPropsWithoutRef
75 | >(({ className, ...props }, ref) => (
76 |
81 | ));
82 |
83 | CommandList.displayName = CommandPrimitive.List.displayName;
84 |
85 | const CommandEmpty = React.forwardRef<
86 | React.ElementRef,
87 | React.ComponentPropsWithoutRef
88 | >((props, ref) => (
89 |
94 | ));
95 |
96 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
97 |
98 | const CommandGroup = React.forwardRef<
99 | React.ElementRef,
100 | React.ComponentPropsWithoutRef
101 | >(({ className, ...props }, ref) => (
102 |
110 | ));
111 |
112 | CommandGroup.displayName = CommandPrimitive.Group.displayName;
113 |
114 | const CommandSeparator = React.forwardRef<
115 | React.ElementRef,
116 | React.ComponentPropsWithoutRef
117 | >(({ className, ...props }, ref) => (
118 |
123 | ));
124 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
125 |
126 | const CommandItem = React.forwardRef<
127 | React.ElementRef,
128 | React.ComponentPropsWithoutRef
129 | >(({ className, ...props }, ref) => (
130 |
138 | ));
139 |
140 | CommandItem.displayName = CommandPrimitive.Item.displayName;
141 |
142 | const CommandShortcut = ({
143 | className,
144 | ...props
145 | }: React.HTMLAttributes) => {
146 | return (
147 |
154 | );
155 | };
156 | CommandShortcut.displayName = "CommandShortcut";
157 |
158 | export {
159 | Command,
160 | CommandDialog,
161 | CommandInput,
162 | CommandList,
163 | CommandEmpty,
164 | CommandGroup,
165 | CommandItem,
166 | CommandShortcut,
167 | CommandSeparator,
168 | };
169 |
--------------------------------------------------------------------------------
/components/ui/context-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
5 | import { Check, ChevronRight, Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const ContextMenu = ContextMenuPrimitive.Root
10 |
11 | const ContextMenuTrigger = ContextMenuPrimitive.Trigger
12 |
13 | const ContextMenuGroup = ContextMenuPrimitive.Group
14 |
15 | const ContextMenuPortal = ContextMenuPrimitive.Portal
16 |
17 | const ContextMenuSub = ContextMenuPrimitive.Sub
18 |
19 | const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
20 |
21 | const ContextMenuSubTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef & {
24 | inset?: boolean
25 | }
26 | >(({ className, inset, children, ...props }, ref) => (
27 |
36 | {children}
37 |
38 |
39 | ))
40 | ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
41 |
42 | const ContextMenuSubContent = React.forwardRef<
43 | React.ElementRef,
44 | React.ComponentPropsWithoutRef
45 | >(({ className, ...props }, ref) => (
46 |
54 | ))
55 | ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
56 |
57 | const ContextMenuContent = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, ...props }, ref) => (
61 |
62 |
70 |
71 | ))
72 | ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
73 |
74 | const ContextMenuItem = React.forwardRef<
75 | React.ElementRef,
76 | React.ComponentPropsWithoutRef & {
77 | inset?: boolean
78 | }
79 | >(({ className, inset, ...props }, ref) => (
80 |
89 | ))
90 | ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
91 |
92 | const ContextMenuCheckboxItem = React.forwardRef<
93 | React.ElementRef,
94 | React.ComponentPropsWithoutRef
95 | >(({ className, children, checked, ...props }, ref) => (
96 |
105 |
106 |
107 |
108 |
109 |
110 | {children}
111 |
112 | ))
113 | ContextMenuCheckboxItem.displayName =
114 | ContextMenuPrimitive.CheckboxItem.displayName
115 |
116 | const ContextMenuRadioItem = React.forwardRef<
117 | React.ElementRef,
118 | React.ComponentPropsWithoutRef
119 | >(({ className, children, ...props }, ref) => (
120 |
128 |
129 |
130 |
131 |
132 |
133 | {children}
134 |
135 | ))
136 | ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
137 |
138 | const ContextMenuLabel = React.forwardRef<
139 | React.ElementRef,
140 | React.ComponentPropsWithoutRef & {
141 | inset?: boolean
142 | }
143 | >(({ className, inset, ...props }, ref) => (
144 |
153 | ))
154 | ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
155 |
156 | const ContextMenuSeparator = React.forwardRef<
157 | React.ElementRef,
158 | React.ComponentPropsWithoutRef
159 | >(({ className, ...props }, ref) => (
160 |
165 | ))
166 | ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
167 |
168 | const ContextMenuShortcut = ({
169 | className,
170 | ...props
171 | }: React.HTMLAttributes) => {
172 | return (
173 |
180 | )
181 | }
182 | ContextMenuShortcut.displayName = "ContextMenuShortcut"
183 |
184 | export {
185 | ContextMenu,
186 | ContextMenuTrigger,
187 | ContextMenuContent,
188 | ContextMenuItem,
189 | ContextMenuCheckboxItem,
190 | ContextMenuRadioItem,
191 | ContextMenuLabel,
192 | ContextMenuSeparator,
193 | ContextMenuShortcut,
194 | ContextMenuGroup,
195 | ContextMenuPortal,
196 | ContextMenuSub,
197 | ContextMenuSubContent,
198 | ContextMenuSubTrigger,
199 | ContextMenuRadioGroup,
200 | }
201 |
--------------------------------------------------------------------------------
/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as DialogPrimitive from "@radix-ui/react-dialog";
5 | import { X } from "lucide-react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const Dialog = DialogPrimitive.Root;
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger;
12 |
13 | const DialogPortal = DialogPrimitive.Portal;
14 |
15 | const DialogClose = DialogPrimitive.Close;
16 |
17 | const DialogOverlay = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, ...props }, ref) => (
21 |
29 | ));
30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
31 |
32 | const DialogContent = React.forwardRef<
33 | React.ElementRef,
34 | React.ComponentPropsWithoutRef & {
35 | closeButtonDisabled?: boolean;
36 | }
37 | >(({ className, children, closeButtonDisabled, ...props }, ref) => (
38 |
39 |
40 |
48 | {children}
49 | {!closeButtonDisabled && (
50 |
51 |
52 | Close
53 |
54 | )}
55 |
56 |
57 | ));
58 | DialogContent.displayName = DialogPrimitive.Content.displayName;
59 |
60 | const DialogHeader = ({
61 | className,
62 | ...props
63 | }: React.HTMLAttributes) => (
64 |
71 | );
72 | DialogHeader.displayName = "DialogHeader";
73 |
74 | const DialogFooter = ({
75 | className,
76 | ...props
77 | }: React.HTMLAttributes) => (
78 |
85 | );
86 | DialogFooter.displayName = "DialogFooter";
87 |
88 | const DialogTitle = React.forwardRef<
89 | React.ElementRef,
90 | React.ComponentPropsWithoutRef
91 | >(({ className, ...props }, ref) => (
92 |
100 | ));
101 | DialogTitle.displayName = DialogPrimitive.Title.displayName;
102 |
103 | const DialogDescription = React.forwardRef<
104 | React.ElementRef,
105 | React.ComponentPropsWithoutRef
106 | >(({ className, ...props }, ref) => (
107 |
112 | ));
113 | DialogDescription.displayName = DialogPrimitive.Description.displayName;
114 |
115 | export {
116 | Dialog,
117 | DialogPortal,
118 | DialogOverlay,
119 | DialogClose,
120 | DialogTrigger,
121 | DialogContent,
122 | DialogHeader,
123 | DialogFooter,
124 | DialogTitle,
125 | DialogDescription,
126 | };
127 |
--------------------------------------------------------------------------------
/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5 | import { Check, ChevronRight, Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const DropdownMenu = DropdownMenuPrimitive.Root
10 |
11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
12 |
13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group
14 |
15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal
16 |
17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub
18 |
19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
20 |
21 | const DropdownMenuSubTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef & {
24 | inset?: boolean
25 | }
26 | >(({ className, inset, children, ...props }, ref) => (
27 |
36 | {children}
37 |
38 |
39 | ))
40 | DropdownMenuSubTrigger.displayName =
41 | DropdownMenuPrimitive.SubTrigger.displayName
42 |
43 | const DropdownMenuSubContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, ...props }, ref) => (
47 |
55 | ))
56 | DropdownMenuSubContent.displayName =
57 | DropdownMenuPrimitive.SubContent.displayName
58 |
59 | const DropdownMenuContent = React.forwardRef<
60 | React.ElementRef,
61 | React.ComponentPropsWithoutRef
62 | >(({ className, sideOffset = 15, ...props }, ref) => (
63 |
64 |
73 |
74 | ))
75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
76 |
77 | const DropdownMenuItem = React.forwardRef<
78 | React.ElementRef,
79 | React.ComponentPropsWithoutRef & {
80 | inset?: boolean
81 | }
82 | >(({ className, inset, ...props }, ref) => (
83 |
92 | ))
93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
94 |
95 | const DropdownMenuCheckboxItem = React.forwardRef<
96 | React.ElementRef,
97 | React.ComponentPropsWithoutRef
98 | >(({ className, children, checked, ...props }, ref) => (
99 |
108 |
109 |
110 |
111 |
112 |
113 | {children}
114 |
115 | ))
116 | DropdownMenuCheckboxItem.displayName =
117 | DropdownMenuPrimitive.CheckboxItem.displayName
118 |
119 | const DropdownMenuRadioItem = React.forwardRef<
120 | React.ElementRef,
121 | React.ComponentPropsWithoutRef
122 | >(({ className, children, ...props }, ref) => (
123 |
131 |
132 |
133 |
134 |
135 |
136 | {children}
137 |
138 | ))
139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
140 |
141 | const DropdownMenuLabel = React.forwardRef<
142 | React.ElementRef,
143 | React.ComponentPropsWithoutRef & {
144 | inset?: boolean
145 | }
146 | >(({ className, inset, ...props }, ref) => (
147 |
156 | ))
157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
158 |
159 | const DropdownMenuSeparator = React.forwardRef<
160 | React.ElementRef,
161 | React.ComponentPropsWithoutRef
162 | >(({ className, ...props }, ref) => (
163 |
168 | ))
169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
170 |
171 | const DropdownMenuShortcut = ({
172 | className,
173 | ...props
174 | }: React.HTMLAttributes) => {
175 | return (
176 |
180 | )
181 | }
182 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
183 |
184 | export {
185 | DropdownMenu,
186 | DropdownMenuTrigger,
187 | DropdownMenuContent,
188 | DropdownMenuItem,
189 | DropdownMenuCheckboxItem,
190 | DropdownMenuRadioItem,
191 | DropdownMenuLabel,
192 | DropdownMenuSeparator,
193 | DropdownMenuShortcut,
194 | DropdownMenuGroup,
195 | DropdownMenuPortal,
196 | DropdownMenuSub,
197 | DropdownMenuSubContent,
198 | DropdownMenuSubTrigger,
199 | DropdownMenuRadioGroup,
200 | }
201 |
--------------------------------------------------------------------------------
/components/ui/hover-card.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const HoverCard = HoverCardPrimitive.Root;
9 |
10 | const HoverCardTrigger = HoverCardPrimitive.Trigger;
11 |
12 | const HoverCardContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
28 | ));
29 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
30 |
31 | export { HoverCard, HoverCardTrigger, HoverCardContent };
32 |
--------------------------------------------------------------------------------
/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as PopoverPrimitive from "@radix-ui/react-popover"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Popover = PopoverPrimitive.Root
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ))
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
30 |
31 | export { Popover, PopoverTrigger, PopoverContent }
32 |
--------------------------------------------------------------------------------
/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ScrollArea = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
17 |
18 | {children}
19 |
20 |
21 |
22 |
23 | ))
24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
25 |
26 | const ScrollBar = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, orientation = "vertical", ...props }, ref) => (
30 |
43 |
44 |
45 | ))
46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
47 |
48 | export { ScrollArea, ScrollBar }
49 |
--------------------------------------------------------------------------------
/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | )
13 | }
14 |
15 | export { Skeleton }
16 |
--------------------------------------------------------------------------------
/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider
9 |
10 | const Tooltip = TooltipPrimitive.Root
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger
13 |
14 | const TooltipPortal = TooltipPrimitive.Portal
15 |
16 | const TooltipContent = React.forwardRef<
17 | React.ElementRef,
18 | React.ComponentPropsWithoutRef
19 | >(({ className, sideOffset = 4, ...props }, ref) => (
20 |
29 | ))
30 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
31 |
32 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider, TooltipPortal }
33 |
--------------------------------------------------------------------------------
/config/site.ts:
--------------------------------------------------------------------------------
1 | interface SiteConfig {
2 | name: string;
3 | description: string;
4 | url: string;
5 | ogImage: string;
6 | links: {
7 | twitter: string;
8 | github: string;
9 | };
10 | }
11 |
12 | export const siteConfig: SiteConfig = {
13 | name: "Next Mobbin Clone",
14 | description:
15 | "An ui colorgen application built to help you with color setup in shadcn/ui.",
16 | url: "https://next-mobbin-clone.app",
17 | ogImage: "https://next-mobbin-clone.app/og.jpg",
18 | links: {
19 | twitter: "https://twitter.com/miickasmt",
20 | github: "https://github.com/mickasmt/next-mobbin-clone",
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/hooks/use-checkbox-selection.ts:
--------------------------------------------------------------------------------
1 | import { create } from "zustand";
2 |
3 | type CheckboxSelectionStore = {
4 | selectedItems: number[];
5 | toggleItem: (item: number) => void;
6 | clearSelection: () => void;
7 | };
8 |
9 | export const useCheckboxSelection = create((set) => ({
10 | selectedItems: [],
11 | toggleItem: (item) =>
12 | set((state) => ({
13 | selectedItems: state.selectedItems.includes(item)
14 | ? state.selectedItems.filter((i) => i !== item)
15 | : [...state.selectedItems, item],
16 | })),
17 | clearSelection: () => set({ selectedItems: [] }),
18 | }));
--------------------------------------------------------------------------------
/hooks/use-hero-selection.ts:
--------------------------------------------------------------------------------
1 | import { create } from "zustand";
2 |
3 | interface useHeroStore {
4 | isChecked: boolean;
5 | toggleCheckbox: () => void;
6 | }
7 |
8 | export const useHero = create((set) => ({
9 | isChecked: false,
10 | toggleCheckbox: () => set((state) => ({ isChecked: !state.isChecked })),
11 | }));
--------------------------------------------------------------------------------
/hooks/use-lock-body.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | // @see https://usehooks.com/useLockBodyScroll.
4 | export function useLockBody() {
5 | React.useLayoutEffect((): (() => void) => {
6 | const originalStyle: string = window.getComputedStyle(
7 | document.body
8 | ).overflow
9 | document.body.style.overflow = "hidden"
10 | return () => (document.body.style.overflow = originalStyle)
11 | }, [])
12 | }
13 |
--------------------------------------------------------------------------------
/hooks/use-media-query.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | export function useMediaQuery(query: string) {
4 | const [value, setValue] = React.useState(false);
5 |
6 | React.useEffect(() => {
7 | function onChange(event: MediaQueryListEvent) {
8 | setValue(event.matches);
9 | }
10 |
11 | const result = matchMedia(query);
12 | result.addEventListener("change", onChange);
13 | setValue(result.matches);
14 |
15 | return () => result.removeEventListener("change", onChange);
16 | }, [query]);
17 |
18 | return value;
19 | }
20 |
--------------------------------------------------------------------------------
/hooks/use-mounted.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | export function useMounted() {
4 | const [mounted, setMounted] = React.useState(false)
5 |
6 | React.useEffect(() => {
7 | setMounted(true)
8 | }, [])
9 |
10 | return mounted
11 | }
12 |
--------------------------------------------------------------------------------
/hooks/use-search-modal.ts:
--------------------------------------------------------------------------------
1 | import { create } from "zustand";
2 |
3 | interface useSearchModalStore {
4 | isOpen: boolean;
5 | onOpen: () => void;
6 | onClose: () => void;
7 | }
8 |
9 | export const useSearchModal = create((set) => ({
10 | isOpen: false,
11 | onOpen: () => set({ isOpen: true }),
12 | onClose: () => set({ isOpen: false }),
13 | }));
14 |
--------------------------------------------------------------------------------
/lib/_data.tsx:
--------------------------------------------------------------------------------
1 | import { Icons } from "@/components/icons";
2 |
3 | interface CategoryCommandProps {
4 | key: string;
5 | label: string;
6 | icon: React.ReactElement;
7 | }
8 |
9 | export const categoriesCommand: CategoryCommandProps[] = [
10 | {
11 | key: "trending",
12 | label: "Trending",
13 | icon: ,
14 | },
15 | {
16 | key: "screens",
17 | label: "Screens",
18 | icon: ,
19 | },
20 | {
21 | key: "ui-elements",
22 | label: "UI Elements",
23 | icon: ,
24 | },
25 | {
26 | key: "flows",
27 | label: "Flows",
28 | icon: ,
29 | },
30 | ];
31 |
32 | export const categoriesList: string[] = [
33 | "Business",
34 | "Finance",
35 | "CRM",
36 | "Shopping",
37 | "Artificial Intelligence",
38 | "Education",
39 | "Food & Drink",
40 | "Health & Fitness",
41 | "Lifestyle",
42 | "Entertainment",
43 | "Travel & Transportation",
44 | "Communication",
45 | "Crypto & Web3",
46 | "Social Networking",
47 | "Medical",
48 | ];
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | async redirects() {
4 | return [
5 | {
6 | source: '/',
7 | destination: '/browse/ios/apps',
8 | permanent: true,
9 | },
10 | ];
11 | },
12 | }
13 |
14 | module.exports = nextConfig
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-mobbin-clone",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@radix-ui/react-checkbox": "^1.0.4",
13 | "@radix-ui/react-context-menu": "^2.1.5",
14 | "@radix-ui/react-dialog": "^1.0.5",
15 | "@radix-ui/react-dropdown-menu": "^2.0.6",
16 | "@radix-ui/react-hover-card": "^1.0.7",
17 | "@radix-ui/react-popover": "^1.0.7",
18 | "@radix-ui/react-scroll-area": "^1.0.5",
19 | "@radix-ui/react-separator": "^1.0.3",
20 | "@radix-ui/react-slot": "^1.0.2",
21 | "@radix-ui/react-tooltip": "^1.0.7",
22 | "@vercel/analytics": "^1.1.2",
23 | "class-variance-authority": "^0.7.0",
24 | "clsx": "^2.1.0",
25 | "cmdk": "^0.2.1",
26 | "embla-carousel": "8.0.0-rc22",
27 | "embla-carousel-autoplay": "8.0.0-rc22",
28 | "embla-carousel-class-names": "8.0.0-rc22",
29 | "embla-carousel-react": "8.0.0-rc22",
30 | "framer-motion": "^10.17.0",
31 | "lucide-react": "^0.300.0",
32 | "next": "14.1.0",
33 | "next-themes": "^0.2.1",
34 | "react": "^18",
35 | "react-dom": "^18",
36 | "tailwind-merge": "^2.2.1",
37 | "tailwindcss-animate": "^1.0.7",
38 | "zustand": "^4.5.0"
39 | },
40 | "devDependencies": {
41 | "@types/node": "^20",
42 | "@types/react": "^18",
43 | "@types/react-dom": "^18",
44 | "autoprefixer": "^10.4.17",
45 | "eslint": "^8",
46 | "eslint-config-next": "14.1.0",
47 | "postcss": "^8",
48 | "tailwindcss": "^3.4.1",
49 | "typescript": "^5"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/fonts/basiersquare-medium-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mickasmt/next-mobbin-clone/7a82cafa4eb029e9a15097d5285a4aca453952e0/public/fonts/basiersquare-medium-webfont.woff2
--------------------------------------------------------------------------------
/public/fonts/basiersquare-regular-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mickasmt/next-mobbin-clone/7a82cafa4eb029e9a15097d5285a4aca453952e0/public/fonts/basiersquare-regular-webfont.woff2
--------------------------------------------------------------------------------
/public/fonts/basiersquare-semibold-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mickasmt/next-mobbin-clone/7a82cafa4eb029e9a15097d5285a4aca453952e0/public/fonts/basiersquare-semibold-webfont.woff2
--------------------------------------------------------------------------------
/public/images/desktop-screen.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mickasmt/next-mobbin-clone/7a82cafa4eb029e9a15097d5285a4aca453952e0/public/images/desktop-screen.webp
--------------------------------------------------------------------------------
/public/images/hero-banner-1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mickasmt/next-mobbin-clone/7a82cafa4eb029e9a15097d5285a4aca453952e0/public/images/hero-banner-1.webp
--------------------------------------------------------------------------------
/public/images/hero-banner-2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mickasmt/next-mobbin-clone/7a82cafa4eb029e9a15097d5285a4aca453952e0/public/images/hero-banner-2.webp
--------------------------------------------------------------------------------
/public/images/hero-banner-3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mickasmt/next-mobbin-clone/7a82cafa4eb029e9a15097d5285a4aca453952e0/public/images/hero-banner-3.webp
--------------------------------------------------------------------------------
/public/images/hero-banner-4.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mickasmt/next-mobbin-clone/7a82cafa4eb029e9a15097d5285a4aca453952e0/public/images/hero-banner-4.webp
--------------------------------------------------------------------------------
/public/images/phone-screen.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mickasmt/next-mobbin-clone/7a82cafa4eb029e9a15097d5285a4aca453952e0/public/images/phone-screen.webp
--------------------------------------------------------------------------------
/public/images/review-star-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mickasmt/next-mobbin-clone/7a82cafa4eb029e9a15097d5285a4aca453952e0/public/images/review-star-dark.png
--------------------------------------------------------------------------------
/public/images/review-star-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mickasmt/next-mobbin-clone/7a82cafa4eb029e9a15097d5285a4aca453952e0/public/images/review-star-light.png
--------------------------------------------------------------------------------
/public/images/square-logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mickasmt/next-mobbin-clone/7a82cafa4eb029e9a15097d5285a4aca453952e0/public/images/square-logo.webp
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mickasmt/next-mobbin-clone/7a82cafa4eb029e9a15097d5285a4aca453952e0/public/og.png
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss'
2 |
3 | const config: Config = {
4 | darkMode: ["class"],
5 | content: [
6 | './pages/**/*.{ts,tsx}',
7 | './components/**/*.{ts,tsx}',
8 | './app/**/*.{ts,tsx}',
9 | './src/**/*.{ts,tsx}',
10 | ],
11 | prefix: "",
12 | theme: {
13 | container: {
14 | center: true,
15 | padding: "2rem",
16 | screens: {
17 | "2xl": "1400px",
18 | },
19 | },
20 | extend: {
21 | colors: {
22 | border: "hsl(var(--border))",
23 | input: "hsl(var(--input))",
24 | ring: "hsl(var(--ring))",
25 | background: "hsl(var(--background))",
26 | foreground: "hsl(var(--foreground))",
27 | primary: {
28 | DEFAULT: "hsl(var(--primary))",
29 | foreground: "hsl(var(--primary-foreground))",
30 | },
31 | secondary: {
32 | DEFAULT: "hsl(var(--secondary))",
33 | foreground: "hsl(var(--secondary-foreground))",
34 | },
35 | destructive: {
36 | DEFAULT: "hsl(var(--destructive))",
37 | foreground: "hsl(var(--destructive-foreground))",
38 | },
39 | muted: {
40 | DEFAULT: "hsl(var(--muted))",
41 | foreground: "hsl(var(--muted-foreground))",
42 | },
43 | accent: {
44 | DEFAULT: "hsl(var(--accent))",
45 | foreground: "hsl(var(--accent-foreground))",
46 | },
47 | popover: {
48 | DEFAULT: "hsl(var(--popover))",
49 | foreground: "hsl(var(--popover-foreground))",
50 | },
51 | card: {
52 | DEFAULT: "hsl(var(--card))",
53 | foreground: "hsl(var(--card-foreground))",
54 | },
55 | },
56 | borderRadius: {
57 | lg: "var(--radius)",
58 | md: "calc(var(--radius) - 2px)",
59 | sm: "calc(var(--radius) - 4px)",
60 | },
61 | keyframes: {
62 | "accordion-down": {
63 | from: { height: "0" },
64 | to: { height: "var(--radix-accordion-content-height)" },
65 | },
66 | "accordion-up": {
67 | from: { height: "var(--radix-accordion-content-height)" },
68 | to: { height: "0" },
69 | },
70 | "progress-bar": {
71 | 'from': { width: '0' },
72 | 'to': { width: '100%' },
73 | },
74 | fade: {
75 | '0%': { opacity: '0' },
76 | '100%': { opacity: '1' },
77 | },
78 | fadeInOut: {
79 | '0%, 100%': { opacity: '0' },
80 | '20%, 80%': { opacity: '1' },
81 | },
82 | },
83 | animation: {
84 | "accordion-down": "accordion-down 0.2s ease-out",
85 | "accordion-up": "accordion-up 0.2s ease-out",
86 | "progress-bar": "progress-bar linear ease-in-out",
87 | "fade": "fade 0.5s ease-in-out",
88 | 'fade-in-out': 'fadeInOut ease-in-out'
89 | },
90 | },
91 | },
92 | plugins: [require("tailwindcss-animate")],
93 | }
94 | export default config
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------