Skip to content

Commit

Permalink
feat(bento): add interactivity to code blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonsaldan committed Dec 26, 2024
1 parent deb5f29 commit 209249e
Show file tree
Hide file tree
Showing 5 changed files with 357 additions and 91 deletions.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"prismjs": "^1.29.0",
"react": "^18",
"react-dom": "^18",
"react-icons": "^5.4.0",
"react-use-measure": "^2.1.1"
},
"devDependencies": {
Expand Down
95 changes: 4 additions & 91 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { Container } from '@/components/container'
import { Footer } from '@/components/footer'
import { Gradient } from '@/components/gradient'
import { InterstateRows } from '@/components/interstate-rows'
import LeftCodeTabs from '@/components/left-code-tabs'
import { Link } from '@/components/link'
import { LogoCluster } from '@/components/logo-cluster'
import { Map } from '@/components/map'
import { Navbar } from '@/components/navbar'
import RightCodeTabs from '@/components/right-code-tabs'
import { Testimonials } from '@/components/testimonials'
import { Heading, Subheading } from '@/components/text'
import { ChevronRightIcon } from '@heroicons/react/16/solid'
Expand Down Expand Up @@ -75,104 +77,15 @@ function BentoSection() {
eyebrow="Free"
title="Open Source"
description="Built by the community, for the community. Always free and open source."
graphic={
<div className="relative px-6 pt-8 md:pr-0">
<div className="mx-auto h-72 max-w-2xl overflow-hidden bg-texture-1 bg-cover md:mx-0 md:max-w-none">
<div className="relative w-screen overflow-hidden rounded-tl-xl bg-white/60 shadow-sm backdrop-blur-xl">
<div className="absolute inset-0 rounded-tl-xl border-l-4 border-gray-50" />
<div className="relative flex bg-gray-50">
<div className="-mb-px flex text-sm/6 font-medium text-gray-600">
<div className="rounded-tl-xl border-b border-l-4 border-r border-t-4 border-b-blue-500 border-l-gray-50 border-r-gray-200 border-t-gray-50 bg-white px-4 py-2 text-blue-600">
playlistService.jsx
</div>
<div className="border-r border-t-4 border-gray-200 border-t-gray-50 px-4 py-2">
artistService.jsx
</div>
</div>
</div>
<div className="relative">
<CodeBlock
code={`export const fetchUserPlaylists = async (accessToken, setPlaylists, updateGradientColors, handleError) => {
try {
const response = await fetch("https://api.spotify.com/v1/me/playlists", {
headers: {
Authorization: \`Bearer \${accessToken}\`,
},
});
if (response.ok) {
const data = await response.json();
const validPlaylists = data.items.filter(item => item && item.id);
if (validPlaylists.length > 0) {
const imageUrl = validPlaylists[0].images[0]?.url;
if (imageUrl) {
localStorage.setItem("libraryImage", imageUrl);
updateGradientColors(imageUrl, "library");
}
}
setPlaylists(validPlaylists);
} else {
console.error("Error fetching user playlists:", response.status);
}
} catch (error) {
console.error("Error fetching user playlists:", error.message);
}
};`}
/>
</div>
</div>
</div>
</div>
}
graphic={<LeftCodeTabs />}
fade={['bottom']}
className="max-lg:rounded-t-4xl lg:col-span-3 lg:rounded-tl-4xl"
/>
<BentoCard
eyebrow="Simple"
title="Easy Setup"
description="Get up and running in minutes with our step-by-step installation guide and community support."
graphic={
<div className="relative px-6 pt-8 md:pl-0">
<div className="mx-auto h-72 max-w-2xl overflow-hidden rounded-tr-xl bg-texture-2 bg-cover md:mx-0 md:max-w-none">
<div className="relative w-full overflow-hidden rounded-tr-xl bg-white/60 shadow-sm backdrop-blur-xl">
<div className="absolute inset-0 rounded-tr-xl border-r-4 border-gray-50" />
<div className="relative flex bg-gray-50">
<div className="-mb-px flex text-sm/6 font-medium text-gray-600">
<div className="border-r border-t-4 border-gray-200 border-t-gray-50 px-4 py-2">
setup_hotspot.py
</div>
<div className="rounded-tr-xl border-b border-t-4 border-b-blue-500 border-l-gray-200 border-r-gray-50 border-t-gray-50 bg-white px-4 py-2 text-blue-600">
README.md
</div>
</div>
</div>
<pre className="whitespace-normal px-4 pt-4 text-sm text-[#383A42]">
<code className="block whitespace-pre-wrap break-words pb-4">
If you haven't already, download superbird-tool and run
the setup process detailed here.
</code>
<code className="block whitespace-pre-wrap break-words pb-4">
Download and unzip the latest image from Releases, connect
Car Thing to your computer in USB Mode (hold preset
buttons 1 and 4 while connecting), and run the following
from your command line:
</code>
<code className="block whitespace-pre-wrap break-words">
# Go into the superbird-tool repository
</code>
<code className="block whitespace-pre-wrap break-words pb-2">
$ cd C:\path\to\superbird-tool-main
</code>
<code className="block whitespace-pre-wrap break-words">
# Find device
</code>
<code className="block whitespace-pre-wrap break-words pb-2">
$ python superbird_tool.py --find_device
</code>
</pre>
</div>
</div>
</div>
}
graphic={<RightCodeTabs />}
fade={['bottom']}
className="lg:col-span-3 lg:rounded-tr-4xl"
/>
Expand Down
180 changes: 180 additions & 0 deletions src/components/left-code-tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import dynamic from 'next/dynamic'
import { useEffect, useState } from 'react'
import { FaJsSquare, FaReact } from 'react-icons/fa'

const CodeBlock = dynamic(() => import('@/components/code-block'), {
ssr: false,
})

type TabKey = 'playlist' | 'artist'

interface TabContent {
title: string
code: string
}

interface TabsData {
playlist: TabContent
artist: TabContent
}

const LeftCodeTabs = () => {
const [activeTab, setActiveTab] = useState<TabKey>('playlist')
const [isMounted, setIsMounted] = useState(false)
const [isChanging, setIsChanging] = useState(false)

useEffect(() => {
setIsMounted(true)
}, [])

const handleTabChange = (newTab: TabKey) => {
if (newTab === activeTab) return

setIsChanging(true)
setTimeout(() => {
setActiveTab(newTab)
setIsChanging(false)
}, 150)
}

const tabs: TabsData = {
playlist: {
title: 'playlistService.js',
code: `export const fetchUserPlaylists = async (
accessToken,
setPlaylists,
updateGradientColors,
handleError
) => {
try {
const response = await fetch("https://api.spotify.com/v1/me/playlists", {
headers: {
Authorization: \`Bearer \${accessToken}\`,
},
});
if (response.ok) {
const data = await response.json();
const validPlaylists = data.items.filter((item) => item && item.id);
if (validPlaylists.length > 0) {
const imageUrl = validPlaylists[0].images?.[0]?.url;
if (imageUrl) {
localStorage.setItem("libraryImage", imageUrl);
updateGradientColors(imageUrl, "library");
}
}
setPlaylists(validPlaylists);
} else {
console.error("Error fetching user playlists:", response.status);
}
} catch (error) {
console.error("Error fetching user playlists:", error.message);
}
};`,
},
artist: {
title: 'AuthSelection.jsx',
code: `const enableBluetoothDiscovery = async () => {
try {
setIsBluetoothDiscovering(true);
const response = await fetch(
"http://localhost:5000/bluetooth/discover/on",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
throw new Error("Failed to enable bluetooth discovery");
}
setTimeout(() => {
setIsBluetoothDiscovering(false);
fetch("http://localhost:5000/bluetooth/discover/off", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
}).catch(console.error);
}, 120000);
} catch (error) {
console.error("Error enabling bluetooth discovery:", error);
setIsBluetoothDiscovering(false);
}
};`,
},
}

return (
<div className="relative px-6 pt-8 md:pr-0">
<div className="mx-auto h-72 max-w-2xl overflow-hidden bg-texture-1 bg-cover md:mx-0 md:max-w-none">
<div className="relative w-screen overflow-hidden rounded-tl-xl bg-white/60 shadow-sm backdrop-blur-xl">
<div className="absolute inset-0 rounded-tl-xl border-l-4 border-gray-50" />
<div className="relative flex bg-gray-50">
<div className="-mb-px flex text-sm/6 font-medium text-gray-600">
<div
onClick={() => handleTabChange('playlist')}
className="relative cursor-pointer"
>
<div
className={
activeTab === 'playlist'
? 'rounded-tl-xl border-l-4 border-r border-t-4 border-l-gray-50 border-r-gray-200 border-t-gray-50 bg-white px-4 py-2 text-blue-600'
: 'border-r border-t-4 border-gray-200 border-t-gray-50 py-2 pl-5 pr-4 hover:text-gray-900'
}
>
<div className="flex items-center gap-2">
<FaJsSquare className="size-4" />
{tabs.playlist.title}
</div>
</div>
<div
className={`absolute bottom-0 left-0 right-0 h-0.5 transition-colors duration-300 ${
activeTab === 'playlist' ? 'bg-blue-500' : 'bg-transparent'
}`}
/>
</div>
<div
onClick={() => handleTabChange('artist')}
className="relative cursor-pointer"
>
<div
className={
activeTab === 'artist'
? 'rounded-tr-xl border-t-4 border-l-gray-200 border-r-gray-50 border-t-gray-50 bg-white px-4 py-2 text-blue-600'
: 'border-r border-t-4 border-gray-200 border-t-gray-50 px-4 py-2 hover:text-gray-900'
}
>
<div className="flex items-center gap-2">
<FaReact className="size-4" />
{tabs.artist.title}
</div>
</div>
<div
className={`absolute bottom-0 left-0 right-0 h-0.5 transition-colors duration-300 ${
activeTab === 'artist' ? 'bg-blue-500' : 'bg-transparent'
}`}
/>
</div>
</div>
</div>
<div className="relative">
{isMounted && (
<div
className={`min-h-[200px] transform transition-all duration-300 ease-in-out ${
isChanging ? 'scale-95 opacity-0' : 'scale-100 opacity-100'
}`}
>
<CodeBlock code={tabs[activeTab].code} />
</div>
)}
</div>
</div>
</div>
</div>
)
}

export default LeftCodeTabs
Loading

0 comments on commit 209249e

Please sign in to comment.