@canonical/react-components 0.47.4 → 0.48.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/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # React components for Vanilla Framework
2
- ![CI](https://github.com/canonical/react-components/workflows/CI/badge.svg?branch=main) ![Cypress chrome headless](https://github.com/canonical/react-components/workflows/Cypress%20chrome%20headless/badge.svg)
2
+ ![CI](https://github.com/canonical/react-components/workflows/CI/badge.svg?branch=main)
3
3
 
4
4
  This is a collection of components designed to be the way to consume [Vanilla Framework](http://vanillaframework.io) when using React.
5
5
 
@@ -30,7 +30,7 @@ let Label = exports.Label = /*#__PURE__*/function (Label) {
30
30
  * @param positionCoords - The coordinates of the position node.
31
31
  * @param constrainPanelWidth - Whether the menu width should be constrained to the position width.
32
32
  */
33
- const getPositionStyle = (position, positionCoords, constrainPanelWidth) => {
33
+ const getPositionStyle = (position, verticalPosition, positionCoords, constrainPanelWidth) => {
34
34
  if (!positionCoords) {
35
35
  return null;
36
36
  }
@@ -40,7 +40,7 @@ const getPositionStyle = (position, positionCoords, constrainPanelWidth) => {
40
40
  top,
41
41
  width
42
42
  } = positionCoords;
43
- const topPos = top + height + (window.scrollY || 0);
43
+ const topPos = verticalPosition === "bottom" ? top + height + (window.scrollY || 0) : top + (window.scrollY || 0);
44
44
  let leftPos = left;
45
45
  switch (position) {
46
46
  case "left":
@@ -120,6 +120,20 @@ const generateLink = (link, key, handleClose) => {
120
120
  } : null
121
121
  }, props), children);
122
122
  };
123
+ const getClosestScrollableParent = node => {
124
+ let currentNode = node;
125
+ while (currentNode && currentNode !== document.body) {
126
+ const {
127
+ overflowY,
128
+ overflowX
129
+ } = window.getComputedStyle(currentNode);
130
+ if (["auto", "scroll", "overlay"].includes(overflowY) && ["auto", "scroll", "overlay"].includes(overflowX)) {
131
+ return currentNode;
132
+ }
133
+ currentNode = currentNode.parentElement;
134
+ }
135
+ return document.body;
136
+ };
123
137
  const ContextualMenuDropdown = _ref => {
124
138
  let {
125
139
  adjustedPosition,
@@ -140,23 +154,46 @@ const ContextualMenuDropdown = _ref => {
140
154
  ...props
141
155
  } = _ref;
142
156
  const dropdown = (0, _react.useRef)();
143
- const [positionStyle, setPositionStyle] = (0, _react.useState)(getPositionStyle(adjustedPosition, positionCoords, constrainPanelWidth));
157
+ const [verticalPosition, setVerticalPosition] = (0, _react.useState)("bottom");
158
+ const [positionStyle, setPositionStyle] = (0, _react.useState)(getPositionStyle(adjustedPosition, verticalPosition, positionCoords, constrainPanelWidth));
144
159
  const [maxHeight, setMaxHeight] = (0, _react.useState)();
145
-
146
160
  // Update the styles to position the menu.
147
161
  const updatePositionStyle = (0, _react.useCallback)(() => {
148
- setPositionStyle(getPositionStyle(adjustedPosition, positionCoords, constrainPanelWidth));
149
- }, [adjustedPosition, positionCoords, constrainPanelWidth]);
162
+ setPositionStyle(getPositionStyle(adjustedPosition, verticalPosition, positionCoords, constrainPanelWidth));
163
+ }, [adjustedPosition, positionCoords, verticalPosition, constrainPanelWidth]);
164
+ const updateVerticalPosition = (0, _react.useCallback)(() => {
165
+ if (!positionNode) {
166
+ return null;
167
+ }
168
+ const scrollableParent = getClosestScrollableParent(positionNode);
169
+ if (!scrollableParent) {
170
+ return null;
171
+ }
172
+ const scrollableParentRect = scrollableParent.getBoundingClientRect();
173
+ const rect = positionNode.getBoundingClientRect();
174
+
175
+ // Calculate the rect in relation to the scrollableParent
176
+ const relativeRect = {
177
+ top: rect.top - scrollableParentRect.top,
178
+ bottom: rect.bottom - scrollableParentRect.top,
179
+ height: rect.height
180
+ };
181
+ const spaceBelow = scrollableParentRect.height - relativeRect.bottom;
182
+ const spaceAbove = relativeRect.top;
183
+ const dropdownHeight = relativeRect.height;
184
+ setVerticalPosition(spaceBelow >= dropdownHeight || spaceBelow > spaceAbove ? "bottom" : "top");
185
+ }, [positionNode]);
150
186
 
151
187
  // Update the position when the window fitment info changes.
152
188
  const onUpdateWindowFitment = (0, _react.useCallback)(fitsWindow => {
153
189
  if (autoAdjust) {
154
190
  setAdjustedPosition(adjustForWindow(position, fitsWindow));
191
+ updateVerticalPosition();
155
192
  }
156
193
  if (scrollOverflow) {
157
194
  setMaxHeight(fitsWindow.fromBottom.spaceBelow - 16);
158
195
  }
159
- }, [autoAdjust, position, scrollOverflow, setAdjustedPosition]);
196
+ }, [autoAdjust, position, scrollOverflow, setAdjustedPosition, updateVerticalPosition]);
160
197
 
161
198
  // Handle adjusting the horizontal position and scrolling of the dropdown so that it remains on screen.
162
199
  (0, _hooks.useWindowFitment)(dropdown.current, positionNode, onUpdateWindowFitment, 0, isOpen && (autoAdjust || scrollOverflow));
@@ -165,6 +202,9 @@ const ContextualMenuDropdown = _ref => {
165
202
  (0, _react.useEffect)(() => {
166
203
  updatePositionStyle();
167
204
  }, [adjustedPosition, updatePositionStyle]);
205
+ (0, _react.useEffect)(() => {
206
+ updateVerticalPosition();
207
+ }, [updateVerticalPosition]);
168
208
  return (
169
209
  /*#__PURE__*/
170
210
  // Vanilla Framework uses .p-contextual-menu parent modifier classnames to determine the correct position of the .p-contextual-menu__dropdown dropdown (left, center, right).
@@ -188,6 +228,9 @@ const ContextualMenuDropdown = _ref => {
188
228
  maxHeight,
189
229
  minHeight: "2rem",
190
230
  overflowX: "auto"
231
+ } : {}),
232
+ ...(verticalPosition === "top" ? {
233
+ bottom: "0"
191
234
  } : {})
192
235
  }
193
236
  }, props), dropdownContent ? typeof dropdownContent === "function" ? dropdownContent(handleClose) : dropdownContent : links.map((item, i) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonical/react-components",
3
- "version": "0.47.4",
3
+ "version": "0.48.0",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "author": "Huw Wilkins <huw.wilkins@canonical.com>",
@@ -26,7 +26,7 @@
26
26
  "@babel/eslint-parser": "7.23.3",
27
27
  "@babel/preset-typescript": "7.23.3",
28
28
  "@percy/cli": "1.27.6",
29
- "@percy/storybook": "4.3.7",
29
+ "@percy/storybook": "5.0.1",
30
30
  "@semantic-release/changelog": "6.0.3",
31
31
  "@semantic-release/git": "10.0.1",
32
32
  "@storybook/addon-a11y": "7.6.7",
@@ -38,64 +38,65 @@
38
38
  "@storybook/blocks": "7.6.7",
39
39
  "@storybook/react": "7.6.7",
40
40
  "@storybook/react-webpack5": "7.6.7",
41
- "@testing-library/cypress": "9.0.0",
41
+ "@testing-library/cypress": "10.0.1",
42
42
  "@testing-library/dom": "9.3.3",
43
- "@testing-library/jest-dom": "5.17.0",
43
+ "@testing-library/jest-dom": "6.2.1",
44
44
  "@testing-library/react": "14.1.2",
45
45
  "@testing-library/user-event": "14.5.2",
46
- "@typescript-eslint/eslint-plugin": "5.62.0",
47
- "@typescript-eslint/parser": "5.62.0",
48
- "babel-jest": "27.5.1",
46
+ "@typescript-eslint/eslint-plugin": "6.19.1",
47
+ "@typescript-eslint/parser": "6.19.1",
48
+ "babel-jest": "29.7.0",
49
49
  "babel-loader": "9.1.3",
50
50
  "babel-plugin-module-resolver": "5.0.0",
51
51
  "babel-plugin-typescript-to-proptypes": "2.1.0",
52
52
  "concurrently": "8.2.2",
53
53
  "css-loader": "6.8.1",
54
- "cypress": "12.17.4",
54
+ "cypress": "13.6.3",
55
55
  "deepmerge": "4.3.1",
56
56
  "eslint": "8.56.0",
57
- "eslint-config-prettier": "8.10.0",
57
+ "eslint-config-prettier": "9.1.0",
58
58
  "eslint-config-react-app": "7.0.1",
59
59
  "eslint-plugin-cypress": "2.15.1",
60
60
  "eslint-plugin-flowtype": "8.0.3",
61
61
  "eslint-plugin-import": "2.29.1",
62
62
  "eslint-plugin-jsx-a11y": "6.8.0",
63
- "eslint-plugin-prettier": "4.2.1",
63
+ "eslint-plugin-prettier": "5.1.3",
64
64
  "eslint-plugin-react": "7.33.2",
65
65
  "eslint-plugin-react-hooks": "4.6.0",
66
66
  "eslint-plugin-storybook": "0.6.15",
67
- "eslint-plugin-testing-library": "5.11.1",
68
- "jest": "27.5.1",
69
- "npm-package-json-lint": "5.4.2",
70
- "prettier": "2.8.8",
67
+ "eslint-plugin-testing-library": "6.2.0",
68
+ "jest": "29.7.0",
69
+ "npm-package-json-lint": "7.1.0",
70
+ "prettier": "3.2.4",
71
71
  "react": "18.2.0",
72
72
  "react-docgen-typescript-loader": "3.7.2",
73
73
  "react-dom": "18.2.0",
74
74
  "sass": "1.69.7",
75
- "sass-loader": "10.5.1",
75
+ "sass-loader": "14.0.0",
76
76
  "semantic-release": "23.0.0",
77
77
  "storybook": "7.6.7",
78
78
  "style-loader": "3.3.3",
79
- "stylelint": "15.11.0",
79
+ "stylelint": "16.2.0",
80
80
  "stylelint-config-prettier": "9.0.5",
81
- "stylelint-config-recommended-scss": "5.0.2",
82
- "stylelint-order": "5.0.0",
83
- "stylelint-prettier": "2.0.0",
84
- "ts-jest": "27.1.5",
81
+ "stylelint-config-recommended-scss": "14.0.0",
82
+ "stylelint-order": "6.0.4",
83
+ "stylelint-prettier": "5.0.0",
84
+ "ts-jest": "29.1.2",
85
85
  "tsc-alias": "1.8.8",
86
- "typescript": "4.9.5",
86
+ "typescript": "5.3.3",
87
87
  "vanilla-framework": "4.6.0",
88
- "wait-on": "5.3.0",
88
+ "wait-on": "7.2.0",
89
89
  "webpack": "5.89.0"
90
90
  },
91
91
  "dependencies": {
92
- "@types/jest": "27.5.2",
92
+ "@types/jest": "29.5.11",
93
93
  "@types/node": "18.19.4",
94
94
  "@types/react": "18.2.46",
95
95
  "@types/react-dom": "18.2.18",
96
96
  "@types/react-table": "7.7.19",
97
97
  "classnames": "2.5.1",
98
- "nanoid": "3.3.7",
98
+ "jest-environment-jsdom": "29.7.0",
99
+ "nanoid": "5.0.4",
99
100
  "prop-types": "15.8.1",
100
101
  "react-table": "7.8.0",
101
102
  "react-useportal": "1.0.19"
@@ -104,10 +105,7 @@
104
105
  "@types/react": "18.2.46",
105
106
  "@types/react-dom": "18.2.18",
106
107
  "postcss": "^8.3.11",
107
- "string-width": "^4",
108
- "jackspeak": "^2",
109
- "strip-ansi": "^6.0.0",
110
- "strip-ansi-cjs": "^8.0.0"
108
+ "jackspeak": "2.1.1"
111
109
  },
112
110
  "peerDependencies": {
113
111
  "@types/react": "^17.0.2 || ^18.0.0",