@telus-uds/components-web 4.16.0 → 4.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,9 +1,22 @@
1
1
  # Change Log - @telus-uds/components-web
2
2
 
3
- This log was last generated on Mon, 12 Jan 2026 14:55:22 GMT and should not be manually modified.
3
+ This log was last generated on Mon, 19 Jan 2026 20:39:51 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
+ ## 4.17.0
8
+
9
+ Mon, 19 Jan 2026 20:39:51 GMT
10
+
11
+ ### Minor changes
12
+
13
+ - `PriceLockup`: Add inverse variant (david.melara1@telus.com)
14
+ - Bump @telus-uds/components-base to v3.26.0
15
+
16
+ ### Patches
17
+
18
+ - `NavigationBar`: Fix flickering from overlay overflow (david.melara1@telus.com)
19
+
7
20
  ## 4.16.0
8
21
 
9
22
  Mon, 12 Jan 2026 14:55:22 GMT
@@ -12,11 +12,52 @@ var _useOverlaidPosition = _interopRequireDefault(require("../utils/useOverlaidP
12
12
  var _resolveItemSelection = _interopRequireDefault(require("./resolveItemSelection"));
13
13
  var _jsxRuntime = require("react/jsx-runtime");
14
14
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
+ const MIN_WIDTH = 192;
16
+ const MAX_WIDTH = 289;
17
+ const DEFAULT_OFFSETS = {
18
+ offsets: {
19
+ vertical: 4
20
+ }
21
+ };
22
+ const XS_ALIGN = {
23
+ top: 'bottom',
24
+ left: 'left'
25
+ };
26
+ const SM_ALIGN = {
27
+ top: 'bottom',
28
+ right: 'right'
29
+ };
30
+ const LG_ALIGN = {
31
+ top: 'bottom',
32
+ center: 'center'
33
+ };
34
+ const getResponsiveBreakpoints = sourceWidth => ({
35
+ xs: {
36
+ ...DEFAULT_OFFSETS,
37
+ align: XS_ALIGN,
38
+ minWidth: sourceWidth,
39
+ maxWidth: sourceWidth
40
+ },
41
+ sm: {
42
+ ...DEFAULT_OFFSETS,
43
+ align: SM_ALIGN,
44
+ minWidth: sourceWidth,
45
+ maxWidth: sourceWidth
46
+ },
47
+ lg: {
48
+ ...DEFAULT_OFFSETS,
49
+ align: LG_ALIGN,
50
+ minWidth: MIN_WIDTH,
51
+ maxWidth: MAX_WIDTH
52
+ }
53
+ });
54
+
15
55
  /**
16
56
  * A NavigationItem that opens or closes a Listbox of other NavigationItems.
17
57
  *
18
58
  * This is rendered automatically by `NavigationBar` and isn't intended be used directly.
19
- */const NavigationSubMenu = /*#__PURE__*/_react.default.forwardRef((_ref, ref) => {
59
+ */
60
+ const NavigationSubMenu = /*#__PURE__*/_react.default.forwardRef((_ref, ref) => {
20
61
  let {
21
62
  children,
22
63
  id,
@@ -33,42 +74,13 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
33
74
  itemsContainerRef
34
75
  } = _ref;
35
76
  const focusTrapRef = _react.default.useRef();
36
- const maxWidth = 289; // Slightly over 288 of nav item to account for subpixel rounding
37
- const defaultOffsets = {
38
- offsets: {
39
- vertical: 4
40
- }
41
- };
77
+ const [sourceWidth, setSourceWidth] = _react.default.useState(0);
42
78
  const {
43
79
  align,
44
80
  offsets,
45
- minWidth
46
- } = (0, _componentsBase.useResponsiveProp)({
47
- xs: {
48
- ...defaultOffsets,
49
- align: {
50
- top: 'bottom',
51
- left: 'left'
52
- },
53
- minWidth: maxWidth
54
- },
55
- sm: {
56
- ...defaultOffsets,
57
- align: {
58
- top: 'bottom',
59
- right: 'right'
60
- },
61
- minWidth: maxWidth
62
- },
63
- lg: {
64
- ...defaultOffsets,
65
- align: {
66
- top: 'bottom',
67
- center: 'center'
68
- },
69
- minWidth: 192
70
- }
71
- });
81
+ minWidth,
82
+ maxWidth
83
+ } = (0, _componentsBase.useResponsiveProp)(getResponsiveBreakpoints(sourceWidth));
72
84
  const {
73
85
  overlaidPosition,
74
86
  sourceRef,
@@ -95,6 +107,11 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
95
107
  } = (0, _componentsBase.useThemeTokens)('NavigationBar', tokens, {}, {
96
108
  expanded: isOpen
97
109
  });
110
+ _react.default.useEffect(() => {
111
+ sourceRef.current?.measureInWindow((_, __, width) => {
112
+ setSourceWidth(width);
113
+ });
114
+ }, [isOpen, sourceRef]);
98
115
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
99
116
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_NavigationItem.default, {
100
117
  ref: sourceRef,
@@ -111,6 +111,13 @@ function getOverlaidPosition(_ref2) {
111
111
  positioning[side] = adjusted.offset;
112
112
  }
113
113
  }
114
+ if (positioning.left) {
115
+ const overflowAmount = positioning.left + targetDimensions.width - windowDimensions.width;
116
+ if (overflowAmount > 0) {
117
+ const spaceToRightEdge = windowDimensions.width - (sourceLayout.x + sourceLayout.width);
118
+ positioning.left = positioning.left - overflowAmount - spaceToRightEdge;
119
+ }
120
+ }
114
121
  return positioning;
115
122
  }
116
123
 
@@ -210,7 +217,10 @@ const useOverlaidPosition = _ref3 => {
210
217
  windowDimensions,
211
218
  offsets,
212
219
  align
213
- }) : {};
220
+ }) : {
221
+ top: 0,
222
+ left: 0
223
+ };
214
224
  return {
215
225
  overlaidPosition,
216
226
  sourceRef,
@@ -4,13 +4,52 @@ import { Icon, useResponsiveProp, useThemeTokens, Listbox, getTokensPropType } f
4
4
  import NavigationItem from './NavigationItem';
5
5
  import useOverlaidPosition from '../utils/useOverlaidPosition';
6
6
  import resolveItemSelection from './resolveItemSelection';
7
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
8
+ const MIN_WIDTH = 192;
9
+ const MAX_WIDTH = 289;
10
+ const DEFAULT_OFFSETS = {
11
+ offsets: {
12
+ vertical: 4
13
+ }
14
+ };
15
+ const XS_ALIGN = {
16
+ top: 'bottom',
17
+ left: 'left'
18
+ };
19
+ const SM_ALIGN = {
20
+ top: 'bottom',
21
+ right: 'right'
22
+ };
23
+ const LG_ALIGN = {
24
+ top: 'bottom',
25
+ center: 'center'
26
+ };
27
+ const getResponsiveBreakpoints = sourceWidth => ({
28
+ xs: {
29
+ ...DEFAULT_OFFSETS,
30
+ align: XS_ALIGN,
31
+ minWidth: sourceWidth,
32
+ maxWidth: sourceWidth
33
+ },
34
+ sm: {
35
+ ...DEFAULT_OFFSETS,
36
+ align: SM_ALIGN,
37
+ minWidth: sourceWidth,
38
+ maxWidth: sourceWidth
39
+ },
40
+ lg: {
41
+ ...DEFAULT_OFFSETS,
42
+ align: LG_ALIGN,
43
+ minWidth: MIN_WIDTH,
44
+ maxWidth: MAX_WIDTH
45
+ }
46
+ });
7
47
 
8
48
  /**
9
49
  * A NavigationItem that opens or closes a Listbox of other NavigationItems.
10
50
  *
11
51
  * This is rendered automatically by `NavigationBar` and isn't intended be used directly.
12
52
  */
13
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
14
53
  const NavigationSubMenu = /*#__PURE__*/React.forwardRef((_ref, ref) => {
15
54
  let {
16
55
  children,
@@ -28,42 +67,13 @@ const NavigationSubMenu = /*#__PURE__*/React.forwardRef((_ref, ref) => {
28
67
  itemsContainerRef
29
68
  } = _ref;
30
69
  const focusTrapRef = React.useRef();
31
- const maxWidth = 289; // Slightly over 288 of nav item to account for subpixel rounding
32
- const defaultOffsets = {
33
- offsets: {
34
- vertical: 4
35
- }
36
- };
70
+ const [sourceWidth, setSourceWidth] = React.useState(0);
37
71
  const {
38
72
  align,
39
73
  offsets,
40
- minWidth
41
- } = useResponsiveProp({
42
- xs: {
43
- ...defaultOffsets,
44
- align: {
45
- top: 'bottom',
46
- left: 'left'
47
- },
48
- minWidth: maxWidth
49
- },
50
- sm: {
51
- ...defaultOffsets,
52
- align: {
53
- top: 'bottom',
54
- right: 'right'
55
- },
56
- minWidth: maxWidth
57
- },
58
- lg: {
59
- ...defaultOffsets,
60
- align: {
61
- top: 'bottom',
62
- center: 'center'
63
- },
64
- minWidth: 192
65
- }
66
- });
74
+ minWidth,
75
+ maxWidth
76
+ } = useResponsiveProp(getResponsiveBreakpoints(sourceWidth));
67
77
  const {
68
78
  overlaidPosition,
69
79
  sourceRef,
@@ -90,6 +100,11 @@ const NavigationSubMenu = /*#__PURE__*/React.forwardRef((_ref, ref) => {
90
100
  } = useThemeTokens('NavigationBar', tokens, {}, {
91
101
  expanded: isOpen
92
102
  });
103
+ React.useEffect(() => {
104
+ sourceRef.current?.measureInWindow((_, __, width) => {
105
+ setSourceWidth(width);
106
+ });
107
+ }, [isOpen, sourceRef]);
93
108
  return /*#__PURE__*/_jsxs(_Fragment, {
94
109
  children: [/*#__PURE__*/_jsx(NavigationItem, {
95
110
  ref: sourceRef,
@@ -103,6 +103,13 @@ function getOverlaidPosition(_ref2) {
103
103
  positioning[side] = adjusted.offset;
104
104
  }
105
105
  }
106
+ if (positioning.left) {
107
+ const overflowAmount = positioning.left + targetDimensions.width - windowDimensions.width;
108
+ if (overflowAmount > 0) {
109
+ const spaceToRightEdge = windowDimensions.width - (sourceLayout.x + sourceLayout.width);
110
+ positioning.left = positioning.left - overflowAmount - spaceToRightEdge;
111
+ }
112
+ }
106
113
  return positioning;
107
114
  }
108
115
 
@@ -202,7 +209,10 @@ const useOverlaidPosition = _ref3 => {
202
209
  windowDimensions,
203
210
  offsets,
204
211
  align
205
- }) : {};
212
+ }) : {
213
+ top: 0,
214
+ left: 0
215
+ };
206
216
  return {
207
217
  overlaidPosition,
208
218
  sourceRef,
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": "^3.25.0",
8
+ "@telus-uds/components-base": "^3.26.0",
9
9
  "@telus-uds/system-constants": "^3.0.0",
10
10
  "@telus-uds/system-theme-tokens": "^4.18.0",
11
11
  "fscreen": "^1.2.0",
@@ -82,5 +82,5 @@
82
82
  "skip": true
83
83
  },
84
84
  "types": "types/index.d.ts",
85
- "version": "4.16.0"
85
+ "version": "4.17.0"
86
86
  }
@@ -11,6 +11,34 @@ import NavigationItem from './NavigationItem'
11
11
  import useOverlaidPosition from '../utils/useOverlaidPosition'
12
12
  import resolveItemSelection from './resolveItemSelection'
13
13
 
14
+ const MIN_WIDTH = 192
15
+ const MAX_WIDTH = 289
16
+ const DEFAULT_OFFSETS = { offsets: { vertical: 4 } }
17
+ const XS_ALIGN = { top: 'bottom', left: 'left' }
18
+ const SM_ALIGN = { top: 'bottom', right: 'right' }
19
+ const LG_ALIGN = { top: 'bottom', center: 'center' }
20
+
21
+ const getResponsiveBreakpoints = (sourceWidth) => ({
22
+ xs: {
23
+ ...DEFAULT_OFFSETS,
24
+ align: XS_ALIGN,
25
+ minWidth: sourceWidth,
26
+ maxWidth: sourceWidth
27
+ },
28
+ sm: {
29
+ ...DEFAULT_OFFSETS,
30
+ align: SM_ALIGN,
31
+ minWidth: sourceWidth,
32
+ maxWidth: sourceWidth
33
+ },
34
+ lg: {
35
+ ...DEFAULT_OFFSETS,
36
+ align: LG_ALIGN,
37
+ minWidth: MIN_WIDTH,
38
+ maxWidth: MAX_WIDTH
39
+ }
40
+ })
41
+
14
42
  /**
15
43
  * A NavigationItem that opens or closes a Listbox of other NavigationItems.
16
44
  *
@@ -36,18 +64,11 @@ const NavigationSubMenu = React.forwardRef(
36
64
  ref
37
65
  ) => {
38
66
  const focusTrapRef = React.useRef()
67
+ const [sourceWidth, setSourceWidth] = React.useState(0)
39
68
 
40
- const maxWidth = 289 // Slightly over 288 of nav item to account for subpixel rounding
41
- const defaultOffsets = { offsets: { vertical: 4 } }
42
- const { align, offsets, minWidth } = useResponsiveProp({
43
- xs: { ...defaultOffsets, align: { top: 'bottom', left: 'left' }, minWidth: maxWidth },
44
- sm: { ...defaultOffsets, align: { top: 'bottom', right: 'right' }, minWidth: maxWidth },
45
- lg: {
46
- ...defaultOffsets,
47
- align: { top: 'bottom', center: 'center' },
48
- minWidth: 192
49
- }
50
- })
69
+ const { align, offsets, minWidth, maxWidth } = useResponsiveProp(
70
+ getResponsiveBreakpoints(sourceWidth)
71
+ )
51
72
 
52
73
  const { overlaidPosition, sourceRef, targetRef, onTargetLayout, isReady } = useOverlaidPosition(
53
74
  {
@@ -64,6 +85,12 @@ const NavigationSubMenu = React.forwardRef(
64
85
 
65
86
  const { icoMenu } = useThemeTokens('NavigationBar', tokens, {}, { expanded: isOpen })
66
87
 
88
+ React.useEffect(() => {
89
+ sourceRef.current?.measureInWindow((_, __, width) => {
90
+ setSourceWidth(width)
91
+ })
92
+ }, [isOpen, sourceRef])
93
+
67
94
  return (
68
95
  <>
69
96
  <NavigationItem
@@ -118,6 +118,14 @@ function getOverlaidPosition({
118
118
  }
119
119
  }
120
120
 
121
+ if (positioning.left) {
122
+ const overflowAmount = positioning.left + targetDimensions.width - windowDimensions.width
123
+ if (overflowAmount > 0) {
124
+ const spaceToRightEdge = windowDimensions.width - (sourceLayout.x + sourceLayout.width)
125
+ positioning.left = positioning.left - overflowAmount - spaceToRightEdge
126
+ }
127
+ }
128
+
121
129
  return positioning
122
130
  }
123
131
 
@@ -212,7 +220,7 @@ const useOverlaidPosition = ({
212
220
  offsets,
213
221
  align
214
222
  })
215
- : {}
223
+ : { top: 0, left: 0 }
216
224
 
217
225
  return {
218
226
  overlaidPosition,