@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,6 @@
1
+ import { Grid2Props } from "@mui/material";
2
+ import { ReactNode } from "react";
3
+ import { UseUserConsent } from "../../../../hooks/useUserConsent/types";
4
+ export interface ConsentProps extends Grid2Props, Pick<UseUserConsent, "handleConsent">, Pick<UseUserConsent["state"], "isDisabled" | "isError"> {
5
+ children: ReactNode;
6
+ }
@@ -0,0 +1,3 @@
1
+ import { TypographyProps } from "@mui/material";
2
+ import { BaseComponentProps } from "../../../../../types";
3
+ export declare const Warning: ({ children, className, ...props }: BaseComponentProps & TypographyProps) => JSX.Element | null;
@@ -0,0 +1,9 @@
1
+ import { Typography } 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
+ export const Warning = ({ children, className, ...props /* Mui TypographyOwnProps */ }) => {
6
+ if (!children)
7
+ return null;
8
+ return (React.createElement(Typography, { className: className, color: COLOR.INK_LIGHT, mt: 6, variant: TEXT_BODY_SMALL_400, ...props }, children));
9
+ };
@@ -0,0 +1,10 @@
1
+ import { ChangeEvent } from "react";
2
+ export interface UseUserConsent {
3
+ handleConsent: (e: ChangeEvent<HTMLInputElement>) => void;
4
+ handleError: (error: boolean) => void;
5
+ state: {
6
+ isDisabled: boolean;
7
+ isError: boolean;
8
+ isValid: boolean;
9
+ };
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { UseUserConsent } from "./types";
2
+ export declare const useUserConsent: () => UseUserConsent;
@@ -0,0 +1,24 @@
1
+ import { useCallback, useState } from "react";
2
+ import { useAuthenticationConfig } from "../../../../hooks/authentication/config/useAuthenticationConfig";
3
+ export const useUserConsent = () => {
4
+ const authConfig = useAuthenticationConfig();
5
+ const [isDisabled] = useState(Boolean(!authConfig?.termsOfService));
6
+ const [isError, setIsError] = useState(false);
7
+ const [isValid, setIsValid] = useState(false);
8
+ const handleError = useCallback((error) => {
9
+ setIsError(error);
10
+ }, []);
11
+ const handleConsent = useCallback((changeEvent) => {
12
+ handleError(false);
13
+ setIsValid(changeEvent.target.checked);
14
+ }, [handleError]);
15
+ return {
16
+ handleConsent,
17
+ handleError,
18
+ state: {
19
+ isDisabled,
20
+ isError,
21
+ isValid,
22
+ },
23
+ };
24
+ };
@@ -0,0 +1,6 @@
1
+ import { ProviderId } from "../../../../providers/authentication/common/types";
2
+ import { UseUserConsent } from "../useUserConsent/types";
3
+ export interface UseUserLogin extends Omit<UseUserConsent, "handleError" | "state"> {
4
+ consentState: Pick<UseUserConsent["state"], "isDisabled" | "isError">;
5
+ handleLogin: (providerId: ProviderId) => void;
6
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { UseUserLogin } from "./types";
2
+ export declare const useUserLogin: () => UseUserLogin;
@@ -0,0 +1,21 @@
1
+ import { useCallback } from "react";
2
+ import { useAuth } from "../../../../providers/authentication/auth/hook";
3
+ import { useUserConsent } from "../useUserConsent/useUserConsent";
4
+ export const useUserLogin = () => {
5
+ const { service: { requestLogin } = {} } = useAuth();
6
+ const { handleConsent, handleError, state: consentState } = useUserConsent();
7
+ const { isDisabled, isError, isValid } = consentState; // Consent state: { isValid } is an indicator of whether the user has accepted the login terms.
8
+ const handleLogin = useCallback((providerId) => {
9
+ if (!isDisabled && !isValid) {
10
+ // If the user has not accepted terms, set error state to true.
11
+ handleError(true);
12
+ return;
13
+ }
14
+ requestLogin?.(providerId);
15
+ }, [handleError, isDisabled, isValid, requestLogin]);
16
+ return {
17
+ consentState: { isDisabled, isError },
18
+ handleConsent,
19
+ handleLogin,
20
+ };
21
+ };
@@ -2,6 +2,7 @@ import SouthRoundedIcon from "@mui/icons-material/SouthRounded";
2
2
  import { TableHead as MTableHead, TableRow as MTableRow, TableCell, TableSortLabel, } from "@mui/material";
3
3
  import { flexRender } from "@tanstack/react-table";
4
4
  import React, { Fragment } from "react";
5
+ import { Tooltip } from "../../../DataDictionary/components/Tooltip/tooltip";
5
6
  import { ROW_DIRECTION } from "../../common/entities";
6
7
  import { getTableCellAlign, getTableCellPadding, } from "../TableCell/common/utils";
7
8
  import { handleToggleSorting } from "../TableFeatures/RowSorting/utils";
@@ -11,6 +12,8 @@ export const TableHead = ({ rowDirection, tableInstance, }) => {
11
12
  tableInstance.getHeaderGroups().map((headerGroup) => (React.createElement(MTableHead, { key: headerGroup.id },
12
13
  React.createElement(MTableRow, null, headerGroup.headers.map(({ column, getContext, id }) => {
13
14
  const { columnDef, getIsGrouped, getIsSorted } = column;
14
- return getIsGrouped() ? null : (React.createElement(TableCell, { key: id, align: getTableCellAlign(column), padding: getTableCellPadding(id) }, shouldSortColumn(tableInstance, column) ? (React.createElement(TableSortLabel, { IconComponent: SouthRoundedIcon, active: Boolean(getIsSorted()), direction: getIsSorted() || undefined, disabled: isSortDisabled(tableInstance), onClick: (mouseEvent) => handleToggleSorting(mouseEvent, tableInstance, column) }, flexRender(columnDef.header, getContext()))) : (flexRender(columnDef.header, getContext()))));
15
+ const annotation = columnDef.meta?.annotation;
16
+ return getIsGrouped() ? null : (React.createElement(TableCell, { key: id, align: getTableCellAlign(column), padding: getTableCellPadding(id) },
17
+ React.createElement(Tooltip, { description: annotation?.description, title: annotation?.label }, shouldSortColumn(tableInstance, column) ? (React.createElement(TableSortLabel, { IconComponent: SouthRoundedIcon, active: Boolean(getIsSorted()), direction: getIsSorted() || undefined, disabled: isSortDisabled(tableInstance), onClick: (mouseEvent) => handleToggleSorting(mouseEvent, tableInstance, column) }, flexRender(columnDef.header, getContext()))) : (flexRender(columnDef.header, getContext())))));
15
18
  })))))));
16
19
  };
@@ -0,0 +1,2 @@
1
+ import { SvgIconProps } from "@mui/material";
2
+ export declare const CloseIcon: ({ fontSize, viewBox, ...props }: SvgIconProps) => JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { SvgIcon } from "@mui/material";
2
+ import React from "react";
3
+ export const CloseIcon = ({ fontSize = "xsmall", viewBox = "0 0 18 18", ...props /* Spread props to allow for Mui SvgIconProps specific prop overrides e.g. "htmlColor". */ }) => {
4
+ return (React.createElement(SvgIcon, { fontSize: fontSize, viewBox: viewBox, ...props },
5
+ React.createElement("path", { 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", fill: "currentColor" })));
6
+ };
@@ -0,0 +1,6 @@
1
+ import { DialogContentTextProps, DialogProps, DialogTitleProps, IconButtonProps, IconProps } from "@mui/material";
2
+ export declare const DIALOG_CONTENT_TEXT_PROPS: DialogContentTextProps;
3
+ export declare const DIALOG_PROPS: Partial<DialogProps>;
4
+ export declare const DIALOG_TITLE_PROPS: DialogTitleProps;
5
+ export declare const ICON_BUTTON_PROPS: IconButtonProps;
6
+ export declare const ICON_PROPS: Pick<IconProps, "fontSize">;
@@ -0,0 +1,21 @@
1
+ import { FONT_SIZE } from "../../../styles/common/mui/icon";
2
+ import { COLOR, VARIANT } from "../../../styles/common/mui/typography";
3
+ export const DIALOG_CONTENT_TEXT_PROPS = {
4
+ color: COLOR.INK_LIGHT,
5
+ component: "div",
6
+ variant: VARIANT.TEXT_BODY_400,
7
+ };
8
+ export const DIALOG_PROPS = {
9
+ PaperProps: { elevation: 0 },
10
+ };
11
+ export const DIALOG_TITLE_PROPS = {
12
+ variant: VARIANT.TEXT_HEADING_SMALL,
13
+ };
14
+ export const ICON_BUTTON_PROPS = {
15
+ color: "inkLight",
16
+ edge: "end",
17
+ size: "xsmall",
18
+ };
19
+ export const ICON_PROPS = {
20
+ fontSize: FONT_SIZE.SMALL,
21
+ };
@@ -0,0 +1,2 @@
1
+ import { LoginDialogProps } from "./types";
2
+ export declare const LoginDialog: ({ onClose, open, }: LoginDialogProps) => JSX.Element | null;
@@ -0,0 +1,27 @@
1
+ import { DialogActions, DialogContent, DialogContentText, DialogTitle, IconButton, } from "@mui/material";
2
+ import React from "react";
3
+ import { useAuthenticationConfig } from "../../../hooks/authentication/config/useAuthenticationConfig";
4
+ import { Buttons } from "../../Login/components/Buttons/buttons";
5
+ import { Consent } from "../../Login/components/Section/components/Consent/consent";
6
+ import { Warning } from "../../Login/components/Section/components/Warning/warning";
7
+ import { useUserLogin } from "../../Login/hooks/useUserLogin/useUserLogin";
8
+ import { CloseIcon } from "../CustomIcon/components/CloseIcon/closeIcon";
9
+ import { DIALOG_CONTENT_TEXT_PROPS, DIALOG_PROPS, DIALOG_TITLE_PROPS, ICON_BUTTON_PROPS, ICON_PROPS, } from "./constants";
10
+ import { StyledDialog } from "./loginDialog.styles";
11
+ export const LoginDialog = ({ onClose, open, }) => {
12
+ const authConfig = useAuthenticationConfig();
13
+ const { consentState, handleConsent, handleLogin } = useUserLogin();
14
+ if (!authConfig)
15
+ return null;
16
+ return (React.createElement(StyledDialog, { ...DIALOG_PROPS, onClose: onClose, open: open },
17
+ React.createElement(DialogTitle, { ...DIALOG_TITLE_PROPS },
18
+ React.createElement("div", null, "Sign In Required"),
19
+ React.createElement(IconButton, { ...ICON_BUTTON_PROPS, onClick: onClose },
20
+ React.createElement(CloseIcon, { ...ICON_PROPS }))),
21
+ React.createElement(DialogContent, null,
22
+ React.createElement(DialogContentText, { ...DIALOG_CONTENT_TEXT_PROPS }, "Please sign in to proceed with this action."),
23
+ React.createElement(Consent, { handleConsent: handleConsent, ...consentState }, authConfig.termsOfService)),
24
+ React.createElement(DialogActions, { disableSpacing: true },
25
+ React.createElement(Buttons, { handleLogin: handleLogin, providers: authConfig.providers })),
26
+ React.createElement(Warning, null, authConfig.warning)));
27
+ };
@@ -0,0 +1,3 @@
1
+ export declare const StyledDialog: import("@emotion/styled").StyledComponent<import("@mui/material").DialogProps & {
2
+ theme?: import("@emotion/react").Theme;
3
+ }, {}, {}>;
@@ -0,0 +1,50 @@
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
+ export const StyledDialog = styled(Dialog) `
6
+ &.MuiDialog-root {
7
+ .MuiBackdrop-root {
8
+ background-color: ${inkMain}${alpha80};
9
+ }
10
+
11
+ .MuiDialog-paper {
12
+ border-radius: 8px;
13
+ max-width: 400px;
14
+ padding: 32px;
15
+ position: relative; /* positions close icon */
16
+
17
+ .MuiDialogTitle-root,
18
+ .MuiDialogContent-root,
19
+ .MuiDialogActions-root {
20
+ padding: 0;
21
+ }
22
+
23
+ .MuiDialogTitle-root {
24
+ font-size: 20px;
25
+
26
+ .MuiIconButton-root {
27
+ position: absolute;
28
+ right: 12px;
29
+ top: 12px;
30
+ }
31
+ }
32
+
33
+ .MuiDialogContent-root {
34
+ .MuiDialogContentText-root {
35
+ margin: 8px 0;
36
+ }
37
+
38
+ .MuiGrid2-root {
39
+ margin: 24px 0;
40
+ }
41
+ }
42
+
43
+ .MuiDialogActions-root {
44
+ display: flex;
45
+ flex-direction: column;
46
+ gap: 16px 0;
47
+ }
48
+ }
49
+ }
50
+ `;
@@ -0,0 +1,4 @@
1
+ export interface LoginDialogProps {
2
+ onClose: () => void;
3
+ open: boolean;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,9 +1,11 @@
1
1
  import { TabProps as MTabProps, TabsProps as MTabsProps } from "@mui/material";
2
2
  import { ReactNode } from "react";
3
+ import { DataDictionaryAnnotation } from "../../../common/entities";
3
4
  export type TabsValue = MTabsProps["value"];
4
5
  export type TabValue = MTabProps["value"];
5
6
  export type OnTabChangeFn = (tabValue: TabValue) => void;
6
7
  export interface Tab {
8
+ annotation?: DataDictionaryAnnotation;
7
9
  count?: string;
8
10
  icon?: MTabProps["icon"];
9
11
  iconPosition?: MTabProps["iconPosition"];
@@ -1,6 +1,19 @@
1
1
  import { Tabs as MTabs, } from "@mui/material";
2
2
  import React from "react";
3
+ import { Tooltip } from "../../DataDictionary/components/Tooltip/tooltip";
3
4
  import { Tab, TabScrollFuzz } from "./tabs.styles";
4
5
  export const Tabs = ({ className, onTabChange, tabs, value, }) => {
5
- return (React.createElement(MTabs, { allowScrollButtonsMobile: true, className: className, onChange: (_, tabValue) => onTabChange(tabValue), ScrollButtonComponent: TabScrollFuzz, value: value }, tabs.map(({ count, icon, iconPosition = "start", label, value: tabValue }, t) => (React.createElement(Tab, { icon: icon, iconPosition: icon ? iconPosition : undefined, key: `${label}${t}`, label: count ? `${label} (${count})` : label, value: tabValue })))));
6
+ return (React.createElement(MTabs, { allowScrollButtonsMobile: true, className: className, onChange: (_, tabValue) => onTabChange(tabValue), ScrollButtonComponent: TabScrollFuzz, value: value }, tabs.map(({ annotation, count, icon, iconPosition = "start", label, value: tabValue, }, t) => (React.createElement(Tab, { icon: icon, iconPosition: icon ? iconPosition : undefined, key: `${label}${t}`, label: buildTabLabel(label, count, annotation), value: tabValue })))));
6
7
  };
8
+ /**
9
+ * Build a tab value from a tab config. Specifically, display the tab label
10
+ * with a tooltip annotation if necessary.
11
+ * @param label - Tab display value.
12
+ * @param count - Optional count to display next to the tab label.
13
+ * @param annotation - Data dictionary annotation.
14
+ * @returns Tab label with optional count and tooltip.
15
+ */
16
+ function buildTabLabel(label, count, annotation) {
17
+ return (React.createElement(Tooltip, { description: annotation?.description, title: annotation?.label },
18
+ React.createElement("span", null, count ? `${label} (${count})` : label)));
19
+ }
@@ -1,7 +1,7 @@
1
1
  import { TabProps as MTabProps, Theme, ThemeOptions } from "@mui/material";
2
2
  import { CellContext, ColumnDef, ColumnMeta, ColumnSort, GroupingState, RowData, Table, TableOptions } from "@tanstack/react-table";
3
3
  import { JSXElementConstructor, ReactNode } from "react";
4
- import { SelectCategoryValueView, SelectedFilter } from "../common/entities";
4
+ import { DataDictionary, DataDictionaryAnnotation, SelectCategoryValueView, SelectedFilter } from "../common/entities";
5
5
  import { HeroTitle } from "../components/common/Title/title";
6
6
  import { FooterProps } from "../components/Layout/components/Footer/footer";
7
7
  import { HeaderProps } from "../components/Layout/components/Header/header";
@@ -70,6 +70,7 @@ export interface CategoryGroup {
70
70
  * Model of category configured in site config.
71
71
  */
72
72
  export interface CategoryConfig {
73
+ annotation?: DataDictionaryAnnotation;
73
74
  key: string;
74
75
  label: string;
75
76
  mapSelectCategoryValue?: (selectCategoryValue: SelectCategoryValueView) => SelectCategoryValueView;
@@ -127,6 +128,7 @@ export type EntityPath = string;
127
128
  * the detail and the list page configuration.
128
129
  */
129
130
  export interface EntityConfig<T = any, I = any> extends TabConfig {
131
+ annotation?: DataDictionaryAnnotation;
130
132
  apiPath?: EntityPath;
131
133
  categoryGroupConfig?: CategoryGroupConfig;
132
134
  detail: BackPageConfig;
@@ -137,6 +139,7 @@ export interface EntityConfig<T = any, I = any> extends TabConfig {
137
139
  getId?: GetIdFunction<T>;
138
140
  getTitle?: GetTitleFunction<T>;
139
141
  hideTabs?: boolean;
142
+ key?: string;
140
143
  list: ListConfig<T>;
141
144
  listView?: ListViewConfig;
142
145
  options?: Options;
@@ -295,10 +298,12 @@ export interface SiteConfig {
295
298
  categoryGroupConfig?: CategoryGroupConfig;
296
299
  contentDir?: string;
297
300
  contentThemeOptionsFn?: ThemeOptionsFn;
301
+ dataDictionary?: DataDictionary;
298
302
  dataSource: DataSourceConfig;
299
303
  entities: EntityConfig[];
300
304
  explorerTitle: HeroTitle;
301
305
  export?: ExportConfig;
306
+ exportsRequireAuth?: boolean;
302
307
  exportToTerraUrl?: string;
303
308
  gitHubUrl?: string;
304
309
  layout: {
@@ -27,6 +27,7 @@ function buildCategoryView(category, categoryValueViews, categoryConfigs) {
27
27
  const categoryConfig = findCategoryConfig(category.key, categoryConfigs);
28
28
  const mapSelectCategoryValue = categoryConfig?.mapSelectCategoryValue || getSelectCategoryValue;
29
29
  return {
30
+ annotation: categoryConfig?.annotation,
30
31
  isDisabled: false,
31
32
  key: category.key,
32
33
  label: getCategoryLabel(category.key, categoryConfig),
@@ -1,4 +1,5 @@
1
- import React, { createContext } from "react";
1
+ import React, { createContext, useState } from "react";
2
+ import { annotateSiteConfig } from "../components/DataDictionary/common/utils";
2
3
  import { getDefaultConfig, getDefaultEntityConfig, getEntityConfig, } from "../config/utils";
3
4
  export const ConfigContext = createContext({
4
5
  config: getDefaultConfig(),
@@ -7,7 +8,13 @@ export const ConfigContext = createContext({
7
8
  entityListType: "",
8
9
  });
9
10
  export function ConfigProvider({ children, config, entityListType = "", }) {
10
- const { entities } = config;
11
+ // Annote config on init. Note config is mutated but using state here to
12
+ // ensure annotated config is calculated once and is used rather than the raw config.
13
+ const [annotatedConfig] = useState(() => {
14
+ annotateSiteConfig(config);
15
+ return config;
16
+ });
17
+ const { entities } = annotatedConfig;
11
18
  const defaultEntityListType = config.redirectRootToPath.slice(1);
12
19
  const entityName = entityListType || defaultEntityListType;
13
20
  const entityConfig = getEntityConfig(entities, entityName);
@@ -0,0 +1,18 @@
1
+ import { ReactNode } from "react";
2
+ /**
3
+ * A callback function to be stored and then executed upon successful login.
4
+ */
5
+ export type LoginGuardCallback = () => void;
6
+ /**
7
+ * The shape of the LoginGuard context, provides a function to trigger the
8
+ * login process.
9
+ */
10
+ export interface LoginGuardContextProps {
11
+ requireLogin: (callback?: LoginGuardCallback) => void;
12
+ }
13
+ /**
14
+ * The properties for the LoginGuardProvider component.
15
+ */
16
+ export interface LoginGuardProviderProps {
17
+ children: ReactNode;
18
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import { LoginGuardContextProps } from "./common/types";
2
+ /**
3
+ * LoginGuardContext provides a way to trigger a login process. Default value is to
4
+ * call the callback immediately, if specified.
5
+ */
6
+ export declare const LoginGuardContext: import("react").Context<LoginGuardContextProps>;
@@ -0,0 +1,10 @@
1
+ import { createContext } from "react";
2
+ /**
3
+ * LoginGuardContext provides a way to trigger a login process. Default value is to
4
+ * call the callback immediately, if specified.
5
+ */
6
+ export const LoginGuardContext = createContext({
7
+ requireLogin: (callback) => {
8
+ callback?.();
9
+ },
10
+ });
@@ -0,0 +1,9 @@
1
+ import { LoginGuardContextProps } from "./common/types";
2
+ /**
3
+ * Custom hook to access the LoginGuard context. This hook returns an object
4
+ * containing the "requireLogin" function, which allows triggering the application's
5
+ * login process.
6
+ *
7
+ * @returns The current LoginGuard context value.
8
+ */
9
+ export declare function useLoginGuard(): LoginGuardContextProps;
@@ -0,0 +1,12 @@
1
+ import { useContext } from "react";
2
+ import { LoginGuardContext } from "./context";
3
+ /**
4
+ * Custom hook to access the LoginGuard context. This hook returns an object
5
+ * containing the "requireLogin" function, which allows triggering the application's
6
+ * login process.
7
+ *
8
+ * @returns The current LoginGuard context value.
9
+ */
10
+ export function useLoginGuard() {
11
+ return useContext(LoginGuardContext);
12
+ }
@@ -0,0 +1,11 @@
1
+ import { LoginGuardProviderProps } from "./common/types";
2
+ /**
3
+ * LoginGuardProvider is responsible for intercepting actions that require user authentication.
4
+ * It provides a "requireLogin" function via context. When a protected action is triggered while the
5
+ * user is unauthenticated, the LoginDialog is displayed. Upon successful authentication, the saved
6
+ * callback is invoked.
7
+ *
8
+ * @param {LoginGuardProviderProps} props - The provider props that include children.
9
+ * @returns The provider component.
10
+ */
11
+ export declare function LoginGuardProvider({ children, }: LoginGuardProviderProps): JSX.Element;
@@ -0,0 +1,55 @@
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 { LoginGuardContext } from "./context";
7
+ /**
8
+ * LoginGuardProvider is responsible for intercepting actions that require user authentication.
9
+ * It provides a "requireLogin" function via context. When a protected action is triggered while the
10
+ * user is unauthenticated, the LoginDialog is displayed. Upon successful authentication, the saved
11
+ * callback is invoked.
12
+ *
13
+ * @param {LoginGuardProviderProps} props - The provider props that include children.
14
+ * @returns The provider component.
15
+ */
16
+ export function LoginGuardProvider({ children, }) {
17
+ // Dialog open state.
18
+ const [open, setOpen] = useState(false);
19
+ // Use ref to store the callback without triggering re-render.
20
+ const callbackRef = useRef(undefined);
21
+ // Determine if authentication is enabled.
22
+ const authConfig = useAuthenticationConfig();
23
+ // Determine if authentication is required for downloads and exports.
24
+ const { config: { exportsRequireAuth }, } = useConfig();
25
+ // Get the user's authenticated state.
26
+ const { authState: { isAuthenticated }, } = useAuth();
27
+ // If the user authenticates, close dialog then fire and clear callback.
28
+ useEffect(() => {
29
+ if (isAuthenticated) {
30
+ setOpen(false);
31
+ callbackRef.current?.();
32
+ // Clear callback after firing.
33
+ callbackRef.current = undefined;
34
+ }
35
+ }, [isAuthenticated]);
36
+ // Handler to close the dialog.
37
+ const onClose = useCallback(() => {
38
+ setOpen(false);
39
+ // Clear any stored callback.
40
+ callbackRef.current = undefined;
41
+ }, []);
42
+ // Block actions that require authentication, or fire callback if already authenticated.
43
+ const requireLogin = useCallback((cb) => {
44
+ if (authConfig && exportsRequireAuth && !isAuthenticated) {
45
+ callbackRef.current = cb;
46
+ setOpen(true);
47
+ }
48
+ else {
49
+ cb?.();
50
+ }
51
+ }, [authConfig, exportsRequireAuth, isAuthenticated]);
52
+ return (React.createElement(LoginGuardContext.Provider, { value: { requireLogin } },
53
+ children,
54
+ React.createElement(LoginDialog, { open: open, onClose: onClose })));
55
+ }
@@ -1,2 +1,3 @@
1
1
  import { TypographyOwnProps } from "@mui/material";
2
+ export declare const COLOR: Record<string, TypographyOwnProps["color"]>;
2
3
  export declare const VARIANT: Record<string, TypographyOwnProps["variant"]>;
@@ -1,3 +1,10 @@
1
+ export const COLOR = {
2
+ INHERIT: "inherit",
3
+ INK_LIGHT: "ink.light",
4
+ INK_MAIN: "ink.main",
5
+ };
1
6
  export const VARIANT = {
2
7
  INHERIT: "inherit",
8
+ TEXT_BODY_400: "text-body-400",
9
+ TEXT_HEADING_SMALL: "text-heading-small",
3
10
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@databiosphere/findable-ui",
3
- "version": "21.2.0",
3
+ "version": "21.4.0",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Model of a value of a metadata class.
3
+ */
4
+ export interface Attribute {
5
+ description: string;
6
+ key: string;
7
+ label: string;
8
+ }
9
+
1
10
  /**
2
11
  * Filterable metadata keys.
3
12
  */
@@ -12,11 +21,38 @@ export interface CategoryTag {
12
21
  superseded: boolean;
13
22
  }
14
23
 
24
+ /**
25
+ * Model of a metadata class, to be specified manually or built from LinkML schema.
26
+ */
27
+ export interface Class {
28
+ attributes: Attribute[];
29
+ description: string;
30
+ key: string;
31
+ label: string;
32
+ name: string;
33
+ }
34
+
15
35
  /**
16
36
  * Category values to be used as keys. For example, "Homo sapiens" or "10X 3' v2 sequencing".
17
37
  */
18
38
  export type CategoryValueKey = unknown;
19
39
 
40
+ /**
41
+ * Model of a metadata dictionary containing a set of classes and their definitions.
42
+ */
43
+ export interface DataDictionary {
44
+ classes: Class[];
45
+ }
46
+
47
+ /**
48
+ * Label and description values from a data dictionary that are added to a site
49
+ * config value.
50
+ */
51
+ export interface DataDictionaryAnnotation {
52
+ description: string;
53
+ label: string;
54
+ }
55
+
20
56
  /**
21
57
  * Set of selected category values.
22
58
  */
@@ -72,6 +108,7 @@ export interface SelectCategoryValueView {
72
108
  * View model of category, for multiselect categories.
73
109
  */
74
110
  export interface SelectCategoryView {
111
+ annotation?: DataDictionaryAnnotation;
75
112
  isDisabled?: boolean;
76
113
  key: CategoryKey;
77
114
  label: string;