From 3efd78025bba7b6a2437755f409abfb48752f1da Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Sat, 3 May 2025 16:06:14 -0600 Subject: [PATCH] add theme selector carousel --- src/assets/css/index.css | 45 ++++++++++++ src/pages/parts/settings/AppearancePart.tsx | 79 ++++++++++++++++++--- 2 files changed, 116 insertions(+), 8 deletions(-) diff --git a/src/assets/css/index.css b/src/assets/css/index.css index 0b50127a..2bcf2fbf 100644 --- a/src/assets/css/index.css +++ b/src/assets/css/index.css @@ -288,4 +288,49 @@ input[type=range].styled-slider.slider-progress::-ms-fill-lower { rgba(0, 0, 0, 0) 100% ); } +} + +.vertical-carousel-container { + --mask-fade-distance: 80px; + position: relative; + -webkit-mask-image: linear-gradient( + to bottom, + rgba(0, 0, 0, 0), + rgba(0, 0, 0, 1) var(--mask-fade-distance), + rgba(0, 0, 0, 1) calc(100% - var(--mask-fade-distance)), + rgba(0, 0, 0, 0) 100% + ); + mask-image: var(-webkit-mask-image); + z-index: 1; +} + +.vertical-carousel-container.mask-none { + -webkit-mask-image: none; + mask-image: none; +} + +.vertical-carousel-container.mask-image-bottom-only { + -webkit-mask-image: linear-gradient( + to bottom, + rgba(0, 0, 0, 1), + rgba(0, 0, 0, 1) calc(100% - var(--mask-fade-distance)), + rgba(0, 0, 0, 0) 100% + ); + mask-image: var(-webkit-mask-image); +} + +.vertical-carousel-container.mask-image-top-only { + -webkit-mask-image: linear-gradient( + to bottom, + rgba(0, 0, 0, 0), + rgba(0, 0, 0, 1) var(--mask-fade-distance), + rgba(0, 0, 0, 1) + ); + mask-image: var(-webkit-mask-image); +} + +@media (max-width: 768px) { + .vertical-carousel-container { + --mask-fade-distance: 20px; + } } \ No newline at end of file diff --git a/src/pages/parts/settings/AppearancePart.tsx b/src/pages/parts/settings/AppearancePart.tsx index e99733d9..b5474c38 100644 --- a/src/pages/parts/settings/AppearancePart.tsx +++ b/src/pages/parts/settings/AppearancePart.tsx @@ -1,4 +1,5 @@ import classNames from "classnames"; +import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { Toggle } from "@/components/buttons/Toggle"; @@ -164,6 +165,51 @@ export function AppearancePart(props: { }) { const { t } = useTranslation(); + const carouselRef = useRef(null); + const activeThemeRef = useRef(null); + const [isAtTop, setIsAtTop] = useState(true); + const [isAtBottom, setIsAtBottom] = useState(false); + const topSentinelRef = useRef(null); + const bottomSentinelRef = useRef(null); + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.target === topSentinelRef.current) { + setIsAtTop(entry.isIntersecting); + } + if (entry.target === bottomSentinelRef.current) { + setIsAtBottom(entry.isIntersecting); + } + }); + }, + { root: carouselRef.current, threshold: 1 }, + ); + + if (topSentinelRef.current) observer.observe(topSentinelRef.current); + if (bottomSentinelRef.current) observer.observe(bottomSentinelRef.current); + + return () => observer.disconnect(); + }, []); + + useEffect(() => { + if (activeThemeRef.current && carouselRef.current) { + const element = activeThemeRef.current; + const container = carouselRef.current; + + const elementRect = element.getBoundingClientRect(); + const containerRect = container.getBoundingClientRect(); + + // Center the element in the container + container.scrollTop = + elementRect.top + + container.scrollTop - + containerRect.top - + (containerRect.height - elementRect.height) / 2; + } + }, [props.active]); + return (
{t("settings.appearance.title")} @@ -214,17 +260,34 @@ export function AppearancePart(props: { {/* Second Column - Themes */}
-
+
+
{availableThemes.map((v) => ( - props.setTheme(v.id)} - /> + ref={props.active === v.id ? activeThemeRef : null} + > + props.setTheme(v.id)} + /> +
))} +