@manuscripts/style-guide 3.3.12 → 3.3.13
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.
- package/dist/cjs/components/Button.js +10 -0
- package/dist/cjs/components/Checkbox.js +5 -0
- package/dist/cjs/components/Menus/Menus.js +67 -5
- package/dist/cjs/components/Menus/Submenu.js +80 -8
- package/dist/cjs/components/NavDropdown.js +1 -1
- package/dist/cjs/components/Resizer/ResizerButton.js +1 -1
- package/dist/cjs/components/ToggleHeader.js +16 -4
- package/dist/cjs/hooks/use-menus.js +4 -0
- package/dist/es/components/Button.js +10 -0
- package/dist/es/components/Checkbox.js +5 -0
- package/dist/es/components/Menus/Menus.js +67 -5
- package/dist/es/components/Menus/Submenu.js +80 -8
- package/dist/es/components/NavDropdown.js +1 -1
- package/dist/es/components/Resizer/ResizerButton.js +1 -1
- package/dist/es/components/ToggleHeader.js +16 -4
- package/dist/es/hooks/use-menus.js +4 -0
- package/dist/types/components/Menus/Menus.d.ts +1 -0
- package/dist/types/components/Menus/Submenu.d.ts +1 -0
- package/dist/types/components/NavDropdown.d.ts +1 -0
- package/dist/types/hooks/use-menus.d.ts +1 -0
- package/dist/types/lib/menus.d.ts +1 -0
- package/dist/types/theme.d.ts +4 -0
- package/package.json +1 -1
|
@@ -128,6 +128,11 @@ exports.SecondaryButton = (0, styled_components_1.default)(ButtonTemplate) `
|
|
|
128
128
|
&:not([disabled]):active {
|
|
129
129
|
${(props) => btnColors(props.theme.colors.button.secondary.color.active, props.theme.colors.button.secondary.background.active, props.theme.colors.button.secondary.border.active, false)}
|
|
130
130
|
}
|
|
131
|
+
|
|
132
|
+
&:focus-visible {
|
|
133
|
+
outline: 3px solid ${(props) => props.theme.colors.outline.focus};
|
|
134
|
+
outline-offset: 4px;
|
|
135
|
+
}
|
|
131
136
|
`;
|
|
132
137
|
exports.PrimaryButton = (0, styled_components_1.default)(ButtonTemplate) `
|
|
133
138
|
${(props) => btnColors(props.theme.colors.button.primary.color.default, props.theme.colors.button.primary.background.default, props.theme.colors.button.primary.border.default, false)}
|
|
@@ -136,6 +141,11 @@ exports.PrimaryButton = (0, styled_components_1.default)(ButtonTemplate) `
|
|
|
136
141
|
${(props) => btnColors(props.theme.colors.button.primary.color.hover, props.theme.colors.button.primary.background.hover, props.theme.colors.button.primary.border.hover, false)}
|
|
137
142
|
}
|
|
138
143
|
|
|
144
|
+
&:focus-visible {
|
|
145
|
+
outline: 3px solid ${(props) => props.theme.colors.outline.focus} !important;
|
|
146
|
+
outline-offset: 4px;
|
|
147
|
+
}
|
|
148
|
+
|
|
139
149
|
&:not([disabled]):active {
|
|
140
150
|
${(props) => btnColors(props.theme.colors.button.primary.color.active, props.theme.colors.button.primary.background.active, props.theme.colors.button.primary.border.active, false)}
|
|
141
151
|
}
|
|
@@ -65,6 +65,11 @@ exports.CheckboxLabel = styled_components_1.default.label `
|
|
|
65
65
|
input:focus + div::before {
|
|
66
66
|
border-color: ${(props) => props.theme.colors.button.primary.border.hover};
|
|
67
67
|
}
|
|
68
|
+
|
|
69
|
+
input:focus-visible + div::before {
|
|
70
|
+
outline: 2px solid ${(props) => props.theme.colors.outline.focus};
|
|
71
|
+
outline-offset: 2px;
|
|
72
|
+
}
|
|
68
73
|
`;
|
|
69
74
|
exports.CheckboxField = styled_components_1.default.input.attrs({
|
|
70
75
|
type: 'checkbox',
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.Menus = void 0;
|
|
7
7
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const react_1 = require("react");
|
|
8
9
|
const styled_components_1 = __importDefault(require("styled-components"));
|
|
9
10
|
const Submenu_1 = require("./Submenu");
|
|
10
11
|
const MenusContainer = styled_components_1.default.div `
|
|
@@ -14,6 +15,11 @@ const MenusContainer = styled_components_1.default.div `
|
|
|
14
15
|
const MenuHeading = styled_components_1.default.div `
|
|
15
16
|
padding: 4px 8px;
|
|
16
17
|
cursor: pointer;
|
|
18
|
+
|
|
19
|
+
&:focus-visible {
|
|
20
|
+
outline: 2px solid ${(props) => props.theme.colors.outline.focus};
|
|
21
|
+
outline-offset: -2px;
|
|
22
|
+
}
|
|
17
23
|
`;
|
|
18
24
|
const MenuContainer = styled_components_1.default.div `
|
|
19
25
|
position: relative;
|
|
@@ -28,13 +34,69 @@ const MenuContainer = styled_components_1.default.div `
|
|
|
28
34
|
}
|
|
29
35
|
}
|
|
30
36
|
`;
|
|
31
|
-
const Menus = ({ menus, innerRef, handleClick, }) => {
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
const Menus = ({ menus, innerRef, handleClick, closeAll, }) => {
|
|
38
|
+
const menuHeadingsRef = (0, react_1.useRef)([]);
|
|
39
|
+
(0, react_1.useEffect)(() => {
|
|
40
|
+
menuHeadingsRef.current.forEach((heading, index) => {
|
|
41
|
+
if (heading) {
|
|
42
|
+
heading.tabIndex = index === 0 ? 0 : -1;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}, [menus]);
|
|
46
|
+
const handleKeyDown = (event) => {
|
|
47
|
+
const target = event.target;
|
|
48
|
+
const currentIndex = menuHeadingsRef.current.findIndex((heading) => heading === target);
|
|
49
|
+
if (currentIndex === -1) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
switch (event.key) {
|
|
53
|
+
case 'ArrowRight': {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
const nextIndex = (currentIndex + 1) % menuHeadingsRef.current.length;
|
|
56
|
+
menuHeadingsRef.current[nextIndex]?.focus();
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
case 'ArrowLeft': {
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
const prevIndex = (currentIndex - 1 + menuHeadingsRef.current.length) %
|
|
62
|
+
menuHeadingsRef.current.length;
|
|
63
|
+
menuHeadingsRef.current[prevIndex]?.focus();
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case 'Enter': {
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
handleClick([currentIndex]);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case 'Escape': {
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
event.stopPropagation();
|
|
74
|
+
closeAll();
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case 'ArrowDown': {
|
|
78
|
+
event.preventDefault();
|
|
79
|
+
const menu = menus[currentIndex];
|
|
80
|
+
if (menu && !menu.isOpen) {
|
|
81
|
+
handleClick([currentIndex]);
|
|
82
|
+
}
|
|
83
|
+
const menuContainer = event.currentTarget.children[currentIndex];
|
|
84
|
+
setTimeout(() => {
|
|
85
|
+
const firstItem = menuContainer?.querySelector('[data-submenu-item]');
|
|
86
|
+
firstItem?.focus();
|
|
87
|
+
}, 0);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
return ((0, jsx_runtime_1.jsx)(MenusContainer, { ref: innerRef, "data-cy": 'manuscript-menus', onKeyDown: handleKeyDown, children: menus.map((menu, index) => {
|
|
93
|
+
return ((0, jsx_runtime_1.jsxs)(MenuContainer, { "data-cy": 'menu', isEnabled: menu.isEnabled, children: [(0, jsx_runtime_1.jsx)(MenuHeading, { ref: (el) => {
|
|
94
|
+
menuHeadingsRef.current[index] = el;
|
|
95
|
+
}, onMouseDown: (e) => {
|
|
34
96
|
e.preventDefault();
|
|
35
97
|
handleClick([index]);
|
|
36
|
-
}, isOpen: menu.isOpen, children: (0, jsx_runtime_1.jsx)(Submenu_1.Text, { children: menu.label }) }), menu.isEnabled && menu.isOpen && menu.submenu && ((0, jsx_runtime_1.jsx)(Submenu_1.SubmenusContainerWrapper, { children: (0, jsx_runtime_1.jsx)(Submenu_1.SubmenusContainer, { children: menu.submenu.map((submenu, sindex) => {
|
|
37
|
-
return ((0, jsx_runtime_1.jsx)(Submenu_1.Submenu, { menu: submenu, handleClick: (i) => handleClick([index, sindex, ...i]) }, `${index}-${sindex}`));
|
|
98
|
+
}, isOpen: menu.isOpen, children: (0, jsx_runtime_1.jsx)(Submenu_1.Text, { children: menu.label }) }), menu.isEnabled && menu.isOpen && menu.submenu && ((0, jsx_runtime_1.jsx)(Submenu_1.SubmenusContainerWrapper, { children: (0, jsx_runtime_1.jsx)(Submenu_1.SubmenusContainer, { "data-submenu-container": true, tabIndex: -1, children: menu.submenu.map((submenu, sindex) => {
|
|
99
|
+
return ((0, jsx_runtime_1.jsx)(Submenu_1.Submenu, { menu: submenu, handleClick: (i) => handleClick([index, sindex, ...i]), closeAll: closeAll }, `${index}-${sindex}`));
|
|
38
100
|
}) }) }))] }, `menu-${index}`));
|
|
39
101
|
}) }));
|
|
40
102
|
};
|
|
@@ -38,6 +38,10 @@ exports.SubmenusContainer = styled_components_1.default.div `
|
|
|
38
38
|
&[data-placement='right-start'] {
|
|
39
39
|
top: 8px;
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
&:focus-visible {
|
|
43
|
+
outline: none;
|
|
44
|
+
}
|
|
41
45
|
`;
|
|
42
46
|
exports.NestedSubmenusContainer = (0, styled_components_1.default)(exports.SubmenusContainer) `
|
|
43
47
|
position: absolute;
|
|
@@ -66,9 +70,10 @@ const Container = styled_components_1.default.div `
|
|
|
66
70
|
align-items: center;
|
|
67
71
|
cursor: pointer;
|
|
68
72
|
padding: 8px 16px 8px 4px;
|
|
73
|
+
outline: none;
|
|
69
74
|
${(props) => props.isOpen && 'background: #f2fbfc;'}
|
|
70
75
|
|
|
71
|
-
&:hover {
|
|
76
|
+
&:hover, &:focus {
|
|
72
77
|
background: #f2fbfc;
|
|
73
78
|
}
|
|
74
79
|
|
|
@@ -78,26 +83,93 @@ const Container = styled_components_1.default.div `
|
|
|
78
83
|
}
|
|
79
84
|
`;
|
|
80
85
|
const activeContent = (menu) => (menu.isActive ? '✓' : '');
|
|
81
|
-
const SubmenuLabel = ({ menu, handleClick }) => {
|
|
86
|
+
const SubmenuLabel = ({ menu, handleClick, closeAll, }) => {
|
|
82
87
|
if ((0, menus_1.isMenuSeparator)(menu)) {
|
|
83
88
|
return null;
|
|
84
89
|
}
|
|
85
|
-
|
|
90
|
+
const handleKeyDown = (e) => {
|
|
91
|
+
if ((0, menus_1.isMenuSeparator)(menu)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const currentElement = e.currentTarget;
|
|
95
|
+
const submenuContainer = currentElement.closest('[data-submenu-container]');
|
|
96
|
+
if (!submenuContainer) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const items = Array.from(submenuContainer.querySelectorAll('[data-submenu-item]'));
|
|
100
|
+
const currentIndex = items.indexOf(currentElement);
|
|
101
|
+
const focusMenuHeading = () => {
|
|
102
|
+
const menuContainer = currentElement.closest('[data-cy="menu"]');
|
|
103
|
+
const menuHeading = menuContainer?.querySelector('[tabindex]');
|
|
104
|
+
menuHeading?.focus();
|
|
105
|
+
};
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
e.stopPropagation();
|
|
108
|
+
switch (e.key) {
|
|
109
|
+
case 'ArrowDown': {
|
|
110
|
+
const nextIndex = currentIndex + 1;
|
|
111
|
+
const nextItem = items[nextIndex] || items[0];
|
|
112
|
+
nextItem?.focus();
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case 'ArrowUp': {
|
|
116
|
+
const prevIndex = currentIndex - 1;
|
|
117
|
+
const prevItem = items[prevIndex] || items[items.length - 1];
|
|
118
|
+
prevItem?.focus();
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case 'ArrowRight': {
|
|
122
|
+
if (menu.submenu) {
|
|
123
|
+
handleClick([]);
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
const nestedContainer = currentElement.nextElementSibling;
|
|
126
|
+
const firstItem = nestedContainer?.querySelector('[data-submenu-item]');
|
|
127
|
+
firstItem?.focus();
|
|
128
|
+
}, 0);
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case 'ArrowLeft': {
|
|
133
|
+
const parentContainer = submenuContainer?.parentElement?.closest('[data-submenu-container]');
|
|
134
|
+
if (parentContainer) {
|
|
135
|
+
const parentLabel = parentContainer.querySelector(':scope > [data-submenu-item]');
|
|
136
|
+
parentLabel?.focus();
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
focusMenuHeading();
|
|
140
|
+
closeAll();
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case 'Escape': {
|
|
145
|
+
closeAll();
|
|
146
|
+
focusMenuHeading();
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case 'Enter': {
|
|
150
|
+
if (menu.isEnabled) {
|
|
151
|
+
handleClick([]);
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
return ((0, jsx_runtime_1.jsxs)(Container, { isOpen: menu.isOpen, "data-cy": 'submenu', "data-submenu-item": true, className: menu.isEnabled ? '' : 'disabled', tabIndex: -1, onMouseDown: (e) => {
|
|
86
158
|
e.preventDefault();
|
|
87
159
|
handleClick([]);
|
|
88
|
-
}, children: [(0, jsx_runtime_1.jsx)(Active, { children: activeContent(menu) }), (0, jsx_runtime_1.jsx)(exports.Text, { children: menu.label }), menu.submenu && (0, jsx_runtime_1.jsx)(Arrow, {}), menu.shortcut && (0, jsx_runtime_1.jsx)(Shortcut_1.Shortcut, { shortcut: menu.shortcut })] }));
|
|
160
|
+
}, onKeyDown: handleKeyDown, children: [(0, jsx_runtime_1.jsx)(Active, { children: activeContent(menu) }), (0, jsx_runtime_1.jsx)(exports.Text, { children: menu.label }), menu.submenu && (0, jsx_runtime_1.jsx)(Arrow, {}), menu.shortcut && (0, jsx_runtime_1.jsx)(Shortcut_1.Shortcut, { shortcut: menu.shortcut })] }));
|
|
89
161
|
};
|
|
90
162
|
exports.SubmenuLabel = SubmenuLabel;
|
|
91
|
-
const Submenu = ({ menu, handleClick }) => {
|
|
163
|
+
const Submenu = ({ menu, handleClick, closeAll, }) => {
|
|
92
164
|
if ((0, menus_1.isMenuSeparator)(menu)) {
|
|
93
165
|
return (0, jsx_runtime_1.jsx)(Separator, {});
|
|
94
166
|
}
|
|
95
167
|
if (menu.component) {
|
|
96
|
-
return (0, jsx_runtime_1.jsx)(menu.component, { menu: menu, handleClick: handleClick });
|
|
168
|
+
return (0, jsx_runtime_1.jsx)(menu.component, { menu: menu, handleClick: handleClick, closeAll: closeAll });
|
|
97
169
|
}
|
|
98
170
|
if (!menu.submenu) {
|
|
99
|
-
return (0, jsx_runtime_1.jsx)(exports.SubmenuLabel, { menu: menu, handleClick: handleClick });
|
|
171
|
+
return ((0, jsx_runtime_1.jsx)(exports.SubmenuLabel, { menu: menu, handleClick: handleClick, closeAll: closeAll }));
|
|
100
172
|
}
|
|
101
|
-
return ((0, jsx_runtime_1.jsxs)(exports.SubmenuContainer, { "data-cy": 'submenu', children: [(0, jsx_runtime_1.jsx)(exports.SubmenuLabel, { menu: menu, handleClick: handleClick }), menu.submenu && menu.isOpen && ((0, jsx_runtime_1.jsx)(exports.NestedSubmenusContainer, { children: menu.submenu.map((submenu, index) => ((0, jsx_runtime_1.jsx)(exports.Submenu, { menu: submenu, handleClick: (i) => handleClick([index, ...i]) }, `menu-${index}`))) }))] }));
|
|
173
|
+
return ((0, jsx_runtime_1.jsxs)(exports.SubmenuContainer, { "data-cy": 'submenu', children: [(0, jsx_runtime_1.jsx)(exports.SubmenuLabel, { menu: menu, handleClick: handleClick, closeAll: closeAll }), menu.submenu && menu.isOpen && ((0, jsx_runtime_1.jsx)(exports.NestedSubmenusContainer, { "data-submenu-container": true, children: menu.submenu.map((submenu, index) => ((0, jsx_runtime_1.jsx)(exports.Submenu, { menu: submenu, handleClick: (i) => handleClick([index, ...i]), closeAll: closeAll }, `menu-${index}`))) }))] }));
|
|
102
174
|
};
|
|
103
175
|
exports.Submenu = Submenu;
|
|
@@ -142,5 +142,5 @@ exports.NavDropdownButtonContainer = (0, styled_components_1.default)(Button_1.S
|
|
|
142
142
|
stroke: currentColor;
|
|
143
143
|
}
|
|
144
144
|
`;
|
|
145
|
-
const NavDropdownButton = ({ as, children, disabled, isOpen, notificationsCount, onClick, removeChevron, }) => ((0, jsx_runtime_1.jsxs)(exports.NavDropdownButtonContainer, { as: as, disabled: disabled, onClick: onClick, isOpen: isOpen, className: 'dropdown-toggle', children: [(0, jsx_runtime_1.jsx)(exports.NavDropdownButtonText, { children: children }), !!notificationsCount && ((0, jsx_runtime_1.jsx)(exports.NotificationsBadge, { isOpen: isOpen, children: notificationsCount })), !removeChevron && (0, jsx_runtime_1.jsx)(exports.NavDropdownToggle, { className: isOpen ? 'open' : '' })] }));
|
|
145
|
+
const NavDropdownButton = ({ as, children, disabled, isOpen, notificationsCount, onClick, removeChevron, buttonRef, }) => ((0, jsx_runtime_1.jsxs)(exports.NavDropdownButtonContainer, { ref: buttonRef, as: as, disabled: disabled, onClick: onClick, isOpen: isOpen, className: 'dropdown-toggle', tabIndex: 0, children: [(0, jsx_runtime_1.jsx)(exports.NavDropdownButtonText, { children: children }), !!notificationsCount && ((0, jsx_runtime_1.jsx)(exports.NotificationsBadge, { isOpen: isOpen, children: notificationsCount })), !removeChevron && (0, jsx_runtime_1.jsx)(exports.NavDropdownToggle, { className: isOpen ? 'open' : '' })] }));
|
|
146
146
|
exports.NavDropdownButton = NavDropdownButton;
|
|
@@ -21,7 +21,7 @@ class ResizerButton extends react_1.default.PureComponent {
|
|
|
21
21
|
render() {
|
|
22
22
|
const { buttonInner, direction, side, isCollapsed, onClick, isVisible } = this.props;
|
|
23
23
|
const ResizerButtonInner = buttonInner || inners[direction][side];
|
|
24
|
-
return ((0, jsx_runtime_1.jsx)(ResizerButtonInner, { "aria-expanded": !isCollapsed, isCollapsed: isCollapsed, isVisible: isVisible, onClick: onClick, onMouseDown: (event) => event.preventDefault() }));
|
|
24
|
+
return ((0, jsx_runtime_1.jsx)(ResizerButtonInner, { "aria-expanded": !isCollapsed, isCollapsed: isCollapsed, isVisible: isVisible, onClick: onClick, onMouseDown: (event) => event.preventDefault(), tabIndex: 0 }));
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
exports.ResizerButton = ResizerButton;
|
|
@@ -8,10 +8,17 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
8
8
|
const styled_components_1 = __importDefault(require("styled-components"));
|
|
9
9
|
const icons_1 = require("./icons");
|
|
10
10
|
const ToggleHeader = ({ title, isOpen, onToggle, }) => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
const handleToggle = (e) => {
|
|
12
|
+
e.stopPropagation();
|
|
13
|
+
onToggle();
|
|
14
|
+
};
|
|
15
|
+
const handleKeyDown = (e) => {
|
|
16
|
+
if (e.key === 'Enter') {
|
|
17
|
+
e.preventDefault();
|
|
18
|
+
handleToggle(e);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
return ((0, jsx_runtime_1.jsxs)(ToggleHeaderContainer, { onClick: handleToggle, children: [(0, jsx_runtime_1.jsx)("span", { children: title }), (0, jsx_runtime_1.jsx)(exports.ToggleIcon, { isOpen: isOpen, onKeyDown: handleKeyDown, tabIndex: 0, children: isOpen ? (0, jsx_runtime_1.jsx)(icons_1.TriangleExpandedIcon, {}) : (0, jsx_runtime_1.jsx)(icons_1.TriangleCollapsedIcon, {}) })] }));
|
|
15
22
|
};
|
|
16
23
|
exports.ToggleHeader = ToggleHeader;
|
|
17
24
|
const ToggleHeaderContainer = styled_components_1.default.div `
|
|
@@ -37,6 +44,11 @@ exports.ToggleIcon = styled_components_1.default.div `
|
|
|
37
44
|
text-align: center;
|
|
38
45
|
cursor: pointer;
|
|
39
46
|
|
|
47
|
+
&:focus-visible {
|
|
48
|
+
outline: 2px solid ${(props) => props.theme.colors.outline.focus};
|
|
49
|
+
outline-offset: 2px;
|
|
50
|
+
}
|
|
51
|
+
|
|
40
52
|
svg {
|
|
41
53
|
width: 19px;
|
|
42
54
|
height: 19px;
|
|
@@ -89,9 +89,13 @@ const useMenus = (menus) => {
|
|
|
89
89
|
document.removeEventListener('click', handleClickOutside);
|
|
90
90
|
};
|
|
91
91
|
}, []);
|
|
92
|
+
const closeAll = (0, react_1.useCallback)(() => {
|
|
93
|
+
setPointer(initialPointer);
|
|
94
|
+
}, []);
|
|
92
95
|
return {
|
|
93
96
|
menus: state,
|
|
94
97
|
handleClick,
|
|
98
|
+
closeAll,
|
|
95
99
|
ref,
|
|
96
100
|
};
|
|
97
101
|
};
|
|
@@ -92,6 +92,11 @@ export const SecondaryButton = styled(ButtonTemplate) `
|
|
|
92
92
|
&:not([disabled]):active {
|
|
93
93
|
${(props) => btnColors(props.theme.colors.button.secondary.color.active, props.theme.colors.button.secondary.background.active, props.theme.colors.button.secondary.border.active, false)}
|
|
94
94
|
}
|
|
95
|
+
|
|
96
|
+
&:focus-visible {
|
|
97
|
+
outline: 3px solid ${(props) => props.theme.colors.outline.focus};
|
|
98
|
+
outline-offset: 4px;
|
|
99
|
+
}
|
|
95
100
|
`;
|
|
96
101
|
export const PrimaryButton = styled(ButtonTemplate) `
|
|
97
102
|
${(props) => btnColors(props.theme.colors.button.primary.color.default, props.theme.colors.button.primary.background.default, props.theme.colors.button.primary.border.default, false)}
|
|
@@ -100,6 +105,11 @@ export const PrimaryButton = styled(ButtonTemplate) `
|
|
|
100
105
|
${(props) => btnColors(props.theme.colors.button.primary.color.hover, props.theme.colors.button.primary.background.hover, props.theme.colors.button.primary.border.hover, false)}
|
|
101
106
|
}
|
|
102
107
|
|
|
108
|
+
&:focus-visible {
|
|
109
|
+
outline: 3px solid ${(props) => props.theme.colors.outline.focus} !important;
|
|
110
|
+
outline-offset: 4px;
|
|
111
|
+
}
|
|
112
|
+
|
|
103
113
|
&:not([disabled]):active {
|
|
104
114
|
${(props) => btnColors(props.theme.colors.button.primary.color.active, props.theme.colors.button.primary.background.active, props.theme.colors.button.primary.border.active, false)}
|
|
105
115
|
}
|
|
@@ -59,6 +59,11 @@ export const CheckboxLabel = styled.label `
|
|
|
59
59
|
input:focus + div::before {
|
|
60
60
|
border-color: ${(props) => props.theme.colors.button.primary.border.hover};
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
input:focus-visible + div::before {
|
|
64
|
+
outline: 2px solid ${(props) => props.theme.colors.outline.focus};
|
|
65
|
+
outline-offset: 2px;
|
|
66
|
+
}
|
|
62
67
|
`;
|
|
63
68
|
export const CheckboxField = styled.input.attrs({
|
|
64
69
|
type: 'checkbox',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
2
3
|
import styled from 'styled-components';
|
|
3
4
|
import { Submenu, SubmenusContainer, SubmenusContainerWrapper, Text, } from './Submenu';
|
|
4
5
|
const MenusContainer = styled.div `
|
|
@@ -8,6 +9,11 @@ const MenusContainer = styled.div `
|
|
|
8
9
|
const MenuHeading = styled.div `
|
|
9
10
|
padding: 4px 8px;
|
|
10
11
|
cursor: pointer;
|
|
12
|
+
|
|
13
|
+
&:focus-visible {
|
|
14
|
+
outline: 2px solid ${(props) => props.theme.colors.outline.focus};
|
|
15
|
+
outline-offset: -2px;
|
|
16
|
+
}
|
|
11
17
|
`;
|
|
12
18
|
const MenuContainer = styled.div `
|
|
13
19
|
position: relative;
|
|
@@ -22,13 +28,69 @@ const MenuContainer = styled.div `
|
|
|
22
28
|
}
|
|
23
29
|
}
|
|
24
30
|
`;
|
|
25
|
-
export const Menus = ({ menus, innerRef, handleClick, }) => {
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
export const Menus = ({ menus, innerRef, handleClick, closeAll, }) => {
|
|
32
|
+
const menuHeadingsRef = useRef([]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
menuHeadingsRef.current.forEach((heading, index) => {
|
|
35
|
+
if (heading) {
|
|
36
|
+
heading.tabIndex = index === 0 ? 0 : -1;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}, [menus]);
|
|
40
|
+
const handleKeyDown = (event) => {
|
|
41
|
+
const target = event.target;
|
|
42
|
+
const currentIndex = menuHeadingsRef.current.findIndex((heading) => heading === target);
|
|
43
|
+
if (currentIndex === -1) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
switch (event.key) {
|
|
47
|
+
case 'ArrowRight': {
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
const nextIndex = (currentIndex + 1) % menuHeadingsRef.current.length;
|
|
50
|
+
menuHeadingsRef.current[nextIndex]?.focus();
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case 'ArrowLeft': {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
const prevIndex = (currentIndex - 1 + menuHeadingsRef.current.length) %
|
|
56
|
+
menuHeadingsRef.current.length;
|
|
57
|
+
menuHeadingsRef.current[prevIndex]?.focus();
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case 'Enter': {
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
handleClick([currentIndex]);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case 'Escape': {
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
event.stopPropagation();
|
|
68
|
+
closeAll();
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case 'ArrowDown': {
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
const menu = menus[currentIndex];
|
|
74
|
+
if (menu && !menu.isOpen) {
|
|
75
|
+
handleClick([currentIndex]);
|
|
76
|
+
}
|
|
77
|
+
const menuContainer = event.currentTarget.children[currentIndex];
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
const firstItem = menuContainer?.querySelector('[data-submenu-item]');
|
|
80
|
+
firstItem?.focus();
|
|
81
|
+
}, 0);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
return (_jsx(MenusContainer, { ref: innerRef, "data-cy": 'manuscript-menus', onKeyDown: handleKeyDown, children: menus.map((menu, index) => {
|
|
87
|
+
return (_jsxs(MenuContainer, { "data-cy": 'menu', isEnabled: menu.isEnabled, children: [_jsx(MenuHeading, { ref: (el) => {
|
|
88
|
+
menuHeadingsRef.current[index] = el;
|
|
89
|
+
}, onMouseDown: (e) => {
|
|
28
90
|
e.preventDefault();
|
|
29
91
|
handleClick([index]);
|
|
30
|
-
}, isOpen: menu.isOpen, children: _jsx(Text, { children: menu.label }) }), menu.isEnabled && menu.isOpen && menu.submenu && (_jsx(SubmenusContainerWrapper, { children: _jsx(SubmenusContainer, { children: menu.submenu.map((submenu, sindex) => {
|
|
31
|
-
return (_jsx(Submenu, { menu: submenu, handleClick: (i) => handleClick([index, sindex, ...i]) }, `${index}-${sindex}`));
|
|
92
|
+
}, isOpen: menu.isOpen, children: _jsx(Text, { children: menu.label }) }), menu.isEnabled && menu.isOpen && menu.submenu && (_jsx(SubmenusContainerWrapper, { children: _jsx(SubmenusContainer, { "data-submenu-container": true, tabIndex: -1, children: menu.submenu.map((submenu, sindex) => {
|
|
93
|
+
return (_jsx(Submenu, { menu: submenu, handleClick: (i) => handleClick([index, sindex, ...i]), closeAll: closeAll }, `${index}-${sindex}`));
|
|
32
94
|
}) }) }))] }, `menu-${index}`));
|
|
33
95
|
}) }));
|
|
34
96
|
};
|
|
@@ -32,6 +32,10 @@ export const SubmenusContainer = styled.div `
|
|
|
32
32
|
&[data-placement='right-start'] {
|
|
33
33
|
top: 8px;
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
&:focus-visible {
|
|
37
|
+
outline: none;
|
|
38
|
+
}
|
|
35
39
|
`;
|
|
36
40
|
export const NestedSubmenusContainer = styled(SubmenusContainer) `
|
|
37
41
|
position: absolute;
|
|
@@ -60,9 +64,10 @@ const Container = styled.div `
|
|
|
60
64
|
align-items: center;
|
|
61
65
|
cursor: pointer;
|
|
62
66
|
padding: 8px 16px 8px 4px;
|
|
67
|
+
outline: none;
|
|
63
68
|
${(props) => props.isOpen && 'background: #f2fbfc;'}
|
|
64
69
|
|
|
65
|
-
&:hover {
|
|
70
|
+
&:hover, &:focus {
|
|
66
71
|
background: #f2fbfc;
|
|
67
72
|
}
|
|
68
73
|
|
|
@@ -72,24 +77,91 @@ const Container = styled.div `
|
|
|
72
77
|
}
|
|
73
78
|
`;
|
|
74
79
|
const activeContent = (menu) => (menu.isActive ? '✓' : '');
|
|
75
|
-
export const SubmenuLabel = ({ menu, handleClick }) => {
|
|
80
|
+
export const SubmenuLabel = ({ menu, handleClick, closeAll, }) => {
|
|
76
81
|
if (isMenuSeparator(menu)) {
|
|
77
82
|
return null;
|
|
78
83
|
}
|
|
79
|
-
|
|
84
|
+
const handleKeyDown = (e) => {
|
|
85
|
+
if (isMenuSeparator(menu)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const currentElement = e.currentTarget;
|
|
89
|
+
const submenuContainer = currentElement.closest('[data-submenu-container]');
|
|
90
|
+
if (!submenuContainer) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const items = Array.from(submenuContainer.querySelectorAll('[data-submenu-item]'));
|
|
94
|
+
const currentIndex = items.indexOf(currentElement);
|
|
95
|
+
const focusMenuHeading = () => {
|
|
96
|
+
const menuContainer = currentElement.closest('[data-cy="menu"]');
|
|
97
|
+
const menuHeading = menuContainer?.querySelector('[tabindex]');
|
|
98
|
+
menuHeading?.focus();
|
|
99
|
+
};
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
e.stopPropagation();
|
|
102
|
+
switch (e.key) {
|
|
103
|
+
case 'ArrowDown': {
|
|
104
|
+
const nextIndex = currentIndex + 1;
|
|
105
|
+
const nextItem = items[nextIndex] || items[0];
|
|
106
|
+
nextItem?.focus();
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case 'ArrowUp': {
|
|
110
|
+
const prevIndex = currentIndex - 1;
|
|
111
|
+
const prevItem = items[prevIndex] || items[items.length - 1];
|
|
112
|
+
prevItem?.focus();
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case 'ArrowRight': {
|
|
116
|
+
if (menu.submenu) {
|
|
117
|
+
handleClick([]);
|
|
118
|
+
setTimeout(() => {
|
|
119
|
+
const nestedContainer = currentElement.nextElementSibling;
|
|
120
|
+
const firstItem = nestedContainer?.querySelector('[data-submenu-item]');
|
|
121
|
+
firstItem?.focus();
|
|
122
|
+
}, 0);
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case 'ArrowLeft': {
|
|
127
|
+
const parentContainer = submenuContainer?.parentElement?.closest('[data-submenu-container]');
|
|
128
|
+
if (parentContainer) {
|
|
129
|
+
const parentLabel = parentContainer.querySelector(':scope > [data-submenu-item]');
|
|
130
|
+
parentLabel?.focus();
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
focusMenuHeading();
|
|
134
|
+
closeAll();
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case 'Escape': {
|
|
139
|
+
closeAll();
|
|
140
|
+
focusMenuHeading();
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case 'Enter': {
|
|
144
|
+
if (menu.isEnabled) {
|
|
145
|
+
handleClick([]);
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
return (_jsxs(Container, { isOpen: menu.isOpen, "data-cy": 'submenu', "data-submenu-item": true, className: menu.isEnabled ? '' : 'disabled', tabIndex: -1, onMouseDown: (e) => {
|
|
80
152
|
e.preventDefault();
|
|
81
153
|
handleClick([]);
|
|
82
|
-
}, children: [_jsx(Active, { children: activeContent(menu) }), _jsx(Text, { children: menu.label }), menu.submenu && _jsx(Arrow, {}), menu.shortcut && _jsx(Shortcut, { shortcut: menu.shortcut })] }));
|
|
154
|
+
}, onKeyDown: handleKeyDown, children: [_jsx(Active, { children: activeContent(menu) }), _jsx(Text, { children: menu.label }), menu.submenu && _jsx(Arrow, {}), menu.shortcut && _jsx(Shortcut, { shortcut: menu.shortcut })] }));
|
|
83
155
|
};
|
|
84
|
-
export const Submenu = ({ menu, handleClick }) => {
|
|
156
|
+
export const Submenu = ({ menu, handleClick, closeAll, }) => {
|
|
85
157
|
if (isMenuSeparator(menu)) {
|
|
86
158
|
return _jsx(Separator, {});
|
|
87
159
|
}
|
|
88
160
|
if (menu.component) {
|
|
89
|
-
return _jsx(menu.component, { menu: menu, handleClick: handleClick });
|
|
161
|
+
return _jsx(menu.component, { menu: menu, handleClick: handleClick, closeAll: closeAll });
|
|
90
162
|
}
|
|
91
163
|
if (!menu.submenu) {
|
|
92
|
-
return _jsx(SubmenuLabel, { menu: menu, handleClick: handleClick });
|
|
164
|
+
return (_jsx(SubmenuLabel, { menu: menu, handleClick: handleClick, closeAll: closeAll }));
|
|
93
165
|
}
|
|
94
|
-
return (_jsxs(SubmenuContainer, { "data-cy": 'submenu', children: [_jsx(SubmenuLabel, { menu: menu, handleClick: handleClick }), menu.submenu && menu.isOpen && (_jsx(NestedSubmenusContainer, { children: menu.submenu.map((submenu, index) => (_jsx(Submenu, { menu: submenu, handleClick: (i) => handleClick([index, ...i]) }, `menu-${index}`))) }))] }));
|
|
166
|
+
return (_jsxs(SubmenuContainer, { "data-cy": 'submenu', children: [_jsx(SubmenuLabel, { menu: menu, handleClick: handleClick, closeAll: closeAll }), menu.submenu && menu.isOpen && (_jsx(NestedSubmenusContainer, { "data-submenu-container": true, children: menu.submenu.map((submenu, index) => (_jsx(Submenu, { menu: submenu, handleClick: (i) => handleClick([index, ...i]), closeAll: closeAll }, `menu-${index}`))) }))] }));
|
|
95
167
|
};
|
|
@@ -106,4 +106,4 @@ export const NavDropdownButtonContainer = styled(SecondaryButton).attrs((props)
|
|
|
106
106
|
stroke: currentColor;
|
|
107
107
|
}
|
|
108
108
|
`;
|
|
109
|
-
export const NavDropdownButton = ({ as, children, disabled, isOpen, notificationsCount, onClick, removeChevron, }) => (_jsxs(NavDropdownButtonContainer, { as: as, disabled: disabled, onClick: onClick, isOpen: isOpen, className: 'dropdown-toggle', children: [_jsx(NavDropdownButtonText, { children: children }), !!notificationsCount && (_jsx(NotificationsBadge, { isOpen: isOpen, children: notificationsCount })), !removeChevron && _jsx(NavDropdownToggle, { className: isOpen ? 'open' : '' })] }));
|
|
109
|
+
export const NavDropdownButton = ({ as, children, disabled, isOpen, notificationsCount, onClick, removeChevron, buttonRef, }) => (_jsxs(NavDropdownButtonContainer, { ref: buttonRef, as: as, disabled: disabled, onClick: onClick, isOpen: isOpen, className: 'dropdown-toggle', tabIndex: 0, children: [_jsx(NavDropdownButtonText, { children: children }), !!notificationsCount && (_jsx(NotificationsBadge, { isOpen: isOpen, children: notificationsCount })), !removeChevron && _jsx(NavDropdownToggle, { className: isOpen ? 'open' : '' })] }));
|
|
@@ -15,7 +15,7 @@ export class ResizerButton extends React.PureComponent {
|
|
|
15
15
|
render() {
|
|
16
16
|
const { buttonInner, direction, side, isCollapsed, onClick, isVisible } = this.props;
|
|
17
17
|
const ResizerButtonInner = buttonInner || inners[direction][side];
|
|
18
|
-
return (_jsx(ResizerButtonInner, { "aria-expanded": !isCollapsed, isCollapsed: isCollapsed, isVisible: isVisible, onClick: onClick, onMouseDown: (event) => event.preventDefault() }));
|
|
18
|
+
return (_jsx(ResizerButtonInner, { "aria-expanded": !isCollapsed, isCollapsed: isCollapsed, isVisible: isVisible, onClick: onClick, onMouseDown: (event) => event.preventDefault(), tabIndex: 0 }));
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
ResizerButton.defaultProps = {
|
|
@@ -2,10 +2,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import styled from 'styled-components';
|
|
3
3
|
import { TriangleCollapsedIcon, TriangleExpandedIcon } from './icons';
|
|
4
4
|
export const ToggleHeader = ({ title, isOpen, onToggle, }) => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
const handleToggle = (e) => {
|
|
6
|
+
e.stopPropagation();
|
|
7
|
+
onToggle();
|
|
8
|
+
};
|
|
9
|
+
const handleKeyDown = (e) => {
|
|
10
|
+
if (e.key === 'Enter') {
|
|
11
|
+
e.preventDefault();
|
|
12
|
+
handleToggle(e);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
return (_jsxs(ToggleHeaderContainer, { onClick: handleToggle, children: [_jsx("span", { children: title }), _jsx(ToggleIcon, { isOpen: isOpen, onKeyDown: handleKeyDown, tabIndex: 0, children: isOpen ? _jsx(TriangleExpandedIcon, {}) : _jsx(TriangleCollapsedIcon, {}) })] }));
|
|
9
16
|
};
|
|
10
17
|
const ToggleHeaderContainer = styled.div `
|
|
11
18
|
display: flex;
|
|
@@ -30,6 +37,11 @@ export const ToggleIcon = styled.div `
|
|
|
30
37
|
text-align: center;
|
|
31
38
|
cursor: pointer;
|
|
32
39
|
|
|
40
|
+
&:focus-visible {
|
|
41
|
+
outline: 2px solid ${(props) => props.theme.colors.outline.focus};
|
|
42
|
+
outline-offset: 2px;
|
|
43
|
+
}
|
|
44
|
+
|
|
33
45
|
svg {
|
|
34
46
|
width: 19px;
|
|
35
47
|
height: 19px;
|
|
@@ -86,9 +86,13 @@ export const useMenus = (menus) => {
|
|
|
86
86
|
document.removeEventListener('click', handleClickOutside);
|
|
87
87
|
};
|
|
88
88
|
}, []);
|
|
89
|
+
const closeAll = useCallback(() => {
|
|
90
|
+
setPointer(initialPointer);
|
|
91
|
+
}, []);
|
|
89
92
|
return {
|
|
90
93
|
menus: state,
|
|
91
94
|
handleClick,
|
|
95
|
+
closeAll,
|
|
92
96
|
ref,
|
|
93
97
|
};
|
|
94
98
|
};
|
|
@@ -23,6 +23,7 @@ export declare const NestedSubmenusContainer: import("styled-components").Styled
|
|
|
23
23
|
export interface SubmenuProps {
|
|
24
24
|
menu: Menu | MenuSeparator;
|
|
25
25
|
handleClick: (position: number[]) => void;
|
|
26
|
+
closeAll: () => void;
|
|
26
27
|
}
|
|
27
28
|
export declare const SubmenuLabel: React.FC<SubmenuProps>;
|
|
28
29
|
export declare const Submenu: React.FC<SubmenuProps>;
|
|
@@ -52,6 +52,7 @@ interface DropdownButtonProps {
|
|
|
52
52
|
onClick?: React.MouseEventHandler;
|
|
53
53
|
removeChevron?: boolean;
|
|
54
54
|
children: React.ReactNode;
|
|
55
|
+
buttonRef?: React.Ref<HTMLButtonElement>;
|
|
55
56
|
}
|
|
56
57
|
export declare const NavDropdownButton: React.FunctionComponent<DropdownButtonProps>;
|
|
57
58
|
export {};
|
package/dist/types/theme.d.ts
CHANGED
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": "3.3.
|
|
4
|
+
"version": "3.3.13",
|
|
5
5
|
"repository": "github:Atypon-OpenSource/manuscripts-style-guide",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"main": "dist/cjs",
|