@databiosphere/findable-ui 29.0.2 → 30.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 (205) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +22 -0
  3. package/lib/common/categories/config/types.d.ts +28 -0
  4. package/lib/common/categories/config/utils.d.ts +31 -0
  5. package/lib/common/categories/config/utils.js +29 -0
  6. package/lib/common/categories/models/range/typeGuards.d.ts +14 -0
  7. package/lib/common/categories/models/range/typeGuards.js +18 -0
  8. package/lib/common/categories/models/range/types.d.ts +15 -0
  9. package/lib/common/categories/models/range/types.js +1 -0
  10. package/lib/common/categories/models/range/utils.d.ts +23 -0
  11. package/lib/common/categories/models/range/utils.js +41 -0
  12. package/lib/common/categories/models/select/utils.d.ts +8 -0
  13. package/lib/common/categories/models/select/utils.js +16 -0
  14. package/lib/common/categories/models/types.d.ts +6 -0
  15. package/lib/common/categories/models/types.js +1 -0
  16. package/lib/common/categories/views/common/types.d.ts +10 -0
  17. package/lib/common/categories/views/common/types.js +1 -0
  18. package/lib/common/categories/views/range/typeGuards.d.ts +8 -0
  19. package/lib/common/categories/views/range/typeGuards.js +8 -0
  20. package/lib/common/categories/views/range/types.d.ts +19 -0
  21. package/lib/common/categories/views/range/types.js +1 -0
  22. package/lib/common/categories/views/range/utils.d.ts +12 -0
  23. package/lib/common/categories/views/range/utils.js +24 -0
  24. package/lib/common/categories/views/select/typeGuards.d.ts +8 -0
  25. package/lib/common/categories/views/select/typeGuards.js +8 -0
  26. package/lib/common/categories/views/select/types.d.ts +7 -0
  27. package/lib/common/categories/views/select/types.js +1 -0
  28. package/lib/common/categories/views/types.d.ts +13 -0
  29. package/lib/common/categories/views/types.js +8 -0
  30. package/lib/common/entities.d.ts +5 -2
  31. package/lib/components/DataDictionary/components/Table/components/BasicCell/basicCell.d.ts +3 -2
  32. package/lib/components/DataDictionary/components/Table/components/BasicCell/basicCell.js +6 -2
  33. package/lib/components/DataDictionary/components/Table/components/BasicCell/utils.d.ts +9 -0
  34. package/lib/components/DataDictionary/components/Table/components/BasicCell/utils.js +12 -0
  35. package/lib/components/DataDictionary/dataDictionary.styles.js +2 -3
  36. package/lib/components/Filter/components/Filter/filter.d.ts +2 -2
  37. package/lib/components/Filter/components/Filter/filter.js +11 -3
  38. package/lib/components/Filter/components/Filter/stories/args.d.ts +5 -0
  39. package/lib/components/Filter/components/Filter/stories/args.js +19 -0
  40. package/lib/components/Filter/components/Filter/stories/filter.stories.d.ts +8 -0
  41. package/lib/components/Filter/components/Filter/stories/filter.stories.js +21 -0
  42. package/lib/components/Filter/components/FilterMenu/filterMenu.js +2 -2
  43. package/lib/components/Filter/components/FilterMenu/filterMenu.styles.d.ts +1 -1
  44. package/lib/components/Filter/components/FilterMenu/filterMenu.styles.js +1 -1
  45. package/lib/components/Filter/components/FilterRange/constants.d.ts +0 -2
  46. package/lib/components/Filter/components/FilterRange/constants.js +0 -5
  47. package/lib/components/Filter/components/FilterRange/filterRange.d.ts +1 -1
  48. package/lib/components/Filter/components/FilterRange/filterRange.js +50 -21
  49. package/lib/components/Filter/components/FilterRange/filterRange.styles.js +58 -10
  50. package/lib/components/Filter/components/FilterRange/hooks/UseFilterRange/constants.d.ts +5 -0
  51. package/lib/components/Filter/components/FilterRange/hooks/UseFilterRange/constants.js +5 -0
  52. package/lib/components/Filter/components/FilterRange/hooks/UseFilterRange/hook.d.ts +2 -2
  53. package/lib/components/Filter/components/FilterRange/hooks/UseFilterRange/hook.js +32 -7
  54. package/lib/components/Filter/components/FilterRange/hooks/UseFilterRange/schema.d.ts +6 -0
  55. package/lib/components/Filter/components/FilterRange/hooks/UseFilterRange/schema.js +50 -0
  56. package/lib/components/Filter/components/FilterRange/hooks/UseFilterRange/types.d.ts +26 -3
  57. package/lib/components/Filter/components/FilterRange/hooks/UseFilterRange/types.js +6 -1
  58. package/lib/components/Filter/components/FilterRange/hooks/UseFilterRange/utils.d.ts +15 -0
  59. package/lib/components/Filter/components/FilterRange/hooks/UseFilterRange/utils.js +25 -0
  60. package/lib/components/Filter/components/FilterRange/stories/args.d.ts +3 -0
  61. package/lib/components/Filter/components/FilterRange/stories/args.js +13 -0
  62. package/lib/components/Filter/components/FilterRange/stories/filterRange.stories.js +2 -2
  63. package/lib/components/Filter/components/FilterRange/types.d.ts +10 -6
  64. package/lib/components/Filter/components/FilterRange/types.js +1 -6
  65. package/lib/components/Filter/components/FilterRange/utils.d.ts +8 -0
  66. package/lib/components/Filter/components/FilterRange/utils.js +15 -0
  67. package/lib/components/Filter/components/FilterTag/stories/args.d.ts +5 -0
  68. package/lib/components/Filter/components/FilterTag/stories/args.js +17 -0
  69. package/lib/components/Filter/components/FilterTag/stories/filterTag.stories.d.ts +8 -0
  70. package/lib/components/Filter/components/FilterTag/stories/filterTag.stories.js +21 -0
  71. package/lib/components/Filter/components/FilterTag/utils.d.ts +10 -0
  72. package/lib/components/Filter/components/FilterTag/utils.js +40 -0
  73. package/lib/components/Filter/components/Filters/filters.d.ts +2 -2
  74. package/lib/components/Filter/components/Filters/filters.js +15 -8
  75. package/lib/components/Filter/components/Filters/stories/args.d.ts +3 -0
  76. package/lib/components/Filter/components/Filters/stories/args.js +15 -0
  77. package/lib/components/Filter/components/Filters/stories/constants.d.ts +22 -0
  78. package/lib/components/Filter/components/Filters/stories/constants.js +134 -0
  79. package/lib/components/Filter/components/Filters/stories/filters.stories.d.ts +6 -0
  80. package/lib/components/Filter/components/Filters/stories/filters.stories.js +15 -0
  81. package/lib/components/Filter/components/SearchAllFilters/components/VariableSizeList/VariableSizeList.d.ts +1 -1
  82. package/lib/components/Filter/components/SearchAllFilters/components/VariableSizeList/VariableSizeList.js +5 -5
  83. package/lib/components/Filter/components/SearchAllFilters/components/VariableSizeListItem/variableSizeListItem.js +2 -1
  84. package/lib/components/Filter/components/SearchAllFilters/searchAllFilters.d.ts +3 -2
  85. package/lib/components/Filter/components/SearchAllFilters/searchAllFilters.js +6 -4
  86. package/lib/components/Filter/components/VariableSizeListItem/variableSizeListItem.js +2 -1
  87. package/lib/components/Index/components/EntitiesView/components/ChartView/utils.js +2 -0
  88. package/lib/components/Index/table/hook.js +4 -0
  89. package/lib/components/Table/columnDef/accessorFn/typeGuards.d.ts +9 -0
  90. package/lib/components/Table/columnDef/accessorFn/typeGuards.js +10 -0
  91. package/lib/components/Table/common/utils.d.ts +2 -2
  92. package/lib/components/Table/common/utils.js +28 -13
  93. package/lib/components/Table/components/TableCell/components/ChipCell/chipCell.d.ts +3 -0
  94. package/lib/components/Table/components/TableCell/components/ChipCell/chipCell.js +8 -0
  95. package/lib/components/Table/components/TableCell/components/LinkCell/linkCell.d.ts +4 -0
  96. package/lib/components/Table/components/TableCell/components/LinkCell/linkCell.js +21 -0
  97. package/lib/components/Table/components/TableCell/components/LinkCell/stories/args.d.ts +6 -0
  98. package/lib/components/Table/components/TableCell/components/LinkCell/stories/args.js +27 -0
  99. package/lib/components/Table/components/TableCell/components/LinkCell/stories/linkCell.stories.d.ts +9 -0
  100. package/lib/components/Table/components/TableCell/components/LinkCell/stories/linkCell.stories.js +18 -0
  101. package/lib/components/Table/components/TableCell/components/LinkCell/stories/types.d.ts +3 -0
  102. package/lib/components/Table/components/TableCell/components/LinkCell/stories/types.js +1 -0
  103. package/lib/components/Table/components/TableCell/components/LinkCell/utils.d.ts +22 -0
  104. package/lib/components/Table/components/TableCell/components/LinkCell/utils.js +45 -0
  105. package/lib/components/Table/featureOptions/facetedColumn/getFacetedMinMaxValues.d.ts +8 -0
  106. package/lib/components/Table/featureOptions/facetedColumn/getFacetedMinMaxValues.js +46 -0
  107. package/lib/components/common/Link/typeGuards.d.ts +13 -0
  108. package/lib/components/common/Link/typeGuards.js +21 -0
  109. package/lib/config/entities.d.ts +2 -11
  110. package/lib/hooks/useCategoryFilter.d.ts +8 -13
  111. package/lib/hooks/useCategoryFilter.js +31 -28
  112. package/lib/providers/exploreState/entities.d.ts +5 -3
  113. package/lib/providers/exploreState/payloads/entities.d.ts +6 -2
  114. package/lib/providers/exploreState.d.ts +3 -2
  115. package/lib/providers/exploreState.js +1 -1
  116. package/lib/tests/utils.d.ts +24 -0
  117. package/lib/tests/utils.js +34 -0
  118. package/lib/theme/common/components.js +19 -1
  119. package/lib/views/ExploreView/exploreView.js +10 -8
  120. package/package.json +2 -1
  121. package/src/common/categories/config/types.ts +42 -0
  122. package/src/common/categories/config/utils.ts +47 -0
  123. package/src/common/categories/models/range/typeGuards.ts +24 -0
  124. package/src/common/categories/models/range/types.ts +17 -0
  125. package/src/common/categories/models/range/utils.ts +51 -0
  126. package/src/common/categories/models/select/utils.ts +23 -0
  127. package/src/common/categories/models/types.ts +7 -0
  128. package/src/common/categories/views/common/types.ts +11 -0
  129. package/src/common/categories/views/range/typeGuards.ts +13 -0
  130. package/src/common/categories/views/range/types.ts +21 -0
  131. package/src/common/categories/views/range/utils.ts +35 -0
  132. package/src/common/categories/views/select/typeGuards.ts +13 -0
  133. package/src/common/categories/views/select/types.ts +8 -0
  134. package/src/common/categories/views/types.ts +15 -0
  135. package/src/common/entities.ts +10 -5
  136. package/src/components/DataDictionary/components/Table/components/BasicCell/basicCell.tsx +12 -4
  137. package/src/components/DataDictionary/components/Table/components/BasicCell/utils.ts +13 -0
  138. package/src/components/DataDictionary/dataDictionary.styles.ts +2 -3
  139. package/src/components/Filter/components/Filter/filter.tsx +38 -13
  140. package/src/components/Filter/components/Filter/stories/args.ts +24 -0
  141. package/src/components/Filter/components/Filter/stories/filter.stories.tsx +32 -0
  142. package/src/components/Filter/components/FilterMenu/filterMenu.styles.ts +1 -1
  143. package/src/components/Filter/components/FilterMenu/filterMenu.tsx +7 -3
  144. package/src/components/Filter/components/FilterRange/constants.ts +0 -7
  145. package/src/components/Filter/components/FilterRange/filterRange.styles.ts +58 -14
  146. package/src/components/Filter/components/FilterRange/filterRange.tsx +112 -40
  147. package/src/components/Filter/components/FilterRange/hooks/UseFilterRange/constants.ts +5 -0
  148. package/src/components/Filter/components/FilterRange/hooks/UseFilterRange/hook.ts +51 -10
  149. package/src/components/Filter/components/FilterRange/hooks/UseFilterRange/schema.ts +60 -0
  150. package/src/components/Filter/components/FilterRange/hooks/UseFilterRange/types.ts +34 -3
  151. package/src/components/Filter/components/FilterRange/hooks/UseFilterRange/utils.ts +32 -0
  152. package/src/components/Filter/components/FilterRange/stories/args.ts +16 -0
  153. package/src/components/Filter/components/FilterRange/stories/filterRange.stories.tsx +2 -2
  154. package/src/components/Filter/components/FilterRange/types.ts +12 -6
  155. package/src/components/Filter/components/FilterRange/utils.ts +16 -0
  156. package/src/components/Filter/components/FilterTag/stories/args.ts +22 -0
  157. package/src/components/Filter/components/FilterTag/stories/filterTag.stories.tsx +32 -0
  158. package/src/components/Filter/components/FilterTag/utils.ts +57 -0
  159. package/src/components/Filter/components/Filters/filters.tsx +21 -12
  160. package/src/components/Filter/components/Filters/stories/args.ts +24 -0
  161. package/src/components/Filter/components/Filters/stories/constants.ts +151 -0
  162. package/src/components/Filter/components/Filters/stories/filters.stories.tsx +24 -0
  163. package/src/components/Filter/components/SearchAllFilters/components/VariableSizeList/VariableSizeList.tsx +32 -29
  164. package/src/components/Filter/components/SearchAllFilters/components/VariableSizeListItem/variableSizeListItem.tsx +9 -1
  165. package/src/components/Filter/components/SearchAllFilters/searchAllFilters.tsx +12 -6
  166. package/src/components/Filter/components/VariableSizeListItem/variableSizeListItem.tsx +2 -1
  167. package/src/components/Index/components/EntitiesView/components/ChartView/utils.ts +2 -0
  168. package/src/components/Index/table/hook.ts +4 -0
  169. package/src/components/Table/columnDef/accessorFn/typeGuards.ts +15 -0
  170. package/src/components/Table/common/utils.ts +37 -16
  171. package/src/components/Table/components/TableCell/components/ChipCell/chipCell.tsx +14 -0
  172. package/src/components/Table/components/TableCell/components/LinkCell/linkCell.tsx +64 -0
  173. package/src/components/Table/components/TableCell/components/LinkCell/stories/args.ts +35 -0
  174. package/src/components/Table/components/TableCell/components/LinkCell/stories/linkCell.stories.tsx +32 -0
  175. package/src/components/Table/components/TableCell/components/LinkCell/stories/types.ts +4 -0
  176. package/src/components/Table/components/TableCell/components/LinkCell/utils.ts +59 -0
  177. package/src/components/Table/featureOptions/facetedColumn/getFacetedMinMaxValues.ts +64 -0
  178. package/src/components/common/Link/typeGuards.ts +35 -0
  179. package/src/config/entities.ts +1 -14
  180. package/src/hooks/useCategoryFilter.ts +56 -53
  181. package/src/providers/exploreState/entities.ts +3 -3
  182. package/src/providers/exploreState/initializer/utils.ts +1 -1
  183. package/src/providers/exploreState/payloads/entities.ts +5 -2
  184. package/src/providers/exploreState.tsx +5 -3
  185. package/src/tests/utils.ts +44 -0
  186. package/src/theme/common/components.ts +19 -1
  187. package/src/views/ExploreView/exploreView.tsx +17 -22
  188. package/tests/filter.test.tsx +100 -0
  189. package/tests/filterRange.test.tsx +331 -46
  190. package/tests/filters.test.tsx +61 -0
  191. package/tests/getFacetedMinMaxValues.test.ts +166 -0
  192. package/tests/linkCell.test.tsx +89 -0
  193. package/lib/components/DataDictionary/components/Table/components/BasicCell/types.d.ts +0 -3
  194. package/lib/components/Filter/components/Filter/filter.stories.d.ts +0 -25
  195. package/lib/components/Filter/components/Filter/filter.stories.js +0 -42
  196. package/lib/components/Filter/components/FilterTag/filterTag.stories.d.ts +0 -16
  197. package/lib/components/Filter/components/FilterTag/filterTag.stories.js +0 -17
  198. package/lib/components/Filter/components/Filters/filters.stories.d.ts +0 -6
  199. package/lib/components/Filter/components/Filters/filters.stories.js +0 -91
  200. package/src/components/DataDictionary/components/Table/components/BasicCell/types.ts +0 -7
  201. package/src/components/Filter/components/Filter/filter.stories.tsx +0 -52
  202. package/src/components/Filter/components/FilterTag/filterTag.stories.tsx +0 -23
  203. package/src/components/Filter/components/Filters/filters.stories.tsx +0 -101
  204. package/tests/filterRangeMock.test.tsx +0 -38
  205. /package/lib/{components/DataDictionary/components/Table/components/BasicCell → common/categories/config}/types.js +0 -0
@@ -9,7 +9,7 @@ import {
9
9
  sortingFns,
10
10
  Table,
11
11
  } from "@tanstack/react-table";
12
- import { SelectCategory } from "../../../common/entities";
12
+ import { Category } from "../../../common/categories/models/types";
13
13
  import { EXPLORE_MODE, ExploreMode } from "../../../hooks/useExploreMode/types";
14
14
  import { COLUMN_IDENTIFIER } from "./columnIdentifier";
15
15
 
@@ -38,26 +38,47 @@ type TableData = number | string | string[];
38
38
  export function buildCategoryViews<T extends RowData>(
39
39
  columns: Column<T>[],
40
40
  columnFilters: ColumnFiltersState
41
- ): SelectCategory[] {
42
- const categoryViews: SelectCategory[] = [];
41
+ ): Category[] {
42
+ const categoryViews: Category[] = [];
43
43
  for (const column of columns) {
44
- const { columnDef, getCanFilter, getFacetedUniqueValues, id } = column;
44
+ const {
45
+ columnDef,
46
+ getCanFilter,
47
+ getFacetedMinMaxValues,
48
+ getFacetedUniqueValues,
49
+ id,
50
+ } = column;
45
51
  const { header: columnHeader } = columnDef;
46
52
  if (getCanFilter()) {
47
- updateFacetedUniqueValues(getFacetedUniqueValues(), columnFilters, id);
48
53
  const key = id;
49
54
  const label = columnHeader as string;
50
- const values = [...getFacetedUniqueValues()].map(([value, count]) => ({
51
- count,
52
- key: value,
53
- label: String(value ?? ""),
54
- selected: false, // Selected state updated in reducer.
55
- }));
56
- categoryViews.push({
57
- key,
58
- label,
59
- values: values,
60
- });
55
+ // Handle range categories.
56
+ if (columnDef.filterFn === "inNumberRange") {
57
+ const minMax = getFacetedMinMaxValues();
58
+ categoryViews.push({
59
+ key,
60
+ label,
61
+ max: minMax?.[1] || Infinity,
62
+ min: minMax?.[0] || -Infinity,
63
+ selectedMax: null, // Selected state updated in reducer.
64
+ selectedMin: null, // Selected state updated in reducer.
65
+ });
66
+ }
67
+ // Handle select categories.
68
+ if (columnDef.filterFn === "arrIncludesSome") {
69
+ updateFacetedUniqueValues(getFacetedUniqueValues(), columnFilters, id);
70
+ const values = [...getFacetedUniqueValues()].map(([value, count]) => ({
71
+ count,
72
+ key: value,
73
+ label: String(value ?? ""),
74
+ selected: false, // Selected state updated in reducer.
75
+ }));
76
+ categoryViews.push({
77
+ key,
78
+ label,
79
+ values: values,
80
+ });
81
+ }
61
82
  }
62
83
  }
63
84
  return categoryViews;
@@ -0,0 +1,14 @@
1
+ import { Chip, ChipProps } from "@mui/material";
2
+ import { CellContext, RowData } from "@tanstack/react-table";
3
+ import React from "react";
4
+
5
+ export const ChipCell = <
6
+ T extends RowData,
7
+ TValue extends ChipProps = ChipProps
8
+ >({
9
+ getValue,
10
+ }: CellContext<T, TValue>): JSX.Element | null => {
11
+ const props = getValue();
12
+ if (!props) return null;
13
+ return <Chip {...props} />;
14
+ };
@@ -0,0 +1,64 @@
1
+ import { LinkProps, Link as MLink, Typography } from "@mui/material";
2
+ import { CellContext, RowData } from "@tanstack/react-table";
3
+ import { BaseComponentProps } from "components/types";
4
+ import React from "react";
5
+ import { isValidUrl } from "../../../../../../common/utils";
6
+ import { TYPOGRAPHY_PROPS } from "../../../../../../styles/common/mui/typography";
7
+ import { isClientSideNavigation } from "../../../../../Links/common/utils";
8
+ import { getComponent, getRelAttribute, getTargetAttribute } from "./utils";
9
+
10
+ export const LinkCell = <
11
+ T extends RowData,
12
+ TValue extends LinkProps = LinkProps
13
+ >({
14
+ getValue,
15
+ }: BaseComponentProps & CellContext<T, TValue>): JSX.Element | null => {
16
+ const props = getValue();
17
+ if (!props) return null;
18
+ const {
19
+ children,
20
+ className,
21
+ color,
22
+ href = "",
23
+ rel,
24
+ target,
25
+ underline,
26
+ ...linkProps
27
+ } = props;
28
+
29
+ // Determine if the href should use client-side navigation.
30
+ const isClientSide = isClientSideNavigation(href);
31
+
32
+ // Determine if the href is valid.
33
+ const isValid = isClientSide || isValidUrl(href);
34
+
35
+ // If the href is invalid, return a Typography component.
36
+ if (!isValid)
37
+ return (
38
+ <Typography
39
+ className={className}
40
+ color={TYPOGRAPHY_PROPS.COLOR.INHERIT}
41
+ component="span"
42
+ variant={TYPOGRAPHY_PROPS.VARIANT.INHERIT}
43
+ {...linkProps}
44
+ >
45
+ {children}
46
+ </Typography>
47
+ );
48
+
49
+ // If the href is valid, return a Link component.
50
+ return (
51
+ <MLink
52
+ className={className}
53
+ color={color}
54
+ component={getComponent(href, isClientSide)}
55
+ href={href}
56
+ rel={getRelAttribute(rel, isClientSide)}
57
+ target={getTargetAttribute(target, isClientSide)}
58
+ underline={underline}
59
+ {...linkProps}
60
+ >
61
+ {children}
62
+ </MLink>
63
+ );
64
+ };
@@ -0,0 +1,35 @@
1
+ import { ComponentProps } from "react";
2
+ import { TYPOGRAPHY_PROPS } from "../../../../../../../styles/common/mui/typography";
3
+ import { LinkCell } from "../linkCell";
4
+ import { GetValue } from "./types";
5
+
6
+ export const CLIENT_SIDE_ARGS: Partial<ComponentProps<typeof LinkCell>> = {
7
+ getValue: (() => ({
8
+ children: "Explore",
9
+ href: "/",
10
+ })) as GetValue,
11
+ };
12
+
13
+ export const EXTERNAL_ARGS: Partial<ComponentProps<typeof LinkCell>> = {
14
+ getValue: (() => ({
15
+ children: "Explore",
16
+ href: "https://www.example.com",
17
+ })) as GetValue,
18
+ };
19
+
20
+ export const INVALID_ARGS: Partial<ComponentProps<typeof LinkCell>> = {
21
+ getValue: (() => ({
22
+ children: "Explore",
23
+ })) as GetValue,
24
+ };
25
+
26
+ export const WITH_CUSTOM_STYLE_ARGS: Partial<ComponentProps<typeof LinkCell>> =
27
+ {
28
+ getValue: (() => ({
29
+ children: "Explore",
30
+ color: "success",
31
+ href: "/",
32
+ underline: "none",
33
+ variant: TYPOGRAPHY_PROPS.VARIANT.TEXT_BODY_SMALL_400,
34
+ })) as GetValue,
35
+ };
@@ -0,0 +1,32 @@
1
+ import { Meta, StoryObj } from "@storybook/react";
2
+ import { LinkCell } from "../linkCell";
3
+ import {
4
+ CLIENT_SIDE_ARGS,
5
+ EXTERNAL_ARGS,
6
+ INVALID_ARGS,
7
+ WITH_CUSTOM_STYLE_ARGS,
8
+ } from "./args";
9
+
10
+ const meta: Meta<typeof LinkCell> = {
11
+ component: LinkCell,
12
+ };
13
+
14
+ export default meta;
15
+
16
+ type Story = StoryObj<typeof meta>;
17
+
18
+ export const ClientSide: Story = {
19
+ args: CLIENT_SIDE_ARGS,
20
+ };
21
+
22
+ export const External: Story = {
23
+ args: EXTERNAL_ARGS,
24
+ };
25
+
26
+ export const Invalid: Story = {
27
+ args: INVALID_ARGS,
28
+ };
29
+
30
+ export const WithCustomStyle: Story = {
31
+ args: WITH_CUSTOM_STYLE_ARGS,
32
+ };
@@ -0,0 +1,4 @@
1
+ import { LinkProps } from "@mui/material";
2
+ import { CellContext, RowData } from "@tanstack/react-table";
3
+
4
+ export type GetValue = CellContext<RowData, LinkProps>["getValue"];
@@ -0,0 +1,59 @@
1
+ import Link from "next/link";
2
+ import { ElementType } from "react";
3
+ import { isValidUrl } from "../../../../../../common/utils";
4
+ import {
5
+ ANCHOR_TARGET,
6
+ REL_ATTRIBUTE,
7
+ } from "../../../../../Links/common/entities";
8
+ import {
9
+ assertAnchorRelAttribute,
10
+ assertAnchorTargetAttribute,
11
+ } from "../../../../../common/Link/typeGuards";
12
+
13
+ /**
14
+ * Returns the component to use for a link based on the given href and client-side navigation flag.
15
+ * @param href - The href attribute to use.
16
+ * @param isClientSide - Whether the link is a client-side navigation.
17
+ * @returns The component to use for the link.
18
+ */
19
+ export function getComponent(href: string, isClientSide: boolean): ElementType {
20
+ if (isClientSide) return Link; // Use Next/Link for client-side navigation.
21
+ if (isValidUrl(href)) return "a"; // Use anchor tag for external links.
22
+ return "span"; // Use span for invalid links.
23
+ }
24
+
25
+ /**
26
+ * Returns the rel attribute for a link based on the given rel and client-side navigation flag.
27
+ * @param rel - The rel attribute to use.
28
+ * @param isClientSideNavigation - Whether the link is a client-side navigation.
29
+ * @returns The rel attribute for the link.
30
+ */
31
+ export function getRelAttribute(
32
+ rel: string | undefined,
33
+ isClientSideNavigation: boolean
34
+ ): string {
35
+ if (rel) {
36
+ assertAnchorRelAttribute(rel);
37
+ return rel;
38
+ }
39
+ return isClientSideNavigation
40
+ ? REL_ATTRIBUTE.NO_OPENER
41
+ : REL_ATTRIBUTE.NO_OPENER_NO_REFERRER;
42
+ }
43
+
44
+ /**
45
+ * Returns the target attribute for a link based on the given target and client-side navigation flag.
46
+ * @param target - The target attribute to use.
47
+ * @param isClientSideNavigation - Whether the link is a client-side navigation.
48
+ * @returns The target attribute for the link.
49
+ */
50
+ export function getTargetAttribute(
51
+ target: string | undefined,
52
+ isClientSideNavigation: boolean
53
+ ): string {
54
+ if (target) {
55
+ assertAnchorTargetAttribute(target);
56
+ return target;
57
+ }
58
+ return isClientSideNavigation ? ANCHOR_TARGET.SELF : ANCHOR_TARGET.BLANK;
59
+ }
@@ -0,0 +1,64 @@
1
+ import { getMemoOptions, memo, RowData, Table } from "@tanstack/react-table";
2
+
3
+ /**
4
+ * Returns an array of two numbers, the minimum and maximum values for the column, or undefined if the column does not exist or has no values.
5
+ * Customized version of the default getFacetedMinMaxValues function from tanstack table handling mixed null and possible NaN values.
6
+ * See https://tanstack.com/table/v8/docs/api/features/column-faceting#getfacetedminmaxvalues.
7
+ * @returns An array of two numbers, the minimum and maximum values for the column, or undefined if the column does not exist or has no values.
8
+ */
9
+ export function getFacetedMinMaxValues<TData extends RowData>(): (
10
+ table: Table<TData>,
11
+ columnId: string
12
+ ) => () => undefined | [number, number] {
13
+ // eslint-disable-next-line sonarjs/cognitive-complexity -- Customized copy of tanstack table function.
14
+ return (table, columnId) =>
15
+ memo(
16
+ () => [table.getColumn(columnId)?.getFacetedRowModel()],
17
+ (facetedRowModel) => {
18
+ if (!facetedRowModel) return undefined;
19
+
20
+ // Initialize with the smallest and largest possible numbers.
21
+ const facetedMinMaxValues: [number, number] = [Infinity, -Infinity];
22
+
23
+ for (let i = 0; i < facetedRowModel.flatRows.length; i++) {
24
+ const values =
25
+ facetedRowModel.flatRows[i]!.getUniqueValues<number>(columnId);
26
+
27
+ for (let j = 0; j < values.length; j++) {
28
+ const value = values[j]!;
29
+ // Convert value to a number.
30
+ const numericValue = Number(value);
31
+
32
+ // Skip null and NaN values.
33
+ if (value === null || isNaN(numericValue)) continue;
34
+
35
+ if (numericValue < facetedMinMaxValues[0]) {
36
+ facetedMinMaxValues[0] = numericValue;
37
+ }
38
+ if (numericValue > facetedMinMaxValues[1]) {
39
+ facetedMinMaxValues[1] = numericValue;
40
+ }
41
+ }
42
+ }
43
+
44
+ // Return undefined if all values are null or NaN.
45
+ if (
46
+ facetedMinMaxValues[0] === Infinity &&
47
+ facetedMinMaxValues[1] === -Infinity
48
+ ) {
49
+ return undefined;
50
+ }
51
+
52
+ // Normalize -0 to 0.
53
+ if (Object.is(facetedMinMaxValues[0], -0)) {
54
+ facetedMinMaxValues[0] = 0;
55
+ }
56
+ if (Object.is(facetedMinMaxValues[1], -0)) {
57
+ facetedMinMaxValues[1] = 0;
58
+ }
59
+
60
+ return facetedMinMaxValues;
61
+ },
62
+ getMemoOptions(table.options, "debugTable", "getFacetedMinMaxValues")
63
+ );
64
+ }
@@ -0,0 +1,35 @@
1
+ import { ANCHOR_TARGET, REL_ATTRIBUTE } from "../../Links/common/entities";
2
+
3
+ /**
4
+ * Asserts that the given value is a valid REL_ATTRIBUTE.
5
+ * @param value - Value to assert.
6
+ * @throws Error if the value is not a valid REL_ATTRIBUTE.
7
+ */
8
+ export function assertAnchorRelAttribute(
9
+ value: string
10
+ ): asserts value is REL_ATTRIBUTE {
11
+ if (!Object.values(REL_ATTRIBUTE).includes(value as REL_ATTRIBUTE)) {
12
+ throw new Error(
13
+ `Expecting rel attribute: ${value} to be one of ${Object.values(
14
+ REL_ATTRIBUTE
15
+ )}`
16
+ );
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Asserts that the given value is a valid ANCHOR_TARGET.
22
+ * @param value - Value to assert.
23
+ * @throws Error if the value is not a valid ANCHOR_TARGET.
24
+ */
25
+ export function assertAnchorTargetAttribute(
26
+ value: string
27
+ ): asserts value is ANCHOR_TARGET {
28
+ if (!Object.values(ANCHOR_TARGET).includes(value as ANCHOR_TARGET)) {
29
+ throw new Error(
30
+ `Expecting anchor target: ${value} to be one of ${Object.values(
31
+ ANCHOR_TARGET
32
+ )}`
33
+ );
34
+ }
35
+ }
@@ -10,10 +10,10 @@ import {
10
10
  TableOptions,
11
11
  } from "@tanstack/react-table";
12
12
  import { JSXElementConstructor, ReactNode } from "react";
13
+ import { CategoryConfig } from "../common/categories/config/types";
13
14
  import {
14
15
  DataDictionaryAnnotation,
15
16
  DataDictionaryConfig,
16
- SelectCategoryValueView,
17
17
  SelectedFilter,
18
18
  } from "../common/entities";
19
19
  import { HeroTitle } from "../components/common/Title/title";
@@ -88,19 +88,6 @@ export interface CategoryGroup {
88
88
  label?: string;
89
89
  }
90
90
 
91
- /**
92
- * Model of category configured in site config.
93
- */
94
- export interface CategoryConfig {
95
- annotation?: DataDictionaryAnnotation;
96
- enableChartView?: boolean;
97
- key: string;
98
- label: string;
99
- mapSelectCategoryValue?: (
100
- selectCategoryValue: SelectCategoryValueView
101
- ) => SelectCategoryValueView;
102
- }
103
-
104
91
  /**
105
92
  * Column configuration.
106
93
  * TanStack ColumnDef properties not currently supported include:
@@ -1,3 +1,11 @@
1
+ import { CategoryConfig } from "../common/categories/config/types";
2
+ import { findSelectCategoryConfig } from "../common/categories/config/utils";
3
+ import { isRangeCategory } from "../common/categories/models/range/typeGuards";
4
+ import { buildNextRangeFilterState } from "../common/categories/models/range/utils";
5
+ import { buildNextSelectFilterState } from "../common/categories/models/select/utils";
6
+ import { Category } from "../common/categories/models/types";
7
+ import { buildRangeCategoryView } from "../common/categories/views/range/utils";
8
+ import { CategoryView, VIEW_KIND } from "../common/categories/views/types";
1
9
  import { COLLATOR_CASE_INSENSITIVE } from "../common/constants";
2
10
  import {
3
11
  CategoryKey,
@@ -9,22 +17,12 @@ import {
9
17
  SelectCategoryView,
10
18
  SelectedFilter,
11
19
  } from "../common/entities";
12
- import { CategoryConfig } from "../config/entities";
13
20
 
14
21
  /**
15
22
  * State backing filter functionality and calculations. Converted to view model for display.
16
23
  */
17
24
  export type FilterState = Filters;
18
25
 
19
- /**
20
- * Shape of return value from this useCategoryFilter hook.
21
- */
22
- export interface FilterInstance {
23
- categories: SelectCategoryView[];
24
- filter: FilterState;
25
- onFilter: OnFilterFn;
26
- }
27
-
28
26
  /**
29
27
  * Function invoked when selected state of a category value is toggled or range is selected.
30
28
  */
@@ -33,6 +31,7 @@ export type OnFilterFn = (
33
31
  selectedCategoryValue: CategoryValueKey,
34
32
  selected: boolean,
35
33
  categorySection?: string,
34
+ viewKind?: VIEW_KIND,
36
35
  searchTerm?: string
37
36
  ) => void;
38
37
 
@@ -62,27 +61,30 @@ function buildCategoryValueView(
62
61
  }
63
62
 
64
63
  /**
65
- * Build the view-specific model of the given category, including the category label pulled from the config.
66
- * @param category - The category to build a view model of.
67
- * @param categoryValueViews - Set of category value view models for the given category.
64
+ * Build the view-specific model of the given select category, including the category label pulled from the config.
65
+ * @param selectCategory - The select category to build a view model of.
66
+ * @param selectCategoryValueViews - Set of select category value view models for the given category.
68
67
  * @param categoryConfigs - Category configs indicating accept list as well as label configuration.
69
- * @returns Full built category value view, ready for display.
68
+ * @returns Full built select category view, ready for display.
70
69
  */
71
70
  function buildCategoryView(
72
- category: SelectCategory,
73
- categoryValueViews: SelectCategoryValueView[],
71
+ selectCategory: SelectCategory,
72
+ selectCategoryValueViews: SelectCategoryValueView[],
74
73
  categoryConfigs: CategoryConfig[]
75
74
  ): SelectCategoryView {
76
- const categoryConfig = findCategoryConfig(category.key, categoryConfigs);
75
+ const selectCategoryConfig = findSelectCategoryConfig(
76
+ selectCategory.key,
77
+ categoryConfigs
78
+ );
77
79
  const mapSelectCategoryValue =
78
- categoryConfig?.mapSelectCategoryValue || getSelectCategoryValue;
80
+ selectCategoryConfig?.mapSelectCategoryValue || getSelectCategoryValue;
79
81
  return {
80
- annotation: categoryConfig?.annotation,
81
- enableChartView: categoryConfig?.enableChartView,
82
+ annotation: selectCategoryConfig?.annotation,
83
+ enableChartView: selectCategoryConfig?.enableChartView,
82
84
  isDisabled: false,
83
- key: category.key,
84
- label: getCategoryLabel(category.key, categoryConfig),
85
- values: categoryValueViews.map(mapSelectCategoryValue),
85
+ key: selectCategory.key,
86
+ label: getCategoryLabel(selectCategory.key, selectCategoryConfig),
87
+ values: selectCategoryValueViews.map(mapSelectCategoryValue),
86
88
  };
87
89
  }
88
90
 
@@ -94,10 +96,10 @@ function buildCategoryView(
94
96
  * @returns Array of category view objects.
95
97
  */
96
98
  export function buildCategoryViews(
97
- categories: SelectCategory[],
99
+ categories: Category[],
98
100
  categoryConfigs: CategoryConfig[] | undefined,
99
101
  filterState: FilterState
100
- ): SelectCategoryView[] {
102
+ ): CategoryView[] {
101
103
  if (!categories || !categoryConfigs) {
102
104
  return [];
103
105
  }
@@ -115,7 +117,16 @@ export function buildCategoryViews(
115
117
  filterState
116
118
  );
117
119
 
118
- // Build view models for each category value in this category and sort alpha.
120
+ // Build view model for range categories.
121
+ if (isRangeCategory(category)) {
122
+ return buildRangeCategoryView(
123
+ category,
124
+ categoryConfigs,
125
+ categorySelectedFilter
126
+ );
127
+ }
128
+
129
+ // Build view model for single or multiselect categories.
119
130
  const categoryValueViews = category.values.map((categoryValue) =>
120
131
  buildCategoryValueView(categoryValue, categorySelectedFilter)
121
132
  );
@@ -136,13 +147,15 @@ export function buildCategoryViews(
136
147
  * @param categoryKey - Key of category that has been de/selected.
137
148
  * @param selectedValue - Key of category value that has been de/selected
138
149
  * @param selected - True if value is selected, false if de-selected.
150
+ * @param viewKind - View kind.
139
151
  * @returns New filter state generated from the current set of selected values and the newly selected value.
140
152
  */
141
153
  export function buildNextFilterState(
142
154
  filterState: FilterState,
143
155
  categoryKey: CategoryKey,
144
156
  selectedValue: CategoryValueKey,
145
- selected: boolean
157
+ selected: boolean,
158
+ viewKind?: VIEW_KIND
146
159
  ): FilterState {
147
160
  // Check if the selected category already has selected values.
148
161
  const categorySelectedFilter = getCategorySelectedFilter(
@@ -162,14 +175,20 @@ export function buildNextFilterState(
162
175
  value: categorySelectedFilter ? [...categorySelectedFilter.value] : [],
163
176
  };
164
177
 
165
- // Handle case where category value is selected.
166
- if (selected) {
167
- nextCategorySelectedFilter.value.push(selectedValue);
168
- }
169
- // Otherwise, category value has been de-selected; remove the selected value from the selected set of values.
170
- else {
171
- nextCategorySelectedFilter.value = nextCategorySelectedFilter.value.filter(
172
- (value: CategoryValueKey) => value !== selectedValue
178
+ // Build next filter state for category.
179
+ if (viewKind === VIEW_KIND.RANGE) {
180
+ // Handle range category.
181
+ buildNextRangeFilterState(
182
+ nextCategorySelectedFilter,
183
+ selectedValue,
184
+ selected
185
+ );
186
+ } else {
187
+ // Handle select category.
188
+ buildNextSelectFilterState(
189
+ nextCategorySelectedFilter,
190
+ selectedValue,
191
+ selected
173
192
  );
174
193
  }
175
194
 
@@ -221,19 +240,6 @@ export function getSelectCategoryValue(
221
240
  return selectCategoryValue;
222
241
  }
223
242
 
224
- /**
225
- * Returns the category config for the given category config key.
226
- * @param key - Category config key.
227
- * @param categoryConfigs - Category configs.
228
- * @returns category config.
229
- */
230
- function findCategoryConfig(
231
- key: string,
232
- categoryConfigs: CategoryConfig[]
233
- ): CategoryConfig | undefined {
234
- return categoryConfigs.find((categoryConfig) => categoryConfig.key === key);
235
- }
236
-
237
243
  /**
238
244
  * Determine if given category value is selected.
239
245
  * @param categoryValueKey - The key of the category value to check if selected in the filter state.
@@ -257,7 +263,7 @@ function isCategoryValueSelected(
257
263
  * @returns true if category is to be included in filter.
258
264
  */
259
265
  function isCategoryAcceptListed(
260
- category: SelectCategory,
266
+ category: Category,
261
267
  categoryConfigs: CategoryConfig[]
262
268
  ): boolean {
263
269
  return categoryConfigs.some(
@@ -288,9 +294,6 @@ function sortCategoryValueViews(
288
294
  * @param c1 - Second category view to compare.
289
295
  * @returns Number indicating sort precedence of c0 vs c1.
290
296
  */
291
- function sortCategoryViews(
292
- c0: SelectCategoryView,
293
- c1: SelectCategoryView
294
- ): number {
297
+ function sortCategoryViews(c0: CategoryView, c1: CategoryView): number {
295
298
  return COLLATOR_CASE_INSENSITIVE.compare(c0.label, c1.label);
296
299
  }
@@ -4,15 +4,15 @@ import {
4
4
  RowSelectionState,
5
5
  VisibilityState,
6
6
  } from "@tanstack/react-table";
7
+ import { CategoryConfig } from "../../common/categories/config/types";
8
+ import { CategoryView } from "../../common/categories/views/types";
7
9
  import {
8
10
  CategoryValueKey,
9
11
  SelectCategory,
10
- SelectCategoryView,
11
12
  SelectedFilter,
12
13
  } from "../../common/entities";
13
14
  import { RowPreviewState } from "../../components/Table/features/RowPreview/entities";
14
15
  import {
15
- CategoryConfig,
16
16
  CategoryGroup,
17
17
  CategoryGroupConfig,
18
18
  EntityPath,
@@ -36,7 +36,7 @@ export interface EntityPageStateMapper {
36
36
  export interface EntityState {
37
37
  categoryConfigs?: CategoryConfig[];
38
38
  categoryGroups?: CategoryGroup[];
39
- categoryViews: SelectCategoryView[];
39
+ categoryViews: CategoryView[];
40
40
  filterState: SelectedFilter[];
41
41
  savedFilterByCategoryValueKey?: SavedFilterByCategoryValueKey;
42
42
  savedFilterState: SelectedFilter[];
@@ -3,10 +3,10 @@ import {
3
3
  GroupingState,
4
4
  VisibilityState,
5
5
  } from "@tanstack/react-table";
6
+ import { CategoryConfig } from "../../../common/categories/config/types";
6
7
  import { SelectCategory, SelectedFilter } from "../../../common/entities";
7
8
  import { getInitialColumnVisibilityState } from "../../../components/TableCreator/options/initialState/columnVisibility";
8
9
  import {
9
- CategoryConfig,
10
10
  CategoryGroup,
11
11
  CategoryGroupConfig,
12
12
  EntityConfig,