@react-fabric/data 1.0.0-beta.5 → 1.0.0-beta.52

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 (109) 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 +48 -16
  4. package/dist/cjs/histogram/Histogram.js +48 -10
  5. package/dist/cjs/hooks/useFilteredList.js +20 -0
  6. package/dist/cjs/hooks/usePagination.js +27 -4
  7. package/dist/cjs/hooks/useSelectableList.js +29 -0
  8. package/dist/cjs/i18n/ar.json.js +8 -1
  9. package/dist/cjs/i18n/en.json.js +8 -1
  10. package/dist/cjs/json/JsonViewer.js +67 -27
  11. package/dist/cjs/pagination/Pagination.js +10 -0
  12. package/dist/cjs/table/AddColumn.js +1 -1
  13. package/dist/cjs/table/BodyCell.js +38 -5
  14. package/dist/cjs/table/BodyRow.js +107 -0
  15. package/dist/cjs/table/CheckboxCell.js +1 -1
  16. package/dist/cjs/table/ColumnFilters.js +97 -0
  17. package/dist/cjs/table/HeaderCell.js +70 -37
  18. package/dist/cjs/table/Table.js +113 -71
  19. package/dist/cjs/table/types.js +29 -0
  20. package/dist/cjs/table/useResizer.js +4 -4
  21. package/dist/cjs/table/useVirtualTable.js +122 -18
  22. package/dist/cjs/tree/TreeNode.js +45 -25
  23. package/dist/cjs/tree/TreePanel.js +28 -10
  24. package/dist/cjs/tree/useTree.js +97 -15
  25. package/dist/cjs/tree/utils.js +20 -5
  26. package/dist/cjs/virtual/Gallery.js +84 -35
  27. package/dist/cjs/virtual/List.js +82 -32
  28. package/dist/esm/checklist/CheckList.js +50 -18
  29. package/dist/esm/histogram/Histogram.js +49 -11
  30. package/dist/esm/hooks/useFilteredList.js +20 -0
  31. package/dist/esm/hooks/usePagination.js +27 -4
  32. package/dist/esm/hooks/useSelectableList.js +29 -0
  33. package/dist/esm/i18n/ar.json.js +8 -2
  34. package/dist/esm/i18n/en.json.js +8 -2
  35. package/dist/esm/json/JsonViewer.js +68 -28
  36. package/dist/esm/pagination/Pagination.js +10 -0
  37. package/dist/esm/table/AddColumn.js +1 -1
  38. package/dist/esm/table/BodyCell.js +38 -5
  39. package/dist/esm/table/BodyRow.js +107 -0
  40. package/dist/esm/table/CheckboxCell.js +1 -1
  41. package/dist/esm/table/ColumnFilters.js +97 -0
  42. package/dist/esm/table/HeaderCell.js +71 -38
  43. package/dist/esm/table/Table.js +115 -73
  44. package/dist/esm/table/types.js +29 -0
  45. package/dist/esm/table/useResizer.js +4 -4
  46. package/dist/esm/table/useVirtualTable.js +124 -20
  47. package/dist/esm/tree/TreeNode.js +47 -27
  48. package/dist/esm/tree/TreePanel.js +28 -10
  49. package/dist/esm/tree/useTree.js +99 -17
  50. package/dist/esm/tree/utils.js +20 -5
  51. package/dist/esm/virtual/Gallery.js +84 -35
  52. package/dist/esm/virtual/List.js +82 -32
  53. package/dist/types/checklist/CheckList.d.ts +24 -2
  54. package/dist/types/histogram/Histogram.d.ts +31 -4
  55. package/dist/types/hooks/useFilteredList.d.ts +20 -2
  56. package/dist/types/hooks/usePagination.d.ts +19 -0
  57. package/dist/types/hooks/useSelectableList.d.ts +29 -0
  58. package/dist/types/index.d.ts +1 -1
  59. package/dist/types/json/JsonViewer.d.ts +27 -2
  60. package/dist/types/pagination/Pagination.d.ts +10 -1
  61. package/dist/types/table/AddColumn.d.ts +2 -2
  62. package/dist/types/table/BodyCell.d.ts +4 -3
  63. package/dist/types/table/BodyRow.d.ts +20 -0
  64. package/dist/types/table/ColumnFilters.d.ts +9 -0
  65. package/dist/types/table/EmptyCell.d.ts +2 -2
  66. package/dist/types/table/HeaderCell.d.ts +2 -2
  67. package/dist/types/table/Table.d.ts +14 -1
  68. package/dist/types/table/types.d.ts +48 -7
  69. package/dist/types/table/useTableColumns.d.ts +6 -6
  70. package/dist/types/table/useVirtualTable.d.ts +23 -8
  71. package/dist/types/tree/TreeNode.d.ts +1 -1
  72. package/dist/types/tree/TreePanel.d.ts +42 -1
  73. package/dist/types/tree/types.d.ts +25 -5
  74. package/dist/types/tree/useTree.d.ts +3 -10
  75. package/dist/types/tree/utils.d.ts +4 -1
  76. package/dist/types/virtual/Gallery.d.ts +44 -4
  77. package/dist/types/virtual/List.d.ts +44 -4
  78. package/package.json +13 -10
  79. package/src/checklist/CheckList.tsx +103 -77
  80. package/src/histogram/Histogram.tsx +45 -11
  81. package/src/hooks/useFilteredList.tsx +19 -0
  82. package/src/hooks/usePagination.tsx +32 -7
  83. package/src/hooks/useSelectableList.ts +29 -0
  84. package/src/i18n/ar.json +5 -0
  85. package/src/i18n/en.json +5 -0
  86. package/src/index.ts +1 -1
  87. package/src/json/JsonViewer.tsx +71 -35
  88. package/src/pagination/Pagination.tsx +10 -0
  89. package/src/table/AddColumn.tsx +3 -3
  90. package/src/table/BodyCell.tsx +34 -5
  91. package/src/table/BodyRow.tsx +143 -0
  92. package/src/table/CheckboxCell.tsx +1 -1
  93. package/src/table/ColumnFilters.tsx +105 -0
  94. package/src/table/EmptyCell.tsx +2 -2
  95. package/src/table/HeaderCell.tsx +72 -14
  96. package/src/table/Table.tsx +177 -77
  97. package/src/table/types.ts +52 -6
  98. package/src/table/useResizer.ts +5 -3
  99. package/src/table/useTableColumns.ts +7 -7
  100. package/src/table/useVirtualTable.ts +146 -24
  101. package/src/tree/TreeNode.tsx +98 -56
  102. package/src/tree/TreePanel.tsx +64 -3
  103. package/src/tree/types.ts +32 -4
  104. package/src/tree/useTree.ts +109 -26
  105. package/src/tree/utils.ts +23 -8
  106. package/src/virtual/Gallery.tsx +96 -42
  107. package/src/virtual/List.tsx +82 -33
  108. package/dist/cjs/histogram/Histogram.module.css +0 -1
  109. package/dist/esm/histogram/Histogram.module.css +0 -1
@@ -22,12 +22,14 @@
22
22
  */
23
23
 
24
24
  import { Badge, CoreIcons, EmptyContent, Icon } from "@react-fabric/core";
25
- import { type BadgeType } from "@react-fabric/core/dist/types/types";
25
+ import {
26
+ type BadgeType,
27
+ type TestProps,
28
+ } from "@react-fabric/core/dist/types/types";
26
29
  import { isString } from "@react-fabric/utilities";
27
30
  import {
28
31
  Fragment,
29
32
  isValidElement,
30
- memo,
31
33
  useEffect,
32
34
  useMemo,
33
35
  useState,
@@ -47,7 +49,7 @@ export interface CheckListItem extends KeyValue {
47
49
  isDisabled?: boolean;
48
50
  }
49
51
 
50
- export interface CheckListProps extends SelectableProps {
52
+ export interface CheckListProps extends SelectableProps, TestProps {
51
53
  /**
52
54
  * list items
53
55
  */
@@ -73,97 +75,121 @@ const CheckItem: FC<
73
75
  selected?: number;
74
76
  allowNegative?: boolean;
75
77
  }
76
- > = memo(
77
- ({
78
- id,
79
- allowNegative,
80
- selected,
81
- icon,
82
- label,
83
- isDisabled,
84
- onClick,
85
- badge,
86
- }: Omit<CheckListItem, "selected"> & {
87
- selected?: number;
88
- allowNegative?: boolean;
89
- }) => {
90
- return (
91
- <div
92
- role="none"
93
- className="ax-checkList__item"
94
- data-disabled={isDisabled}
95
- onClick={() => onClick(id, false)}
96
- onContextMenu={(e) => [
97
- allowNegative && onClick(id, true),
98
- e.preventDefault(),
99
- ]}
100
- >
101
- {!allowNegative && (
78
+ > = ({
79
+ id,
80
+ allowNegative,
81
+ selected,
82
+ icon,
83
+ label,
84
+ isDisabled,
85
+ onClick,
86
+ badge,
87
+ ...aria
88
+ }: Omit<CheckListItem, "selected"> & {
89
+ selected?: number;
90
+ allowNegative?: boolean;
91
+ }) => {
92
+ return (
93
+ <div
94
+ role="none"
95
+ className="ax-checkList__item"
96
+ data-disabled={isDisabled}
97
+ onClick={() => onClick(id, false)}
98
+ onContextMenu={(e) => [
99
+ allowNegative && onClick(id, true),
100
+ e.preventDefault(),
101
+ ]}
102
+ {...aria}
103
+ >
104
+ {!allowNegative && (
105
+ <Icon
106
+ data-type="checkbox"
107
+ className="ax-checkList__checkbox"
108
+ data-selected={selected === 1}
109
+ icon={selected ? CoreIcons.checkboxInt : CoreIcons.checkboxOff}
110
+ />
111
+ )}
112
+ {allowNegative && (
113
+ <Fragment>
102
114
  <Icon
103
- data-type="checkbox"
115
+ data-type="multiple"
104
116
  className="ax-checkList__checkbox"
105
- data-selected={selected === 1}
106
- icon={selected ? CoreIcons.checkboxInt : CoreIcons.checkboxOff}
117
+ icon={
118
+ selected === 1
119
+ ? CoreIcons.expand
120
+ : selected === -1
121
+ ? CoreIcons.collapse
122
+ : CoreIcons.checkboxOff
123
+ }
124
+ data-selected={selected}
107
125
  />
108
- )}
109
- {allowNegative && (
110
- <Fragment>
126
+ <div className="ax-checkList__checkbox">
111
127
  <Icon
112
- data-type="multiple"
113
- className="ax-checkList__checkbox"
128
+ data-type="positive"
129
+ 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"
135
+ onClick={(e) => [(onClick(id, true), e.stopPropagation())]}
114
136
  icon={
115
- selected === 1
116
- ? CoreIcons.expand
117
- : selected === -1
118
- ? CoreIcons.collapse
119
- : CoreIcons.checkboxOff
137
+ selected === -1 ? CoreIcons.collapseActive : CoreIcons.collapse
120
138
  }
121
- data-selected={selected}
139
+ data-selected={selected === -1}
122
140
  />
123
- <div className="ax-checkList__checkbox">
124
- <Icon
125
- data-type="positive"
126
- onClick={(e) => [(onClick(id, false), e.stopPropagation())]}
127
- icon={
128
- selected === 1 ? CoreIcons.expandActive : CoreIcons.expand
129
- }
130
- data-selected={selected === 1}
131
- />
132
- <Icon
133
- data-type="negative"
134
- onClick={(e) => [(onClick(id, true), e.stopPropagation())]}
135
- icon={
136
- selected === -1
137
- ? CoreIcons.collapseActive
138
- : CoreIcons.collapse
139
- }
140
- data-selected={selected === -1}
141
- />
142
- </div>
143
- </Fragment>
144
- )}
145
- {isValidElement(icon) && icon}
146
- {isString(icon) && <Icon icon={icon} />}
147
- <label>{label}</label>
148
- <Badge {...badge} />
149
- </div>
150
- );
151
- },
152
- );
153
- CheckItem.displayName = "CheckItem";
141
+ </div>
142
+ </Fragment>
143
+ )}
144
+ {isValidElement(icon) && icon}
145
+ {isString(icon) && <Icon icon={icon} />}
146
+ <label>{label}</label>
147
+ <Badge {...badge} />
148
+ </div>
149
+ );
150
+ };
154
151
 
152
+ /**
153
+ * CheckList component to display a list of selectable items with checkboxes.
154
+ * It supports selection, deselection, and allows for negative selections.
155
+ *
156
+ * @param {CheckListProps} props - The properties for the CheckList component.
157
+ * @returns {JSX.Element} The rendered CheckList component.
158
+ *
159
+ * @example
160
+ * ```jsx
161
+ * <CheckList
162
+ * items={[
163
+ * { id: "1", label: "Item 1", icon: "icon1" },
164
+ * { id: "2", label: "Item 2", icon: "icon2", badge: { text: "New" } },
165
+ * ]}
166
+ * maxCount={5}
167
+ * allowNegative={true}
168
+ * emptyMessage="No items available"
169
+ * onChange={(selectedItems) => console.log(selectedItems)}
170
+ * onClick={(itemId, isNegative) => console.log(itemId, isNegative)}
171
+ * />
172
+ * ```
173
+ */
155
174
  export const CheckList: FC<CheckListProps> = ({
156
175
  items = [],
157
176
  maxCount = 0,
158
177
  allowNegative,
159
178
  emptyMessage,
160
179
  sortItems = true,
161
- ...props
180
+ selected,
181
+ nonselected,
182
+ onChange,
183
+ onClick,
184
+ ...aria
162
185
  }: CheckListProps) => {
163
186
  const { t } = useTranslation("data");
164
187
  const { selection, toggleSelection } = useSelectableList({
165
188
  items,
166
- ...props,
189
+ selected,
190
+ nonselected,
191
+ onChange,
192
+ onClick,
167
193
  });
168
194
  const [showMore, setShowMore] = useState(false);
169
195
 
@@ -186,7 +212,7 @@ export const CheckList: FC<CheckListProps> = ({
186
212
  }, [items, selection, maxCount, showMore, sortItems]);
187
213
 
188
214
  return (
189
- <div className="ax-checkList">
215
+ <div {...aria}>
190
216
  {listItems.map((item, index) => (
191
217
  <CheckItem
192
218
  {...item}
@@ -31,12 +31,12 @@ import {
31
31
  import {
32
32
  type ColorType,
33
33
  type PaletteType,
34
+ type TestProps,
34
35
  } from "@react-fabric/core/dist/types/types";
35
- import { compareValues, Format } from "@react-fabric/utilities";
36
+ import { compareValues, Format, isColor } from "@react-fabric/utilities";
36
37
  import classNames from "classnames";
37
38
  import { useMemo } from "react";
38
39
  import { useTranslation } from "react-i18next";
39
- import classes from "./Histogram.module.css";
40
40
 
41
41
  const emptyIcon =
42
42
  "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";
@@ -49,7 +49,7 @@ interface HistogramItem {
49
49
  disabled?: boolean;
50
50
  }
51
51
 
52
- export interface HistogramProps {
52
+ export interface HistogramProps extends TestProps {
53
53
  /**
54
54
  * histogram items list
55
55
  */
@@ -80,14 +80,43 @@ export interface HistogramProps {
80
80
  onClick?: (id: string, negative?: true) => void;
81
81
  }
82
82
 
83
+ /**
84
+ * Histogram component displays a bar chart of items with counts.
85
+ * It allows users to visualize the distribution of counts across different items.
86
+ * The component supports sorting, custom colors, and click handling for each item.
87
+ * It also provides an empty state when there are no items to display.
88
+ *
89
+ * @param {HistogramProps} props - The properties for the Histogram component.
90
+ * @returns {JSX.Element} - The rendered Histogram component.
91
+ *
92
+ * @example
93
+ * ```jsx
94
+ * <Histogram
95
+ * items={[
96
+ * { id: "1", label: "Item 1", count: 10 },
97
+ * { id: "2", label: "Item 2", count: 20 },
98
+ * { id: "3", label: "Item 3", count: 5, disabled: true },
99
+ * ]}
100
+ * total={30}
101
+ * color="primary-500"
102
+ * sortBy="count"
103
+ * emptyTitle="No Data Available"
104
+ * emptyMessage="Please check back later."
105
+ * onClick={(id, negative) => console.log(`Clicked on ${id}`, negative ? "Negative" : "Positive")}
106
+ * />
107
+ * ```
108
+ *
109
+ * @see {@link https://adarshpastakia.github.io/react-fabric/?path=/docs/data-histogram--docs}
110
+ */
83
111
  export const Histogram = ({
84
112
  items = [],
85
113
  total,
86
- color = "primary",
114
+ color = "primary-500",
87
115
  sortBy = "count",
88
116
  emptyTitle,
89
117
  emptyMessage,
90
118
  onClick,
119
+ ...aria
91
120
  }: HistogramProps) => {
92
121
  const { t } = useTranslation("data");
93
122
  const maxTotal = useMemo(() => {
@@ -103,10 +132,11 @@ export const Histogram = ({
103
132
 
104
133
  return (
105
134
  <div
106
- className={classes.histogram}
135
+ className={"fabric-histogram from-0% to-100%"}
136
+ {...aria}
107
137
  style={
108
138
  {
109
- "--color": `var(--${color})`,
139
+ "--color": isColor(color) ? color : `var(--color-${color})`,
110
140
  } as AnyObject
111
141
  }
112
142
  >
@@ -124,15 +154,17 @@ export const Histogram = ({
124
154
  data-disabled={item.count === 0 || item.disabled}
125
155
  className={classNames(
126
156
  "relative cursor-pointer flex flex-nowrap items-center select-none overflow-hidden min-h-6 my-px",
127
- classes.histoLabel,
128
- (item.count === 0 || item.disabled) && "pointer-events-none",
157
+ "fabric-histoLabel",
158
+ ((item.total ?? item.count) === 0 || item.disabled) &&
159
+ "pointer-events-none",
129
160
  )}
161
+ data-testid={item.id}
130
162
  onClick={(e) => [onClick?.(item.id), e.stopPropagation()]}
131
163
  >
132
164
  <div
133
165
  className={classNames(
134
- "absolute z-0 inset-0 opacity-50 min-w-2 ltr:bg-gradient-to-r rtl:bg-gradient-to-l from-0% to-100%",
135
- classes.histoBar,
166
+ "absolute z-0 inset-0 opacity-50 min-w-2 ltr:bg-gradient-to-r rtl:bg-gradient-to-l",
167
+ "fabric-histoBar",
136
168
  )}
137
169
  style={{
138
170
  width: `${Math.min(item.count / maxTotal, 1) * 100}%`,
@@ -145,6 +177,7 @@ export const Histogram = ({
145
177
  key="negative"
146
178
  color="danger"
147
179
  size="sm"
180
+ stopPropagation
148
181
  icon={CoreIcons.funnelMinus}
149
182
  aria-label="Filter Negative"
150
183
  onClick={() => onClick?.(item.id, true)}
@@ -153,6 +186,7 @@ export const Histogram = ({
153
186
  key="positive"
154
187
  color="primary"
155
188
  size="sm"
189
+ stopPropagation
156
190
  icon={CoreIcons.funnelPlus}
157
191
  aria-label="Filter"
158
192
  onClick={() => onClick?.(item.id)}
@@ -164,7 +198,7 @@ export const Histogram = ({
164
198
  <span
165
199
  className={classNames(
166
200
  "z-1 rounded-full text-xs font-medium px-2 py-1 leading-none",
167
- classes.histoBadge,
201
+ "fabric-histoBadge",
168
202
  )}
169
203
  >
170
204
  {Format.number(item.count)}
@@ -51,6 +51,25 @@ const filterList = (
51
51
  return newList;
52
52
  };
53
53
 
54
+ /**
55
+ * Custom hook to filter a list of items based on a query.
56
+ * It provides a way to search through the items and returns the filtered list.
57
+ * The hook uses a deferred value to optimize performance during transitions.
58
+ * It also allows for a custom matcher function to define how items should be matched against the query.
59
+ *
60
+ * @param items - The list of items to filter.
61
+ * @param matcher - An optional function to match items against the query.
62
+ * @returns An object containing the search function, current query, filtered list, and a boolean indicating if a search is in progress.
63
+ *
64
+ * @example
65
+ * ```jsx
66
+ * useFilteredList(items, (item, query) => {
67
+ * return item.name.includes(query);
68
+ * });
69
+ * ```
70
+ *
71
+ * @see {@link https://adarshpastakia.github.io/react-fabric/?path=/docs/data-usefilteredlist--docs}
72
+ */
54
73
  export const useFilteredList = <
55
74
  T extends AnyObject = KeyValue,
56
75
  Q extends AnyObject = string,
@@ -57,6 +57,25 @@ const makeRange = (start: number, end: number) => {
57
57
  return Array.from({ length }, (_: AnyObject, idx: number) => idx + start);
58
58
  };
59
59
 
60
+ /**
61
+ * Custom hook to manage pagination state and logic.
62
+ * It calculates the total number of pages, generates page ranges,
63
+ * and provides a way to change the current page.
64
+ * It also handles the transition of page changes to optimize performance.
65
+ *
66
+ * @param options - The pagination options including total records, current page, records per page, and change handler.
67
+ * @returns An object containing the current page, total pages, head label, ranges for pagination pills, and a function to change the page.
68
+ *
69
+ * @example
70
+ * ```jsx
71
+ * const { page, totalPages, headLabel, ranges, onPageChange } = usePagination({
72
+ * totalRecords: 100,
73
+ * currentPage: 0,
74
+ * perPage: 10,
75
+ * onChange: (newPage) => console.log("Page changed to:", newPage),
76
+ * });
77
+ * ```
78
+ */
60
79
  export const usePagination = ({
61
80
  totalRecords,
62
81
  currentPage = 0,
@@ -111,9 +130,12 @@ export const usePagination = ({
111
130
 
112
131
  /** ***************** set page *******************/
113
132
  useEffect(() => {
114
- setPage(Math.min(currentPage, totalPages - 1));
115
- onChange?.(Math.min(currentPage, totalPages - 1));
116
- }, [currentPage, totalPages]);
133
+ if (totalRecords > -1) {
134
+ const actual = Math.min(currentPage, totalPages - 1);
135
+ setPage(actual);
136
+ actual !== currentPage && onChange?.(actual);
137
+ }
138
+ }, [currentPage, totalRecords, totalPages]);
117
139
 
118
140
  const headLabel = useMemo(
119
141
  () => (
@@ -126,10 +148,13 @@ export const usePagination = ({
126
148
  [page, perPage, totalRecords],
127
149
  );
128
150
 
129
- const onPageChange = useCallback((page: number) => {
130
- setPage(page);
131
- startTransition(() => onChange?.(page));
132
- }, []);
151
+ const onPageChange = useCallback(
152
+ (page: number) => {
153
+ setPage(page);
154
+ startTransition(() => onChange?.(page));
155
+ },
156
+ [onChange],
157
+ );
133
158
 
134
159
  return { page, totalPages, headLabel, ranges, onPageChange };
135
160
  };
@@ -46,6 +46,35 @@ export interface SelectableProps {
46
46
  onClick?: (id: string, isNegative: boolean) => void;
47
47
  }
48
48
 
49
+ /**
50
+ * Custom hook to manage selection of items in a list.
51
+ * It allows toggling selection state of items, handling both selected and non-selected states.
52
+ * The hook maintains a selection state and provides a function to toggle the selection of items.
53
+ * It also supports callbacks for when the selection changes or when an item is clicked.
54
+ *
55
+ * @param items - The list of items to manage selection for.
56
+ * @param selected - An array of item IDs that are currently selected.
57
+ * @param nonselected - An array of item IDs that are currently non-selected.
58
+ * @param onChange - Callback function that is called when the selection changes.
59
+ * @param onClick - Callback function that is called when an item is clicked.
60
+ * @returns An object containing the current selection state and a function to toggle selection of items.
61
+ *
62
+ * @example
63
+ * ```jsx
64
+ * const { selection, toggleSelection } = useSelectableList({
65
+ * items: [{ id: '1', label: 'Item 1' }, { id: '2', label: 'Item 2' }],
66
+ * selected: ['1'],
67
+ * nonselected: ['2'],
68
+ * onChange: (selectedIds, nonSelectedIds) => {
69
+ * console.log('Selected:', selectedIds);
70
+ * console.log('Non-selected:', nonSelectedIds);
71
+ * },
72
+ * onClick: (id, isNegative) => {
73
+ * console.log(`Item ${id} clicked, isNegative: ${isNegative}`);
74
+ * },
75
+ * });
76
+ * ```
77
+ */
49
78
  export const useSelectableList = ({
50
79
  items,
51
80
  selected,
package/src/i18n/ar.json CHANGED
@@ -32,5 +32,10 @@
32
32
  "uncheckAll": "الغاءالكل",
33
33
  "addFilter": "أضف عامل تصفية",
34
34
  "addFilterNot": "إضافة لا تصفية"
35
+ },
36
+ "data": {
37
+ "tree": {
38
+ "noItems": "لا توجد عناصر شجرة متوفرة"
39
+ }
35
40
  }
36
41
  }
package/src/i18n/en.json CHANGED
@@ -32,5 +32,10 @@
32
32
  "uncheckAll": "Uncheck All",
33
33
  "addFilter": "Add Filter",
34
34
  "addFilterNot": "Add not Filter"
35
+ },
36
+ "data": {
37
+ "tree": {
38
+ "noItems": "No tree items available"
39
+ }
35
40
  }
36
41
  }
package/src/index.ts CHANGED
@@ -28,7 +28,7 @@ export { Histogram } from "./histogram/Histogram";
28
28
  export { JsonViewer } from "./json/JsonViewer";
29
29
  export { Pagination } from "./pagination/Pagination";
30
30
  export { Table } from "./table/Table";
31
- export { type TableRef } from "./table/types";
31
+ export { type TableColumn, type TableRef } from "./table/types";
32
32
  export { TreePanel } from "./tree/TreePanel";
33
33
  export { type TreeNodeType as TreeNode } from "./tree/types";
34
34
  export { VirtualGallery, type VirtualGalleryRef } from "./virtual/Gallery";
@@ -22,7 +22,6 @@
22
22
  */
23
23
 
24
24
  import {
25
- ActionLabel,
26
25
  Button,
27
26
  Copy,
28
27
  CoreIcons,
@@ -181,6 +180,8 @@ const JsonEntry = ({
181
180
  ret = actualValue ? t("json.true") : t("json.false");
182
181
  if (ret?.length > 128) {
183
182
  ret = <Text clamp={3}>{ret}</Text>;
183
+ } else {
184
+ ret = <div className="break-words">{ret}</div>;
184
185
  }
185
186
 
186
187
  return (
@@ -216,37 +217,36 @@ const JsonEntry = ({
216
217
 
217
218
  const labelDisplay = useMemo(
218
219
  () => (
219
- <ActionLabel
220
- className="text-dimmed text-sm px-2 flex-1 truncate"
221
- actions={
222
- !canExpand && canFilter
223
- ? [
224
- <Button
225
- key="negative"
226
- color="danger"
227
- size="sm"
228
- variant="link"
229
- icon={CoreIcons.funnelMinus}
230
- aria-label="Filter Negative"
231
- onClick={() =>
232
- onFilter?.(pathWithoutOrdinal, actualValue, true)
233
- }
234
- />,
235
- <Button
236
- key="positive"
237
- color="primary"
238
- size="sm"
239
- variant="link"
240
- icon={CoreIcons.funnelPlus}
241
- aria-label="Filter"
242
- onClick={() => onFilter?.(pathWithoutOrdinal, actualValue)}
243
- />,
244
- ]
245
- : []
246
- }
220
+ <div
221
+ className={classNames(
222
+ "text-dimmed text-sm px-2 flex-1 group",
223
+ inline ? "truncate" : "break-words",
224
+ )}
247
225
  >
226
+ {!canExpand && canFilter && (
227
+ <div className="hidden pe-1 group-hover:inline-block">
228
+ <Button
229
+ key="negative"
230
+ color="danger"
231
+ size="xs"
232
+ variant="link"
233
+ icon={CoreIcons.funnelMinus}
234
+ aria-label="Filter Negative"
235
+ onClick={() => onFilter?.(pathWithoutOrdinal, actualValue, true)}
236
+ />
237
+ <Button
238
+ key="positive"
239
+ color="primary"
240
+ size="xs"
241
+ variant="link"
242
+ icon={CoreIcons.funnelPlus}
243
+ aria-label="Filter"
244
+ onClick={() => onFilter?.(pathWithoutOrdinal, actualValue)}
245
+ />
246
+ </div>
247
+ )}
248
248
  {labeler?.(path) ?? field}
249
- </ActionLabel>
249
+ </div>
250
250
  ),
251
251
  [field, labeler, pathWithoutOrdinal, actualValue, canExpand, canFilter],
252
252
  );
@@ -283,9 +283,14 @@ const JsonEntry = ({
283
283
  {labelDisplay}
284
284
  </div>
285
285
  {display && (
286
- <div className={classNames("px-2", inline && "flex-1")}>
286
+ <div
287
+ className={classNames(
288
+ "px-2 flex flex-nowrap overflow-hidden",
289
+ inline && "flex-1",
290
+ )}
291
+ >
287
292
  {canCopy && <Copy size="xs" text={actualValue} />}
288
- {display}
293
+ <div className="flex-1">{display}</div>
289
294
  </div>
290
295
  )}
291
296
  </div>
@@ -309,9 +314,14 @@ const JsonEntry = ({
309
314
  >
310
315
  {labelDisplay}
311
316
  </div>
312
- <div className={classNames("px-2", inline && "flex-1")}>
317
+ <div
318
+ className={classNames(
319
+ "px-2 flex flex-nowrap overflow-hidden",
320
+ inline && "flex-1",
321
+ )}
322
+ >
313
323
  {canCopy && <Copy size="xs" text={value} />}
314
- {display}
324
+ <div className="flex-1">{display}</div>
315
325
  </div>
316
326
  </div>
317
327
  )}
@@ -336,6 +346,32 @@ const JsonEntry = ({
336
346
  );
337
347
  };
338
348
 
349
+ /**
350
+ * JsonViewer component to display JSON data in a structured format.
351
+ * It supports inline display, property filtering, and custom formatting.
352
+ * The component can handle nested objects and arrays, displaying them in a tree-like structure.
353
+ * It also provides options for copying values and filtering properties.
354
+ *
355
+ * @param {JsonViewProps<T>} props - The properties for the JsonViewer component.
356
+ * @returns {JSX.Element} The rendered JsonViewer component.
357
+ *
358
+ * @example
359
+ * ```jsx
360
+ * <JsonViewer
361
+ * json={{ name: "John", age: 30, address: { city: "New York", zip: "10001" } }}
362
+ * copy={true}
363
+ * filters={true}
364
+ * propertyScheme={{ date: ["createdAt"], phone: ["contact"] }}
365
+ * inline={true}
366
+ * labelWidth="25%"
367
+ * formatter={(path, value) => (path === "age" ? `${value} years` : value)}
368
+ * labeler={(path) => path.replace(/_/g, " ").toUpperCase()}
369
+ * onFilter={(path, value, negate) => console.log(`Filter ${negate ? "negate" : ""} on ${path}:`, value)}
370
+ * />
371
+ * ```
372
+ *
373
+ * @see {@link https://adarshpastakia.github.io/react-fabric/?path=/docs/data-jsonviewer--docs}
374
+ */
339
375
  export const JsonViewer = <T extends KeyValue = KeyValue>({
340
376
  className,
341
377
  json,
@@ -356,7 +392,7 @@ export const JsonViewer = <T extends KeyValue = KeyValue>({
356
392
  const list = useMemo(() => reduceObject(json), [json]);
357
393
 
358
394
  return (
359
- <div {...rest} className={classNames(className, "p-4 bg-alternate")}>
395
+ <div {...rest} className={classNames(className, "bg-alternate")}>
360
396
  {!isEmpty(json) ? (
361
397
  list.map((item) => (
362
398
  <JsonEntry