@jobber/components 4.33.0 → 4.35.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.
@@ -76,6 +76,14 @@ export interface DataListProps<T extends DataListObject> {
76
76
  * The callback function when the user scrolls to the bottom of the list.
77
77
  */
78
78
  readonly onLoadMore?: () => void;
79
+ /**
80
+ * Callback when an item checkbox is clicked.
81
+ */
82
+ readonly onSelect?: (items: T["id"][]) => void;
83
+ /**
84
+ * The list of Selected Item ids
85
+ */
86
+ readonly selected?: T["id"][];
79
87
  }
80
88
  export interface DataListLayoutProps<T extends DataListObject> {
81
89
  readonly children: (item: DataListItemType<T[]>) => JSX.Element;
@@ -0,0 +1,8 @@
1
+ /// <reference types="react" />
2
+ import { DataListObject } from "../../DataList.types";
3
+ interface ListItemInternalProps<T extends DataListObject> {
4
+ readonly children: JSX.Element;
5
+ readonly item: T;
6
+ }
7
+ export declare function DataListItemInternal<T extends DataListObject>({ children, item, }: ListItemInternalProps<T>): JSX.Element;
8
+ export {};
@@ -1,2 +1,7 @@
1
1
  /// <reference types="react" />
2
- export declare function DataListLoadMore(): JSX.Element;
2
+ interface DataListLoadMoreProps {
3
+ readonly onBackToTop: () => void;
4
+ }
5
+ export declare const MAX_DATA_COUNT = 50;
6
+ export declare function DataListLoadMore({ onBackToTop }: DataListLoadMoreProps): JSX.Element;
7
+ export {};
@@ -0,0 +1,2 @@
1
+ /// <reference types="react" />
2
+ export declare function DataListLoadMoreTrigger(): JSX.Element;
@@ -5,6 +5,7 @@ export declare const defaultValues: {
5
5
  data: never[];
6
6
  headers: {};
7
7
  children: never[];
8
+ selected: never[];
8
9
  };
9
10
  export declare const DataListContext: import("react").Context<DataListContextProps<DataListObject>>;
10
11
  export declare function useDataListContext(): DataListContextProps<DataListObject>;
@@ -12,6 +12,9 @@ var InlineLabel = require('../InlineLabel-afd5fc6f.js');
12
12
  var FormatDate = require('../FormatDate-70ea5b43.js');
13
13
  var Heading = require('../Heading-a1191b15.js');
14
14
  var classnames = require('classnames');
15
+ require('uuid');
16
+ require('react-hook-form');
17
+ var Checkbox = require('../Checkbox-3bccf65c.js');
15
18
  require('../Typography-fd6f932a.js');
16
19
  var useInView = require('@jobber/hooks/useInView');
17
20
  var debounce = require('lodash/debounce');
@@ -21,10 +24,8 @@ var AnimatedSwitcher = require('../AnimatedSwitcher-d1e1ddcf.js');
21
24
  var Spinner = require('../Spinner-9d8fc7ff.js');
22
25
  require('../tslib.es6-5b8768b7.js');
23
26
  require('../Content-2ca1ffe1.js');
24
- require('../FormField-0f880b07.js');
25
- require('uuid');
26
- require('react-hook-form');
27
27
  require('../Icon-405a216c.js');
28
+ require('../FormField-0f880b07.js');
28
29
  require('../InputValidation-b5a3d92c.js');
29
30
  require('framer-motion');
30
31
  require('react-router-dom');
@@ -36,8 +37,8 @@ var isEmpty__default = /*#__PURE__*/_interopDefaultLegacy(isEmpty);
36
37
  var classnames__default = /*#__PURE__*/_interopDefaultLegacy(classnames);
37
38
  var debounce__default = /*#__PURE__*/_interopDefaultLegacy(debounce);
38
39
 
39
- var css_248z$8 = ".TkdrExYnvcY- {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n position: relative;\n z-index: 0;\n z-index: var(--elevation-default);\n -ms-flex: 1;\n flex: 1;\n}\n\n.IcAlZHoB4LI- {\n display: -ms-flexbox;\n display: flex;\n position: relative;\n z-index: 1;\n z-index: var(--elevation-base);\n margin-bottom: calc(16px / 2);\n margin-bottom: var(--space-small);\n -ms-flex-align: center;\n align-items: center;\n}\n\n.TFO76ysivmg- {\n display: grid;\n padding: calc(16px / 2) 0;\n padding: var(--space-small) 0;\n gap: calc(16px / 2);\n grid-gap: calc(16px / 2);\n grid-gap: var(--space-small);\n gap: var(--space-small);\n grid-template-columns: auto -webkit-max-content;\n grid-template-columns: auto max-content;\n}\n\n.jGHZZYZm1ZY- {\n padding: calc(16px / 2);\n padding: var(--space-small);\n border-bottom: calc(16px / 8) solid rgb(225, 225, 225);\n border-bottom: var(--border-thick) solid var(--color-border);\n}\n\n.Gj9kAGnfKco- > * {\n font-weight: 500;\n}\n\n.ise8kHCfhCY- {\n padding: calc(16px / 2);\n padding: var(--space-small);\n border-bottom: calc(16px / 16) solid rgb(225, 225, 225);\n border-bottom: var(--border-base) solid var(--color-border);\n}\n\n.Kkp-IYmwq-s- {\n display: -ms-flexbox;\n display: flex;\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background-color: rgba(255, 255, 255, 0.6);\n background-color: var(--color-overlay--dimmed);\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n}\n\n.m-eCShL7TU4- {\n position: sticky;\n top: 50vh;\n}\n\n.xeUxuTlqQtk- {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-pack: center;\n justify-content: center;\n padding: calc(16px / 2);\n padding: var(--space-small);\n}\n";
40
- var styles$8 = {"wrapper":"TkdrExYnvcY-","titleContainer":"IcAlZHoB4LI-","headerFilters":"TFO76ysivmg-","headerTitles":"jGHZZYZm1ZY-","headerLabel":"Gj9kAGnfKco-","listItem":"ise8kHCfhCY-","filtering":"Kkp-IYmwq-s-","filteringSpinner":"m-eCShL7TU4-","loadingMore":"xeUxuTlqQtk-"};
40
+ var css_248z$8 = ".TkdrExYnvcY- {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n position: relative;\n z-index: 0;\n z-index: var(--elevation-default);\n -ms-flex: 1;\n flex: 1;\n}\n\n.IcAlZHoB4LI- {\n display: -ms-flexbox;\n display: flex;\n position: relative;\n z-index: 1;\n z-index: var(--elevation-base);\n margin-bottom: calc(16px / 2);\n margin-bottom: var(--space-small);\n -ms-flex-align: center;\n align-items: center;\n}\n\n.TFO76ysivmg- {\n display: grid;\n padding: calc(16px / 2) 0;\n padding: var(--space-small) 0;\n gap: calc(16px / 2);\n grid-gap: calc(16px / 2);\n grid-gap: var(--space-small);\n gap: var(--space-small);\n grid-template-columns: auto -webkit-max-content;\n grid-template-columns: auto max-content;\n}\n\n.jGHZZYZm1ZY- {\n padding: calc(16px / 2);\n padding: var(--space-small);\n border-bottom: calc(16px / 8) solid rgb(225, 225, 225);\n border-bottom: var(--border-thick) solid var(--color-border);\n}\n\n.Gj9kAGnfKco- > * {\n font-weight: 500;\n}\n\n.ise8kHCfhCY- {\n padding: calc(16px / 2);\n padding: var(--space-small);\n border-bottom: calc(16px / 16) solid rgb(225, 225, 225);\n border-bottom: var(--border-base) solid var(--color-border);\n}\n\n.d-79rkMqufg- {\n display: grid;\n -webkit-column-gap: calc(16px / 2);\n -webkit-column-gap: var(--space-small);\n -moz-column-gap: calc(16px / 2);\n -moz-column-gap: var(--space-small);\n column-gap: calc(16px / 2);\n grid-column-gap: calc(16px / 2);\n grid-column-gap: var(--space-small);\n column-gap: var(--space-small);\n -ms-flex-align: start;\n align-items: flex-start;\n grid-template-columns: -webkit-max-content minmax(0px, auto);\n grid-template-columns: max-content minmax(0px, auto);\n}\n\n@media (min-width: 768px) {\n\n.d-79rkMqufg- {\n -ms-flex-align: center;\n align-items: center;\n}\n }\n\n@media (--medium-screens-and-up) {\n\n.d-79rkMqufg- {\n -ms-flex-align: center;\n align-items: center;\n}\n }\n\n.ise8kHCfhCY- .d-79rkMqufg- > :first-child {\n padding-top: calc(16px / 4);\n padding-top: var(--space-smaller);\n opacity: 1;\n transition: opacity 100ms ease-in-out;\n transition: opacity var(--transition-properties);\n --transition-properties: var(--timing-quick) ease-in-out;\n}\n\n@media (min-width: 768px) {\n\n.ise8kHCfhCY- .d-79rkMqufg- > :first-child {\n padding-top: 0;\n opacity: 0;\n}\n }\n\n@media (--medium-screens-and-up) {\n\n.ise8kHCfhCY- .d-79rkMqufg- > :first-child {\n padding-top: 0;\n opacity: 0;\n}\n }\n\n.ise8kHCfhCY-:hover .d-79rkMqufg- > :first-child,\n.ise8kHCfhCY-:focus-within .d-79rkMqufg- > :first-child,\n.ise8kHCfhCY- .d-79rkMqufg-._4L7iXXzLr5k- > :first-child {\n opacity: 1;\n}\n\n.Kkp-IYmwq-s- {\n display: -ms-flexbox;\n display: flex;\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background-color: rgba(255, 255, 255, 0.6);\n background-color: var(--color-overlay--dimmed);\n -ms-flex-align: start;\n align-items: flex-start;\n -ms-flex-pack: center;\n justify-content: center;\n}\n\n.m-eCShL7TU4- {\n position: sticky;\n top: 50vh;\n}\n";
41
+ var styles$8 = {"wrapper":"TkdrExYnvcY-","titleContainer":"IcAlZHoB4LI-","headerFilters":"TFO76ysivmg-","headerTitles":"jGHZZYZm1ZY-","headerLabel":"Gj9kAGnfKco-","listItem":"ise8kHCfhCY-","selectable":"d-79rkMqufg-","selected":"_4L7iXXzLr5k-","filtering":"Kkp-IYmwq-s-","filteringSpinner":"m-eCShL7TU4-"};
41
42
  styleInject_es.styleInject(css_248z$8);
42
43
 
43
44
  var css_248z$7 = ".F56prQsXm3A- {\n min-width: 80px;\n margin-left: calc(16px / 2);\n margin-left: var(--space-small);\n}\n";
@@ -227,11 +228,44 @@ function isLayoutVisible({ layoutSize, mediaMatches, sizePropsOfLayouts, }) {
227
228
  return isVisibleBreakpoint && largerLayoutIsNotVisible;
228
229
  }
229
230
 
231
+ const defaultValues = {
232
+ title: "",
233
+ data: [],
234
+ headers: {},
235
+ children: [],
236
+ selected: [],
237
+ };
238
+ const DataListContext = React.createContext(defaultValues);
239
+ function useDataListContext() {
240
+ return React.useContext(DataListContext);
241
+ }
242
+
243
+ function DataListItemInternal({ children, item, }) {
244
+ const { selected, onSelect } = useDataListContext();
245
+ if (selected !== undefined && onSelect) {
246
+ return (React__default["default"].createElement("div", { className: classnames__default["default"](styles$8.selectable, {
247
+ [styles$8.selected]: selected === null || selected === void 0 ? void 0 : selected.length,
248
+ }) },
249
+ React__default["default"].createElement(Checkbox.Checkbox, { checked: selected === null || selected === void 0 ? void 0 : selected.includes(item.id), onChange: handleChange }),
250
+ children));
251
+ }
252
+ return children;
253
+ function handleChange() {
254
+ if (selected === null || selected === void 0 ? void 0 : selected.includes(item.id)) {
255
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect(selected === null || selected === void 0 ? void 0 : selected.filter(id => id !== item.id));
256
+ }
257
+ else if (selected) {
258
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect([...selected, item.id]);
259
+ }
260
+ }
261
+ }
262
+
230
263
  function DataListItems({ layouts, mediaMatches, data, }) {
231
264
  const elementData = generateListItemElements(data);
232
265
  return (React__default["default"].createElement(DataListLayoutInternal, { layouts: layouts, mediaMatches: mediaMatches, renderLayout: layout => {
233
266
  return (React__default["default"].createElement(React__default["default"].Fragment, null, elementData.map((child, i) => {
234
- return (React__default["default"].createElement("div", { className: styles$8.listItem, key: `${data[i].id}` }, layout.props.children(child)));
267
+ return (React__default["default"].createElement("div", { className: styles$8.listItem, key: `${data[i].id}` },
268
+ React__default["default"].createElement(DataListItemInternal, { item: data[i] }, layout.props.children(child))));
235
269
  })));
236
270
  } }));
237
271
  }
@@ -286,17 +320,6 @@ styleInject_es.styleInject(css_248z$4);
286
320
 
287
321
  const CONTAINER_TEST_ID = "ATL-DataListFilters-Container";
288
322
 
289
- const defaultValues = {
290
- title: "",
291
- data: [],
292
- headers: {},
293
- children: [],
294
- };
295
- const DataListContext = React.createContext(defaultValues);
296
- function useDataListContext() {
297
- return React.useContext(DataListContext);
298
- }
299
-
300
323
  // This component is meant to capture the props of the DataList.Filters
301
324
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
302
325
  function DataListFilters(_) {
@@ -432,11 +455,11 @@ function getEmptyStates(components) {
432
455
  return { defaultEmptyState, filteredEmptyState };
433
456
  }
434
457
 
435
- var css_248z = ".IC-Y9bTIVFo- {\n position: relative;\n -webkit-transform: translateY(calc((16px * 3) * -1));\n -webkit-transform: translateY(calc(calc(16px * 3) * -1));\n -webkit-transform: translateY(calc(var(--space-largest) * -1));\n transform: translateY(calc((16px * 3) * -1));\n transform: translateY(calc(calc(16px * 3) * -1));\n transform: translateY(calc(var(--space-largest) * -1));\n}\n";
436
- var styles = {"trigger":"IC-Y9bTIVFo-"};
458
+ var css_248z = ".IC-Y9bTIVFo- {\n position: relative;\n -webkit-transform: translateY(calc((16px * 3) * -1));\n -webkit-transform: translateY(calc(calc(16px * 3) * -1));\n -webkit-transform: translateY(calc(var(--space-largest) * -1));\n transform: translateY(calc((16px * 3) * -1));\n transform: translateY(calc(calc(16px * 3) * -1));\n transform: translateY(calc(var(--space-largest) * -1));\n}\n\n.PbZLGpbI6ZM-,\n._6zA6B-jmz6o- {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-pack: center;\n justify-content: center;\n padding: calc(16px / 2);\n padding: var(--space-small);\n}\n\n._6zA6B-jmz6o- {\n opacity: 0;\n -webkit-animation: gZ4DUv82g5I- 200ms ease 200ms forwards;\n -webkit-animation: gZ4DUv82g5I- var(--timing-base) ease var(--timing-base) forwards;\n animation: gZ4DUv82g5I- 200ms ease 200ms forwards;\n animation: gZ4DUv82g5I- var(--timing-base) ease var(--timing-base) forwards;\n}\n\n@-webkit-keyframes gZ4DUv82g5I- {\n from {\n opacity: 0;\n }\n\n to {\n opacity: 1;\n }\n}\n\n@keyframes gZ4DUv82g5I- {\n from {\n opacity: 0;\n }\n\n to {\n opacity: 1;\n }\n}\n";
459
+ var styles = {"trigger":"IC-Y9bTIVFo-","loadingMore":"PbZLGpbI6ZM-","backToTop":"_6zA6B-jmz6o-","fadeIn":"gZ4DUv82g5I-"};
437
460
  styleInject_es.styleInject(css_248z);
438
461
 
439
- function DataListLoadMore() {
462
+ function DataListLoadMoreTrigger() {
440
463
  const { onLoadMore } = useDataListContext();
441
464
  const [inViewRef, isInView] = useInView.useInView();
442
465
  React.useEffect(() => {
@@ -445,6 +468,18 @@ function DataListLoadMore() {
445
468
  return (React__default["default"].createElement("div", { ref: inViewRef, className: styles.trigger, "data-testid": DATA_LOAD_MORE_TEST_ID }));
446
469
  }
447
470
 
471
+ const MAX_DATA_COUNT = 50;
472
+ function DataListLoadMore({ onBackToTop }) {
473
+ const { loadingState, data } = useDataListContext();
474
+ const showBackToTop = data.length > MAX_DATA_COUNT && loadingState !== "loadingMore";
475
+ return (React__default["default"].createElement(React__default["default"].Fragment, null,
476
+ React__default["default"].createElement(DataListLoadMoreTrigger, { key: data.length }),
477
+ loadingState === "loadingMore" && (React__default["default"].createElement("div", { "data-testid": DATA_LIST_LOADING_MORE_SPINNER_TEST_ID, className: styles.loadingMore },
478
+ React__default["default"].createElement(Spinner.Spinner, { size: "small" }))),
479
+ showBackToTop && (React__default["default"].createElement("div", { className: styles.backToTop },
480
+ React__default["default"].createElement(Button.Button, { label: "Back to top", onClick: onBackToTop, size: "small", variation: "subtle" })))));
481
+ }
482
+
448
483
  function useLayoutMediaQueries() {
449
484
  const mediaQueries = BREAKPOINTS.reduce((previous, breakpoint) => (Object.assign(Object.assign({}, previous), { [breakpoint]: window.matchMedia(breakpointToMediaQuery(breakpoint)) })), {});
450
485
  const initialMatches = BREAKPOINTS.reduce((previous, breakpoint) => (Object.assign(Object.assign({}, previous), { [breakpoint]: mediaQueries[breakpoint].matches })), {});
@@ -471,18 +506,20 @@ function breakpointToMediaQuery(breakpoint) {
471
506
  }
472
507
 
473
508
  function DataList(props) {
509
+ var _a;
474
510
  const searchComponent = getCompoundComponent(props.children, DataListSearch);
475
511
  const filterComponent = getCompoundComponent(props.children, DataListFilters);
476
512
  const layoutComponents = getCompoundComponents(props.children, DataListLayout);
477
513
  const emptyStateComponents = getCompoundComponents(props.children, DataListEmptyState);
478
- return (React__default["default"].createElement(DataListContext.Provider, { value: Object.assign({ searchComponent,
514
+ return (React__default["default"].createElement(DataListContext.Provider, { value: Object.assign(Object.assign({ searchComponent,
479
515
  filterComponent,
480
516
  layoutComponents,
481
- emptyStateComponents }, props) },
517
+ emptyStateComponents }, props), { selected: (_a = props.selected) !== null && _a !== void 0 ? _a : [] }) },
482
518
  React__default["default"].createElement(InternalDataList, null)));
483
519
  }
484
520
  function InternalDataList() {
485
521
  const { data, headers, title, totalCount, headerVisibility = { xs: true, sm: true, md: true, lg: true, xl: true }, loadingState = "none", layoutComponents, } = useDataListContext();
522
+ const backToTopRef = React.useRef(null);
486
523
  const headerData = generateHeaderElements(headers);
487
524
  const mediaMatches = useLayoutMediaQueries();
488
525
  const initialLoading = loadingState === "initial";
@@ -492,6 +529,7 @@ function InternalDataList() {
492
529
  React__default["default"].createElement("div", { className: styles$8.titleContainer },
493
530
  title && React__default["default"].createElement(Heading.Heading, { level: 3 }, title),
494
531
  React__default["default"].createElement(DataListTotalCount, { totalCount: totalCount, loading: initialLoading })),
532
+ React__default["default"].createElement("div", { ref: backToTopRef }),
495
533
  React__default["default"].createElement(DataListStickyHeader, null,
496
534
  React__default["default"].createElement("div", { className: styles$8.headerFilters },
497
535
  React__default["default"].createElement(InternalDataListFilters, null),
@@ -503,9 +541,19 @@ function InternalDataList() {
503
541
  loadingState === "filtering" && (React__default["default"].createElement("div", { "data-testid": DATA_LIST_FILTERING_SPINNER_TEST_ID, className: styles$8.filtering },
504
542
  React__default["default"].createElement("div", { className: styles$8.filteringSpinner },
505
543
  React__default["default"].createElement(Spinner.Spinner, { size: "small" })))),
506
- shouldRenderLoadMoreTrigger && React__default["default"].createElement(DataListLoadMore, null),
507
- loadingState === "loadingMore" && (React__default["default"].createElement("div", { "data-testid": DATA_LIST_LOADING_MORE_SPINNER_TEST_ID, className: styles$8.loadingMore },
508
- React__default["default"].createElement(Spinner.Spinner, { size: "small" })))));
544
+ shouldRenderLoadMoreTrigger && (React__default["default"].createElement(DataListLoadMore, { onBackToTop: handleBackToTop }))));
545
+ function handleBackToTop() {
546
+ var _a;
547
+ // For testing purposes since jest doesn't know scrollIntoView.
548
+ // This prevents consumer's tests from needing to mock scrollIntoView.
549
+ if (!window.HTMLElement.prototype.scrollIntoView)
550
+ return;
551
+ (_a = backToTopRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({
552
+ behavior: "smooth",
553
+ block: "nearest",
554
+ inline: "start",
555
+ });
556
+ }
509
557
  }
510
558
  DataList.Layout = DataListLayout;
511
559
  DataList.EmptyState = DataListEmptyState;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components",
3
- "version": "4.33.0",
3
+ "version": "4.35.0",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -21,7 +21,7 @@
21
21
  "dependencies": {
22
22
  "@jobber/design": "^0.44.4",
23
23
  "@jobber/formatters": "*",
24
- "@jobber/hooks": "^2.2.1",
24
+ "@jobber/hooks": "^2.3.0",
25
25
  "@popperjs/core": "^2.0.6",
26
26
  "@std-proposal/temporal": "0.0.1",
27
27
  "@tanstack/react-table": "8.5.13",
@@ -84,5 +84,5 @@
84
84
  "> 1%",
85
85
  "IE 10"
86
86
  ],
87
- "gitHead": "4ad7db01af3c57a0eefed5e714059615d71b56d3"
87
+ "gitHead": "616c498cc1bfeedcc752700c6bcbc64f4f914350"
88
88
  }