diff --git a/src/Element/LoadMore.tsx b/src/Element/LoadMore.tsx index cc3e817..d5814fd 100644 --- a/src/Element/LoadMore.tsx +++ b/src/Element/LoadMore.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { useInView } from "react-intersection-observer"; -export default function LoadMore({ onLoadMore, shouldLoadMore }: { onLoadMore: () => void, shouldLoadMore: boolean }) { +export default function LoadMore({ onLoadMore, shouldLoadMore, children }: { onLoadMore: () => void, shouldLoadMore: boolean, children?: React.ReactNode }) { const { ref, inView } = useInView(); const [tick, setTick] = useState(0); @@ -18,5 +18,5 @@ export default function LoadMore({ onLoadMore, shouldLoadMore }: { onLoadMore: ( return () => clearInterval(t); }, []); - return
Loading...
; + return
{children ?? 'Loading...'}
; } \ No newline at end of file diff --git a/src/Element/Skeleton.css b/src/Element/Skeleton.css new file mode 100644 index 0000000..a26348e --- /dev/null +++ b/src/Element/Skeleton.css @@ -0,0 +1,48 @@ +.skeleton { + display: inline-block; + height: 1em; + position: relative; + overflow: hidden; + background-color: #dddbdd; + border-radius: 16px; +} + +.skeleton::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + transform: translateX(-100%); + background-image: linear-gradient( + 90deg, + rgba(255, 255, 255, 0) 0, + rgba(255, 255, 255, 0.2) 20%, + rgba(255, 255, 255, 0.5) 60%, + rgba(255, 255, 255, 0) + ); + animation: shimmer 2s infinite; + content: ""; +} + +@keyframes shimmer { + 100% { + transform: translateX(100%); + } +} + +@media screen and (prefers-color-scheme: dark) { + .skeleton { + background-color: #50535a; + } + + .skeleton::after { + background-image: linear-gradient( + 90deg, + #50535a 0%, + #656871 20%, + #50535a 40%, + #50535a 100% + ); + } +} diff --git a/src/Element/Skeleton.tsx b/src/Element/Skeleton.tsx new file mode 100644 index 0000000..024919c --- /dev/null +++ b/src/Element/Skeleton.tsx @@ -0,0 +1,30 @@ +import "./Skeleton.css"; + +interface ISkepetonProps { + children?: React.ReactNode; + loading?: boolean; + width?: string; + height?: string; + margin?: string; +} + +export default function Skeleton({ + children, + width, + height, + margin, + loading = true, +}: ISkepetonProps) { + if (!loading) { + return <>{children}; + } + + return ( +
+ {children} +
+ ); +} diff --git a/src/Element/Timeline.tsx b/src/Element/Timeline.tsx index dc55b3d..a3223dd 100644 --- a/src/Element/Timeline.tsx +++ b/src/Element/Timeline.tsx @@ -12,6 +12,7 @@ import Note from "Element/Note"; import NoteReaction from "Element/NoteReaction"; import useModeration from "Hooks/useModeration"; import ProfilePreview from "./ProfilePreview"; +import Skeleton from "Element/Skeleton"; export interface TimelineProps { postsOnly: boolean, @@ -71,7 +72,11 @@ export default function Timeline({ subject, postsOnly = false, method, ignoreMod Show latest {latestFeed.length - 1} notes )} {mainFeed.map(eventElement)} - + + + + + ); }