Skip to content

Commit 6e614be

Browse files
committed
Version update 1.3.0
Added OasisMenuProvider for parent controlling.
1 parent d50bb04 commit 6e614be

15 files changed

Lines changed: 436 additions & 61 deletions

File tree

components/Menu.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ function Menu({
55
name,
66
children,
77
className = "",
8-
noStyle = false,
9-
theme = "default",
10-
animation = "slide-in",
8+
noStyle,
9+
theme,
10+
animation,
1111

1212
itemClass = "",
1313
onItemClick = null,

components/Popup.jsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ function Popup({ data, name }) {
88
const popup = useRef();
99

1010
const [hoveredIndex, setHoveredIndex] = useState(null);
11-
const { updateStorage, closePopup } = useContext(MenuContext);
11+
const { updateStorage, closePopup, defaultSettings } = useContext(MenuContext);
1212

1313
const { position = {}, visible = false } = data.modal || {};
1414
const { children, options } = data.popup || {};
1515

16+
console.log(options.theme, defaultSettings.theme);
17+
1618
useEffect(() => {
1719
if (popup.current) {
1820
updateStorage(name, {
@@ -40,12 +42,12 @@ function Popup({ data, name }) {
4042
onClick={() => closePopup(name, data)}
4143
>
4244
<div
43-
className={`oasismenu ${options.className} ${visible ? "oasisopen" : ""}`}
44-
data-oasismenu-styled={!options.noStyle}
45+
className={`oasismenu ${options.className} ${defaultSettings.className} ${visible ? "oasisopen" : ""}`}
46+
data-oasismenu-styled={!(options.noStyle ?? defaultSettings.noStyle)}
4547
data-visible={visible}
46-
data-theme={options.theme}
47-
data-nostyle={options.noStyle}
48-
data-animation={options.animation}
48+
data-theme={(options.theme ?? defaultSettings.theme)}
49+
data-nostyle={(options.noStyle ?? defaultSettings.noStyle)}
50+
data-animation={(options.animation ?? defaultSettings.animation)}
4951
onClick={e => e.stopPropagation()}
5052
>
5153
{React.Children.map(children, (child, index) => {

components/Provider.jsx

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import React, { useEffect } from 'react';
2+
import MenuContext from './core/context';
3+
import Popup from './Popup';
4+
5+
import useStorage from './core/storage';
6+
import { calculatePopupPosition, getPopupPosition } from './core/position';
7+
8+
function Provider({
9+
children,
10+
11+
className = "",
12+
noStyle,
13+
theme = "default",
14+
animation = "slide-in",
15+
16+
inset,
17+
toggle,
18+
placement,
19+
shiftDistance = 10,
20+
trigger = "contextmenu",
21+
}) {
22+
const { storage, updateStorage: renewStorage, getStorage, addElements, loopElements } = useStorage();
23+
24+
const defaultSettings = {
25+
className,
26+
noStyle,
27+
theme,
28+
animation,
29+
inset,
30+
toggle,
31+
placement,
32+
shiftDistance,
33+
trigger,
34+
};
35+
36+
const updateStorage = (key, updates = {}, ...path) => {
37+
return renewStorage(key, updates, ...path);
38+
};
39+
40+
const openPopup = (key, object) => {
41+
setTimeout(() => {
42+
const { beforeConstruct = null, onConstruct = null, beforeShow = null, onShown = null, onOpen = null } = object?.popup?.events || {};
43+
if (beforeConstruct && beforeConstruct(object) === false) return;
44+
updateStorage(key, { enabled: true }, "modal");
45+
onConstruct && onConstruct(object);
46+
if (beforeShow && beforeShow(object) === false) return updateStorage(key, { enabled: false }, "modal");
47+
setTimeout(() => {
48+
const { element } = object.popup;
49+
const { position, trigger, options } = object.modal;
50+
51+
const places = ['top-left', 'top', 'top-right', 'right-top', 'right', 'right-bottom', 'bottom-right', 'bottom', 'bottom-left', 'left-bottom', 'left', 'left-top'];
52+
53+
const { top, left } = options.placement && (places.includes(options.placement) || (options.inset && options.placement === "center")) ? getPopupPosition(trigger, element, options) : calculatePopupPosition(element, position.top, position.left);
54+
updateStorage(key, {
55+
position: {
56+
top,
57+
left,
58+
}
59+
}, "modal");
60+
setTimeout(() => {
61+
updateStorage(key, { visible: true }, "modal");
62+
onShown && onShown(object);
63+
onOpen && onOpen(object);
64+
}, 5);
65+
}, 5);
66+
}, 5);
67+
};
68+
69+
const closePopup = (key, object) => {
70+
setTimeout(() => {
71+
const { beforeDestroy = null, onDestroy = null, beforeHide = null, onHidden = null, onClose = null } = object?.popup?.events || {};
72+
if (beforeHide && beforeHide(object) === false) return;
73+
updateStorage(key, { visible: false }, "modal");
74+
onHidden && onHidden(object);
75+
if (beforeDestroy && beforeDestroy(object) === false) return updateStorage(key, { visible: true }, "modal");
76+
updateStorage(key, { modal: {} });
77+
onDestroy && onDestroy(object);
78+
onClose && onClose(object);
79+
}, 5);
80+
81+
};
82+
83+
useEffect(() => {
84+
const handle = (e) => {
85+
Object.keys(storage).forEach(key => {
86+
const { enabled, visible, toggle } = storage?.[key]?.modal || {};
87+
if (loopElements(key, e.target) && toggle) return;
88+
(enabled || visible) && closePopup(key, storage[key]);
89+
});
90+
};
91+
window.addEventListener("mousedown", handle);
92+
window.addEventListener("scroll", handle);
93+
window.addEventListener("resize", handle);
94+
return () => {
95+
window.removeEventListener("mousedown", handle);
96+
window.removeEventListener("scroll", handle);
97+
window.removeEventListener("resize", handle);
98+
};
99+
}, [storage]);
100+
101+
useEffect(() => {
102+
const styleSheet = `[data-oasismenu-styled=true],[data-oasismenu-styled=true] *{box-sizing: border-box}[data-oasismenu-styled=true]{background-color: var(--bg-popup);transition: 200ms;max-width: 100vw;max-height: 100vh;box-shadow: 5px 5px 10px 2.5px var(--shadow-popup);border-radius: 5px;border: 1px solid var(--border-popup);overflow: auto;padding-block: 5px}[data-oasismenu-styled=true]::-webkit-scrollbar{width: 10px;height: 10px}[data-oasismenu-styled=true]::-webkit-scrollbar-thumb{background: var(--scroll-popup);background-clip: padding-box;border: 3px solid transparent;border-radius: 10px;box-shadow: none;min-height: 40px;min-width: 40px}[data-oasismenu-styled=true]::-webkit-scrollbar-track,[data-oasismenu-styled=true]::-webkit-scrollbar-track:hover{background: none;border: none}[data-oasismenu-styled=true] .oasisbreak{min-height: 1px;max-height: 1px;height: 1px;background-color: var(--bg-break);width: 100%;margin-block: 5px}[data-oasismenu-styled=true] div:has(> .oasisitem){padding-inline: 5px}[data-oasismenu-styled=true] .oasisitem{background-color: var(--bg-popup);color: var(--color-item-primary);display: flex;flex-direction: row;flex-wrap: nowrap;align-items: center;width: 100%;border-radius: 5px;border: none;outline: none;font-size: 15px;transition: 200ms;gap: 15px;font-weight: 500;position: relative;padding: 5px 10px}[data-oasismenu-styled=true] .oasisitem:disabled{background-color: var(--bg-item-disabled);color: var(--color-item-disabled)}[data-oasismenu-styled=true] .oasisitem:not(:disabled).oasishovered{background-color: var(--bg-item-hover);cursor: pointer}[data-oasismenu-styled=true] .oasisicon,[data-oasismenu-styled=true] .oasisstatus{min-width: 20px;display: inline-flex;align-items: center;justify-content: center;flex-wrap: nowrap}[data-oasismenu-styled=true] .oasisicon,[data-oasismenu-styled=true] .oasisstatus,[data-oasismenu-styled=true] .oasiscontent{white-space: nowrap;word-wrap: initial;overflow: hidden;transition: inherit;text-overflow: ellipsis}[data-oasismenu-styled=true] .oasiscontent{flex: 1;text-align: left}[data-oasismenu-styled=true] .oasisstatus{font-size: 0.9em}[data-oasismenu-styled=true] .oasisitem:not(:disabled):not(.oasishovered) .oasisicon,[data-oasismenu-styled=true] .oasisitem:not(:disabled):not(.oasishovered) .oasisstatus{color: var(--color-item-secondary)}[data-oasismenu-styled=true] .oasisafter:not(:empty){margin-left: 30px;white-space: nowrap;font-size: 0.8em;transition: inherit}[data-oasismenu-styled=true] .oasisitem:not(:disabled):not(.oasishovered) .oasisafter{color: var(--color-item-tertiary)}[data-oasismenu-styled=true]:not(:has(.oasisiconhave)) .oasisicon,[data-oasismenu-styled=true]:not(:has(.oasisstatushave)) .oasisstatus,[data-oasismenu-styled=true]:not(:has(.oasisafterhave)) .oasisafter{display: none}[data-oasismenu-styled=true]:not(:has(.oasisiconhave):has(.oasisstatushave):has(.oasisafterhave)) .oasisitem{padding-inline: 15px}[data-oasismenu-styled=true][data-animation]:not([data-animation=""]):not([data-animation="none"]){visibility: hidden;opacity: 0}[data-oasismenu-styled=true][data-animation]:not([data-animation=""]):not([data-animation="none"])[data-visible=true]{visibility: visible;opacity: 1}[data-oasismenu-styled=true][data-animation=slide-in]{transform: translateY(-10px)}[data-oasismenu-styled=true][data-animation=slide-in][data-visible=true]{transform: translateY(0)}[data-oasismenu-styled=true][data-animation=flip-in]{transform: rotateX(-90deg);transform-origin: top}[data-oasismenu-styled=true][data-animation=flip-in][data-visible=true]{transform: rotateX(0)}[data-oasismenu-styled=true][data-animation=drop-in]{transform: translateY(-100%)}[data-oasismenu-styled=true][data-animation=drop-in][data-visible=true]{transform: translateY(0)}[data-oasismenu-styled=true][data-animation=bounce-in]{transform: translateY(-500px)}[data-oasismenu-styled=true][data-animation=bounce-in][data-visible=true]{animation: oasismenu-bounce-in 0.5s cubic-bezier(0.215, 0.610, 0.355, 1.000) both}@keyframes oasismenu-bounce-in{0%{transform: translateY(-500px);animation-timing-function: ease-in;opacity: 0}38%{transform: translateY(0px);animation-timing-function: ease-out;opacity: 1}55%{transform: translateY(-65px);animation-timing-function: ease-in}72%{transform: translateY(0px);animation-timing-function: ease-out}81%{transform: translateY(-28px);animation-timing-function: ease-in}90%{transform: translateY(0px);animation-timing-function: ease-out}95%{transform: translateY(-8px);animation-timing-function: ease-in}100%{transform: translateY(0px);animation-timing-function: ease-out}}`;
103+
const style = document.createElement("style");
104+
style.dataset.oasismenuStyled = true;
105+
style.innerHTML = styleSheet;
106+
document.head.prepend(style);
107+
return () => document.head.contains(style) && document.head.removeChild(style);
108+
}, []);
109+
110+
return (
111+
<MenuContext.Provider value={{ storage, getStorage, updateStorage, openPopup, closePopup, addElements, defaultSettings }}>
112+
{children}
113+
<Mapping />
114+
</MenuContext.Provider>
115+
)
116+
};
117+
118+
export default Provider;
119+
120+
function Mapping() {
121+
return (
122+
<MenuContext.Consumer>{({ storage }) => Object.keys(storage).map(key => storage[key]?.modal?.enabled && <Popup key={key} data={storage[key]} name={key} />)}</MenuContext.Consumer>
123+
);
124+
}

components/Trigger.jsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import MenuContext from './core/context';
44
function Trigger({
55
name,
66
children,
7-
inset = false,
8-
toggle = false,
9-
onTrigger = null,
10-
placement = null,
7+
inset,
8+
toggle,
9+
onTrigger,
10+
placement,
1111
shiftDistance = 10,
12-
trigger = "contextmenu",
12+
trigger,
1313
}) {
1414
return (
1515
<>
@@ -36,9 +36,11 @@ export default Trigger;
3636
function Child({ child, data = {} }) {
3737
const ref = useRef();
3838

39-
const { name, inset, toggle, trigger, onTrigger, placement, shiftDistance } = data;
39+
const { name, inset, toggle, trigger: triggerOrg, onTrigger, placement, shiftDistance } = data;
4040

41-
const { getStorage, updateStorage, openPopup, closePopup, addElements } = useContext(MenuContext);
41+
const { getStorage, updateStorage, openPopup, closePopup, addElements, defaultSettings } = useContext(MenuContext);
42+
43+
const trigger = (triggerOrg ?? defaultSettings.trigger);
4244

4345
useEffect(() => {
4446
if (!ref.current) return;
@@ -77,12 +79,12 @@ function Child({ child, data = {} }) {
7779

7880
updateStorage(name, {
7981
modal: {
80-
toggle,
82+
toggle: (toggle ?? defaultSettings.toggle),
8183
enabled: false,
8284
options: {
83-
inset,
84-
placement,
85-
shiftDistance,
85+
inset: (inset ?? defaultSettings.inset),
86+
placement: (placement ?? defaultSettings.placement),
87+
shiftDistance: (shiftDistance ?? defaultSettings.shiftDistance),
8688
},
8789
position: {
8890
top,
@@ -114,7 +116,7 @@ function Child({ child, data = {} }) {
114116
element.removeEventListener("scroll", toggleHandle);
115117
element.removeEventListener("resize", toggleHandle);
116118
};
117-
}, [inset, name, onTrigger, placement, ref, shiftDistance, toggle, trigger]);
119+
}, [defaultSettings.inset, defaultSettings.placement, defaultSettings.shiftDistance, defaultSettings.toggle, inset, name, onTrigger, placement, ref, shiftDistance, toggle, trigger]);
118120

119121
return (
120122
<>

components/core/position.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
export function calculatePopupPosition(popup, top, left) {
2-
const rect = popup.getBoundingClientRect();
2+
let rect;
3+
4+
try {
5+
rect = popup.getBoundingClientRect();
6+
} catch (e) {
7+
return {
8+
top: 0,
9+
left: 0,
10+
};
11+
}
12+
313
const { innerWidth, innerHeight } = window;
414
const popupWidth = rect.width;
515
const popupHeight = rect.height;
@@ -33,11 +43,21 @@ export function calculatePopupPosition(popup, top, left) {
3343

3444

3545
export function getPopupPosition(item, popup, { placement: position, inset, shiftDistance: distance }) {
36-
const itemRect = item.getBoundingClientRect();
37-
const popupRect = popup.getBoundingClientRect();
46+
const padding = distance || 5;
47+
let itemRect, popupRect;
48+
49+
try {
50+
itemRect = item.getBoundingClientRect();
51+
popupRect = popup.getBoundingClientRect();
52+
} catch (e) {
53+
return {
54+
top: 0,
55+
left: 0,
56+
};
57+
}
58+
3859
const popupWidth = popupRect.width;
3960
const popupHeight = popupRect.height;
40-
const padding = distance || 5;
4161

4262
if (inset) {
4363
return getInsetPopupPosition(itemRect, popupWidth, popupHeight, padding, position);

components/index.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from "react";
2+
import OasisMenuProvider from './Provider';
23
import OasisMenuBlock from "./Block";
34
import OasisMenuTrigger from "./Trigger";
45
import OasisMenu from "./Menu";
@@ -8,6 +9,8 @@ import OasisMenuBreak from "./Break";
89
// import "../styles/default.css";
910
import "../themes/default.css";
1011

12+
export default OasisMenuProvider;
13+
1114
export {
1215
OasisMenu,
1316
OasisMenuTrigger,

dist/Menu.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,9 @@ function Menu(_ref) {
1515
children = _ref.children,
1616
_ref$className = _ref.className,
1717
className = _ref$className === void 0 ? "" : _ref$className,
18-
_ref$noStyle = _ref.noStyle,
19-
noStyle = _ref$noStyle === void 0 ? false : _ref$noStyle,
20-
_ref$theme = _ref.theme,
21-
theme = _ref$theme === void 0 ? "default" : _ref$theme,
22-
_ref$animation = _ref.animation,
23-
animation = _ref$animation === void 0 ? "slide-in" : _ref$animation,
18+
noStyle = _ref.noStyle,
19+
theme = _ref.theme,
20+
animation = _ref.animation,
2421
_ref$itemClass = _ref.itemClass,
2522
itemClass = _ref$itemClass === void 0 ? "" : _ref$itemClass,
2623
_ref$onItemClick = _ref.onItemClick,

dist/Popup.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len
1919
function _iterableToArrayLimit(arr, i) { var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"]; if (null != _i) { var _s, _e, _x, _r, _arr = [], _n = !0, _d = !1; try { if (_x = (_i = _i.call(arr)).next, 0 === i) { if (Object(_i) !== _i) return; _n = !1; } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0); } catch (err) { _d = !0, _e = err; } finally { try { if (!_n && null != _i["return"] && (_r = _i["return"](), Object(_r) !== _r)) return; } finally { if (_d) throw _e; } } return _arr; } }
2020
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
2121
function Popup(_ref) {
22+
var _options$noStyle, _options$theme, _options$noStyle2, _options$animation;
2223
var data = _ref.data,
2324
name = _ref.name;
2425
var popup = (0, _react.useRef)();
@@ -28,7 +29,8 @@ function Popup(_ref) {
2829
setHoveredIndex = _useState2[1];
2930
var _useContext = (0, _react.useContext)(_context["default"]),
3031
updateStorage = _useContext.updateStorage,
31-
closePopup = _useContext.closePopup;
32+
closePopup = _useContext.closePopup,
33+
defaultSettings = _useContext.defaultSettings;
3234
var _ref2 = data.modal || {},
3335
_ref2$position = _ref2.position,
3436
position = _ref2$position === void 0 ? {} : _ref2$position,
@@ -37,6 +39,7 @@ function Popup(_ref) {
3739
var _ref3 = data.popup || {},
3840
children = _ref3.children,
3941
options = _ref3.options;
42+
console.log(options.theme, defaultSettings.theme);
4043
(0, _react.useEffect)(function () {
4144
if (popup.current) {
4245
updateStorage(name, {
@@ -69,12 +72,12 @@ function Popup(_ref) {
6972
return closePopup(name, data);
7073
}
7174
}, /*#__PURE__*/_react["default"].createElement("div", {
72-
className: "oasismenu ".concat(options.className, " ").concat(visible ? "oasisopen" : ""),
73-
"data-oasismenu-styled": !options.noStyle,
75+
className: "oasismenu ".concat(options.className, " ").concat(defaultSettings.className, " ").concat(visible ? "oasisopen" : ""),
76+
"data-oasismenu-styled": !((_options$noStyle = options.noStyle) !== null && _options$noStyle !== void 0 ? _options$noStyle : defaultSettings.noStyle),
7477
"data-visible": visible,
75-
"data-theme": options.theme,
76-
"data-nostyle": options.noStyle,
77-
"data-animation": options.animation,
78+
"data-theme": (_options$theme = options.theme) !== null && _options$theme !== void 0 ? _options$theme : defaultSettings.theme,
79+
"data-nostyle": (_options$noStyle2 = options.noStyle) !== null && _options$noStyle2 !== void 0 ? _options$noStyle2 : defaultSettings.noStyle,
80+
"data-animation": (_options$animation = options.animation) !== null && _options$animation !== void 0 ? _options$animation : defaultSettings.animation,
7881
onClick: function onClick(e) {
7982
return e.stopPropagation();
8083
}

0 commit comments

Comments
 (0)