├── .gitignore
├── .vscode
├── extensions.json
└── launch.json
├── README.md
├── docs
├── assets
│ ├── album-1-bfe26d94.js
│ ├── album-2-6bcd8e89.js
│ ├── album-3-e649e581.js
│ ├── album-4-c4664fee.js
│ ├── album-5-c168a9cb.js
│ ├── album-6-3740d35b.js
│ ├── album-7-9cf691a1.js
│ ├── album-8-0ebba23b.js
│ ├── albums-a0323168.js
│ ├── index-3b3f9cb5.js
│ └── index-7eb3bd97.css
├── favicon.svg
├── index.html
├── mp3
│ ├── song1.mp3
│ ├── song2.mp3
│ ├── song3.mp3
│ └── song4.mp3
└── vinyl-lp.webp
├── index.html
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── favicon.svg
├── mp3
│ ├── song1.mp3
│ ├── song2.mp3
│ ├── song3.mp3
│ └── song4.mp3
└── vinyl-lp.webp
├── screenshot.png
├── src
├── App.tsx
├── components
│ ├── Card.tsx
│ ├── Footer.tsx
│ ├── Header.tsx
│ ├── PlayButton.tsx
│ ├── Player.tsx
│ ├── Record.tsx
│ ├── TrackList.tsx
│ └── icons.tsx
├── data
│ ├── album-1.json
│ ├── album-2.json
│ ├── album-3.json
│ ├── album-4.json
│ ├── album-5.json
│ ├── album-6.json
│ ├── album-7.json
│ ├── album-8.json
│ └── albums.json
├── index.css
├── main.jsx
└── routes
│ ├── Album.tsx
│ ├── Home.tsx
│ └── Layout.tsx
├── tailwind.config.js
├── tsconfig.json
└── vite.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # build output
2 | .output/
3 |
4 | # dependencies
5 | node_modules/
6 |
7 | # logs
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | pnpm-debug.log*
12 |
13 |
14 | # environment variables
15 | .env
16 | .env.production
17 |
18 | # macOS-specific files
19 | .DS_Store
20 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["astro-build.astro-vscode"],
3 | "unwantedRecommendations": []
4 | }
5 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "command": "./node_modules/.bin/astro dev",
6 | "name": "Development server",
7 | "request": "launch",
8 | "type": "node-terminal"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Router View Transitions Demo
2 |
3 | ## ❤️ Credits
4 |
5 | This is a fork of the awesome [Astro Records](https://github.com/Charca/astro-records) Demo (using Astro's view transition support) that has been converted to use React Router's view transition APIs.
6 |
7 | ## 👉🏽 [Live Demo](https://brophdawg11.github.io/react-router-records/)
8 |
9 | 
10 |
11 | ## 🚀 Getting Started
12 |
13 | 1. Clone this repository and install dependencies with `npm ci`.
14 | 2. Start the project locally with `npm run dev`
15 | 3. Have fun! ✨
16 |
--------------------------------------------------------------------------------
/docs/assets/album-1-bfe26d94.js:
--------------------------------------------------------------------------------
1 | const t="1",i="Hot Fuss",e="The Killers",n="https://www.theaudiodb.com/images/media/album/thumb/hot-fuss-limited-edition-7-inch-box-set-4ddc38e3e1d71.jpg",o=[{id:"1-1",position:1,title:"Jenny Was a Friend of Mine",length:"4:05"},{id:"1-2",position:2,title:"Mr. Brightside",length:"3:43"},{id:"1-3",position:3,title:"Smile Like You Mean It",length:"3:55"},{id:"1-4",position:4,title:"Somebody Told Me",length:"3:17"},{id:"1-5",position:5,title:"All These Things That I've Done",length:"5:02"},{id:"1-6",position:6,title:"Andy, You're a Star",length:"3:14"},{id:"1-7",position:7,title:"On Top",length:"4:19"},{id:"1-8",position:8,title:"Change Your Mind",length:"3:11"},{id:"1-9",position:9,title:"Believe Me Natalie",length:"5:07"},{id:"1-10",position:10,title:"Midnight Show",length:"4:03"},{id:"1-11",position:11,title:"Everything Will Be Alright",length:"5:45"}],l={id:t,name:i,artist:e,img:n,tracks:o};export{e as artist,l as default,t as id,n as img,i as name,o as tracks};
2 |
--------------------------------------------------------------------------------
/docs/assets/album-2-6bcd8e89.js:
--------------------------------------------------------------------------------
1 | const t="2",i="First Impressions of Earth",e="The Strokes",n="https://www.theaudiodb.com/images/media/album/thumb/rqsryv1477576489.jpg",o=[{id:"2-1",position:1,title:"You Only Live Once",length:"3:09"},{id:"2-2",position:2,title:"Juicebox",length:"3:18"},{id:"2-3",position:3,title:"Heart in a Cage",length:"3:27"},{id:"2-4",position:4,title:"Razorblade",length:"3:29"},{id:"2-5",position:5,title:"On the Other Side",length:"4:39"},{id:"2-6",position:6,title:"Vision of Division",length:"4:20"},{id:"2-7",position:7,title:"Ask Me Anything",length:"3:13"},{id:"2-8",position:8,title:"Electricityscape",length:"3:33"},{id:"2-9",position:9,title:"Killing Lies",length:"3:50"},{id:"2-10",position:10,title:"Fear of Sleep",length:"4:00"},{id:"2-11",position:11,title:"15 Minutes",length:"4:34"},{id:"2-12",position:12,title:"Ize of the World",length:"4:29"},{id:"2-13",position:13,title:"Evening Sun",length:"3:06"},{id:"2-14",position:14,title:"Red Light",length:"3:12"}],s={id:t,name:i,artist:e,img:n,tracks:o};export{e as artist,s as default,t as id,n as img,i as name,o as tracks};
2 |
--------------------------------------------------------------------------------
/docs/assets/album-3-e649e581.js:
--------------------------------------------------------------------------------
1 | const t="3",i="Inside In/Inside Out",e="The Kooks",o="https://www.theaudiodb.com/images/media/album/thumb/inside-ininside-out-4e5608971ceb2.jpeg",n=[{id:"3-1",position:1,title:"Seaside",length:"1:39"},{id:"3-2",position:2,title:"See the World",length:"2:38"},{id:"3-3",position:3,title:"Sofa Song",length:"2:13"},{id:"3-4",position:4,title:"Eddie’s Gun",length:"2:13"},{id:"3-5",position:5,title:"Ooh La",length:"3:29"},{id:"3-6",position:6,title:"You Don’t Love Me",length:"2:35"},{id:"3-7",position:7,title:"She Moves in Her Own Way",length:"2:49"},{id:"3-8",position:8,title:"Matchbox",length:"3:10"},{id:"3-9",position:9,title:"Naïve",length:"3:24"},{id:"3-10",position:10,title:"I Want You",length:"3:27"},{id:"3-11",position:11,title:"If Only",length:"2:01"},{id:"3-12",position:12,title:"Jackie Big Tees",length:"2:33"},{id:"3-13",position:13,title:"Time Awaits",length:"5:09"},{id:"3-14",position:14,title:"Got No Love",length:"3:38"}],s={id:t,name:i,artist:e,img:o,tracks:n};export{e as artist,s as default,t as id,o as img,i as name,n as tracks};
2 |
--------------------------------------------------------------------------------
/docs/assets/album-4-c4664fee.js:
--------------------------------------------------------------------------------
1 | const t="4",i="Favourite Worst Nightmare",e="Arctic Monkeys",o="https://www.theaudiodb.com/images/media/album/thumb/favourite-worst-nightmare-4e612d12273ea.jpg",n=[{id:"4-1",position:1,title:"Brianstorm",length:"2:50"},{id:"4-2",position:2,title:"Teddy Picker",length:"2:43"},{id:"4-3",position:3,title:"D Is for Dangerous",length:"2:16"},{id:"4-4",position:4,title:"Balaclava",length:"2:49"},{id:"4-5",position:5,title:"Fluorescent Adolescent",length:"2:58"},{id:"4-6",position:6,title:"Only Ones Who Know",length:"3:03"},{id:"4-7",position:7,title:"Do Me a Favour",length:"3:27"},{id:"4-8",position:8,title:"This House Is a Circus",length:"3:10"},{id:"4-9",position:9,title:"If You Were There, Beware",length:"4:34"},{id:"4-10",position:10,title:"The Bad Thing",length:"2:23"},{id:"4-11",position:11,title:"Old Yellow Bricks",length:"3:11"},{id:"4-12",position:12,title:"505",length:"4:14"}],s={id:t,name:i,artist:e,img:o,tracks:n};export{e as artist,s as default,t as id,o as img,i as name,n as tracks};
2 |
--------------------------------------------------------------------------------
/docs/assets/album-5-c168a9cb.js:
--------------------------------------------------------------------------------
1 | const t="5",i="Up All Night",l="Razorlight",o="https://www.theaudiodb.com/images/media/album/thumb/uxputq1399632252.jpg",e=[{id:"5-1",position:1,title:"Leave Me Alone",length:"3:50"},{id:"5-2",position:2,title:"Rock n Roll Lies",length:"3:08"},{id:"5-3",position:3,title:"Vice",length:"3:14"},{id:"5-4",position:4,title:"Up All Night",length:"4:03"},{id:"5-5",position:5,title:"Which Way Is Out",length:"3:19"},{id:"5-6",position:6,title:"Rip It Up",length:"2:25"},{id:"5-7",position:7,title:"Don’t Go Back to Dalston",length:"2:59"},{id:"5-8",position:8,title:"Golden Touch",length:"3:25"},{id:"5-9",position:9,title:"Stumble and Fall",length:"3:02"},{id:"5-10",position:10,title:"Get It and Go",length:"3:23"},{id:"5-11",position:11,title:"In the City",length:"4:51"},{id:"5-12",position:12,title:"To the Sea",length:"5:31"},{id:"5-13",position:13,title:"Fall, Fall, Fall",length:"2:43"}],n={id:t,name:i,artist:l,img:o,tracks:e};export{l as artist,n as default,t as id,o as img,i as name,e as tracks};
2 |
--------------------------------------------------------------------------------
/docs/assets/album-6-3740d35b.js:
--------------------------------------------------------------------------------
1 | const t="6",i="Phrazes for the Young",o="Julian Casablancas",e="https://www.theaudiodb.com/images/media/album/thumb/phrazes-for-the-young-52f7faa979a19.jpg",s=[{id:"6-1",position:1,title:"Out of the Blue",length:"4:43"},{id:"6-2",position:2,title:"Left & Right in the Dark",length:"4:59"},{id:"6-3",position:3,title:"11th Dimension",length:"4:06"},{id:"6-4",position:4,title:"4 Chords of the Apocalypse",length:"5:02"},{id:"6-5",position:5,title:"Ludlow St.",length:"5:45"},{id:"6-6",position:6,title:"River of Brakelights",length:"5:13"},{id:"6-7",position:7,title:"Glass",length:"5:25"},{id:"6-8",position:8,title:"Tourist",length:"5:03"}],n={id:t,name:i,artist:o,img:e,tracks:s};export{o as artist,n as default,t as id,e as img,i as name,s as tracks};
2 |
--------------------------------------------------------------------------------
/docs/assets/album-7-9cf691a1.js:
--------------------------------------------------------------------------------
1 | const t="7",i="Franz Ferdinand",e="Franz Ferdinand",n="https://www.theaudiodb.com/images/media/album/thumb/franz-ferdinand-4dd465083a9a8.jpg",o=[{id:"7-1",position:1,title:"Jacqueline",length:"3:49"},{id:"7-2",position:2,title:"Tell Her Tonight",length:"2:18"},{id:"7-3",position:3,title:"Take Me Out",length:"3:57"},{id:"7-4",position:4,title:"The Dark of the Matinée",length:"4:03"},{id:"7-5",position:5,title:"Auf Achse",length:"4:20"},{id:"7-6",position:6,title:"Cheating on You",length:"2:37"},{id:"7-7",position:7,title:"This Fire",length:"4:15"},{id:"7-8",position:8,title:"Darts of Pleasure",length:"3:00"},{id:"7-9",position:9,title:"Michael",length:"3:21"},{id:"7-10",position:10,title:"Come On Home",length:"3:46"},{id:"7-11",position:11,title:"40′",length:"3:23"}],l={id:t,name:i,artist:e,img:n,tracks:o};export{e as artist,l as default,t as id,n as img,i as name,o as tracks};
2 |
--------------------------------------------------------------------------------
/docs/assets/album-8-0ebba23b.js:
--------------------------------------------------------------------------------
1 | const t="8",i="Get Born",e="Jet",o="https://www.theaudiodb.com/images/media/album/thumb/get-born-4ddedb9009071.jpg",n=[{id:"8-1",position:1,title:"Last Chance",length:"1:52"},{id:"8-2",position:2,title:"Are You Gonna Be My Girl",length:"3:34"},{id:"8-3",position:3,title:"Rollover D.J.",length:"3:17"},{id:"8-4",position:4,title:"Look What You’ve Done",length:"3:51"},{id:"8-5",position:5,title:"Get What You Need",length:"4:08"},{id:"8-6",position:6,title:"Move On",length:"4:21"},{id:"8-7",position:7,title:"Radio Song",length:"4:32"},{id:"8-8",position:8,title:"Get Me Outta Here",length:"2:56"},{id:"8-9",position:9,title:"Cold Hard Peach",length:"4:03"},{id:"8-10",position:10,title:"Come Around Again",length:"4:30"},{id:"8-11",position:11,title:"Take It or Leave It",length:"2:23"},{id:"8-12",position:12,title:"Lazy Gun",length:"4:42"},{id:"8-13",position:13,title:"Timothy",length:"4:30"}],l={id:t,name:i,artist:e,img:o,tracks:n};export{e as artist,l as default,t as id,o as img,i as name,n as tracks};
2 |
--------------------------------------------------------------------------------
/docs/assets/albums-a0323168.js:
--------------------------------------------------------------------------------
1 | const t=[{id:"1",name:"Hot Fuss",artist:"The Killers",img:"https://www.theaudiodb.com/images/media/album/thumb/hot-fuss-limited-edition-7-inch-box-set-4ddc38e3e1d71.jpg"},{id:"2",name:"First Impressions of Earth",artist:"The Strokes",img:"https://www.theaudiodb.com/images/media/album/thumb/rqsryv1477576489.jpg"},{id:"3",name:"Inside In/Inside Out",artist:"The Kooks",img:"https://www.theaudiodb.com/images/media/album/thumb/inside-ininside-out-4e5608971ceb2.jpeg"},{id:"4",name:"Favourite Worst Nightmare",artist:"Arctic Monkeys",img:"https://www.theaudiodb.com/images/media/album/thumb/favourite-worst-nightmare-4e612d12273ea.jpg"},{id:"5",name:"Up All Night",artist:"Razorlight",img:"https://www.theaudiodb.com/images/media/album/thumb/uxputq1399632252.jpg"},{id:"6",name:"Phrazes for the Young",artist:"Julian Casablancas",img:"https://www.theaudiodb.com/images/media/album/thumb/phrazes-for-the-young-52f7faa979a19.jpg"},{id:"7",name:"Franz Ferdinand",artist:"Franz Ferdinand",img:"https://www.theaudiodb.com/images/media/album/thumb/franz-ferdinand-4dd465083a9a8.jpg"},{id:"8",name:"Get Botn",artist:"Jet",img:"https://www.theaudiodb.com/images/media/album/thumb/get-born-4ddedb9009071.jpg"}];export{t as default};
2 |
--------------------------------------------------------------------------------
/docs/assets/index-7eb3bd97.css:
--------------------------------------------------------------------------------
1 | *,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.bottom-0{bottom:0}.left-0{left:0}.right-0{right:0}.top-0{top:0}.z-10{z-index:10}.mx-auto{margin-left:auto;margin-right:auto}.-ml-1{margin-left:-.25rem}.mb-10{margin-bottom:2.5rem}.mb-12{margin-bottom:3rem}.ml-auto{margin-left:auto}.mr-2{margin-right:.5rem}.mr-32{margin-right:8rem}.mr-4{margin-right:1rem}.mt-0{margin-top:0}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-6{height:1.5rem}.w-10{width:2.5rem}.w-6{width:1.5rem}.w-72{width:18rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0px}.max-w-screen-lg{max-width:1024px}.flex-1{flex:1 1 0%}.cursor-pointer{cursor:pointer}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-end{justify-content:flex-end}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.overflow-hidden{overflow:hidden}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border-b{border-bottom-width:1px}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity))}.bg-pink-500{--tw-bg-opacity: 1;background-color:rgb(236 72 153 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-12{padding-bottom:3rem}.pb-32{padding-bottom:8rem}.pl-2{padding-left:.5rem}.pt-1{padding-top:.25rem}.pt-4{padding-top:1rem}.pt-8{padding-top:2rem}.text-center{text-align:center}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-5xl{font-size:3rem;line-height:1}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.leading-normal{line-height:1.5}.tracking-normal{letter-spacing:0em}.tracking-tight{letter-spacing:-.025em}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity))}.text-pink-600{--tw-text-opacity: 1;color:rgb(219 39 119 / var(--tw-text-opacity))}.underline{text-decoration-line:underline}.no-underline{text-decoration-line:none}.opacity-0{opacity:0}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.vinyl-animation-in{animation:appear .6s ease-in-out .3s forwards}.vinyl-animation-in-spinning{animation:appear .6s ease-in-out .3s forwards,spin 5s linear infinite}.vinyl-animation-out{animation:disappear .3s ease-in-out forwards}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes appear{0%{left:0;opacity:0}1%{opacity:1}to{left:24%;opacity:1}}@keyframes disappear{0%{left:24%;opacity:1}99%{opacity:1}to{left:0;opacity:0}}a.transitioning .c-card--album,.c-record--album{view-transition-name:album-expand}::view-transition-old(album-expand):not(:only-child),::view-transition-new(album-expand):not(:only-child){animation:none;mix-blend-mode:normal}a.transitioning .c-card--vinyl,.c-record--vinyl{view-transition-name:vinyl-expand}::view-transition-old(vinyl-expand):not(:only-child),::view-transition-new(vinyl-expand):not(:only-child){animation:none;mix-blend-mode:normal}.c-player{view-transition-name:"player"}::view-transition-old(player){display:none}::view-transition-new(player){animation:none}.first\:border-t:first-child{border-top-width:1px}.hover\:bg-gray-200:hover{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity))}.hover\:text-pink-500:hover{--tw-text-opacity: 1;color:rgb(236 72 153 / var(--tw-text-opacity))}.hover\:no-underline:hover{text-decoration-line:none}.hover\:shadow-lg:hover{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-4:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-gray-100:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(243 244 246 / var(--tw-ring-opacity))}@media (prefers-color-scheme: dark){.dark\:bg-gray-700{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity))}.dark\:focus\:ring-gray-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity))}}@media (min-width: 640px){.sm\:block{display:block}.sm\:h-14{height:3.5rem}.sm\:w-14{width:3.5rem}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:py-4{padding-top:1rem;padding-bottom:1rem}}@media (min-width: 768px){.md\:w-auto{width:auto}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-end{align-items:flex-end}.md\:px-0{padding-left:0;padding-right:0}}@media (min-width: 1024px){.lg\:w-1\/2{width:50%}.lg\:px-0{padding-left:0;padding-right:0}}
2 |
--------------------------------------------------------------------------------
/docs/favicon.svg:
--------------------------------------------------------------------------------
1 |
25 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | React Router View Transitions Demo
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/docs/mp3/song1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brophdawg11/react-router-records/d4defe0317dabf14ac435a17b1e49ad8ec4fc786/docs/mp3/song1.mp3
--------------------------------------------------------------------------------
/docs/mp3/song2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brophdawg11/react-router-records/d4defe0317dabf14ac435a17b1e49ad8ec4fc786/docs/mp3/song2.mp3
--------------------------------------------------------------------------------
/docs/mp3/song3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brophdawg11/react-router-records/d4defe0317dabf14ac435a17b1e49ad8ec4fc786/docs/mp3/song3.mp3
--------------------------------------------------------------------------------
/docs/mp3/song4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brophdawg11/react-router-records/d4defe0317dabf14ac435a17b1e49ad8ec4fc786/docs/mp3/song4.mp3
--------------------------------------------------------------------------------
/docs/vinyl-lp.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brophdawg11/react-router-records/d4defe0317dabf14ac435a17b1e49ad8ec4fc786/docs/vinyl-lp.webp
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | React Router View Transitions Demo
9 |
10 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-router-records",
3 | "private": true,
4 | "version": "0.0.1",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-router-dom": "^6.17.0-pre.2"
16 | },
17 | "devDependencies": {
18 | "@types/react": "^18.2.15",
19 | "@types/react-dom": "^18.2.7",
20 | "@vitejs/plugin-react": "^4.0.3",
21 | "autoprefixer": "^10.4.16",
22 | "eslint": "^8.45.0",
23 | "eslint-plugin-react": "^7.32.2",
24 | "eslint-plugin-react-hooks": "^4.6.0",
25 | "eslint-plugin-react-refresh": "^0.4.3",
26 | "postcss": "^8.4.31",
27 | "tailwindcss": "^3.3.3",
28 | "vite": "^4.4.5"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
25 |
--------------------------------------------------------------------------------
/public/mp3/song1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brophdawg11/react-router-records/d4defe0317dabf14ac435a17b1e49ad8ec4fc786/public/mp3/song1.mp3
--------------------------------------------------------------------------------
/public/mp3/song2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brophdawg11/react-router-records/d4defe0317dabf14ac435a17b1e49ad8ec4fc786/public/mp3/song2.mp3
--------------------------------------------------------------------------------
/public/mp3/song3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brophdawg11/react-router-records/d4defe0317dabf14ac435a17b1e49ad8ec4fc786/public/mp3/song3.mp3
--------------------------------------------------------------------------------
/public/mp3/song4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brophdawg11/react-router-records/d4defe0317dabf14ac435a17b1e49ad8ec4fc786/public/mp3/song4.mp3
--------------------------------------------------------------------------------
/public/vinyl-lp.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brophdawg11/react-router-records/d4defe0317dabf14ac435a17b1e49ad8ec4fc786/public/vinyl-lp.webp
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brophdawg11/react-router-records/d4defe0317dabf14ac435a17b1e49ad8ec4fc786/screenshot.png
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { RouterProvider, createBrowserRouter } from "react-router-dom";
2 | import Layout from "./routes/Layout";
3 | import {
4 | Component as HomeComponent,
5 | loader as homeLoader,
6 | } from "./routes/Home";
7 | import {
8 | Component as AlbumComponent,
9 | loader as albumLoader,
10 | } from "./routes/Album";
11 |
12 | let router = createBrowserRouter(
13 | [
14 | {
15 | path: "/",
16 | Component: Layout,
17 | children: [
18 | {
19 | index: true,
20 | loader: homeLoader,
21 | Component: HomeComponent,
22 | },
23 | {
24 | path: "album/:id",
25 | loader: albumLoader,
26 | Component: AlbumComponent,
27 | },
28 | ],
29 | },
30 | ],
31 | {
32 | basename: "/react-router-records",
33 | }
34 | );
35 |
36 | export default function App() {
37 | return ;
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/Card.tsx:
--------------------------------------------------------------------------------
1 | import { NavLink } from "react-router-dom";
2 |
3 | type CardProps = {
4 | id: string;
5 | name: string;
6 | artist: string;
7 | imageUrl: string;
8 | };
9 |
10 | export function Card({ id, name, artist, imageUrl }: CardProps) {
11 | return (
12 |
13 |
18 |
19 |

26 |

32 |
33 | {name}
34 | {artist}
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | export default function Footer() {
2 | return (
3 |
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | export default function Header() {
4 | return (
5 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/src/components/PlayButton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { PlayerContext, Track } from "../routes/Layout";
3 | import { CirclePauseIcon, CirclePlayIcon } from "./icons";
4 |
5 | type PlayButtonProps = {
6 | tracks: Track[];
7 | albumId: string;
8 | artist: string;
9 | imageUrl: string;
10 | };
11 |
12 | export default function PlayButton({
13 | tracks,
14 | albumId,
15 | artist,
16 | imageUrl,
17 | }: PlayButtonProps) {
18 | let { isPlaying, setIsPlaying, currentTrack, setCurrentTrack } =
19 | React.useContext(PlayerContext);
20 | const isPlayingCurrentRecord = isPlaying && currentTrack?.albumId === albumId;
21 |
22 | return (
23 |
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/src/components/Player.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { PlayerContext } from "../routes/Layout";
3 |
4 | const PlayIcon = (
5 |
17 | );
18 |
19 | const PauseIcon = (
20 |
32 | );
33 |
34 | // This app doesn't have real songs, it only has a few songs
35 | // that we will play over and over as the user uses the app.
36 | const MAX_SONGS = 4;
37 |
38 | export default function Player() {
39 | let audioPlayer = React.useRef(null);
40 | let progressRef = React.useRef(0);
41 | let [songIndex, setSongIndex] = React.useState(4);
42 | let [progress, setProgress] = React.useState(0);
43 | let { isPlaying, setIsPlaying, currentTrack } =
44 | React.useContext(PlayerContext);
45 |
46 | function whilePlaying() {
47 | if (!audioPlayer.current) return;
48 | if (audioPlayer.current.duration) {
49 | const percentage =
50 | (audioPlayer.current.currentTime * 100) / audioPlayer.current.duration;
51 | setProgress(percentage);
52 | }
53 | progressRef.current = requestAnimationFrame(whilePlaying);
54 | }
55 |
56 | React.useEffect(() => {
57 | if (!audioPlayer.current) return;
58 | const newIndex = (songIndex % MAX_SONGS) + 1;
59 | audioPlayer.current.src = `/react-router-records/mp3/song${newIndex}.mp3`;
60 | audioPlayer.current.currentTime = 0;
61 | audioPlayer.current.play();
62 | setSongIndex(newIndex);
63 | }, [currentTrack?.title]);
64 |
65 | React.useEffect(() => {
66 | if (isPlaying) {
67 | audioPlayer.current?.play();
68 | progressRef.current = requestAnimationFrame(whilePlaying);
69 | } else {
70 | audioPlayer.current?.pause();
71 | cancelAnimationFrame(progressRef.current);
72 | }
73 | }, [isPlaying]);
74 |
75 | React.useEffect(() => {
76 | if (progress >= 99.99) {
77 | setIsPlaying(false);
78 | setProgress(0);
79 | }
80 | }, [progress]);
81 |
82 | if (currentTrack === null) {
83 | return null;
84 | }
85 |
86 | return (
87 |
88 |
94 |
95 |

101 |
102 |
103 | {currentTrack.title}
104 |
105 |
106 | {currentTrack.artist}
107 |
108 |
109 |
110 |
111 |
119 |
120 |
123 |
131 |
132 |
133 |
134 | );
135 | }
136 |
--------------------------------------------------------------------------------
/src/components/Record.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { PlayerContext } from "../routes/Layout";
3 |
4 | type RecordProps = {
5 | albumId: string;
6 | title: string;
7 | imageUrl: string;
8 | };
9 |
10 | export default function Record({ albumId, title, imageUrl }: RecordProps) {
11 | let { isPlaying, currentTrack } = React.useContext(PlayerContext);
12 | const isPlayingCurrentRecord = isPlaying && currentTrack?.albumId === albumId;
13 | const animationClass = isPlayingCurrentRecord
14 | ? "vinyl-animation-in-spinning"
15 | : "vinyl-animation-in";
16 |
17 | return (
18 |
19 |

26 |

32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/TrackList.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { PlayerContext, Track } from "../routes/Layout";
3 | import { CirclePauseIcon, CirclePlayIcon } from "./icons";
4 |
5 | type TrackListProps = {
6 | tracks: Track[];
7 | albumId: string;
8 | artist: string;
9 | imageUrl: string;
10 | };
11 |
12 | export default function TrackList({
13 | tracks,
14 | albumId,
15 | artist,
16 | imageUrl,
17 | }: TrackListProps) {
18 | let { isPlaying, setIsPlaying, currentTrack, setCurrentTrack } =
19 | React.useContext(PlayerContext);
20 |
21 | return (
22 |
23 | {tracks.map((track) => {
24 | const isCurrentTrack = track.id == currentTrack?.id;
25 |
26 | return (
27 | - {
31 | setCurrentTrack({
32 | ...track,
33 | albumId,
34 | artist,
35 | imageUrl,
36 | });
37 | setIsPlaying(true);
38 | }}
39 | >
40 |
41 | {isCurrentTrack && !isPlaying ? (
42 |
43 | ) : isCurrentTrack && isPlaying ? (
44 |
45 | ) : (
46 | track.position
47 | )}
48 |
49 | {track.title}
50 | {track.length}
51 |
52 | );
53 | })}
54 |
55 | );
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/icons.tsx:
--------------------------------------------------------------------------------
1 | export function CirclePlayIcon() {
2 | return (
3 |
16 | );
17 | }
18 |
19 | export function CirclePauseIcon() {
20 | return (
21 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/src/data/album-1.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "1",
3 | "name": "Hot Fuss",
4 | "artist": "The Killers",
5 | "img": "https://www.theaudiodb.com/images/media/album/thumb/hot-fuss-limited-edition-7-inch-box-set-4ddc38e3e1d71.jpg",
6 | "tracks": [
7 | {
8 | "id": "1-1",
9 | "position": 1,
10 | "title": "Jenny Was a Friend of Mine",
11 | "length": "4:05"
12 | },
13 | { "id": "1-2", "position": 2, "title": "Mr. Brightside", "length": "3:43" },
14 | {
15 | "id": "1-3",
16 | "position": 3,
17 | "title": "Smile Like You Mean It",
18 | "length": "3:55"
19 | },
20 | {
21 | "id": "1-4",
22 | "position": 4,
23 | "title": "Somebody Told Me",
24 | "length": "3:17"
25 | },
26 | {
27 | "id": "1-5",
28 | "position": 5,
29 | "title": "All These Things That I've Done",
30 | "length": "5:02"
31 | },
32 | {
33 | "id": "1-6",
34 | "position": 6,
35 | "title": "Andy, You're a Star",
36 | "length": "3:14"
37 | },
38 | { "id": "1-7", "position": 7, "title": "On Top", "length": "4:19" },
39 | {
40 | "id": "1-8",
41 | "position": 8,
42 | "title": "Change Your Mind",
43 | "length": "3:11"
44 | },
45 | {
46 | "id": "1-9",
47 | "position": 9,
48 | "title": "Believe Me Natalie",
49 | "length": "5:07"
50 | },
51 | {
52 | "id": "1-10",
53 | "position": 10,
54 | "title": "Midnight Show",
55 | "length": "4:03"
56 | },
57 | {
58 | "id": "1-11",
59 | "position": 11,
60 | "title": "Everything Will Be Alright",
61 | "length": "5:45"
62 | }
63 | ]
64 | }
65 |
--------------------------------------------------------------------------------
/src/data/album-2.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "2",
3 | "name": "First Impressions of Earth",
4 | "artist": "The Strokes",
5 | "img": "https://www.theaudiodb.com/images/media/album/thumb/rqsryv1477576489.jpg",
6 | "tracks": [
7 | {
8 | "id": "2-1",
9 | "position": 1,
10 | "title": "You Only Live Once",
11 | "length": "3:09"
12 | },
13 | { "id": "2-2", "position": 2, "title": "Juicebox", "length": "3:18" },
14 | {
15 | "id": "2-3",
16 | "position": 3,
17 | "title": "Heart in a Cage",
18 | "length": "3:27"
19 | },
20 | { "id": "2-4", "position": 4, "title": "Razorblade", "length": "3:29" },
21 | {
22 | "id": "2-5",
23 | "position": 5,
24 | "title": "On the Other Side",
25 | "length": "4:39"
26 | },
27 | {
28 | "id": "2-6",
29 | "position": 6,
30 | "title": "Vision of Division",
31 | "length": "4:20"
32 | },
33 | {
34 | "id": "2-7",
35 | "position": 7,
36 | "title": "Ask Me Anything",
37 | "length": "3:13"
38 | },
39 | {
40 | "id": "2-8",
41 | "position": 8,
42 | "title": "Electricityscape",
43 | "length": "3:33"
44 | },
45 | { "id": "2-9", "position": 9, "title": "Killing Lies", "length": "3:50" },
46 | {
47 | "id": "2-10",
48 | "position": 10,
49 | "title": "Fear of Sleep",
50 | "length": "4:00"
51 | },
52 | { "id": "2-11", "position": 11, "title": "15 Minutes", "length": "4:34" },
53 | {
54 | "id": "2-12",
55 | "position": 12,
56 | "title": "Ize of the World",
57 | "length": "4:29"
58 | },
59 | { "id": "2-13", "position": 13, "title": "Evening Sun", "length": "3:06" },
60 | { "id": "2-14", "position": 14, "title": "Red Light", "length": "3:12" }
61 | ]
62 | }
63 |
--------------------------------------------------------------------------------
/src/data/album-3.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "3",
3 | "name": "Inside In/Inside Out",
4 | "artist": "The Kooks",
5 | "img": "https://www.theaudiodb.com/images/media/album/thumb/inside-ininside-out-4e5608971ceb2.jpeg",
6 | "tracks": [
7 | { "id": "3-1", "position": 1, "title": "Seaside", "length": "1:39" },
8 | { "id": "3-2", "position": 2, "title": "See the World", "length": "2:38" },
9 | { "id": "3-3", "position": 3, "title": "Sofa Song", "length": "2:13" },
10 | { "id": "3-4", "position": 4, "title": "Eddie’s Gun", "length": "2:13" },
11 | { "id": "3-5", "position": 5, "title": "Ooh La", "length": "3:29" },
12 | {
13 | "id": "3-6",
14 | "position": 6,
15 | "title": "You Don’t Love Me",
16 | "length": "2:35"
17 | },
18 | {
19 | "id": "3-7",
20 | "position": 7,
21 | "title": "She Moves in Her Own Way",
22 | "length": "2:49"
23 | },
24 | { "id": "3-8", "position": 8, "title": "Matchbox", "length": "3:10" },
25 | { "id": "3-9", "position": 9, "title": "Naïve", "length": "3:24" },
26 | { "id": "3-10", "position": 10, "title": "I Want You", "length": "3:27" },
27 | { "id": "3-11", "position": 11, "title": "If Only", "length": "2:01" },
28 | {
29 | "id": "3-12",
30 | "position": 12,
31 | "title": "Jackie Big Tees",
32 | "length": "2:33"
33 | },
34 | { "id": "3-13", "position": 13, "title": "Time Awaits", "length": "5:09" },
35 | { "id": "3-14", "position": 14, "title": "Got No Love", "length": "3:38" }
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/src/data/album-4.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "4",
3 | "name": "Favourite Worst Nightmare",
4 | "artist": "Arctic Monkeys",
5 | "img": "https://www.theaudiodb.com/images/media/album/thumb/favourite-worst-nightmare-4e612d12273ea.jpg",
6 | "tracks": [
7 | { "id": "4-1", "position": 1, "title": "Brianstorm", "length": "2:50" },
8 | { "id": "4-2", "position": 2, "title": "Teddy Picker", "length": "2:43" },
9 | {
10 | "id": "4-3",
11 | "position": 3,
12 | "title": "D Is for Dangerous",
13 | "length": "2:16"
14 | },
15 | { "id": "4-4", "position": 4, "title": "Balaclava", "length": "2:49" },
16 | {
17 | "id": "4-5",
18 | "position": 5,
19 | "title": "Fluorescent Adolescent",
20 | "length": "2:58"
21 | },
22 | {
23 | "id": "4-6",
24 | "position": 6,
25 | "title": "Only Ones Who Know",
26 | "length": "3:03"
27 | },
28 | { "id": "4-7", "position": 7, "title": "Do Me a Favour", "length": "3:27" },
29 | {
30 | "id": "4-8",
31 | "position": 8,
32 | "title": "This House Is a Circus",
33 | "length": "3:10"
34 | },
35 | {
36 | "id": "4-9",
37 | "position": 9,
38 | "title": "If You Were There, Beware",
39 | "length": "4:34"
40 | },
41 | {
42 | "id": "4-10",
43 | "position": 10,
44 | "title": "The Bad Thing",
45 | "length": "2:23"
46 | },
47 | {
48 | "id": "4-11",
49 | "position": 11,
50 | "title": "Old Yellow Bricks",
51 | "length": "3:11"
52 | },
53 | { "id": "4-12", "position": 12, "title": "505", "length": "4:14" }
54 | ]
55 | }
56 |
--------------------------------------------------------------------------------
/src/data/album-5.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "5",
3 | "name": "Up All Night",
4 | "artist": "Razorlight",
5 | "img": "https://www.theaudiodb.com/images/media/album/thumb/uxputq1399632252.jpg",
6 | "tracks": [
7 | { "id": "5-1", "position": 1, "title": "Leave Me Alone", "length": "3:50" },
8 | {
9 | "id": "5-2",
10 | "position": 2,
11 | "title": "Rock n Roll Lies",
12 | "length": "3:08"
13 | },
14 | { "id": "5-3", "position": 3, "title": "Vice", "length": "3:14" },
15 | { "id": "5-4", "position": 4, "title": "Up All Night", "length": "4:03" },
16 | {
17 | "id": "5-5",
18 | "position": 5,
19 | "title": "Which Way Is Out",
20 | "length": "3:19"
21 | },
22 | { "id": "5-6", "position": 6, "title": "Rip It Up", "length": "2:25" },
23 | {
24 | "id": "5-7",
25 | "position": 7,
26 | "title": "Don’t Go Back to Dalston",
27 | "length": "2:59"
28 | },
29 | { "id": "5-8", "position": 8, "title": "Golden Touch", "length": "3:25" },
30 | {
31 | "id": "5-9",
32 | "position": 9,
33 | "title": "Stumble and Fall",
34 | "length": "3:02"
35 | },
36 | {
37 | "id": "5-10",
38 | "position": 10,
39 | "title": "Get It and Go",
40 | "length": "3:23"
41 | },
42 | { "id": "5-11", "position": 11, "title": "In the City", "length": "4:51" },
43 | { "id": "5-12", "position": 12, "title": "To the Sea", "length": "5:31" },
44 | {
45 | "id": "5-13",
46 | "position": 13,
47 | "title": "Fall, Fall, Fall",
48 | "length": "2:43"
49 | }
50 | ]
51 | }
52 |
--------------------------------------------------------------------------------
/src/data/album-6.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "6",
3 | "name": "Phrazes for the Young",
4 | "artist": "Julian Casablancas",
5 | "img": "https://www.theaudiodb.com/images/media/album/thumb/phrazes-for-the-young-52f7faa979a19.jpg",
6 | "tracks": [
7 | {
8 | "id": "6-1",
9 | "position": 1,
10 | "title": "Out of the Blue",
11 | "length": "4:43"
12 | },
13 | {
14 | "id": "6-2",
15 | "position": 2,
16 | "title": "Left & Right in the Dark",
17 | "length": "4:59"
18 | },
19 | { "id": "6-3", "position": 3, "title": "11th Dimension", "length": "4:06" },
20 | {
21 | "id": "6-4",
22 | "position": 4,
23 | "title": "4 Chords of the Apocalypse",
24 | "length": "5:02"
25 | },
26 | { "id": "6-5", "position": 5, "title": "Ludlow St.", "length": "5:45" },
27 | {
28 | "id": "6-6",
29 | "position": 6,
30 | "title": "River of Brakelights",
31 | "length": "5:13"
32 | },
33 | { "id": "6-7", "position": 7, "title": "Glass", "length": "5:25" },
34 | { "id": "6-8", "position": 8, "title": "Tourist", "length": "5:03" }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/src/data/album-7.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "7",
3 | "name": "Franz Ferdinand",
4 | "artist": "Franz Ferdinand",
5 | "img": "https://www.theaudiodb.com/images/media/album/thumb/franz-ferdinand-4dd465083a9a8.jpg",
6 | "tracks": [
7 | { "id": "7-1", "position": 1, "title": "Jacqueline", "length": "3:49" },
8 | {
9 | "id": "7-2",
10 | "position": 2,
11 | "title": "Tell Her Tonight",
12 | "length": "2:18"
13 | },
14 | { "id": "7-3", "position": 3, "title": "Take Me Out", "length": "3:57" },
15 | {
16 | "id": "7-4",
17 | "position": 4,
18 | "title": "The Dark of the Matinée",
19 | "length": "4:03"
20 | },
21 | { "id": "7-5", "position": 5, "title": "Auf Achse", "length": "4:20" },
22 | {
23 | "id": "7-6",
24 | "position": 6,
25 | "title": "Cheating on You",
26 | "length": "2:37"
27 | },
28 | { "id": "7-7", "position": 7, "title": "This Fire", "length": "4:15" },
29 | {
30 | "id": "7-8",
31 | "position": 8,
32 | "title": "Darts of Pleasure",
33 | "length": "3:00"
34 | },
35 | { "id": "7-9", "position": 9, "title": "Michael", "length": "3:21" },
36 | { "id": "7-10", "position": 10, "title": "Come On Home", "length": "3:46" },
37 | { "id": "7-11", "position": 11, "title": "40′", "length": "3:23" }
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/src/data/album-8.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "8",
3 | "name": "Get Born",
4 | "artist": "Jet",
5 | "img": "https://www.theaudiodb.com/images/media/album/thumb/get-born-4ddedb9009071.jpg",
6 | "tracks": [
7 | { "id": "8-1", "position": 1, "title": "Last Chance", "length": "1:52" },
8 | {
9 | "id": "8-2",
10 | "position": 2,
11 | "title": "Are You Gonna Be My Girl",
12 | "length": "3:34"
13 | },
14 | { "id": "8-3", "position": 3, "title": "Rollover D.J.", "length": "3:17" },
15 | {
16 | "id": "8-4",
17 | "position": 4,
18 | "title": "Look What You’ve Done",
19 | "length": "3:51"
20 | },
21 | {
22 | "id": "8-5",
23 | "position": 5,
24 | "title": "Get What You Need",
25 | "length": "4:08"
26 | },
27 | { "id": "8-6", "position": 6, "title": "Move On", "length": "4:21" },
28 | { "id": "8-7", "position": 7, "title": "Radio Song", "length": "4:32" },
29 | {
30 | "id": "8-8",
31 | "position": 8,
32 | "title": "Get Me Outta Here",
33 | "length": "2:56"
34 | },
35 | {
36 | "id": "8-9",
37 | "position": 9,
38 | "title": "Cold Hard Peach",
39 | "length": "4:03"
40 | },
41 | {
42 | "id": "8-10",
43 | "position": 10,
44 | "title": "Come Around Again",
45 | "length": "4:30"
46 | },
47 | {
48 | "id": "8-11",
49 | "position": 11,
50 | "title": "Take It or Leave It",
51 | "length": "2:23"
52 | },
53 | { "id": "8-12", "position": 12, "title": "Lazy Gun", "length": "4:42" },
54 | { "id": "8-13", "position": 13, "title": "Timothy", "length": "4:30" }
55 | ]
56 | }
57 |
--------------------------------------------------------------------------------
/src/data/albums.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "1",
4 | "name": "Hot Fuss",
5 | "artist": "The Killers",
6 | "img": "https://www.theaudiodb.com/images/media/album/thumb/hot-fuss-limited-edition-7-inch-box-set-4ddc38e3e1d71.jpg"
7 | },
8 | {
9 | "id": "2",
10 | "name": "First Impressions of Earth",
11 | "artist": "The Strokes",
12 | "img": "https://www.theaudiodb.com/images/media/album/thumb/rqsryv1477576489.jpg"
13 | },
14 | {
15 | "id": "3",
16 | "name": "Inside In/Inside Out",
17 | "artist": "The Kooks",
18 | "img": "https://www.theaudiodb.com/images/media/album/thumb/inside-ininside-out-4e5608971ceb2.jpeg"
19 | },
20 | {
21 | "id": "4",
22 | "name": "Favourite Worst Nightmare",
23 | "artist": "Arctic Monkeys",
24 | "img": "https://www.theaudiodb.com/images/media/album/thumb/favourite-worst-nightmare-4e612d12273ea.jpg"
25 | },
26 | {
27 | "id": "5",
28 | "name": "Up All Night",
29 | "artist": "Razorlight",
30 | "img": "https://www.theaudiodb.com/images/media/album/thumb/uxputq1399632252.jpg"
31 | },
32 | {
33 | "id": "6",
34 | "name": "Phrazes for the Young",
35 | "artist": "Julian Casablancas",
36 | "img": "https://www.theaudiodb.com/images/media/album/thumb/phrazes-for-the-young-52f7faa979a19.jpg"
37 | },
38 | {
39 | "id": "7",
40 | "name": "Franz Ferdinand",
41 | "artist": "Franz Ferdinand",
42 | "img": "https://www.theaudiodb.com/images/media/album/thumb/franz-ferdinand-4dd465083a9a8.jpg"
43 | },
44 | {
45 | "id": "8",
46 | "name": "Get Botn",
47 | "artist": "Jet",
48 | "img": "https://www.theaudiodb.com/images/media/album/thumb/get-born-4ddedb9009071.jpg"
49 | }
50 | ]
51 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | .vinyl-animation-in {
6 | animation: appear 600ms ease-in-out 300ms forwards;
7 | }
8 | .vinyl-animation-in-spinning {
9 | animation: appear 600ms ease-in-out 300ms forwards, spin 5s linear infinite;
10 | }
11 | .vinyl-animation-out {
12 | animation: disappear 300ms ease-in-out forwards;
13 | }
14 |
15 | @keyframes spin {
16 | from {
17 | transform: rotate(0deg);
18 | }
19 | to {
20 | transform: rotate(360deg);
21 | }
22 | }
23 | @keyframes appear {
24 | 0% {
25 | left: 0;
26 | opacity: 0;
27 | }
28 | 1% {
29 | opacity: 1;
30 | }
31 | 100% {
32 | left: 24%;
33 | opacity: 1;
34 | }
35 | }
36 |
37 | @keyframes disappear {
38 | 0% {
39 | left: 24%;
40 | opacity: 1;
41 | }
42 | 99% {
43 | opacity: 1;
44 | }
45 | 100% {
46 | left: 0;
47 | opacity: 0;
48 | }
49 | }
50 |
51 | a.transitioning .c-card--album,
52 | .c-record--album {
53 | view-transition-name: album-expand;
54 | }
55 |
56 | ::view-transition-old(album-expand):not(:only-child),
57 | ::view-transition-new(album-expand):not(:only-child) {
58 | animation: none;
59 | mix-blend-mode: normal;
60 | }
61 |
62 | a.transitioning .c-card--vinyl,
63 | .c-record--vinyl {
64 | view-transition-name: vinyl-expand;
65 | }
66 |
67 | ::view-transition-old(vinyl-expand):not(:only-child),
68 | ::view-transition-new(vinyl-expand):not(:only-child) {
69 | animation: none;
70 | mix-blend-mode: normal;
71 | }
72 |
73 | .c-player {
74 | view-transition-name: "player";
75 | }
76 |
77 | ::view-transition-old(player) {
78 | display: none;
79 | }
80 |
81 | ::view-transition-new(player) {
82 | animation: none;
83 | }
84 |
--------------------------------------------------------------------------------
/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App";
4 | import "./index.css";
5 |
6 | ReactDOM.createRoot(document.getElementById("root")).render(
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/src/routes/Album.tsx:
--------------------------------------------------------------------------------
1 | import { LoaderFunctionArgs, useLoaderData } from "react-router-dom";
2 | import Record from "../components/Record";
3 | import TrackList from "../components/TrackList";
4 | import PlayButton from "../components/PlayButton";
5 | import { type Track } from "./Layout";
6 |
7 | type AlbumWithTracks = {
8 | id: string;
9 | name: string;
10 | artist: string;
11 | img: string;
12 | tracks: Track[];
13 | };
14 |
15 | export async function loader({ params }: LoaderFunctionArgs) {
16 | // TODO: Fix synchronous navigations
17 | await new Promise((r) => setTimeout(r, 1));
18 | const { default: album }: { default: AlbumWithTracks } = await import(
19 | `../data/album-${params.id}.json`
20 | );
21 | return album;
22 | }
23 |
24 | export function Component() {
25 | let album = useLoaderData() as Awaited>;
26 |
27 | return (
28 |
29 |
30 |
36 |
37 |
38 | {album.name}
39 |
40 |
{album.artist}
41 |
42 | {album.strGenre} — {album.intYearReleased}
43 |
44 |
45 |
51 |
52 |
70 |
71 |
72 |
73 |
74 |
75 |
82 |
83 |
84 | );
85 | }
86 |
--------------------------------------------------------------------------------
/src/routes/Home.tsx:
--------------------------------------------------------------------------------
1 | import { useLoaderData } from "react-router-dom";
2 | import { Card } from "../components/Card";
3 |
4 | export async function loader() {
5 | // TODO: Fix synchronous navigations
6 | await new Promise((r) => setTimeout(r, 1));
7 | const { default: albums } = await import("../data/albums.json");
8 | return albums;
9 | }
10 |
11 | export function Component() {
12 | let albums = useLoaderData() as Awaited>;
13 |
14 | return (
15 |
16 |
17 |
18 | Recently Played
19 |
20 |
21 |
22 | {albums.map((album) => (
23 |
30 | ))}
31 |
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/src/routes/Layout.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Outlet } from "react-router-dom";
3 | import Header from "../components/Header";
4 | import Footer from "../components/Footer";
5 | import Player from "../components/Player";
6 |
7 | export type Track = {
8 | id: string;
9 | title: string;
10 | position: number;
11 | length: string;
12 | };
13 |
14 | export type PlayerTrack = Track & {
15 | albumId: string;
16 | artist: string;
17 | imageUrl: string;
18 | };
19 |
20 | export const PlayerContext = React.createContext<{
21 | isPlaying: boolean;
22 | setIsPlaying: (v: boolean) => void;
23 | currentTrack: PlayerTrack | null;
24 | setCurrentTrack: (v: PlayerTrack) => void;
25 | }>({
26 | isPlaying: false,
27 | setIsPlaying: () => {},
28 | currentTrack: null,
29 | setCurrentTrack: () => {},
30 | });
31 |
32 | export default function Layout() {
33 | let [isPlaying, setIsPlaying] = React.useState(false);
34 | let [currentTrack, setCurrentTrack] = React.useState(
35 | null
36 | );
37 |
38 | console.log(isPlaying, currentTrack);
39 |
40 | return (
41 |
49 |
50 |
51 |
52 |
55 |
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
4 | theme: {
5 | extend: {
6 | animation: {
7 | "spin-slow": "spin 5s linear infinite",
8 | },
9 | },
10 | },
11 | plugins: [],
12 | };
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "target": "ESNext",
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"]
20 | }
21 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | base: "/react-router-records",
7 | plugins: [react()],
8 | server: {
9 | port: 3000,
10 | },
11 | build: {
12 | outDir: "docs",
13 | },
14 | });
15 |
--------------------------------------------------------------------------------