Skip to content

Commit

Permalink
A simple menu widget #581
Browse files Browse the repository at this point in the history
  • Loading branch information
treoden committed Sep 1, 2024
1 parent 969a0b6 commit b3a4925
Show file tree
Hide file tree
Showing 18 changed files with 916 additions and 185 deletions.
614 changes: 614 additions & 0 deletions packages/evershop/src/components/admin/widgets/BasicMenuSetting.jsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.menu__container>* {
margin-top: 10px;
}

.item {
padding: 10px;
border: 1px dashed var(--border);
margin-top: 10px;
background-color: #fff;
}

.menu__container .draggable-source--is-dragging {
background: var(--divider);
color: var(--divider);
}

.menu__container .draggable-source--is-dragging>* {
visibility: hidden;
}
14 changes: 0 additions & 14 deletions packages/evershop/src/components/frontStore/widgets/Banner.jsx

This file was deleted.

129 changes: 129 additions & 0 deletions packages/evershop/src/components/frontStore/widgets/BasicMenu.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import PropTypes from 'prop-types';
import React from 'react';

export default function BasicMenu({
basicMenuWidget: { menus, isMain, className }
}) {
const [isOpen, setIsOpen] = React.useState(!isMain);

const toggleMenu = () => {
setIsOpen(!isOpen);
};

const listingClasses = isMain
? 'md:flex md:justify-center md:space-x-10 absolute md:relative left-[-2.5rem] md:left-0 top-full md:top-auto mt-2 md:mt-0 w-screen md:w-auto md:bg-transparent p-4 md:p-0 min-w-[250px] bg-white z-30 divide-y md:divide-y-0'
: 'flex justify-center space-x-10 relative left-[-2.5rem] md:left-0 top-full md:top-auto mt-2 md:mt-0 w-screen md:w-auto md:bg-transparent p-4 md:p-0 min-w-[250px] bg-white z-30';
return (
<div className={className}>
<div className="flex justify-start gap-6 items-center">
<nav className="p-4 relative md:flex md:justify-center">
<div className="flex justify-between items-center">
{isMain && (
<div className="md:hidden">
<a
href="#"
onClick={(e) => {
e.preventDefault();
toggleMenu();
}}
className="text-black focus:outline-none"
>
<svg
className="w-9 h-9"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4 6h16M4 12h16m-7 6h7"
/>
</svg>
</a>
</div>
)}
<ul className={`${isOpen ? 'block' : 'hidden'} ${listingClasses}`}>
{menus.map((item, index) => (
<li key={index} className="relative group">
<a
href={item.url}
className="hover:text-gray-300 transition-colors block md:inline-block px-4 py-4 md:px-0 md:py-0"
>
{item.name}
</a>
{item.children.length > 0 && (
<ul className="md:absolute left-0 top-full mt-0 md:mt-3 w-48 bg-white md:shadow-lg rounded-md md:opacity-0 md:group-hover:opacity-100 md:group-hover:translate-y-0 transform transition-all duration-300 ease-in-out min-w-full md:min-w-[250px] z-30 border-t-4">
{item.children.map((subItem, subIndex) => (
<li key={subIndex}>
<a
href={subItem.url}
className="block px-8 md:px-4 py-3 text-gray-700 hover:bg-gray-100"
>
{subItem.name}
</a>
</li>
))}
</ul>
)}
</li>
))}
</ul>
</div>
</nav>
</div>
</div>
);
}

BasicMenu.propTypes = {
basicMenuWidget: PropTypes.shape({
menus: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string,
url: PropTypes.string,
type: PropTypes.string,
uuid: PropTypes.string,
children: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string,
url: PropTypes.string,
type: PropTypes.string,
uuid: PropTypes.string
})
)
})
),
isMain: PropTypes.bool,
className: PropTypes.string
}).isRequired
};

export const query = `
query Query($settings: JSON) {
basicMenuWidget(settings: $settings) {
menus {
id
name
url
type
uuid
children {
name
url
type
uuid
}
}
isMain
className
}
}
`;

export const variables = `{
settings: getWidgetSetting()
}`;
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ module.exports.parseGraphqlByFile = function parseGraphqlByFile(module) {
}
);
variablesBody = variablesBody.replace(
/getWidgetSetting\(([^)]+)\)/g,
/getWidgetSetting\(([^)]*)\)/g,
(match, p1) => {
const base64 = Buffer.from(p1).toString('base64');
return `"getWidgetSetting_${base64}"`;
Expand Down
3 changes: 3 additions & 0 deletions packages/evershop/src/modules/catalog/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ module.exports = () => {
config.util.setModuleDefaults('widgets', widgets);

const parseIntCount = (data) => {
if (data.type !== 'collection_products') {
return data;
}
// eslint-disable-next-line no-param-reassign
data.settings = data.settings || {};
if (data.settings.count) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ enum FilterOperation {
input FilterInput {
key: String!
operation: FilterOperation!
value: String
value: ID
}

"""
Expand Down
28 changes: 28 additions & 0 deletions packages/evershop/src/modules/cms/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,15 @@ module.exports = () => {
className: 'page-width'
},
enabled: true
},
basic_menu: {
setting_component:
'@evershop/evershop/src/components/admin/widgets/BasicMenuSetting.jsx',
component:
'@evershop/evershop/src/components/frontStore/widgets/BasicMenu.jsx',
name: 'Menu',
description: 'A menu widget',
enabled: true
}
};
config.util.setModuleDefaults('widgets', defaultWidgets);
Expand Down Expand Up @@ -229,4 +238,23 @@ module.exports = () => {
(filters) => [...filters, ...defaultPaginationFilters],
2
);

const parseMenus = (data) => {
if (data?.type !== 'basic_menu') {
return data;
}
// eslint-disable-next-line no-param-reassign
data.settings = data.settings || {};
if (data.settings.menus) {
// eslint-disable-next-line no-param-reassign
data.settings.menus = JSON.parse(data.settings.menus);
} else {
// eslint-disable-next-line no-param-reassign
data.settings.menus = [];
}
return data;
};

addProcessor('widgetDataBeforeCreate', parseMenus, 1);
addProcessor('widgetDataBeforeUpdate', parseMenus, 1);
};
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,45 @@ type TextBlockWidget {
className: String
}

"""
Return a menu input
"""
input BasicMenuInput {
id: ID
name: String!
label: String
url: String
children: [BasicMenuInput]
type: String!
uuid: String
}

"""
Return a menu item
"""
type Menu {
id: ID!
name: String!
url: String
children: [Menu]
type: String!
uuid: String
}

"""
Return a menu Widget
"""
type BasicMenuWidget {
menus: [Menu],
isMain: Boolean
className: String
}

extend type Query {
widget(id: Int): Widget
widgets(filters: [FilterInput]): WidgetCollection
widgetTypes: [WidgetType]
widgetType(code: String!): WidgetType
textWidget(text: String, className: String): TextBlockWidget
basicMenuWidget(settings: JSON ): BasicMenuWidget
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
const uniqid = require('uniqid');
const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
const {
getEnabledWidgets
} = require('@evershop/evershop/src/lib/util/getEnabledWidgets');
const { select } = require('@evershop/postgres-query-builder');
const {
getWidgetsBaseQuery
} = require('../../../services/getWidgetsBaseQuery');
const { WidgetCollection } = require('../../../services/WidgetCollection');
const {
getCmsPagesBaseQuery
} = require('../../../services/getCmsPagesBaseQuery');

module.exports = {
Query: {
Expand Down Expand Up @@ -58,6 +63,75 @@ module.exports = {
? text.replace(/&lt;|&gt;/g, (match) => replacements[match])
: '[]';
return { text: JSON.parse(jsonText), className };
},
basicMenuWidget: async (_, { settings }, { pool }) => {
const categories = [];
const pages = [];
const menus = settings?.menus || undefined;
const isMain = [1, '1', 'true', true].includes(settings?.isMain) || false;
if (!menus) {
return { menus: [] };
}
// eslint-disable-next-line no-restricted-syntax
for (const menu of menus) {
if (menu.type === 'category') {
categories.push(menu.uuid);
}
if (menu.type === 'page') {
pages.push(menu.uuid);
}
menu.children.forEach((child) => {
if (child.type === 'category') {
categories.push(child.uuid);
}
if (child.type === 'page') {
pages.push(child.uuid);
}
});
}
let urls = [];
if (categories.length > 0) {
const rewrites = await select()
.from('url_rewrite')
.where('entity_uuid', 'IN', categories)
.execute(pool);
urls = urls.concat(
rewrites.map((r) => ({
uuid: r.entity_uuid,
url: r.request_path
}))
);
}
if (pages.length > 0) {
const query = getCmsPagesBaseQuery();
query.where('uuid', 'IN', pages);
const cmsPages = await query.execute(pool);
urls = urls.concat(
cmsPages.map((p) => ({
uuid: p.uuid,
url: buildUrl('cmsPageView', { url_key: p.url_key })
}))
);
}
const items = menus.map((menu) => {
const url = urls.find((u) => u.uuid === menu.uuid);
return {
...menu,
id: uniqid(),
// eslint-disable-next-line no-nested-ternary
url: url ? url.url : menu.type === 'custom' ? menu.url : null,
children: menu.children.map((child) => {
const url = urls.find((u) => u.uuid === child.uuid);
return {
...child,
id: uniqid(),
// eslint-disable-next-line no-nested-ternary
url: url ? url.url : child.type === 'custom' ? child.url : null
};
})
};
});
return { menus: items, isMain, className: settings?.className };
}
},
Widget: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function Layout() {
return (
<>
<LoadingBar />
<div className="header flex justify-between">
<div className="header grid grid-cols-3">
<Area
id="header"
noOuter
Expand All @@ -17,7 +17,7 @@ export default function Layout() {
component: { default: Area },
props: {
id: 'icon-wrapper',
className: 'icon-wrapper flex justify-between space-x-4'
className: 'icon-wrapper flex justify-end space-x-4'
},
sortOrder: 20
}
Expand Down
Loading

0 comments on commit b3a4925

Please sign in to comment.