@react-fabric/data 1.0.0-beta.9 → 1.0.2

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 (141) hide show
  1. package/{src/histogram/Histogram.module.css → css/_histogram.css} +16 -13
  2. package/css/styles.css +24 -0
  3. package/dist/cjs/checklist/CheckList.js +59 -33
  4. package/dist/cjs/histogram/Histogram.js +41 -26
  5. package/dist/cjs/hooks/useFilteredList.js +100 -17
  6. package/dist/cjs/hooks/usePagination.js +29 -8
  7. package/dist/cjs/hooks/useSelectableList.js +30 -5
  8. package/dist/cjs/i18n/{ar.json.js → ar/data.json.js} +8 -1
  9. package/dist/cjs/i18n/{en.json.js → en/data.json.js} +8 -1
  10. package/dist/cjs/i18n/index.js +4 -4
  11. package/dist/cjs/index.js +30 -0
  12. package/dist/cjs/json/JsonViewer.js +80 -43
  13. package/dist/cjs/pagination/Pagination.js +24 -15
  14. package/dist/cjs/table/AddColumn.js +5 -5
  15. package/dist/cjs/table/BodyCell.js +20 -13
  16. package/dist/cjs/table/BodyRow.js +41 -28
  17. package/dist/cjs/table/CheckboxCell.js +10 -9
  18. package/dist/cjs/table/ColumnFilters.js +100 -0
  19. package/dist/cjs/table/EmptyCell.js +4 -4
  20. package/dist/cjs/table/HeaderCell.js +79 -53
  21. package/dist/cjs/table/HtmlTable.js +84 -0
  22. package/dist/cjs/table/{Context.js → Provider.js} +8 -10
  23. package/dist/cjs/table/Table.js +99 -33
  24. package/dist/cjs/{tree/types.js → table/context.js} +9 -5
  25. package/dist/cjs/table/types.js +29 -0
  26. package/dist/cjs/table/useResizer.js +10 -10
  27. package/dist/cjs/table/useTableColumns.js +4 -4
  28. package/dist/cjs/table/useVirtualTable.js +129 -22
  29. package/dist/cjs/tree/TreeNode.js +66 -46
  30. package/dist/cjs/tree/TreePanel.js +60 -24
  31. package/dist/cjs/tree/useTree.js +212 -47
  32. package/dist/cjs/tree/utils.js +63 -34
  33. package/dist/cjs/utils/getColor.js +58 -0
  34. package/dist/cjs/virtual/Gallery.js +119 -61
  35. package/dist/cjs/virtual/List.js +116 -55
  36. package/dist/cjs/virtual/Scroller.js +16 -8
  37. package/dist/esm/checklist/CheckList.js +61 -35
  38. package/dist/esm/histogram/Histogram.js +43 -28
  39. package/dist/esm/hooks/useFilteredList.js +101 -18
  40. package/dist/esm/hooks/usePagination.js +29 -8
  41. package/dist/esm/hooks/useSelectableList.js +30 -5
  42. package/dist/esm/i18n/{ar.json.js → ar/data.json.js} +8 -2
  43. package/dist/esm/i18n/{en.json.js → en/data.json.js} +8 -2
  44. package/dist/esm/i18n/index.js +2 -2
  45. package/dist/esm/index.js +31 -0
  46. package/dist/esm/json/JsonViewer.js +82 -45
  47. package/dist/esm/pagination/Pagination.js +25 -16
  48. package/dist/esm/table/AddColumn.js +6 -6
  49. package/dist/esm/table/BodyCell.js +21 -14
  50. package/dist/esm/table/BodyRow.js +41 -28
  51. package/dist/esm/table/CheckboxCell.js +11 -10
  52. package/dist/esm/table/ColumnFilters.js +100 -0
  53. package/dist/esm/table/EmptyCell.js +4 -4
  54. package/dist/esm/table/HeaderCell.js +80 -54
  55. package/dist/esm/table/HtmlTable.js +84 -0
  56. package/dist/esm/table/{Context.js → Provider.js} +11 -12
  57. package/dist/esm/table/Table.js +100 -34
  58. package/dist/esm/{tree/types.js → table/context.js} +8 -4
  59. package/dist/esm/table/types.js +29 -0
  60. package/dist/esm/table/useResizer.js +10 -10
  61. package/dist/esm/table/useTableColumns.js +4 -4
  62. package/dist/esm/table/useVirtualTable.js +131 -24
  63. package/dist/esm/tree/TreeNode.js +69 -49
  64. package/dist/esm/tree/TreePanel.js +62 -26
  65. package/dist/esm/tree/useTree.js +215 -50
  66. package/dist/esm/tree/utils.js +64 -35
  67. package/dist/esm/utils/getColor.js +58 -0
  68. package/dist/esm/virtual/Gallery.js +121 -63
  69. package/dist/esm/virtual/List.js +118 -57
  70. package/dist/esm/virtual/Scroller.js +17 -9
  71. package/dist/types/checklist/CheckList.d.ts +25 -6
  72. package/dist/types/histogram/Histogram.d.ts +28 -6
  73. package/dist/types/hooks/useFilteredList.d.ts +62 -5
  74. package/dist/types/hooks/usePagination.d.ts +18 -2
  75. package/dist/types/hooks/useSelectableList.d.ts +24 -1
  76. package/dist/types/index.d.ts +27 -0
  77. package/dist/types/json/JsonViewer.d.ts +23 -3
  78. package/dist/types/pagination/Pagination.d.ts +8 -2
  79. package/dist/types/table/AddColumn.d.ts +2 -2
  80. package/dist/types/table/BodyCell.d.ts +2 -2
  81. package/dist/types/table/BodyRow.d.ts +9 -9
  82. package/dist/types/table/CheckboxCell.d.ts +3 -3
  83. package/dist/types/table/ColumnFilters.d.ts +12 -0
  84. package/dist/types/table/EmptyCell.d.ts +1 -1
  85. package/dist/types/table/HeaderCell.d.ts +1 -1
  86. package/dist/types/table/HtmlTable.d.ts +21 -0
  87. package/dist/types/table/Provider.d.ts +2 -0
  88. package/dist/types/table/Table.d.ts +10 -1
  89. package/dist/types/table/context.d.ts +7 -0
  90. package/dist/types/table/types.d.ts +52 -10
  91. package/dist/types/table/useResizer.d.ts +1 -1
  92. package/dist/types/table/useTableColumns.d.ts +1 -1
  93. package/dist/types/table/useVirtualTable.d.ts +31 -12
  94. package/dist/types/tree/TreeNode.d.ts +1 -1
  95. package/dist/types/tree/TreePanel.d.ts +36 -1
  96. package/dist/types/tree/types.d.ts +75 -28
  97. package/dist/types/tree/useTree.d.ts +8 -12
  98. package/dist/types/tree/utils.d.ts +15 -8
  99. package/dist/types/utils/getColor.d.ts +43 -0
  100. package/dist/types/virtual/Gallery.d.ts +37 -6
  101. package/dist/types/virtual/List.d.ts +38 -6
  102. package/dist/types/virtual/Scroller.d.ts +1 -1
  103. package/package.json +17 -19
  104. package/src/checklist/CheckList.tsx +68 -71
  105. package/src/histogram/Histogram.tsx +47 -48
  106. package/src/hooks/useFilteredList.tsx +111 -28
  107. package/src/hooks/usePagination.tsx +39 -39
  108. package/src/hooks/useSelectableList.ts +45 -33
  109. package/src/i18n/{ar.json → ar/data.json} +5 -0
  110. package/src/i18n/{en.json → en/data.json} +5 -0
  111. package/src/i18n/index.ts +2 -2
  112. package/src/index.ts +23 -17
  113. package/src/json/JsonViewer.tsx +89 -120
  114. package/src/pagination/Pagination.tsx +104 -105
  115. package/src/table/AddColumn.tsx +6 -15
  116. package/src/table/BodyCell.tsx +27 -36
  117. package/src/table/BodyRow.tsx +36 -59
  118. package/src/table/CheckboxCell.tsx +12 -15
  119. package/src/table/ColumnFilters.tsx +93 -0
  120. package/src/table/EmptyCell.tsx +5 -18
  121. package/src/table/HeaderCell.tsx +72 -53
  122. package/src/table/HtmlTable.tsx +106 -0
  123. package/src/table/{Context.tsx → Provider.tsx} +11 -28
  124. package/src/table/Table.tsx +98 -83
  125. package/src/table/context.ts +33 -0
  126. package/src/table/types.ts +53 -27
  127. package/src/table/useResizer.ts +5 -9
  128. package/src/table/useTableColumns.ts +4 -6
  129. package/src/table/useVirtualTable.ts +136 -37
  130. package/src/tree/TreeNode.tsx +91 -86
  131. package/src/tree/TreePanel.tsx +87 -51
  132. package/src/tree/types.ts +85 -46
  133. package/src/tree/useTree.ts +210 -65
  134. package/src/tree/utils.ts +81 -62
  135. package/src/utils/getColor.ts +55 -0
  136. package/src/virtual/Gallery.tsx +129 -120
  137. package/src/virtual/List.tsx +124 -91
  138. package/src/virtual/Scroller.tsx +25 -9
  139. package/dist/cjs/histogram/Histogram.module.css +0 -1
  140. package/dist/esm/histogram/Histogram.module.css +0 -1
  141. package/dist/types/table/Context.d.ts +0 -8
@@ -21,32 +21,19 @@
21
21
  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  */
23
23
 
24
- import { Badge, CoreIcons, EmptyContent, Icon } from "@react-fabric/core";
25
- import {
26
- type BadgeType,
27
- type TestProps,
28
- } from "@react-fabric/core/dist/types/types";
29
- import { isString } from "@react-fabric/utilities";
30
- import {
31
- Fragment,
32
- isValidElement,
33
- useEffect,
34
- useMemo,
35
- useState,
36
- type FC,
37
- } from "react";
24
+ import { Badge, EmptyContent, Icon } from "@react-fabric/core";
25
+ import type { BadgeType } from "@react-fabric/core/dist/types/components/badge/Badge";
26
+ import { type TestProps } from "@react-fabric/core/dist/types/types";
27
+ import { Fragment, useEffect, useMemo, useState } from "react";
38
28
  import { useTranslation } from "react-i18next";
39
- import {
40
- useSelectableList,
41
- type SelectableProps,
42
- } from "../hooks/useSelectableList";
29
+ import { useSelectableList, type SelectableProps } from "../hooks/useSelectableList";
43
30
 
44
- export interface CheckListItem extends KeyValue {
31
+ export interface CheckListItem {
45
32
  id: string;
46
33
  label: string;
47
- icon?: string;
34
+ icon?: string | React.ReactElement;
48
35
  badge?: BadgeType;
49
- isDisabled?: boolean;
36
+ disabled?: boolean;
50
37
  }
51
38
 
52
39
  export interface CheckListProps extends SelectableProps, TestProps {
@@ -70,43 +57,34 @@ export interface CheckListProps extends SelectableProps, TestProps {
70
57
  sortItems?: boolean;
71
58
  }
72
59
 
73
- const CheckItem: FC<
74
- Omit<CheckListItem, "selected"> & {
75
- selected?: number;
76
- allowNegative?: boolean;
77
- }
78
- > = ({
60
+ function CheckItem({
79
61
  id,
80
62
  allowNegative,
81
63
  selected,
82
64
  icon,
83
65
  label,
84
- isDisabled,
66
+ disabled,
85
67
  onClick,
86
68
  badge,
87
- ...aria
88
69
  }: Omit<CheckListItem, "selected"> & {
70
+ onClick: (id: string, isNegative: boolean) => void;
89
71
  selected?: number;
90
72
  allowNegative?: boolean;
91
- }) => {
73
+ }) {
92
74
  return (
93
75
  <div
94
76
  role="none"
95
77
  className="ax-checkList__item"
96
- data-disabled={isDisabled}
78
+ data-disabled={disabled}
97
79
  onClick={() => onClick(id, false)}
98
- onContextMenu={(e) => [
99
- allowNegative && onClick(id, true),
100
- e.preventDefault(),
101
- ]}
102
- {...aria}
80
+ onContextMenu={(e) => [allowNegative && onClick(id, true), e.preventDefault()]}
103
81
  >
104
82
  {!allowNegative && (
105
83
  <Icon
106
84
  data-type="checkbox"
107
85
  className="ax-checkList__checkbox"
108
86
  data-selected={selected === 1}
109
- icon={selected ? CoreIcons.checkboxInt : CoreIcons.checkboxOff}
87
+ icon={selected ? "icon-[mdi--checkbox-marked]" : "icon-[mdi--checkbox-blank-outline]"}
110
88
  />
111
89
  )}
112
90
  {allowNegative && (
@@ -116,40 +94,66 @@ const CheckItem: FC<
116
94
  className="ax-checkList__checkbox"
117
95
  icon={
118
96
  selected === 1
119
- ? CoreIcons.expand
97
+ ? "icon-[mdi--plus-box-outline]"
120
98
  : selected === -1
121
- ? CoreIcons.collapse
122
- : CoreIcons.checkboxOff
99
+ ? "icon-[mdi--minus-box-outline]"
100
+ : "icon-[mdi--checkbox-blank-outline]"
123
101
  }
124
102
  data-selected={selected}
125
103
  />
126
104
  <div className="ax-checkList__checkbox">
127
- <Icon
128
- data-type="positive"
105
+ <button
106
+ type="button"
107
+ className="size-fit leading-0 cursor-pointer"
129
108
  onClick={(e) => [(onClick(id, false), e.stopPropagation())]}
130
- icon={selected === 1 ? CoreIcons.expandActive : CoreIcons.expand}
131
- data-selected={selected === 1}
132
- />
133
- <Icon
134
- data-type="negative"
109
+ >
110
+ <Icon
111
+ data-type="positive"
112
+ icon={selected === 1 ? "icon-[mdi--plus-box]" : "icon-[mdi--plus-box-outline]"}
113
+ data-selected={selected === 1}
114
+ />
115
+ </button>
116
+ <button
117
+ type="button"
118
+ className="size-fit leading-0 cursor-pointer"
135
119
  onClick={(e) => [(onClick(id, true), e.stopPropagation())]}
136
- icon={
137
- selected === -1 ? CoreIcons.collapseActive : CoreIcons.collapse
138
- }
139
- data-selected={selected === -1}
140
- />
120
+ >
121
+ <Icon
122
+ data-type="negative"
123
+ icon={selected === -1 ? "icon-[mdi--minus-box]" : "icon-[mdi--minus-box-outline]"}
124
+ data-selected={selected === -1}
125
+ />
126
+ </button>
141
127
  </div>
142
128
  </Fragment>
143
129
  )}
144
- {isValidElement(icon) && icon}
145
- {isString(icon) && <Icon icon={icon} />}
130
+ {icon && <Icon icon={icon} />}
146
131
  <label>{label}</label>
147
- <Badge {...badge} />
132
+ {badge && <Badge {...(badge as KeyValue)} />}
148
133
  </div>
149
134
  );
150
- };
135
+ }
151
136
 
152
- export const CheckList: FC<CheckListProps> = ({
137
+ /**
138
+ * CheckList component to display a list of selectable items with checkboxes.
139
+ * It supports selection, deselection, and allows for negative selections.
140
+ *
141
+ * @example
142
+ * ```jsx
143
+ * <CheckList
144
+ * items={[
145
+ * { id: "1", label: "Item 1", icon: "icon1" },
146
+ * { id: "2", label: "Item 2", icon: "icon2", badge: { text: "New" } },
147
+ * ]}
148
+ * maxCount={5}
149
+ * allowNegative={true}
150
+ * emptyMessage="No items available"
151
+ * onChange={(selectedItems) => console.log(selectedItems)}
152
+ * onClick={(itemId, isNegative) => console.log(itemId, isNegative)}
153
+ * />
154
+ * ```
155
+ */
156
+ export function CheckList({
153
157
  items = [],
154
158
  maxCount = 0,
155
159
  allowNegative,
@@ -160,7 +164,7 @@ export const CheckList: FC<CheckListProps> = ({
160
164
  onChange,
161
165
  onClick,
162
166
  ...aria
163
- }: CheckListProps) => {
167
+ }: CheckListProps) {
164
168
  const { t } = useTranslation("data");
165
169
  const { selection, toggleSelection } = useSelectableList({
166
170
  items,
@@ -172,6 +176,7 @@ export const CheckList: FC<CheckListProps> = ({
172
176
  const [showMore, setShowMore] = useState(false);
173
177
 
174
178
  useEffect(() => {
179
+ // eslint-disable-next-line @eslint-react/set-state-in-effect
175
180
  setShowMore(false);
176
181
  }, [items]);
177
182
 
@@ -193,8 +198,9 @@ export const CheckList: FC<CheckListProps> = ({
193
198
  <div {...aria}>
194
199
  {listItems.map((item, index) => (
195
200
  <CheckItem
196
- {...item}
201
+ // eslint-disable-next-line @eslint-react/no-array-index-key
197
202
  key={index}
203
+ {...item}
198
204
  onClick={toggleSelection}
199
205
  allowNegative={allowNegative}
200
206
  selected={selection[item.id]}
@@ -202,21 +208,12 @@ export const CheckList: FC<CheckListProps> = ({
202
208
  ))}
203
209
  {maxCount > 0 && items.length > maxCount && (
204
210
  <div className="ax-moreLink">
205
- <span
206
- role="none"
207
- className="link"
208
- onClick={() => setShowMore(!showMore)}
209
- >
211
+ <span role="none" className="link" onClick={() => setShowMore(!showMore)}>
210
212
  ...{t(`core:action.${showMore ? "less" : "more"}`)}
211
213
  </span>
212
214
  </div>
213
215
  )}
214
- {listItems.length === 0 && (
215
- <EmptyContent
216
- className="text-sm"
217
- message={emptyMessage ?? t("checkList.empty")}
218
- />
219
- )}
216
+ {listItems.length === 0 && <EmptyContent className="text-sm" message={emptyMessage ?? t("checkList.empty")} />}
220
217
  </div>
221
218
  );
222
- };
219
+ }
@@ -21,23 +21,12 @@
21
21
  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  */
23
23
 
24
- import {
25
- ActionLabel,
26
- Button,
27
- CoreIcons,
28
- EmptyContent,
29
- Icon,
30
- } from "@react-fabric/core";
31
- import {
32
- type ColorType,
33
- type PaletteType,
34
- type TestProps,
35
- } from "@react-fabric/core/dist/types/types";
36
- import { compareValues, Format } from "@react-fabric/utilities";
37
- import classNames from "classnames";
24
+ import { ActionLabel, Button, EmptyContent, Icon } from "@react-fabric/core";
25
+ import type { CustomColors, TestProps } from "@react-fabric/core/dist/types/types";
26
+ import { cn, compareValues, Format } from "@react-fabric/utilities";
38
27
  import { useMemo } from "react";
39
28
  import { useTranslation } from "react-i18next";
40
- import classes from "./Histogram.module.css";
29
+ import { getColor } from "../utils/getColor";
41
30
 
42
31
  const emptyIcon =
43
32
  "M20.84 22.73L11.11 13H3V11H9.11L6.11 8H3V6H4.11L1.11 3L2.39 1.73L22.11 21.46L20.84 22.73M15 11H14.2L15 11.8V11M21 8V6H9.2L11.2 8H21M3 18H9V16H3V18Z";
@@ -62,7 +51,7 @@ export interface HistogramProps extends TestProps {
62
51
  /**
63
52
  * histogram bar color
64
53
  */
65
- color?: ColorType | PaletteType;
54
+ color?: CustomColors;
66
55
  /**
67
56
  * sorted list
68
57
  */
@@ -70,27 +59,49 @@ export interface HistogramProps extends TestProps {
70
59
  /**
71
60
  * title when histo list is empty
72
61
  */
73
- emptyTitle?: string | JSX.Element;
62
+ emptyTitle?: string | React.ReactElement;
74
63
  /**
75
64
  * message when histo list is empty
76
65
  */
77
- emptyMessage?: string | JSX.Element;
66
+ emptyMessage?: string | React.ReactElement;
78
67
  /**
79
68
  * handle histo click
80
69
  */
81
70
  onClick?: (id: string, negative?: true) => void;
82
71
  }
83
72
 
84
- export const Histogram = ({
73
+ /**
74
+ * Histogram component displays a bar chart of items with counts.
75
+ * It allows users to visualize the distribution of counts across different items.
76
+ * The component supports sorting, custom colors, and click handling for each item.
77
+ * It also provides an empty state when there are no items to display.
78
+ *
79
+ * @example
80
+ * ```jsx
81
+ * <Histogram
82
+ * items={[
83
+ * { id: "1", label: "Item 1", count: 10 },
84
+ * { id: "2", label: "Item 2", count: 20 },
85
+ * { id: "3", label: "Item 3", count: 5, disabled: true },
86
+ * ]}
87
+ * total={30}
88
+ * color="primary-500"
89
+ * sortBy="count"
90
+ * emptyTitle="No Data Available"
91
+ * emptyMessage="Please check back later."
92
+ * onClick={(id, negative) => console.log(`Clicked on ${id}`, negative ? "Negative" : "Positive")}
93
+ * />
94
+ * ```
95
+ */
96
+ export function Histogram({
85
97
  items = [],
86
98
  total,
87
- color = "primary",
99
+ color = "primary-500",
88
100
  sortBy = "count",
89
101
  emptyTitle,
90
102
  emptyMessage,
91
103
  onClick,
92
- ...aria
93
- }: HistogramProps) => {
104
+ }: HistogramProps) {
94
105
  const { t } = useTranslation("data");
95
106
  const maxTotal = useMemo(() => {
96
107
  if (total) return total;
@@ -98,19 +109,16 @@ export const Histogram = ({
98
109
  }, [total, items]);
99
110
 
100
111
  const sorted = useMemo(() => {
101
- return items.sort(
102
- compareValues(sortBy === "label" ? "asc" : "desc", sortBy),
103
- );
112
+ return items.sort(compareValues(sortBy === "label" ? "asc" : "desc", sortBy));
104
113
  }, [items, sortBy]);
105
114
 
106
115
  return (
107
116
  <div
108
- className={classes.histogram}
109
- {...aria}
117
+ className={"fabric-histogram from-0% to-100%"}
110
118
  style={
111
119
  {
112
- "--color": `var(--${color})`,
113
- } as AnyObject
120
+ "--color": getColor(color),
121
+ } as React.CSSProperties
114
122
  }
115
123
  >
116
124
  {sorted.length === 0 && (
@@ -125,20 +133,16 @@ export const Histogram = ({
125
133
  role="none"
126
134
  key={item.id}
127
135
  data-disabled={item.count === 0 || item.disabled}
128
- className={classNames(
136
+ className={cn(
129
137
  "relative cursor-pointer flex flex-nowrap items-center select-none overflow-hidden min-h-6 my-px",
130
- classes.histoLabel,
131
- ((item.total ?? item.count) === 0 || item.disabled) &&
132
- "pointer-events-none",
138
+ "fabric-histoLabel",
139
+ ((item.total ?? item.count) === 0 || item.disabled) && "pointer-events-none",
133
140
  )}
134
141
  data-testid={item.id}
135
142
  onClick={(e) => [onClick?.(item.id), e.stopPropagation()]}
136
143
  >
137
144
  <div
138
- className={classNames(
139
- "absolute z-0 inset-0 opacity-50 min-w-2 ltr:bg-gradient-to-r rtl:bg-gradient-to-l from-0% to-100%",
140
- classes.histoBar,
141
- )}
145
+ className={cn("absolute z-0 inset-0 opacity-50 min-w-2 ltr:bg-linear-to-r rtl:bg-linear-to-l", "fabric-histoBar")}
142
146
  style={{
143
147
  width: `${Math.min(item.count / maxTotal, 1) * 100}%`,
144
148
  }}
@@ -150,32 +154,27 @@ export const Histogram = ({
150
154
  key="negative"
151
155
  color="danger"
152
156
  size="sm"
153
- icon={CoreIcons.funnelMinus}
157
+ icon="icon-[mdi--filter-minus]"
154
158
  aria-label="Filter Negative"
155
- onClick={() => onClick?.(item.id, true)}
159
+ onClick={(e) => (e?.stopPropagation(), onClick?.(item.id, true))}
156
160
  />,
157
161
  <Button
158
162
  key="positive"
159
163
  color="primary"
160
164
  size="sm"
161
- icon={CoreIcons.funnelPlus}
165
+ icon="icon-[mdi--filter-plus]"
162
166
  aria-label="Filter"
163
- onClick={() => onClick?.(item.id)}
167
+ onClick={(e) => (e?.stopPropagation(), onClick?.(item.id))}
164
168
  />,
165
169
  ]}
166
170
  >
167
171
  {item.label}
168
172
  </ActionLabel>
169
- <span
170
- className={classNames(
171
- "z-1 rounded-full text-xs font-medium px-2 py-1 leading-none",
172
- classes.histoBadge,
173
- )}
174
- >
173
+ <span className={cn("z-1 rounded-full text-xs font-medium px-2 py-1 leading-none", "fabric-histoBadge")}>
175
174
  {Format.number(item.count)}
176
175
  </span>
177
176
  </div>
178
177
  ))}
179
178
  </div>
180
179
  );
181
- };
180
+ }
@@ -21,26 +21,28 @@
21
21
  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  */
23
23
 
24
- import { isString, matchString } from "@react-fabric/utilities";
25
- import {
26
- useCallback,
27
- useDeferredValue,
28
- useEffect,
29
- useState,
30
- useTransition,
31
- } from "react";
24
+ import { isArray, isString, matchString } from "@react-fabric/utilities";
25
+ import { useCallback, useDeferredValue, useEffect, useState, useTransition } from "react";
32
26
 
33
- const filterList = (
34
- items: AnyObject[],
35
- query: AnyObject = "",
36
- matcher?: AnyObject,
37
- ) => {
38
- const newList: AnyObject[] = [];
27
+ function filterList<T extends AnyObject = KeyValue>(
28
+ items: Array<T & { label?: string }>,
29
+ query: string | string[] = "",
30
+ matchAll: boolean = true,
31
+ matcher?: (option: T, query: string | string[]) => boolean,
32
+ ) {
33
+ const newList: T[] = [];
39
34
  items.forEach((item) => {
40
35
  if (matcher) {
41
- return matcher?.(item, query) && newList.push(item);
36
+ if (matcher?.(item, query)) return newList.push(item);
42
37
  }
43
- if (query && isString(query)) {
38
+ if (query && isArray(query) && query.length > 0) {
39
+ if (matchAll && query.every((q) => matchString(item.label ?? item.toString(), q))) {
40
+ return newList.push(item);
41
+ }
42
+ if (!matchAll && query.some((q) => matchString(item.label ?? item.toString(), q))) {
43
+ return newList.push(item);
44
+ }
45
+ } else if (query && isString(query)) {
44
46
  if (matchString(item.label ?? item.toString(), query)) {
45
47
  return newList.push(item);
46
48
  }
@@ -49,30 +51,111 @@ const filterList = (
49
51
  }
50
52
  });
51
53
  return newList;
52
- };
54
+ }
55
+
56
+ interface RetType<T, Q> {
57
+ onSearch: (query?: Q) => void;
58
+ query: Q;
59
+ filteredList: T[];
60
+ isSearching: boolean;
61
+ }
62
+
63
+ /**
64
+ * Custom hook to filter a list of items based on a query.
65
+ * It provides a way to search through the items and returns the filtered list.
66
+ * The hook uses a deferred value to optimize performance during transitions.
67
+ * It also allows for a custom matcher function to define how items should be matched against the query.
68
+ *
69
+ * @param items - The list of items to filter.
70
+ * @param matchAll - Match all phrases in the query list against the items.
71
+ * @returns An object containing the search function, current query, filtered list, and a boolean indicating if a search is in progress.
72
+ *
73
+ * @example
74
+ * ```jsx
75
+ * useFilteredList(items, (item, query) => {
76
+ * return item.name.includes(query);
77
+ * });
78
+ * ```
79
+ *
80
+ * @see {@link https://adarshpastakia.github.io/react-fabric/?path=/docs/data-usefilteredlist--docs}
81
+ */
82
+ export function useFilteredList<T extends AnyObject = KeyValue, Q extends AnyObject = string>(
83
+ items: T[],
84
+ matchAll?: boolean,
85
+ ): RetType<T, Q>;
86
+
87
+ /**
88
+ * Custom hook to filter a list of items based on a query.
89
+ * It provides a way to search through the items and returns the filtered list.
90
+ * The hook uses a deferred value to optimize performance during transitions.
91
+ * It also allows for a custom matcher function to define how items should be matched against the query.
92
+ *
93
+ * @param items - The list of items to filter.
94
+ * @param matcher - An optional function to match items against the query.
95
+ * @returns An object containing the search function, current query, filtered list, and a boolean indicating if a search is in progress.
96
+ *
97
+ * @example
98
+ * ```jsx
99
+ * useFilteredList(items, (item, query) => {
100
+ * return item.name.includes(query);
101
+ * });
102
+ * ```
103
+ */
104
+ export function useFilteredList<T extends AnyObject = KeyValue, Q extends AnyObject = string>(
105
+ items: T[],
106
+ matcher?: (item: T, query: Q) => boolean,
107
+ ): RetType<T, Q>;
108
+
109
+ /**
110
+ * Custom hook to filter a list of items based on a query.
111
+ * It provides a way to search through the items and returns the filtered list.
112
+ * The hook uses a deferred value to optimize performance during transitions.
113
+ * It also allows for a custom matcher function to define how items should be matched against the query.
114
+ *
115
+ * @param items - The list of items to filter.
116
+ * @param matchAll - Match all phrases in the query list against the items.
117
+ * @param matcher - An optional function to match items against the query.
118
+ * @returns An object containing the search function, current query, filtered list, and a boolean indicating if a search is in progress.
119
+ *
120
+ * @example
121
+ * ```jsx
122
+ * useFilteredList(items, (item, query) => {
123
+ * return item.name.includes(query);
124
+ * });
125
+ * ```
126
+ */
127
+ export function useFilteredList<T extends AnyObject = KeyValue, Q extends AnyObject = string>(
128
+ items: T[],
129
+ matchAll?: boolean,
130
+ matcher?: (item: T, query: Q) => boolean,
131
+ ): RetType<T, Q>;
53
132
 
54
- export const useFilteredList = <
55
- T extends AnyObject = KeyValue,
56
- Q extends AnyObject = string,
57
- >(
133
+ export function useFilteredList<T extends AnyObject = KeyValue, Q extends AnyObject = string>(
58
134
  items: T[],
135
+ matchAll?: boolean | ((item: T, query: Q) => boolean),
59
136
  matcher?: (item: T, query: Q) => boolean,
60
- ) => {
137
+ ) {
61
138
  const [query, setQuery] = useState<Q>();
62
139
  const _items = useDeferredValue(items ?? []);
63
- const [filteredList, setFilteredList] = useState<T[]>([]);
140
+ const [filteredList, setFilteredList] = useState<T[]>(() => []);
64
141
  const [isSearching, startTransition] = useTransition();
65
142
 
143
+ const matchingAll = typeof matchAll === "boolean" ? matchAll : true;
144
+ const matcherFn = typeof matchAll === "function" ? matchAll : matcher;
145
+
66
146
  const filterItems = useCallback(
67
- (items: T[], query?: Q) =>
68
- startTransition(() => setFilteredList(filterList(items, query, matcher))),
147
+ (items: T[], query?: Q, matchingAll?: boolean) =>
148
+ // eslint-disable-next-line @eslint-react/set-state-in-effect
149
+ startTransition(() => setFilteredList(filterList(items as never, query as never, matchingAll, matcherFn as never))),
150
+ // eslint-disable-next-line @eslint-react/exhaustive-deps
69
151
  [],
70
152
  );
71
153
 
72
154
  useEffect(() => {
73
- if (query) filterItems(_items, query);
155
+ if (query) filterItems(_items, query, matchingAll);
156
+ // eslint-disable-next-line @eslint-react/set-state-in-effect
74
157
  else setFilteredList(items);
75
- }, [query, _items]);
158
+ }, [query, matchingAll, _items, filterItems, items]);
76
159
 
77
160
  return { onSearch: setQuery, query, filteredList, isSearching };
78
- };
161
+ }