Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented Spotify Clone UI #22

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,255 changes: 4,255 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,21 @@
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.0"
},
"devDependencies": {
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.17",
"eslint": "^8.56.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"postcss": "^8.4.35",
"tailwindcss": "^3.4.1",
"typescript": "^5.2.2",
"vite": "^5.1.0"
}
Expand Down
6 changes: 6 additions & 0 deletions postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
42 changes: 0 additions & 42 deletions src/App.css

This file was deleted.

71 changes: 44 additions & 27 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,52 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

import Navbar from './components/Navbar'
import { Routes, Route, Outlet, Link } from "react-router-dom";
import Home from './pages/Home'
import Search from './pages/Search'
import Button from './components/Button';
function App() {
const [count, setCount] = useState(0)
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="search" element={<Search />} />
<Route path="*" element={<NoMatch />} />
</Route>
</Routes>
)
}

function Layout(){
return (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
<div className='flex flex-col gap-2 p-2 h-screen w-screen'>
<div className="app flex grow gap-2">
<div className='hidden lg:flex'>
<Navbar />
</div>
<main className="main bg-neutral-900 grow">
<Outlet />
</main>
</div>

<div className="px-6 py-3 flex flex-col lg:flex-row gap-3 lg:items-center lg:justify-between" style={{backgroundImage: 'linear-gradient(90deg,#af2896,#509bf5)'}}>
<div>
<div className='text-sm font-semibold'>Preview of Spotify</div>
<div className=''>Sign up to get unlimited songs and podcasts with occasional ads. No credit card needed.</div>
</div>
<Button>Signup for free</Button>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
</div>
)
}

function NoMatch() {
return (
<div>
<h2>Nothing to see here!</h2>
<p>
<Link to="/">Go to the home page</Link>
</p>
</div>
);
}

export default App
3 changes: 3 additions & 0 deletions src/assets/add.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/bars.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/chevron-left.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/chevron-right.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/home.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/library.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/web.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 57 additions & 0 deletions src/components/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
interface ButtonProps {
plain?: boolean;
iconOnly?: boolean;
children?: React.ReactNode;
icon?: React.ReactNode;
small?: boolean;
outline?: boolean;
}

function Button(props: ButtonProps){
if(props.plain){
return <PlainButton {...props}/>
}else if(props.iconOnly){
return <IconButton {...props}/>
}else if(props.outline){
return <OutlineButton {...props}/>
}

return <BaseButton {...props} />
}

function BaseButton(props: ButtonProps){
const baseClass = ['bg-white text-black px-7 py-3 font-semibold rounded-3xl hover:scale-105'];
if(props.small){
baseClass.push('text-md !py-2 !px-6');
}
return (
<button className={baseClass.join(' ')}>{props.children}</button>
)
}

function PlainButton({children}: ButtonProps){
return (
<button className="text-neutral-400 px-7 py-3 font-semibold rounded-3xl hover:scale-105">{children}</button>
)
}

function OutlineButton({children, small=false}: ButtonProps){
const baseClass = ['flex gap-1 bg-black text-white border border-white px-7 py-3 font-semibold rounded-3xl hover:scale-105'];
if(small){
baseClass.push('text-md !py-2 !px-6');
}
return (
<button className={baseClass.join(' ')}>{children}</button>
)
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
function IconButton({icon, iconOnly, ...props}: ButtonProps){
return (
<button className="p-2 bg-black hover:bg-neutral-800 rounded-full disabled:cursor-not-allowed" {...props}>
{icon}
</button>
)
}

export default Button;
33 changes: 33 additions & 0 deletions src/components/Card/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

interface CardProps {
noPadding?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
children: any;
className?: string;
title?: string;

}

function Card({noPadding = false, children, className}: CardProps){
let cardClasses = [];
const baseClass = 'bg-neutral-900 flex flex-col rounded-lg';
const withPadding = 'p-4';

cardClasses = [baseClass];
if(!noPadding){
cardClasses.push(withPadding)
}
if(className){
cardClasses.push(className)
}



return (
<div className={cardClasses.join(' ')}>
{children}
</div>
)
}

export default Card
43 changes: 43 additions & 0 deletions src/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

import Button from '../Button'
import Icon from '../Icon'

interface HeaderProps {
search?: React.ReactNode
}

const Header = ({search}: HeaderProps) => {
const arrowLeft = <Icon name="left-arrow" />
const arrowRight = <Icon name="right-arrow" />
const searchIcon = <Icon name="search" />
const menu = <Icon name="bars" />
let searchBar = null;
if(search){
searchBar = search;
}
return (
<div className="header bg-neutral-950 flex justify-between items-center px-4 py-3">
<div className='hidden lg:flex items-center'>
<Button icon={arrowLeft} iconOnly></Button>
<Button icon={arrowRight} iconOnly></Button>
{searchBar}
</div>
<div className='lg:hidden'>
Shopify
</div>
<div className='flex items-center'>
<div className='hidden lg:flex'>
<Button plain>Sign up</Button>
<Button>Log in</Button>
</div>
<div className='flex items-center gap-2 lg:hidden'>
<Button icon={searchIcon} iconOnly></Button>
<Button small>Open App</Button>
<Button icon={menu} iconOnly></Button>
</div>
</div>
</div>
)
}

export default Header
Loading