Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ module.exports = [
HTMLButtonElement: "readonly",
HTMLDivElement: "readonly",
HTMLLIElement: "readonly",
HTMLImageElement: "readonly",
MouseEvent: "readonly",
KeyboardEvent: "readonly",
Node: "readonly",
Expand Down
38 changes: 0 additions & 38 deletions src/components/Common/CardImage.tsx

This file was deleted.

11 changes: 5 additions & 6 deletions src/components/Common/CardWithImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Heading from "@theme/Heading";
import { Icon } from "@site/src/components/Common/Icon";
import { IconName } from "@site/src/typescript/iconName";
import Badge from "@site/src/components/Common/Badge";
import CardImage from "@site/src/components/Common/CardImage";
import LazyImage from "@site/src/components/Common/LazyImage";
import isInternalUrl from "@docusaurus/isInternalUrl";
import clsx from "clsx";
import { useTagLimit } from "@site/src/hooks/useTagLimit";
Expand Down Expand Up @@ -65,15 +65,14 @@ export default function CardWithImage({
"flex items-center justify-center",
"rounded-xl mb-4 overflow-hidden",
"relative aspect-[79/24]",
hasImage
? "bg-black/40"
: "bg-gradient-to-b from-[#204879] to-[#0F1425] to-[70%]",
!hasImage &&
"bg-gradient-to-b from-[#204879] to-[#0F1425] to-[70%]",
)}
>
{hasImage ? (
<CardImage
<LazyImage
imgSrc={imgSrc}
imgAlt={imgAlt}
alt={imgAlt}
className={clsx(
"pointer-events-none",
"w-full h-full object-cover object-center",
Expand Down
6 changes: 3 additions & 3 deletions src/components/Common/CardWithImageHorizontal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Link from "@docusaurus/Link";
import Heading from "@theme/Heading";
import { IconName } from "@site/src/typescript/iconName";
import Badge from "@site/src/components/Common/Badge";
import CardImage from "@site/src/components/Common/CardImage";
import LazyImage from "@site/src/components/Common/LazyImage";
import isInternalUrl from "@docusaurus/isInternalUrl";
import { clsx } from "clsx";

Expand Down Expand Up @@ -44,9 +44,9 @@ export default function CardWithImageHorizontal({
"relative flex items-center",
)}
>
<CardImage
<LazyImage
imgSrc={imgSrc}
imgAlt={imgAlt}
alt={imgAlt}
className={clsx(
"pointer-events-none",
"w-full h-auto object-contain",
Expand Down
79 changes: 79 additions & 0 deletions src/components/Common/LazyImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useState } from "react";
import clsx from "clsx";
import ThemedImage, { Props as ThemedImageProps } from "@theme/ThemedImage";

export interface LazyImageProps
extends React.ImgHTMLAttributes<HTMLImageElement> {
imgSrc?: string | { light: string; dark: string };
}

export default function LazyImage({
imgSrc,
src,
alt = "",
className,
style,
...props
}: LazyImageProps) {
const [isLoaded, setIsLoaded] = useState(false);

const handleLoaded = () => {
setIsLoaded(true);
};

const sources = getSources({ imgSrc, src });

return (
<span
className={clsx(
"relative overflow-hidden inline-block w-full",
className,
)}
style={{
...style,
minHeight: !isLoaded ? "100px" : undefined,
}}
>
{!isLoaded && (
<span
className="absolute inset-0 skeleton rounded-[inherit] z-10"
aria-hidden="true"
/>
)}
<ThemedImage
{...props}
sources={sources}
alt={alt}
className={clsx(
className,
"transition-opacity duration-300",
!isLoaded ? "opacity-0" : "opacity-100",
)}
onLoad={handleLoaded}
onError={handleLoaded}
loading="lazy"
/>
</span>
);
}

function getSources({
imgSrc,
src,
}: Pick<LazyImageProps, "imgSrc" | "src">): ThemedImageProps["sources"] {
if (src) {
return {
light: src,
dark: src,
};
}

if (typeof imgSrc === "string") {
return {
light: imgSrc,
dark: imgSrc,
};
}

return imgSrc;
}
41 changes: 41 additions & 0 deletions src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

@custom-variant dark (&:is([data-theme="dark"] *));

@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
scroll-behavior: auto !important;
}
}

/* Custom animations for layout switcher */
@keyframes slide-in-from-bottom {
from {
Expand Down Expand Up @@ -575,3 +583,36 @@ a {
.theme-doc-sidebar-item-category-level-1 > .menu__list {
@apply !m-0 !p-0 !border-0;
}

/* Skeleton loading animation */
@keyframes skeleton-loading {
0% {
background-position: 150% 0;
}
100% {
background-position: -150% 0;
}
}

.skeleton {
@apply !bg-black/10 dark:!bg-white/5;
color: transparent !important;
background-image: linear-gradient(
45deg,
color-mix(in oklab, var(--color-black) 7.5%, transparent) 40%,
color-mix(in oklab, var(--color-black) 0.5%, transparent) 50%,
color-mix(in oklab, var(--color-black) 7.5%, transparent) 60%
);
background-size: 350% 100%;
border-radius: var(--radius-xl);
animation: skeleton-loading 2000ms infinite linear;
}

[data-theme="dark"] .skeleton {
background-image: linear-gradient(
45deg,
color-mix(in oklab, var(--color-white) 0.5%, transparent) 40%,
color-mix(in oklab, var(--color-white) 7.5%, transparent) 50%,
color-mix(in oklab, var(--color-white) 0.5%, transparent) 60%
);
}
6 changes: 6 additions & 0 deletions src/theme/MDXComponents/MDXImg.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React, { ComponentProps } from "react";
import LazyImage from "@site/src/components/Common/LazyImage";

export default function MDXImg(props: ComponentProps<typeof LazyImage>) {
return <LazyImage {...props} />;
}
7 changes: 7 additions & 0 deletions src/theme/MDXComponents/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import MDXComponents from "@theme-original/MDXComponents";
import MDXImg from "./MDXImg";

export default {
...MDXComponents,
img: MDXImg,
} satisfies typeof MDXComponents;