@manuscripts/style-guide 1.5.0 → 1.6.0-LEAN-3074-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -46,7 +46,7 @@ const AvatarNormal_1 = __importDefault(require("@manuscripts/assets/react/Avatar
46
46
  const react_1 = __importStar(require("react"));
47
47
  const styled_components_1 = __importDefault(require("styled-components"));
48
48
  const AvatarContainer = styled_components_1.default.div `
49
- display: inline-flex;
49
+ display: flex;
50
50
  align-items: center;
51
51
  justify-content: center;
52
52
  position: relative;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ /*!
3
+ * © 2019 Atypon Systems LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ var __importDefault = (this && this.__importDefault) || function (mod) {
18
+ return (mod && mod.__esModule) ? mod : { "default": mod };
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.Menus = void 0;
22
+ const react_1 = __importDefault(require("react"));
23
+ const styled_components_1 = __importDefault(require("styled-components"));
24
+ const Submenu_1 = require("./Submenu");
25
+ const MenusContainer = styled_components_1.default.div `
26
+ display: flex;
27
+ font-size: 14px;
28
+ `;
29
+ const MenuHeading = styled_components_1.default.div `
30
+ padding: 4px 8px;
31
+ cursor: pointer;
32
+ border: 1px solid transparent;
33
+ border-bottom: none;
34
+ `;
35
+ const MenuContainer = styled_components_1.default.div `
36
+ position: relative;
37
+
38
+ & ${MenuHeading} {
39
+ background-color: #fff;
40
+ color: ${(props) => (props.isEnabled ? '#353535' : '#e2e2e2')};
41
+
42
+ &:hover {
43
+ background-color: ${(props) => (props.isEnabled ? '#f2fbfc' : '#fff')};
44
+ }
45
+ }
46
+ `;
47
+ const Menus = ({ menus, innerRef, handleClick, }) => {
48
+ return (react_1.default.createElement(MenusContainer, { ref: innerRef }, menus.map((menu, index) => {
49
+ return (react_1.default.createElement(MenuContainer, { key: `menu-${index}`, isEnabled: menu.isEnabled },
50
+ react_1.default.createElement(MenuHeading, { onMouseDown: (e) => {
51
+ e.preventDefault();
52
+ handleClick([index]);
53
+ }, isOpen: menu.isOpen },
54
+ react_1.default.createElement(Submenu_1.Text, null, menu.label)),
55
+ menu.isEnabled && menu.isOpen && menu.submenu && (react_1.default.createElement(Submenu_1.SubmenusContainer, null, menu.submenu.map((submenu, sindex) => {
56
+ return (react_1.default.createElement(Submenu_1.Submenu, { key: `${index}-${sindex}`, menu: submenu, handleClick: (i) => handleClick([index, sindex, ...i]) }));
57
+ })))));
58
+ })));
59
+ };
60
+ exports.Menus = Menus;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ /*!
3
+ * © 2019 Atypon Systems LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ var __importDefault = (this && this.__importDefault) || function (mod) {
18
+ return (mod && mod.__esModule) ? mod : { "default": mod };
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.Shortcut = exports.ShortcutContainer = void 0;
22
+ const react_1 = __importDefault(require("react"));
23
+ const styled_components_1 = __importDefault(require("styled-components"));
24
+ const isMac = /Mac/.test(window.navigator.platform);
25
+ exports.ShortcutContainer = styled_components_1.default.div `
26
+ display: inline-flex;
27
+ color: #6e6e6e;
28
+ margin-left: 16px;
29
+ flex-shrink: 0;
30
+ justify-content: flex-end;
31
+ `;
32
+ const macModifiers = {
33
+ Option: '⌥',
34
+ CommandOrControl: '⌘',
35
+ Shift: '⇧',
36
+ };
37
+ const pcModifiers = {
38
+ Option: 'Alt',
39
+ CommandOrControl: 'Ctrl',
40
+ Shift: 'Shift',
41
+ };
42
+ const modifiers = isMac ? macModifiers : pcModifiers;
43
+ const system = isMac ? 'mac' : 'pc';
44
+ const separator = isMac ? '' : '-';
45
+ const Character = styled_components_1.default.span `
46
+ display: inline-block;
47
+ min-width: 1ch;
48
+ `;
49
+ const parts = (shortcut) => {
50
+ const nodes = [];
51
+ for (const part of shortcut[system].split('+')) {
52
+ const modifier = modifiers[part];
53
+ if (modifier) {
54
+ nodes.push(modifier);
55
+ nodes.push(separator);
56
+ }
57
+ else {
58
+ nodes.push(react_1.default.createElement(Character, { key: part }, part));
59
+ }
60
+ }
61
+ return nodes;
62
+ };
63
+ const Shortcut = ({ shortcut }) => (react_1.default.createElement(exports.ShortcutContainer, null, parts(shortcut)));
64
+ exports.Shortcut = Shortcut;
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ /*!
3
+ * © 2019 Atypon Systems LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ var __importDefault = (this && this.__importDefault) || function (mod) {
18
+ return (mod && mod.__esModule) ? mod : { "default": mod };
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.Submenu = exports.SubmenusContainer = exports.Text = void 0;
22
+ const TriangleCollapsed_1 = __importDefault(require("@manuscripts/assets/react/TriangleCollapsed"));
23
+ const react_1 = __importDefault(require("react"));
24
+ const styled_components_1 = __importDefault(require("styled-components"));
25
+ const menus_1 = require("../../lib/menus");
26
+ const Shortcut_1 = require("./Shortcut");
27
+ exports.Text = styled_components_1.default.div `
28
+ flex: 1 0 auto;
29
+ `;
30
+ const SubmenuContainer = styled_components_1.default.div `
31
+ position: relative;
32
+ `;
33
+ exports.SubmenusContainer = styled_components_1.default.div `
34
+ background: #fff;
35
+ border: 1px solid #e2e2e2;
36
+ border-radius: 4px;
37
+ box-shadow: 0 4px 9px 0 rgba(84, 83, 83, 0.3);
38
+ color: #353535;
39
+ min-width: 150px;
40
+ max-height: 70vh;
41
+ overflow-y: auto;
42
+ padding: 4px 0;
43
+ white-space: nowrap;
44
+ width: auto;
45
+ z-index: 10;
46
+
47
+ position: absolute;
48
+
49
+ &[data-placement='bottom-start'] {
50
+ border-top-left-radius: 0;
51
+ border-top-right-radius: 0;
52
+ }
53
+
54
+ &[data-placement='right-start'] {
55
+ top: 8px;
56
+ }
57
+ `;
58
+ const NestedSubmenusContainer = (0, styled_components_1.default)(exports.SubmenusContainer) `
59
+ top: 0;
60
+ left: 100%;
61
+ `;
62
+ const Separator = styled_components_1.default.div `
63
+ height: 0;
64
+ border-bottom: 1px solid #e2e2e2;
65
+ margin: 4px 0;
66
+ `;
67
+ const Active = styled_components_1.default.div `
68
+ width: 16px;
69
+ display: inline-flex;
70
+ flex-shrink: 0;
71
+ justify-content: center;
72
+ align-items: center;
73
+ `;
74
+ const Arrow = (0, styled_components_1.default)(TriangleCollapsed_1.default) `
75
+ margin-left: 8px;
76
+ `;
77
+ const Container = styled_components_1.default.div `
78
+ align-items: center;
79
+ cursor: pointer;
80
+ display: flex;
81
+ padding: 8px 16px 8px 4px;
82
+ position: relative;
83
+ ${(props) => props.isOpen && 'background: #f2fbfc;'}
84
+
85
+ &:hover {
86
+ background: #f2fbfc;
87
+ }
88
+
89
+ &.disabled {
90
+ cursor: default;
91
+ opacity: 0.4;
92
+ }
93
+ `;
94
+ const activeContent = (menu) => (menu.isActive ? '✓' : '');
95
+ const Submenu = ({ menu, handleClick }) => {
96
+ if ((0, menus_1.isMenuSeparator)(menu)) {
97
+ return react_1.default.createElement(Separator, null);
98
+ }
99
+ if (!menu.submenu) {
100
+ return (react_1.default.createElement(Container, { isOpen: menu.isOpen, className: menu.isEnabled ? '' : 'disabled', onMouseDown: (e) => {
101
+ e.preventDefault();
102
+ handleClick([]);
103
+ } },
104
+ react_1.default.createElement(Active, null, activeContent(menu)),
105
+ react_1.default.createElement(exports.Text, null, menu.label),
106
+ menu.shortcut && react_1.default.createElement(Shortcut_1.Shortcut, { shortcut: menu.shortcut })));
107
+ }
108
+ return (react_1.default.createElement(SubmenuContainer, null,
109
+ react_1.default.createElement(Container, { onMouseDown: (e) => {
110
+ e.preventDefault();
111
+ handleClick([]);
112
+ }, isOpen: menu.isOpen, className: menu.isEnabled ? '' : 'disabled' },
113
+ react_1.default.createElement(Active, null, activeContent(menu)),
114
+ react_1.default.createElement(exports.Text, null, menu.label),
115
+ menu.submenu && react_1.default.createElement(Arrow, null),
116
+ menu.shortcut && react_1.default.createElement(Shortcut_1.Shortcut, { shortcut: menu.shortcut })),
117
+ menu.submenu && menu.isOpen && (react_1.default.createElement(NestedSubmenusContainer, null, menu.submenu.map((submenu, index) => (react_1.default.createElement(exports.Submenu, { key: `menu-${index}`, menu: submenu, handleClick: (i) => handleClick([index, ...i]) })))))));
118
+ };
119
+ exports.Submenu = Submenu;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./Menus"), exports);
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useMenus = void 0;
4
+ const react_1 = require("react");
5
+ const menus_1 = require("../lib/menus");
6
+ const initialPointer = [-1, -1, -1];
7
+ const transformPointer = (depth, index) => (pointer) => pointer.map((pointerPart, i) => {
8
+ if (i === depth) {
9
+ return index;
10
+ }
11
+ if (i > depth) {
12
+ return -1;
13
+ }
14
+ return pointerPart;
15
+ });
16
+ const isPart = (pointer, position) => {
17
+ const limit = position.length < 3 ? position.length : 3;
18
+ for (let i = 0; i < limit; i++) {
19
+ if (position[i] !== pointer[i]) {
20
+ return false;
21
+ }
22
+ }
23
+ return true;
24
+ };
25
+ const getSubmenuState = (specs, pointer, position) => {
26
+ return specs.map((spec, index) => {
27
+ if ((0, menus_1.isMenuSeparator)(spec)) {
28
+ return spec;
29
+ }
30
+ const current = [...position, index];
31
+ return Object.assign(Object.assign({}, spec), { submenu: spec.submenu && getSubmenuState(spec.submenu, pointer, current), isOpen: isPart(pointer, current) });
32
+ });
33
+ };
34
+ const getMenuState = (specs, pointer) => {
35
+ return specs.map((spec, index) => {
36
+ const position = [index];
37
+ return Object.assign(Object.assign({}, spec), { submenu: spec.submenu && getSubmenuState(spec.submenu, pointer, position), isOpen: isPart(pointer, position) });
38
+ });
39
+ };
40
+ const getMenuAt = (state, position) => {
41
+ const [head, ...tail] = position.filter((i) => i !== -1);
42
+ const menu = state[head];
43
+ if ((0, menus_1.isMenuSeparator)(menu)) {
44
+ return;
45
+ }
46
+ if (!tail.length) {
47
+ return menu;
48
+ }
49
+ else if (menu.submenu) {
50
+ return getMenuAt(menu.submenu, tail);
51
+ }
52
+ };
53
+ const useMenus = (menus) => {
54
+ const [pointer, setPointer] = (0, react_1.useState)(initialPointer);
55
+ const state = getMenuState(menus, pointer);
56
+ const handleClick = (0, react_1.useCallback)((indices) => {
57
+ const menu = getMenuAt(state, indices);
58
+ if (!menu || !menu.isEnabled) {
59
+ return;
60
+ }
61
+ if (menu.run) {
62
+ menu.run();
63
+ setPointer([-1, -1, -1]);
64
+ }
65
+ else if (menu.submenu) {
66
+ const depth = indices.length - 1;
67
+ const index = indices[depth];
68
+ setPointer(transformPointer(depth, index));
69
+ }
70
+ }, [state]);
71
+ const ref = (0, react_1.useRef)(null);
72
+ (0, react_1.useEffect)(() => {
73
+ const handleClickOutside = (event) => {
74
+ if (ref.current && !ref.current.contains(event.target)) {
75
+ setPointer([-1, -1, -1]);
76
+ }
77
+ };
78
+ document.addEventListener('click', handleClickOutside);
79
+ return () => {
80
+ document.removeEventListener('click', handleClickOutside);
81
+ };
82
+ }, []);
83
+ return {
84
+ menus: state,
85
+ handleClick,
86
+ ref,
87
+ };
88
+ };
89
+ exports.useMenus = useMenus;
package/dist/cjs/index.js CHANGED
@@ -73,8 +73,10 @@ __exportStar(require("./components/Text"), exports);
73
73
  __exportStar(require("./components/ManuscriptNoteList"), exports);
74
74
  __exportStar(require("./components/Comments"), exports);
75
75
  __exportStar(require("./components/RelativeDate"), exports);
76
+ __exportStar(require("./components/Menus"), exports);
76
77
  __exportStar(require("./hooks/use-dropdown"), exports);
77
78
  __exportStar(require("./hooks/use-files"), exports);
79
+ __exportStar(require("./hooks/use-menus"), exports);
78
80
  var use_deep_compare_1 = require("./hooks/use-deep-compare");
79
81
  Object.defineProperty(exports, "useDeepCompareMemo", { enumerable: true, get: function () { return use_deep_compare_1.useDeepCompareMemo; } });
80
82
  Object.defineProperty(exports, "useDeepCompareCallback", { enumerable: true, get: function () { return use_deep_compare_1.useDeepCompareCallback; } });
@@ -82,6 +84,7 @@ __exportStar(require("./lib/authors"), exports);
82
84
  __exportStar(require("./lib/capabilities"), exports);
83
85
  __exportStar(require("./lib/files"), exports);
84
86
  __exportStar(require("./lib/comments"), exports);
87
+ __exportStar(require("./lib/menus"), exports);
85
88
  var errors_decoder_1 = require("./lib/errors-decoder");
86
89
  Object.defineProperty(exports, "errorsDecoder", { enumerable: true, get: function () { return __importDefault(errors_decoder_1).default; } });
87
90
  __exportStar(require("./types"), exports);
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ /*!
3
+ * © 2024 Atypon Systems LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.isMenuSeparator = void 0;
19
+ const isMenuSeparator = (menu) => {
20
+ return (menu === null || menu === void 0 ? void 0 : menu.role) === 'separator';
21
+ };
22
+ exports.isMenuSeparator = isMenuSeparator;
@@ -17,7 +17,7 @@ import AvatarNormal from '@manuscripts/assets/react/AvatarNormal';
17
17
  import React, { useCallback, useState } from 'react';
18
18
  import styled from 'styled-components';
19
19
  const AvatarContainer = styled.div `
20
- display: inline-flex;
20
+ display: flex;
21
21
  align-items: center;
22
22
  justify-content: center;
23
23
  position: relative;
@@ -0,0 +1,53 @@
1
+ /*!
2
+ * © 2019 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import React from 'react';
17
+ import styled from 'styled-components';
18
+ import { Submenu, SubmenusContainer, Text } from './Submenu';
19
+ const MenusContainer = styled.div `
20
+ display: flex;
21
+ font-size: 14px;
22
+ `;
23
+ const MenuHeading = styled.div `
24
+ padding: 4px 8px;
25
+ cursor: pointer;
26
+ border: 1px solid transparent;
27
+ border-bottom: none;
28
+ `;
29
+ const MenuContainer = styled.div `
30
+ position: relative;
31
+
32
+ & ${MenuHeading} {
33
+ background-color: #fff;
34
+ color: ${(props) => (props.isEnabled ? '#353535' : '#e2e2e2')};
35
+
36
+ &:hover {
37
+ background-color: ${(props) => (props.isEnabled ? '#f2fbfc' : '#fff')};
38
+ }
39
+ }
40
+ `;
41
+ export const Menus = ({ menus, innerRef, handleClick, }) => {
42
+ return (React.createElement(MenusContainer, { ref: innerRef }, menus.map((menu, index) => {
43
+ return (React.createElement(MenuContainer, { key: `menu-${index}`, isEnabled: menu.isEnabled },
44
+ React.createElement(MenuHeading, { onMouseDown: (e) => {
45
+ e.preventDefault();
46
+ handleClick([index]);
47
+ }, isOpen: menu.isOpen },
48
+ React.createElement(Text, null, menu.label)),
49
+ menu.isEnabled && menu.isOpen && menu.submenu && (React.createElement(SubmenusContainer, null, menu.submenu.map((submenu, sindex) => {
50
+ return (React.createElement(Submenu, { key: `${index}-${sindex}`, menu: submenu, handleClick: (i) => handleClick([index, sindex, ...i]) }));
51
+ })))));
52
+ })));
53
+ };
@@ -0,0 +1,57 @@
1
+ /*!
2
+ * © 2019 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import React from 'react';
17
+ import styled from 'styled-components';
18
+ const isMac = /Mac/.test(window.navigator.platform);
19
+ export const ShortcutContainer = styled.div `
20
+ display: inline-flex;
21
+ color: #6e6e6e;
22
+ margin-left: 16px;
23
+ flex-shrink: 0;
24
+ justify-content: flex-end;
25
+ `;
26
+ const macModifiers = {
27
+ Option: '⌥',
28
+ CommandOrControl: '⌘',
29
+ Shift: '⇧',
30
+ };
31
+ const pcModifiers = {
32
+ Option: 'Alt',
33
+ CommandOrControl: 'Ctrl',
34
+ Shift: 'Shift',
35
+ };
36
+ const modifiers = isMac ? macModifiers : pcModifiers;
37
+ const system = isMac ? 'mac' : 'pc';
38
+ const separator = isMac ? '' : '-';
39
+ const Character = styled.span `
40
+ display: inline-block;
41
+ min-width: 1ch;
42
+ `;
43
+ const parts = (shortcut) => {
44
+ const nodes = [];
45
+ for (const part of shortcut[system].split('+')) {
46
+ const modifier = modifiers[part];
47
+ if (modifier) {
48
+ nodes.push(modifier);
49
+ nodes.push(separator);
50
+ }
51
+ else {
52
+ nodes.push(React.createElement(Character, { key: part }, part));
53
+ }
54
+ }
55
+ return nodes;
56
+ };
57
+ export const Shortcut = ({ shortcut }) => (React.createElement(ShortcutContainer, null, parts(shortcut)));
@@ -0,0 +1,112 @@
1
+ /*!
2
+ * © 2019 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import TriangleCollapsed from '@manuscripts/assets/react/TriangleCollapsed';
17
+ import React from 'react';
18
+ import styled from 'styled-components';
19
+ import { isMenuSeparator } from '../../lib/menus';
20
+ import { Shortcut } from './Shortcut';
21
+ export const Text = styled.div `
22
+ flex: 1 0 auto;
23
+ `;
24
+ const SubmenuContainer = styled.div `
25
+ position: relative;
26
+ `;
27
+ export const SubmenusContainer = styled.div `
28
+ background: #fff;
29
+ border: 1px solid #e2e2e2;
30
+ border-radius: 4px;
31
+ box-shadow: 0 4px 9px 0 rgba(84, 83, 83, 0.3);
32
+ color: #353535;
33
+ min-width: 150px;
34
+ max-height: 70vh;
35
+ overflow-y: auto;
36
+ padding: 4px 0;
37
+ white-space: nowrap;
38
+ width: auto;
39
+ z-index: 10;
40
+
41
+ position: absolute;
42
+
43
+ &[data-placement='bottom-start'] {
44
+ border-top-left-radius: 0;
45
+ border-top-right-radius: 0;
46
+ }
47
+
48
+ &[data-placement='right-start'] {
49
+ top: 8px;
50
+ }
51
+ `;
52
+ const NestedSubmenusContainer = styled(SubmenusContainer) `
53
+ top: 0;
54
+ left: 100%;
55
+ `;
56
+ const Separator = styled.div `
57
+ height: 0;
58
+ border-bottom: 1px solid #e2e2e2;
59
+ margin: 4px 0;
60
+ `;
61
+ const Active = styled.div `
62
+ width: 16px;
63
+ display: inline-flex;
64
+ flex-shrink: 0;
65
+ justify-content: center;
66
+ align-items: center;
67
+ `;
68
+ const Arrow = styled(TriangleCollapsed) `
69
+ margin-left: 8px;
70
+ `;
71
+ const Container = styled.div `
72
+ align-items: center;
73
+ cursor: pointer;
74
+ display: flex;
75
+ padding: 8px 16px 8px 4px;
76
+ position: relative;
77
+ ${(props) => props.isOpen && 'background: #f2fbfc;'}
78
+
79
+ &:hover {
80
+ background: #f2fbfc;
81
+ }
82
+
83
+ &.disabled {
84
+ cursor: default;
85
+ opacity: 0.4;
86
+ }
87
+ `;
88
+ const activeContent = (menu) => (menu.isActive ? '✓' : '');
89
+ export const Submenu = ({ menu, handleClick }) => {
90
+ if (isMenuSeparator(menu)) {
91
+ return React.createElement(Separator, null);
92
+ }
93
+ if (!menu.submenu) {
94
+ return (React.createElement(Container, { isOpen: menu.isOpen, className: menu.isEnabled ? '' : 'disabled', onMouseDown: (e) => {
95
+ e.preventDefault();
96
+ handleClick([]);
97
+ } },
98
+ React.createElement(Active, null, activeContent(menu)),
99
+ React.createElement(Text, null, menu.label),
100
+ menu.shortcut && React.createElement(Shortcut, { shortcut: menu.shortcut })));
101
+ }
102
+ return (React.createElement(SubmenuContainer, null,
103
+ React.createElement(Container, { onMouseDown: (e) => {
104
+ e.preventDefault();
105
+ handleClick([]);
106
+ }, isOpen: menu.isOpen, className: menu.isEnabled ? '' : 'disabled' },
107
+ React.createElement(Active, null, activeContent(menu)),
108
+ React.createElement(Text, null, menu.label),
109
+ menu.submenu && React.createElement(Arrow, null),
110
+ menu.shortcut && React.createElement(Shortcut, { shortcut: menu.shortcut })),
111
+ menu.submenu && menu.isOpen && (React.createElement(NestedSubmenusContainer, null, menu.submenu.map((submenu, index) => (React.createElement(Submenu, { key: `menu-${index}`, menu: submenu, handleClick: (i) => handleClick([index, ...i]) })))))));
112
+ };
@@ -0,0 +1 @@
1
+ export * from './Menus';
@@ -0,0 +1,85 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { isMenuSeparator, } from '../lib/menus';
3
+ const initialPointer = [-1, -1, -1];
4
+ const transformPointer = (depth, index) => (pointer) => pointer.map((pointerPart, i) => {
5
+ if (i === depth) {
6
+ return index;
7
+ }
8
+ if (i > depth) {
9
+ return -1;
10
+ }
11
+ return pointerPart;
12
+ });
13
+ const isPart = (pointer, position) => {
14
+ const limit = position.length < 3 ? position.length : 3;
15
+ for (let i = 0; i < limit; i++) {
16
+ if (position[i] !== pointer[i]) {
17
+ return false;
18
+ }
19
+ }
20
+ return true;
21
+ };
22
+ const getSubmenuState = (specs, pointer, position) => {
23
+ return specs.map((spec, index) => {
24
+ if (isMenuSeparator(spec)) {
25
+ return spec;
26
+ }
27
+ const current = [...position, index];
28
+ return Object.assign(Object.assign({}, spec), { submenu: spec.submenu && getSubmenuState(spec.submenu, pointer, current), isOpen: isPart(pointer, current) });
29
+ });
30
+ };
31
+ const getMenuState = (specs, pointer) => {
32
+ return specs.map((spec, index) => {
33
+ const position = [index];
34
+ return Object.assign(Object.assign({}, spec), { submenu: spec.submenu && getSubmenuState(spec.submenu, pointer, position), isOpen: isPart(pointer, position) });
35
+ });
36
+ };
37
+ const getMenuAt = (state, position) => {
38
+ const [head, ...tail] = position.filter((i) => i !== -1);
39
+ const menu = state[head];
40
+ if (isMenuSeparator(menu)) {
41
+ return;
42
+ }
43
+ if (!tail.length) {
44
+ return menu;
45
+ }
46
+ else if (menu.submenu) {
47
+ return getMenuAt(menu.submenu, tail);
48
+ }
49
+ };
50
+ export const useMenus = (menus) => {
51
+ const [pointer, setPointer] = useState(initialPointer);
52
+ const state = getMenuState(menus, pointer);
53
+ const handleClick = useCallback((indices) => {
54
+ const menu = getMenuAt(state, indices);
55
+ if (!menu || !menu.isEnabled) {
56
+ return;
57
+ }
58
+ if (menu.run) {
59
+ menu.run();
60
+ setPointer([-1, -1, -1]);
61
+ }
62
+ else if (menu.submenu) {
63
+ const depth = indices.length - 1;
64
+ const index = indices[depth];
65
+ setPointer(transformPointer(depth, index));
66
+ }
67
+ }, [state]);
68
+ const ref = useRef(null);
69
+ useEffect(() => {
70
+ const handleClickOutside = (event) => {
71
+ if (ref.current && !ref.current.contains(event.target)) {
72
+ setPointer([-1, -1, -1]);
73
+ }
74
+ };
75
+ document.addEventListener('click', handleClickOutside);
76
+ return () => {
77
+ document.removeEventListener('click', handleClickOutside);
78
+ };
79
+ }, []);
80
+ return {
81
+ menus: state,
82
+ handleClick,
83
+ ref,
84
+ };
85
+ };
package/dist/es/index.js CHANGED
@@ -53,12 +53,15 @@ export * from './components/Text';
53
53
  export * from './components/ManuscriptNoteList';
54
54
  export * from './components/Comments';
55
55
  export * from './components/RelativeDate';
56
+ export * from './components/Menus';
56
57
  export * from './hooks/use-dropdown';
57
58
  export * from './hooks/use-files';
59
+ export * from './hooks/use-menus';
58
60
  export { useDeepCompareMemo, useDeepCompareCallback, } from './hooks/use-deep-compare';
59
61
  export * from './lib/authors';
60
62
  export * from './lib/capabilities';
61
63
  export * from './lib/files';
62
64
  export * from './lib/comments';
65
+ export * from './lib/menus';
63
66
  export { default as errorsDecoder } from './lib/errors-decoder';
64
67
  export * from './types';
@@ -0,0 +1,18 @@
1
+ /*!
2
+ * © 2024 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ export const isMenuSeparator = (menu) => {
17
+ return (menu === null || menu === void 0 ? void 0 : menu.role) === 'separator';
18
+ };
@@ -0,0 +1,24 @@
1
+ /*!
2
+ * © 2019 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import React, { Ref } from 'react';
17
+ import { Menu } from '../../lib/menus';
18
+ interface MenusProps {
19
+ menus: Menu[];
20
+ innerRef: Ref<HTMLDivElement>;
21
+ handleClick: (position: number[]) => void;
22
+ }
23
+ export declare const Menus: React.FC<MenusProps>;
24
+ export {};
@@ -0,0 +1,23 @@
1
+ /*!
2
+ * © 2019 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import React from 'react';
17
+ import { MenuShortcut } from '../../lib/menus';
18
+ export declare const ShortcutContainer: import("styled-components").StyledComponent<"div", import("styled-components").DefaultTheme, {}, never>;
19
+ interface ShortcutProps {
20
+ shortcut: MenuShortcut;
21
+ }
22
+ export declare const Shortcut: React.FC<ShortcutProps>;
23
+ export {};
@@ -0,0 +1,25 @@
1
+ /*!
2
+ * © 2019 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import React from 'react';
17
+ import { Menu, MenuSeparator } from '../../lib/menus';
18
+ export declare const Text: import("styled-components").StyledComponent<"div", import("styled-components").DefaultTheme, {}, never>;
19
+ export declare const SubmenusContainer: import("styled-components").StyledComponent<"div", import("styled-components").DefaultTheme, {}, never>;
20
+ interface SubmenuProps {
21
+ menu: Menu | MenuSeparator;
22
+ handleClick: (position: number[]) => void;
23
+ }
24
+ export declare const Submenu: React.FC<SubmenuProps>;
25
+ export {};
@@ -0,0 +1 @@
1
+ export * from './Menus';
@@ -0,0 +1,7 @@
1
+ /// <reference types="react" />
2
+ import { Menu, MenuSpec } from '../lib/menus';
3
+ export declare const useMenus: (menus: MenuSpec[]) => {
4
+ menus: Menu[];
5
+ handleClick: (indices: number[]) => void;
6
+ ref: import("react").RefObject<HTMLDivElement>;
7
+ };
@@ -54,12 +54,15 @@ export * from './components/Text';
54
54
  export * from './components/ManuscriptNoteList';
55
55
  export * from './components/Comments';
56
56
  export * from './components/RelativeDate';
57
+ export * from './components/Menus';
57
58
  export * from './hooks/use-dropdown';
58
59
  export * from './hooks/use-files';
60
+ export * from './hooks/use-menus';
59
61
  export { useDeepCompareMemo, useDeepCompareCallback, } from './hooks/use-deep-compare';
60
62
  export * from './lib/authors';
61
63
  export * from './lib/capabilities';
62
64
  export * from './lib/files';
63
65
  export * from './lib/comments';
66
+ export * from './lib/menus';
64
67
  export { default as errorsDecoder } from './lib/errors-decoder';
65
68
  export * from './types';
@@ -0,0 +1,38 @@
1
+ /*!
2
+ * © 2024 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ export interface MenuShortcut {
17
+ mac: string;
18
+ pc: string;
19
+ }
20
+ export interface MenuSpec {
21
+ id: string;
22
+ label: string;
23
+ role?: string;
24
+ shortcut?: MenuShortcut;
25
+ isActive?: boolean;
26
+ isEnabled: boolean;
27
+ run?: () => void;
28
+ submenu?: (MenuSpec | MenuSeparator)[];
29
+ }
30
+ export interface Menu extends MenuSpec {
31
+ isOpen: boolean;
32
+ submenu?: (Menu | MenuSeparator)[];
33
+ }
34
+ export type MenuSeparator = {
35
+ role: 'separator';
36
+ };
37
+ export declare const isMenuSeparator: (menu: any) => menu is MenuSeparator;
38
+ export type MenuPointer = [number, number, number];
@@ -76,9 +76,11 @@ interface Variations {
76
76
  interface Background {
77
77
  dark: string;
78
78
  selected: string;
79
+ tracked: States;
79
80
  }
80
81
  interface Border {
81
82
  field: States;
83
+ tracked: States;
82
84
  }
83
85
  interface Brand {
84
86
  dark: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@manuscripts/style-guide",
3
3
  "description": "Shared components for Manuscripts applications",
4
- "version": "1.5.0",
4
+ "version": "1.6.0-LEAN-3074-0",
5
5
  "repository": "github:Atypon-OpenSource/manuscripts-style-guide",
6
6
  "license": "Apache-2.0",
7
7
  "main": "dist/cjs",
@@ -36,7 +36,7 @@
36
36
  "@formatjs/intl-relativetimeformat": "^4.5.9",
37
37
  "@formatjs/intl-utils": "^2.2.0",
38
38
  "@manuscripts/assets": "^0.6.2",
39
- "@manuscripts/transform": "2.0.0",
39
+ "@manuscripts/transform": "2.0.3-LEAN-3074-0",
40
40
  "@manuscripts/json-schema": "2.2.1",
41
41
  "@reach/tabs": "^0.11.2",
42
42
  "formik": "^2.2.9",
@@ -113,4 +113,4 @@
113
113
  "resolutions": {
114
114
  "@types/react": "^17.0.2"
115
115
  }
116
- }
116
+ }