diff --git a/src/components/common/table/ScrollableTable.js b/src/components/common/table/ScrollableTable.js new file mode 100644 index 00000000..ba501ada --- /dev/null +++ b/src/components/common/table/ScrollableTable.js @@ -0,0 +1,199 @@ +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 [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)", + }; + + 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 }); + }; + + 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(); + updateFirstColWidth(); + }); + + return () => { + table.removeEventListener("scroll", updateThumb); + window.removeEventListener("resize", updateThumb); + }; + }, []); + + 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 ( + <> + + +
+
+
+
+ {children} +
+
+
+ +
+
+
+
+
+
+ + ); +} diff --git a/src/components/community-stats/SdvCoreSection.js b/src/components/community-stats/SdvCoreSection.js index 1d0bce6e..c568def6 100644 --- a/src/components/community-stats/SdvCoreSection.js +++ b/src/components/community-stats/SdvCoreSection.js @@ -1,93 +1,26 @@ -import React, { useState, useRef, useEffect } from "react"; -import Table from "../common/table/Table"; +import React 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 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 (
-
+

SDV Community downloads

-
- +
+ -
- - - {isMobile && ( -
-
-
- )} -
- {isMobile ? ( -
- - {metricLabels[activeMetricIndex]} - -
- ) : ( - <> - {metricLabels.map((ml) => ( - {ml} - ))} - - )} + + To date + {currentYear} @@ -96,58 +29,26 @@ export default function SdvCoreSection({ dependenciesData }) { return ( -
- -
- {row.name} -
-
- - {isMobile && ( -
-
-
- )} -
- {isMobile ? ( -
- -
- {row[metricKeys[activeMetricIndex]]} -
-
+ +
+ {row.name} +
+
+ +
+ {row.toDate} +
+
+ +
+ {row.yearToDate}
- ) : ( - <> - -
- {row.toDate} -
-
- -
- {row.yearToDate} -
-
- - )} +
); })} -
+
diff --git a/src/components/community-stats/SdvInNumbersSection.js b/src/components/community-stats/SdvInNumbersSection.js index 9b4c9b23..c954823d 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" && }