@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
@@ -46,9 +46,10 @@ export const ExploreView = (props) => {
46
46
  * @param selectedCategoryValue - The value to set or clear.
47
47
  * @param selected - Indication of whether the selected value is being set or cleared.
48
48
  * @param categorySection - Name of group the category is in.
49
+ * @param viewKind - View kind.
49
50
  * @param searchTerm - Search term used to find the value.
50
51
  */
51
- const onFilterChange = (fromSearchAll, categoryKey, selectedCategoryValue, selected, categorySection, searchTerm) => {
52
+ const onFilterChange = (fromSearchAll, categoryKey, selectedCategoryValue, selected, categorySection, viewKind, searchTerm) => {
52
53
  const dispatchType = categoryKey === SELECT_CATEGORY_KEY.SAVED_FILTERS
53
54
  ? ExploreActionKind.ApplySavedFilter
54
55
  : ExploreActionKind.UpdateFilter;
@@ -57,6 +58,7 @@ export const ExploreView = (props) => {
57
58
  categoryKey,
58
59
  selected,
59
60
  selectedValue: selectedCategoryValue,
61
+ viewKind,
60
62
  },
61
63
  type: dispatchType,
62
64
  });
@@ -107,25 +109,25 @@ export const ExploreView = (props) => {
107
109
  };
108
110
  /**
109
111
  * Builds the category views into category views grouped by the given category group configuration.
110
- * @param selectCategoryViews - View models of categories to display.
112
+ * @param categoryViews - View models of categories to display.
111
113
  * @param categoryGroups - Category groups.
112
114
  * @returns category filters.
113
115
  */
114
- function buildCategoryFilters(selectCategoryViews, categoryGroups) {
116
+ function buildCategoryFilters(categoryViews, categoryGroups) {
115
117
  if (!categoryGroups) {
116
- return [{ categoryViews: selectCategoryViews }];
118
+ return [{ categoryViews }];
117
119
  }
118
120
  return categoryGroups.reduce((accGroups, { categoryConfigs, label }) => {
119
121
  // Grab the category views for the configured grouped categories.
120
- const categoryViews = categoryConfigs.reduce((accViews, { key: categoryKey }) => {
121
- const categoryView = selectCategoryViews.find(({ key }) => key === categoryKey);
122
+ const views = categoryConfigs.reduce((accViews, { key: categoryKey }) => {
123
+ const categoryView = categoryViews.find(({ key }) => key === categoryKey);
122
124
  if (categoryView) {
123
125
  accViews.push(categoryView);
124
126
  }
125
127
  return accViews;
126
128
  }, []);
127
- if (categoryViews.length > 0) {
128
- accGroups.push({ categoryViews, label });
129
+ if (views.length > 0) {
130
+ accGroups.push({ categoryViews: views, label });
129
131
  }
130
132
  return accGroups;
131
133
  }, []);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@databiosphere/findable-ui",
3
- "version": "29.0.2",
3
+ "version": "30.0.0",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
@@ -24,6 +24,7 @@
24
24
  "devDependencies": {
25
25
  "@commitlint/cli": "^17.4.2",
26
26
  "@commitlint/config-conventional": "^17.4.2",
27
+ "@emotion/jest": "^11.13.0",
27
28
  "@mui/types": "^7.4.0",
28
29
  "@next/eslint-plugin-next": "^14.2.28",
29
30
  "@storybook/addon-actions": "^8.6.4",
@@ -0,0 +1,42 @@
1
+ import {
2
+ CategoryKey,
3
+ DataDictionaryAnnotation,
4
+ SelectCategoryValueView,
5
+ } from "../../entities";
6
+ import { RangeViewKind } from "../views/range/types";
7
+ import { SelectViewKind } from "../views/select/types";
8
+
9
+ /**
10
+ * Category config.
11
+ */
12
+ export type CategoryConfig = RangeCategoryConfig | SelectCategoryConfig;
13
+
14
+ /**
15
+ * Common category config.
16
+ */
17
+ export interface CommonCategoryConfig {
18
+ annotation?: DataDictionaryAnnotation;
19
+ key: CategoryKey;
20
+ label: string; // human readable label
21
+ }
22
+
23
+ /**
24
+ * Range category config.
25
+ */
26
+ export interface RangeCategoryConfig
27
+ extends CommonCategoryConfig,
28
+ RangeViewKind {
29
+ unit?: string; // e.g. "kg"
30
+ }
31
+
32
+ /**
33
+ * Select category config.
34
+ */
35
+ export interface SelectCategoryConfig
36
+ extends CommonCategoryConfig,
37
+ SelectViewKind {
38
+ enableChartView?: boolean;
39
+ mapSelectCategoryValue?: (
40
+ selectCategoryValue: SelectCategoryValueView
41
+ ) => SelectCategoryValueView;
42
+ }
@@ -0,0 +1,47 @@
1
+ import { Category } from "../models/types";
2
+ import { VIEW_KIND } from "../views/types";
3
+ import { CategoryConfig } from "./types";
4
+
5
+ /**
6
+ * Returns the category config for the given category config key and view kind.
7
+ * @param viewKind - View kind.
8
+ * @param key - Category config key.
9
+ * @param configs - Category configs.
10
+ * @returns category config.
11
+ */
12
+ export function findCategoryConfig<V extends VIEW_KIND>(
13
+ viewKind: V,
14
+ key: Category["key"],
15
+ configs: CategoryConfig[]
16
+ ): Extract<CategoryConfig, { viewKind: V }> | undefined {
17
+ return configs.find(
18
+ (c): c is Extract<CategoryConfig, { viewKind: V }> =>
19
+ c.viewKind === viewKind && c.key === key
20
+ );
21
+ }
22
+
23
+ /**
24
+ * Returns the range category config for the given category config key.
25
+ * @param key - Category config key.
26
+ * @param configs - Category configs.
27
+ * @returns category config.
28
+ */
29
+ export function findRangeCategoryConfig(
30
+ key: Category["key"],
31
+ configs: CategoryConfig[]
32
+ ): Extract<CategoryConfig, { viewKind: VIEW_KIND.RANGE }> | undefined {
33
+ return findCategoryConfig(VIEW_KIND.RANGE, key, configs);
34
+ }
35
+
36
+ /**
37
+ * Returns the select category config for the given category config key.
38
+ * @param key - Category config key.
39
+ * @param configs - Category configs.
40
+ * @returns category config.
41
+ */
42
+ export function findSelectCategoryConfig(
43
+ key: Category["key"],
44
+ configs: CategoryConfig[]
45
+ ): Extract<CategoryConfig, { viewKind?: VIEW_KIND.SELECT }> | undefined {
46
+ return findCategoryConfig(VIEW_KIND.SELECT, key, configs);
47
+ }
@@ -0,0 +1,24 @@
1
+ import { Category } from "../types";
2
+ import { RangeCategory, SelectedRange } from "./types";
3
+
4
+ /**
5
+ * Determine if category is a range category.
6
+ * @param category - Category to check if range category.
7
+ * @returns true if category is a range category.
8
+ */
9
+ export function isRangeCategory(category: Category): category is RangeCategory {
10
+ return "max" in category && "min" in category;
11
+ }
12
+
13
+ /**
14
+ * Determine if value is a selected range.
15
+ * @param value - Value to check if selected range.
16
+ * @returns true if value is a selected range.
17
+ */
18
+ export function isSelectedRange(value: unknown): value is SelectedRange {
19
+ return (
20
+ Array.isArray(value) &&
21
+ value.length === 2 &&
22
+ value.every((v) => v === null || typeof v === "number")
23
+ );
24
+ }
@@ -0,0 +1,17 @@
1
+ import { CategoryKey } from "../../../entities";
2
+
3
+ /**
4
+ * Internal filter model of a range category.
5
+ */
6
+ export interface RangeCategory {
7
+ key: CategoryKey;
8
+ max: number;
9
+ min: number;
10
+ selectedMax: SelectedRange[1];
11
+ selectedMin: SelectedRange[0];
12
+ }
13
+
14
+ /**
15
+ * Min and max values selected in range category.
16
+ */
17
+ export type SelectedRange = [number | null, number | null];
@@ -0,0 +1,51 @@
1
+ import { CategoryValueKey, SelectedFilter } from "../../../entities";
2
+ import { isSelectedRange } from "./typeGuards";
3
+ import { SelectedRange } from "./types";
4
+
5
+ /**
6
+ * Asserts that the given value is a selected range.
7
+ * Throws an error if the value is not a selected range.
8
+ * @param value - Value to assert is a selected range.
9
+ */
10
+ export function assertIsRange(value: unknown): asserts value is SelectedRange {
11
+ if (!isSelectedRange(value)) {
12
+ throw new Error("Value is not SelectedRange");
13
+ }
14
+ }
15
+
16
+ /**
17
+ * Build the next filter state for the given range category.
18
+ * @param nextCategorySelectedFilter - Next filter state for the range category.
19
+ * @param selectedValue - Selected value for the range category.
20
+ * @param selected - Whether the category value is selected.
21
+ */
22
+ export function buildNextRangeFilterState(
23
+ nextCategorySelectedFilter: SelectedFilter,
24
+ selectedValue: CategoryValueKey,
25
+ selected: boolean
26
+ ): void {
27
+ if (selected) {
28
+ // Assert that the selected value is a range.
29
+ assertIsRange(selectedValue);
30
+ // Set the selected range.
31
+ nextCategorySelectedFilter.value = selectedValue;
32
+ } else {
33
+ // Remove the selected range.
34
+ nextCategorySelectedFilter.value = [];
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Get the selected values for the given category, if any.
40
+ * Handles type checking for selected range.
41
+ * Falls back to empty array if no selected values or invalid type (should never happen).
42
+ * @param categorySelectedFilter - Current filter state for a category.
43
+ * @returns The selected filter (i.e. the set of selected values) for the given category.
44
+ */
45
+ export function getRangeSelectedValue(
46
+ categorySelectedFilter?: SelectedFilter
47
+ ): SelectedRange {
48
+ return isSelectedRange(categorySelectedFilter?.value)
49
+ ? categorySelectedFilter?.value
50
+ : [null, null];
51
+ }
@@ -0,0 +1,23 @@
1
+ import { CategoryValueKey, SelectedFilter } from "../../../entities";
2
+
3
+ /**
4
+ * Build the next filter state for the given select category.
5
+ * @param nextCategorySelectedFilter - Next filter state for the select category.
6
+ * @param selectedValue - Selected value for the select category.
7
+ * @param selected - Whether the category value is selected.
8
+ */
9
+ export function buildNextSelectFilterState(
10
+ nextCategorySelectedFilter: SelectedFilter,
11
+ selectedValue: CategoryValueKey,
12
+ selected: boolean
13
+ ): void {
14
+ if (selected) {
15
+ // Set the selected value.
16
+ nextCategorySelectedFilter.value.push(selectedValue);
17
+ } else {
18
+ // Remove the selected value from the selected set of values.
19
+ nextCategorySelectedFilter.value = nextCategorySelectedFilter.value.filter(
20
+ (value: CategoryValueKey) => value !== selectedValue
21
+ );
22
+ }
23
+ }
@@ -0,0 +1,7 @@
1
+ import { SelectCategory } from "../../entities";
2
+ import { RangeCategory } from "./range/types";
3
+
4
+ /**
5
+ * Internal filter model of a category.
6
+ */
7
+ export type Category = SelectCategory | RangeCategory;
@@ -0,0 +1,11 @@
1
+ import { CategoryKey, DataDictionaryAnnotation } from "../../../entities";
2
+
3
+ /**
4
+ * Common properties for category views.
5
+ */
6
+ export interface BaseCategoryView {
7
+ annotation?: DataDictionaryAnnotation;
8
+ isDisabled?: boolean;
9
+ key: CategoryKey;
10
+ label: string;
11
+ }
@@ -0,0 +1,13 @@
1
+ import { CategoryView } from "../types";
2
+ import { RangeCategoryView } from "./types";
3
+
4
+ /**
5
+ * Returns true if the category view is a range category view.
6
+ * @param view - Category view.
7
+ * @returns true if the category view is a range category view.
8
+ */
9
+ export function isRangeCategoryView(
10
+ view: CategoryView
11
+ ): view is RangeCategoryView {
12
+ return "max" in view && "min" in view;
13
+ }
@@ -0,0 +1,21 @@
1
+ import { SelectedRange } from "../../models/range/types";
2
+ import { BaseCategoryView } from "../common/types";
3
+ import { VIEW_KIND } from "../types";
4
+
5
+ /**
6
+ * View model of range category.
7
+ */
8
+ export interface RangeCategoryView extends BaseCategoryView {
9
+ max: number;
10
+ min: number;
11
+ selectedMax: SelectedRange[1];
12
+ selectedMin: SelectedRange[0];
13
+ unit?: string;
14
+ }
15
+
16
+ /**
17
+ * Model of range category view kind.
18
+ */
19
+ export interface RangeViewKind {
20
+ viewKind: VIEW_KIND.RANGE;
21
+ }
@@ -0,0 +1,35 @@
1
+ import { SelectedFilter } from "../../../entities";
2
+ import { CategoryConfig } from "../../config/types";
3
+ import { findRangeCategoryConfig } from "../../config/utils";
4
+ import { RangeCategory } from "../../models/range/types";
5
+ import { getRangeSelectedValue } from "../../models/range/utils";
6
+ import { RangeCategoryView } from "../../views/range/types";
7
+
8
+ /**
9
+ * Build the view-specific model of the given range category.
10
+ * @param category - The range category to build a view model of.
11
+ * @param categoryConfigs - Category configs indicating accept list as well as label configuration.
12
+ * @param categorySelectedFilter - Current filter state for a category.
13
+ * @returns Full built range category view, ready for display.
14
+ */
15
+ export function buildRangeCategoryView(
16
+ category: RangeCategory,
17
+ categoryConfigs: CategoryConfig[],
18
+ categorySelectedFilter?: SelectedFilter
19
+ ): RangeCategoryView {
20
+ const categoryConfig = findRangeCategoryConfig(category.key, categoryConfigs);
21
+ const [selectedMin, selectedMax] = getRangeSelectedValue(
22
+ categorySelectedFilter
23
+ );
24
+ return {
25
+ annotation: categoryConfig?.annotation,
26
+ isDisabled: false,
27
+ key: category.key,
28
+ label: categoryConfig?.label || category.key,
29
+ max: category.max,
30
+ min: category.min,
31
+ selectedMax,
32
+ selectedMin,
33
+ unit: categoryConfig?.unit,
34
+ };
35
+ }
@@ -0,0 +1,13 @@
1
+ import { SelectCategoryView } from "../../../entities";
2
+ import { CategoryView } from "../types";
3
+
4
+ /**
5
+ * Returns true if the category view is a select category view.
6
+ * @param view - Category view.
7
+ * @returns true if the category view is a select category view.
8
+ */
9
+ export function isSelectCategoryView(
10
+ view: CategoryView
11
+ ): view is SelectCategoryView {
12
+ return "values" in view;
13
+ }
@@ -0,0 +1,8 @@
1
+ import { VIEW_KIND } from "../types";
2
+
3
+ /**
4
+ * Model of select category view kind.
5
+ */
6
+ export interface SelectViewKind {
7
+ viewKind?: VIEW_KIND.SELECT;
8
+ }
@@ -0,0 +1,15 @@
1
+ import { SelectCategoryView } from "../../entities";
2
+ import { RangeCategoryView } from "./range/types";
3
+
4
+ /**
5
+ * Possible category view model types.
6
+ */
7
+ export type CategoryView = RangeCategoryView | SelectCategoryView;
8
+
9
+ /**
10
+ * Category view kind.
11
+ */
12
+ export enum VIEW_KIND {
13
+ RANGE = "RANGE",
14
+ SELECT = "SELECT",
15
+ }
@@ -1,4 +1,5 @@
1
1
  import { ColumnDef } from "@tanstack/react-table";
2
+ import { GridTrackSize } from "../config/entities";
2
3
 
3
4
  /**
4
5
  * Model of a value of a metadata class.
@@ -21,7 +22,7 @@ export interface Attribute {
21
22
  /**
22
23
  * Model of attribute key types; used mostly when building data dictionary column definitions.
23
24
  */
24
- export type AttributeValueTypes = string | boolean;
25
+ export type AttributeValueTypes<TValue = unknown> = TValue;
25
26
 
26
27
  /**
27
28
  * Filterable metadata keys.
@@ -76,13 +77,17 @@ export interface DataDictionary {
76
77
  * Display model of a data dictionary column.
77
78
  */
78
79
  export interface DataDictionaryColumnDef {
80
+ attributeAccessorFnName?: string; // Name of accessor function to map to.
81
+ attributeCellName?: string; // Name of cell renderer component to map to.
79
82
  attributeDisplayName: string;
80
83
  attributeSlotName: string;
81
84
  // Adding width here for now; possibly revisit separating column def and UI.
82
- width: {
83
- max: string;
84
- min: string;
85
- };
85
+ width:
86
+ | Omit<GridTrackSize, "GridTrackMinMax">
87
+ | {
88
+ max: string;
89
+ min: string;
90
+ };
86
91
  }
87
92
 
88
93
  /**
@@ -1,12 +1,20 @@
1
1
  import { Typography } from "@mui/material";
2
- import React from "react";
2
+ import { CellContext, RowData } from "@tanstack/react-table";
3
+ import React, { ReactNode } from "react";
3
4
  import { TYPOGRAPHY_PROPS } from "../../../../../../styles/common/mui/typography";
4
- import { BasicCellProps } from "./types";
5
+ import { parseValue } from "./utils";
5
6
 
6
- export const BasicCell = ({ getValue }: BasicCellProps): JSX.Element => {
7
+ export const BasicCell = <
8
+ T extends RowData,
9
+ TValue extends ReactNode = ReactNode
10
+ >({
11
+ getValue,
12
+ }: CellContext<T, TValue>): JSX.Element | null => {
13
+ const value = getValue();
14
+ if (value === undefined || value === null) return null;
7
15
  return (
8
16
  <Typography variant={TYPOGRAPHY_PROPS.VARIANT.INHERIT}>
9
- {getValue()}
17
+ {parseValue(value)}
10
18
  </Typography>
11
19
  );
12
20
  };
@@ -0,0 +1,13 @@
1
+ import { ReactNode } from "react";
2
+
3
+ /**
4
+ * Returns a parsed value based on the original value.
5
+ * If the value is a boolean, it is converted to its string representation ("true" or "false").
6
+ * For all other types of ReactNode, the value is returned unchanged.
7
+ * @param value - The original value.
8
+ * @returns Parsed value or original value.
9
+ */
10
+ export function parseValue(value: ReactNode): ReactNode {
11
+ if (typeof value === "boolean") return value.toString();
12
+ return value;
13
+ }
@@ -11,12 +11,11 @@ export const grid = css`
11
11
  export const View = styled("div")`
12
12
  ${grid};
13
13
  flex: 1;
14
- margin: 0 auto;
15
- max-width: min(calc(100vw - 48px), 1392px);
14
+ margin: 0 24px;
16
15
  position: relative;
17
16
 
18
17
  ${mediaTabletDown} {
19
18
  grid-template-columns: 1fr;
20
- max-width: calc(100vw - 32px);
19
+ margin: 0 16px;
21
20
  }
22
21
  `;
@@ -1,15 +1,23 @@
1
1
  import { CloseRounded } from "@mui/icons-material";
2
2
  import { Grow, PopoverPosition, PopoverProps } from "@mui/material";
3
3
  import React, { MouseEvent, ReactNode, useState } from "react";
4
- import { SelectCategoryView } from "../../../../common/entities";
4
+ import { isRangeCategoryView } from "../../../../common/categories/views/range/typeGuards";
5
+ import { CategoryView } from "../../../../common/categories/views/types";
5
6
  import { TrackFilterOpenedFunction } from "../../../../config/entities";
6
7
  import { OnFilterFn } from "../../../../hooks/useCategoryFilter";
7
8
  import { TEST_IDS } from "../../../../tests/testIds";
8
9
  import { FilterLabel } from "../FilterLabel/filterLabel";
9
10
  import { FilterMenu } from "../FilterMenu/filterMenu";
11
+ import { FilterRange } from "../FilterRange/filterRange";
10
12
  import { DrawerTransition } from "./components/DrawerTransition/drawerTransition";
11
13
  import { FilterPopover, IconButton } from "./filter.styles";
12
14
 
15
+ /**
16
+ * Filter component.
17
+ * TODO(cc) refactor: build tags from categoryView for selected values.
18
+ * TODO(cc) tests: add tests for selected values (rending of tags) for select and range categories.
19
+ */
20
+
13
21
  const DEFAULT_POSITION: PopoverPosition = { left: 0, top: 0 };
14
22
  const DEFAULT_SLOT_PROPS: PopoverProps["slotProps"] = {
15
23
  paper: { variant: "menu" },
@@ -20,7 +28,7 @@ const DRAWER_SLOT_PROPS: PopoverProps["slotProps"] = {
20
28
 
21
29
  export interface FilterProps {
22
30
  categorySection?: string;
23
- categoryView: SelectCategoryView;
31
+ categoryView: CategoryView;
24
32
  closeAncestor?: () => void;
25
33
  isFilterDrawer: boolean;
26
34
  onFilter: OnFilterFn;
@@ -44,6 +52,7 @@ export const Filter = ({
44
52
  const TransitionComponent = isFilterDrawer ? DrawerTransition : Grow;
45
53
  const transitionDuration = isOpen ? 250 : 300;
46
54
  const TransitionDuration = isFilterDrawer ? transitionDuration : undefined;
55
+ const isRangeView = isRangeCategoryView(categoryView);
47
56
 
48
57
  /**
49
58
  * Closes filter popover.
@@ -79,7 +88,7 @@ export const Filter = ({
79
88
  <>
80
89
  <FilterLabel
81
90
  annotation={categoryView.annotation}
82
- count={categoryView.values.length}
91
+ count={isRangeView ? undefined : categoryView.values.length}
83
92
  disabled={categoryView.isDisabled}
84
93
  isOpen={isOpen}
85
94
  label={categoryView.label}
@@ -93,7 +102,7 @@ export const Filter = ({
93
102
  onClose={onCloseFilters}
94
103
  open={isOpen}
95
104
  slotProps={slotProps}
96
- TransitionComponent={TransitionComponent}
105
+ slots={{ transition: TransitionComponent }}
97
106
  transitionDuration={TransitionDuration}
98
107
  >
99
108
  {isOpen && isFilterDrawer && (
@@ -103,15 +112,31 @@ export const Filter = ({
103
112
  size="medium"
104
113
  />
105
114
  )}
106
- <FilterMenu
107
- categorySection={categorySection}
108
- categoryKey={categoryView.key}
109
- categoryLabel={categoryView.label}
110
- isFilterDrawer={isFilterDrawer}
111
- onFilter={onFilter}
112
- onCloseFilter={onCloseFilter}
113
- values={categoryView.values}
114
- />
115
+ {isRangeView ? (
116
+ <FilterRange
117
+ categoryKey={categoryView.key}
118
+ categoryLabel={categoryView.label}
119
+ categorySection={categorySection}
120
+ isFilterDrawer={isFilterDrawer}
121
+ max={categoryView.max}
122
+ min={categoryView.min}
123
+ selectedMax={categoryView.selectedMax}
124
+ selectedMin={categoryView.selectedMin}
125
+ onCloseFilter={onCloseFilter}
126
+ onFilter={onFilter}
127
+ unit={categoryView.unit}
128
+ />
129
+ ) : (
130
+ <FilterMenu
131
+ categoryKey={categoryView.key}
132
+ categoryLabel={categoryView.label}
133
+ categorySection={categorySection}
134
+ isFilterDrawer={isFilterDrawer}
135
+ onCloseFilter={onCloseFilter}
136
+ onFilter={onFilter}
137
+ values={categoryView.values}
138
+ />
139
+ )}
115
140
  </FilterPopover>
116
141
  {tags}
117
142
  </>
@@ -0,0 +1,24 @@
1
+ import { fn } from "@storybook/test";
2
+ import { ComponentProps } from "react";
3
+ import { DONOR_COUNT, GENUS_SPECIES } from "../../Filters/stories/constants";
4
+ import { Filter } from "../filter";
5
+
6
+ export const SELECT_ARGS: ComponentProps<typeof Filter> = {
7
+ categoryView: GENUS_SPECIES,
8
+ isFilterDrawer: false,
9
+ onFilter: fn(),
10
+ };
11
+
12
+ export const DISABLED_SELECT_ARGS: ComponentProps<typeof Filter> = {
13
+ ...SELECT_ARGS,
14
+ categoryView: {
15
+ ...SELECT_ARGS.categoryView,
16
+ isDisabled: true,
17
+ },
18
+ };
19
+
20
+ export const RANGE_ARGS: ComponentProps<typeof Filter> = {
21
+ categoryView: DONOR_COUNT,
22
+ isFilterDrawer: false,
23
+ onFilter: fn(),
24
+ };
@@ -0,0 +1,32 @@
1
+ import { Box } from "@mui/material";
2
+ import { Meta, StoryObj } from "@storybook/react";
3
+ import React from "react";
4
+ import { Filter } from "../filter";
5
+ import { DISABLED_SELECT_ARGS, RANGE_ARGS, SELECT_ARGS } from "./args";
6
+
7
+ const meta: Meta<typeof Filter> = {
8
+ component: Filter,
9
+ decorators: [
10
+ (Story): JSX.Element => (
11
+ <Box sx={{ width: 248 }}>
12
+ <Story />
13
+ </Box>
14
+ ),
15
+ ],
16
+ };
17
+
18
+ export default meta;
19
+
20
+ type Story = StoryObj<typeof meta>;
21
+
22
+ export const SelectCategory: Story = {
23
+ args: SELECT_ARGS,
24
+ };
25
+
26
+ export const DisabledSelectCategory: Story = {
27
+ args: DISABLED_SELECT_ARGS,
28
+ };
29
+
30
+ export const RangeCategory: Story = {
31
+ args: RANGE_ARGS,
32
+ };