@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.
@@ -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
- return ((0, jsx_runtime_1.jsx)(MenusContainer, { ref: innerRef, "data-cy": 'manuscript-menus', children: menus.map((menu, index) => {
33
- return ((0, jsx_runtime_1.jsxs)(MenuContainer, { "data-cy": 'menu', isEnabled: menu.isEnabled, children: [(0, jsx_runtime_1.jsx)(MenuHeading, { onMouseDown: (e) => {
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
- return ((0, jsx_runtime_1.jsxs)(Container, { isOpen: menu.isOpen, "data-cy": 'submenu', className: menu.isEnabled ? '' : 'disabled', onMouseDown: (e) => {
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
- return ((0, jsx_runtime_1.jsxs)(ToggleHeaderContainer, { onClick: (e) => {
12
- e.stopPropagation();
13
- onToggle();
14
- }, children: [(0, jsx_runtime_1.jsx)("span", { children: title }), (0, jsx_runtime_1.jsx)(exports.ToggleIcon, { isOpen: isOpen, children: isOpen ? (0, jsx_runtime_1.jsx)(icons_1.TriangleExpandedIcon, {}) : (0, jsx_runtime_1.jsx)(icons_1.TriangleCollapsedIcon, {}) })] }));
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
- return (_jsx(MenusContainer, { ref: innerRef, "data-cy": 'manuscript-menus', children: menus.map((menu, index) => {
27
- return (_jsxs(MenuContainer, { "data-cy": 'menu', isEnabled: menu.isEnabled, children: [_jsx(MenuHeading, { onMouseDown: (e) => {
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
- return (_jsxs(Container, { isOpen: menu.isOpen, "data-cy": 'submenu', className: menu.isEnabled ? '' : 'disabled', onMouseDown: (e) => {
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
- return (_jsxs(ToggleHeaderContainer, { onClick: (e) => {
6
- e.stopPropagation();
7
- onToggle();
8
- }, children: [_jsx("span", { children: title }), _jsx(ToggleIcon, { isOpen: isOpen, children: isOpen ? _jsx(TriangleExpandedIcon, {}) : _jsx(TriangleCollapsedIcon, {}) })] }));
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
  };
@@ -19,6 +19,7 @@ interface MenusProps {
19
19
  menus: Menu[];
20
20
  innerRef: Ref<HTMLDivElement>;
21
21
  handleClick: (position: number[]) => void;
22
+ closeAll: () => void;
22
23
  }
23
24
  export declare const Menus: React.FC<MenusProps>;
24
25
  export {};
@@ -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 {};
@@ -2,5 +2,6 @@ import { Menu, MenuSpec } from '../lib/menus';
2
2
  export declare const useMenus: (menus: MenuSpec[]) => {
3
3
  menus: Menu[];
4
4
  handleClick: (indices: number[]) => void;
5
+ closeAll: () => void;
5
6
  ref: import("react").RefObject<HTMLDivElement | null>;
6
7
  };
@@ -21,6 +21,7 @@ export interface MenuShortcut {
21
21
  export interface MenuComponentProps {
22
22
  menu: Menu;
23
23
  handleClick: (position: number[]) => void;
24
+ closeAll: () => void;
24
25
  }
25
26
  export interface MenuSpec {
26
27
  id: string;
@@ -26,6 +26,10 @@ interface Colors {
26
26
  brand: Brand;
27
27
  button: Button;
28
28
  text: Text & Alerts & Variations;
29
+ outline: Outline;
30
+ }
31
+ interface Outline {
32
+ focus: string;
29
33
  }
30
34
  interface Font {
31
35
  family: FontFamily;
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.12",
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",