feat(bento): skeleton components

This commit is contained in:
Jason
2023-09-26 16:22:38 -07:00
parent 03d8342fa7
commit b89bc531ea
8 changed files with 108 additions and 8 deletions

View File

@@ -18,7 +18,7 @@
This is a list of the various technologies used to build this website: This is a list of the various technologies used to build this website:
| Name | Link | | Category | Technology Name |
| ------------------- | ------------------------------------------------------------------------- | | ------------------- | ------------------------------------------------------------------------- |
| Framework | [Next.js](https://nextjs.org/) | | Framework | [Next.js](https://nextjs.org/) |
| Deployment | [Vercel](https://vercel.com) | | Deployment | [Vercel](https://vercel.com) |

View File

@@ -6,20 +6,22 @@ import { useState } from 'react'
interface ExtendedImageProps extends ImageProps { interface ExtendedImageProps extends ImageProps {
noSkeleton?: boolean noSkeleton?: boolean
noRelative?: boolean
skeletonClassName?: string skeletonClassName?: string
} }
const Image = ({ const Image = ({
onLoad, onLoad,
className, className,
noSkeleton, noSkeleton = false,
noRelative = false,
skeletonClassName, skeletonClassName,
...rest ...rest
}: ExtendedImageProps) => { }: ExtendedImageProps) => {
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
return ( return (
<div className="relative"> <div className={`${noRelative ? 'inline-block' : 'relative'}`}>
{!noSkeleton && isLoading && ( {!noSkeleton && isLoading && (
<Skeleton <Skeleton
className={`absolute left-0 top-0 h-full w-full rounded-md object-contain ${skeletonClassName}`} className={`absolute left-0 top-0 h-full w-full rounded-md object-contain ${skeletonClassName}`}

View File

@@ -1,12 +1,13 @@
'use client' 'use client'
import { lgLayout, mdLayout, smLayout } from '@/scripts/utils/bento-layouts' import { lgLayout, mdLayout, smLayout } from '@/scripts/utils/bento-layouts'
import Image from 'next/image'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { Responsive, WidthProvider } from 'react-grid-layout' import { Responsive, WidthProvider } from 'react-grid-layout'
import { FaGithub, FaTwitter } from 'react-icons/fa' import { FaGithub, FaTwitter } from 'react-icons/fa'
import { useLanyard } from 'react-use-lanyard' import { useLanyard } from 'react-use-lanyard'
import Image from '../Image'
import { Skeleton } from '../shadcn/skeleton'
import DiscordPresence from './DiscordPresence' import DiscordPresence from './DiscordPresence'
import ExternalLink from './ExternalLink' import ExternalLink from './ExternalLink'
import SilhouetteHover from './SilhouetteHover' import SilhouetteHover from './SilhouetteHover'
@@ -22,6 +23,9 @@ const BentoBox = ({ posts }) => {
const [rowHeight, setRowHeight] = useState(280) const [rowHeight, setRowHeight] = useState(280)
const [introSilhouette, setIntroSilhouette] = useState(false) const [introSilhouette, setIntroSilhouette] = useState(false)
const [isDiscordLoaded, setDiscordLoaded] = useState(false)
const [isSpotifyLoaded, setIsSpotifyLoaded] = useState(false)
const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null) const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)
const handleWidthChange = (width) => { const handleWidthChange = (width) => {
@@ -89,6 +93,8 @@ const BentoBox = ({ posts }) => {
className={`rounded-3xl object-cover transition-opacity duration-300 ${ className={`rounded-3xl object-cover transition-opacity duration-300 ${
introSilhouette ? 'opacity-100' : 'opacity-0 delay-75' introSilhouette ? 'opacity-100' : 'opacity-0 delay-75'
}`} }`}
skeletonClassName="rounded-3xl"
noRelative
unoptimized unoptimized
priority priority
/> />
@@ -99,6 +105,8 @@ const BentoBox = ({ posts }) => {
className={`rounded-3xl object-cover transition-opacity duration-300 ${ className={`rounded-3xl object-cover transition-opacity duration-300 ${
introSilhouette ? 'opacity-0 delay-75' : 'opacity-100' introSilhouette ? 'opacity-0 delay-75' : 'opacity-100'
}`} }`}
skeletonClassName="rounded-3xl"
noRelative
unoptimized unoptimized
priority priority
/> />
@@ -126,12 +134,22 @@ const BentoBox = ({ posts }) => {
src="/static/images/bento/bento-image-1.svg" src="/static/images/bento/bento-image-1.svg"
alt="Bento Box 1" alt="Bento Box 1"
fill={true} fill={true}
noRelative
className="rounded-3xl object-cover" className="rounded-3xl object-cover"
skeletonClassName="rounded-3xl"
unoptimized unoptimized
priority
/> />
</div> </div>
<div key="discord"> <div key="discord">
{!lanyard.isValidating && <DiscordPresence lanyard={lanyard.data} />} {lanyard.data && !lanyard.isValidating ? (
<DiscordPresence
lanyard={lanyard.data}
onLoad={() => setDiscordLoaded(true)}
/>
) : (
<Skeleton className="w-full h-full rounded-3xl" />
)}
</div> </div>
<div <div
key="latest-post" key="latest-post"
@@ -152,6 +170,8 @@ const BentoBox = ({ posts }) => {
width={0} width={0}
height={0} height={0}
className="m-2 w-[80%] rounded-2xl border border-border md:m-3 lg:m-4" className="m-2 w-[80%] rounded-2xl border border-border md:m-3 lg:m-4"
skeletonClassName="rounded-3xl"
noRelative
unoptimized unoptimized
/> />
</SilhouetteHover> </SilhouetteHover>
@@ -163,7 +183,9 @@ const BentoBox = ({ posts }) => {
alt="Bento Box 2" alt="Bento Box 2"
fill={true} fill={true}
className="rounded-3xl object-cover" className="rounded-3xl object-cover"
noRelative
unoptimized unoptimized
priority
/> />
</div> </div>
<div key="wakatime">Child G</div> <div key="wakatime">Child G</div>
@@ -191,7 +213,14 @@ const BentoBox = ({ posts }) => {
onMouseEnter={() => setIntroSilhouette(true)} onMouseEnter={() => setIntroSilhouette(true)}
onMouseLeave={() => setIntroSilhouette(false)} onMouseLeave={() => setIntroSilhouette(false)}
> >
{!lanyard.isValidating && <SpotifyPresence lanyard={lanyard.data} />} {lanyard.data && !lanyard.isValidating ? (
<SpotifyPresence
lanyard={lanyard.data}
onLoad={() => setIsSpotifyLoaded(true)}
/>
) : (
<Skeleton className="w-full h-full rounded-3xl z-[1]" />
)}
<SilhouetteHover <SilhouetteHover
silhouetteSrc="/static/images/bento/bento-spotify-silhouette.svg" silhouetteSrc="/static/images/bento/bento-spotify-silhouette.svg"
silhouetteAlt="Bento Spotify Silhouette" silhouetteAlt="Bento Spotify Silhouette"
@@ -213,6 +242,8 @@ const BentoBox = ({ posts }) => {
alt="Bento Technologies" alt="Bento Technologies"
fill={true} fill={true}
className="rounded-3xl object-cover" className="rounded-3xl object-cover"
skeletonClassName="rounded-3xl"
noRelative
unoptimized unoptimized
/> />
</div> </div>

View File

@@ -2,7 +2,7 @@ import Image from 'next/image'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { FaDiscord } from 'react-icons/fa' import { FaDiscord } from 'react-icons/fa'
const DiscordPresence = ({ lanyard }) => { const DiscordPresence = ({ lanyard, onLoad }) => {
const mainActivity = lanyard.data.activities.filter( const mainActivity = lanyard.data.activities.filter(
(activity) => activity.type === 0 && activity.assets (activity) => activity.type === 0 && activity.assets
)[0] )[0]
@@ -25,6 +25,12 @@ const DiscordPresence = ({ lanyard }) => {
} }
}, [mainActivity?.timestamps?.start]) }, [mainActivity?.timestamps?.start])
useEffect(() => {
if (hasMainActivity && onLoad) {
onLoad()
}
}, [hasMainActivity, onLoad])
return ( return (
<> <>
<div className="hidden bento-lg:relative w-full h-full bento-lg:flex flex-col"> <div className="hidden bento-lg:relative w-full h-full bento-lg:flex flex-col">

View File

@@ -5,7 +5,7 @@ import { set } from 'react-use-lanyard'
import ExternalLink from './ExternalLink' import ExternalLink from './ExternalLink'
const SpotifyPresence = ({ lanyard }) => { const SpotifyPresence = ({ lanyard, onLoad }) => {
useEffect(() => { useEffect(() => {
if ( if (
JSON.parse(lanyard.data.kv.spotify_last_played) !== lanyard.data.spotify && JSON.parse(lanyard.data.kv.spotify_last_played) !== lanyard.data.spotify &&
@@ -37,6 +37,12 @@ const SpotifyPresence = ({ lanyard }) => {
const { song, artist, album, album_art_url, track_id } = displayData const { song, artist, album, album_art_url, track_id } = displayData
useEffect(() => {
if (lanyard && onLoad) {
onLoad()
}
}, [lanyard, onLoad])
return ( return (
<> <>
<div className="flex bento-md:hidden z-[1] bento-lg:flex h-full w-full flex-col justify-between p-6"> <div className="flex bento-md:hidden z-[1] bento-lg:flex h-full w-full flex-col justify-between p-6">

View File

@@ -13,6 +13,8 @@ import { formatDate } from 'pliny/utils/formatDate'
/* eslint-disable jsx-a11y/anchor-is-valid */ /* eslint-disable jsx-a11y/anchor-is-valid */
/* eslint-disable jsx-a11y/anchor-is-valid */
interface PaginationProps { interface PaginationProps {
totalPages: number totalPages: number
currentPage: number currentPage: number

52
package-lock.json generated
View File

@@ -28,6 +28,7 @@
"postcss": "^8.4.24", "postcss": "^8.4.24",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-github-calendar": "^4.0.1",
"react-grid-layout": "^1.4.1", "react-grid-layout": "^1.4.1",
"react-icons": "^4.11.0", "react-icons": "^4.11.0",
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",
@@ -4572,6 +4573,11 @@
"@types/estree": "*" "@types/estree": "*"
} }
}, },
"node_modules/@types/chroma-js": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.4.1.tgz",
"integrity": "sha512-YIm3RLfWdDU0/3rsNnu/Hh4uKCLQ9p/w1NvnFfBOBL/BGqOvuLNuhXwkJMUKMscmFJdan25lYqv2+qh1l7tZLg=="
},
"node_modules/@types/debug": { "node_modules/@types/debug": {
"version": "4.1.8", "version": "4.1.8",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz",
@@ -5793,6 +5799,11 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/chroma-js": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz",
"integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A=="
},
"node_modules/citeproc": { "node_modules/citeproc": {
"version": "2.4.63", "version": "2.4.63",
"resolved": "https://registry.npmjs.org/citeproc/-/citeproc-2.4.63.tgz", "resolved": "https://registry.npmjs.org/citeproc/-/citeproc-2.4.63.tgz",
@@ -6411,6 +6422,21 @@
"node": ">= 12" "node": ">= 12"
} }
}, },
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
"dependencies": {
"@babel/runtime": "^7.21.0"
},
"engines": {
"node": ">=0.11"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/date-fns"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -12589,6 +12615,20 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-activity-calendar": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/react-activity-calendar/-/react-activity-calendar-2.0.2.tgz",
"integrity": "sha512-gUm7j8rgBgYnrfBQkR4y3gl/gPI1By2Yfg1O1EpYuOdYdRsTtn404H4eH9+iZBCAeZjG49NBPo/BmjvOSyaChg==",
"dependencies": {
"@types/chroma-js": "^2.4.0",
"chroma-js": "^2.4.0",
"date-fns": "^2.30.0"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
}
},
"node_modules/react-dom": { "node_modules/react-dom": {
"version": "18.2.0", "version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@@ -12622,6 +12662,18 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/react-github-calendar": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/react-github-calendar/-/react-github-calendar-4.0.1.tgz",
"integrity": "sha512-WTJFto5i628k8XhRKv/VsV93RL9jGoMl6thxv7UWPaF1Okyo3u/mHhEg261nkPI+8CbPSzmlgfoGUnldZKJgvA==",
"dependencies": {
"react-activity-calendar": "^2.0.0"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
}
},
"node_modules/react-grid-layout": { "node_modules/react-grid-layout": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.4.1.tgz", "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.4.1.tgz",

View File

@@ -32,6 +32,7 @@
"postcss": "^8.4.24", "postcss": "^8.4.24",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-github-calendar": "^4.0.1",
"react-grid-layout": "^1.4.1", "react-grid-layout": "^1.4.1",
"react-icons": "^4.11.0", "react-icons": "^4.11.0",
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",