@telus-uds/components-web 2.32.2 → 2.33.1

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/CHANGELOG.md CHANGED
@@ -1,12 +1,37 @@
1
1
  # Change Log - @telus-uds/components-web
2
2
 
3
- This log was last generated on Wed, 27 Mar 2024 21:09:55 GMT and should not be manually modified.
3
+ This log was last generated on Wed, 24 Apr 2024 16:28:55 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
+ ## 2.33.1
8
+
9
+ Wed, 24 Apr 2024 16:28:55 GMT
10
+
11
+ ### Patches
12
+
13
+ - DatePicker visual inconsistency fixed: when the selected date was outside the actual month, the date was highlighted in the calendar but the calendar still appeared in the actual month instead of the correct month. (35577399+JoshHC@users.noreply.github.com)
14
+ - vertical position fixed for the calendar in datepicker when scrolling (35577399+JoshHC@users.noreply.github.com)
15
+ - `NavigationSubMenu:` fix proptype (guillermo.peitzner@telus.com)
16
+ - `NavigationBar:` close menus when clicking outside the component (guillermo.peitzner@telus.com)
17
+ - Bump @telus-uds/components-base to v1.83.0
18
+
19
+ ## 2.33.0
20
+
21
+ Fri, 05 Apr 2024 17:16:24 GMT
22
+
23
+ ### Minor changes
24
+
25
+ - Bump @telus-uds/components-base to v1.82.0
26
+ - Bump @telus-uds/system-theme-tokens to v2.54.0
27
+
28
+ ### Patches
29
+
30
+ - `Breadcrumbs`: fix text wrapping error (guillermo.peitzner@telus.com)
31
+
7
32
  ## 2.32.2
8
33
 
9
- Wed, 27 Mar 2024 21:09:55 GMT
34
+ Wed, 27 Mar 2024 21:13:10 GMT
10
35
 
11
36
  ### Patches
12
37
 
@@ -27,15 +27,9 @@ const StyledList = /*#__PURE__*/_styledComponents.default.ol.withConfig({
27
27
  listStylePosition: 'inside',
28
28
  margin: 0,
29
29
  padding: 0,
30
- alignItems: 'baseline',
31
- height: _ref => {
32
- let {
33
- iconContainerSize
34
- } = _ref;
35
- return `${iconContainerSize}px`;
36
- }
30
+ alignItems: 'baseline'
37
31
  });
38
- const omitProps = _ref2 => {
32
+ const omitProps = _ref => {
39
33
  let {
40
34
  current,
41
35
  path,
@@ -44,7 +38,7 @@ const omitProps = _ref2 => {
44
38
  childRoutes,
45
39
  component,
46
40
  ...props
47
- } = _ref2;
41
+ } = _ref;
48
42
  return props;
49
43
  };
50
44
  const getBreadcrumbName = (item, params) => {
@@ -107,7 +101,7 @@ const getStructuredData = (items, baseUrl) => {
107
101
  /**
108
102
  * Display a hierarchy of links, commonly used for navigation.
109
103
  */
110
- const Breadcrumbs = _ref3 => {
104
+ const Breadcrumbs = _ref2 => {
111
105
  let {
112
106
  baseUrl,
113
107
  children,
@@ -118,7 +112,7 @@ const Breadcrumbs = _ref3 => {
118
112
  variant,
119
113
  LinkRouter,
120
114
  ...rest
121
- } = _ref3;
115
+ } = _ref2;
122
116
  // React Helmet can cause a memory leak in SSR if not properly configured.
123
117
  // Only run it in SSR if theme options tells us to.
124
118
  /* const {
@@ -128,7 +122,7 @@ const Breadcrumbs = _ref3 => {
128
122
  // const isSSR = typeof window === 'undefined' || isHydrating
129
123
  // const hasMetadata = isSSR ? enableHelmetSSR : true
130
124
  const helmetContext = {};
131
- const activeRoutes = children ? _react.default.Children.map((0, _componentsBase.unpackFragment)(children), _ref4 => {
125
+ const activeRoutes = children ? _react.default.Children.map((0, _componentsBase.unpackFragment)(children), _ref3 => {
132
126
  let {
133
127
  props: {
134
128
  href,
@@ -136,7 +130,7 @@ const Breadcrumbs = _ref3 => {
136
130
  ...itemRest
137
131
  },
138
132
  ref
139
- } = _ref4;
133
+ } = _ref3;
140
134
  return {
141
135
  path: href,
142
136
  breadcrumbName,
@@ -166,7 +160,7 @@ const Breadcrumbs = _ref3 => {
166
160
  ...selectProps(rest),
167
161
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(StyledList, {
168
162
  iconContainerSize: themeTokens.iconContainerSize,
169
- children: items.map(_ref5 => {
163
+ children: items.map(_ref4 => {
170
164
  let {
171
165
  href,
172
166
  current,
@@ -174,7 +168,7 @@ const Breadcrumbs = _ref3 => {
174
168
  LinkRouter: ItemLinkRouter = LinkRouter,
175
169
  linkRouterProps: itemLinkRouterProps,
176
170
  ...itemRest
177
- } = _ref5;
171
+ } = _ref4;
178
172
  return /*#__PURE__*/(0, _react.createElement)(_Item.default, {
179
173
  ...itemRest,
180
174
  current: current,
@@ -37,7 +37,8 @@ const StyledItemContainer = /*#__PURE__*/_styledComponents.default.li.withConfig
37
37
  iconContainerSize
38
38
  } = _ref3;
39
39
  return `${iconContainerSize}px`;
40
- }
40
+ },
41
+ marginBottom: '8px'
41
42
  });
42
43
  const IconContainer = /*#__PURE__*/_styledComponents.default.span.withConfig({
43
44
  displayName: "Item__IconContainer",
@@ -131,9 +131,10 @@ const DatePicker = /*#__PURE__*/(0, _react.forwardRef)((_ref3, ref) => {
131
131
  left,
132
132
  top
133
133
  } = textInputRef.current.getBoundingClientRect();
134
+ const inputTop = top + window.scrollY;
134
135
  setDatePickerPosition({
135
136
  left,
136
- top: top + textInputRef.current.offsetHeight
137
+ top: inputTop + textInputRef.current.offsetHeight
137
138
  });
138
139
  };
139
140
  const throttledUpdateDimensions = (0, _lodash.throttle)(updateDimensions, 100, {
@@ -308,7 +309,7 @@ const DatePicker = /*#__PURE__*/(0, _react.forwardRef)((_ref3, ref) => {
308
309
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_DayPickerSingleDateController.default, {
309
310
  date: inputDate,
310
311
  onDateChange: onChange,
311
- focused: isFocused,
312
+ focused: inputDate ?? isFocused,
312
313
  onFocusChange: onFocusChange,
313
314
  numberOfMonths: 1,
314
315
  hideKeyboardShortcutsPanel: true,
@@ -70,27 +70,49 @@ const NavigationBar = /*#__PURE__*/(0, _react.forwardRef)((_ref, ref) => {
70
70
  setOpenSubMenuId(null);
71
71
  }
72
72
  };
73
+ const navRefDefault = (0, _react.useRef)(null);
74
+ const navRef = ref ?? navRefDefault;
75
+ const itemsRef = (0, _react.useRef)(null);
76
+
77
+ // Close the submenu when the user clicks outside the navigation bar
78
+ const handleMouseDown = (0, _react.useCallback)(event => {
79
+ if (navRef.current && itemsRef.current) {
80
+ // Get the bounding rectangles of the navigation bar and the items container
81
+ const navRect = navRef.current.getBoundingClientRect();
82
+ const itemsRect = itemsRef.current.getBoundingClientRect();
83
+
84
+ // Check if the click was outside the navigation bar and the items container
85
+ const isOutsideNav = event.clientX < navRect.left || event.clientX > navRect.right || event.clientY < navRect.top || event.clientY > navRect.bottom;
86
+ const isOutsideItems = event.clientX < itemsRect.left || event.clientX > itemsRect.right || event.clientY < itemsRect.top || event.clientY > itemsRect.bottom;
87
+ if (isOutsideNav && isOutsideItems) {
88
+ setOpenSubMenuId(null);
89
+ }
90
+ }
91
+ }, [navRef, itemsRef, setOpenSubMenuId]);
73
92
 
74
- // Add listeners for mouse clicks outside and for ESCAPE key presses
75
93
  // TODO: create a custom hook for that and use here and in the Footnote
76
94
  (0, _react.useEffect)(() => {
95
+ // Add listeners for mouse clicks outside and for key presses
96
+ document.addEventListener('mousedown', handleMouseDown);
77
97
  if (openSubMenuId !== null) {
78
98
  window.addEventListener('click', handleSubMenuClose);
79
99
  window.addEventListener('keydown', handleSubMenuClose);
80
100
  window.addEventListener('touchstart', handleSubMenuClose);
81
101
  }
82
102
  return () => {
103
+ // Remove listeners when the component is unmounted
104
+ document.removeEventListener('mousedown', handleMouseDown);
83
105
  if (openSubMenuId !== null) {
84
106
  window.removeEventListener('click', handleSubMenuClose);
85
107
  window.removeEventListener('keydown', handleSubMenuClose);
86
108
  window.removeEventListener('touchstart', handleSubMenuClose);
87
109
  }
88
110
  };
89
- }, [openSubMenuId]);
111
+ }, [openSubMenuId, handleMouseDown]);
90
112
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_componentsBase.StackView, {
91
113
  accessibilityRole: accessibilityRole,
92
114
  direction: direction,
93
- ref: ref,
115
+ ref: navRef,
94
116
  space: 2,
95
117
  tokens: {
96
118
  alignItems: direction === 'column' ? 'flex-start' : 'center',
@@ -142,6 +164,7 @@ const NavigationBar = /*#__PURE__*/(0, _react.forwardRef)((_ref, ref) => {
142
164
  },
143
165
  items: nestedItems,
144
166
  selected: itemId === selectedId,
167
+ itemsContainerRef: itemsRef,
145
168
  ...itemRest,
146
169
  ...(nestedItems && {
147
170
  isOpen
@@ -29,7 +29,8 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
29
29
  items = [],
30
30
  openOverlayRef,
31
31
  LinkRouter,
32
- linkRouterProps
32
+ linkRouterProps,
33
+ itemsContainerRef
33
34
  } = _ref;
34
35
  const focusTrapRef = (0, _react.useRef)();
35
36
  const maxWidth = 289; // Slightly over 288 of nav item to account for subpixel rounding
@@ -131,7 +132,8 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
131
132
  parentRef: sourceRef,
132
133
  selectedId: selectedId,
133
134
  LinkRouter: LinkRouter,
134
- linkRouterProps: linkRouterProps
135
+ linkRouterProps: linkRouterProps,
136
+ ref: itemsContainerRef
135
137
  })
136
138
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
137
139
  // This catches and shifts focus to other interactive elements.
@@ -156,7 +158,8 @@ NavigationSubMenu.propTypes = {
156
158
  items: _propTypes.default.array,
157
159
  openOverlayRef: _propTypes.default.object,
158
160
  LinkRouter: _propTypes.default.elementType,
159
- linkRouterProps: _propTypes.default.object
161
+ linkRouterProps: _propTypes.default.object,
162
+ itemsContainerRef: _propTypes.default.object
160
163
  };
161
164
  var _default = NavigationSubMenu;
162
165
  exports.default = _default;
@@ -20,15 +20,9 @@ const StyledList = /*#__PURE__*/styled.ol.withConfig({
20
20
  listStylePosition: 'inside',
21
21
  margin: 0,
22
22
  padding: 0,
23
- alignItems: 'baseline',
24
- height: _ref => {
25
- let {
26
- iconContainerSize
27
- } = _ref;
28
- return `${iconContainerSize}px`;
29
- }
23
+ alignItems: 'baseline'
30
24
  });
31
- const omitProps = _ref2 => {
25
+ const omitProps = _ref => {
32
26
  let {
33
27
  current,
34
28
  path,
@@ -37,7 +31,7 @@ const omitProps = _ref2 => {
37
31
  childRoutes,
38
32
  component,
39
33
  ...props
40
- } = _ref2;
34
+ } = _ref;
41
35
  return props;
42
36
  };
43
37
  const getBreadcrumbName = (item, params) => {
@@ -100,7 +94,7 @@ const getStructuredData = (items, baseUrl) => {
100
94
  /**
101
95
  * Display a hierarchy of links, commonly used for navigation.
102
96
  */
103
- const Breadcrumbs = _ref3 => {
97
+ const Breadcrumbs = _ref2 => {
104
98
  let {
105
99
  baseUrl,
106
100
  children,
@@ -111,7 +105,7 @@ const Breadcrumbs = _ref3 => {
111
105
  variant,
112
106
  LinkRouter,
113
107
  ...rest
114
- } = _ref3;
108
+ } = _ref2;
115
109
  // React Helmet can cause a memory leak in SSR if not properly configured.
116
110
  // Only run it in SSR if theme options tells us to.
117
111
  /* const {
@@ -121,7 +115,7 @@ const Breadcrumbs = _ref3 => {
121
115
  // const isSSR = typeof window === 'undefined' || isHydrating
122
116
  // const hasMetadata = isSSR ? enableHelmetSSR : true
123
117
  const helmetContext = {};
124
- const activeRoutes = children ? React.Children.map(unpackFragment(children), _ref4 => {
118
+ const activeRoutes = children ? React.Children.map(unpackFragment(children), _ref3 => {
125
119
  let {
126
120
  props: {
127
121
  href,
@@ -129,7 +123,7 @@ const Breadcrumbs = _ref3 => {
129
123
  ...itemRest
130
124
  },
131
125
  ref
132
- } = _ref4;
126
+ } = _ref3;
133
127
  return {
134
128
  path: href,
135
129
  breadcrumbName,
@@ -159,7 +153,7 @@ const Breadcrumbs = _ref3 => {
159
153
  ...selectProps(rest),
160
154
  children: [/*#__PURE__*/_jsx(StyledList, {
161
155
  iconContainerSize: themeTokens.iconContainerSize,
162
- children: items.map(_ref5 => {
156
+ children: items.map(_ref4 => {
163
157
  let {
164
158
  href,
165
159
  current,
@@ -167,7 +161,7 @@ const Breadcrumbs = _ref3 => {
167
161
  LinkRouter: ItemLinkRouter = LinkRouter,
168
162
  linkRouterProps: itemLinkRouterProps,
169
163
  ...itemRest
170
- } = _ref5;
164
+ } = _ref4;
171
165
  return /*#__PURE__*/_createElement(Item, {
172
166
  ...itemRest,
173
167
  current: current,
@@ -30,7 +30,8 @@ const StyledItemContainer = /*#__PURE__*/styled.li.withConfig({
30
30
  iconContainerSize
31
31
  } = _ref3;
32
32
  return `${iconContainerSize}px`;
33
- }
33
+ },
34
+ marginBottom: '8px'
34
35
  });
35
36
  const IconContainer = /*#__PURE__*/styled.span.withConfig({
36
37
  displayName: "Item__IconContainer",
@@ -124,9 +124,10 @@ const DatePicker = /*#__PURE__*/forwardRef((_ref3, ref) => {
124
124
  left,
125
125
  top
126
126
  } = textInputRef.current.getBoundingClientRect();
127
+ const inputTop = top + window.scrollY;
127
128
  setDatePickerPosition({
128
129
  left,
129
- top: top + textInputRef.current.offsetHeight
130
+ top: inputTop + textInputRef.current.offsetHeight
130
131
  });
131
132
  };
132
133
  const throttledUpdateDimensions = throttle(updateDimensions, 100, {
@@ -301,7 +302,7 @@ const DatePicker = /*#__PURE__*/forwardRef((_ref3, ref) => {
301
302
  children: /*#__PURE__*/_jsx(DayPickerSingleDateController, {
302
303
  date: inputDate,
303
304
  onDateChange: onChange,
304
- focused: isFocused,
305
+ focused: inputDate ?? isFocused,
305
306
  onFocusChange: onFocusChange,
306
307
  numberOfMonths: 1,
307
308
  hideKeyboardShortcutsPanel: true,
@@ -1,5 +1,5 @@
1
1
  var _withLinkRouter$propT, _withLinkRouter$propT2, _withLinkRouter$propT3, _withLinkRouter$propT4;
2
- import React, { forwardRef, useEffect, useRef, useState } from 'react';
2
+ import React, { forwardRef, useEffect, useRef, useState, useCallback } from 'react';
3
3
  import PropTypes from 'prop-types';
4
4
  import { selectSystemProps, StackView, Typography, useResponsiveProp, withLinkRouter, getTokensPropType } from '@telus-uds/components-base';
5
5
  import styled from 'styled-components';
@@ -62,27 +62,49 @@ const NavigationBar = /*#__PURE__*/forwardRef((_ref, ref) => {
62
62
  setOpenSubMenuId(null);
63
63
  }
64
64
  };
65
+ const navRefDefault = useRef(null);
66
+ const navRef = ref ?? navRefDefault;
67
+ const itemsRef = useRef(null);
68
+
69
+ // Close the submenu when the user clicks outside the navigation bar
70
+ const handleMouseDown = useCallback(event => {
71
+ if (navRef.current && itemsRef.current) {
72
+ // Get the bounding rectangles of the navigation bar and the items container
73
+ const navRect = navRef.current.getBoundingClientRect();
74
+ const itemsRect = itemsRef.current.getBoundingClientRect();
75
+
76
+ // Check if the click was outside the navigation bar and the items container
77
+ const isOutsideNav = event.clientX < navRect.left || event.clientX > navRect.right || event.clientY < navRect.top || event.clientY > navRect.bottom;
78
+ const isOutsideItems = event.clientX < itemsRect.left || event.clientX > itemsRect.right || event.clientY < itemsRect.top || event.clientY > itemsRect.bottom;
79
+ if (isOutsideNav && isOutsideItems) {
80
+ setOpenSubMenuId(null);
81
+ }
82
+ }
83
+ }, [navRef, itemsRef, setOpenSubMenuId]);
65
84
 
66
- // Add listeners for mouse clicks outside and for ESCAPE key presses
67
85
  // TODO: create a custom hook for that and use here and in the Footnote
68
86
  useEffect(() => {
87
+ // Add listeners for mouse clicks outside and for key presses
88
+ document.addEventListener('mousedown', handleMouseDown);
69
89
  if (openSubMenuId !== null) {
70
90
  window.addEventListener('click', handleSubMenuClose);
71
91
  window.addEventListener('keydown', handleSubMenuClose);
72
92
  window.addEventListener('touchstart', handleSubMenuClose);
73
93
  }
74
94
  return () => {
95
+ // Remove listeners when the component is unmounted
96
+ document.removeEventListener('mousedown', handleMouseDown);
75
97
  if (openSubMenuId !== null) {
76
98
  window.removeEventListener('click', handleSubMenuClose);
77
99
  window.removeEventListener('keydown', handleSubMenuClose);
78
100
  window.removeEventListener('touchstart', handleSubMenuClose);
79
101
  }
80
102
  };
81
- }, [openSubMenuId]);
103
+ }, [openSubMenuId, handleMouseDown]);
82
104
  return /*#__PURE__*/_jsxs(StackView, {
83
105
  accessibilityRole: accessibilityRole,
84
106
  direction: direction,
85
- ref: ref,
107
+ ref: navRef,
86
108
  space: 2,
87
109
  tokens: {
88
110
  alignItems: direction === 'column' ? 'flex-start' : 'center',
@@ -134,6 +156,7 @@ const NavigationBar = /*#__PURE__*/forwardRef((_ref, ref) => {
134
156
  },
135
157
  items: nestedItems,
136
158
  selected: itemId === selectedId,
159
+ itemsContainerRef: itemsRef,
137
160
  ...itemRest,
138
161
  ...(nestedItems && {
139
162
  isOpen
@@ -24,7 +24,8 @@ const NavigationSubMenu = _ref => {
24
24
  items = [],
25
25
  openOverlayRef,
26
26
  LinkRouter,
27
- linkRouterProps
27
+ linkRouterProps,
28
+ itemsContainerRef
28
29
  } = _ref;
29
30
  const focusTrapRef = useRef();
30
31
  const maxWidth = 289; // Slightly over 288 of nav item to account for subpixel rounding
@@ -126,7 +127,8 @@ const NavigationSubMenu = _ref => {
126
127
  parentRef: sourceRef,
127
128
  selectedId: selectedId,
128
129
  LinkRouter: LinkRouter,
129
- linkRouterProps: linkRouterProps
130
+ linkRouterProps: linkRouterProps,
131
+ ref: itemsContainerRef
130
132
  })
131
133
  }), /*#__PURE__*/_jsx("div", {
132
134
  // This catches and shifts focus to other interactive elements.
@@ -151,6 +153,7 @@ NavigationSubMenu.propTypes = {
151
153
  items: PropTypes.array,
152
154
  openOverlayRef: PropTypes.object,
153
155
  LinkRouter: PropTypes.elementType,
154
- linkRouterProps: PropTypes.object
156
+ linkRouterProps: PropTypes.object,
157
+ itemsContainerRef: PropTypes.object
155
158
  };
156
159
  export default NavigationSubMenu;
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  ],
6
6
  "dependencies": {
7
7
  "@gorhom/portal": "^1.0.14",
8
- "@telus-uds/components-base": "1.81.0",
8
+ "@telus-uds/components-base": "1.83.0",
9
9
  "@telus-uds/system-constants": "^1.3.0",
10
10
  "fscreen": "^1.2.0",
11
11
  "lodash.omit": "^4.5.0",
@@ -13,7 +13,7 @@
13
13
  "react-dates": "^21.8.0",
14
14
  "react-helmet-async": "^1.3.0",
15
15
  "react-moment-proptypes": "^1.8.1",
16
- "@telus-uds/system-theme-tokens": "^2.53.0",
16
+ "@telus-uds/system-theme-tokens": "^2.54.0",
17
17
  "prop-types": "^15.7.2",
18
18
  "lodash.throttle": "^4.1.1",
19
19
  "react-youtube": "^10.1.0",
@@ -83,5 +83,5 @@
83
83
  "skip": true
84
84
  },
85
85
  "types": "types/index.d.ts",
86
- "version": "2.32.2"
86
+ "version": "2.33.1"
87
87
  }
@@ -25,8 +25,7 @@ const StyledList = styled.ol({
25
25
  listStylePosition: 'inside',
26
26
  margin: 0,
27
27
  padding: 0,
28
- alignItems: 'baseline',
29
- height: ({ iconContainerSize }) => `${iconContainerSize}px`
28
+ alignItems: 'baseline'
30
29
  })
31
30
 
32
31
  const omitProps = ({
@@ -17,7 +17,8 @@ const StyledItemContainer = styled.li({
17
17
  display: 'flex',
18
18
  paddingLeft: ({ listItemPadding }) => `${listItemPadding}px`,
19
19
  lineHeight: ({ lineHeight, fontSize }) => `${Math.ceil(lineHeight * fontSize)}px`,
20
- height: ({ iconContainerSize }) => `${iconContainerSize}px`
20
+ height: ({ iconContainerSize }) => `${iconContainerSize}px`,
21
+ marginBottom: '8px'
21
22
  })
22
23
 
23
24
  const IconContainer = styled.span({
@@ -114,9 +114,10 @@ const DatePicker = forwardRef(
114
114
  const updateDimensions = () => {
115
115
  if (inline || !textInputRef.current) return
116
116
  const { left, top } = textInputRef.current.getBoundingClientRect()
117
+ const inputTop = top + window.scrollY
117
118
  setDatePickerPosition({
118
119
  left,
119
- top: top + textInputRef.current.offsetHeight
120
+ top: inputTop + textInputRef.current.offsetHeight
120
121
  })
121
122
  }
122
123
  const throttledUpdateDimensions = throttle(updateDimensions, 100, { leading: false })
@@ -282,7 +283,7 @@ const DatePicker = forwardRef(
282
283
  <DayPickerSingleDateController
283
284
  date={inputDate}
284
285
  onDateChange={onChange}
285
- focused={isFocused}
286
+ focused={inputDate ?? isFocused}
286
287
  onFocusChange={onFocusChange}
287
288
  numberOfMonths={1}
288
289
  hideKeyboardShortcutsPanel={true}
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, useEffect, useRef, useState } from 'react'
1
+ import React, { forwardRef, useEffect, useRef, useState, useCallback } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import {
4
4
  selectSystemProps,
@@ -69,28 +69,66 @@ const NavigationBar = forwardRef(
69
69
  }
70
70
  }
71
71
 
72
- // Add listeners for mouse clicks outside and for ESCAPE key presses
72
+ const navRefDefault = useRef(null)
73
+ const navRef = ref ?? navRefDefault
74
+ const itemsRef = useRef(null)
75
+
76
+ // Close the submenu when the user clicks outside the navigation bar
77
+ const handleMouseDown = useCallback(
78
+ (event) => {
79
+ if (navRef.current && itemsRef.current) {
80
+ // Get the bounding rectangles of the navigation bar and the items container
81
+ const navRect = navRef.current.getBoundingClientRect()
82
+ const itemsRect = itemsRef.current.getBoundingClientRect()
83
+
84
+ // Check if the click was outside the navigation bar and the items container
85
+ const isOutsideNav =
86
+ event.clientX < navRect.left ||
87
+ event.clientX > navRect.right ||
88
+ event.clientY < navRect.top ||
89
+ event.clientY > navRect.bottom
90
+
91
+ const isOutsideItems =
92
+ event.clientX < itemsRect.left ||
93
+ event.clientX > itemsRect.right ||
94
+ event.clientY < itemsRect.top ||
95
+ event.clientY > itemsRect.bottom
96
+
97
+ if (isOutsideNav && isOutsideItems) {
98
+ setOpenSubMenuId(null)
99
+ }
100
+ }
101
+ },
102
+ [navRef, itemsRef, setOpenSubMenuId]
103
+ )
104
+
73
105
  // TODO: create a custom hook for that and use here and in the Footnote
74
106
  useEffect(() => {
107
+ // Add listeners for mouse clicks outside and for key presses
108
+ document.addEventListener('mousedown', handleMouseDown)
109
+
75
110
  if (openSubMenuId !== null) {
76
111
  window.addEventListener('click', handleSubMenuClose)
77
112
  window.addEventListener('keydown', handleSubMenuClose)
78
113
  window.addEventListener('touchstart', handleSubMenuClose)
79
114
  }
80
115
  return () => {
116
+ // Remove listeners when the component is unmounted
117
+ document.removeEventListener('mousedown', handleMouseDown)
118
+
81
119
  if (openSubMenuId !== null) {
82
120
  window.removeEventListener('click', handleSubMenuClose)
83
121
  window.removeEventListener('keydown', handleSubMenuClose)
84
122
  window.removeEventListener('touchstart', handleSubMenuClose)
85
123
  }
86
124
  }
87
- }, [openSubMenuId])
125
+ }, [openSubMenuId, handleMouseDown])
88
126
 
89
127
  return (
90
128
  <StackView
91
129
  accessibilityRole={accessibilityRole}
92
130
  direction={direction}
93
- ref={ref}
131
+ ref={navRef}
94
132
  space={2}
95
133
  tokens={{
96
134
  alignItems: direction === 'column' ? 'flex-start' : 'center',
@@ -145,6 +183,7 @@ const NavigationBar = forwardRef(
145
183
  linkRouterProps={{ ...linkRouterProps, ...itemLinkRouterProps }}
146
184
  items={nestedItems}
147
185
  selected={itemId === selectedId}
186
+ itemsContainerRef={itemsRef}
148
187
  {...itemRest}
149
188
  {...(nestedItems && { isOpen })}
150
189
  {...(nestedItems && isOpen && { openOverlayRef })}
@@ -20,7 +20,8 @@ const NavigationSubMenu = ({
20
20
  items = [],
21
21
  openOverlayRef,
22
22
  LinkRouter,
23
- linkRouterProps
23
+ linkRouterProps,
24
+ itemsContainerRef
24
25
  }) => {
25
26
  const focusTrapRef = useRef()
26
27
 
@@ -86,6 +87,7 @@ const NavigationSubMenu = ({
86
87
  selectedId={selectedId}
87
88
  LinkRouter={LinkRouter}
88
89
  linkRouterProps={linkRouterProps}
90
+ ref={itemsContainerRef}
89
91
  />
90
92
  </Listbox.Overlay>
91
93
  <div
@@ -114,7 +116,8 @@ NavigationSubMenu.propTypes = {
114
116
  items: PropTypes.array,
115
117
  openOverlayRef: PropTypes.object,
116
118
  LinkRouter: PropTypes.elementType,
117
- linkRouterProps: PropTypes.object
119
+ linkRouterProps: PropTypes.object,
120
+ itemsContainerRef: PropTypes.object
118
121
  }
119
122
 
120
123
  export default NavigationSubMenu