From 72ab0e25b4550d65fe5fae90b3ec834b65d16f5e Mon Sep 17 00:00:00 2001 From: Leonardo Tuna Date: Mon, 6 Feb 2023 16:05:22 -0300 Subject: [PATCH] Skeleton component on timeline loading for better user experience (#190) * the LoadMore component should accept children to have something different than "Loading..." * skeleton component * the timeline component now loads with a skeleton * border radius for skeleton * dark mode for skeleton --- src/Element/LoadMore.tsx | 4 ++-- src/Element/Skeleton.css | 48 ++++++++++++++++++++++++++++++++++++++++ src/Element/Skeleton.tsx | 30 +++++++++++++++++++++++++ src/Element/Timeline.tsx | 7 +++++- 4 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 src/Element/Skeleton.css create mode 100644 src/Element/Skeleton.tsx diff --git a/src/Element/LoadMore.tsx b/src/Element/LoadMore.tsx index cc3e817e..d5814fd6 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 00000000..a26348ec --- /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 00000000..024919cd --- /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 dc55b3d9..a3223dd5 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)} - + + + + + ); }