@databiosphere/findable-ui 3.0.0 → 3.2.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 (54) hide show
  1. package/lib/components/Links/common/constants.d.ts +1 -0
  2. package/lib/components/Links/common/constants.js +4 -0
  3. package/lib/components/Links/common/entities.d.ts +8 -0
  4. package/lib/components/Links/common/utils.d.ts +13 -0
  5. package/lib/components/Links/common/utils.js +21 -1
  6. package/lib/components/Links/components/Link/components/ExploreViewLink/exploreViewLink.d.ts +7 -0
  7. package/lib/components/Links/components/Link/components/ExploreViewLink/exploreViewLink.js +148 -0
  8. package/lib/components/Links/components/Link/link.d.ts +2 -2
  9. package/lib/components/Links/components/Link/link.js +22 -6
  10. package/lib/components/common/Progress/components/CircularProgress/circularProgress.d.ts +7 -0
  11. package/lib/components/common/Progress/components/CircularProgress/circularProgress.js +28 -0
  12. package/lib/components/common/Progress/components/CircularProgress/circularProgress.styles.d.ts +5 -0
  13. package/lib/components/common/Progress/components/CircularProgress/circularProgress.styles.js +11 -0
  14. package/lib/components/common/Progress/components/CircularProgress/components/CircularProgressTrack/circularProgressTrack.d.ts +7 -0
  15. package/lib/components/common/Progress/components/CircularProgress/components/CircularProgressTrack/circularProgressTrack.js +44 -0
  16. package/lib/components/common/Progress/components/CircularProgress/components/CircularProgressTrack/circularProgressTrack.styles.d.ts +3 -0
  17. package/lib/components/common/Progress/components/CircularProgress/components/CircularProgressTrack/circularProgressTrack.styles.js +32 -0
  18. package/lib/config/entities.d.ts +2 -1
  19. package/lib/hooks/useCategoryFilter.d.ts +7 -1
  20. package/lib/hooks/useCategoryFilter.js +25 -6
  21. package/lib/providers/exploreState/initializer/constants.d.ts +2 -0
  22. package/lib/providers/exploreState/initializer/constants.js +7 -1
  23. package/lib/providers/exploreState/initializer/utils.js +1 -8
  24. package/lib/providers/exploreState/payloads/entities.d.ts +8 -1
  25. package/lib/providers/exploreState/utils.d.ts +4 -2
  26. package/lib/providers/exploreState/utils.js +4 -4
  27. package/lib/providers/exploreState.d.ts +10 -2
  28. package/lib/providers/exploreState.js +13 -0
  29. package/lib/styles/common/mixins/colors.d.ts +3 -3
  30. package/lib/styles/common/mixins/colors.js +7 -7
  31. package/lib/theme/common/components.d.ts +6 -0
  32. package/lib/theme/common/components.js +31 -1
  33. package/lib/theme/theme.js +1 -0
  34. package/package.json +1 -1
  35. package/src/components/Links/common/constants.ts +1 -0
  36. package/src/components/Links/common/entities.ts +11 -0
  37. package/src/components/Links/common/utils.ts +28 -0
  38. package/src/components/Links/components/Link/components/ExploreViewLink/exploreViewLink.tsx +172 -0
  39. package/src/components/Links/components/Link/link.tsx +36 -14
  40. package/src/components/common/Progress/components/CircularProgress/circularProgress.styles.ts +6 -0
  41. package/src/components/common/Progress/components/CircularProgress/circularProgress.tsx +26 -0
  42. package/src/components/common/Progress/components/CircularProgress/components/CircularProgressTrack/circularProgressTrack.styles.ts +33 -0
  43. package/src/components/common/Progress/components/CircularProgress/components/CircularProgressTrack/circularProgressTrack.tsx +23 -0
  44. package/src/config/entities.ts +4 -0
  45. package/src/hooks/useCategoryFilter.ts +31 -7
  46. package/src/providers/exploreState/initializer/constants.ts +8 -0
  47. package/src/providers/exploreState/initializer/utils.ts +6 -9
  48. package/src/providers/exploreState/payloads/entities.ts +8 -0
  49. package/src/providers/exploreState/utils.ts +11 -7
  50. package/src/providers/exploreState.tsx +33 -0
  51. package/src/styles/common/mixins/colors.ts +6 -6
  52. package/src/theme/common/components.ts +32 -0
  53. package/src/theme/theme.ts +1 -0
  54. package/types/data-explorer-ui.d.ts +10 -0
@@ -3,7 +3,7 @@ import { AzulSearchIndex } from "../apis/azul/common/entities";
3
3
  import { SelectCategoryView, SelectedFilter } from "../common/entities";
4
4
  import { CategoryGroup, SiteConfig } from "../config/entities";
5
5
  import { EntityPageStateMapper, EntityStateByCategoryGroupConfigKey, ListItem } from "./exploreState/entities";
6
- import { ApplySavedFilterPayload, PaginateTablePayload, PatchExploreResponsePayload, ProcessExploreResponsePayload, ProcessRelatedResponsePayload, ResetExploreResponsePayload, ToggleEntityViewPayload, UpdateColumnVisibilityPayload, UpdateEntityViewAccessPayload, UpdateFilterPayload, UpdateRowSelectionPayload, UpdateSortingPayload } from "./exploreState/payloads/entities";
6
+ import { ApplySavedFilterPayload, PaginateTablePayload, PatchExploreResponsePayload, ProcessExploreResponsePayload, ProcessRelatedResponsePayload, ResetExploreResponsePayload, ToggleEntityViewPayload, UpdateColumnVisibilityPayload, UpdateEntityFiltersPayload, UpdateEntityViewAccessPayload, UpdateFilterPayload, UpdateRowSelectionPayload, UpdateSortingPayload } from "./exploreState/payloads/entities";
7
7
  export declare type CatalogState = string | undefined;
8
8
  /**
9
9
  * Entity view.
@@ -114,6 +114,7 @@ export declare enum ExploreActionKind {
114
114
  SelectEntityType = "SELECT_ENTITY_TYPE",
115
115
  ToggleEntityView = "TOGGLE_ENTITY_VIEW",
116
116
  UpdateColumnVisibility = "UPDATE_COLUMN_VISIBILITY",
117
+ UpdateEntityFilters = "UPDATE_ENTITY_FILTERS",
117
118
  UpdateEntityViewAccess = "UPDATE_ENTITY_VIEW_ACCESS",
118
119
  UpdateFilter = "UPDATE_FILTER",
119
120
  UpdateRowSelection = "UPDATE_ROW_SELECTION",
@@ -122,7 +123,7 @@ export declare enum ExploreActionKind {
122
123
  /**
123
124
  * Explore action.
124
125
  */
125
- export declare type ExploreAction = ApplySavedFilterAction | ClearFiltersAction | PaginateTableAction | PatchExploreResponseAction | ProcessExploreResponseAction | ProcessRelatedResponseAction | ResetExploreResponseAction | ResetStateAction | SelectEntityTypeAction | ToggleEntityViewAction | UpdateColumnVisibilityAction | UpdateEntityViewAccessAction | UpdateFilterAction | UpdateRowSelectionAction | UpdateSortingAction;
126
+ export declare type ExploreAction = ApplySavedFilterAction | ClearFiltersAction | PaginateTableAction | PatchExploreResponseAction | ProcessExploreResponseAction | ProcessRelatedResponseAction | ResetExploreResponseAction | ResetStateAction | SelectEntityTypeAction | ToggleEntityViewAction | UpdateColumnVisibilityAction | UpdateEntityFiltersAction | UpdateEntityViewAccessAction | UpdateFilterAction | UpdateRowSelectionAction | UpdateSortingAction;
126
127
  /**
127
128
  * Apply saved filter action.
128
129
  */
@@ -200,6 +201,13 @@ declare type UpdateColumnVisibilityAction = {
200
201
  payload: UpdateColumnVisibilityPayload;
201
202
  type: ExploreActionKind.UpdateColumnVisibility;
202
203
  };
204
+ /**
205
+ * Update entity filters action.
206
+ */
207
+ declare type UpdateEntityFiltersAction = {
208
+ payload: UpdateEntityFiltersPayload;
209
+ type: ExploreActionKind.UpdateEntityFilters;
210
+ };
203
211
  /**
204
212
  * Update entity view access action.
205
213
  */
@@ -104,6 +104,7 @@ var ExploreActionKind;
104
104
  ExploreActionKind["SelectEntityType"] = "SELECT_ENTITY_TYPE";
105
105
  ExploreActionKind["ToggleEntityView"] = "TOGGLE_ENTITY_VIEW";
106
106
  ExploreActionKind["UpdateColumnVisibility"] = "UPDATE_COLUMN_VISIBILITY";
107
+ ExploreActionKind["UpdateEntityFilters"] = "UPDATE_ENTITY_FILTERS";
107
108
  ExploreActionKind["UpdateEntityViewAccess"] = "UPDATE_ENTITY_VIEW_ACCESS";
108
109
  ExploreActionKind["UpdateFilter"] = "UPDATE_FILTER";
109
110
  ExploreActionKind["UpdateRowSelection"] = "UPDATE_ROW_SELECTION";
@@ -224,6 +225,18 @@ function exploreReducer(state, action, exploreContext) {
224
225
  case ExploreActionKind.UpdateColumnVisibility: {
225
226
  return Object.assign(Object.assign({}, state), { entityPageState: (0, utils_2.updateEntityPageState)(state.tabValue, state.entityPageState, { columnsVisibility: payload }) });
226
227
  }
228
+ /**
229
+ * Update entity filters.
230
+ */
231
+ case ExploreActionKind.UpdateEntityFilters: {
232
+ const { entityListType, filters: filterState } = payload;
233
+ const categoryGroupConfigKey = (0, utils_2.getEntityCategoryGroupConfigKey)(entityListType, state.entityPageState);
234
+ (0, utils_2.updateEntityStateByCategoryGroupConfigKey)(state, {
235
+ filterState,
236
+ savedFilterState: [], // Clear saved filter state.
237
+ }, categoryGroupConfigKey);
238
+ return Object.assign(Object.assign({}, state), { entityPageState: (0, utils_2.resetRowSelection)(state, categoryGroupConfigKey) });
239
+ }
227
240
  /**
228
241
  * Update entity view access
229
242
  **/
@@ -1,15 +1,13 @@
1
1
  import { CommonColors, PaletteColor } from "@mui/material/styles/createPalette";
2
2
  import { ThemeProps } from "../../../theme/theme";
3
+ export declare const alertLight: ({ theme }: ThemeProps) => PaletteColor["light"];
3
4
  export declare const alertLightest: ({ theme, }: ThemeProps) => PaletteColor["lightest"];
4
5
  export declare const alertMain: ({ theme }: ThemeProps) => PaletteColor["main"];
5
6
  export declare const errorMain: ({ theme }: ThemeProps) => PaletteColor["main"];
6
- export declare const infoDark: ({ theme }: ThemeProps) => PaletteColor["dark"];
7
7
  export declare const infoLight: ({ theme }: ThemeProps) => PaletteColor["light"];
8
8
  export declare const infoLightest: ({ theme }: ThemeProps) => PaletteColor["lightest"];
9
9
  export declare const infoMain: ({ theme }: ThemeProps) => PaletteColor["main"];
10
- export declare const inkDark: ({ theme }: ThemeProps) => PaletteColor["dark"];
11
10
  export declare const inkLight: ({ theme }: ThemeProps) => PaletteColor["light"];
12
- export declare const inkLightest: ({ theme }: ThemeProps) => PaletteColor["lightest"];
13
11
  export declare const inkMain: ({ theme }: ThemeProps) => PaletteColor["main"];
14
12
  export declare const primaryDark: ({ theme }: ThemeProps) => PaletteColor["dark"];
15
13
  export declare const primaryMain: ({ theme }: ThemeProps) => PaletteColor["main"];
@@ -17,8 +15,10 @@ export declare const smokeDark: ({ theme }: ThemeProps) => PaletteColor["dark"];
17
15
  export declare const smokeLight: ({ theme }: ThemeProps) => PaletteColor["light"];
18
16
  export declare const smokeLightest: ({ theme, }: ThemeProps) => PaletteColor["lightest"];
19
17
  export declare const smokeMain: ({ theme }: ThemeProps) => PaletteColor["main"];
18
+ export declare const successLight: ({ theme }: ThemeProps) => PaletteColor["light"];
20
19
  export declare const successLightest: ({ theme, }: ThemeProps) => PaletteColor["lightest"];
21
20
  export declare const successMain: ({ theme }: ThemeProps) => PaletteColor["main"];
21
+ export declare const warningLight: ({ theme }: ThemeProps) => PaletteColor["light"];
22
22
  export declare const warningLightest: ({ theme, }: ThemeProps) => PaletteColor["lightest"];
23
23
  export declare const warningMain: ({ theme }: ThemeProps) => PaletteColor["main"];
24
24
  export declare const white: ({ theme }: ThemeProps) => CommonColors["white"];
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.white = exports.warningMain = exports.warningLightest = exports.successMain = exports.successLightest = exports.smokeMain = exports.smokeLightest = exports.smokeLight = exports.smokeDark = exports.primaryMain = exports.primaryDark = exports.inkMain = exports.inkLightest = exports.inkLight = exports.inkDark = exports.infoMain = exports.infoLightest = exports.infoLight = exports.infoDark = exports.errorMain = exports.alertMain = exports.alertLightest = void 0;
3
+ exports.white = exports.warningMain = exports.warningLightest = exports.warningLight = exports.successMain = exports.successLightest = exports.successLight = exports.smokeMain = exports.smokeLightest = exports.smokeLight = exports.smokeDark = exports.primaryMain = exports.primaryDark = exports.inkMain = exports.inkLight = exports.infoMain = exports.infoLightest = exports.infoLight = exports.errorMain = exports.alertMain = exports.alertLightest = exports.alertLight = void 0;
4
4
  // Alert
5
+ const alertLight = ({ theme }) => theme.palette.alert.light;
6
+ exports.alertLight = alertLight;
5
7
  const alertLightest = ({ theme, }) => theme.palette.alert.lightest;
6
8
  exports.alertLightest = alertLightest;
7
9
  const alertMain = ({ theme }) => theme.palette.alert.main;
@@ -10,8 +12,6 @@ exports.alertMain = alertMain;
10
12
  const errorMain = ({ theme }) => theme.palette.error.main;
11
13
  exports.errorMain = errorMain;
12
14
  // Info
13
- const infoDark = ({ theme }) => theme.palette.info.dark;
14
- exports.infoDark = infoDark;
15
15
  const infoLight = ({ theme }) => theme.palette.info.light;
16
16
  exports.infoLight = infoLight;
17
17
  const infoLightest = ({ theme }) => theme.palette.info.lightest;
@@ -19,12 +19,8 @@ exports.infoLightest = infoLightest;
19
19
  const infoMain = ({ theme }) => theme.palette.info.main;
20
20
  exports.infoMain = infoMain;
21
21
  // Ink
22
- const inkDark = ({ theme }) => theme.palette.ink.dark;
23
- exports.inkDark = inkDark;
24
22
  const inkLight = ({ theme }) => theme.palette.ink.light;
25
23
  exports.inkLight = inkLight;
26
- const inkLightest = ({ theme }) => theme.palette.ink.lightest;
27
- exports.inkLightest = inkLightest;
28
24
  const inkMain = ({ theme }) => theme.palette.ink.main;
29
25
  exports.inkMain = inkMain;
30
26
  // Primary
@@ -42,11 +38,15 @@ exports.smokeLightest = smokeLightest;
42
38
  const smokeMain = ({ theme }) => theme.palette.smoke.main;
43
39
  exports.smokeMain = smokeMain;
44
40
  // Success
41
+ const successLight = ({ theme }) => theme.palette.success.light;
42
+ exports.successLight = successLight;
45
43
  const successLightest = ({ theme, }) => theme.palette.success.lightest;
46
44
  exports.successLightest = successLightest;
47
45
  const successMain = ({ theme }) => theme.palette.success.main;
48
46
  exports.successMain = successMain;
49
47
  // Warning
48
+ const warningLight = ({ theme }) => theme.palette.warning.light;
49
+ exports.warningLight = warningLight;
50
50
  const warningLightest = ({ theme, }) => theme.palette.warning.lightest;
51
51
  exports.warningLightest = warningLightest;
52
52
  const warningMain = ({ theme }) => theme.palette.warning.main;
@@ -75,6 +75,12 @@ export declare const MuiCheckbox: (theme: Theme) => Components["MuiCheckbox"];
75
75
  * @returns MuiChip component theme styles.
76
76
  */
77
77
  export declare const MuiChip: (theme: Theme) => Components["MuiChip"];
78
+ /**
79
+ * MuiCircularProgress Component
80
+ * @param theme - Theme.
81
+ * @returns MuiCircularProgress component theme styles.
82
+ */
83
+ export declare const MuiCircularProgress: (theme: Theme) => Components["MuiCircularProgress"];
78
84
  /**
79
85
  * MuiCssBaseline Component
80
86
  * @param theme - Theme.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MuiTypography = exports.MuiTooltip = exports.MuiToolbar = exports.MuiToggleButtonGroup = exports.MuiToggleButton = exports.MuiTabs = exports.MuiTableSortLabel = exports.MuiTableCell = exports.MuiTab = exports.MuiSvgIcon = exports.MuiSelect = exports.MuiRadio = exports.MuiPaper = exports.MuiOutlinedInput = exports.MuiMenuItem = exports.MuiListSubheader = exports.MuiListItemText = exports.MuiListItemButton = exports.MuiLink = exports.MuiInputBase = exports.MuiIconButton = exports.MuiFormHelperText = exports.MuiFormGroup = exports.MuiFormControlLabel = exports.MuiDrawer = exports.MuiDivider = exports.MuiDialogTitle = exports.MuiDialogContent = exports.MuiDialogActions = exports.MuiDialog = exports.MuiCssBaseline = exports.MuiChip = exports.MuiCheckbox = exports.MuiCard = exports.MuiButtonGroup = exports.MuiButtonBase = exports.MuiButton = exports.MuiBreadcrumbs = exports.MuiBackdrop = exports.MuiAppBar = exports.MuiAlertTitle = exports.MuiAlert = exports.MuiAccordionSummary = exports.MuiAccordionDetails = exports.MuiAccordion = void 0;
3
+ exports.MuiTypography = exports.MuiTooltip = exports.MuiToolbar = exports.MuiToggleButtonGroup = exports.MuiToggleButton = exports.MuiTabs = exports.MuiTableSortLabel = exports.MuiTableCell = exports.MuiTab = exports.MuiSvgIcon = exports.MuiSelect = exports.MuiRadio = exports.MuiPaper = exports.MuiOutlinedInput = exports.MuiMenuItem = exports.MuiListSubheader = exports.MuiListItemText = exports.MuiListItemButton = exports.MuiLink = exports.MuiInputBase = exports.MuiIconButton = exports.MuiFormHelperText = exports.MuiFormGroup = exports.MuiFormControlLabel = exports.MuiDrawer = exports.MuiDivider = exports.MuiDialogTitle = exports.MuiDialogContent = exports.MuiDialogActions = exports.MuiDialog = exports.MuiCssBaseline = exports.MuiCircularProgress = exports.MuiChip = exports.MuiCheckbox = exports.MuiCard = exports.MuiButtonGroup = exports.MuiButtonBase = exports.MuiButton = exports.MuiBreadcrumbs = exports.MuiBackdrop = exports.MuiAppBar = exports.MuiAlertTitle = exports.MuiAlert = exports.MuiAccordionSummary = exports.MuiAccordionDetails = exports.MuiAccordion = void 0;
4
4
  const errorIcon_1 = require("../../components/common/CustomIcon/components/ErrorIcon/errorIcon");
5
5
  const infoIcon_1 = require("../../components/common/CustomIcon/components/InfoIcon/infoIcon");
6
6
  const successIcon_1 = require("../../components/common/CustomIcon/components/SuccessIcon/successIcon");
@@ -552,6 +552,31 @@ const MuiChip = (theme) => {
552
552
  };
553
553
  };
554
554
  exports.MuiChip = MuiChip;
555
+ /**
556
+ * MuiCircularProgress Component
557
+ * @param theme - Theme.
558
+ * @returns MuiCircularProgress component theme styles.
559
+ */
560
+ const MuiCircularProgress = (theme) => {
561
+ return {
562
+ styleOverrides: {
563
+ circle: {
564
+ strokeLinecap: "round",
565
+ },
566
+ },
567
+ variants: [
568
+ {
569
+ props: {
570
+ color: "alert",
571
+ },
572
+ style: {
573
+ color: theme.palette.alert.main,
574
+ },
575
+ },
576
+ ],
577
+ };
578
+ };
579
+ exports.MuiCircularProgress = MuiCircularProgress;
555
580
  /**
556
581
  * MuiCssBaseline Component
557
582
  * @param theme - Theme.
@@ -851,6 +876,11 @@ exports.MuiLink = {
851
876
  defaultProps: {
852
877
  underline: "hover",
853
878
  },
879
+ styleOverrides: {
880
+ root: {
881
+ cursor: "pointer",
882
+ },
883
+ },
854
884
  };
855
885
  /**
856
886
  * MuiListItemButton Component
@@ -96,6 +96,7 @@ function createAppTheme(customOptions) {
96
96
  MuiCard: C.MuiCard,
97
97
  MuiCheckbox: C.MuiCheckbox(theme),
98
98
  MuiChip: C.MuiChip(theme),
99
+ MuiCircularProgress: C.MuiCircularProgress(theme),
99
100
  MuiCssBaseline: C.MuiCssBaseline(theme),
100
101
  MuiDialog: C.MuiDialog(theme),
101
102
  MuiDialogActions: C.MuiDialogActions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@databiosphere/findable-ui",
3
- "version": "3.0.0",
3
+ "version": "3.2.0",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "jest",
@@ -0,0 +1 @@
1
+ export const URL_OBJECT_KEYS = ["href", "query"];
@@ -1,4 +1,15 @@
1
+ import { UrlObject } from "url";
2
+
1
3
  export enum ANCHOR_TARGET {
2
4
  BLANK = "_blank",
3
5
  SELF = "_self",
4
6
  }
7
+
8
+ export type StrictUrlObject = Omit<UrlObject, "href" | "query"> & {
9
+ href: string;
10
+ query: string;
11
+ };
12
+
13
+ export type Url = string | UrlObjectWithHrefAndQuery;
14
+
15
+ export type UrlObjectWithHrefAndQuery = Pick<StrictUrlObject, "href" | "query">;
@@ -1,3 +1,6 @@
1
+ import { URL_OBJECT_KEYS } from "./constants";
2
+ import { Url, UrlObjectWithHrefAndQuery } from "./entities";
3
+
1
4
  /**
2
5
  * Returns true if the given link is an internal link.
3
6
  * @param link - Link.
@@ -6,3 +9,28 @@
6
9
  export function isClientSideNavigation(link: string): boolean {
7
10
  return /^\/(?!\/)/.test(link);
8
11
  }
12
+
13
+ /**
14
+ * Returns true if the given url is a URL object with href and query.
15
+ * @param value - URL.
16
+ * @returns true if the given url is a URL object with href and query.
17
+ */
18
+ export function isURLObjectWithHrefAndQuery(
19
+ value: Url
20
+ ): value is UrlObjectWithHrefAndQuery {
21
+ return (
22
+ typeof value !== "string" &&
23
+ Object.entries(value).every(
24
+ ([key, value]) => URL_OBJECT_KEYS.includes(key) && !!value
25
+ )
26
+ );
27
+ }
28
+
29
+ /**
30
+ * Returns true if the given url is a string.
31
+ * @param value - URL.
32
+ * @returns true if the given url is a string.
33
+ */
34
+ export function isURLString(value: Url): value is string {
35
+ return typeof value === "string";
36
+ }
@@ -0,0 +1,172 @@
1
+ import Link from "next/link";
2
+ import React, { useCallback } from "react";
3
+ import { UrlObject } from "url";
4
+ import { SelectedFilter } from "../../../../../../common/entities";
5
+ import { useExploreState } from "../../../../../../hooks/useExploreState";
6
+ import {
7
+ ExploreActionKind,
8
+ ExploreState,
9
+ } from "../../../../../../providers/exploreState";
10
+ import {
11
+ ANCHOR_TARGET,
12
+ UrlObjectWithHrefAndQuery,
13
+ } from "../../../../common/entities";
14
+ import { LinkProps } from "../../link";
15
+
16
+ const PARAM_FILTER = "filter";
17
+
18
+ export interface ExploreViewLinkProps
19
+ extends Omit<LinkProps, "copyable" | "noWrap" | "url"> {
20
+ url: UrlObjectWithHrefAndQuery;
21
+ }
22
+
23
+ export const ExploreViewLink = ({
24
+ className,
25
+ label,
26
+ onClick,
27
+ target = ANCHOR_TARGET.SELF,
28
+ url,
29
+ }: ExploreViewLinkProps): JSX.Element => {
30
+ const { exploreDispatch, exploreState } = useExploreState();
31
+
32
+ if (!isValidExploreURL(url, exploreState)) {
33
+ throwError(url);
34
+ }
35
+
36
+ const onNavigate = useCallback(() => {
37
+ const entityListType = getEntityListType(url.href);
38
+ const filters = getSelectedFilters(url.query);
39
+ exploreDispatch({
40
+ payload: { entityListType, filters },
41
+ type: ExploreActionKind.UpdateEntityFilters,
42
+ });
43
+ onClick?.();
44
+ }, [exploreDispatch, onClick, url]);
45
+
46
+ return (
47
+ <Link
48
+ className={className}
49
+ href={url.href}
50
+ onClick={onNavigate}
51
+ rel="noopener"
52
+ target={target}
53
+ >
54
+ {label}
55
+ </Link>
56
+ );
57
+ };
58
+
59
+ /**
60
+ * Returns the entity list type "entityListType" inferred from the given href.
61
+ * @param href - Href.
62
+ * @returns entity list type.
63
+ */
64
+ function getEntityListType(href: UrlObjectWithHrefAndQuery["href"]): string {
65
+ return href.substring(1);
66
+ }
67
+
68
+ /**
69
+ * Returns the selected filters from the given query.
70
+ * @param query - Query.
71
+ * @returns selected filters.
72
+ */
73
+ function getSelectedFilters(
74
+ query: UrlObjectWithHrefAndQuery["query"]
75
+ ): SelectedFilter[] {
76
+ const decodedQuery = decodeURIComponent(query);
77
+ const parsedQuery = JSON.parse(decodedQuery);
78
+ return parsedQuery[PARAM_FILTER];
79
+ }
80
+
81
+ /**
82
+ * Returns true if the given value is a SelectedFilter.
83
+ * @param value - Value.
84
+ * @returns true if the given value is a SelectedFilter.
85
+ */
86
+ function isSelectedFilter(value: unknown): value is SelectedFilter {
87
+ return (
88
+ typeof value === "object" &&
89
+ value !== null &&
90
+ "categoryKey" in value &&
91
+ "value" in value
92
+ );
93
+ }
94
+
95
+ /**
96
+ * Returns true if the given query string is a valid JSON string.
97
+ * @param query - Query string.
98
+ * @returns true if the given query string is a valid JSON string.
99
+ */
100
+ function isValidJsonString(query: string): boolean {
101
+ try {
102
+ JSON.parse(query);
103
+ return true;
104
+ } catch (e) {
105
+ return false;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Returns true if the given explore link is valid.
111
+ * @param url - Explore link URL.
112
+ * @param exploreState - Explore state.
113
+ * @returns true if the given explore link is valid.
114
+ */
115
+ function isValidExploreURL(
116
+ url: UrlObjectWithHrefAndQuery,
117
+ exploreState: ExploreState
118
+ ): boolean {
119
+ const validHref = isValidHref(url, exploreState);
120
+ const validQuery = isValidQuery(url);
121
+ return validHref && validQuery;
122
+ }
123
+
124
+ /**
125
+ * Returns true if the given href is a configured key in the explore state's entityPageState.
126
+ * @param url - Explore link URL.
127
+ * @param exploreState - Explore state.
128
+ * @returns true if the given href is configured in the explore state.
129
+ */
130
+ function isValidHref(
131
+ url: UrlObjectWithHrefAndQuery,
132
+ exploreState: ExploreState
133
+ ): boolean {
134
+ const { entityPageState } = exploreState;
135
+ const { href } = url;
136
+ return href.startsWith("/") && href.substring(1) in entityPageState;
137
+ }
138
+
139
+ /**
140
+ * Returns true if the given explore query is valid.
141
+ * @param url - Explore link URL.
142
+ * @returns true if the given explore query is valid.
143
+ */
144
+ function isValidQuery(url: UrlObjectWithHrefAndQuery): boolean {
145
+ const { query } = url;
146
+ // Decode and parse the query.
147
+ const decodedQuery = decodeURIComponent(query);
148
+ // Query should be a valid JSON string.
149
+ if (isValidJsonString(decodedQuery)) {
150
+ const parsedQuery = JSON.parse(decodedQuery);
151
+ // Query should contain "filter" key.
152
+ if (PARAM_FILTER in parsedQuery) {
153
+ const filters = parsedQuery[PARAM_FILTER];
154
+ // Filter should be an array.
155
+ if (Array.isArray(filters)) {
156
+ // Filter should contain only SelectedFilter objects.
157
+ return filters.every(isSelectedFilter);
158
+ }
159
+ }
160
+ }
161
+ return false;
162
+ }
163
+
164
+ /**
165
+ * Throws an error with the given URL object.
166
+ * @param url - URL object.
167
+ */
168
+ function throwError(url: UrlObject): never {
169
+ throw new Error(
170
+ `Invalid explore URL href or query: ${url.href}, ${url.query}`
171
+ );
172
+ }
@@ -3,8 +3,13 @@ import NLink from "next/link";
3
3
  import React, { ReactNode } from "react";
4
4
  import { isValidUrl } from "../../../../common/utils";
5
5
  import { CopyToClipboard } from "../../../common/CopyToClipboard/copyToClipboard";
6
- import { ANCHOR_TARGET } from "../../common/entities";
7
- import { isClientSideNavigation } from "../../common/utils";
6
+ import { ANCHOR_TARGET, Url } from "../../common/entities";
7
+ import {
8
+ isClientSideNavigation,
9
+ isURLObjectWithHrefAndQuery,
10
+ isURLString,
11
+ } from "../../common/utils";
12
+ import { ExploreViewLink } from "./components/ExploreViewLink/exploreViewLink";
8
13
 
9
14
  export interface LinkProps {
10
15
  className?: string;
@@ -13,7 +18,7 @@ export interface LinkProps {
13
18
  noWrap?: MLinkProps["noWrap"];
14
19
  onClick?: () => void;
15
20
  target?: ANCHOR_TARGET;
16
- url: string;
21
+ url: Url /* url specified as UrlObject with href and query defined, and is currently only used for internal links */;
17
22
  }
18
23
 
19
24
  export const Link = ({
@@ -25,9 +30,22 @@ export const Link = ({
25
30
  target,
26
31
  url,
27
32
  }: LinkProps): JSX.Element => {
28
- return (
29
- <>
30
- {isClientSideNavigation(url) ? (
33
+ if (isURLObjectWithHrefAndQuery(url)) {
34
+ /* Internal navigation - explore link */
35
+ return (
36
+ <ExploreViewLink
37
+ className={className}
38
+ label={label}
39
+ onClick={onClick}
40
+ target={target}
41
+ url={url}
42
+ />
43
+ );
44
+ }
45
+ if (isURLString(url)) {
46
+ if (isClientSideNavigation(url)) {
47
+ /* Client-side navigation */
48
+ return (
31
49
  <>
32
50
  <NLink href={url} legacyBehavior passHref>
33
51
  <MLink
@@ -42,23 +60,27 @@ export const Link = ({
42
60
  </NLink>
43
61
  {copyable && <CopyToClipboard copyStr={url} />}
44
62
  </>
45
- ) : isValidUrl(url) ? (
63
+ );
64
+ }
65
+ if (isValidUrl(url)) {
66
+ /* External navigation */
67
+ return (
46
68
  <>
47
69
  <MLink
48
70
  className={className}
49
71
  href={url}
50
- rel="noopener noreferrer"
51
72
  noWrap={noWrap}
52
- target={target || ANCHOR_TARGET.BLANK}
53
73
  onClick={onClick}
74
+ rel="noopener noreferrer"
75
+ target={target || ANCHOR_TARGET.BLANK}
54
76
  >
55
77
  {label}
56
78
  </MLink>
57
79
  {copyable && <CopyToClipboard copyStr={url} />}
58
80
  </>
59
- ) : (
60
- label
61
- )}
62
- </>
63
- );
81
+ );
82
+ }
83
+ }
84
+ /* Invalid URL */
85
+ return <>{label}</>;
64
86
  };
@@ -0,0 +1,6 @@
1
+ import styled from "@emotion/styled";
2
+
3
+ export const ProgressPositioner = styled.div`
4
+ display: flex;
5
+ position: relative; /* positions track */
6
+ `;
@@ -0,0 +1,26 @@
1
+ import {
2
+ CircularProgress as MCircularProgress,
3
+ CircularProgressProps as MCircularProgressProps,
4
+ } from "@mui/material";
5
+ import React, { ElementType } from "react";
6
+ import { ProgressPositioner } from "./circularProgress.styles";
7
+ import { CircularProgressTrack } from "./components/CircularProgressTrack/circularProgressTrack";
8
+
9
+ export interface CircularProgressProps extends MCircularProgressProps {
10
+ className?: string;
11
+ Track?: ElementType<CircularProgressProps>;
12
+ }
13
+
14
+ export const CircularProgress = ({
15
+ className,
16
+ value,
17
+ Track = CircularProgressTrack,
18
+ ...props /* Spread props to allow for CircularProgress specific props e.g. "disableShrink". */
19
+ }: CircularProgressProps): JSX.Element => {
20
+ return (
21
+ <ProgressPositioner className={className}>
22
+ <Track {...props} />
23
+ <MCircularProgress value={value} {...props} />
24
+ </ProgressPositioner>
25
+ );
26
+ };
@@ -0,0 +1,33 @@
1
+ import styled from "@emotion/styled";
2
+ import { CircularProgress as MCircularProgress } from "@mui/material";
3
+ import {
4
+ alertLight,
5
+ infoLight,
6
+ smokeLight,
7
+ successLight,
8
+ warningLight,
9
+ } from "../../../../../../../styles/common/mixins/colors";
10
+
11
+ export const CircularProgress = styled(MCircularProgress)`
12
+ color: ${smokeLight};
13
+ left: 0;
14
+ position: absolute;
15
+ top: 0;
16
+ z-index: 0;
17
+
18
+ &.MuiCircularProgress-colorAlert {
19
+ color: ${alertLight};
20
+ }
21
+
22
+ &.MuiCircularProgress-colorInfo {
23
+ color: ${infoLight};
24
+ }
25
+
26
+ &.MuiCircularProgress-colorSuccess {
27
+ color: ${successLight};
28
+ }
29
+
30
+ &.MuiCircularProgress-colorWarning {
31
+ color: ${warningLight};
32
+ }
33
+ `;
@@ -0,0 +1,23 @@
1
+ import { CircularProgressProps as MCircularProgressProps } from "@mui/material";
2
+ import React, { Fragment } from "react";
3
+ import { CircularProgress } from "./circularProgressTrack.styles";
4
+
5
+ export interface CircularProgressTrackProps extends MCircularProgressProps {
6
+ className?: string;
7
+ track?: boolean;
8
+ }
9
+
10
+ export const CircularProgressTrack = ({
11
+ className,
12
+ track = true,
13
+ value = 100,
14
+ ...props /* Spread props to allow for CircularProgress specific props e.g. "disableShrink". */
15
+ }: CircularProgressTrackProps): JSX.Element => {
16
+ return (
17
+ <Fragment>
18
+ {track && (
19
+ <CircularProgress className={className} value={value} {...props} />
20
+ )}
21
+ </Fragment>
22
+ );
23
+ };
@@ -3,6 +3,7 @@ import { ColumnSort } from "@tanstack/react-table";
3
3
  import { JSXElementConstructor, ReactNode } from "react";
4
4
  import {
5
5
  CategoryKey,
6
+ SelectCategoryValueView,
6
7
  SelectedFilter,
7
8
  SelectedFilterValue,
8
9
  } from "../common/entities";
@@ -77,6 +78,9 @@ export interface CategoryGroup {
77
78
  export interface CategoryConfig {
78
79
  key: string;
79
80
  label: string;
81
+ mapSelectCategoryValue?: (
82
+ selectCategoryValue: SelectCategoryValueView
83
+ ) => SelectCategoryValueView;
80
84
  }
81
85
 
82
86
  /**