@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
@@ -20,7 +20,7 @@ export const FilterViewTools = styled.div`
20
20
  }
21
21
  `;
22
22
 
23
- export const Button = styled(ButtonBase)`
23
+ export const StyledButtonBase = styled(ButtonBase)`
24
24
  ${textBodyLarge500};
25
25
  align-items: center;
26
26
  display: flex;
@@ -12,7 +12,11 @@ import { List } from "../FilterList/filterList.styles";
12
12
  import { FilterMenuSearch } from "../FilterMenuSearch/filterMenuSearch";
13
13
  import { FilterNoResultsFound } from "../FilterNoResultsFound/filterNoResultsFound";
14
14
  import { VariableSizeList } from "../VariableSizeList/VariableSizeList";
15
- import { Button, FilterView, FilterViewTools } from "./filterMenu.styles";
15
+ import {
16
+ FilterView,
17
+ FilterViewTools,
18
+ StyledButtonBase,
19
+ } from "./filterMenu.styles";
16
20
 
17
21
  export interface FilterMenuProps {
18
22
  categoryKey: CategoryKey;
@@ -43,10 +47,10 @@ export const FilterMenu = ({
43
47
  <FilterView menuWidth={menuWidth}>
44
48
  <FilterViewTools>
45
49
  {isFilterDrawer && (
46
- <Button onClick={onCloseFilter}>
50
+ <StyledButtonBase onClick={onCloseFilter}>
47
51
  <SouthIcon fontSize="small" />
48
52
  {categoryLabel}
49
- </Button>
53
+ </StyledButtonBase>
50
54
  )}
51
55
  {isSearchable && (
52
56
  <FilterMenuSearch
@@ -6,7 +6,6 @@ import {
6
6
  ToggleButtonGroupProps,
7
7
  } from "@mui/material";
8
8
  import { BUTTON_PROPS as DX_BUTTON_PROPS } from "../../../common/Button/constants";
9
- import { RANGE_OPERATOR } from "./types";
10
9
 
11
10
  export const BUTTON_PROPS: ButtonProps = {
12
11
  ...DX_BUTTON_PROPS.PRIMARY_MEDIUM_CONTAINED,
@@ -29,12 +28,6 @@ export const INPUT_LABEL_PROPS: InputLabelProps = {
29
28
  shrink: true,
30
29
  };
31
30
 
32
- export const RANGE_OPERATOR_DISPLAY: Record<RANGE_OPERATOR, string> = {
33
- between: "From",
34
- greaterThan: "Greater Than",
35
- lessThan: "Less Than",
36
- };
37
-
38
31
  export const TOGGLE_BUTTON_GROUP_PROPS: ToggleButtonGroupProps = {
39
32
  exclusive: true,
40
33
  fullWidth: true,
@@ -1,9 +1,6 @@
1
1
  import styled from "@emotion/styled";
2
- import {
3
- inkLight,
4
- inkMain,
5
- smokeDark,
6
- } from "../../../../styles/common/mixins/colors";
2
+ import { PALETTE } from "../../../../styles/common/constants/palette";
3
+ import { mediaDesktopSmallDown } from "../../../../styles/common/mixins/breakpoints";
7
4
  import { textBody400 } from "../../../../styles/common/mixins/fonts";
8
5
 
9
6
  export const StyledForm = styled("form")`
@@ -15,16 +12,16 @@ export const StyledForm = styled("form")`
15
12
  width: 100%;
16
13
 
17
14
  .MuiToggleButton-root {
18
- color: ${inkLight};
15
+ color: ${PALETTE.INK_LIGHT};
19
16
  text-transform: capitalize;
20
17
 
21
18
  &.Mui-selected {
22
- color: ${inkMain};
19
+ color: ${PALETTE.INK_MAIN};
23
20
  }
24
21
  }
25
22
 
26
23
  .MuiDivider-root {
27
- border-color: ${smokeDark};
24
+ border-color: ${PALETTE.SMOKE_DARK};
28
25
  border-radius: 4px;
29
26
  margin: 6px 0;
30
27
  }
@@ -34,31 +31,58 @@ export const StyledForm = styled("form")`
34
31
  display: grid;
35
32
  gap: 4px 0;
36
33
  grid-auto-flow: column;
37
- grid-template-rows: auto auto;
34
+ grid-template-rows: auto auto auto;
38
35
  margin: 12px 0 16px 0;
39
36
 
40
37
  .MuiFormControl-root {
41
38
  display: grid;
42
- gap: inherit;
39
+ gap: 4px 0;
43
40
  grid-row: 1 / -1;
44
41
  grid-template-rows: subgrid;
45
42
 
46
43
  .MuiInputLabel-root {
47
44
  ${textBody400};
48
- color: ${inkMain};
45
+ color: ${PALETTE.INK_MAIN};
49
46
  max-width: unset;
50
47
  position: relative;
51
48
  transform: unset;
52
49
  }
53
50
 
54
- .MuiOutlinedInput-input {
55
- padding-right: 10px;
51
+ .MuiOutlinedInput-root {
52
+ .MuiOutlinedInput-input {
53
+ padding-right: 10px;
54
+ }
55
+
56
+ input::placeholder {
57
+ color: ${PALETTE.INK_LIGHT};
58
+ opacity: 0.8;
59
+ }
60
+
61
+ &.Mui-focused {
62
+ input::placeholder {
63
+ opacity: 0;
64
+ }
65
+ }
66
+ }
67
+
68
+ .MuiFormLabel-filled + .MuiOutlinedInput-root {
69
+ .MuiOutlinedInput-input {
70
+ color: ${PALETTE.INK_MAIN};
71
+ }
72
+ }
73
+
74
+ .MuiFormHelperText-root {
75
+ color: ${PALETTE.INK_LIGHT};
76
+
77
+ &.Mui-error {
78
+ color: ${PALETTE.ALERT_MAIN};
79
+ }
56
80
  }
57
81
  }
58
82
 
59
83
  .MuiDivider-root {
60
84
  align-self: center;
61
- border-color: ${inkLight};
85
+ border-color: ${PALETTE.INK_LIGHT};
62
86
  grid-row: 2;
63
87
  margin: 0 4px;
64
88
  width: 8px;
@@ -68,4 +92,24 @@ export const StyledForm = styled("form")`
68
92
  .MuiButton-root {
69
93
  grid-column: 1 / -1;
70
94
  }
95
+
96
+ ${mediaDesktopSmallDown} {
97
+ padding: 0 16px;
98
+ width: 312px;
99
+
100
+ .MuiGrid-root {
101
+ gap: 16px 0;
102
+ grid-template-rows: auto auto;
103
+ margin: 16px 0;
104
+
105
+ .MuiFormControl-root {
106
+ grid-row: unset;
107
+ grid-template-rows: unset;
108
+ }
109
+
110
+ .MuiDivider-root {
111
+ display: none;
112
+ }
113
+ }
114
+ }
71
115
  `;
@@ -2,6 +2,7 @@ import {
2
2
  Button,
3
3
  Divider,
4
4
  FormControl,
5
+ FormHelperText,
5
6
  Grid,
6
7
  InputLabel,
7
8
  OutlinedInput,
@@ -10,62 +11,133 @@ import {
10
11
  } from "@mui/material";
11
12
  import React, { Fragment } from "react";
12
13
  import { TEST_IDS } from "../../../../tests/testIds";
14
+ import { SouthIcon } from "../../../common/CustomIcon/components/SouthIcon/southIcon";
15
+ import {
16
+ FilterViewTools,
17
+ StyledButtonBase,
18
+ } from "../FilterMenu/filterMenu.styles";
13
19
  import {
14
20
  BUTTON_PROPS,
15
21
  DIVIDER_PROPS,
16
22
  INPUT_LABEL_PROPS,
17
23
  INPUT_PROPS,
18
- RANGE_OPERATOR_DISPLAY,
19
24
  TOGGLE_BUTTON_GROUP_PROPS,
20
25
  } from "./constants";
21
26
  import { StyledForm } from "./filterRange.styles";
27
+ import { FIELD_NAME } from "./hooks/UseFilterRange/constants";
22
28
  import { useFilterRange } from "./hooks/UseFilterRange/hook";
23
- import { FilterRangeProps, RANGE_OPERATOR } from "./types";
29
+ import { RANGE_OPERATOR } from "./hooks/UseFilterRange/types";
30
+ import { FilterRangeProps } from "./types";
31
+ import { getRangeOperator } from "./utils";
24
32
 
25
- export const FilterRange = ({ className }: FilterRangeProps): JSX.Element => {
26
- const { onChange, onSubmit, value } = useFilterRange();
33
+ export const FilterRange = ({
34
+ categoryKey,
35
+ categoryLabel,
36
+ categorySection,
37
+ className,
38
+ isFilterDrawer,
39
+ max,
40
+ min,
41
+ onCloseFilter,
42
+ onFilter,
43
+ selectedMax,
44
+ selectedMin,
45
+ unit,
46
+ }: FilterRangeProps): JSX.Element => {
47
+ const rangeOperator = getRangeOperator({ selectedMax, selectedMin });
48
+ const {
49
+ clearErrors,
50
+ formState: { errors },
51
+ handleSubmit,
52
+ onChange,
53
+ value,
54
+ } = useFilterRange(rangeOperator);
27
55
  return (
28
- <StyledForm
29
- className={className}
30
- data-testid={TEST_IDS.FILTER_RANGE}
31
- onSubmit={onSubmit}
32
- >
33
- <ToggleButtonGroup
34
- {...TOGGLE_BUTTON_GROUP_PROPS}
35
- onChange={onChange}
36
- value={value}
56
+ <Fragment>
57
+ {isFilterDrawer && (
58
+ <FilterViewTools>
59
+ <StyledButtonBase onClick={onCloseFilter}>
60
+ <SouthIcon fontSize="small" />
61
+ {categoryLabel}
62
+ </StyledButtonBase>
63
+ </FilterViewTools>
64
+ )}
65
+ <StyledForm
66
+ className={className}
67
+ data-testid={TEST_IDS.FILTER_RANGE}
68
+ onSubmit={handleSubmit(onFilter, {
69
+ categoryKey,
70
+ categorySection,
71
+ })}
37
72
  >
38
- <ToggleButton value={RANGE_OPERATOR.BETWEEN}>Between</ToggleButton>
39
- <ToggleButton value={RANGE_OPERATOR.LESS_THAN}>Less Than</ToggleButton>
40
- <Divider {...DIVIDER_PROPS} />
41
- <ToggleButton value={RANGE_OPERATOR.GREATER_THAN}>
42
- Greater Than
43
- </ToggleButton>
44
- </ToggleButtonGroup>
45
- <Grid>
46
- <FormControl>
47
- <InputLabel {...INPUT_LABEL_PROPS} htmlFor={value}>
48
- {RANGE_OPERATOR_DISPLAY[value]}
49
- </InputLabel>
50
- <OutlinedInput {...INPUT_PROPS} name={value} placeholder="eg. 1" />
51
- </FormControl>
52
- {value === RANGE_OPERATOR.BETWEEN && (
53
- <Fragment>
54
- <Divider />
55
- <FormControl>
56
- <InputLabel {...INPUT_LABEL_PROPS} htmlFor="between-to">
57
- To
73
+ <ToggleButtonGroup
74
+ {...TOGGLE_BUTTON_GROUP_PROPS}
75
+ onChange={(e, value) => {
76
+ clearErrors();
77
+ onChange?.(e, value);
78
+ }}
79
+ value={value}
80
+ >
81
+ <ToggleButton value={RANGE_OPERATOR.BETWEEN}>Between</ToggleButton>
82
+ <ToggleButton value={RANGE_OPERATOR.LESS_THAN}>
83
+ Less Than
84
+ </ToggleButton>
85
+ <Divider {...DIVIDER_PROPS} />
86
+ <ToggleButton value={RANGE_OPERATOR.GREATER_THAN}>
87
+ Greater Than
88
+ </ToggleButton>
89
+ </ToggleButtonGroup>
90
+ <Grid>
91
+ {value !== RANGE_OPERATOR.LESS_THAN && (
92
+ <FormControl error={!!errors[FIELD_NAME.MIN]}>
93
+ <InputLabel {...INPUT_LABEL_PROPS} htmlFor={FIELD_NAME.MIN}>
94
+ {value === RANGE_OPERATOR.BETWEEN ? "Min" : "Greater Than"}
95
+ {unit && ` (${unit})`}
96
+ </InputLabel>
97
+ <OutlinedInput
98
+ {...INPUT_PROPS}
99
+ defaultValue={selectedMin}
100
+ id={FIELD_NAME.MIN}
101
+ name={FIELD_NAME.MIN}
102
+ onFocus={clearErrors}
103
+ placeholder="eg. 1"
104
+ />
105
+ <FormHelperText>
106
+ {errors[FIELD_NAME.MIN]
107
+ ? errors[FIELD_NAME.MIN]
108
+ : value === RANGE_OPERATOR.BETWEEN
109
+ ? `Min allowed: ${min}`
110
+ : `Allowed values: \u2265 ${min} and \u2264 ${max}`}
111
+ </FormHelperText>
112
+ </FormControl>
113
+ )}
114
+ {value === RANGE_OPERATOR.BETWEEN && <Divider />}
115
+ {value !== RANGE_OPERATOR.GREATER_THAN && (
116
+ <FormControl error={!!errors[FIELD_NAME.MAX]}>
117
+ <InputLabel {...INPUT_LABEL_PROPS} htmlFor={FIELD_NAME.MAX}>
118
+ {value === RANGE_OPERATOR.BETWEEN ? "Max" : "Less Than"}
119
+ {unit && ` (${unit})`}
58
120
  </InputLabel>
59
121
  <OutlinedInput
60
122
  {...INPUT_PROPS}
61
- name="between-to"
62
- placeholder="eg. 2"
123
+ defaultValue={selectedMax}
124
+ id={FIELD_NAME.MAX}
125
+ name={FIELD_NAME.MAX}
126
+ onFocus={clearErrors}
127
+ placeholder="eg. 20"
63
128
  />
129
+ <FormHelperText color="inkLight">
130
+ {errors[FIELD_NAME.MAX]
131
+ ? errors[FIELD_NAME.MAX]
132
+ : value === RANGE_OPERATOR.BETWEEN
133
+ ? `Max allowed: ${max}`
134
+ : `Allowed values: \u2265 ${min} and \u2264 ${max}`}
135
+ </FormHelperText>
64
136
  </FormControl>
65
- </Fragment>
66
- )}
67
- </Grid>
68
- <Button {...BUTTON_PROPS}>Filter</Button>
69
- </StyledForm>
137
+ )}
138
+ </Grid>
139
+ <Button {...BUTTON_PROPS}>Filter</Button>
140
+ </StyledForm>
141
+ </Fragment>
70
142
  );
71
143
  };
@@ -0,0 +1,5 @@
1
+ export const FIELD_NAME = {
2
+ MAX: "max",
3
+ MIN: "min",
4
+ RANGE_OPERATOR: "rangeOperator",
5
+ };
@@ -1,20 +1,61 @@
1
- import { FormEvent, useCallback } from "react";
1
+ import { FormEvent, useCallback, useState } from "react";
2
+ import { ValidationError } from "yup";
3
+ import { VIEW_KIND } from "../../../../../../common/categories/views/types";
2
4
  import { useToggleButtonGroup } from "../../../../../common/ToggleButtonGroup/hooks/UseToggleButtonGroup/hook";
3
- import { RANGE_OPERATOR } from "../../types";
4
- import { UseFilterRange } from "./types";
5
+ import { SCHEMA } from "./schema";
6
+ import {
7
+ FieldErrors,
8
+ OnSubmitFn,
9
+ RANGE_OPERATOR,
10
+ SubmitParams,
11
+ UseFilterRange,
12
+ } from "./types";
13
+ import { getFormValues } from "./utils";
5
14
 
6
- export const useFilterRange = (): UseFilterRange => {
7
- const { onChange, value } = useToggleButtonGroup<RANGE_OPERATOR>(
8
- RANGE_OPERATOR.BETWEEN
9
- );
15
+ export const useFilterRange = (
16
+ initialValue: RANGE_OPERATOR = RANGE_OPERATOR.BETWEEN
17
+ ): UseFilterRange => {
18
+ const [errors, setErrors] = useState<FieldErrors>({});
19
+ const { onChange, value } =
20
+ useToggleButtonGroup<RANGE_OPERATOR>(initialValue);
10
21
 
11
- const onSubmit = useCallback((e: FormEvent) => {
12
- e.preventDefault();
22
+ const clearErrors = useCallback(() => {
23
+ setErrors({});
13
24
  }, []);
14
25
 
26
+ const handleSubmit = useCallback(
27
+ (onSubmit: OnSubmitFn, parameters: SubmitParams) => {
28
+ return (e: FormEvent<HTMLFormElement>): void => {
29
+ e.preventDefault();
30
+ const fieldValues = getFormValues(e.currentTarget, value); // `value` is current range operator.
31
+ SCHEMA.validate(fieldValues, { abortEarly: false })
32
+ .then((result) => {
33
+ setErrors({});
34
+ onSubmit(
35
+ parameters.categoryKey,
36
+ [result.min, result.max],
37
+ true,
38
+ parameters.categorySection,
39
+ VIEW_KIND.RANGE
40
+ );
41
+ })
42
+ .catch((validationError: ValidationError) => {
43
+ const fieldErrors: FieldErrors = {};
44
+ for (const error of validationError.inner) {
45
+ if (error.path) fieldErrors[error.path] = error.message;
46
+ }
47
+ setErrors(fieldErrors);
48
+ });
49
+ };
50
+ },
51
+ [value]
52
+ );
53
+
15
54
  return {
55
+ clearErrors,
56
+ formState: { errors },
57
+ handleSubmit,
16
58
  onChange,
17
- onSubmit,
18
59
  value,
19
60
  };
20
61
  };
@@ -0,0 +1,60 @@
1
+ import { mixed, number, object } from "yup";
2
+ import { FIELD_NAME } from "./constants";
3
+ import { RANGE_OPERATOR } from "./types";
4
+
5
+ export const SCHEMA = object({
6
+ [FIELD_NAME.MAX]: number()
7
+ .typeError("Value must be a number")
8
+ .when(FIELD_NAME.RANGE_OPERATOR, {
9
+ is: RANGE_OPERATOR.BETWEEN,
10
+ then: (schema) => schema.notRequired(),
11
+ })
12
+ .when(FIELD_NAME.RANGE_OPERATOR, {
13
+ is: RANGE_OPERATOR.LESS_THAN,
14
+ then: (schema) => schema.required("Value is required"),
15
+ })
16
+ .when(FIELD_NAME.RANGE_OPERATOR, {
17
+ is: RANGE_OPERATOR.GREATER_THAN,
18
+ then: (schema) => schema.notRequired(),
19
+ }),
20
+ [FIELD_NAME.MIN]: number()
21
+ .typeError("Value must be a number")
22
+ .when(FIELD_NAME.RANGE_OPERATOR, {
23
+ is: RANGE_OPERATOR.BETWEEN,
24
+ then: (schema) =>
25
+ schema
26
+ .notRequired()
27
+ .test(
28
+ "min-less-than-max",
29
+ "Min must be less than max",
30
+ function (min) {
31
+ const max = this.parent[FIELD_NAME.MAX];
32
+ // If either value is not a number, skip validation.
33
+ if (!min || !max) return true;
34
+ if (Number.isNaN(min) || Number.isNaN(max)) return true;
35
+ return min < max;
36
+ }
37
+ )
38
+ .test(
39
+ "at-least-min-or-max",
40
+ "Min or Max is required",
41
+ function (min) {
42
+ const max = this.parent[FIELD_NAME.MAX];
43
+ // If both values are null, validation fails.
44
+ return !(min === null && max === null);
45
+ }
46
+ ),
47
+ })
48
+ .when(FIELD_NAME.RANGE_OPERATOR, {
49
+ is: RANGE_OPERATOR.LESS_THAN,
50
+ then: (schema) => schema.notRequired(),
51
+ })
52
+ .when(FIELD_NAME.RANGE_OPERATOR, {
53
+ is: RANGE_OPERATOR.GREATER_THAN,
54
+ otherwise: (schema) => schema.notRequired(),
55
+ then: (schema) => schema.required("Value is required"),
56
+ }),
57
+ [FIELD_NAME.RANGE_OPERATOR]: mixed<RANGE_OPERATOR>().default(
58
+ RANGE_OPERATOR.BETWEEN
59
+ ),
60
+ });
@@ -1,9 +1,40 @@
1
1
  import { ToggleButtonGroupProps } from "@mui/material";
2
- import { FormEvent } from "react";
3
- import { RANGE_OPERATOR } from "../../types";
2
+ import { OnFilterFn } from "hooks/useCategoryFilter";
3
+ import { FormEventHandler } from "react";
4
+ import { FIELD_NAME } from "./constants";
5
+
6
+ export type FieldErrors = Partial<Record<FieldName, string>>;
7
+
8
+ export type FieldName = (typeof FIELD_NAME)[keyof typeof FIELD_NAME];
9
+
10
+ export interface FieldValues {
11
+ max: FormDataEntryValue | null;
12
+ min: FormDataEntryValue | null;
13
+ rangeOperator: RANGE_OPERATOR;
14
+ }
15
+
16
+ export type OnSubmitFn = OnFilterFn;
17
+
18
+ export type OnSubmitParams = Parameters<OnSubmitFn>;
19
+
20
+ export enum RANGE_OPERATOR {
21
+ BETWEEN = "between",
22
+ GREATER_THAN = "greaterThan",
23
+ LESS_THAN = "lessThan",
24
+ }
25
+
26
+ export type SubmitParams = {
27
+ categoryKey: OnSubmitParams[0];
28
+ categorySection?: OnSubmitParams[3];
29
+ };
4
30
 
5
31
  export interface UseFilterRange {
32
+ clearErrors: () => void;
33
+ formState: { errors: FieldErrors };
34
+ handleSubmit: (
35
+ onSubmit: OnSubmitFn,
36
+ parameters: SubmitParams
37
+ ) => FormEventHandler;
6
38
  onChange: ToggleButtonGroupProps["onChange"];
7
- onSubmit: (e: FormEvent) => void;
8
39
  value: RANGE_OPERATOR;
9
40
  }
@@ -0,0 +1,32 @@
1
+ import { FieldValues, RANGE_OPERATOR } from "./types";
2
+
3
+ /**
4
+ * Retrieves the min and max field values from a form.
5
+ * @param e - The form element to retrieve values from.
6
+ * @param rangeOperator - The range operator value in use.
7
+ * @returns The values from the form.
8
+ */
9
+ export function getFormValues(
10
+ e: HTMLFormElement,
11
+ rangeOperator: RANGE_OPERATOR
12
+ ): FieldValues {
13
+ const formData = new FormData(e);
14
+ const fieldValues = {} as FieldValues;
15
+ fieldValues.max = parseMinMaxValue(formData.get("max"));
16
+ fieldValues.min = parseMinMaxValue(formData.get("min"));
17
+ fieldValues.rangeOperator = rangeOperator;
18
+ return fieldValues;
19
+ }
20
+
21
+ /**
22
+ * Parses a form data value null or empty string to null.
23
+ * Schema validation will handle the rest.
24
+ * @param value - The value to parse.
25
+ * @returns The parsed value, or null if the value is null or empty string.
26
+ */
27
+ export function parseMinMaxValue(
28
+ value: FormDataEntryValue | null
29
+ ): FormDataEntryValue | null {
30
+ if (value === null || value === "") return null;
31
+ return value;
32
+ }
@@ -0,0 +1,16 @@
1
+ import { fn } from "@storybook/test";
2
+ import { ComponentProps } from "react";
3
+ import { FilterRange } from "../filterRange";
4
+
5
+ export const DEFAULT_ARGS: ComponentProps<typeof FilterRange> = {
6
+ categoryKey: "Weight",
7
+ categoryLabel: "Weight",
8
+ isFilterDrawer: false,
9
+ max: 2100,
10
+ min: 100,
11
+ onCloseFilter: fn(),
12
+ onFilter: fn(),
13
+ selectedMax: null,
14
+ selectedMin: null,
15
+ unit: "kg",
16
+ };
@@ -1,8 +1,8 @@
1
1
  import { Meta, StoryObj } from "@storybook/react";
2
2
  import { FilterRange } from "../filterRange";
3
+ import { DEFAULT_ARGS } from "./args";
3
4
 
4
5
  const meta: Meta<typeof FilterRange> = {
5
- args: {},
6
6
  component: FilterRange,
7
7
  };
8
8
 
@@ -11,5 +11,5 @@ export default meta;
11
11
  type Story = StoryObj<typeof meta>;
12
12
 
13
13
  export const Default: Story = {
14
- args: {},
14
+ args: DEFAULT_ARGS,
15
15
  };
@@ -1,9 +1,15 @@
1
+ import { RangeCategoryView } from "../../../../common/categories/views/range/types";
2
+ import { CategoryKey } from "../../../../common/entities";
3
+ import { OnFilterFn } from "../../../../hooks/useCategoryFilter";
1
4
  import { BaseComponentProps } from "../../../types";
2
5
 
3
- export interface FilterRangeProps extends BaseComponentProps {}
4
-
5
- export enum RANGE_OPERATOR {
6
- BETWEEN = "between",
7
- GREATER_THAN = "greaterThan",
8
- LESS_THAN = "lessThan",
6
+ export interface FilterRangeProps
7
+ extends Omit<RangeCategoryView, "key" | "label">,
8
+ BaseComponentProps {
9
+ categoryKey: CategoryKey;
10
+ categoryLabel: string;
11
+ categorySection?: string;
12
+ isFilterDrawer: boolean;
13
+ onCloseFilter: () => void;
14
+ onFilter: OnFilterFn;
9
15
  }
@@ -0,0 +1,16 @@
1
+ import { RangeCategoryView } from "../../../../common/categories/views/range/types";
2
+ import { RANGE_OPERATOR } from "./hooks/UseFilterRange/types";
3
+
4
+ /**
5
+ * Returns the range operator based on the selected values.
6
+ * @param categoryView - View model of range category.
7
+ * @returns The range operator or undefined if no valid range is selected.
8
+ */
9
+ export function getRangeOperator(
10
+ categoryView: Pick<RangeCategoryView, "selectedMax" | "selectedMin">
11
+ ): RANGE_OPERATOR | undefined {
12
+ const { selectedMax, selectedMin } = categoryView;
13
+ if (selectedMin && selectedMax) return RANGE_OPERATOR.BETWEEN;
14
+ if (selectedMin) return RANGE_OPERATOR.GREATER_THAN;
15
+ if (selectedMax) return RANGE_OPERATOR.LESS_THAN;
16
+ }
@@ -0,0 +1,22 @@
1
+ import { fn } from "@storybook/test";
2
+ import { ComponentProps } from "react";
3
+ import { LOREM_IPSUM } from "storybook/loremIpsum";
4
+ import { FilterTag } from "../filterTag";
5
+
6
+ export const DEFAULT_ARGS: ComponentProps<typeof FilterTag> = {
7
+ label: "male",
8
+ onRemove: fn(),
9
+ superseded: false,
10
+ };
11
+
12
+ export const WITH_ELLIPSIS_ARGS: ComponentProps<typeof FilterTag> = {
13
+ label: LOREM_IPSUM.LONG,
14
+ onRemove: fn(),
15
+ superseded: false,
16
+ };
17
+
18
+ export const WITH_RANGE_ARGS: ComponentProps<typeof FilterTag> = {
19
+ label: "10 - 34",
20
+ onRemove: fn(),
21
+ superseded: false,
22
+ };