@jobber/components 4.61.0 → 4.63.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.
@@ -2,10 +2,10 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var ConfirmationModal = require('../ConfirmationModal-285c3cbe.js');
5
+ var ConfirmationModal = require('../ConfirmationModal-467f6d4e.js');
6
6
  require('react');
7
7
  require('@jobber/hooks/useOnKeyDown');
8
- require('../Modal-c17cc105.js');
8
+ require('../Modal-7daf6831.js');
9
9
  require('react-dom');
10
10
  require('classnames');
11
11
  require('framer-motion');
@@ -2,7 +2,7 @@
2
2
 
3
3
  var React = require('react');
4
4
  var useOnKeyDown = require('@jobber/hooks/useOnKeyDown');
5
- var Modal = require('./Modal-c17cc105.js');
5
+ var Modal = require('./Modal-7daf6831.js');
6
6
  var Content = require('./Content-2ca1ffe1.js');
7
7
  var Markdown = require('./Markdown-d53b01bf.js');
8
8
 
@@ -17,8 +17,8 @@ export declare namespace DataList {
17
17
  var Filters: typeof DataListFilters;
18
18
  var Search: typeof DataListSearch;
19
19
  var ItemActions: typeof DataListItemActions;
20
- var BulkActions: typeof DataListBulkActions;
21
20
  var ItemAction: typeof DataListAction;
21
+ var BatchActions: typeof DataListBulkActions;
22
22
  var BatchAction: (props: DataListBulkActionProps) => JSX.Element;
23
23
  var StatusBar: typeof DataListStatusBar;
24
24
  }
@@ -92,16 +92,38 @@ export interface DataListProps<T extends DataListObject> {
92
92
  readonly state: DataListSorting | undefined;
93
93
  readonly onSort: (sorting?: DataListSorting) => void;
94
94
  };
95
+ /**
96
+ * The list of Selected Item ids
97
+ */
98
+ readonly selected?: DataListSelectedType<T["id"]>;
95
99
  /**
96
100
  * Callback when an item checkbox is clicked.
97
101
  */
98
- readonly onSelect?: (items: T["id"][]) => void;
102
+ readonly onSelect?: (selected: DataListSelectedType<T["id"]>) => void;
99
103
  /**
100
- * The list of Selected Item ids
104
+ * Callback when the select all checkbox is clicked.
105
+ */
106
+ readonly onSelectAll?: (selected: DataListSelectedType<T["id"]>) => void;
107
+ }
108
+ /**
109
+ * Select all items in the DataList except the ones in the unselected array.
110
+ *
111
+ * This is used in scenarios where we need to select all items in the DataList
112
+ * but couldn't query all the ID's. So instead of feeding all the ID's to the
113
+ * DataList, we set all checkboxes to be checked except for the items that are
114
+ * in the unselected array.
115
+ */
116
+ export interface DataListSelectedAllType<T extends DataListObject = DataListObject> {
117
+ /**
118
+ * The total number of selected items in the DataList.
119
+ */
120
+ readonly totalCount: number;
121
+ /**
122
+ * Track the ID's of the unchecked items.
101
123
  */
102
- readonly selected?: T["id"][];
103
- readonly onSelectAll?: () => void;
124
+ readonly unselected: T["id"][];
104
125
  }
126
+ export type DataListSelectedType<T extends string | number = DataListObject["id"]> = T[] | DataListSelectedAllType;
105
127
  export type LayoutRenderer<T extends DataListObject> = (item: DataListItemType<T[]>) => JSX.Element;
106
128
  export interface DataListLayoutProps<T extends DataListObject> {
107
129
  readonly children: LayoutRenderer<T>;
@@ -0,0 +1,13 @@
1
+ import { DataListSelectedType } from "../DataList.types";
2
+ export declare function useBatchSelect(): {
3
+ canSelect: boolean;
4
+ canSelectAll: boolean;
5
+ hasAtLeastOneSelected: boolean;
6
+ isSelectAll: boolean;
7
+ isSelectSome: boolean;
8
+ selectedCount: number;
9
+ selectedIDs: (string | number)[];
10
+ selected: DataListSelectedType<string | number> | undefined;
11
+ onSelect: ((selected: DataListSelectedType<string | number>) => void) | undefined;
12
+ onSelectAll: ((selected: DataListSelectedType<string | number>) => void) | undefined;
13
+ };
@@ -1,2 +1,2 @@
1
1
  export * from "./DataList";
2
- export { DataListItemType, DataListSorting } from "./DataList.types";
2
+ export { DataListItemType, DataListSorting, DataListSelectedType, DataListSelectedAllType, } from "./DataList.types";
@@ -509,22 +509,79 @@ function handleContextMenu(event) {
509
509
  event.stopPropagation();
510
510
  }
511
511
 
512
+ function useBatchSelect() {
513
+ const { selected, onSelect, onSelectAll } = useDataListContext();
514
+ const isArray = Array.isArray(selected);
515
+ const selectedIDs = getIDs(selected);
516
+ const isSelectAll = !isArray;
517
+ return {
518
+ canSelect: Boolean(selected && onSelect),
519
+ canSelectAll: Boolean(onSelect && onSelectAll),
520
+ hasAtLeastOneSelected: isSelectAll || selectedIDs.length > 0,
521
+ isSelectAll,
522
+ isSelectSome: isArray,
523
+ selectedCount: getSelectedCount(selected),
524
+ selectedIDs,
525
+ selected,
526
+ onSelect,
527
+ onSelectAll,
528
+ };
529
+ }
530
+ function getIDs(selected) {
531
+ if (Array.isArray(selected))
532
+ return selected;
533
+ if (selected === undefined)
534
+ return [];
535
+ return selected.unselected;
536
+ }
537
+ function getSelectedCount(selected) {
538
+ if (Array.isArray(selected))
539
+ return selected.length;
540
+ if (selected === undefined)
541
+ return 0;
542
+ return selected.totalCount - selected.unselected.length;
543
+ }
544
+
512
545
  function DataListItemInternal({ children, item, }) {
513
- const { selected, onSelect } = useDataListContext();
514
- if (selected !== undefined && onSelect) {
515
- return (React__default["default"].createElement("div", { className: classnames__default["default"](styles$f.selectable, {
516
- [styles$f.selected]: selected === null || selected === void 0 ? void 0 : selected.length,
517
- }) },
518
- children,
519
- React__default["default"].createElement(Checkbox.Checkbox, { checked: selected === null || selected === void 0 ? void 0 : selected.includes(item.id), onChange: handleChange })));
546
+ const { canSelect, hasAtLeastOneSelected, isSelectAll, selectedIDs, selected, onSelect, } = useBatchSelect();
547
+ if (!canSelect)
548
+ return children;
549
+ return (React__default["default"].createElement("div", { className: classnames__default["default"](styles$f.selectable, {
550
+ [styles$f.selected]: hasAtLeastOneSelected,
551
+ }) },
552
+ children,
553
+ React__default["default"].createElement(Checkbox.Checkbox, { checked: getIsChecked(), onChange: handleChange })));
554
+ function getIsChecked() {
555
+ const isItemInSelectedIDs = selectedIDs.includes(item.id);
556
+ // If we're in a "select all" state, the selectedID's becomes a list of
557
+ // unchecked ID's.
558
+ if (isSelectAll)
559
+ return !isItemInSelectedIDs;
560
+ // Otherwise, we're in a "select some" state, so we can just check if the
561
+ // item is in the selectedIDs list.
562
+ return isItemInSelectedIDs;
520
563
  }
521
- return children;
522
564
  function handleChange() {
523
- if (selected === null || selected === void 0 ? void 0 : selected.includes(item.id)) {
524
- onSelect === null || onSelect === void 0 ? void 0 : onSelect(selected === null || selected === void 0 ? void 0 : selected.filter(id => id !== item.id));
565
+ if (isSelectAll)
566
+ return handleSelectAllChange();
567
+ return handleSelectSomeChange();
568
+ }
569
+ function handleSelectAllChange() {
570
+ if (!selected || Array.isArray(selected))
571
+ return;
572
+ if (selectedIDs === null || selectedIDs === void 0 ? void 0 : selectedIDs.includes(item.id)) {
573
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect(Object.assign(Object.assign({}, selected), { unselected: selectedIDs === null || selectedIDs === void 0 ? void 0 : selectedIDs.filter(id => id !== item.id) }));
574
+ }
575
+ else if (selectedIDs) {
576
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect(Object.assign(Object.assign({}, selected), { unselected: [...selectedIDs, item.id] }));
577
+ }
578
+ }
579
+ function handleSelectSomeChange() {
580
+ if (selectedIDs === null || selectedIDs === void 0 ? void 0 : selectedIDs.includes(item.id)) {
581
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect(selectedIDs === null || selectedIDs === void 0 ? void 0 : selectedIDs.filter(id => id !== item.id));
525
582
  }
526
- else if (selected) {
527
- onSelect === null || onSelect === void 0 ? void 0 : onSelect([...selected, item.id]);
583
+ else if (selectedIDs) {
584
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect([...selectedIDs, item.id]);
528
585
  }
529
586
  }
530
587
  }
@@ -674,47 +731,69 @@ function InternalDataListBulkActions() {
674
731
  }
675
732
 
676
733
  function DataListHeaderCheckbox({ children }) {
677
- const { data, totalCount, selected = [], onSelectAll, onSelect, } = useDataListContext();
678
- if (!onSelectAll && !onSelect)
679
- return children;
680
734
  const { sm } = useResponsiveSizing();
681
- // Show "Deselect All" if breakpoint is sm or higher
735
+ const { data, totalCount } = useDataListContext();
736
+ const { canSelectAll, hasAtLeastOneSelected, isSelectAll, selectedCount, selectedIDs, selected, onSelectAll, onSelect, } = useBatchSelect();
737
+ // If there's no onSelectAll or onSelect, we don't need to render the checkbox.
738
+ if (!canSelectAll)
739
+ return children;
682
740
  const deselectText = sm ? "Deselect All" : "Deselect";
683
- const selectedLabel = selected.length ? `${selected.length} selected` : "";
684
- return (React__default["default"].createElement("div", { className: classnames__default["default"](styles$f.selectable) },
741
+ const selectedLabel = selectedCount ? `${selectedCount} selected` : "";
742
+ return (React__default["default"].createElement("div", { className: styles$f.selectable },
685
743
  React__default["default"].createElement("div", { className: classnames__default["default"](styles$f.selectAllCheckbox, {
686
- [styles$f.visible]: Boolean(onSelectAll),
744
+ [styles$f.visible]: canSelectAll,
687
745
  }) },
688
- React__default["default"].createElement(Checkbox.Checkbox, { checked: isAllSelected(), indeterminate: selected.length > 0 && !isAllSelected(), onChange: onSelectAll },
746
+ React__default["default"].createElement(Checkbox.Checkbox, { checked: isAllSelected(), indeterminate: isIndeterminate(), onChange: handleSelectAll },
689
747
  React__default["default"].createElement("div", { className: styles$f.srOnly }, selectedLabel))),
690
- React__default["default"].createElement(AnimatedSwitcher.AnimatedSwitcher, { switched: Boolean(selected.length), initialChild: children, switchTo: React__default["default"].createElement("div", { className: styles$f.batchSelectContainer },
748
+ React__default["default"].createElement(AnimatedSwitcher.AnimatedSwitcher, { switched: hasAtLeastOneSelected, initialChild: children, switchTo: React__default["default"].createElement("div", { className: styles$f.batchSelectContainer },
691
749
  React__default["default"].createElement("div", { className: styles$f.headerBatchSelect },
692
- React__default["default"].createElement(Text.Text, null,
693
- selected.length,
750
+ Boolean(selectedCount) && React__default["default"].createElement(Text.Text, null,
751
+ selectedCount,
694
752
  " selected"),
695
753
  React__default["default"].createElement(Button.Button, { label: deselectText, onClick: () => onSelect === null || onSelect === void 0 ? void 0 : onSelect([]), type: "tertiary" })),
696
754
  React__default["default"].createElement(InternalDataListBulkActions, null)) })));
755
+ function isIndeterminate() {
756
+ if (isSelectAll)
757
+ return selectedIDs.length > 0;
758
+ return selectedCount > 0 && !isAllSelected();
759
+ }
697
760
  function isAllSelected() {
761
+ if (isSelectAll)
762
+ return true;
698
763
  // If there's a totalCount, we can use that to accurately determine if the
699
764
  // user have "selected all".
700
765
  if (totalCount) {
701
- return totalCount === selected.length;
766
+ return totalCount === selectedCount;
702
767
  }
703
768
  // Otherwise, we'll use the total count of data. This is not as reliable, as
704
769
  // it's possible that the would select all loaded data while we're
705
770
  // loading more. It's still hard to get to that state as the load more
706
771
  // triggers before you see the last item.
707
- return data.length > 0 && selected.length >= data.length;
772
+ return data.length > 0 && selectedCount >= data.length;
773
+ }
774
+ function handleSelectAll() {
775
+ if (isAllSelected()) {
776
+ return onSelectAll === null || onSelectAll === void 0 ? void 0 : onSelectAll([]);
777
+ }
778
+ onSelectAll === null || onSelectAll === void 0 ? void 0 : onSelectAll({ totalCount: getTotalCount(), unselected: [] });
779
+ }
780
+ function getTotalCount() {
781
+ if (selected && "totalCount" in selected)
782
+ return selected.totalCount;
783
+ if (totalCount)
784
+ return totalCount;
785
+ return 0;
708
786
  }
709
787
  }
710
788
 
711
789
  function DataListHeader() {
712
790
  const breakpoints = useResponsiveSizing();
713
- const { headerVisibility = { xs: true, sm: true, md: true, lg: true, xl: true }, headers, selected, layoutBreakpoints, } = useDataListContext();
791
+ const { headerVisibility = { xs: true, sm: true, md: true, lg: true, xl: true }, headers, layoutBreakpoints, } = useDataListContext();
792
+ const { selectedIDs } = useBatchSelect();
714
793
  const size = getVisibleSize();
715
794
  const { layout } = useActiveLayout();
716
795
  const visible = headerVisibility[size];
717
- const noItemsSelected = (selected === null || selected === void 0 ? void 0 : selected.length) === 0;
796
+ const noItemsSelected = (selectedIDs === null || selectedIDs === void 0 ? void 0 : selectedIDs.length) === 0;
718
797
  if ((!visible && noItemsSelected) || !layout)
719
798
  return null;
720
799
  const headerData = generateHeaderElements(headers);
@@ -1123,18 +1202,18 @@ DataList.Search = DataListSearch;
1123
1202
  * Defines the group actions you could do on a single DataList item.
1124
1203
  */
1125
1204
  DataList.ItemActions = DataListItemActions;
1126
- /**
1127
- * Defines the group actions you could do on multiple DataList items.
1128
- */
1129
- DataList.BulkActions = DataListBulkActions;
1130
1205
  /**
1131
1206
  * Defines the action in a DataList. This should be used inside the
1132
1207
  * DataListItemActions component.
1133
1208
  */
1134
1209
  DataList.ItemAction = DataListAction;
1210
+ /**
1211
+ * Defines the group actions you could do on multiple DataList items.
1212
+ */
1213
+ DataList.BatchActions = DataListBulkActions;
1135
1214
  /**
1136
1215
  * Defines the batch action in a DataList. This should be used inside the
1137
- * DataListBulkActions component.
1216
+ * DataListBatchActions component.
1138
1217
  */
1139
1218
  DataList.BatchAction = function DataListBatchAction(props) {
1140
1219
  return React__default["default"].createElement(DataListAction, Object.assign({}, props));
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var FormatFile = require('../FormatFile-6423c3b5.js');
5
+ var FormatFile = require('../FormatFile-bdd09938.js');
6
6
  require('react');
7
7
  require('classnames');
8
8
  require('filesize');
@@ -12,9 +12,9 @@ require('react-router-dom');
12
12
  require('../Icon-405a216c.js');
13
13
  require('@jobber/design');
14
14
  require('../Typography-83915c54.js');
15
- require('../ConfirmationModal-285c3cbe.js');
15
+ require('../ConfirmationModal-467f6d4e.js');
16
16
  require('@jobber/hooks/useOnKeyDown');
17
- require('../Modal-c17cc105.js');
17
+ require('../Modal-7daf6831.js');
18
18
  require('react-dom');
19
19
  require('framer-motion');
20
20
  require('@jobber/hooks/useRefocusOnActivator');
@@ -5,7 +5,7 @@ var classnames = require('classnames');
5
5
  var getHumanReadableFileSize = require('filesize');
6
6
  var styleInject_es = require('./style-inject.es-9d2f5f4e.js');
7
7
  var Button = require('./Button-db6114a6.js');
8
- var ConfirmationModal = require('./ConfirmationModal-285c3cbe.js');
8
+ var ConfirmationModal = require('./ConfirmationModal-467f6d4e.js');
9
9
  var Glimmer = require('./Glimmer-cfa92a88.js');
10
10
  var Icon = require('./Icon-405a216c.js');
11
11
  var Typography = require('./Typography-83915c54.js');
@@ -6,7 +6,7 @@ var React = require('react');
6
6
  var classnames = require('classnames');
7
7
  var styleInject_es = require('../style-inject.es-9d2f5f4e.js');
8
8
  var LightBox = require('../LightBox-93ef99b9.js');
9
- var FormatFile = require('../FormatFile-6423c3b5.js');
9
+ var FormatFile = require('../FormatFile-bdd09938.js');
10
10
  var Button = require('../Button-db6114a6.js');
11
11
  require('framer-motion');
12
12
  require('react-dom');
@@ -16,8 +16,8 @@ require('@jobber/hooks/useOnKeyDown');
16
16
  require('@jobber/hooks/useFocusTrap');
17
17
  require('../ButtonDismiss-1ccb84ec.js');
18
18
  require('filesize');
19
- require('../ConfirmationModal-285c3cbe.js');
20
- require('../Modal-c17cc105.js');
19
+ require('../ConfirmationModal-467f6d4e.js');
20
+ require('../Modal-7daf6831.js');
21
21
  require('../Heading-4cde9989.js');
22
22
  require('../Typography-83915c54.js');
23
23
  require('../Content-2ca1ffe1.js');
@@ -1,7 +1,7 @@
1
1
  import React, { ReactNode } from "react";
2
2
  import sizes from "./Sizes.css";
3
3
  import { ButtonProps } from "../Button";
4
- interface ModalProps {
4
+ export interface ModalProps {
5
5
  /**
6
6
  * @default false
7
7
  */
@@ -19,4 +19,3 @@ interface ModalProps {
19
19
  onRequestClose?(): void;
20
20
  }
21
21
  export declare function Modal({ open, title, size, dismissible, children, primaryAction, secondaryAction, tertiaryAction, onRequestClose, }: ModalProps): React.ReactPortal;
22
- export {};
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var Modal = require('../Modal-c17cc105.js');
5
+ var Modal = require('../Modal-7daf6831.js');
6
6
  require('react');
7
7
  require('react-dom');
8
8
  require('classnames');
@@ -31,7 +31,7 @@ function Modal({ open = false, title, size, dismissible = true, children, primar
31
31
  useRefocusOnActivator.useRefocusOnActivator(open);
32
32
  const modalRef = useFocusTrap.useFocusTrap(open);
33
33
  useOnKeyDown.useOnKeyDown(handleRequestClose, "Escape");
34
- const template = (React__default["default"].createElement(framerMotion.AnimatePresence, null, open && (React__default["default"].createElement("div", { ref: modalRef, className: styles.container, tabIndex: 0 },
34
+ const template = (React__default["default"].createElement(framerMotion.AnimatePresence, null, open && (React__default["default"].createElement("div", { ref: modalRef, role: "dialog", className: styles.container, tabIndex: 0 },
35
35
  React__default["default"].createElement(framerMotion.motion.div, { key: styles.overlay, className: styles.overlay, onClick: onRequestClose, initial: { opacity: 0 }, animate: { opacity: 0.8 }, exit: { opacity: 0 }, transition: { duration: 0.2 } }),
36
36
  React__default["default"].createElement(framerMotion.motion.div, { key: styles.modal, className: modalClassName, initial: { scale: 0.9, opacity: 0 }, animate: { scale: 1, opacity: 1 }, exit: { scale: 0.9, opacity: 0 }, transition: {
37
37
  duration: 0.2,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components",
3
- "version": "4.61.0",
3
+ "version": "4.63.0",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -83,5 +83,5 @@
83
83
  "> 1%",
84
84
  "IE 10"
85
85
  ],
86
- "gitHead": "c4ebb94961e32303004d56d30daf6f6489feed0e"
86
+ "gitHead": "8acc3a5d06d6e04c2718e9ed81990642eaa10157"
87
87
  }