├── .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 | next mobbin clone 3 |

Next Mobbin Clone

4 |
5 | 6 |

7 | 8 | Mickasmt Twitter follower count 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 | 31 | 34 | 39 |
40 | 41 |
42 |
43 | 44 |
45 |
46 | 47 |
48 |
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 | Phone Screen App 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 |
55 |
56 | Phone Screen App 63 |
64 |
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 | 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 | 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 | 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 | 75 | 76 | 77 | {/* Tags buttons */} 78 | {categoriesList.map((category) => ( 79 | 83 | 89 | handleMouseEnter(category)} 91 | asChild 92 | > 93 | 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 | phone screen 75 | 76 |
77 | 85 | 86 | {Array.from({ length: 3 }).map((_, index) => ( 87 | 88 | phone screen 96 | 97 | ))} 98 | 99 | 106 | 113 | 114 | 115 |
116 |
117 | {Array.from({ length: 3 }).map((_, index) => ( 118 | 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 | 163 | 164 | 165 | 169 |

Save to collections

170 |
171 |
172 |
173 | 174 | 175 | setMenuOpen(!menuOpen)} 178 | > 179 | 180 | 181 | 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 | 54 | 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 | {item.title} 127 |
128 |

{item.title}

129 |
130 |
131 |
132 | ))} 133 |
134 | 135 |
136 | 137 |
138 | 139 |
140 |
141 | {slides.map((_, index) => ( 142 | 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 | phone screen 46 | 47 | 54 | 55 |
61 | useSelection.toggleItem(id)} 67 | /> 68 |
69 | 70 |
76 |
77 |
83 | 84 | setMenuOpen(!menuOpen)} 87 | > 88 | 89 | 90 | 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 | 171 | 172 | 173 | 177 |

Copy screen to clipboard

178 |
179 |
180 |
181 | 182 | 183 | 184 | 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 | 48 | 49 | 50 | 51 |

Clear selection

52 |
53 |
54 |
55 | 56 | 57 | 58 | 69 | 70 | 71 | 72 |

Download as PNG

73 |
74 |
75 |
76 | 77 | 78 | 79 | 87 | 88 | 89 | 90 |

Save to collection

91 |
92 |
93 |
94 | 95 | 96 | 97 | 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 | 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 | square-logo 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 | 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 | 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 | ConversionRateExpert 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 | 35 | 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 | review stars 120 | review stars 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 | Phone Screen App 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 |
33 |
34 | Phone Screen App 41 |
42 |
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 | 83 | ), 84 | twitter: (props: LucideProps) => ( 85 | 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 | 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 | 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 | 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 | 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 | 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 | 33 | 41 | 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 |
157 |
166 |
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 | 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 | 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 | --------------------------------------------------------------------------------