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 { useEffect, useState } from "react";
|
||||||
import { useInView } from "react-intersection-observer";
|
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 { ref, inView } = useInView();
|
||||||
const [tick, setTick] = useState<number>(0);
|
const [tick, setTick] = useState<number>(0);
|
||||||
|
|
||||||
@ -18,5 +18,5 @@ export default function LoadMore({ onLoadMore, shouldLoadMore }: { onLoadMore: (
|
|||||||
return () => clearInterval(t);
|
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 NoteReaction from "Element/NoteReaction";
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
import ProfilePreview from "./ProfilePreview";
|
import ProfilePreview from "./ProfilePreview";
|
||||||
|
import Skeleton from "Element/Skeleton";
|
||||||
|
|
||||||
export interface TimelineProps {
|
export interface TimelineProps {
|
||||||
postsOnly: boolean,
|
postsOnly: boolean,
|
||||||
@ -71,7 +72,11 @@ export default function Timeline({ subject, postsOnly = false, method, ignoreMod
|
|||||||
Show latest {latestFeed.length - 1} notes
|
Show latest {latestFeed.length - 1} notes
|
||||||
</div>)}
|
</div>)}
|
||||||
{mainFeed.map(eventElement)}
|
{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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user