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:
|
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) |
|
||||||
|
|||||||
@@ -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}`}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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
52
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user