@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
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.default = exports.RenderBelow = exports.RenderAbove = exports.Default = exports.CustomPageLimit = exports.CustomDisplayTitle = exports.ControlsOnly = void 0;
6
+ exports.default = exports.RenderBelow = exports.RenderAbove = exports.Default = exports.CustomPageLimit = exports.CustomDisplayTitle = exports.ControlsWithUnknownEntries = exports.ControlsWithPartiallyKnownEntries = exports.ControlsOnly = void 0;
7
7
  var _react = _interopRequireDefault(require("react"));
8
8
  var _TablePagination = _interopRequireDefault(require("./TablePagination"));
9
9
  var _MainTable = _interopRequireDefault(require("../MainTable"));
@@ -327,4 +327,56 @@ const ControlsOnly = exports.ControlsOnly = {
327
327
  visibleCount: 10
328
328
  });
329
329
  }
330
+ };
331
+
332
+ /** The `TablePaginationControls` component can be used when the total
333
+ * number of entries is unknown.
334
+ */
335
+ const ControlsWithUnknownEntries = exports.ControlsWithUnknownEntries = {
336
+ render: () => {
337
+ return /*#__PURE__*/_react.default.createElement(_TablePaginationControls.default, {
338
+ currentPage: 1,
339
+ itemName: "row",
340
+ nextButtonProps: {
341
+ disabled: false
342
+ },
343
+ onInputPageChange: console.log,
344
+ onNextPage: console.log,
345
+ onPageSizeChange: console.log,
346
+ onPreviousPage: console.log,
347
+ pageLimits: [10, 25, 50],
348
+ pageSize: 20,
349
+ previousButtonProps: {
350
+ disabled: false
351
+ },
352
+ showPageInput: true,
353
+ visibleCount: 10
354
+ });
355
+ }
356
+ };
357
+
358
+ /** The `TablePaginationControls` component can be used when it is known
359
+ * that there are more entries than the amount displayed on the current page.
360
+ */
361
+ const ControlsWithPartiallyKnownEntries = exports.ControlsWithPartiallyKnownEntries = {
362
+ render: () => {
363
+ return /*#__PURE__*/_react.default.createElement(_TablePaginationControls.default, {
364
+ currentPage: 3,
365
+ itemName: "row",
366
+ nextButtonProps: {
367
+ disabled: false
368
+ },
369
+ onInputPageChange: console.log,
370
+ onNextPage: console.log,
371
+ onPageSizeChange: console.log,
372
+ onPreviousPage: console.log,
373
+ pageLimits: [10, 25, 50],
374
+ pageSize: 20,
375
+ previousButtonProps: {
376
+ disabled: false
377
+ },
378
+ showPageInput: true,
379
+ visibleCount: 10
380
+ });
381
+ }
330
382
  };
@@ -27,7 +27,7 @@ const TablePaginationControls = _ref => {
27
27
  description,
28
28
  displayDescription = true,
29
29
  onInputPageChange,
30
- itemName,
30
+ itemName = "row",
31
31
  nextButtonProps,
32
32
  onNextPage,
33
33
  onPageChange,
@@ -48,7 +48,8 @@ const TablePaginationControls = _ref => {
48
48
  visibleCount,
49
49
  isSmallScreen,
50
50
  totalItems,
51
- itemName
51
+ itemName,
52
+ currentPage
52
53
  });
53
54
  const handleDecrementPage = currentPage => {
54
55
  if (currentPage > 1) {
@@ -70,7 +71,7 @@ const TablePaginationControls = _ref => {
70
71
  const handlePageSizeChange = e => {
71
72
  onPageSizeChange(parseInt(e.target.value));
72
73
  };
73
- const isInputDisabled = !totalPages || totalPages == 1;
74
+ const isInputDisabled = typeof totalItems === "number" && (!totalPages || totalPages == 1);
74
75
  const maxPageValue = typeof totalPages === "number" ? totalPages : 1;
75
76
  return /*#__PURE__*/_react.default.createElement("div", _extends({
76
77
  className: (0, _classnames.default)("pagination", className)
@@ -99,7 +100,9 @@ const TablePaginationControls = _ref => {
99
100
  disabled: isInputDisabled,
100
101
  min: 1,
101
102
  max: maxPageValue
102
- }), " ", typeof totalPages === "number" ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "of\xA0", totalPages) : null) : null, /*#__PURE__*/_react.default.createElement(_Button.default, _extends({
103
+ }), " ", typeof totalPages === "number" ? /*#__PURE__*/_react.default.createElement("div", {
104
+ className: "pagination-item-count"
105
+ }, "of\xA0", totalPages) : null) : null, /*#__PURE__*/_react.default.createElement(_Button.default, _extends({
103
106
  "aria-label": Label.NEXT_PAGE,
104
107
  className: "next",
105
108
  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;
@@ -47,18 +47,25 @@ const getDescription = _ref => {
47
47
  isSmallScreen,
48
48
  totalItems,
49
49
  itemName,
50
- visibleCount
50
+ visibleCount,
51
+ currentPage
51
52
  } = _ref;
52
53
  if (description) {
53
54
  return description;
54
55
  }
56
+ let closing = "";
57
+ if (typeof totalItems === "number") {
58
+ closing = " out of ".concat(totalItems);
59
+ } else if (currentPage !== 1) {
60
+ closing = " of more than ".concat(visibleCount);
61
+ }
55
62
  if (isSmallScreen) {
56
- return "".concat(visibleCount, " out of ").concat(totalItems);
63
+ return "".concat(visibleCount).concat(closing);
57
64
  }
58
65
  if (visibleCount === totalItems && visibleCount > 1) {
59
66
  return "Showing all ".concat(totalItems, " ").concat(itemName, "s");
60
67
  }
61
- return "Showing ".concat(visibleCount, " out of ").concat(totalItems, " ").concat(itemName).concat(totalItems !== 1 ? "s" : "");
68
+ return "Showing ".concat(visibleCount).concat(closing, " ").concat(itemName).concat(totalItems !== 1 ? "s" : "");
62
69
  };
63
70
  exports.getDescription = getDescription;
64
71
  const useFigureSmallScreen = () => {
@@ -1,3 +1,3 @@
1
1
  export { NotificationConsumer, NotificationProvider, useNotify, } from "./NotificationProvider";
2
- export { info, success, failure, queue } from "./messageBuilder";
2
+ export { info, success, failure, caution, queue } from "./messageBuilder";
3
3
  export type { NotificationAction, NotificationType, QueuedNotification, NotificationHelper, } from "./types";
@@ -1,2 +1,2 @@
1
1
  export { NotificationConsumer, NotificationProvider, useNotify } from "./NotificationProvider";
2
- export { info, success, failure, queue } from "./messageBuilder";
2
+ export { info, success, failure, caution, queue } from "./messageBuilder";
@@ -1,6 +1,8 @@
1
1
  import { NotificationAction, NotificationType, QueuedNotification } from "./types";
2
- import { ReactNode } from "react";
2
+ import React, { ReactNode } from "react";
3
3
  export declare const queue: (notification: NotificationType) => QueuedNotification;
4
- export declare const info: (message: ReactNode, title?: string) => NotificationType;
5
- export declare const success: (message: ReactNode, title?: string) => NotificationType;
4
+ export declare const info: (message: ReactNode, title?: string, actions?: NotificationAction[]) => NotificationType;
5
+ export declare const success: (message: ReactNode, title?: string, actions?: NotificationAction[]) => NotificationType;
6
6
  export declare const failure: (title: string, error: unknown, message?: ReactNode, actions?: NotificationAction[]) => NotificationType;
7
+ export declare const caution: (message: ReactNode, title?: string, actions?: NotificationAction[]) => NotificationType;
8
+ export declare const formatErrorMessage: (message?: ReactNode, error?: unknown) => string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode>> | React.JSX.Element;
@@ -7,25 +7,42 @@ export var queue = notification => {
7
7
  }
8
8
  };
9
9
  };
10
- export var info = (message, title) => {
10
+ export var info = (message, title, actions) => {
11
11
  return {
12
12
  message,
13
13
  title,
14
+ actions,
14
15
  type: NotificationSeverity.INFORMATION
15
16
  };
16
17
  };
17
- export var success = (message, title) => {
18
+ export var success = (message, title, actions) => {
18
19
  return {
19
20
  message,
20
21
  title,
22
+ actions,
21
23
  type: NotificationSeverity.POSITIVE
22
24
  };
23
25
  };
24
26
  export var failure = (title, error, message, actions) => {
25
27
  return {
26
28
  actions,
27
- message: error && error instanceof Error ? /*#__PURE__*/React.createElement(React.Fragment, null, message, " ", error.message) : message,
29
+ message: formatErrorMessage(message, error),
28
30
  title,
29
31
  type: NotificationSeverity.NEGATIVE
30
32
  };
33
+ };
34
+ export var caution = (message, title, actions) => {
35
+ return {
36
+ message,
37
+ actions,
38
+ title,
39
+ type: NotificationSeverity.CAUTION
40
+ };
41
+ };
42
+ export var formatErrorMessage = (message, error) => {
43
+ if (error && error instanceof Error) {
44
+ return /*#__PURE__*/React.createElement(React.Fragment, null, message, " ", error.message);
45
+ } else {
46
+ return message;
47
+ }
31
48
  };
@@ -51,6 +51,8 @@ var PreloadedList = () => {
51
51
  onClick: () => console.log("Retry clicked")
52
52
  }])
53
53
  }, "Add Error"), /*#__PURE__*/React.createElement(Button, {
54
+ onClick: () => toastNotify.caution("You have unsaved changes")
55
+ }, "Add Warning"), /*#__PURE__*/React.createElement(Button, {
54
56
  onClick: toastNotify.toggleListView
55
57
  }, "Toggle List View"));
56
58
  };
@@ -11,10 +11,10 @@ interface Props {
11
11
  }
12
12
  interface ToastNotificationHelper {
13
13
  notifications: ToastNotificationType[];
14
- success: (message: ReactNode, actions?: NotificationAction[], title?: string) => ToastNotificationType;
15
- info: (message: ReactNode, title?: string, actions?: NotificationAction[]) => ToastNotificationType;
16
- failure: (title: string, error: unknown, message?: ReactNode, actions?: NotificationAction[]) => ToastNotificationType;
17
- caution: (message: ReactNode, actions?: NotificationAction[], title?: string) => ToastNotificationType;
14
+ success: (message: ReactNode, actions?: NotificationAction[], title?: string, id?: ToastNotificationType["id"]) => ToastNotificationType;
15
+ info: (message: ReactNode, title?: string, actions?: NotificationAction[], id?: ToastNotificationType["id"]) => ToastNotificationType;
16
+ failure: (title: string, error: unknown, message?: ReactNode, actions?: NotificationAction[], id?: ToastNotificationType["id"]) => ToastNotificationType;
17
+ caution: (message: ReactNode, actions?: NotificationAction[], title?: string, id?: ToastNotificationType["id"]) => ToastNotificationType;
18
18
  clear: (notification?: ToastNotificationType[]) => void;
19
19
  toggleListView: () => void;
20
20
  isListView: boolean;
@@ -3,8 +3,7 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
3
3
  function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
4
4
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
5
5
  function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
6
- import { failure as _failure } from "../../NotificationProvider";
7
- import { NotificationSeverity } from "./..";
6
+ import { failure as _failure, info as _info, caution as _caution, success as _success } from "../../NotificationProvider";
8
7
  import ToastNotification from "./ToastNotification";
9
8
  import ToastNotificationList from "./ToastNotificationList";
10
9
  import { createContext, useContext, useEffect, useState } from "react";
@@ -138,9 +137,10 @@ var ToastNotificationProvider = _ref => {
138
137
  });
139
138
  };
140
139
  var addNotification = notification => {
140
+ var _notification$id;
141
141
  var notificationToAdd = _objectSpread(_objectSpread({}, notification), {}, {
142
142
  timestamp: new Date().toLocaleString(),
143
- id: Date.now().toString() + (Math.random() + 1).toString(36).substring(7)
143
+ id: (_notification$id = notification.id) !== null && _notification$id !== void 0 ? _notification$id : Date.now().toString() + (Math.random() + 1).toString(36).substring(7)
144
144
  });
145
145
  setNotifications(prev => {
146
146
  return [...prev, notificationToAdd];
@@ -186,25 +186,18 @@ var ToastNotificationProvider = _ref => {
186
186
  });
187
187
  var helper = {
188
188
  notifications,
189
- failure: (title, error, message, actions) => addNotification(_failure(title, error, message, actions)),
190
- info: (message, title, actions) => addNotification({
191
- message,
192
- actions,
193
- title,
194
- type: NotificationSeverity.INFORMATION
195
- }),
196
- success: (message, actions, title) => addNotification({
197
- message,
198
- actions,
199
- title,
200
- type: NotificationSeverity.POSITIVE
201
- }),
202
- caution: (message, actions, title) => addNotification({
203
- message,
204
- actions,
205
- title,
206
- type: NotificationSeverity.CAUTION
207
- }),
189
+ failure: (title, error, message, actions, id) => addNotification(_objectSpread(_objectSpread({}, _failure(title, error, message, actions)), {}, {
190
+ id
191
+ })),
192
+ info: (message, title, actions, id) => addNotification(_objectSpread(_objectSpread({}, _info(message, title, actions)), {}, {
193
+ id
194
+ })),
195
+ success: (message, actions, title, id) => addNotification(_objectSpread(_objectSpread({}, _success(message, title, actions)), {}, {
196
+ id
197
+ })),
198
+ caution: (message, actions, title, id) => addNotification(_objectSpread(_objectSpread({}, _caution(message, title, actions)), {}, {
199
+ id
200
+ })),
208
201
  clear,
209
202
  toggleListView,
210
203
  isListView: showList,
@@ -0,0 +1,27 @@
1
+ import type { DependencyList, FC, ReactNode } from "react";
2
+ import "./ScrollableContainer.scss";
3
+ export type Props = {
4
+ /**
5
+ * The content that should be scrollable on overflow.
6
+ */
7
+ children: ReactNode;
8
+ /**
9
+ * An array of dependencies that will trigger a re-calculation of the scrollable height when changed.
10
+ */
11
+ dependencies: DependencyList;
12
+ /**
13
+ * An array of element IDs below the scrollable container that should be considered when calculating the height.
14
+ */
15
+ belowIds?: string[];
16
+ /**
17
+ * Additional CSS classes to apply to the scrollable container.
18
+ */
19
+ className?: string;
20
+ };
21
+ /**
22
+ * This is a [React](https://reactjs.org/) component for use within the [Vanilla framework](https://docs.vanillaframework.io/).
23
+ *
24
+ * It is used to make any child element scrollable by adjusting the height based on the viewport height and the heights of elements above and below it. As a result the surrounding elements are sticky and only the wrapped element contents scroll
25
+ */
26
+ declare const ScrollableContainer: FC<Props>;
27
+ export default ScrollableContainer;
@@ -0,0 +1,43 @@
1
+ import React from "react";
2
+ import { useEffect, useRef } from "react";
3
+ import classnames from "classnames";
4
+ import "./ScrollableContainer.scss";
5
+ import { getAbsoluteHeightBelowById, getParentsBottomSpacing } from "../../utils";
6
+ import { useListener } from "../../hooks";
7
+ /**
8
+ * This is a [React](https://reactjs.org/) component for use within the [Vanilla framework](https://docs.vanillaframework.io/).
9
+ *
10
+ * It is used to make any child element scrollable by adjusting the height based on the viewport height and the heights of elements above and below it. As a result the surrounding elements are sticky and only the wrapped element contents scroll
11
+ */
12
+ var ScrollableContainer = _ref => {
13
+ var {
14
+ dependencies,
15
+ children,
16
+ belowIds = ["status-bar"],
17
+ className
18
+ } = _ref;
19
+ var ref = useRef(null);
20
+ var updateChildContainerHeight = () => {
21
+ var _ref$current;
22
+ var childContainer = (_ref$current = ref.current) === null || _ref$current === void 0 ? void 0 : _ref$current.children[0];
23
+ if (!childContainer) {
24
+ return;
25
+ }
26
+ var above = childContainer.getBoundingClientRect().top + 1;
27
+ var below = belowIds.reduce((acc, belowId) => acc + getAbsoluteHeightBelowById(belowId), 0);
28
+ var parentsBottomSpacing = getParentsBottomSpacing(childContainer);
29
+ var offset = Math.ceil(above + below + parentsBottomSpacing);
30
+ var style = "height: calc(100dvh - ".concat(offset, "px); min-height: calc(100dvh - ").concat(offset, "px)");
31
+ childContainer.setAttribute("style", style);
32
+ };
33
+ useListener(window, updateChildContainerHeight, "resize", true);
34
+ useEffect(updateChildContainerHeight, [dependencies, belowIds, ref]);
35
+ return /*#__PURE__*/React.createElement("div", {
36
+ ref: ref,
37
+ className: classnames("scrollable-container", className)
38
+ }, /*#__PURE__*/React.createElement("div", {
39
+ id: "content-details",
40
+ className: "content-details"
41
+ }, children));
42
+ };
43
+ export default ScrollableContainer;
@@ -0,0 +1,7 @@
1
+ .scrollable-container {
2
+ .content-details {
3
+ display: block;
4
+ overflow: auto;
5
+ scrollbar-gutter: stable;
6
+ }
7
+ }
@@ -0,0 +1,6 @@
1
+ import { Meta, StoryObj } from "@storybook/react";
2
+ import ScrollableContainer from "./ScrollableContainer";
3
+ declare const meta: Meta<typeof ScrollableContainer>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof ScrollableContainer>;
6
+ export declare const Default: Story;
@@ -0,0 +1,29 @@
1
+ import ScrollableContainer from "./ScrollableContainer";
2
+ import React from "react";
3
+ import { Description, Subtitle, Title } from "@storybook/blocks";
4
+ var meta = {
5
+ component: ScrollableContainer,
6
+ tags: ["autodocs"]
7
+ };
8
+ export default meta;
9
+ var StoryExample = args => {
10
+ return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", null, "Above contents"), /*#__PURE__*/React.createElement(ScrollableContainer, {
11
+ belowIds: args.belowIds,
12
+ dependencies: []
13
+ }, /*#__PURE__*/React.createElement("p", null, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras varius mi eu pretium vulputate. Nunc commodo sit amet nibh quis rhoncus. Aliquam rhoncus porttitor semper. Aenean faucibus consectetur neque in sodales. Sed cursus mauris id ex sollicitudin sodales. Quisque molestie rutrum odio, ornare pharetra ligula. Pellentesque ornare tristique feugiat. In a augue neque. Aenean eget arcu quis lacus tempus posuere in sit amet dui. Suspendisse faucibus sapien nisl, nec laoreet sem convallis nec."), /*#__PURE__*/React.createElement("p", null, "Vestibulum sed placerat lorem. Nam luctus ex id nisi luctus, id vestibulum sem bibendum. Vivamus turpis sem, pellentesque fermentum malesuada eu, faucibus porta libero. Duis eget venenatis odio. Etiam sed volutpat magna, non tempus erat. Nunc id tortor ac quam consectetur dapibus ac ut tellus. Pellentesque ut tellus venenatis elit vehicula condimentum eget quis lorem. Ut non consectetur est, a fringilla ipsum. Nunc vitae ligula ipsum. Etiam suscipit, libero ut lacinia viverra, nunc urna consequat ex, vel eleifend eros mauris vitae ipsum. Pellentesque sed dictum augue. Ut sit amet ullamcorper mauris. Nunc congue orci mollis purus sodales facilisis ac ut arcu. Maecenas feugiat sapien ac massa mollis sodales. Donec vitae turpis eu nisi laoreet pulvinar quis at nisl. Integer volutpat, metus eget elementum dictum, lacus sapien viverra felis, consequat fermentum nisl mi ac dui."), /*#__PURE__*/React.createElement("p", null, "Nullam nulla turpis, dignissim vel dapibus ut, volutpat ac dui. Donec vel elementum lacus. Mauris maximus nec felis at faucibus. Nunc faucibus gravida velit, id blandit lectus tincidunt ac. Vestibulum orci diam, elementum in congue eu, placerat id risus. Sed tempor tempus tellus, vitae iaculis turpis fringilla nec. Phasellus imperdiet facilisis velit, sit amet lobortis odio dignissim ut."), /*#__PURE__*/React.createElement("p", null, "Nam placerat urna vitae ligula hendrerit, ac tincidunt lorem maximus. Mauris eu odio nisi. Nulla facilisi. Sed egestas elit sed velit rutrum, sit amet bibendum metus hendrerit. Pellentesque luctus placerat tellus, eu bibendum justo. Cras eget leo ac ex volutpat gravida. Duis vitae mollis ante. Duis a congue nunc. Aenean aliquet, sapien quis tincidunt tincidunt, odio eros consectetur lacus, vel finibus mauris tortor id velit. Donec tincidunt vitae purus eu interdum. Pellentesque scelerisque dui viverra ex ullamcorper volutpat. Vestibulum lacinia vitae arcu volutpat porta. Etiam et cursus nulla, id aliquet felis. Nam ultricies, urna id mattis pretium, velit erat viverra elit, eu maximus diam eros id nisi."), /*#__PURE__*/React.createElement("p", null, "Nullam eget nisl lectus. Pellentesque eu mauris ut tortor malesuada sagittis. Cras dictum cursus est non ultricies. Duis mollis non neque at commodo. Nunc feugiat justo et consequat aliquam. Ut consectetur libero eu erat feugiat finibus. Duis varius convallis quam eu sagittis. Maecenas ac est arcu. Suspendisse at enim eget nibh ultricies dictum. Etiam aliquet tellus vel felis malesuada laoreet.")), /*#__PURE__*/React.createElement("div", {
14
+ id: "footer"
15
+ }, /*#__PURE__*/React.createElement("h2", {
16
+ className: "u-no-margin"
17
+ }, "Below contents"), /*#__PURE__*/React.createElement("div", null, "here be dragons.")));
18
+ };
19
+ export var Default = {
20
+ args: {
21
+ belowIds: ["footer"]
22
+ },
23
+ render: StoryExample,
24
+ parameters: {
25
+ docs: {
26
+ page: () => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Title, null), /*#__PURE__*/React.createElement(Subtitle, null), /*#__PURE__*/React.createElement(Description, null))
27
+ }
28
+ }
29
+ };
@@ -0,0 +1,2 @@
1
+ export { default } from "./ScrollableContainer";
2
+ export type { Props as ScrollableContainerProps } from "./ScrollableContainer";
@@ -0,0 +1 @@
1
+ export { default } from "./ScrollableContainer";
@@ -0,0 +1,27 @@
1
+ import type { DependencyList, FC, ReactNode } from "react";
2
+ import "./ScrollableTable.scss";
3
+ export type Props = {
4
+ /**
5
+ * A MainTable that will be made scrollable.
6
+ */
7
+ children: ReactNode;
8
+ /**
9
+ * An array of dependencies that will trigger a re-calculation of the table body height when they change.
10
+ */
11
+ dependencies: DependencyList;
12
+ /**
13
+ * The ID of the table element that contains the `<tbody>` to be made scrollable.
14
+ */
15
+ tableId: string;
16
+ /**
17
+ * An array of element IDs below the table that should be considered when calculating the height.
18
+ */
19
+ belowIds?: string[];
20
+ };
21
+ /**
22
+ * This is a [React](https://reactjs.org/) component for use within the [Vanilla framework](https://docs.vanillaframework.io/).
23
+ *
24
+ * It is used to make a table scrollable by adjusting the height of the `<tbody>` element based on the viewport height and the heights of elements above and below it. As a result the header is sticky and only the body scrolls
25
+ */
26
+ declare const ScrollableTable: FC<Props>;
27
+ export default ScrollableTable;
@@ -0,0 +1,37 @@
1
+ import React from "react";
2
+ import { useEffect } from "react";
3
+ import { getAbsoluteHeightBelowById, getParentsBottomSpacing } from "../../utils";
4
+ import "./ScrollableTable.scss";
5
+ import { useListener } from "../../hooks";
6
+ /**
7
+ * This is a [React](https://reactjs.org/) component for use within the [Vanilla framework](https://docs.vanillaframework.io/).
8
+ *
9
+ * It is used to make a table scrollable by adjusting the height of the `<tbody>` element based on the viewport height and the heights of elements above and below it. As a result the header is sticky and only the body scrolls
10
+ */
11
+ var ScrollableTable = _ref => {
12
+ var {
13
+ dependencies,
14
+ children,
15
+ tableId,
16
+ belowIds = []
17
+ } = _ref;
18
+ var updateTBodyHeight = () => {
19
+ var table = document.getElementById(tableId);
20
+ if (!table || table.children.length !== 2) {
21
+ return;
22
+ }
23
+ var tBody = table.children[1];
24
+ var above = tBody.getBoundingClientRect().top + 1;
25
+ var below = belowIds.reduce((acc, belowId) => acc + getAbsoluteHeightBelowById(belowId), 0);
26
+ var parentsBottomSpacing = getParentsBottomSpacing(table);
27
+ var offset = Math.ceil(above + below + parentsBottomSpacing);
28
+ var style = "height: calc(100dvh - ".concat(offset, "px); min-height: calc(100dvh - ").concat(offset, "px)");
29
+ tBody.setAttribute("style", style);
30
+ };
31
+ useListener(window, updateTBodyHeight, "resize", true);
32
+ useEffect(updateTBodyHeight, [...dependencies, belowIds, tableId]);
33
+ return /*#__PURE__*/React.createElement("div", {
34
+ className: "scrollable-table"
35
+ }, children);
36
+ };
37
+ export default ScrollableTable;
@@ -0,0 +1,58 @@
1
+ @import "vanilla-framework/scss/settings";
2
+
3
+ /* stylelint-disable selector-max-type */
4
+ .scrollable-table {
5
+ table {
6
+ margin-bottom: 0;
7
+ }
8
+
9
+ th,
10
+ td {
11
+ display: table-cell;
12
+ vertical-align: top;
13
+ }
14
+
15
+ th::before {
16
+ content: "";
17
+ height: 1rem;
18
+ }
19
+
20
+ thead,
21
+ tbody {
22
+ display: block;
23
+ overflow: hidden auto;
24
+ scrollbar-gutter: stable;
25
+ }
26
+
27
+ tbody {
28
+ height: calc(100dvh - 323px);
29
+ }
30
+
31
+ thead tr,
32
+ tbody tr {
33
+ display: table;
34
+ table-layout: fixed;
35
+ width: 100%;
36
+ }
37
+
38
+ @media screen and (max-width: $breakpoint-large) {
39
+ .p-table--mobile-card {
40
+ thead {
41
+ display: none;
42
+ }
43
+
44
+ td {
45
+ display: block;
46
+ }
47
+
48
+ tbody tr {
49
+ display: block;
50
+ }
51
+
52
+ tbody tr:first-child {
53
+ margin-top: $spv--x-large;
54
+ }
55
+ }
56
+ }
57
+ }
58
+ /* stylelint-enable selector-max-type */
@@ -0,0 +1,6 @@
1
+ import { Meta, StoryObj } from "@storybook/react";
2
+ import ScrollableTable from "./ScrollableTable";
3
+ declare const meta: Meta<typeof ScrollableTable>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof ScrollableTable>;
6
+ export declare const Default: Story;
@@ -0,0 +1,62 @@
1
+ import React from "react";
2
+ import ScrollableTable from "./ScrollableTable";
3
+ import { MainTable } from "../../index";
4
+ import { Description, Subtitle, Title } from "@storybook/blocks";
5
+ var meta = {
6
+ component: ScrollableTable,
7
+ tags: ["autodocs"]
8
+ };
9
+ export default meta;
10
+ var StoryExample = args => {
11
+ var headers = [{
12
+ content: "Fact"
13
+ }, {
14
+ content: "Relevancy score"
15
+ }, {
16
+ content: "Size"
17
+ }, {
18
+ content: "ID"
19
+ }, {
20
+ "aria-label": "Actions",
21
+ className: "actions"
22
+ }];
23
+ var facts = ["Dragons are mythical creatures", "They can fly", "They breathe fire", "They hoard treasure", "They are often depicted as large reptiles", "They appear in various cultures' folklore", "They can be friendly or hostile", "They are often associated with wisdom", "They can shapeshift in some legends", "They are sometimes guardians of sacred places", "They can be found in literature and movies", "They are often depicted with wings", "They can be of various colors", "They are sometimes associated with knights"];
24
+ var rows = facts.map(item => {
25
+ return {
26
+ columns: [{
27
+ content: item,
28
+ role: "rowheader"
29
+ }, {
30
+ content: 1
31
+ }, {
32
+ content: "1 GiB"
33
+ }, {
34
+ content: 2
35
+ }]
36
+ };
37
+ });
38
+ return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", null, "Above contents"), /*#__PURE__*/React.createElement(ScrollableTable, {
39
+ belowIds: args.belowIds,
40
+ dependencies: [headers, rows],
41
+ tableId: "facts-about-dragons"
42
+ }, /*#__PURE__*/React.createElement(MainTable, {
43
+ headers: headers,
44
+ rows: rows,
45
+ id: "facts-about-dragons"
46
+ })), /*#__PURE__*/React.createElement("div", {
47
+ id: "footer"
48
+ }, /*#__PURE__*/React.createElement("h2", {
49
+ className: "u-no-margin"
50
+ }, "Below contents"), /*#__PURE__*/React.createElement("div", null, "here be dragons.")));
51
+ };
52
+ export var Default = {
53
+ args: {
54
+ belowIds: ["footer"]
55
+ },
56
+ render: StoryExample,
57
+ parameters: {
58
+ docs: {
59
+ page: () => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Title, null), /*#__PURE__*/React.createElement(Subtitle, null), /*#__PURE__*/React.createElement(Description, null))
60
+ }
61
+ }
62
+ };
@@ -0,0 +1,2 @@
1
+ export { default } from "./ScrollableTable";
2
+ export type { Props as ScrollableTableProps } from "./ScrollableTable";
@@ -0,0 +1 @@
1
+ export { default } from "./ScrollableTable";