wip moving menu nav buttons in widget menu

This commit is contained in:
2026-09-11 14:45:39 -07:00
parent 4914b64fe2
commit 0c8820d109
3 changed files with 253 additions and 105 deletions
@@ -31,20 +31,23 @@ export function SettingsButton() {
const cancelEdit = useDashboardStore(state => state.cancelEdit);
const addWidget = useDashboardStore(state => state.addWidget);
const recipe = useRecipe({key: "glassTab"});
const tabStyles = recipe();
const glassTabRecipe = useRecipe({key: "glassTab"});
const settingsButtonRecipe = useRecipe({key: "settingsButton"});
const tabStyles = glassTabRecipe();
const settingsButtonStyles = settingsButtonRecipe();
const [canScrollLeft, setCanScrollLeft] = useState(false);
const [canScrollRight, setCanScrollRight] = useState(false);
// Pull the glass container styles for the dropdown and modal
const panelRecipe = useSlotRecipe({key: "glassPanel"});
const panelStyles = panelRecipe({layout: "dropdown"});
const modalStyles = panelRecipe({layout: "popover"});
const modalStyles = panelRecipe({layout: "popover", leftScroll: canScrollLeft ? "visible" : "hidden", rightScroll: canScrollRight ? "visible" : "hidden"});
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) {
@@ -92,7 +95,7 @@ export function SettingsButton() {
<>
{isEditing ? (
// 📝 EDIT MODE: The 3-Pill Layout
<HStack gap={2}>
<HStack css={settingsButtonStyles.root}>
{/* 1. The Add Widget Button */}
<Box
as="button"
@@ -137,7 +140,7 @@ export function SettingsButton() {
<Menu.Root positioning={{getAnchorRect}}>
<Menu.Trigger asChild>
<Box ref={menuRef} as="button" css={tabStyles}>
{/* size increased to 24 to match tabs. flexShrink: 0 is CRITICAL */}
{/* settings icon size set to 24 to match tabs. flexShrink 0 is CRITICAL */}
<Settings size={24} style={{flexShrink: 0}}/>
<Box as="span">Dashboard Settings</Box>
</Box>
@@ -174,27 +177,24 @@ export function SettingsButton() {
<Dialog.Root
open={isLibraryOpen}
onOpenChange={(e) => setIsLibraryOpen(e.open)}
size="xl"
placement="center"
closeOnInteractOutside={true}
closeOnEscapeKeyDown={true}
size="xl"
placement="center"
>
{/* Soft blur can be added to the background behind the modal */}
{/*<Dialog.Backdrop bg="blackAlpha.100" backdropFilter="blur(2px)"/>*/}
<Dialog.Positioner>
<Dialog.Content css={modalStyles.root} overflow="hidden" maxW="60%" minH="80%" p={0}>
<Dialog.Header bg="transparent" color={{base: "gray.800", _dark: "white"}} py={4}
borderColor={{base: "gray.200", _dark: "whiteAlpha.200"}}>
<Dialog.Title fontSize="xl" fontWeight="bold">Widgets Shop</Dialog.Title>
<Dialog.Content css={modalStyles.root}>
<Dialog.Header>
<Dialog.Title>Widgets Shop</Dialog.Title>
</Dialog.Header>
<Dialog.Body p={0}>
<Dialog.Body>
<Tabs.Root defaultValue={categories[0]} variant="unstyled">
<Box position="relative" display="flex" mx="auto" maxWidth="100%" mx={5} mt={4} mb={6}>
{/* LEFT GLASS CRESCENT */}
{/* HORIZONTAL SCROLLABLE MENU OF WIDGET CATEGORIES */}
<Box position="relative" display="flex" maxWidth="100%" mx={5} mt={4} mb={6}>
{/* left glass crescent */}
{canScrollLeft && (
<Flex
position="absolute"
@@ -224,55 +224,20 @@ export function SettingsButton() {
<ChevronLeft size={18} style={{ marginLeft: "-30px" }}/>
</Flex>
)}
<Tabs.List
ref={tabsRef}
onScroll={checkScroll}
bg={{base: "rgba(0, 0, 0, 0.04)", _dark: "rgba(255, 255, 255, 0.05)"}}
p={1}
borderRadius="full"
display="flex"
flexWrap="nowrap"
overflowX="auto"
maxWidth="100%"
border="1px solid"
borderColor={{base: "blackAlpha.50", _dark: "whiteAlpha.100"}}
css={{
scrollbarWidth: "none",
"&::-webkit-scrollbar": {display: "none"},
}}
// This is for desktop users not on trackpad to scroll the
onWheel={(e) => {
if (e.currentTarget) {
e.currentTarget.scrollLeft += e.deltaY;
}
}}
{/* the categories */}
<Tabs.List ref={tabsRef} onScroll={checkScroll}
// For desktop users (not on trackpad) to scroll
onWheel={(e) => { if (e.currentTarget) e.currentTarget.scrollLeft += e.deltaY; }}
>
{categories.map(cat => (
<Tabs.Trigger
key={cat}
value={cat}
px={5}
py={2}
borderRadius="full"
whiteSpace="nowrap"
flexShrink={0}
color={{base: "gray.500", _dark: "gray.400"}}
fontWeight="medium"
transition="background-color 0.2s, color 0.2s, box-shadow 0.2s"
_hover={{
color: {base: "gray.800", _dark: "gray.200"}
}}
_selected={{
bg: {base: "white", _dark: "rgba(255, 255, 255, 0.12)"},
color: {base: "blue.600", _dark: "blue.200"},
boxShadow: "sm",
fontWeight: "bold",
}}
>
<Tabs.Trigger key={cat} value={cat}>
{cat}
</Tabs.Trigger>
))}
</Tabs.List>
{/* right glass crescent */}
{canScrollRight && (
<Flex
position="absolute"
@@ -302,8 +267,9 @@ export function SettingsButton() {
<ChevronRight size={18} style={{ marginRight: "-30px" }}/>
</Flex>
)}
</Box>
{/* THE WIDGET OPTIONS W/IN EACH CATEGORY */}
{categories.map(cat => (
<Tabs.Content key={cat} value={cat} p={6}>
<Grid templateColumns="repeat(auto-fill, minmax(300px, 1fr))" gap={4}>
+5 -24
View File
@@ -68,28 +68,21 @@ export default function MyDashboard() {
return (
<>
<Box css={styles.header}>
<Text color="titleColor" fontWeight="semibold" fontSize="md" isTruncated>
<Text>
{registryEntry.name}
</Text>
{/* WIDGET SETTINGS BUTTON (ELLIPSES) */}
{SettingsUI ? (
{SettingsUI && (
<Popover.Root positioning={{placement: "bottom-end"}}>
<Popover.Trigger asChild>
<IconButton
size="xs"
variant="ghost"
color="gray.400"
borderRadius="xl"
_hover={{color: "white", bg: "gray.700"}}
aria-label="Widget Settings"
>
<IconButton size="xs" variant="ghost" aria-label="Widget Settings">
<MoreVertical size={16}/>
</IconButton>
</Popover.Trigger>
{/* NEW: The Positioner lifts the content out of the Flexbox flow */}
<Popover.Positioner zIndex={10}>
<Popover.Positioner zIndex={1}>
<Popover.Content css={popoverStyles.root}>
<Suspense fallback={<Spinner size="sm"/>}>
<SettingsUI
@@ -100,8 +93,6 @@ export default function MyDashboard() {
</Popover.Content>
</Popover.Positioner>
</Popover.Root>
) : (
<Box w="20px" h="20px"/>
)}
</Box>
@@ -131,8 +122,8 @@ export default function MyDashboard() {
rowHeight={30}
isDraggable={isEditing} // Only allows dragging when Settings tools toggle this
isResizable={false} // Double lock-down
onLayoutChange={(newLayout) => updateLayoutPositions(newLayout)} // Fires when user drops a widget
compactType="vertical" // Auto-packs widgets to the top
onLayoutChange={(newLayout) => updateLayoutPositions(newLayout)} // Fires when user drops a widget
>
{/* 🎨 Paint the Widgets */}
{widgets.map(widget => (
@@ -151,17 +142,7 @@ export default function MyDashboard() {
{isEditing && (
<IconButton
aria-label="Remove widget"
position="absolute"
top={2}
right={2}
size="xs"
bg="white"
color="black"
border="1px solid"
borderRadius="xl"
borderColor="gray.200"
_hover={{bg: "red.50", color: "red.500", borderColor: "red.200"}}
zIndex={2}
onClick={() => removeWidget(widget.id)}
onMouseDown={(e) => e.stopPropagation()} // 🚫 Stops RGL from grabbing the widget
onTouchStart={(e) => e.stopPropagation()} // 🚫 Same protection for mobile touches
+219 -18
View File
@@ -97,9 +97,11 @@ const sidebarRecipe = defineSlotRecipe({
_hover: {
bg: "rgba(255, 255, 255, 0.2)",
color: "{text}",
fontWeight: "500",
"& svg": {
strokeWidth: "3"
},
"& span": {
fontWeight: "700",
}
},
@@ -119,9 +121,11 @@ const sidebarRecipe = defineSlotRecipe({
_hover: {
bg: "{MainBackground}",
color: "{textInverted}",
fontWeight: "normal",
"& svg": {
strokeWidth: "2" // Keeps the active icon at normal thickness on hover
},
"& span": {
fontWeight: "500",
}
},
@@ -181,7 +185,7 @@ const sidebarRecipe = defineSlotRecipe({
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "clip",
fontWeight: "500"
fontWeight: "500",
},
collapse_button: {
"& svg": {flexShrink: "0"},
@@ -244,14 +248,7 @@ const dashboardRecipe = defineRecipe({
}
});
const canvasRecipe = defineRecipe({
base: {
bg: "{transparent}",
zIndex: "-1",
color: "{text}"
}
});
// CONSOLIDATED ✔
const homeRecipe = defineSlotRecipe({
slots: ["root"],
base: {
@@ -291,6 +288,30 @@ const homeRecipe = defineSlotRecipe({
},
})
const settingsButtonRecipe = defineSlotRecipe({
slots: ["root", "dialog"],
base: {
root: {
gap: 2,
"& > button": {
}
},
dialog: {
}
}
})
// const canvasRecipe = defineRecipe({
// base: {
// bg: "{transparent}",
// zIndex: "-1",
// color: "{text}"
// }
// });
// const widgetRecipe = defineSlotRecipe({
// slots: ["root", "header", "canvas"],
// base: {
@@ -329,6 +350,9 @@ const homeRecipe = defineSlotRecipe({
// }
// });
// USED IN COMPONENTS:
// * Settings Button
// * My Dashboard
const glassPanelRecipe = defineSlotRecipe({
slots: ["root", "header", "body"],
@@ -352,26 +376,202 @@ const glassPanelRecipe = defineSlotRecipe({
variants: {
layout: {
widget: {
root: {width: "100%", height: "100%", borderRadius: "3xl"},
header: {display: "flex", justifyContent: "space-between", alignItems: "center", px: 3, pt: 2, pb: 0},
body: {flex: "1", overflow: "hidden", position: "relative", px: 1, pb: 1, pt: 0}
root: {
width: "100%",
height: "100%",
borderRadius: "3xl",
"& > button": {
position: "absolute",
top: 2,
right: 2,
// size="xs",
bg: "white",
color: "black",
border: "1px solid",
borderRadius: "xl",
borderColor: "gray.200",
_hover: {
bg: "red.50",
color: "red.500",
borderColor: "red.200"
},
zIndex: 2,
}
},
header: {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
px: 3,
pt: 2,
pb: 0,
"& > p": {
color: "titleColor",
fontWeight: "500",
fontSize: "16px",
// the following three specifications are equivalent to Chakra's 'truncate' prop
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "clip",
},
"& > button": {
borderRadius: "xl",
_hover: {
color: "white",
bg: "gray.500",
},
}
},
body: {
flex: "1",
overflow: "hidden",
position: "relative",
px: 1,
pb: 1,
pt: 0
}
},
dropdown: {
root: {borderRadius: "xl", p: 1}
root: {
borderRadius: "xl",
p: 1
}
// (Header and body slots are ignored here because dropdowns don't use them)
},
popover: {
root: {
borderRadius: "2xl",
p: 4,
p: 0,
boxShadow: "xl",
minW: "240px",
maxW: "60%",
minH: "80%",
overflow: "hidden",
// OVERRIDES: Make the background 85-90% opaque instead of 40%
bg: {base: "rgba(255, 255, 255, 0.75)", _dark: "rgba(20, 22, 25, 0.8)"},
bg: {
base: "rgba(255, 255, 255, 0.75)",
_dark: "rgba(20, 22, 25, 0.8)"
},
// OVERRIDES: Increase the blur just to smooth out any stubborn gridlines
backdropFilter: "blur(15px)",
WebkitBackdropFilter: "blur(15px)",
// dialog header
"& > div.chakra-dialog__header": {
bg: "transparent",
color: {
base: "gray.800",
_dark: "white"
},
py: 4,
borderColor: {
base: "gray.200",
_dark: "whiteAlpha.200"
},
// title
"& h2": {
fontSize: "xl",
fontWeight: "bold"
},
},
// dialog body
"& > div.chakra-dialog__body": {
p: 0,
// everything below is specific to the widget menu found in the settings button component
// the horizontal widget categories menu
"& div.chakra-tabs__list": {
bg: {
base: "rgba(0, 0, 0, 0.04)",
_dark: "rgba(255, 255, 255, 0.05)"
},
p: 1,
borderRadius: "full",
display: "flex",
flexWrap: "nowrap",
overflowX: "auto",
maxW: "100%",
border: "1px solid",
borderColor: {
base: "blackAlpha.50",
_dark: "whiteAlpha.100"
},
scrollbarWidth: "none",
"&::-webkit-scrollbar": {display: "none"},
"& button.chakra-tabs__trigger": {
px: 5,
py: 2,
borderRadius: "full",
whiteSpace: "nowrap",
flexShrink: 0,
color: {
base: "gray.500",
_dark: "gray.400"
},
fontWeight: "medium",
transition: "background-color 0.2s, color 0.2s, box-shadow 0.2s",
_hover: {
color: {
base: "gray.800",
_dark: "gray.200"
},
},
_selected: {
bg: {
base: "white",
_dark: "rgba(255, 255, 255, 0.12)"
},
color: {
base: "blue.600",
_dark: "blue.200"
},
boxShadow: "sm",
fontWeight: "bold",
}
}
},
// "& div.chakra-tabs__root > div:first-child div > svg": {
// position: "absolute",
// top: "0",
// bottom: "0",
// w: "50px",
// align: "center",
// justify: "center",
// zIndex: 2,
// cursor: "pointer",
// bg: {
// base: "rgba(255, 255, 255, 0.7)",
// _dark: "rgba(30, 30, 30, 0.8)"
// },
// backdropFilter: "blur(1px)",
//
// color: {
// base: "gray.600",
// _dark: "gray.300"
// },
// transition: "background-color 0.2s",
// _hover: {
// color: {
// base: "blue.500",
// _dark: "blue.300"
// },
// bg: {
// base: "rgba(255, 255, 255, 0.9)",
// _dark: "rgba(40, 40, 40, 0.95)"
// },
// }
// }
}
}
}
},
@@ -554,7 +754,7 @@ const config = defineConfig({
},
recipes: {
dashboard: dashboardRecipe,
canvas: canvasRecipe,
// canvas: canvasRecipe,
glassTab: glassTabRecipe
},
slotRecipes: {
@@ -563,6 +763,7 @@ const config = defineConfig({
// widget: widgetRecipe,
glassPanel: glassPanelRecipe,
home: homeRecipe,
settingsButton: settingsButtonRecipe,
},
},
});