@canonical/react-components 2.13.0 → 2.15.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.
Files changed (75) hide show
  1. package/dist/components/NotificationProvider/index.d.ts +1 -1
  2. package/dist/components/NotificationProvider/index.js +6 -0
  3. package/dist/components/NotificationProvider/messageBuilder.d.ts +5 -3
  4. package/dist/components/NotificationProvider/messageBuilder.js +24 -5
  5. package/dist/components/Notifications/ToastNotification/ToastNotification.stories.js +2 -0
  6. package/dist/components/Notifications/ToastNotification/ToastNotificationProvider.d.ts +4 -4
  7. package/dist/components/Notifications/ToastNotification/ToastNotificationProvider.js +15 -18
  8. package/dist/components/ScrollableContainer/ScrollableContainer.d.ts +27 -0
  9. package/dist/components/ScrollableContainer/ScrollableContainer.js +51 -0
  10. package/dist/components/ScrollableContainer/ScrollableContainer.scss +7 -0
  11. package/dist/components/ScrollableContainer/ScrollableContainer.stories.d.ts +6 -0
  12. package/dist/components/ScrollableContainer/ScrollableContainer.stories.js +36 -0
  13. package/dist/components/ScrollableContainer/ScrollableContainer.test.d.ts +1 -0
  14. package/dist/components/ScrollableContainer/index.d.ts +2 -0
  15. package/dist/components/ScrollableContainer/index.js +13 -0
  16. package/dist/components/ScrollableTable/ScrollableTable.d.ts +27 -0
  17. package/dist/components/ScrollableTable/ScrollableTable.js +44 -0
  18. package/dist/components/ScrollableTable/ScrollableTable.scss +58 -0
  19. package/dist/components/ScrollableTable/ScrollableTable.stories.d.ts +6 -0
  20. package/dist/components/ScrollableTable/ScrollableTable.stories.js +69 -0
  21. package/dist/components/ScrollableTable/ScrollableTable.test.d.ts +1 -0
  22. package/dist/components/ScrollableTable/index.d.ts +2 -0
  23. package/dist/components/ScrollableTable/index.js +13 -0
  24. package/dist/components/SidePanel/SidePanel.js +1 -1
  25. package/dist/components/TablePagination/TablePagination.scss +6 -6
  26. package/dist/components/TablePagination/TablePagination.stories.d.ts +8 -0
  27. package/dist/components/TablePagination/TablePagination.stories.js +53 -1
  28. package/dist/components/TablePagination/TablePaginationControls/TablePaginationControls.js +7 -4
  29. package/dist/components/TablePagination/utils.d.ts +2 -1
  30. package/dist/components/TablePagination/utils.js +10 -3
  31. package/dist/esm/components/NotificationProvider/index.d.ts +1 -1
  32. package/dist/esm/components/NotificationProvider/index.js +1 -1
  33. package/dist/esm/components/NotificationProvider/messageBuilder.d.ts +5 -3
  34. package/dist/esm/components/NotificationProvider/messageBuilder.js +20 -3
  35. package/dist/esm/components/Notifications/ToastNotification/ToastNotification.stories.js +2 -0
  36. package/dist/esm/components/Notifications/ToastNotification/ToastNotificationProvider.d.ts +4 -4
  37. package/dist/esm/components/Notifications/ToastNotification/ToastNotificationProvider.js +15 -22
  38. package/dist/esm/components/ScrollableContainer/ScrollableContainer.d.ts +27 -0
  39. package/dist/esm/components/ScrollableContainer/ScrollableContainer.js +43 -0
  40. package/dist/esm/components/ScrollableContainer/ScrollableContainer.scss +7 -0
  41. package/dist/esm/components/ScrollableContainer/ScrollableContainer.stories.d.ts +6 -0
  42. package/dist/esm/components/ScrollableContainer/ScrollableContainer.stories.js +29 -0
  43. package/dist/esm/components/ScrollableContainer/ScrollableContainer.test.d.ts +1 -0
  44. package/dist/esm/components/ScrollableContainer/index.d.ts +2 -0
  45. package/dist/esm/components/ScrollableContainer/index.js +1 -0
  46. package/dist/esm/components/ScrollableTable/ScrollableTable.d.ts +27 -0
  47. package/dist/esm/components/ScrollableTable/ScrollableTable.js +37 -0
  48. package/dist/esm/components/ScrollableTable/ScrollableTable.scss +58 -0
  49. package/dist/esm/components/ScrollableTable/ScrollableTable.stories.d.ts +6 -0
  50. package/dist/esm/components/ScrollableTable/ScrollableTable.stories.js +62 -0
  51. package/dist/esm/components/ScrollableTable/ScrollableTable.test.d.ts +1 -0
  52. package/dist/esm/components/ScrollableTable/index.d.ts +2 -0
  53. package/dist/esm/components/ScrollableTable/index.js +1 -0
  54. package/dist/esm/components/SidePanel/SidePanel.js +1 -1
  55. package/dist/esm/components/TablePagination/TablePagination.scss +6 -6
  56. package/dist/esm/components/TablePagination/TablePagination.stories.d.ts +8 -0
  57. package/dist/esm/components/TablePagination/TablePagination.stories.js +52 -0
  58. package/dist/esm/components/TablePagination/TablePaginationControls/TablePaginationControls.js +7 -4
  59. package/dist/esm/components/TablePagination/utils.d.ts +2 -1
  60. package/dist/esm/components/TablePagination/utils.js +10 -3
  61. package/dist/esm/hooks/useId.d.ts +1 -1
  62. package/dist/esm/hooks/useId.js +1 -1
  63. package/dist/esm/hooks/useListener.js +8 -7
  64. package/dist/esm/index.d.ts +5 -1
  65. package/dist/esm/index.js +3 -1
  66. package/dist/esm/utils.d.ts +4 -0
  67. package/dist/esm/utils.js +32 -1
  68. package/dist/hooks/useId.d.ts +1 -1
  69. package/dist/hooks/useId.js +1 -1
  70. package/dist/hooks/useListener.js +8 -7
  71. package/dist/index.d.ts +5 -1
  72. package/dist/index.js +37 -0
  73. package/dist/utils.d.ts +4 -0
  74. package/dist/utils.js +37 -2
  75. package/package.json +2 -2
@@ -26,7 +26,7 @@ The sidepanel component should be used to show additional information relating t
26
26
 
27
27
  * **SidePanel.HeaderControls:** To show controls in the header, such as buttons or icons for actions like closing the panel.
28
28
 
29
- * **SidePanel.Sticky:** Can be wrapped around the header or footer to make them sticky when scrolling.
29
+ * **SidePanel.Sticky:** Can be wrapped around the header or footer to make them sticky when scrolling. The scrollbar will use the full area of the side panel, not just the content area. To limit the scrollbar to the content area, use the `ScrollableContainer` component instead of this one.
30
30
 
31
31
  * **SidePanel.Content:** To show the main content of the side panel.
32
32
 
@@ -26,7 +26,7 @@
26
26
  }
27
27
 
28
28
  .next {
29
- margin: 0 $spv--large;
29
+ margin: 0 $spv--large 0 0;
30
30
 
31
31
  .p-icon--chevron-down {
32
32
  rotate: 270deg;
@@ -34,11 +34,15 @@
34
34
  }
35
35
 
36
36
  .pagination-input {
37
- margin: 0 $spv--small 0 $spv--large;
37
+ margin: 0 $spv--large;
38
38
  min-width: 0;
39
39
  width: 4rem;
40
40
  }
41
41
 
42
+ .pagination-item-count {
43
+ margin: 0 $spv--large 0 0;
44
+ }
45
+
42
46
  .pagination-select {
43
47
  margin-bottom: 0;
44
48
  margin-left: $spv--x-large;
@@ -48,10 +52,6 @@
48
52
 
49
53
  @media screen and (max-width: $breakpoint-small) {
50
54
  .back,
51
- .pagination-input {
52
- margin-left: 0;
53
- }
54
-
55
55
  .next {
56
56
  margin-left: 0;
57
57
  margin-right: 0;
@@ -12,3 +12,11 @@ export declare const RenderBelow: Story;
12
12
  * using the `TablePaginationControls` component.
13
13
  */
14
14
  export declare const ControlsOnly: Story;
15
+ /** The `TablePaginationControls` component can be used when the total
16
+ * number of entries is unknown.
17
+ */
18
+ export declare const ControlsWithUnknownEntries: Story;
19
+ /** The `TablePaginationControls` component can be used when it is known
20
+ * that there are more entries than the amount displayed on the current page.
21
+ */
22
+ export declare const ControlsWithPartiallyKnownEntries: Story;
@@ -320,4 +320,56 @@ export var ControlsOnly = {
320
320
  visibleCount: 10
321
321
  });
322
322
  }
323
+ };
324
+
325
+ /** The `TablePaginationControls` component can be used when the total
326
+ * number of entries is unknown.
327
+ */
328
+ export var ControlsWithUnknownEntries = {
329
+ render: () => {
330
+ return /*#__PURE__*/React.createElement(TablePaginationControls, {
331
+ currentPage: 1,
332
+ itemName: "row",
333
+ nextButtonProps: {
334
+ disabled: false
335
+ },
336
+ onInputPageChange: console.log,
337
+ onNextPage: console.log,
338
+ onPageSizeChange: console.log,
339
+ onPreviousPage: console.log,
340
+ pageLimits: [10, 25, 50],
341
+ pageSize: 20,
342
+ previousButtonProps: {
343
+ disabled: false
344
+ },
345
+ showPageInput: true,
346
+ visibleCount: 10
347
+ });
348
+ }
349
+ };
350
+
351
+ /** The `TablePaginationControls` component can be used when it is known
352
+ * that there are more entries than the amount displayed on the current page.
353
+ */
354
+ export var ControlsWithPartiallyKnownEntries = {
355
+ render: () => {
356
+ return /*#__PURE__*/React.createElement(TablePaginationControls, {
357
+ currentPage: 3,
358
+ itemName: "row",
359
+ nextButtonProps: {
360
+ disabled: false
361
+ },
362
+ onInputPageChange: console.log,
363
+ onNextPage: console.log,
364
+ onPageSizeChange: console.log,
365
+ onPreviousPage: console.log,
366
+ pageLimits: [10, 25, 50],
367
+ pageSize: 20,
368
+ previousButtonProps: {
369
+ disabled: false
370
+ },
371
+ showPageInput: true,
372
+ visibleCount: 10
373
+ });
374
+ }
323
375
  };
@@ -23,7 +23,7 @@ var TablePaginationControls = _ref => {
23
23
  description,
24
24
  displayDescription = true,
25
25
  onInputPageChange,
26
- itemName,
26
+ itemName = "row",
27
27
  nextButtonProps,
28
28
  onNextPage,
29
29
  onPageChange,
@@ -44,7 +44,8 @@ var TablePaginationControls = _ref => {
44
44
  visibleCount,
45
45
  isSmallScreen,
46
46
  totalItems,
47
- itemName
47
+ itemName,
48
+ currentPage
48
49
  });
49
50
  var handleDecrementPage = currentPage => {
50
51
  if (currentPage > 1) {
@@ -66,7 +67,7 @@ var TablePaginationControls = _ref => {
66
67
  var handlePageSizeChange = e => {
67
68
  onPageSizeChange(parseInt(e.target.value));
68
69
  };
69
- var isInputDisabled = !totalPages || totalPages == 1;
70
+ var isInputDisabled = typeof totalItems === "number" && (!totalPages || totalPages == 1);
70
71
  var maxPageValue = typeof totalPages === "number" ? totalPages : 1;
71
72
  return /*#__PURE__*/React.createElement("div", _extends({
72
73
  className: classnames("pagination", className)
@@ -95,7 +96,9 @@ var TablePaginationControls = _ref => {
95
96
  disabled: isInputDisabled,
96
97
  min: 1,
97
98
  max: maxPageValue
98
- }), " ", typeof totalPages === "number" ? /*#__PURE__*/React.createElement(React.Fragment, null, "of\xA0", totalPages) : null) : null, /*#__PURE__*/React.createElement(Button, _extends({
99
+ }), " ", typeof totalPages === "number" ? /*#__PURE__*/React.createElement("div", {
100
+ className: "pagination-item-count"
101
+ }, "of\xA0", totalPages) : null) : null, /*#__PURE__*/React.createElement(Button, _extends({
99
102
  "aria-label": Label.NEXT_PAGE,
100
103
  className: "next",
101
104
  appearance: "base",
@@ -17,11 +17,12 @@ export declare const generatePagingOptions: (pageLimits: number[]) => {
17
17
  value: number;
18
18
  label: string;
19
19
  }[];
20
- export declare const getDescription: ({ description, isSmallScreen, totalItems, itemName, visibleCount, }: {
20
+ export declare const getDescription: ({ description, isSmallScreen, totalItems, itemName, visibleCount, currentPage, }: {
21
21
  description: ReactNode;
22
22
  isSmallScreen: boolean;
23
23
  totalItems: number;
24
24
  itemName: string;
25
25
  visibleCount: number;
26
+ currentPage: number;
26
27
  }) => string | number | bigint | true | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode>>;
27
28
  export declare const useFigureSmallScreen: () => boolean;
@@ -39,18 +39,25 @@ export var getDescription = _ref => {
39
39
  isSmallScreen,
40
40
  totalItems,
41
41
  itemName,
42
- visibleCount
42
+ visibleCount,
43
+ currentPage
43
44
  } = _ref;
44
45
  if (description) {
45
46
  return description;
46
47
  }
48
+ var closing = "";
49
+ if (typeof totalItems === "number") {
50
+ closing = " out of ".concat(totalItems);
51
+ } else if (currentPage !== 1) {
52
+ closing = " of more than ".concat(visibleCount);
53
+ }
47
54
  if (isSmallScreen) {
48
- return "".concat(visibleCount, " out of ").concat(totalItems);
55
+ return "".concat(visibleCount).concat(closing);
49
56
  }
50
57
  if (visibleCount === totalItems && visibleCount > 1) {
51
58
  return "Showing all ".concat(totalItems, " ").concat(itemName, "s");
52
59
  }
53
- return "Showing ".concat(visibleCount, " out of ").concat(totalItems, " ").concat(itemName).concat(totalItems !== 1 ? "s" : "");
60
+ return "Showing ".concat(visibleCount).concat(closing, " ").concat(itemName).concat(totalItems !== 1 ? "s" : "");
54
61
  };
55
62
  export var useFigureSmallScreen = () => {
56
63
  var [isSmallScreen, setSmallScreen] = useState(false);
@@ -1,4 +1,4 @@
1
1
  /**
2
- * @deprecated Code component is deprecated. Use CodeSnippet component or inline `<code>` instead.
2
+ * @deprecated Code component is deprecated. Use useId from React directly instead.
3
3
  */
4
4
  export declare const useId: () => string;
@@ -2,7 +2,7 @@ import { useId as useIdReact } from "react";
2
2
  import { IS_DEV } from "../utils";
3
3
 
4
4
  /**
5
- * @deprecated Code component is deprecated. Use CodeSnippet component or inline `<code>` instead.
5
+ * @deprecated Code component is deprecated. Use useId from React directly instead.
6
6
  */
7
7
  export var useId = () => {
8
8
  var id = useIdReact();
@@ -40,12 +40,13 @@ export var useListener = function useListener(targetNode, callback, eventType) {
40
40
  targetNode.addEventListener(eventType, eventListener.current, options);
41
41
  isListening.current = true;
42
42
  }
43
+ return () => {
44
+ // Unattach the listener if the component gets unmounted while
45
+ // listening.
46
+ if (targetNode && eventListener.current && isListening.current) {
47
+ targetNode.removeEventListener(eventType, eventListener.current, options);
48
+ isListening.current = false;
49
+ }
50
+ };
43
51
  }, [callback, eventType, options, previousCallback, previousEventType, previousOptions, previousShouldThrottle, previousTargetNode, shouldListen, shouldThrottle, targetNode, throttle]);
44
- useEffect(() => () => {
45
- // Unattach the listener if the component gets unmounted while
46
- // listening.
47
- if (targetNode && isListening.current) {
48
- targetNode.removeEventListener(eventType, eventListener.current, options);
49
- }
50
- }, [eventType, targetNode, options]);
51
52
  };
@@ -44,6 +44,8 @@ export { default as Panel } from "./components/Panel";
44
44
  export { default as PasswordToggle } from "./components/PasswordToggle";
45
45
  export { default as RadioInput } from "./components/RadioInput";
46
46
  export { default as Row } from "./components/Row";
47
+ export { default as ScrollableContainer } from "./components/ScrollableContainer";
48
+ export { default as ScrollableTable } from "./components/ScrollableTable";
47
49
  export { default as SearchAndFilter } from "./components/SearchAndFilter";
48
50
  export { default as SearchBox } from "./components/SearchBox";
49
51
  export { default as Select } from "./components/Select";
@@ -116,6 +118,8 @@ export type { PaginationProps } from "./components/Pagination";
116
118
  export type { PanelProps } from "./components/Panel";
117
119
  export type { RadioInputProps } from "./components/RadioInput";
118
120
  export type { RowProps } from "./components/Row";
121
+ export type { ScrollableTableProps } from "./components/ScrollableTable";
122
+ export type { ScrollableContainerProps } from "./components/ScrollableContainer";
119
123
  export type { SearchAndFilterProps } from "./components/SearchAndFilter";
120
124
  export type { SearchBoxProps } from "./components/SearchBox";
121
125
  export type { SelectProps } from "./components/Select";
@@ -143,7 +147,7 @@ export type { TablePaginationProps } from "./components/TablePagination";
143
147
  export type { CustomSelectProps, CustomSelectDropdownProps, CustomSelectOption, } from "./components/CustomSelect";
144
148
  export { useOnClickOutside, useClickOutside, useId, useListener, useOnEscapePressed, usePagination, usePrevious, usePrefersReducedMotion, useThrottle, useWindowFitment, } from "./hooks";
145
149
  export type { WindowFitment } from "./hooks";
146
- export { isNavigationAnchor, isNavigationButton } from "./utils";
150
+ export { isNavigationAnchor, isNavigationButton, getElementAbsoluteHeight, getAbsoluteHeightBelowById, getParentsBottomSpacing, } from "./utils";
147
151
  export type { ClassName, Headings, PropsWithSpread, SortDirection, SubComponentProps, TSFixMe, ValueOf, } from "./types";
148
152
  export { Theme } from "./enums";
149
153
  export type { UsePortalOptions } from "./external";
package/dist/esm/index.js CHANGED
@@ -44,6 +44,8 @@ export { default as Panel } from "./components/Panel";
44
44
  export { default as PasswordToggle } from "./components/PasswordToggle";
45
45
  export { default as RadioInput } from "./components/RadioInput";
46
46
  export { default as Row } from "./components/Row";
47
+ export { default as ScrollableContainer } from "./components/ScrollableContainer";
48
+ export { default as ScrollableTable } from "./components/ScrollableTable";
47
49
  export { default as SearchAndFilter } from "./components/SearchAndFilter";
48
50
  export { default as SearchBox } from "./components/SearchBox";
49
51
  export { default as Select } from "./components/Select";
@@ -74,6 +76,6 @@ export { default as TablePaginationControls } from "./components/TablePagination
74
76
  export { default as CustomLayout } from "./components/CustomLayout";
75
77
  export { default as CustomSelect } from "./components/CustomSelect";
76
78
  export { useOnClickOutside, useClickOutside, useId, useListener, useOnEscapePressed, usePagination, usePrevious, usePrefersReducedMotion, useThrottle, useWindowFitment } from "./hooks";
77
- export { isNavigationAnchor, isNavigationButton } from "./utils";
79
+ export { isNavigationAnchor, isNavigationButton, getElementAbsoluteHeight, getAbsoluteHeightBelowById, getParentsBottomSpacing } from "./utils";
78
80
  export { Theme } from "./enums";
79
81
  export { usePortal } from "./external";
@@ -25,3 +25,7 @@ export declare const isNavigationButton: (link: NavLink) => link is NavLinkButto
25
25
  * A typeguard for whether an element is a ReactNode.
26
26
  */
27
27
  export declare const isReactNode: (element: unknown) => element is ReactNode;
28
+ export declare const getElementAbsoluteHeight: (element: HTMLElement) => number;
29
+ export declare const getAbsoluteHeightBelowById: (belowId: string) => number;
30
+ export declare const getParentsBottomSpacing: (element: Element) => number;
31
+ export declare const toFloat: (value: string) => number;
package/dist/esm/utils.js CHANGED
@@ -38,4 +38,35 @@ export var isNavigationButton = link => !link.url;
38
38
  /**
39
39
  * A typeguard for whether an element is a ReactNode.
40
40
  */
41
- export var isReactNode = element => /*#__PURE__*/isValidElement(element);
41
+ export var isReactNode = element => /*#__PURE__*/isValidElement(element);
42
+ export var getElementAbsoluteHeight = element => {
43
+ if (!element) {
44
+ return 0;
45
+ }
46
+ var style = window.getComputedStyle(element);
47
+ var margin = toFloat(style.marginTop) + toFloat(style.marginBottom);
48
+ var padding = toFloat(style.paddingTop) + toFloat(style.paddingBottom);
49
+ return element.offsetHeight + margin + padding + 1;
50
+ };
51
+ export var getAbsoluteHeightBelowById = belowId => {
52
+ var element = belowId ? document.getElementById(belowId) : undefined;
53
+ if (!element) {
54
+ return 0;
55
+ }
56
+ return getElementAbsoluteHeight(element);
57
+ };
58
+ export var getParentsBottomSpacing = element => {
59
+ var sum = 0;
60
+ while (element.parentElement) {
61
+ element = element.parentElement;
62
+ var style = window.getComputedStyle(element);
63
+ var margin = toFloat(style.marginBottom);
64
+ var padding = toFloat(style.paddingBottom);
65
+ sum += margin + padding;
66
+ }
67
+ return sum;
68
+ };
69
+ export var toFloat = value => {
70
+ var result = parseFloat(value);
71
+ return Number.isNaN(result) ? 0 : result;
72
+ };
@@ -1,4 +1,4 @@
1
1
  /**
2
- * @deprecated Code component is deprecated. Use CodeSnippet component or inline `<code>` instead.
2
+ * @deprecated Code component is deprecated. Use useId from React directly instead.
3
3
  */
4
4
  export declare const useId: () => string;
@@ -7,7 +7,7 @@ exports.useId = void 0;
7
7
  var _react = require("react");
8
8
  var _utils = require("../utils");
9
9
  /**
10
- * @deprecated Code component is deprecated. Use CodeSnippet component or inline `<code>` instead.
10
+ * @deprecated Code component is deprecated. Use useId from React directly instead.
11
11
  */
12
12
  const useId = () => {
13
13
  const id = (0, _react.useId)();
@@ -45,13 +45,14 @@ const useListener = function (targetNode, callback, eventType) {
45
45
  targetNode.addEventListener(eventType, eventListener.current, options);
46
46
  isListening.current = true;
47
47
  }
48
+ return () => {
49
+ // Unattach the listener if the component gets unmounted while
50
+ // listening.
51
+ if (targetNode && eventListener.current && isListening.current) {
52
+ targetNode.removeEventListener(eventType, eventListener.current, options);
53
+ isListening.current = false;
54
+ }
55
+ };
48
56
  }, [callback, eventType, options, previousCallback, previousEventType, previousOptions, previousShouldThrottle, previousTargetNode, shouldListen, shouldThrottle, targetNode, throttle]);
49
- (0, _react.useEffect)(() => () => {
50
- // Unattach the listener if the component gets unmounted while
51
- // listening.
52
- if (targetNode && isListening.current) {
53
- targetNode.removeEventListener(eventType, eventListener.current, options);
54
- }
55
- }, [eventType, targetNode, options]);
56
57
  };
57
58
  exports.useListener = useListener;
package/dist/index.d.ts CHANGED
@@ -44,6 +44,8 @@ export { default as Panel } from "./components/Panel";
44
44
  export { default as PasswordToggle } from "./components/PasswordToggle";
45
45
  export { default as RadioInput } from "./components/RadioInput";
46
46
  export { default as Row } from "./components/Row";
47
+ export { default as ScrollableContainer } from "./components/ScrollableContainer";
48
+ export { default as ScrollableTable } from "./components/ScrollableTable";
47
49
  export { default as SearchAndFilter } from "./components/SearchAndFilter";
48
50
  export { default as SearchBox } from "./components/SearchBox";
49
51
  export { default as Select } from "./components/Select";
@@ -116,6 +118,8 @@ export type { PaginationProps } from "./components/Pagination";
116
118
  export type { PanelProps } from "./components/Panel";
117
119
  export type { RadioInputProps } from "./components/RadioInput";
118
120
  export type { RowProps } from "./components/Row";
121
+ export type { ScrollableTableProps } from "./components/ScrollableTable";
122
+ export type { ScrollableContainerProps } from "./components/ScrollableContainer";
119
123
  export type { SearchAndFilterProps } from "./components/SearchAndFilter";
120
124
  export type { SearchBoxProps } from "./components/SearchBox";
121
125
  export type { SelectProps } from "./components/Select";
@@ -143,7 +147,7 @@ export type { TablePaginationProps } from "./components/TablePagination";
143
147
  export type { CustomSelectProps, CustomSelectDropdownProps, CustomSelectOption, } from "./components/CustomSelect";
144
148
  export { useOnClickOutside, useClickOutside, useId, useListener, useOnEscapePressed, usePagination, usePrevious, usePrefersReducedMotion, useThrottle, useWindowFitment, } from "./hooks";
145
149
  export type { WindowFitment } from "./hooks";
146
- export { isNavigationAnchor, isNavigationButton } from "./utils";
150
+ export { isNavigationAnchor, isNavigationButton, getElementAbsoluteHeight, getAbsoluteHeightBelowById, getParentsBottomSpacing, } from "./utils";
147
151
  export type { ClassName, Headings, PropsWithSpread, SortDirection, SubComponentProps, TSFixMe, ValueOf, } from "./types";
148
152
  export { Theme } from "./enums";
149
153
  export type { UsePortalOptions } from "./external";
package/dist/index.js CHANGED
@@ -63,6 +63,8 @@ var _exportNames = {
63
63
  PasswordToggle: true,
64
64
  RadioInput: true,
65
65
  Row: true,
66
+ ScrollableContainer: true,
67
+ ScrollableTable: true,
66
68
  SearchAndFilter: true,
67
69
  SearchBox: true,
68
70
  Select: true,
@@ -108,6 +110,9 @@ var _exportNames = {
108
110
  useWindowFitment: true,
109
111
  isNavigationAnchor: true,
110
112
  isNavigationButton: true,
113
+ getElementAbsoluteHeight: true,
114
+ getAbsoluteHeightBelowById: true,
115
+ getParentsBottomSpacing: true,
111
116
  Theme: true,
112
117
  usePortal: true
113
118
  };
@@ -417,6 +422,18 @@ Object.defineProperty(exports, "Row", {
417
422
  return _Row.default;
418
423
  }
419
424
  });
425
+ Object.defineProperty(exports, "ScrollableContainer", {
426
+ enumerable: true,
427
+ get: function () {
428
+ return _ScrollableContainer.default;
429
+ }
430
+ });
431
+ Object.defineProperty(exports, "ScrollableTable", {
432
+ enumerable: true,
433
+ get: function () {
434
+ return _ScrollableTable.default;
435
+ }
436
+ });
420
437
  Object.defineProperty(exports, "SearchAndFilter", {
421
438
  enumerable: true,
422
439
  get: function () {
@@ -627,6 +644,24 @@ Object.defineProperty(exports, "failure", {
627
644
  return _NotificationProvider.failure;
628
645
  }
629
646
  });
647
+ Object.defineProperty(exports, "getAbsoluteHeightBelowById", {
648
+ enumerable: true,
649
+ get: function () {
650
+ return _utils.getAbsoluteHeightBelowById;
651
+ }
652
+ });
653
+ Object.defineProperty(exports, "getElementAbsoluteHeight", {
654
+ enumerable: true,
655
+ get: function () {
656
+ return _utils.getElementAbsoluteHeight;
657
+ }
658
+ });
659
+ Object.defineProperty(exports, "getParentsBottomSpacing", {
660
+ enumerable: true,
661
+ get: function () {
662
+ return _utils.getParentsBottomSpacing;
663
+ }
664
+ });
630
665
  Object.defineProperty(exports, "info", {
631
666
  enumerable: true,
632
667
  get: function () {
@@ -804,6 +839,8 @@ var _Panel = _interopRequireDefault(require("./components/Panel"));
804
839
  var _PasswordToggle = _interopRequireDefault(require("./components/PasswordToggle"));
805
840
  var _RadioInput = _interopRequireDefault(require("./components/RadioInput"));
806
841
  var _Row = _interopRequireDefault(require("./components/Row"));
842
+ var _ScrollableContainer = _interopRequireDefault(require("./components/ScrollableContainer"));
843
+ var _ScrollableTable = _interopRequireDefault(require("./components/ScrollableTable"));
807
844
  var _SearchAndFilter = _interopRequireDefault(require("./components/SearchAndFilter"));
808
845
  var _SearchBox = _interopRequireDefault(require("./components/SearchBox"));
809
846
  var _Select = _interopRequireDefault(require("./components/Select"));
package/dist/utils.d.ts CHANGED
@@ -25,3 +25,7 @@ export declare const isNavigationButton: (link: NavLink) => link is NavLinkButto
25
25
  * A typeguard for whether an element is a ReactNode.
26
26
  */
27
27
  export declare const isReactNode: (element: unknown) => element is ReactNode;
28
+ export declare const getElementAbsoluteHeight: (element: HTMLElement) => number;
29
+ export declare const getAbsoluteHeightBelowById: (belowId: string) => number;
30
+ export declare const getParentsBottomSpacing: (element: Element) => number;
31
+ export declare const toFloat: (value: string) => number;
package/dist/utils.js CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.isReactNode = exports.isNavigationButton = exports.isNavigationAnchor = exports.highlightSubString = exports.IS_DEV = void 0;
6
+ exports.toFloat = exports.isReactNode = exports.isNavigationButton = exports.isNavigationAnchor = exports.highlightSubString = exports.getParentsBottomSpacing = exports.getElementAbsoluteHeight = exports.getAbsoluteHeightBelowById = exports.IS_DEV = void 0;
7
7
  var _react = require("react");
8
8
  const IS_DEV = exports.IS_DEV = process.env.NODE_ENV === "development";
9
9
 
@@ -48,4 +48,39 @@ const isNavigationButton = link => !link.url;
48
48
  */
49
49
  exports.isNavigationButton = isNavigationButton;
50
50
  const isReactNode = element => /*#__PURE__*/(0, _react.isValidElement)(element);
51
- exports.isReactNode = isReactNode;
51
+ exports.isReactNode = isReactNode;
52
+ const getElementAbsoluteHeight = element => {
53
+ if (!element) {
54
+ return 0;
55
+ }
56
+ const style = window.getComputedStyle(element);
57
+ const margin = toFloat(style.marginTop) + toFloat(style.marginBottom);
58
+ const padding = toFloat(style.paddingTop) + toFloat(style.paddingBottom);
59
+ return element.offsetHeight + margin + padding + 1;
60
+ };
61
+ exports.getElementAbsoluteHeight = getElementAbsoluteHeight;
62
+ const getAbsoluteHeightBelowById = belowId => {
63
+ const element = belowId ? document.getElementById(belowId) : undefined;
64
+ if (!element) {
65
+ return 0;
66
+ }
67
+ return getElementAbsoluteHeight(element);
68
+ };
69
+ exports.getAbsoluteHeightBelowById = getAbsoluteHeightBelowById;
70
+ const getParentsBottomSpacing = element => {
71
+ let sum = 0;
72
+ while (element.parentElement) {
73
+ element = element.parentElement;
74
+ const style = window.getComputedStyle(element);
75
+ const margin = toFloat(style.marginBottom);
76
+ const padding = toFloat(style.paddingBottom);
77
+ sum += margin + padding;
78
+ }
79
+ return sum;
80
+ };
81
+ exports.getParentsBottomSpacing = getParentsBottomSpacing;
82
+ const toFloat = value => {
83
+ const result = parseFloat(value);
84
+ return Number.isNaN(result) ? 0 : result;
85
+ };
86
+ exports.toFloat = toFloat;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonical/react-components",
3
- "version": "2.13.0",
3
+ "version": "2.15.0",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "author": {
@@ -93,7 +93,7 @@
93
93
  "tsc-alias": "1.8.10",
94
94
  "typescript": "5.7.3",
95
95
  "typescript-eslint": "8.24.1",
96
- "vanilla-framework": "4.26.1",
96
+ "vanilla-framework": "4.29.0",
97
97
  "wait-on": "8.0.2",
98
98
  "webpack": "5.98.0"
99
99
  },