@databiosphere/findable-ui 27.0.0 → 28.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +17 -0
  3. package/lib/components/Index/components/EntitiesView/components/ChartView/chartView.js +1 -1
  4. package/lib/components/Index/components/EntitiesView/components/ChartView/components/Chart/barX/plot.js +3 -2
  5. package/lib/components/Index/components/EntitiesView/components/ChartView/components/Chart/barX/utils.d.ts +21 -0
  6. package/lib/components/Index/components/EntitiesView/components/ChartView/components/Chart/barX/utils.js +34 -0
  7. package/lib/components/Index/components/EntitiesView/components/EntityList/entityList.d.ts +2 -1
  8. package/lib/components/Index/components/EntitiesView/components/EntityList/entityList.js +3 -5
  9. package/lib/components/Index/components/EntitiesView/components/EntityList/types.d.ts +4 -1
  10. package/lib/components/Index/components/Hero/components/Summaries/stories/summaries.stories.d.ts +6 -0
  11. package/lib/components/Index/components/Hero/components/Summaries/{summaries.stories.js → stories/summaries.stories.js} +2 -6
  12. package/lib/components/Index/components/Hero/stories/hero.stories.d.ts +6 -0
  13. package/lib/components/Index/components/Hero/stories/hero.stories.js +16 -0
  14. package/lib/components/Index/index.d.ts +1 -1
  15. package/lib/components/Index/index.js +6 -2
  16. package/lib/components/Index/stories/index.stories.d.ts +6 -0
  17. package/lib/components/Index/stories/index.stories.js +17 -0
  18. package/lib/components/Index/table/coreOptions/columns/cellFactory.d.ts +9 -0
  19. package/lib/components/Index/table/coreOptions/columns/cellFactory.js +13 -0
  20. package/lib/components/Index/table/hook.d.ts +3 -0
  21. package/lib/components/Index/table/hook.js +166 -0
  22. package/lib/components/Index/table/types.d.ts +4 -0
  23. package/lib/components/Index/table/types.js +1 -0
  24. package/lib/components/Index/types.d.ts +6 -2
  25. package/lib/components/Table/table.d.ts +5 -20
  26. package/lib/components/Table/table.js +10 -138
  27. package/lib/components/TableCreator/tableCreator.d.ts +5 -7
  28. package/lib/components/TableCreator/tableCreator.js +3 -35
  29. package/lib/views/ExploreView/exploreView.js +1 -3
  30. package/package.json +1 -1
  31. package/src/components/Index/components/EntitiesView/components/ChartView/chartView.tsx +1 -1
  32. package/src/components/Index/components/EntitiesView/components/ChartView/components/Chart/barX/plot.ts +4 -1
  33. package/src/components/Index/components/EntitiesView/components/ChartView/components/Chart/barX/utils.ts +43 -0
  34. package/src/components/Index/components/EntitiesView/components/EntityList/entityList.tsx +7 -14
  35. package/src/components/Index/components/EntitiesView/components/EntityList/types.ts +5 -1
  36. package/src/components/Index/components/Hero/components/Summaries/{summaries.stories.tsx → stories/summaries.stories.tsx} +4 -8
  37. package/src/components/Index/components/Hero/stories/hero.stories.tsx +22 -0
  38. package/src/components/Index/index.tsx +21 -3
  39. package/src/components/Index/stories/index.stories.tsx +23 -0
  40. package/src/components/Index/table/coreOptions/columns/cellFactory.tsx +22 -0
  41. package/src/components/Index/table/hook.ts +234 -0
  42. package/src/components/Index/table/types.ts +5 -0
  43. package/src/components/Index/types.ts +6 -2
  44. package/src/components/Table/table.tsx +16 -199
  45. package/src/components/TableCreator/tableCreator.tsx +7 -79
  46. package/src/views/ExploreView/exploreView.tsx +4 -10
  47. package/tests/chart.test.tsx +19 -8
  48. package/tests/chartView.test.tsx +1 -1
  49. package/lib/components/Index/components/Hero/components/Summaries/summaries.stories.d.ts +0 -13
  50. package/lib/components/Index/components/Hero/hero.stories.d.ts +0 -23
  51. package/lib/components/Index/components/Hero/hero.stories.js +0 -22
  52. package/lib/components/Index/index.stories.d.ts +0 -6
  53. package/lib/components/Index/index.stories.js +0 -26
  54. package/src/components/Index/components/Hero/hero.stories.tsx +0 -28
  55. package/src/components/Index/index.stories.tsx +0 -31
@@ -1,127 +1,35 @@
1
1
  import { TableContainer } from "@mui/material";
2
- import { getCoreRowModel, getFacetedRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from "@tanstack/react-table";
3
- import React, { Fragment, useCallback, useEffect, useMemo } from "react";
2
+ import React, { Fragment } from "react";
4
3
  import { track } from "../../common/analytics/analytics";
5
- import { EVENT_NAME, EVENT_PARAM, PAGINATION_DIRECTION, SORT_DIRECTION, } from "../../common/analytics/entities";
4
+ import { EVENT_NAME, EVENT_PARAM, PAGINATION_DIRECTION, } from "../../common/analytics/entities";
6
5
  import { BREAKPOINT_FN_NAME, useBreakpointHelper, } from "../../hooks/useBreakpointHelper";
7
6
  import { useExploreMode } from "../../hooks/useExploreMode/useExploreMode";
8
7
  import { useExploreState } from "../../hooks/useExploreState";
9
8
  import { useScroll } from "../../hooks/useScroll";
10
9
  import { ExploreActionKind } from "../../providers/exploreState";
11
- import { DEFAULT_PAGINATION_STATE } from "../../providers/exploreState/initializer/constants";
12
10
  import { TABLET } from "../../theme/common/breakpoints";
13
11
  import { Loading, LOADING_PANEL_STYLE } from "../Loading/loading";
14
12
  import { NoResults } from "../NoResults/noResults";
15
13
  import { getColumnTrackSizing } from "../TableCreator/options/columnTrackSizing/utils";
16
- import { arrIncludesSome } from "./columnDef/columnFilters/filterFn";
17
14
  import { ROW_DIRECTION } from "./common/entities";
18
- import { buildCategoryViews, getFacetedUniqueValuesWithArrayValues, getTableStatePagination, isClientFilteringEnabled, } from "./common/utils";
15
+ import { isClientFilteringEnabled } from "./common/utils";
19
16
  import { Pagination as DXPagination } from "./components/Pagination/pagination";
20
17
  import { TableBody } from "./components/TableBody/tableBody";
21
18
  import { TableHead } from "./components/TableHead/tableHead";
22
19
  import { TableToolbar } from "./components/TableToolbar/tableToolbar";
23
- import { ROW_POSITION } from "./features/RowPosition/constants";
24
- import { ROW_PREVIEW } from "./features/RowPreview/constants";
25
20
  import { GridTable } from "./table.styles";
26
- /**
27
- * This table can be Controlled or Uncontrolled based on the set of props passed to it.
28
- * Controlled table will receive the navigation functions, and it will be used for dynamic loads.
29
- * Uncontrolled table will take advantage of React Table's state and will be used for static loads.
30
- * @param tableProps - Set of props required for displaying the table.
31
- * @param tableProps.columns - Set of columns to display.
32
- * @param tableProps.getRowId - Function to customize the row ID.
33
- * @param tableProps.items - Row data to display.
34
- * @param tableProps.listView - List view configuration.
35
- * @param tableProps.tableOptions - TanStack table options.
36
- * @returns Configured table element for display.
37
- */
38
- export const TableComponent = ({ columns, getRowId, items, listView, tableOptions, }) => {
21
+ export const TableComponent = ({ listView, loading, table, }) => {
39
22
  const tabletDown = useBreakpointHelper(BREAKPOINT_FN_NAME.DOWN, TABLET);
40
23
  const exploreMode = useExploreMode();
41
24
  const { exploreDispatch, exploreState } = useExploreState();
42
- const { entityPageState, filterState, listItems, loading = false, paginationState, rowPreview, tabValue, } = exploreState;
43
- const { columnVisibility, grouping, rowSelection, sorting } = entityPageState[tabValue];
44
- const { currentPage, pages, pageSize, rows: pageCount } = paginationState;
25
+ const { paginationState } = exploreState;
26
+ const { currentPage, pages } = paginationState;
45
27
  const { disablePagination = false } = listView || {};
46
28
  const clientFiltering = isClientFilteringEnabled(exploreMode);
47
29
  const rowDirection = tabletDown
48
30
  ? ROW_DIRECTION.VERTICAL
49
31
  : ROW_DIRECTION.DEFAULT;
50
- const pagination = useMemo(() => getTableStatePagination(currentPage - 1, pageSize), [currentPage, pageSize]);
51
- const onSortingChange = (updater) => {
52
- // TODO(cc) memoize `onSortingChange` with `useCallback`.
53
- // TODO(cc) copy `onSortingChange` to ../options/sorting/hook.ts see src/components/Table/options/grouping/hook.ts for example.
54
- exploreDispatch({
55
- payload: typeof updater === "function" ? updater(sorting) : updater,
56
- type: ExploreActionKind.UpdateSorting,
57
- });
58
- // Execute GTM tracking.
59
- // TODO(cc) update tracking to handle sorting of multiple columns.
60
- // TODO(cc) GTM tracking when `onSortingChange` is triggered only tracks the first column sorted, and takes the value from explore state which is not updated yet.
61
- track(EVENT_NAME.ENTITY_TABLE_SORTED, {
62
- [EVENT_PARAM.ENTITY_NAME]: exploreState.tabValue,
63
- [EVENT_PARAM.COLUMN_NAME]: sorting?.[0]?.id, // TODO(cc) sorting should always be at least `[]` and never `undefined`.
64
- [EVENT_PARAM.SORT_DIRECTION]: sorting?.[0]?.desc // TODO(cc) sorting should always be at least `[]` and never `undefined`.
65
- ? SORT_DIRECTION.DESC
66
- : SORT_DIRECTION.ASC,
67
- });
68
- };
69
- const onRowPreviewChange = useCallback((updater) => {
70
- exploreDispatch({
71
- payload: typeof updater === "function" ? updater(rowPreview) : updater,
72
- type: ExploreActionKind.UpdateRowPreview,
73
- });
74
- }, [exploreDispatch, rowPreview]);
75
- const onRowSelectionChange = useCallback((updater) => {
76
- // TODO(cc) refactor `onRowSelectionChange` to /options/rowSelection/hook.ts see onGroupingChange.
77
- exploreDispatch({
78
- payload: typeof updater === "function" ? updater(rowSelection) : updater,
79
- type: ExploreActionKind.UpdateRowSelection,
80
- });
81
- }, [exploreDispatch, rowSelection]);
82
- const state = {
83
- columnVisibility,
84
- grouping,
85
- pagination,
86
- rowPreview,
87
- rowSelection,
88
- sorting,
89
- };
90
- /**
91
- * TODO: Update `ColumnConfig` to follow the `ColumnDef` API of TanStack Table.
92
- * - Standardize column definitions to leverage the full power of TanStack Table's feature set and improve compatibility.
93
- * TODO: Define `sorting` directly within `ListConfig` via the `tableOptions.initialState` property.
94
- * - This will simplify the configuration structure and centralize table state definitions, reducing redundancy and improving clarity.
95
- */
96
- const tableInstance = useReactTable({
97
- _features: [ROW_POSITION, ROW_PREVIEW],
98
- columns,
99
- data: items,
100
- enableColumnFilters: true, // client-side filtering.
101
- enableFilters: true, // client-side filtering.
102
- enableMultiSort: clientFiltering, // TODO(cc) move to sorting options; default to false and let the table options in config flag this value.
103
- filterFns: { arrIncludesSome },
104
- getCoreRowModel: getCoreRowModel(),
105
- getFacetedRowModel: clientFiltering ? getFacetedRowModel() : undefined,
106
- getFacetedUniqueValues: clientFiltering
107
- ? getFacetedUniqueValuesWithArrayValues()
108
- : undefined,
109
- getFilteredRowModel: clientFiltering ? getFilteredRowModel() : undefined,
110
- getPaginationRowModel: getPaginationRowModel(),
111
- getRowId,
112
- getSortedRowModel: clientFiltering ? getSortedRowModel() : undefined,
113
- manualPagination: true,
114
- manualSorting: !clientFiltering,
115
- onRowPreviewChange,
116
- onRowSelectionChange,
117
- onSortingChange,
118
- pageCount,
119
- state,
120
- ...tableOptions,
121
- });
122
- const { getAllColumns, getRowModel, getState, getVisibleFlatColumns, nextPage: tableNextPage, previousPage: tablePreviousPage, } = tableInstance;
123
- const allColumns = getAllColumns();
124
- const { columnFilters } = getState();
32
+ const { getRowModel, getVisibleFlatColumns, nextPage: tableNextPage, previousPage: tablePreviousPage, } = table;
125
33
  const { rows } = getRowModel();
126
34
  const noResults = !loading && (!rows || rows.length === 0);
127
35
  const scrollTop = useScroll();
@@ -160,42 +68,6 @@ export const TableComponent = ({ columns, getRowId, items, listView, tableOption
160
68
  previousPage();
161
69
  scrollTop();
162
70
  };
163
- // Sets react table column filters `columnFilters` state - for client-side filtering only - with update of filterState.
164
- useEffect(() => {
165
- if (clientFiltering) {
166
- tableInstance.setColumnFilters(filterState.map(({ categoryKey, value }) => ({
167
- id: categoryKey,
168
- value,
169
- })));
170
- }
171
- }, [clientFiltering, filterState, tableInstance]);
172
- // Process explore response - client-side filtering only.
173
- useEffect(() => {
174
- if (!listItems || listItems.length === 0)
175
- return;
176
- if (clientFiltering) {
177
- exploreDispatch({
178
- payload: {
179
- listItems,
180
- loading: false,
181
- paginationResponse: {
182
- ...DEFAULT_PAGINATION_STATE,
183
- pageSize: rows.filter(({ getIsGrouped }) => !getIsGrouped()).length,
184
- rows: rows.filter(({ getIsGrouped }) => !getIsGrouped()).length,
185
- },
186
- selectCategories: buildCategoryViews(allColumns, columnFilters),
187
- },
188
- type: ExploreActionKind.ProcessExploreResponse,
189
- });
190
- }
191
- }, [
192
- allColumns,
193
- clientFiltering,
194
- columnFilters,
195
- exploreDispatch,
196
- listItems,
197
- rows,
198
- ]);
199
71
  function canNextPage() {
200
72
  return currentPage < pages;
201
73
  }
@@ -203,12 +75,12 @@ export const TableComponent = ({ columns, getRowId, items, listView, tableOption
203
75
  return currentPage > 1;
204
76
  }
205
77
  return noResults ? (React.createElement(NoResults, { Paper: null, title: "No Results found" })) : (React.createElement(Fragment, null,
206
- React.createElement(TableToolbar, { listView: listView, rowDirection: rowDirection, tableInstance: tableInstance }),
78
+ React.createElement(TableToolbar, { listView: listView, rowDirection: rowDirection, tableInstance: table }),
207
79
  React.createElement(Loading, { appear: false, autoPosition: false, loading: loading, panelStyle: LOADING_PANEL_STYLE.INHERIT }),
208
80
  React.createElement(TableContainer, null,
209
81
  React.createElement(GridTable, { collapsable: true, gridTemplateColumns: getColumnTrackSizing(getVisibleFlatColumns()) },
210
- React.createElement(TableHead, { rowDirection: rowDirection, tableInstance: tableInstance }),
211
- React.createElement(TableBody, { rows: rows, rowDirection: rowDirection, tableInstance: tableInstance }))),
82
+ React.createElement(TableHead, { rowDirection: rowDirection, tableInstance: table }),
83
+ React.createElement(TableBody, { rows: rows, rowDirection: rowDirection, tableInstance: table }))),
212
84
  !disablePagination && (React.createElement(DXPagination, { canNextPage: canNextPage(), canPreviousPage: canPreviousPage(), currentPage: currentPage, onNextPage: handleTableNextPage, onPreviousPage: handleTablePreviousPage, totalPage: pages ?? 0 }))));
213
85
  };
214
86
  // TODO(Dave) review whether memo is necessary - flash between tabs / loading state.
@@ -1,10 +1,8 @@
1
- import { CoreOptions, RowData } from "@tanstack/react-table";
2
- import { ColumnConfig, ListViewConfig } from "../../config/entities";
1
+ import { RowData, Table as TanStackTable } from "@tanstack/react-table";
2
+ import { ListViewConfig } from "../../config/entities";
3
3
  export interface TableCreatorProps<T> {
4
- columns: ColumnConfig<T>[];
5
- getRowId?: CoreOptions<T>["getRowId"];
6
- items: T[];
7
4
  listView?: ListViewConfig;
8
- loading?: boolean;
5
+ loading: boolean;
6
+ table: TanStackTable<T>;
9
7
  }
10
- export declare const TableCreator: <T extends RowData>({ columns, getRowId, items, listView, loading, }: TableCreatorProps<T>) => JSX.Element;
8
+ export declare const TableCreator: <T extends RowData>({ listView, loading, table, }: TableCreatorProps<T>) => JSX.Element;
@@ -1,37 +1,5 @@
1
- import React, { useMemo } from "react";
2
- import { ComponentCreator } from "../ComponentCreator/ComponentCreator";
3
- import { COLUMN_DEF } from "../Table/common/columnDef";
4
- import { sortingFn } from "../Table/common/utils";
1
+ import React from "react";
5
2
  import { Table } from "../Table/table";
6
- import { buildBaseColumnDef } from "./common/utils";
7
- import { useTableOptions } from "./options/hook";
8
- const createCell = (config) => function CellCreator(cellContext) {
9
- return (React.createElement(ComponentCreator, { components: [config.componentConfig], response: cellContext.row.original, viewContext: { cellContext } }));
10
- };
11
- export const TableCreator = ({ columns, getRowId, items, listView, loading, }) => {
12
- const tableOptions = useTableOptions();
13
- const columnDefs = useMemo(() => columns.reduce((acc, {
14
- /**
15
- * Applies the custom `arrIncludesSome` filter function as the default for multi-value filtering.
16
- * Although `ColumnFilter["value"]` is typed as `unknown`, in practice it's consistently an array (`unknown[]`) in entity lists.
17
- * This custom filter function supports multi-select filtering, even when individual cell values are single strings.
18
- * This override of TanStack's default `arrIncludesSome` resolves a limitation where the base implementation
19
- * does not support matching an array of filter values against a single string cell value.
20
- * For range filtering, specify TanStack's `inNumberRange` filter function on the column definition.
21
- */
22
- filterFn = "arrIncludesSome", ...columnConfig }) => {
23
- acc.push({
24
- ...buildBaseColumnDef(columnConfig),
25
- cell: createCell(columnConfig),
26
- filterFn,
27
- sortingFn: sortingFn,
28
- });
29
- return acc;
30
- }, [
31
- /* Initialize column definitions with the "row position" column */
32
- COLUMN_DEF.ROW_POSITION,
33
- /* Initialize column definitions with the "row selection" column */
34
- COLUMN_DEF.ROW_SELECTION,
35
- ]), [columns]);
36
- return (React.createElement(Table, { columns: columnDefs, getRowId: getRowId, items: items, listView: listView, loading: loading, tableOptions: tableOptions }));
3
+ export const TableCreator = ({ listView, loading, table, }) => {
4
+ return React.createElement(Table, { listView: listView, loading: loading, table: table });
37
5
  };
@@ -5,8 +5,6 @@ import { ComponentCreator } from "../../components/ComponentCreator/ComponentCre
5
5
  import { ClearAllFilters } from "../../components/Filter/components/ClearAllFilters/clearAllFilters";
6
6
  import { Filters, } from "../../components/Filter/components/Filters/filters";
7
7
  import { SearchAllFilters } from "../../components/Filter/components/SearchAllFilters/searchAllFilters";
8
- import { ChartView } from "../../components/Index/components/EntitiesView/components/ChartView/chartView";
9
- import { EntityList } from "../../components/Index/components/EntitiesView/components/EntityList/entityList";
10
8
  import { Tabs } from "../../components/Index/components/Tabs/tabs";
11
9
  import { Index as IndexView } from "../../components/Index/index";
12
10
  import { SidebarButton } from "../../components/Layout/components/Sidebar/components/SidebarButton/sidebarButton";
@@ -105,7 +103,7 @@ export const ExploreView = (props) => {
105
103
  React.createElement(ClearAllFilters, null),
106
104
  React.createElement(SearchAllFilters, { categoryViews: categoryViews, drawerOpen: isDrawerOpen, onFilter: onFilterChange.bind(null, true) })),
107
105
  React.createElement(Filters, { categoryFilters: categoryFilters, closeAncestor: onCloseDrawer, onFilter: onFilterChange.bind(null, false), trackFilterOpened: trackingConfig?.trackFilterOpened }))),
108
- React.createElement(IndexView, { className: props.className, chart: React.createElement(ChartView, { categoryFilters: categoryFilters, entityName: label, loading: loading }), list: React.createElement(EntityList, { entityListType: entityListType }), ListHero: renderComponent(listHero), SideBarButton: tabletDown ? (React.createElement(SidebarButton, { count: filterCount, label: "Filter", onClick: onOpenDrawer })) : undefined, SubTitleHero: renderComponent(subTitleHero), Summaries: renderSummary(summaryConfig, summaryResponse), Tabs: React.createElement(Tabs, null), title: entityConfig.explorerTitle || explorerTitle })));
106
+ React.createElement(IndexView, { className: props.className, categoryFilters: categoryFilters, entityListType: entityListType, entityName: label, loading: loading, ListHero: renderComponent(listHero), SideBarButton: tabletDown ? (React.createElement(SidebarButton, { count: filterCount, label: "Filter", onClick: onOpenDrawer })) : undefined, SubTitleHero: renderComponent(subTitleHero), Summaries: renderSummary(summaryConfig, summaryResponse), Tabs: React.createElement(Tabs, null), title: entityConfig.explorerTitle || explorerTitle })));
109
107
  };
110
108
  /**
111
109
  * Builds the category views into category views grouped by the given category group configuration.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@databiosphere/findable-ui",
3
- "version": "27.0.0",
3
+ "version": "28.0.0",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
@@ -28,7 +28,7 @@ export const ChartView = ({
28
28
  {selectCategoryViews.map(({ key, label, values }) => (
29
29
  <StyledGridPaperSection key={key}>
30
30
  <Typography variant={TYPOGRAPHY_PROPS.VARIANT.TEXT_HEADING_SMALL}>
31
- {entityName} per {label}
31
+ {entityName} by {label}
32
32
  </Typography>
33
33
  <Chart selectCategoryValueViews={values} width={width} />
34
34
  </StyledGridPaperSection>
@@ -5,9 +5,11 @@ import { PALETTE } from "../../../../../../../../../styles/common/constants/pale
5
5
  import { formatCountSize } from "../../../../../../../../../utils/formatCountSize";
6
6
  import { DATA_FIELD, MARGIN_LEFT, TEXT_PADDING } from "./constants";
7
7
  import {
8
+ getCategoryTotalCount,
8
9
  getCategoryValueText,
9
10
  getCategoryValueTextFill,
10
11
  getColorRangeValue,
12
+ getCountText,
11
13
  getCountTextFill,
12
14
  getPlotHeight,
13
15
  getTicks,
@@ -23,6 +25,7 @@ export function getPlotOptions(
23
25
  width: number
24
26
  ): PlotOptions {
25
27
  const isCategorySelected = isAnyValueSelected(selectCategoryValueViews);
28
+ const totalCount = getCategoryTotalCount(selectCategoryValueViews);
26
29
  return {
27
30
  color: {
28
31
  domain: [false, true], // false = unselected, true = selected.
@@ -77,7 +80,7 @@ export function getPlotOptions(
77
80
  fontWeight: 500,
78
81
  lineHeight: 0.8125,
79
82
  render: renderText,
80
- text: DATA_FIELD.COUNT,
83
+ text: (d) => getCountText(d, totalCount),
81
84
  textAnchor: "end",
82
85
  x: DATA_FIELD.COUNT,
83
86
  y: DATA_FIELD.LABEL,
@@ -15,6 +15,17 @@ import {
15
15
  TICKS,
16
16
  } from "./constants";
17
17
 
18
+ /**
19
+ * Calculates the total count of all category values from the provided array of category value views.
20
+ * @param selectCategoryValueViews - An array of objects representing category values, where each object contains a count property.
21
+ * @returns The total sum of the count property from all category value objects in the array.
22
+ */
23
+ export function getCategoryTotalCount(
24
+ selectCategoryValueViews: SelectCategoryValueView[]
25
+ ): number {
26
+ return selectCategoryValueViews.reduce((acc, { count }) => acc + count, 0);
27
+ }
28
+
18
29
  /**
19
30
  * Returns the text for the category value point.
20
31
  * @param d - Data point.
@@ -49,6 +60,38 @@ export function getColorRangeValue(isCategorySelected: boolean): string {
49
60
  return isCategorySelected ? PALETTE.SMOKE_LIGHT : "#C5E3FC";
50
61
  }
51
62
 
63
+ /**
64
+ * Calculates the percentage representation of a count value relative to a total.
65
+ * @param d - Data point containing the count value to calculate the percentage for.
66
+ * @param total - The total value used as the denominator to compute the percentage.
67
+ * @returns The calculated percentage value, rounded to one decimal place.
68
+ */
69
+ export function getCountPercentage(
70
+ d: SelectCategoryValueView,
71
+ total: number
72
+ ): string {
73
+ if (total === 0) return "0";
74
+ const percentage = (d.count / total) * 100;
75
+ const roundedPercentage = Math.round(percentage * 10) / 10;
76
+ if (roundedPercentage < 0.1) return "< 0.1";
77
+ // Round to one decimal place.
78
+ return roundedPercentage.toFixed(1);
79
+ }
80
+
81
+ /**
82
+ * Returns the text for the count point.
83
+ * Includes the count and its percentage of the total.
84
+ * @param d - Data point.
85
+ * @param total - Total count.
86
+ * @returns Text string.
87
+ */
88
+ export function getCountText(
89
+ d: SelectCategoryValueView,
90
+ total: number
91
+ ): string {
92
+ return `${d.count.toLocaleString()} (${getCountPercentage(d, total)}%)`;
93
+ }
94
+
52
95
  /**
53
96
  * Returns the fill color for the count point.
54
97
  * @param d - Data point.
@@ -1,29 +1,22 @@
1
+ import { RowData } from "@tanstack/react-table";
1
2
  import React from "react";
2
3
  import { useConfig } from "../../../../../../hooks/useConfig";
3
4
  import { useExploreState } from "../../../../../../hooks/useExploreState";
4
5
  import { TableCreator } from "../../../../../TableCreator/tableCreator";
5
6
  import { EntityListProps } from "./types";
6
7
 
7
- export const EntityList = ({
8
+ export const EntityList = <T extends RowData>({
8
9
  entityListType,
9
- }: EntityListProps): JSX.Element | null => {
10
+ loading,
11
+ table,
12
+ }: EntityListProps<T>): JSX.Element | null => {
10
13
  const { entityConfig } = useConfig();
11
14
  const { exploreState } = useExploreState();
12
- const { listItems, loading } = exploreState;
13
- const { getId: getRowId, list, listView } = entityConfig;
14
- const { columns: columnsConfig } = list;
15
+ const { listView } = entityConfig;
15
16
 
16
17
  // required currently for client-side fetching as the pre-rendered page
17
18
  // loads with the previous tabs data on the first render after switching tabs. (or similar)
18
19
  if (entityListType !== exploreState.tabValue) return null;
19
20
 
20
- return (
21
- <TableCreator
22
- columns={columnsConfig}
23
- getRowId={getRowId}
24
- items={listItems ?? []}
25
- listView={listView}
26
- loading={loading}
27
- />
28
- );
21
+ return <TableCreator listView={listView} loading={loading} table={table} />;
29
22
  };
@@ -1,3 +1,7 @@
1
- export interface EntityListProps {
1
+ import { RowData, Table } from "@tanstack/react-table";
2
+
3
+ export interface EntityListProps<T extends RowData> {
2
4
  entityListType: string;
5
+ loading: boolean;
6
+ table: Table<T>;
3
7
  }
@@ -1,19 +1,15 @@
1
1
  import { Meta, StoryObj } from "@storybook/react";
2
- import { Summaries } from "./summaries";
2
+ import { Summaries } from "../summaries";
3
3
 
4
- const meta = {
5
- argTypes: {
6
- summaries: { control: "object" },
7
- },
4
+ const meta: Meta<typeof Summaries> = {
8
5
  component: Summaries,
9
- title: "Components/Summary",
10
- } satisfies Meta<typeof Summaries>;
6
+ };
11
7
 
12
8
  export default meta;
13
9
 
14
10
  type Story = StoryObj<typeof meta>;
15
11
 
16
- export const SummariesStory: Story = {
12
+ export const Default: Story = {
17
13
  args: {
18
14
  summaries: [
19
15
  { count: "1", label: "Species" },
@@ -0,0 +1,22 @@
1
+ import { composeStories, Meta, StoryObj } from "@storybook/react";
2
+ import React from "react";
3
+ import * as summaryStories from "../components/Summaries/stories/summaries.stories";
4
+ import { Hero } from "../hero";
5
+
6
+ const { Default: Summaries } = composeStories(summaryStories);
7
+
8
+ const meta: Meta<typeof Hero> = {
9
+ component: Hero,
10
+ parameters: { layout: "fullscreen" },
11
+ };
12
+
13
+ export default meta;
14
+
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ export const Default: Story = {
18
+ args: {
19
+ Summaries: <Summaries />,
20
+ title: "Data Explorer",
21
+ },
22
+ };
@@ -1,17 +1,22 @@
1
1
  import React from "react";
2
2
  import { useLayoutDimensions } from "../../providers/layoutDimensions/hook";
3
+ import { ChartView } from "./components/EntitiesView/components/ChartView/chartView";
4
+ import { EntityList } from "./components/EntitiesView/components/EntityList/entityList";
3
5
  import { EntitiesView } from "./components/EntitiesView/entitiesView";
4
6
  import { useEntitiesView } from "./components/EntitiesView/hooks/UseEntitiesView/hook";
5
7
  import { VIEW_MODE } from "./components/EntitiesView/hooks/UseEntitiesView/types";
6
8
  import { Hero } from "./components/Hero/hero";
7
9
  import { Index as IndexLayout } from "./index.styles";
10
+ import { useTable } from "./table/hook";
8
11
  import { IndexProps } from "./types";
9
12
 
10
13
  export const Index = ({
11
- chart,
14
+ categoryFilters,
12
15
  className,
13
- list,
16
+ entityListType,
17
+ entityName,
14
18
  ListHero,
19
+ loading,
15
20
  SideBarButton,
16
21
  SubTitleHero,
17
22
  Summaries,
@@ -20,6 +25,7 @@ export const Index = ({
20
25
  }: IndexProps): JSX.Element => {
21
26
  const { onChange, viewMode, viewStatus } = useEntitiesView();
22
27
  const { dimensions } = useLayoutDimensions();
28
+ const { table } = useTable();
23
29
  return (
24
30
  <IndexLayout className={className} marginTop={dimensions.header.height}>
25
31
  <Hero SideBarButton={SideBarButton} Summaries={Summaries} title={title} />
@@ -31,7 +37,19 @@ export const Index = ({
31
37
  viewMode={viewMode}
32
38
  viewStatus={viewStatus}
33
39
  >
34
- {viewMode === VIEW_MODE.TABLE ? list : chart}
40
+ {viewMode === VIEW_MODE.TABLE ? (
41
+ <EntityList
42
+ entityListType={entityListType}
43
+ loading={loading}
44
+ table={table}
45
+ />
46
+ ) : (
47
+ <ChartView
48
+ categoryFilters={categoryFilters}
49
+ entityName={entityName}
50
+ loading={loading}
51
+ />
52
+ )}
35
53
  </EntitiesView>
36
54
  </IndexLayout>
37
55
  );
@@ -0,0 +1,23 @@
1
+ import { composeStories, Meta, StoryObj } from "@storybook/react";
2
+ import React from "react";
3
+ import * as summaryStories from "../components/Hero/components/Summaries/stories/summaries.stories";
4
+ import { Index } from "../index";
5
+
6
+ const { Default: Summaries } = composeStories(summaryStories);
7
+
8
+ const meta: Meta<typeof Index> = {
9
+ component: Index,
10
+ parameters: { layout: "fullscreen" },
11
+ };
12
+
13
+ export default meta;
14
+
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ export const Default: Story = {
18
+ args: {
19
+ Summaries: <Summaries />,
20
+ Tabs: undefined,
21
+ title: "Explore Data",
22
+ },
23
+ };
@@ -0,0 +1,22 @@
1
+ import { CellContext, RowData } from "@tanstack/react-table";
2
+ import React from "react";
3
+ import { ComponentCreator } from "../../../../../components/ComponentCreator/ComponentCreator";
4
+ import { ColumnConfig } from "../../../../../config/entities";
5
+
6
+ /**
7
+ * Creates a cell renderer function for a given column configuration.
8
+ * This factory pattern allows us to create cell renderers without defining JSX in hook files.
9
+ * @param config - The column configuration.
10
+ * @returns A function that renders the cell content.
11
+ */
12
+ export function createCell<T extends RowData>(config: ColumnConfig<T>) {
13
+ return function CellCreator(context: CellContext<T, unknown>): JSX.Element {
14
+ return (
15
+ <ComponentCreator
16
+ components={[config.componentConfig]}
17
+ response={context.row.original}
18
+ viewContext={{ cellContext: context }}
19
+ />
20
+ );
21
+ };
22
+ }