Update added chevrons to scrollbar - bug in size of chevrons
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import {useState, useMemo, useRef} from "react";
|
import {useState, useMemo, useRef, useEffect, useCallback} from "react";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Box,
|
Box,
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
useRecipe,
|
useRecipe,
|
||||||
useSlotRecipe
|
useSlotRecipe
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import {Settings, LayoutGrid, Move, Save, X} from 'lucide-react';
|
import {Settings, LayoutGrid, Move, Save, X, ChevronRight, ChevronLeft} from 'lucide-react';
|
||||||
import {useDashboardStore} from "../../store/useDashboardStore.jsx";
|
import {useDashboardStore} from "../../store/useDashboardStore.jsx";
|
||||||
import {WIDGET_REGISTRY} from "../../constants/widgetRegistry.jsx";
|
import {WIDGET_REGISTRY} from "../../constants/widgetRegistry.jsx";
|
||||||
|
|
||||||
@@ -41,6 +41,29 @@ export function SettingsButton() {
|
|||||||
|
|
||||||
const [isLibraryOpen, setIsLibraryOpen] = useState(false);
|
const [isLibraryOpen, setIsLibraryOpen] = useState(false);
|
||||||
|
|
||||||
|
// ⬅️ ➡️ This is the callbacks and scroll control to add the chevron arrows in the tab list
|
||||||
|
const tabsRef = useRef(null);
|
||||||
|
const [canScrollLeft, setCanScrollLeft] = useState(false);
|
||||||
|
const [canScrollRight, setCanScrollRight] = useState(false);
|
||||||
|
|
||||||
|
const checkScroll = useCallback(() => {
|
||||||
|
if (tabsRef.current) {
|
||||||
|
const { scrollLeft, scrollWidth, clientWidth } = tabsRef.current;
|
||||||
|
setCanScrollLeft(scrollLeft > 0);
|
||||||
|
// We use -1 here to account for sub-pixel rendering in some browsers
|
||||||
|
setCanScrollRight(Math.ceil(scrollLeft + clientWidth) < scrollWidth - 1);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLibraryOpen) {
|
||||||
|
// Small timeout ensures the DOM has rendered the tabs before measuring
|
||||||
|
setTimeout(checkScroll, 50);
|
||||||
|
window.addEventListener('resize', checkScroll);
|
||||||
|
return () => window.removeEventListener('resize', checkScroll);
|
||||||
|
}
|
||||||
|
}, [isLibraryOpen, checkScroll]);
|
||||||
|
|
||||||
// Smart Add Handler
|
// Smart Add Handler
|
||||||
const handleAddWidget = (widgetId, sizeKey) => {
|
const handleAddWidget = (widgetId, sizeKey) => {
|
||||||
enterEditMode();
|
enterEditMode();
|
||||||
@@ -147,7 +170,7 @@ export function SettingsButton() {
|
|||||||
</Menu.Root>
|
</Menu.Root>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* THE WIDGET LIBRARY MODAL (Controlled by isLibraryOpen) */}
|
{/* 📚📚📚 THE WIDGET LIBRARY MODAL (Controlled by isLibraryOpen) */}
|
||||||
<Dialog.Root
|
<Dialog.Root
|
||||||
open={isLibraryOpen}
|
open={isLibraryOpen}
|
||||||
onOpenChange={(e) => setIsLibraryOpen(e.open)}
|
onOpenChange={(e) => setIsLibraryOpen(e.open)}
|
||||||
@@ -169,24 +192,51 @@ export function SettingsButton() {
|
|||||||
|
|
||||||
<Dialog.Body p={0}>
|
<Dialog.Body p={0}>
|
||||||
<Tabs.Root defaultValue={categories[0]} variant="unstyled">
|
<Tabs.Root defaultValue={categories[0]} variant="unstyled">
|
||||||
|
<Box position="relative" display="flex" mx="auto" maxWidth="100%" mt={4} mb={6}>
|
||||||
|
|
||||||
|
{/* LEFT GLASS CRESCENT */}
|
||||||
|
{canScrollLeft && (
|
||||||
|
<Flex
|
||||||
|
position="absolute"
|
||||||
|
left="0" top="0" bottom="0"
|
||||||
|
w="40px"
|
||||||
|
align="center" justify="center"
|
||||||
|
zIndex={2}
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => tabsRef.current.scrollBy({ left: -200, behavior: 'smooth' })}
|
||||||
|
// The Glass Look hugging the left pill edge
|
||||||
|
bg={{ base: "rgba(255, 255, 255, 0.7)", _dark: "rgba(30, 30, 30, 0.8)" }}
|
||||||
|
backdropFilter="blur(8px)"
|
||||||
|
borderTopLeftRadius="full"
|
||||||
|
borderBottomLeftRadius="full"
|
||||||
|
// Fades the inner edge so it blends into the track
|
||||||
|
boxShadow={{ base: "10px 0 15px -5px rgba(255,255,255,0.8)", _dark: "10px 0 15px -5px rgba(30,30,30,0.9)" }}
|
||||||
|
color={{ base: "gray.600", _dark: "gray.300" }}
|
||||||
|
_hover={{ color: { base: "blue.500", _dark: "blue.300" } }}
|
||||||
|
>
|
||||||
|
<ChevronLeft size={18} />
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
<Tabs.List
|
<Tabs.List
|
||||||
|
ref={tabsRef}
|
||||||
|
onScroll={checkScroll}
|
||||||
bg={{ base: "rgba(0, 0, 0, 0.04)", _dark: "rgba(255, 255, 255, 0.05)" }}
|
bg={{ base: "rgba(0, 0, 0, 0.04)", _dark: "rgba(255, 255, 255, 0.05)" }}
|
||||||
p={1}
|
p={1}
|
||||||
mx={3}
|
mx={3}
|
||||||
mt={4}
|
mt={4}
|
||||||
mb={6}
|
mb={6}
|
||||||
borderRadius="full" // Back to a perfect pill shape!
|
borderRadius="full"
|
||||||
display="flex"
|
display="flex"
|
||||||
flexWrap="nowrap" // 🚨 STRICTLY lock to one line
|
flexWrap="nowrap"
|
||||||
overflowX="auto" // 🚨 Allow horizontal scrolling
|
overflowX="auto"
|
||||||
maxWidth="100%" // Ensure it doesn't break out of the modal
|
maxWidth="100%"
|
||||||
border="1px solid"
|
border="1px solid"
|
||||||
borderColor={{ base: "blackAlpha.50", _dark: "whiteAlpha.100" }}
|
borderColor={{ base: "blackAlpha.50", _dark: "whiteAlpha.100" }}
|
||||||
css={{
|
css={{
|
||||||
// 🚨 Hide the scrollbar but keep the scrolling functionality
|
|
||||||
scrollbarWidth: "none",
|
scrollbarWidth: "none",
|
||||||
"&::-webkit-scrollbar": { display: "none" },
|
"&::-webkit-scrollbar": { display: "none" },
|
||||||
}}
|
}}
|
||||||
|
// This is for desktop users not on trackpad to scroll the
|
||||||
onWheel={(e) => {
|
onWheel={(e) => {
|
||||||
if (e.currentTarget) {
|
if (e.currentTarget) {
|
||||||
e.currentTarget.scrollLeft += e.deltaY;
|
e.currentTarget.scrollLeft += e.deltaY;
|
||||||
@@ -200,8 +250,8 @@ export function SettingsButton() {
|
|||||||
px={5}
|
px={5}
|
||||||
py={2}
|
py={2}
|
||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
whiteSpace="nowrap" // Keep text on one straight line
|
whiteSpace="nowrap"
|
||||||
flexShrink={0} // 🚨 Prevent the browser from trying to crush the tabs
|
flexShrink={0}
|
||||||
color={{ base: "gray.500", _dark: "gray.400" }}
|
color={{ base: "gray.500", _dark: "gray.400" }}
|
||||||
fontWeight="medium"
|
fontWeight="medium"
|
||||||
transition="background-color 0.2s, color 0.2s, box-shadow 0.2s"
|
transition="background-color 0.2s, color 0.2s, box-shadow 0.2s"
|
||||||
@@ -219,7 +269,29 @@ export function SettingsButton() {
|
|||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
))}
|
))}
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
|
{canScrollRight && (
|
||||||
|
<Flex
|
||||||
|
position="absolute"
|
||||||
|
right="0" top="0" bottom="0"
|
||||||
|
w="40px"
|
||||||
|
align="center" justify="center"
|
||||||
|
zIndex={2}
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => tabsRef.current.scrollBy({ left: 200, behavior: 'smooth' })}
|
||||||
|
// The Glass Look hugging the right pill edge
|
||||||
|
bg={{ base: "rgba(255, 255, 255, 0.7)", _dark: "rgba(30, 30, 30, 0.8)" }}
|
||||||
|
backdropFilter="blur(8px)"
|
||||||
|
borderTopRightRadius="full"
|
||||||
|
borderBottomRightRadius="full"
|
||||||
|
boxShadow={{ base: "-10px 0 15px -5px rgba(255,255,255,0.8)", _dark: "-10px 0 15px -5px rgba(30,30,30,0.9)" }}
|
||||||
|
color={{ base: "gray.600", _dark: "gray.300" }}
|
||||||
|
_hover={{ color: { base: "blue.500", _dark: "blue.300" } }}
|
||||||
|
>
|
||||||
|
<ChevronRight size={18} />
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
{categories.map(cat => (
|
{categories.map(cat => (
|
||||||
<Tabs.Content key={cat} value={cat} p={6}>
|
<Tabs.Content key={cat} value={cat} p={6}>
|
||||||
<Grid templateColumns="repeat(auto-fill, minmax(300px, 1fr))" gap={4}>
|
<Grid templateColumns="repeat(auto-fill, minmax(300px, 1fr))" gap={4}>
|
||||||
|
|||||||
Reference in New Issue
Block a user