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
This commit is contained in:
parent
cc185674b0
commit
72ab0e25b4
@ -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<number>(0);
|
||||
|
||||
@ -18,5 +18,5 @@ export default function LoadMore({ onLoadMore, shouldLoadMore }: { onLoadMore: (
|
||||
return () => clearInterval(t);
|
||||
}, []);
|
||||
|
||||
return <div ref={ref} className="mb10">Loading...</div>;
|
||||
return <div ref={ref} className="mb10">{children ?? 'Loading...'}</div>;
|
||||
}
|
48
src/Element/Skeleton.css
Normal file
48
src/Element/Skeleton.css
Normal file
@ -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%
|
||||
);
|
||||
}
|
||||
}
|
30
src/Element/Skeleton.tsx
Normal file
30
src/Element/Skeleton.tsx
Normal file
@ -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 (
|
||||
<div
|
||||
className="skeleton"
|
||||
style={{ width: width, height: height, margin: margin }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -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
|
||||
</div>)}
|
||||
{mainFeed.map(eventElement)}
|
||||
<LoadMore onLoadMore={loadMore} shouldLoadMore={main.end} />
|
||||
<LoadMore onLoadMore={loadMore} shouldLoadMore={main.end}>
|
||||
<Skeleton width="100%" height="120px" margin="0 0 16px 0" />
|
||||
<Skeleton width="100%" height="120px" margin="0 0 16px 0" />
|
||||
<Skeleton width="100%" height="120px" margin="0 0 16px 0" />
|
||||
</LoadMore>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user