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 }) {
) : ( -
- -
- )} +
+ +
+ )} ); }