feat(bento): skeleton components
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
|
||||
This is a list of the various technologies used to build this website:
|
||||
|
||||
| Name | Link |
|
||||
| Category | Technology Name |
|
||||
| ------------------- | ------------------------------------------------------------------------- |
|
||||
| Framework | [Next.js](https://nextjs.org/) |
|
||||
| Deployment | [Vercel](https://vercel.com) |
|
||||
|
||||
@@ -6,20 +6,22 @@ import { useState } from 'react'
|
||||
|
||||
interface ExtendedImageProps extends ImageProps {
|
||||
noSkeleton?: boolean
|
||||
noRelative?: boolean
|
||||
skeletonClassName?: string
|
||||
}
|
||||
|
||||
const Image = ({
|
||||
onLoad,
|
||||
className,
|
||||
noSkeleton,
|
||||
noSkeleton = false,
|
||||
noRelative = false,
|
||||
skeletonClassName,
|
||||
...rest
|
||||
}: ExtendedImageProps) => {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className={`${noRelative ? 'inline-block' : 'relative'}`}>
|
||||
{!noSkeleton && isLoading && (
|
||||
<Skeleton
|
||||
className={`absolute left-0 top-0 h-full w-full rounded-md object-contain ${skeletonClassName}`}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
'use client'
|
||||
|
||||
import { lgLayout, mdLayout, smLayout } from '@/scripts/utils/bento-layouts'
|
||||
import Image from 'next/image'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Responsive, WidthProvider } from 'react-grid-layout'
|
||||
import { FaGithub, FaTwitter } from 'react-icons/fa'
|
||||
import { useLanyard } from 'react-use-lanyard'
|
||||
|
||||
import Image from '../Image'
|
||||
import { Skeleton } from '../shadcn/skeleton'
|
||||
import DiscordPresence from './DiscordPresence'
|
||||
import ExternalLink from './ExternalLink'
|
||||
import SilhouetteHover from './SilhouetteHover'
|
||||
@@ -22,6 +23,9 @@ const BentoBox = ({ posts }) => {
|
||||
const [rowHeight, setRowHeight] = useState(280)
|
||||
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 handleWidthChange = (width) => {
|
||||
@@ -89,6 +93,8 @@ const BentoBox = ({ posts }) => {
|
||||
className={`rounded-3xl object-cover transition-opacity duration-300 ${
|
||||
introSilhouette ? 'opacity-100' : 'opacity-0 delay-75'
|
||||
}`}
|
||||
skeletonClassName="rounded-3xl"
|
||||
noRelative
|
||||
unoptimized
|
||||
priority
|
||||
/>
|
||||
@@ -99,6 +105,8 @@ const BentoBox = ({ posts }) => {
|
||||
className={`rounded-3xl object-cover transition-opacity duration-300 ${
|
||||
introSilhouette ? 'opacity-0 delay-75' : 'opacity-100'
|
||||
}`}
|
||||
skeletonClassName="rounded-3xl"
|
||||
noRelative
|
||||
unoptimized
|
||||
priority
|
||||
/>
|
||||
@@ -126,12 +134,22 @@ const BentoBox = ({ posts }) => {
|
||||
src="/static/images/bento/bento-image-1.svg"
|
||||
alt="Bento Box 1"
|
||||
fill={true}
|
||||
noRelative
|
||||
className="rounded-3xl object-cover"
|
||||
skeletonClassName="rounded-3xl"
|
||||
unoptimized
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
key="latest-post"
|
||||
@@ -152,6 +170,8 @@ const BentoBox = ({ posts }) => {
|
||||
width={0}
|
||||
height={0}
|
||||
className="m-2 w-[80%] rounded-2xl border border-border md:m-3 lg:m-4"
|
||||
skeletonClassName="rounded-3xl"
|
||||
noRelative
|
||||
unoptimized
|
||||
/>
|
||||
</SilhouetteHover>
|
||||
@@ -163,7 +183,9 @@ const BentoBox = ({ posts }) => {
|
||||
alt="Bento Box 2"
|
||||
fill={true}
|
||||
className="rounded-3xl object-cover"
|
||||
noRelative
|
||||
unoptimized
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<div key="wakatime">Child G</div>
|
||||
@@ -191,7 +213,14 @@ const BentoBox = ({ posts }) => {
|
||||
onMouseEnter={() => setIntroSilhouette(true)}
|
||||
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
|
||||
silhouetteSrc="/static/images/bento/bento-spotify-silhouette.svg"
|
||||
silhouetteAlt="Bento Spotify Silhouette"
|
||||
@@ -213,6 +242,8 @@ const BentoBox = ({ posts }) => {
|
||||
alt="Bento Technologies"
|
||||
fill={true}
|
||||
className="rounded-3xl object-cover"
|
||||
skeletonClassName="rounded-3xl"
|
||||
noRelative
|
||||
unoptimized
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import Image from 'next/image'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { FaDiscord } from 'react-icons/fa'
|
||||
|
||||
const DiscordPresence = ({ lanyard }) => {
|
||||
const DiscordPresence = ({ lanyard, onLoad }) => {
|
||||
const mainActivity = lanyard.data.activities.filter(
|
||||
(activity) => activity.type === 0 && activity.assets
|
||||
)[0]
|
||||
@@ -25,6 +25,12 @@ const DiscordPresence = ({ lanyard }) => {
|
||||
}
|
||||
}, [mainActivity?.timestamps?.start])
|
||||
|
||||
useEffect(() => {
|
||||
if (hasMainActivity && onLoad) {
|
||||
onLoad()
|
||||
}
|
||||
}, [hasMainActivity, onLoad])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="hidden bento-lg:relative w-full h-full bento-lg:flex flex-col">
|
||||
|
||||
@@ -5,7 +5,7 @@ import { set } from 'react-use-lanyard'
|
||||
|
||||
import ExternalLink from './ExternalLink'
|
||||
|
||||
const SpotifyPresence = ({ lanyard }) => {
|
||||
const SpotifyPresence = ({ lanyard, onLoad }) => {
|
||||
useEffect(() => {
|
||||
if (
|
||||
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
|
||||
|
||||
useEffect(() => {
|
||||
if (lanyard && onLoad) {
|
||||
onLoad()
|
||||
}
|
||||
}, [lanyard, onLoad])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex bento-md:hidden z-[1] bento-lg:flex h-full w-full flex-col justify-between p-6">
|
||||
|
||||
@@ -13,6 +13,8 @@ import { formatDate } from 'pliny/utils/formatDate'
|
||||
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
|
||||
interface PaginationProps {
|
||||
totalPages: number
|
||||
currentPage: number
|
||||
|
||||
52
package-lock.json
generated
52
package-lock.json
generated
@@ -28,6 +28,7 @@
|
||||
"postcss": "^8.4.24",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-github-calendar": "^4.0.1",
|
||||
"react-grid-layout": "^1.4.1",
|
||||
"react-icons": "^4.11.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
@@ -4572,6 +4573,11 @@
|
||||
"@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": {
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz",
|
||||
@@ -5793,6 +5799,11 @@
|
||||
"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": {
|
||||
"version": "2.4.63",
|
||||
"resolved": "https://registry.npmjs.org/citeproc/-/citeproc-2.4.63.tgz",
|
||||
@@ -6411,6 +6422,21 @@
|
||||
"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": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
@@ -12589,6 +12615,20 @@
|
||||
"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": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
@@ -12622,6 +12662,18 @@
|
||||
"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": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.4.1.tgz",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"postcss": "^8.4.24",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-github-calendar": "^4.0.1",
|
||||
"react-grid-layout": "^1.4.1",
|
||||
"react-icons": "^4.11.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
|
||||
Reference in New Issue
Block a user