wip moving menu nav buttons in widget menu
This commit is contained in:
@@ -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}>
|
||||
|
||||
@@ -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
@@ -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,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user