From 08517d1a9993c62d5b1c443b3dfb3d7685fdc2a0 Mon Sep 17 00:00:00 2001 From: pcarapic15 Date: Fri, 17 Oct 2025 16:58:35 +0200 Subject: [PATCH 1/4] Add horizontal scroll to community table --- src/components/common/table/Table.js | 104 +++++++++++-- .../community-stats/SdvCoreSection.js | 139 +++--------------- 2 files changed, 113 insertions(+), 130 deletions(-) diff --git a/src/components/common/table/Table.js b/src/components/common/table/Table.js index b16e4543..d9f4c3ae 100644 --- a/src/components/common/table/Table.js +++ b/src/components/common/table/Table.js @@ -1,25 +1,107 @@ -import React from "react"; +import React, { useRef, useEffect, useState } from "react"; export default function Table({ children, tableColDimensions }) { + const tableScrollRef = useRef(null); + const [thumbStyle, setThumbStyle] = useState({ width: "0%", left: "0%" }); + const customStyles = { "--table-col-dimensions": `${tableColDimensions ?? "minmax(0, 1fr)"}`, gridTemplateAreas: `"header" "body"`, boxShadow: "2.135px 8.539px 21.347px 0px rgba(0, 0, 54, 0.12)", }; + useEffect(() => { + const table = tableScrollRef.current; + if (!table) return; + + const updateThumb = () => { + const { scrollWidth, clientWidth, scrollLeft } = table; + if (scrollWidth <= clientWidth) { + setThumbStyle({ width: "100%", left: "0%" }); + return; + } + + const ratio = clientWidth / scrollWidth; + const width = `${ratio * 100}%`; + const left = `${ + (scrollLeft / (scrollWidth - clientWidth)) * (100 - ratio * 100) + }%`; + setThumbStyle({ width, left }); + }; + + updateThumb(); + table.addEventListener("scroll", updateThumb); + window.addEventListener("resize", updateThumb); + + return () => { + table.removeEventListener("scroll", updateThumb); + window.removeEventListener("resize", updateThumb); + }; + }, []); + return ( -
+ <> + +
- {children} +
+
+ {children} +
+
+
+ +
+
+
+
-
+ ); } diff --git a/src/components/community-stats/SdvCoreSection.js b/src/components/community-stats/SdvCoreSection.js index 1d0bce6e..9210560a 100644 --- a/src/components/community-stats/SdvCoreSection.js +++ b/src/components/community-stats/SdvCoreSection.js @@ -1,50 +1,13 @@ -import React, { useState, useRef, useEffect } from "react"; +import React from "react"; import Table from "../common/table/Table"; import TableHeader from "../common/table/TableHeader"; import TableHeaderCell from "../common/table/TableHeaderCell"; import TableBody from "../common/table/TableBody"; import TableRow from "../common/table/TableRow"; import TableRowCell from "../common/table/TableRowCell"; -import useWindowWidth from "../../hooks/useviewport"; export default function SdvCoreSection({ dependenciesData }) { const currentYear = new Date().getFullYear().toString(); - const metricKeys = ["toDate", "yearToDate"]; - const metricLabels = ["To date", currentYear]; - const [tableColDimensions, setTableColDimensions] = useState( - "minmax(134px, 199px) minmax(136px, 488px) minmax(136px, 488px)" - ); - const width = useWindowWidth(); - const isMobile = width < 768; - - const [activeMetricIndex, setActiveMetricIndex] = useState(0); - const touchStartX = useRef(null); - - useEffect(() => { - setTableColDimensions( - isMobile - ? "minmax(134px, 199px) minmax(136px, 488px)" - : "minmax(134px, 199px) minmax(136px, 488px) minmax(136px, 488px)" - ); - }, [isMobile]); - - const handleSwipeStart = (e) => { - touchStartX.current = e.touches[0].clientX; - }; - - const handleSwipeMove = (e) => { - if (touchStartX.current === null) return; - const deltaX = e.touches[0].clientX - touchStartX.current; - const threshold = 50; - - if (deltaX > threshold) { - setActiveMetricIndex((prev) => Math.max(prev - 1, 0)); - touchStartX.current = null; - } else if (deltaX < -threshold) { - setActiveMetricIndex((prev) => Math.min(prev + 1, metricKeys.length - 1)); - touchStartX.current = null; - } - }; return (
@@ -52,42 +15,12 @@ export default function SdvCoreSection({ dependenciesData }) {

SDV Community downloads

-
- +
+
-
- - - {isMobile && ( -
-
-
- )} -
- {isMobile ? ( -
- - {metricLabels[activeMetricIndex]} - -
- ) : ( - <> - {metricLabels.map((ml) => ( - {ml} - ))} - - )} + + To date + {currentYear} @@ -96,53 +29,21 @@ export default function SdvCoreSection({ dependenciesData }) { return ( -
- -
- {row.name} -
-
- - {isMobile && ( -
-
-
- )} -
- {isMobile ? ( -
- -
- {row[metricKeys[activeMetricIndex]]} -
-
+ +
+ {row.name} +
+
+ +
+ {row.toDate} +
+
+ +
+ {row.yearToDate}
- ) : ( - <> - -
- {row.toDate} -
-
- -
- {row.yearToDate} -
-
- - )} +
); })} From 194c398d167ce7839a78c594e6233a364233c9b3 Mon Sep 17 00:00:00 2001 From: pcarapic15 Date: Mon, 20 Oct 2025 10:40:38 +0200 Subject: [PATCH 2/4] Fix scrollable table layout, apply to numbers section --- .../common/table/ScrollableTable.js | 120 +++++++++++++++ src/components/common/table/Table.js | 104 ++----------- .../community-stats/SdvCoreSection.js | 8 +- .../community-stats/SdvInNumbersSection.js | 141 +++--------------- 4 files changed, 156 insertions(+), 217 deletions(-) create mode 100644 src/components/common/table/ScrollableTable.js diff --git a/src/components/common/table/ScrollableTable.js b/src/components/common/table/ScrollableTable.js new file mode 100644 index 00000000..1e5da523 --- /dev/null +++ b/src/components/common/table/ScrollableTable.js @@ -0,0 +1,120 @@ +import React, { useRef, useEffect, useState } from "react"; + +export default function ScrollableTable({ children, tableColDimensions }) { + const tableScrollRef = useRef(null); + const [thumbStyle, setThumbStyle] = useState({ width: "0%", left: "0%" }); + + const customStyles = { + "--table-col-dimensions": `${tableColDimensions ?? "minmax(0, 1fr)"}`, + gridTemplateAreas: `"header" "body"`, + boxShadow: "2.135px 8.539px 21.347px 0px rgba(0, 0, 54, 0.12)", + }; + + useEffect(() => { + const table = tableScrollRef.current; + if (!table) return; + + const updateThumb = () => { + const { scrollWidth, clientWidth, scrollLeft } = table; + if (scrollWidth <= clientWidth) { + setThumbStyle({ width: "100%", left: "0%" }); + return; + } + + const ratio = clientWidth / scrollWidth; + const width = `${ratio * 100}%`; + const left = `${ + (scrollLeft / (scrollWidth - clientWidth)) * (100 - ratio * 100) + }%`; + setThumbStyle({ width, left }); + }; + + updateThumb(); + table.addEventListener("scroll", updateThumb); + window.addEventListener("resize", updateThumb); + + return () => { + table.removeEventListener("scroll", updateThumb); + window.removeEventListener("resize", updateThumb); + }; + }, []); + + return ( + <> + + +
+
+
+
+ {children} +
+
+
+ +
+
+
+
+
+
+ + ); +} diff --git a/src/components/common/table/Table.js b/src/components/common/table/Table.js index d9f4c3ae..b16e4543 100644 --- a/src/components/common/table/Table.js +++ b/src/components/common/table/Table.js @@ -1,107 +1,25 @@ -import React, { useRef, useEffect, useState } from "react"; +import React from "react"; export default function Table({ children, tableColDimensions }) { - const tableScrollRef = useRef(null); - const [thumbStyle, setThumbStyle] = useState({ width: "0%", left: "0%" }); - const customStyles = { "--table-col-dimensions": `${tableColDimensions ?? "minmax(0, 1fr)"}`, gridTemplateAreas: `"header" "body"`, boxShadow: "2.135px 8.539px 21.347px 0px rgba(0, 0, 54, 0.12)", }; - useEffect(() => { - const table = tableScrollRef.current; - if (!table) return; - - const updateThumb = () => { - const { scrollWidth, clientWidth, scrollLeft } = table; - if (scrollWidth <= clientWidth) { - setThumbStyle({ width: "100%", left: "0%" }); - return; - } - - const ratio = clientWidth / scrollWidth; - const width = `${ratio * 100}%`; - const left = `${ - (scrollLeft / (scrollWidth - clientWidth)) * (100 - ratio * 100) - }%`; - setThumbStyle({ width, left }); - }; - - updateThumb(); - table.addEventListener("scroll", updateThumb); - window.addEventListener("resize", updateThumb); - - return () => { - table.removeEventListener("scroll", updateThumb); - window.removeEventListener("resize", updateThumb); - }; - }, []); - return ( - <> - - +
-
-
- {children} -
-
-
- -
-
-
-
+ {children}
- +
); } diff --git a/src/components/community-stats/SdvCoreSection.js b/src/components/community-stats/SdvCoreSection.js index 9210560a..8a3a5eef 100644 --- a/src/components/community-stats/SdvCoreSection.js +++ b/src/components/community-stats/SdvCoreSection.js @@ -1,5 +1,5 @@ import React from "react"; -import Table from "../common/table/Table"; +import ScrollableTable from "../common/table/ScrollableTable"; import TableHeader from "../common/table/TableHeader"; import TableHeaderCell from "../common/table/TableHeaderCell"; import TableBody from "../common/table/TableBody"; @@ -11,12 +11,12 @@ export default function SdvCoreSection({ dependenciesData }) { return (
-
+

SDV Community downloads

-
+ To date @@ -48,7 +48,7 @@ export default function SdvCoreSection({ dependenciesData }) { ); })} -
+
diff --git a/src/components/community-stats/SdvInNumbersSection.js b/src/components/community-stats/SdvInNumbersSection.js index 9b4c9b23..2fc55dc4 100644 --- a/src/components/community-stats/SdvInNumbersSection.js +++ b/src/components/community-stats/SdvInNumbersSection.js @@ -1,52 +1,15 @@ -import React, { useState, useEffect, useRef } from "react"; -import Table from "../common/table/Table"; +import React, { useState } from "react"; +import ScrollableTable from "../common/table/ScrollableTable"; import TableHeader from "../common/table/TableHeader"; import TableHeaderCell from "../common/table/TableHeaderCell"; import TableBody from "../common/table/TableBody"; import TableRow from "../common/table/TableRow"; import TableRowCell from "../common/table/TableRowCell"; import Tab from "../common/Tab"; -import useWindowWidth from "../../hooks/useviewport"; import CustomPieChart from "./CustomPieChart"; export default function SdvInNumbersSection({ data }) { const currentYear = new Date().getFullYear().toString(); - const metricKeys = ["toDate", "yearToDate"]; - const metricLabels = ["To date", currentYear]; - const [tableColDimensions, setTableColDimensions] = useState( - "minmax(199px, 199px) minmax(130px, 488px) minmax(136px, 488px)" - ); - const width = useWindowWidth(); - const isMobile = width < 768; - - const [activeMetricIndex, setActiveMetricIndex] = useState(0); - const touchStartX = useRef(null); - - useEffect(() => { - setTableColDimensions( - isMobile - ? "minmax(192px, 199px) minmax(110px, 488px)" - : "minmax(199px, 199px) minmax(130px, 488px) minmax(136px, 488px)" - ); - }, [isMobile]); - - const handleSwipeStart = (e) => { - touchStartX.current = e.touches[0].clientX; - }; - - const handleSwipeMove = (e) => { - if (touchStartX.current === null) return; - const deltaX = e.touches[0].clientX - touchStartX.current; - const threshold = 50; - - if (deltaX > threshold) { - setActiveMetricIndex((prev) => Math.max(prev - 1, 0)); - touchStartX.current = null; - } else if (deltaX < -threshold) { - setActiveMetricIndex((prev) => Math.min(prev + 1, metricKeys.length - 1)); - touchStartX.current = null; - } - }; const [tabs, setTabs] = useState([ { label: "Visualize", isActive: true }, @@ -98,41 +61,11 @@ export default function SdvInNumbersSection({ data }) {
{activeTab.label === "Downloads" && ( - + -
- - - {isMobile && ( -
-
-
- )} -
- {isMobile ? ( -
- - {metricLabels[activeMetricIndex]} - -
- ) : ( - <> - {metricLabels.map((ml) => ( - {ml} - ))} - - )} + + To date + {currentYear} @@ -141,58 +74,26 @@ export default function SdvInNumbersSection({ data }) { return ( -
- -
- {row.name} -
-
- - {isMobile && ( -
-
-
- )} -
- {isMobile ? ( -
- -
- {row[metricKeys[activeMetricIndex]]} -
-
+ +
+ {row.name} +
+
+ +
+ {row.toDate} +
+
+ +
+ {row.yearToDate}
- ) : ( - <> - -
- {row.toDate} -
-
- -
- {row.yearToDate} -
-
- - )} +
); })} -
+ )} {activeTab.label === "Visualize" && }
From a52a5f06bbdf6a4f1e1b9ab85b263a8d85b67495 Mon Sep 17 00:00:00 2001 From: pcarapic15 Date: Mon, 20 Oct 2025 15:30:46 +0200 Subject: [PATCH 3/4] Fix box shadow, add touch scrolling --- .../common/table/ScrollableTable.js | 79 ++++++++++++++++--- 1 file changed, 66 insertions(+), 13 deletions(-) diff --git a/src/components/common/table/ScrollableTable.js b/src/components/common/table/ScrollableTable.js index 1e5da523..6cccb611 100644 --- a/src/components/common/table/ScrollableTable.js +++ b/src/components/common/table/ScrollableTable.js @@ -2,7 +2,10 @@ import React, { useRef, useEffect, useState } from "react"; export default function ScrollableTable({ children, tableColDimensions }) { const tableScrollRef = useRef(null); + const scrollbarRef = useRef(null); const [thumbStyle, setThumbStyle] = useState({ width: "0%", left: "0%" }); + const [isDragging, setIsDragging] = useState(false); + const dragStartRef = useRef({ touchX: 0, scrollLeft: 0 }); const customStyles = { "--table-col-dimensions": `${tableColDimensions ?? "minmax(0, 1fr)"}`, @@ -39,6 +42,48 @@ export default function ScrollableTable({ children, tableColDimensions }) { }; }, []); + const handleTouchStart = (e) => { + const table = tableScrollRef.current; + const scrollbar = scrollbarRef.current; + if (!table || !scrollbar) return; + + setIsDragging(true); + dragStartRef.current = { + touchX: e.touches[0].clientX, + scrollLeft: table.scrollLeft, + }; + }; + + useEffect(() => { + if (!isDragging) return; + + const handleTouchMove = (e) => { + e.preventDefault(); + const table = tableScrollRef.current; + const scrollbar = scrollbarRef.current; + if (!table || !scrollbar) return; + + const rect = scrollbar.getBoundingClientRect(); + const x = Math.max(0, Math.min(e.touches[0].clientX - rect.left, rect.width)); + const percentage = x / rect.width; + + const { scrollWidth, clientWidth } = table; + table.scrollLeft = percentage * (scrollWidth - clientWidth); + }; + + const handleTouchEnd = () => { + setIsDragging(false); + }; + + document.addEventListener("touchmove", handleTouchMove, { passive: false }); + document.addEventListener("touchend", handleTouchEnd); + + return () => { + document.removeEventListener("touchmove", handleTouchMove); + document.removeEventListener("touchend", handleTouchEnd); + }; + }, [isDragging]); + return ( <> @@ -110,8 +163,8 @@ export default function ScrollableTable({ children, tableColDimensions }) {
-
-
+
+
From 68e1edc684064967aba263187a662f7e9a7fb070 Mon Sep 17 00:00:00 2001 From: pcarapic15 Date: Mon, 20 Oct 2025 16:24:00 +0200 Subject: [PATCH 4/4] Add swipe scroll, fix box shadow, fix table column widths --- .../common/table/ScrollableTable.js | 36 ++++++++++++++++--- .../community-stats/SdvCoreSection.js | 2 +- .../community-stats/SdvInNumbersSection.js | 2 +- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/components/common/table/ScrollableTable.js b/src/components/common/table/ScrollableTable.js index 6cccb611..ba501ada 100644 --- a/src/components/common/table/ScrollableTable.js +++ b/src/components/common/table/ScrollableTable.js @@ -6,9 +6,11 @@ export default function ScrollableTable({ children, tableColDimensions }) { const [thumbStyle, setThumbStyle] = useState({ width: "0%", left: "0%" }); const [isDragging, setIsDragging] = useState(false); const dragStartRef = useRef({ touchX: 0, scrollLeft: 0 }); + const [firstColWidth, setFirstColWidth] = useState(199); const customStyles = { "--table-col-dimensions": `${tableColDimensions ?? "minmax(0, 1fr)"}`, + "--first-col-width": `${firstColWidth}px`, gridTemplateAreas: `"header" "body"`, boxShadow: "2.135px 8.539px 21.347px 0px rgba(0, 0, 54, 0.12)", }; @@ -32,9 +34,22 @@ export default function ScrollableTable({ children, tableColDimensions }) { setThumbStyle({ width, left }); }; + const updateFirstColWidth = () => { + const firstCell = table.querySelector( + ".scrollable-table-grid > div:first-child > div:first-child" + ); + if (firstCell) { + setFirstColWidth(firstCell.offsetWidth); + } + }; + updateThumb(); + updateFirstColWidth(); table.addEventListener("scroll", updateThumb); - window.addEventListener("resize", updateThumb); + window.addEventListener("resize", () => { + updateThumb(); + updateFirstColWidth(); + }); return () => { table.removeEventListener("scroll", updateThumb); @@ -64,7 +79,10 @@ export default function ScrollableTable({ children, tableColDimensions }) { if (!table || !scrollbar) return; const rect = scrollbar.getBoundingClientRect(); - const x = Math.max(0, Math.min(e.touches[0].clientX - rect.left, rect.width)); + const x = Math.max( + 0, + Math.min(e.touches[0].clientX - rect.left, rect.width) + ); const percentage = x / rect.width; const { scrollWidth, clientWidth } = table; @@ -118,7 +136,7 @@ export default function ScrollableTable({ children, tableColDimensions }) { top: 0; bottom: 0; left: 0; - width: 199px; + width: var(--first-col-width, 199px); box-shadow: 7px 0px 20px -10px rgba(0, 0, 54, 0.14); pointer-events: none; z-index: 1; @@ -151,6 +169,7 @@ export default function ScrollableTable({ children, tableColDimensions }) { style={{ scrollbarWidth: "none", msOverflowStyle: "none", + boxShadow: "rgba(0, 0, 54, 0.12) 2.135px 8.539px 21.347px 0px", }} >
-
-
+
+
diff --git a/src/components/community-stats/SdvCoreSection.js b/src/components/community-stats/SdvCoreSection.js index 8a3a5eef..c568def6 100644 --- a/src/components/community-stats/SdvCoreSection.js +++ b/src/components/community-stats/SdvCoreSection.js @@ -16,7 +16,7 @@ export default function SdvCoreSection({ dependenciesData }) { SDV Community downloads
- + To date diff --git a/src/components/community-stats/SdvInNumbersSection.js b/src/components/community-stats/SdvInNumbersSection.js index 2fc55dc4..c954823d 100644 --- a/src/components/community-stats/SdvInNumbersSection.js +++ b/src/components/community-stats/SdvInNumbersSection.js @@ -61,7 +61,7 @@ export default function SdvInNumbersSection({ data }) {
{activeTab.label === "Downloads" && ( - + To date