From 666f98c68b962e0935233277ee9f1e04d1241b46 Mon Sep 17 00:00:00 2001 From: Sam Chau Date: Wed, 27 Aug 2025 03:23:24 +0930 Subject: [PATCH] feat(card): add visibility handling and loading placeholders for FormatCard and RegexCard components to improve performance --- frontend/src/components/format/FormatCard.jsx | 35 ++++++++++++++++-- frontend/src/components/regex/RegexCard.jsx | 36 +++++++++++++++++-- 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/format/FormatCard.jsx b/frontend/src/components/format/FormatCard.jsx index 506cb43..a0860a3 100644 --- a/frontend/src/components/format/FormatCard.jsx +++ b/frontend/src/components/format/FormatCard.jsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react'; +import React, {useState, useEffect, useRef} from 'react'; import PropTypes from 'prop-types'; import {Copy, Check, FlaskConical, FileText, ListFilter} from 'lucide-react'; import Tooltip from '@ui/Tooltip'; @@ -14,6 +14,8 @@ function FormatCard({ willBeSelected, onSelect }) { + const [isVisible, setIsVisible] = useState(false); + const cardRef = useRef(null); const [showDescription, setShowDescription] = useState(() => { const saved = localStorage.getItem(`format-view-${format.file_name}`); return saved !== null ? JSON.parse(saved) : true; @@ -64,8 +66,27 @@ function FormatCard({ } }; + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + setIsVisible(entry.isIntersecting); + }, + { + threshold: 0, + rootMargin: '100px' // Keep cards rendered 100px outside viewport + } + ); + + if (cardRef.current) { + observer.observe(cardRef.current); + } + + return () => observer.disconnect(); + }, []); + return (
-
+ {isVisible ? ( +
{/* Header Section */}
@@ -237,6 +259,15 @@ function FormatCard({ )}
+ ) : ( +
+
+
+
+
+
+
+ )}
); } diff --git a/frontend/src/components/regex/RegexCard.jsx b/frontend/src/components/regex/RegexCard.jsx index 1fde0ac..5f5e434 100644 --- a/frontend/src/components/regex/RegexCard.jsx +++ b/frontend/src/components/regex/RegexCard.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useState, useEffect, useRef} from 'react'; import PropTypes from 'prop-types'; import {Copy, Check, FlaskConical} from 'lucide-react'; import Tooltip from '@ui/Tooltip'; @@ -15,6 +15,9 @@ const RegexCard = ({ willBeSelected, onSelect }) => { + const [isVisible, setIsVisible] = useState(false); + const cardRef = useRef(null); + const totalTests = pattern.tests?.length || 0; const passedTests = pattern.tests?.filter(t => t.passes)?.length || 0; const passRate = @@ -46,8 +49,27 @@ const RegexCard = ({ return 'text-red-400'; }; + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + setIsVisible(entry.isIntersecting); + }, + { + threshold: 0, + rootMargin: '100px' // Keep cards rendered 100px outside viewport + } + ); + + if (cardRef.current) { + observer.observe(cardRef.current); + } + + return () => observer.disconnect(); + }, []); + return (
-
+ {isVisible ? ( +
{/* Header Section */}
@@ -183,6 +206,15 @@ const RegexCard = ({ )}
+ ) : ( +
+
+
+
+
+
+
+ )}
); };