@databiosphere/findable-ui 21.2.0 → 21.4.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 (109) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +15 -0
  3. package/lib/common/entities.d.ts +33 -0
  4. package/lib/components/DataDictionary/common/utils.d.ts +38 -0
  5. package/lib/components/DataDictionary/common/utils.js +122 -0
  6. package/lib/components/Export/components/ExportForm/components/ExportButton/exportButton.js +6 -1
  7. package/lib/components/Export/components/ManifestDownload/components/ManifestDownloadEntity/components/FileManifestDownload/fileManifestDownload.js +5 -2
  8. package/lib/components/Filter/components/Filter/filter.js +1 -1
  9. package/lib/components/Filter/components/FilterLabel/filterLabel.d.ts +3 -1
  10. package/lib/components/Filter/components/FilterLabel/filterLabel.js +4 -2
  11. package/lib/components/Index/components/AzulFileDownload/azulFileDownload.js +10 -5
  12. package/lib/components/Index/components/Tabs/common/utils.js +2 -1
  13. package/lib/components/Layout/components/Header/components/Content/components/Navigation/components/NavigationMenu/navigationMenu.js +1 -1
  14. package/lib/components/Layout/components/Header/components/Content/components/Navigation/components/NavigationMenuItems/navigationMenuItems.js +20 -21
  15. package/lib/components/Layout/components/Header/components/Content/components/Navigation/constants.d.ts +1 -0
  16. package/lib/components/Layout/components/Header/components/Content/components/Navigation/constants.js +1 -0
  17. package/lib/components/Layout/components/Header/components/Content/components/Navigation/navigation.d.ts +2 -1
  18. package/lib/components/Layout/components/Header/components/Content/components/Navigation/navigation.js +16 -17
  19. package/lib/components/Layout/components/Header/header.js +2 -1
  20. package/lib/components/Login/components/Button/types.d.ts +1 -1
  21. package/lib/components/Login/components/Buttons/buttons.d.ts +2 -0
  22. package/lib/components/Login/components/Buttons/buttons.js +5 -0
  23. package/lib/components/Login/components/Buttons/types.d.ts +8 -0
  24. package/lib/components/Login/components/Buttons/types.js +1 -0
  25. package/lib/components/Login/components/Section/components/Consent/consent.d.ts +3 -0
  26. package/lib/components/Login/components/Section/components/Consent/consent.js +14 -0
  27. package/lib/components/Login/components/Section/components/Consent/consent.styles.d.ts +7 -0
  28. package/lib/components/Login/components/Section/components/Consent/consent.styles.js +14 -0
  29. package/lib/components/Login/components/Section/components/Consent/types.d.ts +6 -0
  30. package/lib/components/Login/components/Section/components/Consent/types.js +1 -0
  31. package/lib/components/Login/components/Section/components/Warning/warning.d.ts +3 -0
  32. package/lib/components/Login/components/Section/components/Warning/warning.js +9 -0
  33. package/lib/components/Login/hooks/useUserConsent/types.d.ts +10 -0
  34. package/lib/components/Login/hooks/useUserConsent/types.js +1 -0
  35. package/lib/components/Login/hooks/useUserConsent/useUserConsent.d.ts +2 -0
  36. package/lib/components/Login/hooks/useUserConsent/useUserConsent.js +24 -0
  37. package/lib/components/Login/hooks/useUserLogin/types.d.ts +6 -0
  38. package/lib/components/Login/hooks/useUserLogin/types.js +1 -0
  39. package/lib/components/Login/hooks/useUserLogin/useUserLogin.d.ts +2 -0
  40. package/lib/components/Login/hooks/useUserLogin/useUserLogin.js +21 -0
  41. package/lib/components/Table/components/TableHead/tableHead.js +4 -1
  42. package/lib/components/common/CustomIcon/components/CloseIcon/closeIcon.d.ts +2 -0
  43. package/lib/components/common/CustomIcon/components/CloseIcon/closeIcon.js +6 -0
  44. package/lib/components/common/LoginDialog/constants.d.ts +6 -0
  45. package/lib/components/common/LoginDialog/constants.js +21 -0
  46. package/lib/components/common/LoginDialog/loginDialog.d.ts +2 -0
  47. package/lib/components/common/LoginDialog/loginDialog.js +27 -0
  48. package/lib/components/common/LoginDialog/loginDialog.styles.d.ts +3 -0
  49. package/lib/components/common/LoginDialog/loginDialog.styles.js +50 -0
  50. package/lib/components/common/LoginDialog/types.d.ts +4 -0
  51. package/lib/components/common/LoginDialog/types.js +1 -0
  52. package/lib/components/common/Tabs/tabs.d.ts +2 -0
  53. package/lib/components/common/Tabs/tabs.js +14 -1
  54. package/lib/config/entities.d.ts +6 -1
  55. package/lib/hooks/useCategoryFilter.js +1 -0
  56. package/lib/providers/config.js +9 -2
  57. package/lib/providers/loginGuard/common/types.d.ts +18 -0
  58. package/lib/providers/loginGuard/common/types.js +1 -0
  59. package/lib/providers/loginGuard/context.d.ts +6 -0
  60. package/lib/providers/loginGuard/context.js +10 -0
  61. package/lib/providers/loginGuard/hook.d.ts +9 -0
  62. package/lib/providers/loginGuard/hook.js +12 -0
  63. package/lib/providers/loginGuard/provider.d.ts +11 -0
  64. package/lib/providers/loginGuard/provider.js +55 -0
  65. package/lib/styles/common/mui/typography.d.ts +1 -0
  66. package/lib/styles/common/mui/typography.js +7 -0
  67. package/package.json +1 -1
  68. package/src/common/entities.ts +37 -0
  69. package/src/components/DataDictionary/common/utils.ts +160 -0
  70. package/src/components/Export/components/ExportForm/components/ExportButton/exportButton.tsx +8 -1
  71. package/src/components/Export/components/ManifestDownload/components/ManifestDownloadEntity/components/FileManifestDownload/fileManifestDownload.tsx +11 -3
  72. package/src/components/Filter/components/Filter/filter.tsx +1 -0
  73. package/src/components/Filter/components/FilterLabel/filterLabel.tsx +16 -10
  74. package/src/components/Index/components/AzulFileDownload/azulFileDownload.tsx +12 -5
  75. package/src/components/Index/components/Tabs/common/utils.ts +2 -0
  76. package/src/components/Layout/components/Header/components/Content/components/Navigation/components/NavigationMenu/navigationMenu.tsx +1 -1
  77. package/src/components/Layout/components/Header/components/Content/components/Navigation/components/NavigationMenuItems/navigationMenuItems.tsx +16 -15
  78. package/src/components/Layout/components/Header/components/Content/components/Navigation/constants.ts +1 -0
  79. package/src/components/Layout/components/Header/components/Content/components/Navigation/navigation.tsx +26 -18
  80. package/src/components/Layout/components/Header/header.tsx +6 -1
  81. package/src/components/Login/components/Button/types.ts +1 -1
  82. package/src/components/Login/components/Buttons/buttons.tsx +22 -0
  83. package/src/components/Login/components/Buttons/types.ts +9 -0
  84. package/src/components/Login/components/Section/components/Consent/consent.styles.ts +15 -0
  85. package/src/components/Login/components/Section/components/Consent/consent.tsx +30 -0
  86. package/src/components/Login/components/Section/components/Consent/types.ts +10 -0
  87. package/src/components/Login/components/Section/components/Warning/warning.tsx +24 -0
  88. package/src/components/Login/hooks/useUserConsent/types.ts +11 -0
  89. package/src/components/Login/hooks/useUserConsent/useUserConsent.ts +32 -0
  90. package/src/components/Login/hooks/useUserLogin/types.ts +8 -0
  91. package/src/components/Login/hooks/useUserLogin/useUserLogin.ts +29 -0
  92. package/src/components/Table/components/TableHead/tableHead.tsx +26 -15
  93. package/src/components/common/CustomIcon/components/CloseIcon/closeIcon.tsx +17 -0
  94. package/src/components/common/LoginDialog/constants.ts +33 -0
  95. package/src/components/common/LoginDialog/loginDialog.styles.ts +51 -0
  96. package/src/components/common/LoginDialog/loginDialog.tsx +56 -0
  97. package/src/components/common/LoginDialog/types.ts +4 -0
  98. package/src/components/common/Tabs/tabs.tsx +33 -3
  99. package/src/config/entities.ts +11 -1
  100. package/src/hooks/useCategoryFilter.ts +1 -0
  101. package/src/providers/config.tsx +10 -2
  102. package/src/providers/loginGuard/common/types.ts +21 -0
  103. package/src/providers/loginGuard/context.ts +12 -0
  104. package/src/providers/loginGuard/hook.ts +14 -0
  105. package/src/providers/loginGuard/provider.tsx +76 -0
  106. package/src/styles/common/mui/typography.ts +8 -0
  107. package/tests/dataDictionary_utils.test.ts +153 -0
  108. package/tests/provider.test.tsx +191 -0
  109. package/types/data-explorer-ui.d.ts +2 -0
@@ -0,0 +1,24 @@
1
+ import { Typography, TypographyProps } from "@mui/material";
2
+ import React from "react";
3
+ import { COLOR } from "../../../../../../styles/common/mui/typography";
4
+ import { TEXT_BODY_SMALL_400 } from "../../../../../../theme/common/typography";
5
+ import { BaseComponentProps } from "../../../../../types";
6
+
7
+ export const Warning = ({
8
+ children,
9
+ className,
10
+ ...props /* Mui TypographyOwnProps */
11
+ }: BaseComponentProps & TypographyProps): JSX.Element | null => {
12
+ if (!children) return null;
13
+ return (
14
+ <Typography
15
+ className={className}
16
+ color={COLOR.INK_LIGHT}
17
+ mt={6}
18
+ variant={TEXT_BODY_SMALL_400}
19
+ {...props}
20
+ >
21
+ {children}
22
+ </Typography>
23
+ );
24
+ };
@@ -0,0 +1,11 @@
1
+ import { ChangeEvent } from "react";
2
+
3
+ export interface UseUserConsent {
4
+ handleConsent: (e: ChangeEvent<HTMLInputElement>) => void;
5
+ handleError: (error: boolean) => void;
6
+ state: {
7
+ isDisabled: boolean;
8
+ isError: boolean;
9
+ isValid: boolean;
10
+ };
11
+ }
@@ -0,0 +1,32 @@
1
+ import { ChangeEvent, useCallback, useState } from "react";
2
+ import { useAuthenticationConfig } from "../../../../hooks/authentication/config/useAuthenticationConfig";
3
+ import { UseUserConsent } from "./types";
4
+
5
+ export const useUserConsent = (): UseUserConsent => {
6
+ const authConfig = useAuthenticationConfig();
7
+ const [isDisabled] = useState<boolean>(Boolean(!authConfig?.termsOfService));
8
+ const [isError, setIsError] = useState<boolean>(false);
9
+ const [isValid, setIsValid] = useState<boolean>(false);
10
+
11
+ const handleError = useCallback((error: boolean) => {
12
+ setIsError(error);
13
+ }, []);
14
+
15
+ const handleConsent = useCallback(
16
+ (changeEvent: ChangeEvent<HTMLInputElement>): void => {
17
+ handleError(false);
18
+ setIsValid(changeEvent.target.checked);
19
+ },
20
+ [handleError]
21
+ );
22
+
23
+ return {
24
+ handleConsent,
25
+ handleError,
26
+ state: {
27
+ isDisabled,
28
+ isError,
29
+ isValid,
30
+ },
31
+ };
32
+ };
@@ -0,0 +1,8 @@
1
+ import { ProviderId } from "../../../../providers/authentication/common/types";
2
+ import { UseUserConsent } from "../useUserConsent/types";
3
+
4
+ export interface UseUserLogin
5
+ extends Omit<UseUserConsent, "handleError" | "state"> {
6
+ consentState: Pick<UseUserConsent["state"], "isDisabled" | "isError">;
7
+ handleLogin: (providerId: ProviderId) => void;
8
+ }
@@ -0,0 +1,29 @@
1
+ import { useCallback } from "react";
2
+ import { useAuth } from "../../../../providers/authentication/auth/hook";
3
+ import { ProviderId } from "../../../../providers/authentication/common/types";
4
+ import { useUserConsent } from "../useUserConsent/useUserConsent";
5
+ import { UseUserLogin } from "./types";
6
+
7
+ export const useUserLogin = (): UseUserLogin => {
8
+ const { service: { requestLogin } = {} } = useAuth();
9
+ const { handleConsent, handleError, state: consentState } = useUserConsent();
10
+ const { isDisabled, isError, isValid } = consentState; // Consent state: { isValid } is an indicator of whether the user has accepted the login terms.
11
+
12
+ const handleLogin = useCallback(
13
+ (providerId: ProviderId): void => {
14
+ if (!isDisabled && !isValid) {
15
+ // If the user has not accepted terms, set error state to true.
16
+ handleError(true);
17
+ return;
18
+ }
19
+ requestLogin?.(providerId);
20
+ },
21
+ [handleError, isDisabled, isValid, requestLogin]
22
+ );
23
+
24
+ return {
25
+ consentState: { isDisabled, isError },
26
+ handleConsent,
27
+ handleLogin,
28
+ };
29
+ };
@@ -7,6 +7,7 @@ import {
7
7
  } from "@mui/material";
8
8
  import { flexRender, RowData } from "@tanstack/react-table";
9
9
  import React, { Fragment } from "react";
10
+ import { Tooltip } from "../../../DataDictionary/components/Tooltip/tooltip";
10
11
  import { ROW_DIRECTION } from "../../common/entities";
11
12
  import {
12
13
  getTableCellAlign,
@@ -28,27 +29,37 @@ export const TableHead = <T extends RowData>({
28
29
  <MTableRow>
29
30
  {headerGroup.headers.map(({ column, getContext, id }) => {
30
31
  const { columnDef, getIsGrouped, getIsSorted } = column;
32
+ const annotation = columnDef.meta?.annotation;
31
33
  return getIsGrouped() ? null : (
32
34
  <TableCell
33
35
  key={id}
34
36
  align={getTableCellAlign(column)}
35
37
  padding={getTableCellPadding(id)}
36
38
  >
37
- {shouldSortColumn(tableInstance, column) ? (
38
- <TableSortLabel
39
- IconComponent={SouthRoundedIcon}
40
- active={Boolean(getIsSorted())}
41
- direction={getIsSorted() || undefined}
42
- disabled={isSortDisabled(tableInstance)}
43
- onClick={(mouseEvent) =>
44
- handleToggleSorting(mouseEvent, tableInstance, column)
45
- }
46
- >
47
- {flexRender(columnDef.header, getContext())}
48
- </TableSortLabel>
49
- ) : (
50
- flexRender(columnDef.header, getContext())
51
- )}
39
+ <Tooltip
40
+ description={annotation?.description}
41
+ title={annotation?.label}
42
+ >
43
+ {shouldSortColumn(tableInstance, column) ? (
44
+ <TableSortLabel
45
+ IconComponent={SouthRoundedIcon}
46
+ active={Boolean(getIsSorted())}
47
+ direction={getIsSorted() || undefined}
48
+ disabled={isSortDisabled(tableInstance)}
49
+ onClick={(mouseEvent) =>
50
+ handleToggleSorting(
51
+ mouseEvent,
52
+ tableInstance,
53
+ column
54
+ )
55
+ }
56
+ >
57
+ {flexRender(columnDef.header, getContext())}
58
+ </TableSortLabel>
59
+ ) : (
60
+ flexRender(columnDef.header, getContext())
61
+ )}
62
+ </Tooltip>
52
63
  </TableCell>
53
64
  );
54
65
  })}
@@ -0,0 +1,17 @@
1
+ import { SvgIcon, SvgIconProps } from "@mui/material";
2
+ import React from "react";
3
+
4
+ export const CloseIcon = ({
5
+ fontSize = "xsmall",
6
+ viewBox = "0 0 18 18",
7
+ ...props /* Spread props to allow for Mui SvgIconProps specific prop overrides e.g. "htmlColor". */
8
+ }: SvgIconProps): JSX.Element => {
9
+ return (
10
+ <SvgIcon fontSize={fontSize} viewBox={viewBox} {...props}>
11
+ <path
12
+ d="M8.99994 10.1061L5.38104 13.725C5.23104 13.875 5.04984 13.947 4.83744 13.941C4.62504 13.9344 4.44384 13.8561 4.29384 13.7061C4.14384 13.5561 4.06884 13.3719 4.06884 13.1535C4.06884 12.9345 4.14384 12.75 4.29384 12.6L7.89384 9.00005L4.27494 5.38115C4.12494 5.23115 4.05294 5.04695 4.05894 4.82855C4.06554 4.60955 4.14384 4.42505 4.29384 4.27505C4.44384 4.12505 4.62804 4.05005 4.84644 4.05005C5.06544 4.05005 5.24994 4.12505 5.39994 4.27505L8.99994 7.89395L12.6188 4.27505C12.7688 4.12505 12.953 4.05005 13.1714 4.05005C13.3904 4.05005 13.5749 4.12505 13.7249 4.27505C13.8749 4.42505 13.9499 4.60955 13.9499 4.82855C13.9499 5.04695 13.8749 5.23115 13.7249 5.38115L10.106 9.00005L13.7249 12.6189C13.8749 12.7689 13.9499 12.9501 13.9499 13.1625C13.9499 13.3749 13.8749 13.5561 13.7249 13.7061C13.5749 13.8561 13.3904 13.9311 13.1714 13.9311C12.953 13.9311 12.7688 13.8561 12.6188 13.7061L8.99994 10.1061Z"
13
+ fill="currentColor"
14
+ />
15
+ </SvgIcon>
16
+ );
17
+ };
@@ -0,0 +1,33 @@
1
+ import {
2
+ DialogContentTextProps,
3
+ DialogProps,
4
+ DialogTitleProps,
5
+ IconButtonProps,
6
+ IconProps,
7
+ } from "@mui/material";
8
+ import { FONT_SIZE } from "../../../styles/common/mui/icon";
9
+ import { COLOR, VARIANT } from "../../../styles/common/mui/typography";
10
+
11
+ export const DIALOG_CONTENT_TEXT_PROPS: DialogContentTextProps = {
12
+ color: COLOR.INK_LIGHT,
13
+ component: "div",
14
+ variant: VARIANT.TEXT_BODY_400,
15
+ };
16
+
17
+ export const DIALOG_PROPS: Partial<DialogProps> = {
18
+ PaperProps: { elevation: 0 },
19
+ };
20
+
21
+ export const DIALOG_TITLE_PROPS: DialogTitleProps = {
22
+ variant: VARIANT.TEXT_HEADING_SMALL,
23
+ };
24
+
25
+ export const ICON_BUTTON_PROPS: IconButtonProps = {
26
+ color: "inkLight",
27
+ edge: "end",
28
+ size: "xsmall",
29
+ };
30
+
31
+ export const ICON_PROPS: Pick<IconProps, "fontSize"> = {
32
+ fontSize: FONT_SIZE.SMALL,
33
+ };
@@ -0,0 +1,51 @@
1
+ import styled from "@emotion/styled";
2
+ import { Dialog } from "@mui/material";
3
+ import { inkMain } from "../../../styles/common/mixins/colors";
4
+ import { alpha80 } from "../../../theme/common/palette";
5
+
6
+ export const StyledDialog = styled(Dialog)`
7
+ &.MuiDialog-root {
8
+ .MuiBackdrop-root {
9
+ background-color: ${inkMain}${alpha80};
10
+ }
11
+
12
+ .MuiDialog-paper {
13
+ border-radius: 8px;
14
+ max-width: 400px;
15
+ padding: 32px;
16
+ position: relative; /* positions close icon */
17
+
18
+ .MuiDialogTitle-root,
19
+ .MuiDialogContent-root,
20
+ .MuiDialogActions-root {
21
+ padding: 0;
22
+ }
23
+
24
+ .MuiDialogTitle-root {
25
+ font-size: 20px;
26
+
27
+ .MuiIconButton-root {
28
+ position: absolute;
29
+ right: 12px;
30
+ top: 12px;
31
+ }
32
+ }
33
+
34
+ .MuiDialogContent-root {
35
+ .MuiDialogContentText-root {
36
+ margin: 8px 0;
37
+ }
38
+
39
+ .MuiGrid2-root {
40
+ margin: 24px 0;
41
+ }
42
+ }
43
+
44
+ .MuiDialogActions-root {
45
+ display: flex;
46
+ flex-direction: column;
47
+ gap: 16px 0;
48
+ }
49
+ }
50
+ }
51
+ `;
@@ -0,0 +1,56 @@
1
+ import {
2
+ DialogActions,
3
+ DialogContent,
4
+ DialogContentText,
5
+ DialogTitle,
6
+ IconButton,
7
+ } from "@mui/material";
8
+ import React from "react";
9
+ import { useAuthenticationConfig } from "../../../hooks/authentication/config/useAuthenticationConfig";
10
+ import { Buttons } from "../../Login/components/Buttons/buttons";
11
+ import { Consent } from "../../Login/components/Section/components/Consent/consent";
12
+ import { Warning } from "../../Login/components/Section/components/Warning/warning";
13
+ import { useUserLogin } from "../../Login/hooks/useUserLogin/useUserLogin";
14
+ import { CloseIcon } from "../CustomIcon/components/CloseIcon/closeIcon";
15
+ import {
16
+ DIALOG_CONTENT_TEXT_PROPS,
17
+ DIALOG_PROPS,
18
+ DIALOG_TITLE_PROPS,
19
+ ICON_BUTTON_PROPS,
20
+ ICON_PROPS,
21
+ } from "./constants";
22
+ import { StyledDialog } from "./loginDialog.styles";
23
+ import { LoginDialogProps } from "./types";
24
+
25
+ export const LoginDialog = ({
26
+ onClose,
27
+ open,
28
+ }: LoginDialogProps): JSX.Element | null => {
29
+ const authConfig = useAuthenticationConfig();
30
+ const { consentState, handleConsent, handleLogin } = useUserLogin();
31
+
32
+ if (!authConfig) return null;
33
+
34
+ return (
35
+ <StyledDialog {...DIALOG_PROPS} onClose={onClose} open={open}>
36
+ <DialogTitle {...DIALOG_TITLE_PROPS}>
37
+ <div>Sign In Required</div>
38
+ <IconButton {...ICON_BUTTON_PROPS} onClick={onClose}>
39
+ <CloseIcon {...ICON_PROPS} />
40
+ </IconButton>
41
+ </DialogTitle>
42
+ <DialogContent>
43
+ <DialogContentText {...DIALOG_CONTENT_TEXT_PROPS}>
44
+ Please sign in to proceed with this action.
45
+ </DialogContentText>
46
+ <Consent handleConsent={handleConsent} {...consentState}>
47
+ {authConfig.termsOfService}
48
+ </Consent>
49
+ </DialogContent>
50
+ <DialogActions disableSpacing>
51
+ <Buttons handleLogin={handleLogin} providers={authConfig.providers} />
52
+ </DialogActions>
53
+ <Warning>{authConfig.warning}</Warning>
54
+ </StyledDialog>
55
+ );
56
+ };
@@ -0,0 +1,4 @@
1
+ export interface LoginDialogProps {
2
+ onClose: () => void;
3
+ open: boolean;
4
+ }
@@ -3,7 +3,9 @@ import {
3
3
  Tabs as MTabs,
4
4
  TabsProps as MTabsProps,
5
5
  } from "@mui/material";
6
- import React, { ReactNode } from "react";
6
+ import React, { ReactElement, ReactNode } from "react";
7
+ import { DataDictionaryAnnotation } from "../../../common/entities";
8
+ import { Tooltip } from "../../DataDictionary/components/Tooltip/tooltip";
7
9
  import { Tab, TabScrollFuzz } from "./tabs.styles";
8
10
 
9
11
  export type TabsValue = MTabsProps["value"]; // any
@@ -11,6 +13,7 @@ export type TabValue = MTabProps["value"]; // any
11
13
  export type OnTabChangeFn = (tabValue: TabValue) => void; // Function invoked when selected tab value changes.
12
14
 
13
15
  export interface Tab {
16
+ annotation?: DataDictionaryAnnotation;
14
17
  count?: string;
15
18
  icon?: MTabProps["icon"]; // element or string
16
19
  iconPosition?: MTabProps["iconPosition"]; // "bottom" or "end" or "start" or "top
@@ -41,14 +44,21 @@ export const Tabs = ({
41
44
  >
42
45
  {tabs.map(
43
46
  (
44
- { count, icon, iconPosition = "start", label, value: tabValue },
47
+ {
48
+ annotation,
49
+ count,
50
+ icon,
51
+ iconPosition = "start",
52
+ label,
53
+ value: tabValue,
54
+ },
45
55
  t
46
56
  ) => (
47
57
  <Tab
48
58
  icon={icon}
49
59
  iconPosition={icon ? iconPosition : undefined}
50
60
  key={`${label}${t}`}
51
- label={count ? `${label} (${count})` : label}
61
+ label={buildTabLabel(label, count, annotation)}
52
62
  value={tabValue}
53
63
  />
54
64
  )
@@ -56,3 +66,23 @@ export const Tabs = ({
56
66
  </MTabs>
57
67
  );
58
68
  };
69
+
70
+ /**
71
+ * Build a tab value from a tab config. Specifically, display the tab label
72
+ * with a tooltip annotation if necessary.
73
+ * @param label - Tab display value.
74
+ * @param count - Optional count to display next to the tab label.
75
+ * @param annotation - Data dictionary annotation.
76
+ * @returns Tab label with optional count and tooltip.
77
+ */
78
+ function buildTabLabel(
79
+ label: ReactNode,
80
+ count?: string,
81
+ annotation?: DataDictionaryAnnotation
82
+ ): ReactElement {
83
+ return (
84
+ <Tooltip description={annotation?.description} title={annotation?.label}>
85
+ <span>{count ? `${label} (${count})` : label}</span>
86
+ </Tooltip>
87
+ );
88
+ }
@@ -10,7 +10,12 @@ import {
10
10
  TableOptions,
11
11
  } from "@tanstack/react-table";
12
12
  import { JSXElementConstructor, ReactNode } from "react";
13
- import { SelectCategoryValueView, SelectedFilter } from "../common/entities";
13
+ import {
14
+ DataDictionary,
15
+ DataDictionaryAnnotation,
16
+ SelectCategoryValueView,
17
+ SelectedFilter,
18
+ } from "../common/entities";
14
19
  import { HeroTitle } from "../components/common/Title/title";
15
20
  import { FooterProps } from "../components/Layout/components/Footer/footer";
16
21
  import { HeaderProps } from "../components/Layout/components/Header/header";
@@ -87,6 +92,7 @@ export interface CategoryGroup {
87
92
  * Model of category configured in site config.
88
93
  */
89
94
  export interface CategoryConfig {
95
+ annotation?: DataDictionaryAnnotation;
90
96
  key: string;
91
97
  label: string;
92
98
  mapSelectCategoryValue?: (
@@ -170,6 +176,7 @@ export type EntityPath = string;
170
176
  */
171
177
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This config model is part of a generic array
172
178
  export interface EntityConfig<T = any, I = any> extends TabConfig {
179
+ annotation?: DataDictionaryAnnotation;
173
180
  apiPath?: EntityPath;
174
181
  categoryGroupConfig?: CategoryGroupConfig;
175
182
  detail: BackPageConfig;
@@ -180,6 +187,7 @@ export interface EntityConfig<T = any, I = any> extends TabConfig {
180
187
  getId?: GetIdFunction<T>;
181
188
  getTitle?: GetTitleFunction<T>;
182
189
  hideTabs?: boolean;
190
+ key?: string; // Optional data dictionary key
183
191
  list: ListConfig<T>;
184
192
  listView?: ListViewConfig;
185
193
  options?: Options;
@@ -366,10 +374,12 @@ export interface SiteConfig {
366
374
  categoryGroupConfig?: CategoryGroupConfig;
367
375
  contentDir?: string;
368
376
  contentThemeOptionsFn?: ThemeOptionsFn;
377
+ dataDictionary?: DataDictionary;
369
378
  dataSource: DataSourceConfig;
370
379
  entities: EntityConfig[];
371
380
  explorerTitle: HeroTitle;
372
381
  export?: ExportConfig;
382
+ exportsRequireAuth?: boolean;
373
383
  exportToTerraUrl?: string; // TODO(cc) revist location; possibly nest inside "export"?
374
384
  gitHubUrl?: string;
375
385
  layout: {
@@ -77,6 +77,7 @@ function buildCategoryView(
77
77
  const mapSelectCategoryValue =
78
78
  categoryConfig?.mapSelectCategoryValue || getSelectCategoryValue;
79
79
  return {
80
+ annotation: categoryConfig?.annotation,
80
81
  isDisabled: false,
81
82
  key: category.key,
82
83
  label: getCategoryLabel(category.key, categoryConfig),
@@ -1,4 +1,5 @@
1
- import React, { createContext, ReactNode } from "react";
1
+ import React, { createContext, ReactNode, useState } from "react";
2
+ import { annotateSiteConfig } from "../components/DataDictionary/common/utils";
2
3
  import { EntityConfig, SiteConfig } from "../config/entities";
3
4
  import {
4
5
  getDefaultConfig,
@@ -31,7 +32,14 @@ export function ConfigProvider({
31
32
  config,
32
33
  entityListType = "",
33
34
  }: ConfigProps): JSX.Element {
34
- const { entities } = config;
35
+ // Annote config on init. Note config is mutated but using state here to
36
+ // ensure annotated config is calculated once and is used rather than the raw config.
37
+ const [annotatedConfig] = useState(() => {
38
+ annotateSiteConfig(config);
39
+ return config;
40
+ });
41
+
42
+ const { entities } = annotatedConfig;
35
43
  const defaultEntityListType = config.redirectRootToPath.slice(1);
36
44
  const entityName = entityListType || defaultEntityListType;
37
45
  const entityConfig = getEntityConfig(entities, entityName);
@@ -0,0 +1,21 @@
1
+ import { ReactNode } from "react";
2
+
3
+ /**
4
+ * A callback function to be stored and then executed upon successful login.
5
+ */
6
+ export type LoginGuardCallback = () => void;
7
+
8
+ /**
9
+ * The shape of the LoginGuard context, provides a function to trigger the
10
+ * login process.
11
+ */
12
+ export interface LoginGuardContextProps {
13
+ requireLogin: (callback?: LoginGuardCallback) => void;
14
+ }
15
+
16
+ /**
17
+ * The properties for the LoginGuardProvider component.
18
+ */
19
+ export interface LoginGuardProviderProps {
20
+ children: ReactNode;
21
+ }
@@ -0,0 +1,12 @@
1
+ import { createContext } from "react";
2
+ import { LoginGuardCallback, LoginGuardContextProps } from "./common/types";
3
+
4
+ /**
5
+ * LoginGuardContext provides a way to trigger a login process. Default value is to
6
+ * call the callback immediately, if specified.
7
+ */
8
+ export const LoginGuardContext = createContext<LoginGuardContextProps>({
9
+ requireLogin: (callback?: LoginGuardCallback) => {
10
+ callback?.();
11
+ },
12
+ });
@@ -0,0 +1,14 @@
1
+ import { useContext } from "react";
2
+ import { LoginGuardContextProps } from "./common/types";
3
+ import { LoginGuardContext } from "./context";
4
+
5
+ /**
6
+ * Custom hook to access the LoginGuard context. This hook returns an object
7
+ * containing the "requireLogin" function, which allows triggering the application's
8
+ * login process.
9
+ *
10
+ * @returns The current LoginGuard context value.
11
+ */
12
+ export function useLoginGuard(): LoginGuardContextProps {
13
+ return useContext<LoginGuardContextProps>(LoginGuardContext);
14
+ }
@@ -0,0 +1,76 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from "react";
2
+ import { LoginDialog } from "../../components/common/LoginDialog/loginDialog";
3
+ import { useAuthenticationConfig } from "../../hooks/authentication/config/useAuthenticationConfig";
4
+ import { useConfig } from "../../hooks/useConfig";
5
+ import { useAuth } from "../authentication/auth/hook";
6
+ import { LoginGuardCallback, LoginGuardProviderProps } from "./common/types";
7
+ import { LoginGuardContext } from "./context";
8
+
9
+ /**
10
+ * LoginGuardProvider is responsible for intercepting actions that require user authentication.
11
+ * It provides a "requireLogin" function via context. When a protected action is triggered while the
12
+ * user is unauthenticated, the LoginDialog is displayed. Upon successful authentication, the saved
13
+ * callback is invoked.
14
+ *
15
+ * @param {LoginGuardProviderProps} props - The provider props that include children.
16
+ * @returns The provider component.
17
+ */
18
+ export function LoginGuardProvider({
19
+ children,
20
+ }: LoginGuardProviderProps): JSX.Element {
21
+ // Dialog open state.
22
+ const [open, setOpen] = useState(false);
23
+
24
+ // Use ref to store the callback without triggering re-render.
25
+ const callbackRef = useRef<LoginGuardCallback | undefined>(undefined);
26
+
27
+ // Determine if authentication is enabled.
28
+ const authConfig = useAuthenticationConfig();
29
+
30
+ // Determine if authentication is required for downloads and exports.
31
+ const {
32
+ config: { exportsRequireAuth },
33
+ } = useConfig();
34
+
35
+ // Get the user's authenticated state.
36
+ const {
37
+ authState: { isAuthenticated },
38
+ } = useAuth();
39
+
40
+ // If the user authenticates, close dialog then fire and clear callback.
41
+ useEffect(() => {
42
+ if (isAuthenticated) {
43
+ setOpen(false);
44
+ callbackRef.current?.();
45
+ // Clear callback after firing.
46
+ callbackRef.current = undefined;
47
+ }
48
+ }, [isAuthenticated]);
49
+
50
+ // Handler to close the dialog.
51
+ const onClose = useCallback(() => {
52
+ setOpen(false);
53
+ // Clear any stored callback.
54
+ callbackRef.current = undefined;
55
+ }, []);
56
+
57
+ // Block actions that require authentication, or fire callback if already authenticated.
58
+ const requireLogin = useCallback(
59
+ (cb?: LoginGuardCallback) => {
60
+ if (authConfig && exportsRequireAuth && !isAuthenticated) {
61
+ callbackRef.current = cb;
62
+ setOpen(true);
63
+ } else {
64
+ cb?.();
65
+ }
66
+ },
67
+ [authConfig, exportsRequireAuth, isAuthenticated]
68
+ );
69
+
70
+ return (
71
+ <LoginGuardContext.Provider value={{ requireLogin }}>
72
+ {children}
73
+ <LoginDialog open={open} onClose={onClose} />
74
+ </LoginGuardContext.Provider>
75
+ );
76
+ }
@@ -1,5 +1,13 @@
1
1
  import { TypographyOwnProps } from "@mui/material";
2
2
 
3
+ export const COLOR: Record<string, TypographyOwnProps["color"]> = {
4
+ INHERIT: "inherit",
5
+ INK_LIGHT: "ink.light",
6
+ INK_MAIN: "ink.main",
7
+ };
8
+
3
9
  export const VARIANT: Record<string, TypographyOwnProps["variant"]> = {
4
10
  INHERIT: "inherit",
11
+ TEXT_BODY_400: "text-body-400",
12
+ TEXT_HEADING_SMALL: "text-heading-small",
5
13
  };