diff --git a/extensions/default/package.json b/extensions/default/package.json
index 09342a611..74e36c504 100644
--- a/extensions/default/package.json
+++ b/extensions/default/package.json
@@ -28,6 +28,8 @@
},
"peerDependencies": {
"@ohif/core": "^0.50.0",
+ "@ohif/i18n": "^0.52.8",
+ "react-i18next": "^10.11.0",
"prop-types": "^15.6.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
diff --git a/extensions/default/src/ViewerLayout/Header.jsx b/extensions/default/src/ViewerLayout/Header.jsx
index b5f8010c0..6000e5c22 100644
--- a/extensions/default/src/ViewerLayout/Header.jsx
+++ b/extensions/default/src/ViewerLayout/Header.jsx
@@ -1,22 +1,37 @@
-import React from 'react';
+import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
+import { useTranslation } from 'react-i18next';
// TODO: This may fail if package is split from PWA build
import { useHistory } from 'react-router-dom';
//
-import { NavBar, Svg, Icon, IconButton } from '@ohif/ui';
+import { NavBar, Svg, Icon, IconButton, Dropdown, useModal } from '@ohif/ui';
function Header({ children }) {
+ const { t } = useTranslation();
const history = useHistory();
- // const dropdownContent = [
- // {
- // name: 'Soft tissue',
- // value: '400/40',
- // },
- // { name: 'Lung', value: '1500 / -600' },
- // { name: 'Liver', value: '150 / 90' },
- // { name: 'Bone', value: '2500 / 480' },
- // { name: 'Brain', value: '80 / 40' },
- // ]
+ const { show } = useModal();
+
+ // TODO: IT SHOULD BE REFACTORED WHEN THE MODAL CONTENT IS DEFINED
+ const showAboutModal = useCallback(() => {
+ const modalComponent = () => (
+
{t('AboutModal:OHIF Viewer - About')}
+ );
+ show({
+ title: t('AboutModal:OHIF Viewer - About'),
+ content: modalComponent,
+ });
+ }, [show, t]);
+
+ // TODO: IT SHOULD BE REFACTORED WHEN THE MODAL CONTENT IS DEFINED
+ const showPreferencesModal = useCallback(() => {
+ const modalComponent = () => (
+ {t('UserPreferencesModal:User Preferences')}
+ );
+ show({
+ title: t('UserPreferencesModal:User Preferences'),
+ content: modalComponent,
+ });
+ }, [show, t]);
return (
@@ -40,18 +55,40 @@ function Header({ children }) {
{children}
- FOR INVESTIGATIONAL USE ONLY
+ {t('Header:INVESTIGATIONAL USE ONLY')}
- {}}
+
-
-
-
-
+
+
+
+
+
+
+
diff --git a/platform/ui/index.js b/platform/ui/index.js
index 12b086abd..e3c8a8f25 100644
--- a/platform/ui/index.js
+++ b/platform/ui/index.js
@@ -31,6 +31,7 @@ export {
ButtonGroup,
DateRange,
Dialog,
+ Dropdown,
EmptyStudies,
ExpandableToolbarButton,
ListMenu,
diff --git a/platform/ui/src/assets/icons/link.svg b/platform/ui/src/assets/icons/link.svg
index 7c99fc27c..1f7f94009 100644
--- a/platform/ui/src/assets/icons/link.svg
+++ b/platform/ui/src/assets/icons/link.svg
@@ -2,8 +2,6 @@
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 32 32"
aria-labelledby="title"
- width="1em"
- height="1em"
fill="currentColor"
>
diff --git a/platform/ui/src/assets/icons/unlink.svg b/platform/ui/src/assets/icons/unlink.svg
index 37c53bbb9..ed9526b7f 100644
--- a/platform/ui/src/assets/icons/unlink.svg
+++ b/platform/ui/src/assets/icons/unlink.svg
@@ -2,8 +2,6 @@
xmlns="http://www.w3.org/2000/svg"
aria-labelledby="unlink"
viewBox="0 0 512 512"
- width="1em"
- height="1em"
fill="currentColor"
>
Unlink
diff --git a/platform/ui/src/components/Dropdown/Dropdown.jsx b/platform/ui/src/components/Dropdown/Dropdown.jsx
new file mode 100644
index 000000000..b0b58497f
--- /dev/null
+++ b/platform/ui/src/components/Dropdown/Dropdown.jsx
@@ -0,0 +1,119 @@
+import React, { useEffect, useCallback, useState, useRef } from 'react';
+import PropTypes from 'prop-types';
+import classnames from 'classnames';
+
+import { Icon, Typography } from '@ohif/ui';
+
+const Dropdown = ({ children, showDropdownIcon, list }) => {
+ const [open, setOpen] = useState(false);
+ const element = useRef(null);
+
+ const DropdownItem = useCallback(({ title, icon, onClick }) => {
+ return (
+ {
+ setOpen(false);
+ onClick();
+ }}
+ >
+ {!!icon && }
+ {title}
+
+ );
+ }, []);
+
+ DropdownItem.defaultProps = {
+ icon: '',
+ };
+
+ DropdownItem.propTypes = {
+ title: PropTypes.string.isRequired,
+ icon: PropTypes.string,
+ onClick: PropTypes.func.isRequired,
+ };
+
+ const renderTitleElement = () => {
+ return (
+
+ {children}
+ {showDropdownIcon && (
+
+ )}
+
+ );
+ };
+
+ const toggleList = () => {
+ setOpen(s => !s);
+ };
+
+ const handleClick = e => {
+ if (element.current && !element.current.contains(e.target)) {
+ setOpen(false);
+ }
+ };
+
+ const renderList = () => {
+ return (
+
+ {list.map((item, idx) => (
+
+ ))}
+
+ );
+ };
+
+ useEffect(() => {
+ document.addEventListener('click', handleClick);
+
+ if (!open) {
+ document.removeEventListener('click', handleClick);
+ }
+ }, [open]);
+
+ return (
+
+
+ {renderTitleElement()}
+
+
+ {renderList()}
+
+ );
+};
+
+Dropdown.defaultProps = {
+ showDropdownIcon: true,
+};
+
+Dropdown.propTypes = {
+ children: PropTypes.node.isRequired,
+ showDropdownIcon: PropTypes.bool,
+ /** Items to render in the select's drop down */
+ list: PropTypes.arrayOf(
+ PropTypes.shape({
+ title: PropTypes.string.isRequired,
+ icon: PropTypes.string,
+ onClick: PropTypes.func.isRequired,
+ })
+ ).isRequired,
+};
+
+export default Dropdown;
diff --git a/platform/ui/src/components/Dropdown/Dropdown.mdx b/platform/ui/src/components/Dropdown/Dropdown.mdx
new file mode 100644
index 000000000..dc4b02555
--- /dev/null
+++ b/platform/ui/src/components/Dropdown/Dropdown.mdx
@@ -0,0 +1,68 @@
+---
+name: Dropdown
+menu: General
+route: components/dropdown
+---
+
+import { Playground, Props } from 'docz';
+import { Dropdown, IconButton, Icon } from '@ohif/ui';
+
+# Dropdown
+
+This component is used when there are more than a few options to choose from. By
+hovering or clicking on the trigger, a dropdown menu will appear, which allows
+you to choose an option and execute the relevant action.
+
+## Import
+
+```javascript
+import { Dropdown } from '@ohif/ui';
+```
+
+
+ {() => {
+ const handleClick = () => {
+ alert("Clicked");
+ }
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+ }}
+
+
+## Properties
+
+
diff --git a/platform/ui/src/components/Dropdown/index.js b/platform/ui/src/components/Dropdown/index.js
new file mode 100644
index 000000000..453771730
--- /dev/null
+++ b/platform/ui/src/components/Dropdown/index.js
@@ -0,0 +1 @@
+export { default } from './Dropdown';
diff --git a/platform/ui/src/components/NavBar/NavBar.jsx b/platform/ui/src/components/NavBar/NavBar.jsx
index 5ab60f626..8adc0ce3b 100644
--- a/platform/ui/src/components/NavBar/NavBar.jsx
+++ b/platform/ui/src/components/NavBar/NavBar.jsx
@@ -8,7 +8,7 @@ const NavBar = ({ className, children, isSticky }) => {
return (
-
-
-
-
-
-
-
- {appRoutes}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ {appRoutes}
+
+
+
+
+
+
+
+
);
}
diff --git a/platform/viewer/src/components/PreferencesDropdown/PreferencesDropdown.jsx b/platform/viewer/src/components/PreferencesDropdown/PreferencesDropdown.jsx
new file mode 100644
index 000000000..791905819
--- /dev/null
+++ b/platform/viewer/src/components/PreferencesDropdown/PreferencesDropdown.jsx
@@ -0,0 +1,57 @@
+import React from 'react';
+
+import { Dropdown, IconButton, Icon, useModal } from '@ohif/ui';
+
+const PreferencesDropdown = () => {
+ const { show } = useModal();
+
+ const showAboutModal = () => {
+ const modalComponent = () =>
About modal
;
+ show({
+ content: modalComponent,
+ title: 'About',
+ });
+ };
+
+ const showPreferencesModal = () => {
+ const modalComponent = () =>
Preferences modal
;
+ show({
+ content: modalComponent,
+ title: 'Preferences',
+ });
+ };
+
+ return (
+
+
+
+
+ {}}
+ >
+
+
+
+ );
+};
+
+export default PreferencesDropdown;
diff --git a/platform/viewer/src/components/PreferencesDropdown/index.js b/platform/viewer/src/components/PreferencesDropdown/index.js
new file mode 100644
index 000000000..edf8641a2
--- /dev/null
+++ b/platform/viewer/src/components/PreferencesDropdown/index.js
@@ -0,0 +1 @@
+export { default } from './PreferencesDropdown';
diff --git a/platform/viewer/src/routes/WorkList/WorkList.jsx b/platform/viewer/src/routes/WorkList/WorkList.jsx
index e3610cf90..f7edc664d 100644
--- a/platform/viewer/src/routes/WorkList/WorkList.jsx
+++ b/platform/viewer/src/routes/WorkList/WorkList.jsx
@@ -9,13 +9,14 @@ import filtersMeta from './filtersMeta.js';
import { useAppConfig } from '@state';
import { useDebounce, useQuery } from '@hooks';
+import PreferencesDropdown from '../../components/PreferencesDropdown';
+
import {
Icon,
StudyListExpandedRow,
Button,
NavBar,
Svg,
- IconButton,
EmptyStudies,
StudyListTable,
StudyListPagination,
@@ -34,7 +35,10 @@ function WorkList({ history, data: studies, isLoadingData, dataSource }) {
// ~ Filters
const query = useQuery();
const queryFilterValues = _getQueryFilterValues(query);
- const [filterValues, _setFilterValues] = useState({ ...defaultFilterValues, ...queryFilterValues });
+ const [filterValues, _setFilterValues] = useState({
+ ...defaultFilterValues,
+ ...queryFilterValues,
+ });
const debouncedFilterValues = useDebounce(filterValues, 200);
const { resultsPerPage, pageNumber, sortBy, sortDirection } = filterValues;
@@ -211,8 +215,8 @@ function WorkList({ history, data: studies, isLoadingData, dataSource }) {
content: patientName ? (
patientName
) : (
-
(Empty)
- ),
+
(Empty)
+ ),
title: patientName,
gridCol: 4,
},
@@ -299,13 +303,13 @@ function WorkList({ history, data: studies, isLoadingData, dataSource }) {
seriesTableDataSource={
seriesInStudiesMap.has(studyInstanceUid)
? seriesInStudiesMap.get(studyInstanceUid).map(s => {
- return {
- description: s.description || '(empty)',
- seriesNumber: s.seriesNumber || '',
- modality: s.modality || '',
- instances: s.numSeriesInstances || '',
- };
- })
+ return {
+ description: s.description || '(empty)',
+ seriesNumber: s.seriesNumber || '',
+ modality: s.modality || '',
+ instances: s.numSeriesInstances || '',
+ };
+ })
: []
}
>
@@ -322,7 +326,7 @@ function WorkList({ history, data: studies, isLoadingData, dataSource }) {
>
) : (
-
-
-
- )}
+
+
+
+ )}
);
}