From 7d56d67a7cbb3e20571efe52ff206f085ae5ef7a Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Sun, 21 Dec 2025 08:21:55 -0800 Subject: [PATCH] Refactor DevicePanel to enhance location selection UI - Introduced a new `LocationsScroller` component to improve the display and selection of device locations, allowing for horizontal scrolling. - Updated the `DeviceCard` to utilize the `LocationsScroller`, enhancing user interaction with location buttons. - Added scroll state management to enable smooth scrolling behavior and visual feedback for available scroll directions. - Integrated new icons for navigation buttons, improving the overall aesthetic and usability of the location selection interface. --- .../src/routes/overview/DevicePanel.tsx | 178 +++++++++++++----- 1 file changed, 128 insertions(+), 50 deletions(-) diff --git a/packages/interface/src/routes/overview/DevicePanel.tsx b/packages/interface/src/routes/overview/DevicePanel.tsx index 38705ceec..a16dfdbc5 100644 --- a/packages/interface/src/routes/overview/DevicePanel.tsx +++ b/packages/interface/src/routes/overview/DevicePanel.tsx @@ -1,6 +1,6 @@ -import { useState } from "react"; +import { useState, useRef, useEffect } from "react"; import { motion } from "framer-motion"; -import { HardDrive, Plus, Database } from "@phosphor-icons/react"; +import { HardDrive, Plus, Database, CaretLeft, CaretRight } from "@phosphor-icons/react"; import Masonry from "react-masonry-css"; import DriveIcon from "@sd/assets/icons/Drive.png"; import HDDIcon from "@sd/assets/icons/HDD.png"; @@ -10,6 +10,7 @@ import DriveAmazonS3Icon from "@sd/assets/icons/Drive-AmazonS3.png"; import DriveGoogleDriveIcon from "@sd/assets/icons/Drive-GoogleDrive.png"; import DriveDropboxIcon from "@sd/assets/icons/Drive-Dropbox.png"; import LocationIcon from "@sd/assets/icons/Location.png"; +import { TopBarButton } from "@sd/ui/TopBarButton"; import { useNormalizedQuery, useLibraryMutation, @@ -386,54 +387,11 @@ function DeviceCard({ {/* Locations for this device */} {locations.length > 0 && ( -
-
- {locations.map((location) => { - const isSelected = - selectedLocationId === location.id; - return ( - - ); - })} -
-
+ )} {/* Volumes for this device */} @@ -460,6 +418,126 @@ function DeviceCard({ ); } +interface LocationsScrollerProps { + locations: Location[]; + selectedLocationId: string | null; + onLocationSelect?: (location: Location | null) => void; +} + +function LocationsScroller({ + locations, + selectedLocationId, + onLocationSelect, +}: LocationsScrollerProps) { + const scrollRef = useRef(null); + const [canScrollLeft, setCanScrollLeft] = useState(false); + const [canScrollRight, setCanScrollRight] = useState(false); + + const updateScrollState = () => { + if (!scrollRef.current) return; + const { scrollLeft, scrollWidth, clientWidth } = scrollRef.current; + setCanScrollLeft(scrollLeft > 0); + setCanScrollRight(scrollLeft < scrollWidth - clientWidth - 1); + }; + + useEffect(() => { + updateScrollState(); + window.addEventListener("resize", updateScrollState); + return () => window.removeEventListener("resize", updateScrollState); + }, [locations]); + + const scroll = (direction: "left" | "right") => { + if (!scrollRef.current) return; + const scrollAmount = 200; + scrollRef.current.scrollBy({ + left: direction === "left" ? -scrollAmount : scrollAmount, + behavior: "smooth", + }); + }; + + return ( +
+
+ {/* Left fade and button */} + {canScrollLeft && ( + <> +
+
+ scroll("left")} + /> +
+ + )} + + {/* Scrollable container */} +
+ {locations.map((location) => { + const isSelected = selectedLocationId === location.id; + return ( + + ); + })} +
+ + {/* Right fade and button */} + {canScrollRight && ( + <> +
+
+ scroll("right")} + /> +
+ + )} +
+
+ ); +} + interface VolumeBarProps { volume: VolumeItem; index: number;