diff --git a/package.json b/package.json index 770e145..a485b9a 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.14", "globals": "^15.12.0", + "sass": "^1.83.0", "vite": "^6.0.1" } -} \ No newline at end of file +} diff --git a/src/App.jsx b/src/App.jsx index cc74cbb..44743ea 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import "./App.css"; -import { MenuBuilder } from "./Component/MenuBuilder"; +import "./app.scss"; +import MenuWrapper from "./Component/MenuWrapper"; const initMenus = [ { @@ -59,18 +59,18 @@ const initMenus = [ }, ]; -function App() { +const App = () => { const [menus, setMenus] = useState(initMenus); return ( -
+

Wordpress like menu structure




- +
); -} +}; export default App; diff --git a/src/Component/CustomDragLayer.jsx b/src/Component/CustomDragLayer.jsx index 259ac48..3b86158 100644 --- a/src/Component/CustomDragLayer.jsx +++ b/src/Component/CustomDragLayer.jsx @@ -51,8 +51,6 @@ const CustomDragLayer = ({ menuitems }) => { depth={item.depth} clone childCount={getChildCount(menuitems, item.id) + 1} - value={item.id.toString()} - otherfields={item} menu={item} childs={getChildrens(menuitems, item.id)} /> diff --git a/src/Component/Menu/MenuItem.jsx b/src/Component/Menu/MenuItem.jsx index 5011731..d1acc27 100644 --- a/src/Component/Menu/MenuItem.jsx +++ b/src/Component/Menu/MenuItem.jsx @@ -27,12 +27,11 @@ export const MenuItem = ({ const [{ handlerId }, setDroppableNodeRef] = useDrop({ accept: [ITEM_TYPE], collect(monitor) { - return { handlerId: monitor.getHandlerId() }; + return { handlerId: monitor.getHandlerId()}; }, drop: (item) => { onDragEnd({ id: item.id }, { id: menu.id }); }, - hover: (item, monitor) => { if (!dndRef.current) { return; @@ -55,7 +54,7 @@ export const MenuItem = ({ collect: (monitor) => { const isDragging = monitor.isDragging(); if (isDragging) { - onDragStart({ active: { id: menu.id } }); + onDragStart(menu.id); } return { isDragging }; @@ -74,8 +73,9 @@ export const MenuItem = ({ data-handler-id={handlerId} data-depth={depth} className={classNames({ - Wrapper: true, - dragging: isDragging, + [`${classPrefix}-item-wrapper`]: true, + [`${classPrefix}-item-dragging`]: isDragging, + // dragging: isDragging, })} style={{ ...(!clone @@ -83,9 +83,7 @@ export const MenuItem = ({ paddingLeft: `${INDENTATION_WIDTH * depth}px`, } : {}), - }} - {...props} - > + }}>
+ }}> + display: + activeId || clone ? "none" : menu?.parentId ? "block" : "none", + }}> {menu?.name}{" "} + }}> {depth > 0 ? "sub item" : ""} @@ -150,8 +146,7 @@ const RecursiveItem = (props) => { paddingLeft: "0.5rem", fontWeight: "600", fontSize: "13px", - }} - > + }}> {props.child.name}{" "} { fontStyle: "italic", color: "#50575e", marginLeft: "4px", - }} - > + }}> sub item
diff --git a/src/Component/MenuBuilder.jsx b/src/Component/MenuWrapper.jsx old mode 100755 new mode 100644 similarity index 79% rename from src/Component/MenuBuilder.jsx rename to src/Component/MenuWrapper.jsx index 42d77d3..39a769b --- a/src/Component/MenuBuilder.jsx +++ b/src/Component/MenuWrapper.jsx @@ -13,8 +13,17 @@ import { import CustomDragLayer from "./CustomDragLayer"; import { MenuItem } from "./Menu/MenuItem"; -export function MenuBuilder({ items: itemsProps, setItems }) { - const menuList = generateItemChildren(itemsProps); +const generateItemChildren = (menuList) => { + return menuList.map((menu) => { + return { + ...menu, + children: menu?.children ? generateItemChildren(menu.children) : [], + }; + }); +}; + +const MenuWrapper = ({ menus: menuData, setMenus }) => { + const menuList = generateItemChildren(menuData); const [activeId, setActiveId] = useState(null); const [overId, setOverId] = useState(null); const [offsetLeft, setOffsetLeft] = useState(0); @@ -33,10 +42,8 @@ export function MenuBuilder({ items: itemsProps, setItems }) { ); }, [activeId, menuList]); - let projected = - activeId && overId - ? getProjection(flattenedMenus, activeId, overId, offsetLeft) - : null; + // This is the projected position of the dragged item over the hovered item. Initially set to null + let projected = null; const handleOnHover = (dragId, hoverId, deltaX) => { const { depth, parentId } = getProjection( @@ -59,7 +66,7 @@ export function MenuBuilder({ items: itemsProps, setItems }) { const sortedItems = arrayMove(clonedItems, activeIndex, overIndex); const newItems = buildTree(sortedItems); - setItems(newItems); + setMenus(newItems); }; const getBranchPathHeight = (menu) => { @@ -75,60 +82,29 @@ export function MenuBuilder({ items: itemsProps, setItems }) { return "0px"; }; - return ( -
- {flattenedMenus.map((menu, index) => ( - - ))} - - -
- ); - - function handleDragStart({ active: { id: aId } }) { - if (activeId === aId || overId === aId) return; - setActiveId(aId); - setOverId(aId); - } + const handleDragStart = (menuId) => { + if (activeId === menuId || overId === menuId) return; + setActiveId(menuId); + setOverId(menuId); + }; - function handleDragOver(deltaX, id) { + const handleDragOver = (deltaX, id) => { setOffsetLeft(deltaX); if (overId === id) return; setOverId(id ?? null); - } + }; - function handleDragEnd() { - const active = { id: activeId }; + const handleDragEnd = () => { const over = { id: overId }; + if (projected && over) { const { depth, parentId } = projected; const clonedItems = JSON.parse(JSON.stringify(flattenTree(menuList))); const overIndex = clonedItems.findIndex(({ id }) => id === over.id); - const activeIndex = clonedItems.findIndex(({ id }) => id === active.id); + const activeIndex = clonedItems.findIndex(({ id }) => id === activeId); const activeTreeItem = clonedItems[activeIndex]; clonedItems[activeIndex] = { ...activeTreeItem, depth, parentId }; @@ -136,24 +112,48 @@ export function MenuBuilder({ items: itemsProps, setItems }) { const sortedItems = arrayMove(clonedItems, activeIndex, overIndex); const newItems = buildTree(sortedItems); - setItems(newItems); + setMenus(newItems); } resetState(); - } + }; - function resetState() { + const resetState = () => { setOverId(null); setActiveId(null); setOffsetLeft(0); + }; + + // Get the projection of the dragged item over the hovered item + if (activeId && overId) { + projected = getProjection(flattenedMenus, activeId, overId, offsetLeft); } -} -const generateItemChildren = (menuList) => { - return menuList.map((menu) => { - return { - ...menu, - children: menu?.children ? generateItemChildren(menu.children) : [], - }; - }); + return ( +
+ {flattenedMenus.map((menu, index) => ( + + ))} + + +
+ ); }; + +export default MenuWrapper; diff --git a/src/App.css b/src/app.scss similarity index 75% rename from src/App.css rename to src/app.scss index e6f69d7..25401a6 100644 --- a/src/App.css +++ b/src/app.scss @@ -1,3 +1,5 @@ +$class-prefix: "lgmenu"; + :root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; @@ -17,14 +19,14 @@ body { } /* Wrapper styles */ -.Wrapper { +.#{$class-prefix}-item-wrapper { list-style: none; box-sizing: border-box; margin-bottom: -1px; -webkit-font-smoothing: subpixel-antialiased; } -.Wrapper.clone { +.#{$class-prefix}-item-wrapper.clone { display: inline-block; pointer-events: none; padding: 0; @@ -32,7 +34,7 @@ body { padding-top: 5px; } -.Wrapper.clone .TreeItem { +.#{$class-prefix}-item-wrapper.clone .TreeItem { --vertical-padding: 5px; padding-right: 24px; border-radius: 4px; @@ -41,14 +43,14 @@ body { min-width: 414px; } -.Wrapper.dragging { +.#{$class-prefix}-item-wrapper.#{$class-prefix}-item-dragging { opacity: 1; position: relative; z-index: 1; margin-bottom: -1px; } -.Wrapper.dragging .TreeItem { +.#{$class-prefix}-item-wrapper.#{$class-prefix}-item-dragging .TreeItem { position: relative; width: "100%"; max-width: 414px; @@ -59,12 +61,12 @@ body { background-color: #fff; } -.Wrapper.dragging .TreeItem > * { +.#{$class-prefix}-item-wrapper.#{$class-prefix}-item-dragging .TreeItem > * { opacity: 0; height: 0; } -.Wrapper.dragging .TreeItem > * { +.#{$class-prefix}-item-wrapper.#{$class-prefix}-item-dragging .TreeItem > * { box-shadow: none; background-color: transparent; } @@ -110,38 +112,6 @@ body { flex-direction: column; } -/* Disable Selection styles */ -.disableSelection, -.clone .Text, -.clone .Count { - user-select: none; - -webkit-user-select: none; -} - -/* Collapse styles */ -.Collapse svg { - transition: transform 250ms ease; -} - -.Collapse.collapsed svg { - transform: rotate(-90deg); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - .navigation-item-path { display: block; position: absolute; diff --git a/src/main.jsx b/src/main.jsx index 2bad55b..6e69881 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -3,6 +3,9 @@ import App from "./App.jsx"; import { HTML5Backend } from "react-dnd-html5-backend"; import { DndProvider } from "react-dnd"; +// global variables +window.classPrefix = "lgmenu"; + createRoot(document.getElementById("I-am-groot")).render(