feat(ui): move to React 18 and base for using shadcn/ui (#4174)

Co-authored-by: IbrahimCSAE <ibrahim.mdev@gmail.com>
This commit is contained in:
Alireza 2024-05-27 14:30:25 -04:00 committed by GitHub
parent 72b63dcd6a
commit 70f2c797f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
153 changed files with 2973 additions and 2043 deletions

View File

@ -42,6 +42,7 @@ jobs:
###
UNIT_TESTS:
<<: *defaults
resource_class: large
steps:
# Update yarn
- run: yarn -v
@ -98,6 +99,7 @@ jobs:
###
BUILD:
<<: *defaults
resource_class: large
steps:
# Checkout code and ALL Git Tags
- checkout
@ -146,6 +148,7 @@ jobs:
# just to make sure later on we can publish them
BUILD_PACKAGES_QUICK:
<<: *defaults
resource_class: large
steps:
- run: yarn -v
# Checkout code and ALL Git Tags
@ -195,6 +198,7 @@ jobs:
###
NPM_PUBLISH:
<<: *defaults
resource_class: large
steps:
- run: yarn -v
# Checkout code and ALL Git Tags
@ -264,6 +268,7 @@ jobs:
DOCKER_RELEASE_PUBLISH:
<<: *defaults
resource_class: large
steps:
- attach_workspace:
at: ~/repo
@ -293,6 +298,7 @@ jobs:
DOCKER_BETA_PUBLISH:
<<: *defaults
resource_class: large
steps:
- attach_workspace:
at: ~/repo
@ -397,6 +403,7 @@ jobs:
default: ''
description: Directory containing package.json
type: string
resource_class: large
steps:
- cypress/install:
cypress-cache-key: << parameters.cypress-cache-key >>

View File

@ -98,6 +98,38 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
module: {
noParse: [/(codec)/, /(dicomicc)/],
rules: [
{
test: /\.svg?$/,
oneOf: [
{
use: [
{
loader: '@svgr/webpack',
options: {
svgoConfig: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false
},
},
},
]
},
prettier: false,
svgo: true,
titleProp: true,
},
},
],
issuer: {
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
},
},
],
},
{
test: /\.js$/,
enforce: 'pre',

View File

@ -5,23 +5,6 @@ module.exports = {
babelrcRoots: ['./platform/*', './extensions/*', './modes/*'],
presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'],
plugins: [
[
'inline-react-svg',
{
svgo: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false,
},
},
},
],
},
},
],
['@babel/plugin-proposal-class-properties', { loose: true }],
'@babel/plugin-transform-typescript',
['@babel/plugin-proposal-private-property-in-object', { loose: true }],

View File

@ -1,5 +1,5 @@
module.exports = {
plugins: ['inline-react-svg', '@babel/plugin-proposal-class-properties'],
plugins: ['@babel/plugin-proposal-class-properties'],
env: {
test: {
presets: [

View File

@ -38,8 +38,8 @@
"@ohif/extension-default": "3.9.0-beta.21",
"@ohif/i18n": "3.9.0-beta.21",
"prop-types": "^15.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^10.11.0",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0"

View File

@ -358,11 +358,6 @@ OHIFCornerstoneRTViewport.propTypes = {
viewportId: PropTypes.string.isRequired,
dataSource: PropTypes.object,
children: PropTypes.node,
customProps: PropTypes.object,
};
OHIFCornerstoneRTViewport.defaultProps = {
customProps: {},
};
function _getReferencedDisplaySetMetadata(referencedDisplaySet) {

View File

@ -1,5 +1,5 @@
module.exports = {
plugins: ['inline-react-svg', '@babel/plugin-proposal-class-properties'],
plugins: ['@babel/plugin-proposal-class-properties'],
env: {
test: {
presets: [

View File

@ -38,8 +38,8 @@
"@ohif/extension-default": "3.9.0-beta.21",
"@ohif/i18n": "3.9.0-beta.21",
"prop-types": "^15.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^12.2.2",
"react-router": "^6.8.1",
"react-router-dom": "^6.8.1"

View File

@ -367,11 +367,6 @@ OHIFCornerstoneSEGViewport.propTypes = {
viewportId: PropTypes.string.isRequired,
dataSource: PropTypes.object,
children: PropTypes.node,
customProps: PropTypes.object,
};
OHIFCornerstoneSEGViewport.defaultProps = {
customProps: {},
};
function _getReferencedDisplaySetMetadata(referencedDisplaySet, segDisplaySet) {

View File

@ -42,7 +42,7 @@
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
"react": "^17.0.2"
"react": "^18.3.1"
},
"dependencies": {
"@babel/runtime": "^7.20.13",

View File

@ -367,16 +367,11 @@ OHIFCornerstoneSRViewport.propTypes = {
dataSource: PropTypes.object,
children: PropTypes.node,
viewportLabel: PropTypes.string,
customProps: PropTypes.object,
viewportOptions: PropTypes.object,
servicesManager: PropTypes.object.isRequired,
extensionManager: PropTypes.instanceOf(ExtensionManager).isRequired,
};
OHIFCornerstoneSRViewport.defaultProps = {
customProps: {},
};
async function _getViewportReferencedDisplaySetData(
displaySet,
measurementSelected,

View File

@ -38,7 +38,7 @@
"dicom-parser": "^1.8.21",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
"react": "^17.0.2"
"react": "^18.3.1"
},
"dependencies": {
"@babel/runtime": "^7.20.13",

View File

@ -46,9 +46,9 @@
"dicom-parser": "^1.8.21",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-resize-detector": "^6.7.6"
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-resize-detector": "^10.0.1"
},
"devDependencies": {
"lodash": "^4.17.21"

View File

@ -1,5 +1,5 @@
import React, { useEffect, useRef, useCallback, useState } from 'react';
import ReactResizeDetector from 'react-resize-detector';
import { useResizeDetector } from 'react-resize-detector';
import PropTypes from 'prop-types';
import * as cs3DTools from '@cornerstonejs/tools';
import {
@ -109,7 +109,7 @@ const OHIFCornerstoneViewport = React.memo((props: withAppTypes) => {
onElementEnabled,
// eslint-disable-next-line react/prop-types
onElementDisabled,
isJumpToMeasurementDisabled,
isJumpToMeasurementDisabled = false,
// Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation
// of the imageData in the OHIFCornerstoneViewport. This prop is used
// to set the initial state of the viewport's first image to render
@ -140,7 +140,7 @@ const OHIFCornerstoneViewport = React.memo((props: withAppTypes) => {
const [scrollbarHeight, setScrollbarHeight] = useState('100px');
const [enabledVPElement, setEnabledVPElement] = useState(null);
const elementRef = useRef();
const elementRef = useRef() as React.MutableRefObject<HTMLDivElement>;
const [appConfig] = useAppConfig();
const {
@ -417,19 +417,21 @@ const OHIFCornerstoneViewport = React.memo((props: withAppTypes) => {
});
}, [displaySets, viewportId, viewportActionCornersService, servicesManager, commandsManager]);
const { ref: resizeRef } = useResizeDetector({
onResize,
});
return (
<React.Fragment>
<div className="viewport-wrapper">
<ReactResizeDetector
onResize={onResize}
targetRef={elementRef.current}
/>
<div
className="cornerstone-viewport-element"
style={{ height: '100%', width: '100%' }}
onContextMenu={e => e.preventDefault()}
onMouseDown={e => e.preventDefault()}
ref={elementRef}
ref={el => {
resizeRef.current = el;
elementRef.current = el;
}}
></div>
<CornerstoneOverlays
viewportId={viewportId}
@ -683,10 +685,6 @@ function _rehydrateSynchronizers(
// Component displayName
OHIFCornerstoneViewport.displayName = 'OHIFCornerstoneViewport';
OHIFCornerstoneViewport.defaultProps = {
isJumpToMeasurementDisabled: false,
};
OHIFCornerstoneViewport.propTypes = {
displaySets: PropTypes.array.isRequired,
dataSource: PropTypes.object.isRequired,

View File

@ -78,14 +78,8 @@ function ViewportImageSliceLoadingIndicator({ viewportData, element }) {
}
ViewportImageSliceLoadingIndicator.propTypes = {
percentComplete: PropTypes.number,
error: PropTypes.object,
element: PropTypes.object,
};
ViewportImageSliceLoadingIndicator.defaultProps = {
percentComplete: 0,
error: null,
};
export default ViewportImageSliceLoadingIndicator;

View File

@ -136,16 +136,6 @@ function ViewportOrientationMarkers({
return <div className="ViewportOrientationMarkers select-none">{markers}</div>;
}
ViewportOrientationMarkers.propTypes = {
percentComplete: PropTypes.number,
error: PropTypes.object,
};
ViewportOrientationMarkers.defaultProps = {
percentComplete: 0,
error: null,
};
/**
*
* Computes the orientation labels on a Cornerstone-enabled Viewport element

View File

@ -6,7 +6,7 @@ function _getVolumesFromViewport(viewport) {
}
function _getVolumeFromViewport(viewport) {
const volumes = _getVolumesFromViewport(viewport);
const volumes = _getVolumesFromViewport(viewport).filter(volume => volume);
const dynamicVolume = volumes.find(volume => volume.isDynamicVolume());
return dynamicVolume ?? volumes[0];

View File

@ -37,8 +37,8 @@
"dcmjs": "^0.29.12",
"dicomweb-client": "^0.10.4",
"prop-types": "^15.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^12.2.2",
"react-window": "^1.8.9",
"webpack": "^5.50.0",

View File

@ -2,7 +2,13 @@ import React, { useEffect, useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import { LayoutSelector as OHIFLayoutSelector, ToolbarButton } from '@ohif/ui';
function LegacyLayoutSelectorWithServices({ servicesManager, ...props }: withAppTypes) {
function LegacyLayoutSelectorWithServices({
servicesManager,
rows = 3,
columns = 3,
onLayoutChange = () => {},
...props
}) {
const { toolbarService } = servicesManager.services;
const onSelection = useCallback(
@ -78,10 +84,4 @@ LayoutSelector.propTypes = {
servicesManager: PropTypes.object.isRequired,
};
LayoutSelector.defaultProps = {
rows: 3,
columns: 3,
onLayoutChange: () => {},
};
export default LegacyLayoutSelectorWithServices;

View File

@ -124,8 +124,9 @@ function ToolbarLayoutSelectorWithServices({
}
function LayoutSelector({
rows,
columns,
rows = 3,
columns = 4,
onLayoutChange = () => {},
className,
onSelection,
onSelectionPreset,
@ -242,10 +243,4 @@ LayoutSelector.propTypes = {
servicesManager: PropTypes.object.isRequired,
};
LayoutSelector.defaultProps = {
columns: 4,
rows: 3,
onLayoutChange: () => {},
};
export default ToolbarLayoutSelectorWithServices;

View File

@ -54,7 +54,6 @@ function ToolbarSplitButtonWithServices({
}
ToolbarSplitButtonWithServices.propTypes = {
isRadio: PropTypes.bool,
groupId: PropTypes.string,
primary: PropTypes.shape({
id: PropTypes.string.isRequired,
@ -87,9 +86,4 @@ ToolbarSplitButtonWithServices.propTypes = {
}),
};
ToolbarSplitButtonWithServices.defaultProps = {
isRadio: false,
isAction: false,
};
export default ToolbarSplitButtonWithServices;

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { ToolbarButton } from '@ohif/ui';
function NestedMenu({ children, label, icon, isActive }) {
function NestedMenu({ children, label = 'More', icon = 'tool-more-menu', isActive }) {
const [isOpen, setIsOpen] = useState(false);
const toggleNestedMenu = () => setIsOpen(!isOpen);
@ -39,9 +39,4 @@ NestedMenu.propTypes = {
label: PropTypes.string,
};
NestedMenu.defaultProps = {
icon: 'tool-more-menu',
label: 'More',
};
export default NestedMenu;

View File

@ -1,5 +1,5 @@
module.exports = {
plugins: ['inline-react-svg', '@babel/plugin-proposal-class-properties'],
plugins: ['@babel/plugin-proposal-class-properties'],
env: {
test: {
presets: [

View File

@ -35,8 +35,8 @@
"@ohif/i18n": "3.9.0-beta.21",
"@ohif/ui": "3.9.0-beta.21",
"prop-types": "^15.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^10.11.0",
"react-router": "^6.8.1",
"react-router-dom": "^6.8.1"

View File

@ -1,7 +1,5 @@
import React, { Component } from 'react';
import ReactResizeDetector from 'react-resize-detector';
import PropTypes from 'prop-types';
import debounce from 'lodash.debounce';
import { LoadingIndicatorProgress } from '@ohif/ui';
import './DicomMicroscopyViewport.css';
@ -23,18 +21,12 @@ class DicomMicroscopyViewport extends Component {
container = React.createRef();
overlayElement = React.createRef();
debouncedResize: () => any;
constructor(props: any) {
super(props);
const { microscopyService } = this.props.servicesManager.services;
this.microscopyService = microscopyService;
this.debouncedResize = debounce(() => {
if (this.viewer) {
this.viewer.resize();
}
}, 100);
}
static propTypes = {
@ -54,6 +46,7 @@ class DicomMicroscopyViewport extends Component {
servicesManager: PropTypes.object,
extensionManager: PropTypes.object,
commandsManager: PropTypes.object,
resizeRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({ current: PropTypes.any })]),
};
/**
@ -298,13 +291,16 @@ class DicomMicroscopyViewport extends Component {
</div>
</div>
</div>
{ReactResizeDetector && (
<ReactResizeDetector handleWidth handleHeight onResize={this.onWindowResize} />
)}
{this.state.error ? (
<h2>{JSON.stringify(this.state.error)}</h2>
) : (
<div style={style} ref={this.container} />
<div
style={style}
ref={(ref: any) => {
this.container.current = ref;
this.props.resizeRef.current = ref;
}}
/>
)}
{this.state.isLoaded ? null : (
<LoadingIndicatorProgress className={'h-full w-full bg-black'} />
@ -312,10 +308,6 @@ class DicomMicroscopyViewport extends Component {
</div>
);
}
onWindowResize = () => {
this.debouncedResize();
};
}
export default DicomMicroscopyViewport;

View File

@ -8,6 +8,8 @@ import { useViewportGrid } from '@ohif/ui';
import getDicomMicroscopySopClassHandler from './DicomMicroscopySopClassHandler';
import getDicomMicroscopySRSopClassHandler from './DicomMicroscopySRSopClassHandler';
import MicroscopyService from './services/MicroscopyService';
import { useResizeDetector } from 'react-resize-detector';
import debounce from 'lodash.debounce';
const Component = React.lazy(() => {
return import('./DicomMicroscopyViewport');
@ -59,14 +61,25 @@ const extension: Types.Extensions.Extension = {
const [viewportGrid, viewportGridService] = useViewportGrid();
const { activeViewportId } = viewportGrid;
// a unique identifier based on the contents of displaySets.
// since we changed our rendering pipeline and if there is no
// element size change nor viewportId change we won't re-render
// we need a way to force re-rendering when displaySets change.
const displaySetsKey = useMemo(() => {
return props.displaySets.map(ds => ds.displaySetInstanceUID).join('-');
}, [props.displaySets]);
const onResize = debounce(() => {
const { microscopyService } = servicesManager.services;
const managedViewer = microscopyService.getAllManagedViewers();
if (managedViewer && managedViewer.length > 0) {
managedViewer[0].viewer.resize();
}
}, 100);
const { ref: resizeRef } = useResizeDetector({
onResize,
handleHeight: true,
handleWidth: true,
});
return (
<MicroscopyViewport
key={displaySetsKey}
@ -78,6 +91,7 @@ const extension: Types.Extensions.Extension = {
viewportGridService.setActiveViewportId(viewportId);
}}
viewportData={viewportOptions}
resizeRef={resizeRef}
{...props}
/>
);

View File

@ -628,6 +628,15 @@ export default class MicroscopyService extends PubSubService {
setROIStyle(uid, styleOptions) {
this.managedViewers.forEach(mv => mv.setROIStyle(uid, styleOptions));
}
/**
* Get all managed viewers
*
* @returns {Array} managedViewers
*/
getAllManagedViewers() {
return Array.from(this.managedViewers);
}
}
export { EVENTS };

View File

@ -36,7 +36,7 @@
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
"react": "^17.0.2"
"react": "^18.3.1"
},
"dependencies": {
"@babel/runtime": "^7.20.13",

View File

@ -36,7 +36,7 @@
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
"react": "^17.0.2"
"react": "^18.3.1"
},
"dependencies": {
"@babel/runtime": "^7.20.13",

View File

@ -41,8 +41,8 @@
"dcmjs": "^0.29.12",
"lodash.debounce": "^4.17.21",
"prop-types": "^15.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"webpack": "^5.50.0",
"webpack-merge": "^5.7.3"
},

View File

@ -242,11 +242,6 @@ TrackedCornerstoneViewport.propTypes = {
viewportId: PropTypes.string.isRequired,
dataSource: PropTypes.object,
children: PropTypes.node,
customProps: PropTypes.object,
};
TrackedCornerstoneViewport.defaultProps = {
customProps: {},
};
function _getNextMeasurementUID(

View File

@ -36,7 +36,7 @@
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
"react": "^17.0.2"
"react": "^18.3.1"
},
"dependencies": {
"@babel/runtime": "^7.20.13",

View File

@ -36,7 +36,7 @@
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
"react": "^17.0.2"
"react": "^18.3.1"
},
"dependencies": {
"@babel/runtime": "^7.20.13",

View File

@ -1,5 +1,5 @@
module.exports = {
plugins: ['inline-react-svg', '@babel/plugin-proposal-class-properties'],
plugins: ['@babel/plugin-proposal-class-properties'],
env: {
test: {
presets: [

View File

@ -1,5 +1,5 @@
module.exports = {
plugins: ['inline-react-svg', '@babel/plugin-proposal-class-properties'],
plugins: ['@babel/plugin-proposal-class-properties'],
env: {
test: {
presets: [

View File

@ -51,6 +51,38 @@ const config = {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
{
test: /\.svg?$/,
oneOf: [
{
use: [
{
loader: '@svgr/webpack',
options: {
svgoConfig: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false
},
},
},
]
},
prettier: false,
svgo: true,
titleProp: true,
},
},
],
issuer: {
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
},
},
],
},
],
},
resolve: {

View File

@ -1,5 +1,5 @@
module.exports = {
plugins: ['inline-react-svg', '@babel/plugin-proposal-class-properties'],
plugins: [ '@babel/plugin-proposal-class-properties'],
env: {
test: {
presets: [

View File

@ -63,7 +63,7 @@
"@babel/preset-typescript": "^7.13.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.0.0-beta.4",
"babel-plugin-inline-react-svg": "^2.0.1",
"@svgr/webpack": "^8.1.0",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.2.0",
"cross-env": "^7.0.3",

View File

@ -65,8 +65,8 @@
"core-js": "^3.2.1"
},
"peerDependencies": {
"react": "17.0.2",
"react-dom": "17.0.2"
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@babel/core": "^7.23.2",
@ -82,13 +82,13 @@
"@babel/preset-env": "^7.23.2",
"@babel/preset-react": "^7.16.7",
"@babel/preset-typescript": "^7.13.0",
"@svgr/webpack": "^8.1.0",
"@types/jest": "^27.5.0",
"@typescript-eslint/eslint-plugin": "^6.3.0",
"@typescript-eslint/parser": "^6.3.0",
"autoprefixer": "^10.4.4",
"babel-eslint": "9.x",
"babel-loader": "^8.2.4",
"babel-plugin-inline-react-svg": "^2.0.2",
"babel-plugin-module-resolver": "^5.0.0",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^9.0.1",

View File

@ -28,6 +28,7 @@ export default defineConfig({
defaultCommandTimeout: 10000,
requestTimeout: 10000,
responseTimeout: 10000,
pageLoadTimeout: 10000,
specPattern: 'cypress/integration/**/*.spec.[jt]s',
projectId: '4oe38f',
video: true,

View File

@ -78,8 +78,8 @@ describe('OHIF MPR', () => {
const fOR = Object.keys(fORMap)[0];
const fORAnnotation = fORMap[fOR];
// it should have crosshairs as the only key
expect(Object.keys(fORAnnotation)).to.have.length(1);
// it should have crosshairs as the only key (references lines make this 2)
expect(Object.keys(fORAnnotation)).to.have.length(2);
const crosshairs = fORAnnotation.Crosshairs;

View File

@ -61,8 +61,8 @@ export function initStudyListAliasesOnDesktop() {
// We can't use data attributes (e.g. data--cy) for these since
// they are using third party libraries (i.e. react-dates, react-select)
cy.get('#date-range-studyDate-start-date').as('studyListStartDate');
cy.get('#date-range-studyDate-end-date').as('studyListEndDate');
cy.get('[data-cy="input-date-range-start"').as('studyListStartDate');
cy.get('[data-cy="input-date-range-end"').as('studyListEndDate');
cy.get('#input-modalities').as('modalities');
}

View File

@ -71,7 +71,9 @@
"@ohif/mode-microscopy": "3.9.0-beta.21",
"@ohif/mode-test": "3.9.0-beta.21",
"@ohif/ui": "3.9.0-beta.21",
"@types/react": "^17.0.38",
"@ohif/ui-next": "3.9.0-beta.14",
"@svgr/webpack": "^8.1.0",
"@types/react": "^18.3.3",
"classnames": "^2.3.2",
"core-js": "^3.16.1",
"cornerstone-math": "^0.1.9",
@ -87,11 +89,11 @@
"oidc-client": "1.11.5",
"prop-types": "^15.7.2",
"query-string": "^6.12.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-dropzone": "^10.1.7",
"react-i18next": "^12.2.2",
"react-resize-detector": "^9.1.0",
"react-resize-detector": "^10.0.1",
"react-router": "^6.8.1",
"react-router-dom": "^6.8.1"
},

View File

@ -1,4 +1,5 @@
// External
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import i18n from '@ohif/i18n';
@ -23,6 +24,7 @@ import {
UserAuthenticationProvider,
ToolboxProvider,
} from '@ohif/ui';
import { ThemeWrapper as ThemeWrapperNext, NotificationProvider } from '@ohif/ui-next';
// Viewer Project
// TODO: Should this influence study list?
import { AppConfigProvider } from '@state';
@ -36,7 +38,27 @@ let commandsManager: CommandsManager,
serviceProvidersManager: ServiceProvidersManager,
hotkeysManager: HotkeysManager;
function App({ config, defaultExtensions, defaultModes }) {
function App({
config = {
/**
* Relative route from domain root that OHIF instance is installed at.
* For example:
*
* Hosted at: https://ohif.org/where-i-host-the/viewer/
* Value: `/where-i-host-the/viewer/`
* */
routerBaseName: '/',
/**
*
*/
showLoadingIndicator: true,
showStudyList: true,
oidc: [],
extensions: [],
},
defaultExtensions = [],
defaultModes = [],
}) {
const [init, setInit] = useState(null);
useEffect(() => {
const run = async () => {
@ -83,11 +105,13 @@ function App({ config, defaultExtensions, defaultModes }) {
[AppConfigProvider, { value: appConfigState }],
[UserAuthenticationProvider, { service: userAuthenticationService }],
[I18nextProvider, { i18n }],
[ThemeWrapperNext],
[ThemeWrapper],
[ToolboxProvider],
[ViewportGridProvider, { service: viewportGridService }],
[ViewportDialogProvider, { service: uiViewportDialogService }],
[CineProvider, { service: cineService }],
// [NotificationProvider, { service: uiNotificationService }],
[SnackbarProvider, { service: uiNotificationService }],
[DialogProvider, { service: uiDialogService }],
[ModalProvider, { service: uiModalService, modal: Modal }],
@ -153,27 +177,9 @@ App.propTypes = {
/* Extensions that are "bundled" or "baked-in" to the application.
* These would be provided at build time as part of they entry point. */
defaultExtensions: PropTypes.array,
};
App.defaultProps = {
config: {
/**
* Relative route from domain root that OHIF instance is installed at.
* For example:
*
* Hosted at: https://ohif.org/where-i-host-the/viewer/
* Value: `/where-i-host-the/viewer/`
* */
routerBaseName: '/',
/**
*
*/
showLoadingIndicator: true,
showStudyList: true,
oidc: [],
extensions: [],
},
defaultExtensions: [],
/* Modes that are "bundled" or "baked-in" to the application.
* These would be provided at build time as part of they entry point. */
defaultModes: PropTypes.array,
};
export default App;

View File

@ -1,5 +1,5 @@
import React, { useEffect, useCallback, useRef, useState } from 'react';
import ReactResizeDetector from 'react-resize-detector';
import { useResizeDetector } from 'react-resize-detector';
import PropTypes from 'prop-types';
import { Types, MeasurementService } from '@ohif/core';
import { ViewportGrid, ViewportPane, useViewportGrid } from '@ohif/ui';
@ -8,16 +8,22 @@ import classNames from 'classnames';
import { useAppConfig } from '@state';
function ViewerViewportGrid(props: withAppTypes) {
const { servicesManager, viewportComponents, dataSource } = props;
const { servicesManager, viewportComponents = [], dataSource } = props;
const [viewportGrid, viewportGridService] = useViewportGrid();
const [appConfig] = useAppConfig();
const { layout, activeViewportId, viewports, isHangingProtocolLayout } = viewportGrid;
const { numCols, numRows } = layout;
const elementRef = useRef(null);
const { ref: resizeRef } = useResizeDetector({
refreshMode: 'debounce',
refreshRate: 7,
refreshOptions: { leading: true },
onResize: () => {
viewportGridService.setViewportGridSizeChanged();
},
});
const layoutHash = useRef(null);
// TODO -> Need some way of selecting which displaySets hit the viewports.
const { displaySetService, measurementService, hangingProtocolService, uiNotificationService } =
servicesManager.services;
@ -198,50 +204,6 @@ function ViewerViewportGrid(props: withAppTypes) {
};
}, [viewports]);
/**
const onDoubleClick = viewportId => {
// TODO -> Disabled for now.
// onNewImage on a cornerstone viewport is firing setDisplaySetsForViewport.
// Which it really really shouldn't. We need a larger fix for jump to
// measurements and all cornerstone "imageIndex" state to fix this.
if (cachedLayout) {
viewportGridService.set({
numCols: cachedLayout.numCols,
numRows: cachedLayout.numRows,
activeViewportId: cachedLayout.activeViewportId,
viewports: cachedLayout.viewports,
cachedLayout: null,
});
return;
}
const cachedViewports = viewports.map(viewport => {
return {
displaySetInstanceUID: viewport.displaySetInstanceUID,
};
});
viewportGridService.set({
numCols: 1,
numRows: 1,
activeViewportId: 0,
viewports: [
{
displaySetInstanceUID: viewports[viewportId].displaySetInstanceUID,
imageIndex: undefined,
},
],
cachedLayout: {
numCols,
numRows,
viewports: cachedViewports,
activeViewportId: viewportId,
},
});
};
*/
const onDropHandler = (viewportId, { displaySetInstanceUID }) => {
const updatedViewports = _getUpdatedViewports(viewportId, displaySetInstanceUID);
viewportGridService.setDisplaySetsForViewports(updatedViewports);
@ -363,21 +325,13 @@ function ViewerViewportGrid(props: withAppTypes) {
return (
<div
ref={elementRef}
ref={resizeRef}
className="h-full w-full"
>
<ViewportGrid
numRows={numRows}
numCols={numCols}
>
<ReactResizeDetector
refreshMode="debounce"
refreshRate={7} // ms seems to be fine for 10 viewports
onResize={() => {
viewportGridService.setViewportGridSizeChanged();
}}
targetRef={elementRef.current}
/>
{getViewportPanes()}
</ViewportGrid>
</div>
@ -389,10 +343,6 @@ ViewerViewportGrid.propTypes = {
servicesManager: PropTypes.instanceOf(Object).isRequired,
};
ViewerViewportGrid.defaultProps = {
viewportComponents: [],
};
function _getViewportComponent(displaySets, viewportComponents, uiNotificationService) {
if (!displaySets || !displaySets.length) {
return EmptyViewport;

View File

@ -2,6 +2,7 @@
* Entry point for development and production PWA builds.
*/
import 'regenerator-runtime/runtime';
import { createRoot } from 'react-dom/client';
import App from './App';
import React from 'react';
import ReactDOM from 'react-dom';
@ -35,10 +36,10 @@ loadDynamicConfig(window.config).then(config_json => {
defaultModes,
};
/** Create App */
const app = React.createElement(App, appProps, null);
/** Render */
ReactDOM.render(app, document.getElementById('root'));
const container = document.getElementById('root');
const root = createRoot(container);
root.render(React.createElement(App, appProps));
});
export { history };

View File

@ -306,9 +306,11 @@ export default function ModeRoute({
}
// The unsubscriptions must occur before the extension onModeExit
// in order to prevent exceptions during cleanup caused by spurious events
unsubscriptions.forEach(unsub => {
unsub();
});
if (unsubscriptions) {
unsubscriptions.forEach(unsub => {
unsub();
});
}
// The extension manager must be called after the mode, this is
// expected to cleanup the state to a standard setup.
extensionManager.onModeExit();

View File

@ -1,5 +1,4 @@
// Todo: we probably should add external installed extensions/modes here as well.
/** @type {import('tailwindcss').Config} */
module.exports = {
// Note: in Tailwind 3.0, JIT will purge unused styles by default
// but in development, it is often useful to disable this to see
@ -7,6 +6,7 @@ module.exports = {
// ...(process.env.NODE_ENV === 'development' && {
// safelist: [{ pattern: /.*/ }],
// }),
presets: [require('../ui/tailwind.config.js'), require('../ui-next/tailwind.config.js')],
content: [
'./src/**/*.{jsx,js,ts,tsx, css}',
'../../extensions/**/*.{jsx,js,ts,tsx, css}',
@ -14,256 +14,10 @@ module.exports = {
'../../modes/**/*.{jsx,js,ts,tsx, css}',
'./node_modules/@ohif/ui/src/**/*.{js,jsx,ts,tsx, css}',
'../../node_modules/@ohif/ui/src/**/*.{js,jsx,ts,tsx,css}',
'../../node_modules/@ohif/ui/src/**/*.{js,jsx,ts,tsx,css}',
'../../node_modules/@ohif/ui-next/src/**/*.{js,jsx,ts,tsx,css}',
'../../node_modules/@ohif/extension-*/src/**/*.{js,jsx,css, ts,tsx}',
],
theme: {
screens: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
},
colors: {
overlay: 'rgba(0, 0, 0, 0.8)',
transparent: 'transparent',
black: '#000',
white: '#fff',
initial: 'initial',
inherit: 'inherit',
indigo: {
dark: '#0b1a42',
},
aqua: {
pale: '#7bb2ce',
},
primary: {
light: '#5acce6',
main: '#0944b3',
dark: '#090c29',
active: '#348cfd',
},
inputfield: {
main: '#3a3f99',
disabled: '#2b166b',
focus: '#5acce6',
placeholder: '#39383f',
},
secondary: {
light: '#3a3f99',
main: '#2b166b',
dark: '#041c4a',
active: '#1f1f27',
},
common: {
bright: '#e1e1e1',
light: '#a19fad',
main: '#fff',
dark: '#726f7e',
active: '#2c3074',
},
customgreen: {
100: '#05D97C',
200: '#0FD97C',
},
customblue: {
10: '#0A163F',
20: '#0B1F54',
30: '#09286e',
40: '#0E307F',
50: '#0F3A94',
80: '#1454D4',
100: '#c4fdff',
200: '#38daff',
300: '#1D204D',
400: '#90A0C1',
},
customgray: {
100: '#262943',
},
gray: {
100: '#f7fafc',
200: '#edf2f7',
300: '#e2e8f0',
400: '#cbd5e0',
500: '#a0aec0',
600: '#718096',
700: '#4a5568',
800: '#2d3748',
900: '#1a202c',
},
red: {
100: '#fff5f5',
200: '#fed7d7',
300: '#feb2b2',
400: '#fc8181',
500: '#f56565',
600: '#e53e3e',
700: '#c53030',
800: '#9b2c2c',
900: '#742a2a',
},
orange: {
100: '#fffaf0',
200: '#feebc8',
300: '#fbd38d',
400: '#f6ad55',
500: '#ed8936',
600: '#dd6b20',
700: '#c05621',
800: '#9c4221',
900: '#7b341e',
},
yellow: {
100: '#fffff0',
200: '#fefcbf',
300: '#faf089',
400: '#f6e05e',
500: '#ecc94b',
600: '#d69e2e',
700: '#b7791f',
800: '#975a16',
900: '#744210',
},
green: {
100: '#f0fff4',
200: '#c6f6d5',
300: '#9ae6b4',
400: '#68d391',
500: '#48bb78',
600: '#38a169',
700: '#2f855a',
800: '#276749',
900: '#22543d',
},
teal: {
100: '#e6fffa',
200: '#b2f5ea',
300: '#81e6d9',
400: '#4fd1c5',
500: '#38b2ac',
600: '#319795',
700: '#2c7a7b',
800: '#285e61',
900: '#234e52',
},
blue: {
100: '#ebf8ff',
200: '#bee3f8',
300: '#90cdf4',
400: '#63b3ed',
500: '#4299e1',
600: '#3182ce',
700: '#2b6cb0',
800: '#2c5282',
900: '#2a4365',
},
indigo: {
100: '#ebf4ff',
200: '#c3dafe',
300: '#a3bffa',
400: '#7f9cf5',
500: '#667eea',
600: '#5a67d8',
700: '#4c51bf',
800: '#434190',
900: '#3c366b',
},
purple: {
100: '#faf5ff',
200: '#e9d8fd',
300: '#d6bcfa',
400: '#b794f4',
500: '#9f7aea',
600: '#805ad5',
700: '#6b46c1',
800: '#553c9a',
900: '#44337a',
},
pink: {
100: '#fff5f7',
200: '#fed7e2',
300: '#fbb6ce',
400: '#f687b3',
500: '#ed64a6',
600: '#d53f8c',
700: '#b83280',
800: '#97266d',
900: '#702459',
},
},
backgroundColor: theme => theme('colors'),
backgroundPosition: {
bottom: 'bottom',
center: 'center',
left: 'left',
'left-bottom': 'left bottom',
'left-top': 'left top',
right: 'right',
'right-bottom': 'right bottom',
'right-top': 'right top',
top: 'top',
},
backgroundSize: {
auto: 'auto',
cover: 'cover',
contain: 'contain',
},
borderColor: theme => ({
...theme('colors'),
DEFAULT: theme('colors.gray.300', 'currentColor'),
}),
boxShadow: {
xs: '0 0 0 1px rgba(0, 0, 0, 0.05)',
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
'2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
outline: '0 0 0 3px rgba(66, 153, 225, 0.5)',
none: 'none',
},
container: {},
cursor: {
auto: 'auto',
default: 'default',
pointer: 'pointer',
wait: 'wait',
text: 'text',
move: 'move',
'not-allowed': 'not-allowed',
},
fill: {
current: 'currentColor',
},
flex: {
1: '1 1 0%',
0.3: '0.3 0.3 0%',
0.5: '0.5 0.5 0%',
auto: '1 1 auto',
initial: '0 1 auto',
none: 'none',
static: '0 0 auto',
},
flexGrow: {
0: '0',
DEFAULT: '1',
},
flexShrink: {
0: '0',
DEFAULT: '1',
},
fontFamily: {
sans: [
'Inter',
@ -296,500 +50,5 @@ module.exports = {
'5xl': '3rem',
'6xl': '4rem',
},
fontWeight: {
hairline: '100',
thin: '200',
light: '300',
normal: '300',
medium: '500',
semibold: '600',
bold: '700',
extrabold: '800',
black: '900',
},
height: theme => ({
auto: 'auto',
...theme('spacing'),
full: '100%',
screen: '100vh',
inherit: 'inherit',
}),
inset: theme => ({
...theme('spacing'),
0: '0',
auto: 'auto',
full: '100%',
viewport: '0.5rem',
'1/2': '50%',
'viewport-scrollbar': '1.3rem',
}),
minHeight: theme => ({
...theme('spacing'),
0: '0',
full: '100%',
screen: '100vh',
}),
letterSpacing: {
tighter: '-0.05em',
tight: '-0.025em',
normal: '0',
wide: '0.025em',
wider: '0.05em',
widest: '0.1em',
},
lineHeight: {
none: '1',
tight: '1.25',
snug: '1.375',
normal: '1.5',
relaxed: '1.625',
loose: '2',
3: '.75rem',
4: '1rem',
5: '1.25rem',
6: '1.5rem',
7: '1.75rem',
8: '2rem',
9: '2.25rem',
10: '2.5rem',
},
listStyleType: {
none: 'none',
disc: 'disc',
decimal: 'decimal',
},
margin: (theme, { negative }) => ({
auto: 'auto',
...theme('spacing'),
...negative(theme('spacing')),
}),
maxHeight: theme => ({
full: '100%',
screen: '100vh',
...theme('spacing'),
}),
maxWidth: (theme, { breakpoints }) => ({
none: 'none',
xs: '20rem',
sm: '24rem',
md: '28rem',
lg: '32rem',
xl: '36rem',
'2xl': '42rem',
'3xl': '48rem',
'4xl': '56rem',
'5xl': '64rem',
'6xl': '72rem',
full: '100%',
...breakpoints(theme('screens')),
...theme('spacing'),
}),
minWidth: theme => ({
...theme('spacing'),
0: '0',
xs: '2rem',
sm: '4rem',
md: '6rem',
lg: '8rem',
xl: '10rem',
full: '100%',
}),
objectPosition: {
bottom: 'bottom',
center: 'center',
left: 'left',
'left-bottom': 'left bottom',
'left-top': 'left top',
right: 'right',
'right-bottom': 'right bottom',
'right-top': 'right top',
top: 'top',
},
opacity: {
0: '0',
5: '.5',
10: '.10',
15: '.15',
20: '.20',
25: '.25',
30: '.30',
35: '.35',
40: '.40',
45: '.45',
50: '.50',
55: '.55',
60: '.60',
65: '.65',
70: '.70',
75: '.75',
80: '.80',
85: '.85',
90: '.90',
95: '.95',
100: '1',
},
order: {
first: '-9999',
last: '9999',
none: '0',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12',
},
padding: theme => theme('spacing'),
placeholderColor: theme => theme('colors'),
stroke: theme => ({
...theme('colors'),
current: 'currentColor',
}),
strokeWidth: {
0: '0',
1: '1',
2: '2',
},
textColor: theme => theme('colors'),
width: theme => ({
auto: 'auto',
...theme('spacing'),
'1/2': '50%',
'1/3': '33.333333%',
'2/3': '66.666667%',
'1/4': '25%',
'2/4': '50%',
'3/4': '75%',
'1/5': '20%',
'2/5': '40%',
'3/5': '60%',
'4/5': '80%',
'1/6': '16.666667%',
'2/6': '33.333333%',
'3/6': '50%',
'4/6': '66.666667%',
'5/6': '83.333333%',
'1/12': '8.333333%',
'2/12': '16.666667%',
'3/12': '25%',
'4/12': '33.333333%',
'5/12': '41.666667%',
'6/12': '50%',
'7/12': '58.333333%',
'8/12': '66.666667%',
'9/12': '75%',
'10/12': '83.333333%',
'11/12': '91.666667%',
'1/24': '4.166666667%',
'2/24': '8.333333333%',
'3/24': '12.5%',
'4/24': '16.66666667%',
'5/24': '20.83333333%',
'6/24': '25%',
'7/24': '29.16666667%',
'8/24': '33.33333333%',
'9/24': '37.5%',
'10/24': '41.66666667%',
'11/24': '45.83333333%',
'12/24': '50%',
'13/24': '54.16666667%',
'14/24': '58.33333333%',
'15/24': '62.5%',
'16/24': '66.66666667%',
'17/24': '70.83333333%',
'18/24': '75%',
'19/24': '79.16666667%',
'20/24': '83.33333333%',
'21/24': '87.5%',
'22/24': '91.66666667%',
'23/24': '95.83333333%',
full: '100%',
screen: '100vw',
'max-content': 'max-content',
}),
zIndex: {
auto: 'auto',
0: '0',
10: '10',
20: '20',
30: '30',
40: '40',
50: '50',
},
gap: theme => theme('spacing'),
gridTemplateColumns: {
none: 'none',
1: 'repeat(1, minmax(0, 1fr))',
2: 'repeat(2, minmax(0, 1fr))',
3: 'repeat(3, minmax(0, 1fr))',
4: 'repeat(4, minmax(0, 1fr))',
5: 'repeat(5, minmax(0, 1fr))',
6: 'repeat(6, minmax(0, 1fr))',
7: 'repeat(7, minmax(0, 1fr))',
8: 'repeat(8, minmax(0, 1fr))',
9: 'repeat(9, minmax(0, 1fr))',
10: 'repeat(10, minmax(0, 1fr))',
11: 'repeat(11, minmax(0, 1fr))',
12: 'repeat(12, minmax(0, 1fr))',
},
gridColumn: {
auto: 'auto',
'span-1': 'span 1 / span 1',
'span-2': 'span 2 / span 2',
'span-3': 'span 3 / span 3',
'span-4': 'span 4 / span 4',
'span-5': 'span 5 / span 5',
'span-6': 'span 6 / span 6',
'span-7': 'span 7 / span 7',
'span-8': 'span 8 / span 8',
'span-9': 'span 9 / span 9',
'span-10': 'span 10 / span 10',
'span-11': 'span 11 / span 11',
'span-12': 'span 12 / span 12',
},
gridColumnStart: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12',
13: '13',
},
gridColumnEnd: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12',
13: '13',
},
gridTemplateRows: {
none: 'none',
1: 'repeat(1, minmax(0, 1fr))',
2: 'repeat(2, minmax(0, 1fr))',
3: 'repeat(3, minmax(0, 1fr))',
4: 'repeat(4, minmax(0, 1fr))',
5: 'repeat(5, minmax(0, 1fr))',
6: 'repeat(6, minmax(0, 1fr))',
},
gridRow: {
auto: 'auto',
'span-1': 'span 1 / span 1',
'span-2': 'span 2 / span 2',
'span-3': 'span 3 / span 3',
'span-4': 'span 4 / span 4',
'span-5': 'span 5 / span 5',
'span-6': 'span 6 / span 6',
},
gridRowStart: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
},
gridRowEnd: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
},
transformOrigin: {
center: 'center',
top: 'top',
'top-right': 'top right',
right: 'right',
'bottom-right': 'bottom right',
bottom: 'bottom',
'bottom-left': 'bottom left',
left: 'left',
'top-left': 'top left',
},
scale: {
0: '0',
50: '.5',
75: '.75',
90: '.9',
95: '.95',
100: '1',
105: '1.05',
110: '1.1',
125: '1.25',
150: '1.5',
},
rotate: {
'-180': '-180deg',
'-90': '-90deg',
'-45': '-45deg',
0: '0',
45: '45deg',
90: '90deg',
180: '180deg',
},
translate: (theme, { negative }) => ({
...theme('spacing'),
...negative(theme('spacing')),
'-full': '-100%',
'-1/2': '-50%',
'1/2': '50%',
full: '100%',
}),
skew: {
'-12': '-12deg',
'-6': '-6deg',
'-3': '-3deg',
0: '0',
3: '3deg',
6: '6deg',
12: '12deg',
},
transitionProperty: {
none: 'none',
all: 'all',
height: 'height',
DEFAULT:
'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform',
colors: 'background-color, border-color, color, fill, stroke',
opacity: 'opacity',
shadow: 'box-shadow',
transform: 'transform',
},
transitionTimingFunction: {
linear: 'linear',
in: 'cubic-bezier(0.4, 0, 1, 1)',
out: 'cubic-bezier(0, 0, 0.2, 1)',
'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',
},
transitionDuration: {
75: '75ms',
100: '100ms',
150: '150ms',
200: '200ms',
300: '300ms',
500: '500ms',
700: '700ms',
1000: '1000ms',
},
},
variants: {
accessibility: ['responsive', 'focus'],
alignContent: ['responsive'],
alignItems: ['responsive'],
alignSelf: ['responsive'],
appearance: ['responsive'],
backgroundAttachment: ['responsive'],
backgroundColor: ['responsive', 'hover', 'focus', 'active', 'group-focus', 'group-hover'],
backgroundPosition: ['responsive'],
backgroundRepeat: ['responsive'],
backgroundSize: ['responsive'],
borderCollapse: ['responsive'],
borderColor: ['responsive', 'hover', 'focus', 'active', 'group-focus', 'group-hover'],
borderRadius: ['responsive', 'focus', 'first', 'last'],
borderStyle: ['responsive', 'focus'],
borderWidth: ['responsive', 'focus', 'first', 'last'],
boxShadow: ['responsive', 'hover', 'focus'],
boxSizing: ['responsive'],
cursor: ['responsive'],
display: ['responsive'],
fill: ['responsive'],
flex: ['responsive'],
flexDirection: ['responsive'],
flexGrow: ['responsive'],
flexShrink: ['responsive'],
flexWrap: ['responsive'],
float: ['responsive'],
clear: ['responsive'],
fontFamily: ['responsive'],
fontSize: ['responsive'],
fontSmoothing: ['responsive'],
fontStyle: ['responsive'],
fontWeight: ['responsive', 'hover', 'focus'],
height: ['responsive'],
inset: ['responsive'],
justifyContent: ['responsive'],
letterSpacing: ['responsive'],
lineHeight: ['responsive'],
listStylePosition: ['responsive'],
listStyleType: ['responsive'],
margin: ['responsive'],
maxHeight: ['responsive'],
maxWidth: ['responsive'],
minHeight: ['responsive'],
minWidth: ['responsive'],
objectFit: ['responsive'],
objectPosition: ['responsive'],
opacity: ['responsive', 'hover', 'focus', 'active'],
order: ['responsive'],
outline: ['responsive', 'focus'],
overflow: ['responsive'],
padding: ['responsive', 'first'],
placeholderColor: ['responsive', 'focus'],
pointerEvents: ['responsive'],
position: ['responsive'],
resize: ['responsive'],
stroke: ['responsive'],
strokeWidth: ['responsive'],
tableLayout: ['responsive'],
textAlign: ['responsive'],
textColor: ['responsive', 'hover', 'focus', 'active', 'group-hover'],
textDecoration: ['responsive', 'hover', 'focus'],
textTransform: ['responsive'],
userSelect: ['responsive'],
verticalAlign: ['responsive'],
visibility: ['responsive'],
whitespace: ['responsive'],
width: ['responsive'],
wordBreak: ['responsive'],
zIndex: ['responsive'],
gap: ['responsive'],
gridAutoFlow: ['responsive'],
gridTemplateColumns: ['responsive'],
gridColumn: ['responsive'],
gridColumnStart: ['responsive'],
gridColumnEnd: ['responsive'],
gridTemplateRows: ['responsive'],
gridRow: ['responsive'],
gridRowStart: ['responsive'],
gridRowEnd: ['responsive'],
transform: ['responsive'],
transformOrigin: ['responsive'],
scale: ['responsive', 'hover', 'focus'],
rotate: ['responsive', 'hover', 'focus'],
translate: ['responsive', 'hover', 'focus'],
skew: ['responsive', 'hover', 'focus'],
transitionProperty: ['responsive'],
transitionTimingFunction: ['responsive'],
transitionDuration: ['responsive'],
},
corePlugins: {},
plugins: [],
};

View File

@ -43,7 +43,40 @@ const config = {
},
],
module: {
rules: [
{
test: /\.svg?$/,
oneOf: [
{
use: [
{
loader: '@svgr/webpack',
options: {
svgoConfig: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false
},
},
},
]
},
prettier: false,
svgo: true,
titleProp: true,
},
},
],
issuer: {
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
},
},
],
},
{
test: /(\.jsx|\.js|\.tsx|\.ts)$/,
loader: 'babel-loader',

View File

@ -1,21 +1,5 @@
// https://babeljs.io/docs/en/options#babelrcroots
const { extendDefaultPlugins } = require('svgo');
module.exports = {
plugins: [
[
'inline-react-svg',
{
svgo: {
plugins: extendDefaultPlugins([
{
name: 'removeViewBox',
active: false,
},
]),
},
},
],
['@babel/plugin-proposal-class-properties', { loose: true }],
'@babel/plugin-transform-typescript',
['@babel/plugin-proposal-private-property-in-object', { loose: true }],

View File

@ -20,8 +20,8 @@
"@ohif/extension-cornerstone": "^3.0.0",
"@ohif/i18n": "^1.0.0",
"prop-types": "^15.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^12.2.2",
"react-router": "^6.8.1",
"react-router-dom": "^6.8.1",
@ -47,7 +47,7 @@
"@babel/plugin-proposal-private-property-in-object": "7.21.11",
"babel-eslint": "9.x",
"babel-loader": "^8.2.4",
"babel-plugin-inline-react-svg": "^2.0.2",
"@svgr/webpack": "^8.1.0",
"babel-plugin-module-resolver": "^5.0.0",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.2.0",

View File

@ -49,6 +49,38 @@ const config = {
],
module: {
rules: [
{
test: /\.svg?$/,
oneOf: [
{
use: [
{
loader: '@svgr/webpack',
options: {
svgoConfig: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false
},
},
},
]
},
prettier: false,
svgo: true,
titleProp: true,
},
},
],
issuer: {
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
},
},
],
},
{
test: /(\.jsx|\.js|\.tsx|\.ts)$/,
loader: 'babel-loader',

View File

@ -1,5 +1,5 @@
module.exports = {
plugins: ['inline-react-svg', '@babel/plugin-proposal-class-properties'],
plugins: ['@babel/plugin-proposal-class-properties'],
env: {
test: {
presets: [

View File

@ -37,7 +37,7 @@
"@babel/preset-typescript": "^7.13.0",
"babel-eslint": "^8.0.3",
"babel-loader": "^8.0.0-beta.4",
"babel-plugin-inline-react-svg": "^2.0.1",
"@svgr/webpack": "^8.1.0",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.2.0",
"cross-env": "^7.0.3",

View File

@ -47,8 +47,8 @@
"url-loader": "^4.1.1"
},
"peerDependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"browserslist": {
"production": [

View File

@ -33,8 +33,8 @@
"peerDependencies": {
"i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^12.2.2"
},
"dependencies": {
@ -46,8 +46,8 @@
"devDependencies": {
"i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^12.2.2",
"webpack-merge": "^5.7.3"
}

View File

@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/tailwind.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "src/components",
"utils": "src/lib/utils"
}
}

View File

@ -0,0 +1,37 @@
{
"name": "@ohif/ui-next",
"version": "3.9.0-beta.14",
"description": "Next version of OHIF Viewers UI, more customizable using shadcn/ui",
"main": "index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"exports": {
"./tailwind.config": "./tailwind.config.ts",
"./lib/*": "./src/lib/*.ts",
"./components/*": "./src/components/*.tsx",
".": "./src/index.ts"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-slot": "^1.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
"framer-motion": "6.2.4",
"lucide-react": "^0.379.0",
"next-themes": "^0.3.0",
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
"sonner": "^1.4.41",
"tailwind-merge": "^2.3.0",
"tailwindcss": "3.2.4",
"tailwindcss-animate": "^1.0.7"
},
"keywords": [],
"author": "OHIF",
"license": "MIT"
}

View File

@ -0,0 +1,54 @@
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '../../lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
outline:
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-9 px-4 py-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = 'Button';
export { Button, buttonVariants };

View File

@ -0,0 +1 @@
export { Button , buttonVariants} from "./Button"

View File

@ -0,0 +1,61 @@
import * as React from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { DayPicker } from 'react-day-picker';
import { cn } from '../../lib/utils';
import { buttonVariants } from '../Button';
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn('p-3', className)}
captionLayout="dropdown"
fromYear={1945}
toYear={2024}
labels={{
labelMonthDropdown: () => undefined,
labelYearDropdown: () => undefined,
}}
classNames={{
months: 'flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0',
month: 'space-y-4',
caption: 'flex justify-between items-center px-2',
caption_dropdowns: 'flex space-x-2',
caption_label: 'hidden',
nav: 'space-x-1 flex items-center',
table: 'w-full border-collapse space-y-1',
head_row: 'flex',
head_cell: 'text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]',
row: 'flex w-full mt-2',
cell: 'h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20',
day: cn(
buttonVariants({ variant: 'ghost' }),
'h-9 w-9 p-0 font-normal aria-selected:opacity-100'
),
day_range_end: 'day-range-end',
day_selected:
'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
day_today: 'bg-accent text-accent-foreground',
day_outside:
'day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',
day_disabled: 'text-muted-foreground opacity-50',
day_range_middle: 'aria-selected:bg-accent aria-selected:text-accent-foreground',
day_hidden: 'invisible',
...classNames,
}}
components={{
IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
}}
{...props}
/>
);
}
Calendar.displayName = 'Calendar';
export { Calendar };

View File

@ -0,0 +1,3 @@
import { Calendar } from './Calendar';
export default Calendar;

View File

@ -0,0 +1,66 @@
import * as React from 'react';
import { Check, ChevronsUpDown } from 'lucide-react';
import { cn } from '../../lib/utils';
import { Button } from '../Button/Button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '../Command/Command';
import { Popover, PopoverContent, PopoverTrigger } from '../Popover/Popover';
export function Combobox({ data = [], placeholder = 'Select item...' }) {
const [open, setOpen] = React.useState(false);
const [value, setValue] = React.useState('');
return (
<Popover
open={open}
onOpenChange={setOpen}
>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-[200px] justify-between"
>
{value ? data.find(item => item.value === value)?.label : placeholder}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandInput placeholder={`Search ${placeholder.toLowerCase()}...`} />
<CommandEmpty>No {placeholder.toLowerCase()} found.</CommandEmpty>
<CommandList>
<CommandGroup>
{data.map(item => (
<CommandItem
key={item.value}
value={item.value}
onSelect={currentValue => {
setValue(currentValue === value ? '' : currentValue);
setOpen(false);
}}
>
<Check
className={cn(
'mr-2 h-4 w-4',
value === item.value ? 'opacity-100' : 'opacity-0'
)}
/>
{item.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View File

@ -0,0 +1,3 @@
import { Combobox } from './Combobox';
export default Combobox;

View File

@ -0,0 +1,150 @@
import * as React from 'react';
import { type DialogProps } from '@radix-ui/react-dialog';
import { MagnifyingGlassIcon } from '@radix-ui/react-icons';
import { Command as CommandPrimitive } from 'cmdk';
import { cn } from '../../lib/utils';
import { Dialog, DialogContent } from '../Dialog/Dialog';
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
'bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md',
className
)}
{...props}
/>
));
Command.displayName = CommandPrimitive.displayName;
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0">
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
);
};
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div
className="flex items-center border-b px-3"
cmdk-input-wrapper=""
>
<MagnifyingGlassIcon className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
'placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50',
className
)}
{...props}
/>
</div>
));
CommandInput.displayName = CommandPrimitive.Input.displayName;
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn('max-h-[300px] overflow-y-auto overflow-x-hidden', className)}
{...props}
/>
));
CommandList.displayName = CommandPrimitive.List.displayName;
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm"
{...props}
/>
));
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
'text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium',
className
)}
{...props}
/>
));
CommandGroup.displayName = CommandPrimitive.Group.displayName;
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn('bg-border -mx-1 h-px', className)}
{...props}
/>
));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
'aria-selected:bg-accent aria-selected:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50',
className
)}
{...props}
/>
));
CommandItem.displayName = CommandPrimitive.Item.displayName;
const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
{...props}
/>
);
};
CommandShortcut.displayName = 'CommandShortcut';
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
};

View File

@ -0,0 +1,3 @@
import * as CommandComponents from './Command';
export default CommandComponents;

View File

@ -0,0 +1,153 @@
import * as React from 'react';
import { format, parse, isValid } from 'date-fns';
import { Calendar as CalendarIcon } from 'lucide-react';
import { cn } from '../../lib/utils';
import Calendar from '../Calendar';
import Popover from '../Popover';
export type DatePickerWithRangeProps = {
id: string;
/** YYYYMMDD (19921022) */
startDate: string;
/** YYYYMMDD (19921022) */
endDate: string;
/** Callback that received { startDate: string(YYYYMMDD), endDate: string(YYYYMMDD)} */
onChange: (value: { startDate: string; endDate: string }) => void;
};
export function DatePickerWithRange({
className,
id,
startDate,
endDate,
onChange,
...props
}: React.HTMLAttributes<HTMLDivElement> & DatePickerWithRangeProps) {
const [start, setStart] = React.useState<string>(
startDate ? format(parse(startDate, 'yyyyMMdd', new Date()), 'yyyy-MM-dd') : ''
);
const [end, setEnd] = React.useState<string>(
endDate ? format(parse(endDate, 'yyyyMMdd', new Date()), 'yyyy-MM-dd') : ''
);
const [openEnd, setOpenEnd] = React.useState(false);
const handleStartSelect = (selectedDate: Date | undefined) => {
if (selectedDate) {
const formattedDate = format(selectedDate, 'yyyy-MM-dd');
setStart(formattedDate);
setOpenEnd(true);
onChange({
startDate: format(selectedDate, 'yyyyMMdd'),
endDate: end.replace(/-/g, ''),
});
}
};
const handleEndSelect = (selectedDate: Date | undefined) => {
if (selectedDate) {
const formattedDate = format(selectedDate, 'yyyy-MM-dd');
setEnd(formattedDate);
setOpenEnd(false);
onChange({
startDate: start.replace(/-/g, ''),
endDate: format(selectedDate, 'yyyyMMdd'),
});
}
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>, type: 'start' | 'end') => {
const value = e.target.value;
const date = parse(value, 'yyyy-MM-dd', new Date());
if (type === 'start') {
setStart(value);
if (isValid(date)) {
handleStartSelect(date);
}
} else {
setEnd(value);
if (isValid(date)) {
handleEndSelect(date);
}
}
};
React.useEffect(() => {
setStart(startDate ? format(parse(startDate, 'yyyyMMdd', new Date()), 'yyyy-MM-dd') : '');
setEnd(endDate ? format(parse(endDate, 'yyyyMMdd', new Date()), 'yyyy-MM-dd') : '');
}, [startDate, endDate]);
return (
<div className={cn('flex gap-2', className)}>
<Popover.Popover>
<Popover.PopoverTrigger asChild>
<div className="relative w-full">
<CalendarIcon className="absolute right-2 top-1/2 h-4 w-4 -translate-y-1/2 transform text-white" />
<input
id={`${id}-start`}
type="text"
placeholder="Start date"
autoComplete="off"
value={start}
onChange={e => handleInputChange(e, 'start')}
className={cn(
'border-inputfield-main focus:border-inputfield-focus h-full w-full justify-start rounded border bg-black py-[6.5px] pl-[6.5px] pr-[6.5px] text-left text-sm font-normal hover:bg-black hover:text-white',
!start && 'text-muted-foreground'
)}
data-cy="input-date-range-start"
/>
</div>
</Popover.PopoverTrigger>
<Popover.PopoverContent
className="w-auto p-0"
align="start"
>
<Calendar
initialFocus
mode="single"
defaultMonth={start ? parse(start, 'yyyy-MM-dd', new Date()) : new Date()}
selected={start ? parse(start, 'yyyy-MM-dd', new Date()) : undefined}
onSelect={handleStartSelect}
numberOfMonths={1}
/>
</Popover.PopoverContent>
</Popover.Popover>
<Popover.Popover
open={openEnd}
onOpenChange={setOpenEnd}
>
<Popover.PopoverTrigger asChild>
<div className="relative w-full">
<CalendarIcon className="absolute right-2 top-1/2 h-4 w-4 -translate-y-1/2 transform text-white" />
<input
id={`${id}-end`}
type="text"
placeholder="End date"
autoComplete="off"
value={end}
onChange={e => handleInputChange(e, 'end')}
className={cn(
'border-inputfield-main focus:border-inputfield-focus h-full w-full justify-start rounded border bg-black py-[6.5px] pl-[6.5px] pr-[6.5px] text-left text-sm font-normal hover:bg-black hover:text-white',
!end && 'text-muted-foreground'
)}
data-cy="input-date-range-end"
/>
</div>
</Popover.PopoverTrigger>
<Popover.PopoverContent
className="w-auto p-0"
align="start"
>
<Calendar
initialFocus
mode="single"
defaultMonth={start ? parse(start, 'yyyy-MM-dd', new Date()) : new Date()}
selected={end ? parse(end, 'yyyy-MM-dd', new Date()) : undefined}
onSelect={handleEndSelect}
numberOfMonths={1}
/>
</Popover.PopoverContent>
</Popover.Popover>
</div>
);
}

View File

@ -0,0 +1,3 @@
import { DatePickerWithRange } from "./DateRange";
export default DatePickerWithRange;

View File

@ -0,0 +1,105 @@
import * as React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { Cross2Icon } from '@radix-ui/react-icons';
import { cn } from '../../lib/utils';
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80',
className
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg',
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)}
{...props}
/>
);
DialogHeader.displayName = 'DialogHeader';
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...props}
/>
);
DialogFooter.displayName = 'DialogFooter';
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn('text-lg font-semibold leading-none tracking-tight', className)}
{...props}
/>
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
};

View File

@ -0,0 +1,3 @@
import * as DialogComponents from './Dialog';
export default DialogComponents;

View File

@ -0,0 +1,31 @@
import * as React from 'react';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import { cn } from '../../lib/utils';
const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverAnchor = PopoverPrimitive.Anchor;
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md outline-none',
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };

View File

@ -0,0 +1,3 @@
import * as Popover from './Popover';
export default Popover;

View File

@ -0,0 +1,4 @@
import React from 'react';
import '../../tailwind.css';
export const ThemeWrapper = ({ children }) => <React.Fragment>{children}</React.Fragment>;

View File

@ -0,0 +1 @@
export { ThemeWrapper } from './ThemeWrapper';

View File

@ -0,0 +1,10 @@
import { Button, buttonVariants } from './Button';
import { ThemeWrapper } from './ThemeWrapper';
import Command from './Command';
import Dialog from './Dialog';
import Combobox from './Combobox';
import Popover from './Popover';
import Calendar from './Calendar';
import DatePickerWithRange from './DateRange';
export { Button, buttonVariants, ThemeWrapper, Command, Dialog, Combobox, Popover, Calendar, DatePickerWithRange};

View File

@ -0,0 +1,85 @@
import React, { useState, createContext, useContext, useCallback, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Toaster, toast } from 'sonner';
const NotificationContext = createContext(null);
export const useNotification = () => useContext(NotificationContext);
const NotificationProvider = ({ children, service }) => {
const DEFAULT_OPTIONS = {
title: '',
message: '',
duration: 5000,
position: 'bottom-right', // Aligning to Sonner's positioning system
type: 'info', // info, success, error
};
const [notificationItems, setNotificationItems] = useState([]);
const show = useCallback(options => {
const { title, message, duration, position, type } = { ...DEFAULT_OPTIONS, ...options };
const id = toast[type](message, {
duration,
position,
render: () => (
<div>
<strong>{title}</strong>
<p>{message}</p>
</div>
),
});
setNotificationItems(state => [...state, { id, ...options }]);
}, []);
const hide = useCallback(id => {
toast.dismiss(id);
setNotificationItems(state => state.filter(item => item.id !== id));
}, []);
const hideAll = useCallback(() => {
toast.dismiss();
setNotificationItems([]);
}, []);
useEffect(() => {
window.notification = { show, hide, hideAll };
}, [show, hide, hideAll]);
/**
* Sets the implementation of a notification service that can be used by extensions.
*
* @returns void
*/
useEffect(() => {
if (service) {
service.setServiceImplementation({ hide, show });
}
}, [service, hide, show]);
return (
<NotificationContext.Provider value={{ show, hide, hideAll, notificationItems }}>
<Toaster position="bottom-right" />
{children}
</NotificationContext.Provider>
);
};
NotificationProvider.propTypes = {
children: PropTypes.node.isRequired,
};
export const withNotification = Component => {
return function WrappedComponent(props) {
const notificationContext = useNotification();
return (
<Component
{...props}
notificationContext={notificationContext}
/>
);
};
};
export default NotificationProvider;

View File

@ -0,0 +1,3 @@
import NotificationProvider, { useNotification } from './NotificationProvider';
export { useNotification, NotificationProvider };

View File

@ -0,0 +1,28 @@
import {
Button,
buttonVariants,
ThemeWrapper,
Dialog,
Command,
Popover,
Combobox,
Calendar,
DatePickerWithRange,
} from './components';
import { useNotification, NotificationProvider } from './contextProviders';
export {
// components
Button,
Dialog,
Command,
Popover,
Combobox,
buttonVariants,
ThemeWrapper,
Calendar,
DatePickerWithRange,
// contextProviders
NotificationProvider,
useNotification,
};

View File

@ -0,0 +1,6 @@
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

View File

@ -0,0 +1,67 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}

View File

@ -0,0 +1,70 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
presets: [require('../ui/tailwind.config.js')],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
prefix: '',
theme: {
extend: {
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
},
keyframes: {
'accordion-down': {
from: { height: '0' },
to: { height: 'var(--radix-accordion-content-height)' },
},
'accordion-up': {
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: '0' },
},
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
},
},
},
plugins: [require('tailwindcss-animate')],
};

View File

@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@ui/*": ["./src/*"]
}
},
"include": ["src"],
"exclude": ["node_modules"]
}

View File

@ -29,8 +29,8 @@
"build-storybook": "storybook build"
},
"peerDependencies": {
"react": "17.0.2",
"react-dom": "17.0.2"
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"dependencies": {
"@testing-library/react-hooks": "^3.2.1",
@ -46,11 +46,11 @@
"lodash.debounce": "4.0.8",
"moment": "2.29.4",
"mousetrap": "^1.6.5",
"react": "^17.0.2",
"react": "^18.3.1",
"react-dates": "^21.8.0",
"react-dnd": "14.0.2",
"react-dnd-html5-backend": "14.0.0",
"react-dom": "^17.0.2",
"react-dom": "^18.3.1",
"react-draggable": "4.4.3",
"react-error-boundary": "^3.1.3",
"react-modal": "3.11.2",

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { Button, ButtonEnums } from '../../components';
function ActionButtons({ actions, disabled, t }) {
function ActionButtons({ actions, disabled = false, t }) {
return (
<React.Fragment>
{actions.map((action, index) => (
@ -33,8 +33,4 @@ ActionButtons.propTypes = {
disabled: PropTypes.bool,
};
ActionButtons.defaultProps = {
disabled: false,
};
export default ActionButtons;

View File

@ -58,7 +58,7 @@ const defaults = {
};
const Button = ({
children,
children = '',
size = defaults.size,
disabled = defaults.disabled,
type = defaults.type,
@ -66,7 +66,7 @@ const Button = ({
endIcon: endIconProp,
name,
className,
onClick,
onClick = () => {},
dataCY,
startIconTooltip = null,
endIconTooltip = null,
@ -121,14 +121,6 @@ const Button = ({
);
};
Button.defaultProps = {
disabled: false,
children: '',
onClick: () => {},
type: defaults.type,
size: defaults.size,
};
Button.propTypes = {
/** What is inside the button, can be text or react component */
children: PropTypes.node,

View File

@ -31,15 +31,15 @@ const fpsButtonClassNames =
const CinePlayer: React.FC<CinePlayerProps> = ({
className,
isPlaying,
minFrameRate,
maxFrameRate,
stepFrameRate,
frameRate: defaultFrameRate,
isPlaying = false,
minFrameRate = 1,
maxFrameRate = 90,
stepFrameRate = 1,
frameRate: defaultFrameRate = 24,
onFrameRateChange = () => {},
onPlayPauseChange = () => {},
onClose = () => {},
dynamicInfo = {},
onFrameRateChange,
onPlayPauseChange,
onClose,
updateDynamicInfo,
}) => {
const isDynamic = !!dynamicInfo?.numTimePoints;
@ -163,21 +163,6 @@ const CinePlayer: React.FC<CinePlayerProps> = ({
);
};
const noop = () => {};
CinePlayer.defaultProps = {
isPlaying: false,
minFrameRate: 1,
maxFrameRate: 90,
stepFrameRate: 1,
frameRate: 24,
onPlayPauseChange: noop,
onFrameRateChange: noop,
onClose: noop,
isDynamic: false,
dynamicInfo: {},
};
CinePlayer.propTypes = {
/** Minimum value for range slider */
minFrameRate: PropTypes.number,

View File

@ -29,7 +29,7 @@ const renderYearsOptions = () => {
};
const DateRange = props => {
const { id, onChange, startDate, endDate } = props;
const { id = '', onChange, startDate = null, endDate = null } = props;
const [focusedInput, setFocusedInput] = useState(null);
const renderYearsOptionsCallback = useCallback(renderYearsOptions, []);
const { t } = useTranslation('DatePicker');
@ -165,12 +165,6 @@ const DateRange = props => {
);
};
DateRange.defaultProps = {
id: '',
startDate: null,
endDate: null,
};
DateRange.propTypes = {
id: PropTypes.string,
/** YYYYMMDD (19921022) */

View File

@ -15,10 +15,10 @@ const Dialog = ({
actions,
onShow,
onSubmit,
header: HeaderComponent,
body: BodyComponent,
footer: FooterComponent,
value: defaultValue,
header: HeaderComponent = Header,
body: BodyComponent = Body,
footer: FooterComponent = Footer,
value: defaultValue = {},
}) => {
const [value, setValue] = useState(defaultValue);
@ -78,13 +78,7 @@ Dialog.propTypes = {
type: PropTypes.oneOf(['primary', 'secondary', 'cancel']).isRequired,
})
).isRequired,
};
Dialog.defaultProps = {
header: Header,
footer: Footer,
body: Body,
value: {},
onShow: PropTypes.func,
};
export default Dialog;

View File

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import Button, { ButtonEnums } from '../Button';
const Footer = ({ actions, className, onSubmit, value }) => {
const Footer = ({ actions = [], className, onSubmit = () => {}, value }) => {
const flex = 'flex items-center justify-end';
const padding = 'pt-[20px]';
@ -47,9 +47,4 @@ Footer.propTypes = {
).isRequired,
};
Footer.defaultProps = {
onSubmit: noop,
actions: [],
};
export default Footer;

View File

@ -20,7 +20,7 @@ CloseButton.propTypes = {
onClick: PropTypes.func,
};
const Header = ({ title, noCloseButton, onClose }) => {
const Header = ({ title, noCloseButton = false, onClose }) => {
const theme = 'bg-primary-dark';
const flex = 'flex items-center justify-between';
const padding = 'pb-[20px]';
@ -46,8 +46,4 @@ Header.propTypes = {
onClose: PropTypes.func,
};
Header.defaultProps = {
noCloseButton: false,
};
export default Header;

View File

@ -11,15 +11,15 @@ const borderStyle = 'border-b last:border-b-0 border-secondary-main';
const Dropdown = ({
id,
children,
showDropdownIcon,
showDropdownIcon = true,
list,
itemsClassName,
titleClassName,
showBorders,
showBorders = true,
alignment,
// By default the max characters per line is the longest title
// if you wish to override this, you can pass in a number
maxCharactersPerLine,
maxCharactersPerLine = 20,
}) => {
const [open, setOpen] = useState(false);
const elementRef = useRef(null);
@ -199,11 +199,6 @@ const Dropdown = ({
</div>
);
};
Dropdown.defaultProps = {
showDropdownIcon: true,
maxCharactersPerLine: 20,
showBorders: true,
};
Dropdown.propTypes = {
id: PropTypes.string,

View File

@ -7,7 +7,7 @@ import Icon from '../Icon';
import Typography from '../Typography';
// TODO: Add loading spinner to OHIF + use it here.
const EmptyStudies = ({ className }) => {
const EmptyStudies = ({ className = '' }) => {
const { t } = useTranslation('StudyList');
return (
<div className={classnames('inline-flex flex-col items-center', className)}>
@ -25,10 +25,6 @@ const EmptyStudies = ({ className }) => {
);
};
EmptyStudies.defaultProps = {
className: '',
};
EmptyStudies.propTypes = {
className: PropTypes.string,
};

View File

@ -10,7 +10,7 @@ import IconButton from '../IconButton';
const isProduction = process.env.NODE_ENV === 'production';
const DefaultFallback = ({ error, context, resetErrorBoundary, fallbackRoute }) => {
const DefaultFallback = ({ error, context, resetErrorBoundary = () => {}, fallbackRoute }) => {
const { t } = useTranslation('ErrorBoundary');
const [showDetails, setShowDetails] = useState(false);
const title = `${t('Something went wrong')}${!isProduction && ` ${t('in')} ${context}`}.`;
@ -57,25 +57,19 @@ const DefaultFallback = ({ error, context, resetErrorBoundary, fallbackRoute })
);
};
const noop = () => {};
DefaultFallback.propTypes = {
error: PropTypes.object.isRequired,
resetErrorBoundary: PropTypes.func,
componentStack: PropTypes.string,
};
DefaultFallback.defaultProps = {
resetErrorBoundary: noop,
};
const ErrorBoundary = ({
context,
onReset,
onError,
fallbackComponent: FallbackComponent,
context = 'OHIF',
onReset = () => {},
onError = () => {},
fallbackComponent: FallbackComponent = DefaultFallback,
children,
fallbackRoute,
fallbackRoute = null,
isPage,
}) => {
const [isOpen, setIsOpen] = useState(true);
@ -132,12 +126,4 @@ ErrorBoundary.propTypes = {
fallbackRoute: PropTypes.string,
};
ErrorBoundary.defaultProps = {
context: 'OHIF',
onReset: noop,
onError: noop,
fallbackComponent: DefaultFallback,
fallbackRoute: null,
};
export default ErrorBoundary;

View File

@ -8,14 +8,14 @@ import Icon from '../Icon';
import './ExpandableToolbarButton.css';
const ExpandableToolbarButton = ({
type,
id,
isActive,
onClick,
icon,
type = 'primary',
id = '',
isActive = false,
onClick = () => {},
icon = 'clipboard',
className,
content: Content,
contentProps,
content: Content = null,
contentProps = {},
}) => {
const classes = {
type: {
@ -71,15 +71,6 @@ const ExpandableToolbarButton = ({
const noop = () => {};
ExpandableToolbarButton.defaultProps = {
id: '',
icon: 'clipboard',
isActive: false,
type: 'primary',
content: null,
onClick: noop,
};
ExpandableToolbarButton.propTypes = {
/* Influences background/hover styling */
type: PropTypes.oneOf(['primary', 'secondary']),

View File

@ -14,9 +14,9 @@ import { PatientInfoVisibility } from '../../types/PatientInfoVisibility';
function Header({
children,
menuOptions,
isReturnEnabled,
isReturnEnabled = true,
onClickReturnButton,
isSticky,
isSticky = false,
WhiteLabeling,
showPatientInfo = PatientInfoVisibility.VISIBLE_COLLAPSED,
servicesManager,
@ -113,9 +113,4 @@ Header.propTypes = {
servicesManager: PropTypes.object,
};
Header.defaultProps = {
isReturnEnabled: true,
isSticky: false,
};
export default Header;

View File

@ -15,7 +15,7 @@ import { getKeys, formatKeysForInput } from './utils';
* @param {string} props.className input classes
* @param {Array[]} props.modifierKeys
*/
const HotkeyField = ({ disabled, keys, onChange, className, modifierKeys, hotkeys }) => {
const HotkeyField = ({ disabled = false, keys, onChange, className, modifierKeys, hotkeys }) => {
const inputValue = formatKeysForInput(keys);
const onInputKeyDown = event => {
@ -58,8 +58,4 @@ HotkeyField.propTypes = {
}).isRequired,
};
HotkeyField.defaultProps = {
disabled: false,
};
export default HotkeyField;

View File

@ -11,10 +11,10 @@ import { MODIFIER_KEYS } from './hotkeysConfig';
import { validate, splitHotkeyDefinitionsAndCreateTuples } from './utils';
const HotkeysPreferences = ({
disabled,
disabled = false,
hotkeyDefinitions,
errors: controlledErrors,
onChange,
onChange = () => {},
hotkeysModule,
}) => {
const { t } = useTranslation('UserPreferencesModal');
@ -127,8 +127,6 @@ const HotkeysPreferences = ({
);
};
const noop = () => {};
HotkeysPreferences.propTypes = {
onChange: PropTypes.func,
disabled: PropTypes.bool,
@ -142,9 +140,4 @@ HotkeysPreferences.propTypes = {
}).isRequired,
};
HotkeysPreferences.defaultProps = {
onChange: noop,
disabled: false,
};
export default HotkeysPreferences;

View File

@ -1,179 +1,179 @@
import React from 'react';
// Icons
import arrowDown from './../../assets/icons/arrow-down.svg';
import arrowLeft from './../../assets/icons/arrow-left.svg';
import arrowRight from './../../assets/icons/arrow-right.svg';
import arrowLeftSmall from './../../assets/icons/arrow-left-small.svg';
import arrowRightSmall from './../../assets/icons/arrow-right-small.svg';
import calendar from './../../assets/icons/calendar.svg';
import cancel from './../../assets/icons/cancel.svg';
import clipboard from './../../assets/icons/clipboard.svg';
import close from './../../assets/icons/closeIcon.svg';
import dottedCircle from './../../assets/icons/dotted-circle.svg';
import circledCheckmark from './../../assets/icons/circled-checkmark.svg';
import chevronDown from './../../assets/icons/chevron-down.svg';
import chevronLeft from './../../assets/icons/chevron-left.svg';
import chevronMenu from './../../assets/icons/chevron-menu.svg';
import chevronNext from './../../assets/icons/chevron-next.svg';
import chevronPrev from './../../assets/icons/chevron-prev.svg';
import chevronRight from './../../assets/icons/chevron-right.svg';
import contentNext from './../../assets/icons/content-next.svg';
import contentPrev from './../../assets/icons/content-prev.svg';
import eyeVisible from './../../assets/icons/eye-visible.svg';
import eyeHidden from './../../assets/icons/eye-hidden.svg';
import exclamation from './../../assets/icons/exclamation.svg';
import externalLink from './../../assets/icons/external-link.svg';
import groupLayers from './../../assets/icons/group-layers.svg';
import info from './../../assets/icons/info.svg';
import infoAction from './../../assets/icons/info-action.svg';
import infoLink from './../../assets/icons/info-link.svg';
import launchArrow from './../../assets/icons/launch-arrow.svg';
import launchInfo from './../../assets/icons/launch-info.svg';
import link from './../../assets/icons/tool-stack-image-sync.svg';
import listBullets from './../../assets/icons/list-bullets.svg';
import lock from './../../assets/icons/lock.svg';
import logoOhifSmall from './../../assets/icons/logo-ohif-small.svg';
import logoDarkBackGround from './../../assets/icons/ohif-logo-color-darkbg.svg';
import magnifier from './../../assets/icons/magnifier.svg';
import notificationwarningDiamond from './../../assets/icons/notificationwarning-diamond.svg';
import pencil from './../../assets/icons/pencil.svg';
import powerOff from './../../assets/icons/power-off.svg';
import profile from './../../assets/icons/profile.svg';
import pushLeft from './../../assets/icons/push-left.svg';
import pushRight from './../../assets/icons/push-right.svg';
import settings from './../../assets/icons/settings.svg';
import sidePanelCloseLeft from './../../assets/icons/side-panel-close-left.svg';
import sidePanelCloseRight from './../../assets/icons/side-panel-close-right.svg';
import sorting from './../../assets/icons/sorting.svg';
import sortingActiveDown from './../../assets/icons/sorting-active-down.svg';
import sortingActiveUp from './../../assets/icons/sorting-active-up.svg';
import statusAlertWarning from './../../assets/icons/status-alert-warning.svg';
import statusAlert from './../../assets/icons/status-alert.svg';
import statusLocked from './../../assets/icons/status-locked.svg';
import statusTracked from './../../assets/icons/status-tracked.svg';
import statusUntracked from './../../assets/icons/status-untracked.svg';
import tracked from './../../assets/icons/tracked.svg';
import unlink from './../../assets/icons/unlink.svg';
import checkboxChecked from './../../assets/icons/checkbox-checked.svg';
import checkboxUnchecked from './../../assets/icons/checkbox-unchecked.svg';
import iconAlertOutline from './../../assets/icons/icons-alert-outline.svg';
import iconAlertSmall from './../../assets/icons/icon-alert-small.svg';
import iconClose from './../../assets/icons/icon-close.svg';
import iconClearField from './../../assets/icons/icon-clear-field.svg';
import iconNextInactive from './../../assets/icons/icon-next-inactive.svg';
import iconNext from './../../assets/icons/icon-next.svg';
import iconPlay from './../../assets/icons/icon-play.svg';
import iconPause from './../../assets/icons/icon-pause.svg';
import iconPrevInactive from './../../assets/icons/icon-prev-inactive.svg';
import iconPrev from './../../assets/icons/icon-prev.svg';
import iconSearch from './../../assets/icons/icon-search.svg';
import iconStatusAlert from './../../assets/icons/icon-status-alert.svg';
import iconTransferring from './../../assets/icons/icon-transferring.svg';
import iconUpload from './../../assets/icons/icon-upload.svg';
import navigationPanelRightHide from './../../assets/icons/navigation-panel-right-hide.svg';
import navigationPanelRightReveal from './../../assets/icons/navigation-panel-right-reveal.svg';
import tabLinear from './../../assets/icons/tab-linear.svg';
import tabPatientInfo from './../../assets/icons/tab-patient-info.svg';
import tabROIThreshold from './../../assets/icons/tab-roi-threshold.svg';
import tabSegmentation from './../../assets/icons/tab-segmentation.svg';
import tabStudies from './../../assets/icons/tab-studies.svg';
import uiArrowDown from './../../assets/icons/ui-arrow-down.svg';
import uiArrowUp from './../../assets/icons/ui-arrow-up.svg';
import uiArrowLeft from './../../assets/icons/ui-arrow-left.svg';
import uiArrowRight from './../../assets/icons/ui-arrow-right.svg';
import loadingOHIFMark from './../../assets/icons/loading-ohif-mark.svg';
import notificationsInfo from './../../assets/icons/notifications-info.svg';
import notificationsWarning from './../../assets/icons/notifications-warning.svg';
import notificationsError from './../../assets/icons/notifications-error.svg';
import notificationsSuccess from './../../assets/icons/notifications-success.svg';
import nextArrow from './../../assets/icons/next-arrow.svg';
import prevArrow from './../../assets/icons/prev-arrow.svg';
import viewportStatusTracked from './../../assets/icons/viewport-status-tracked.svg';
import { ReactComponent as arrowDown } from './../../assets/icons/arrow-down.svg';
import { ReactComponent as arrowLeft } from './../../assets/icons/arrow-left.svg';
import { ReactComponent as arrowRight } from './../../assets/icons/arrow-right.svg';
import { ReactComponent as arrowLeftSmall } from './../../assets/icons/arrow-left-small.svg';
import { ReactComponent as arrowRightSmall } from './../../assets/icons/arrow-right-small.svg';
import { ReactComponent as calendar } from './../../assets/icons/calendar.svg';
import { ReactComponent as cancel } from './../../assets/icons/cancel.svg';
import { ReactComponent as clipboard } from './../../assets/icons/clipboard.svg';
import { ReactComponent as close } from './../../assets/icons/closeIcon.svg';
import { ReactComponent as dottedCircle } from './../../assets/icons/dotted-circle.svg';
import { ReactComponent as circledCheckmark } from './../../assets/icons/circled-checkmark.svg';
import { ReactComponent as chevronDown } from './../../assets/icons/chevron-down.svg';
import { ReactComponent as chevronLeft } from './../../assets/icons/chevron-left.svg';
import { ReactComponent as chevronMenu } from './../../assets/icons/chevron-menu.svg';
import { ReactComponent as chevronNext } from './../../assets/icons/chevron-next.svg';
import { ReactComponent as chevronPrev } from './../../assets/icons/chevron-prev.svg';
import { ReactComponent as chevronRight } from './../../assets/icons/chevron-right.svg';
import { ReactComponent as contentNext } from './../../assets/icons/content-next.svg';
import { ReactComponent as contentPrev } from './../../assets/icons/content-prev.svg';
import { ReactComponent as eyeVisible } from './../../assets/icons/eye-visible.svg';
import { ReactComponent as eyeHidden } from './../../assets/icons/eye-hidden.svg';
import { ReactComponent as exclamation } from './../../assets/icons/exclamation.svg';
import { ReactComponent as externalLink } from './../../assets/icons/external-link.svg';
import { ReactComponent as groupLayers } from './../../assets/icons/group-layers.svg';
import { ReactComponent as info } from './../../assets/icons/info.svg';
import { ReactComponent as infoAction } from './../../assets/icons/info-action.svg';
import { ReactComponent as infoLink } from './../../assets/icons/info-link.svg';
import { ReactComponent as launchArrow } from './../../assets/icons/launch-arrow.svg';
import { ReactComponent as launchInfo } from './../../assets/icons/launch-info.svg';
import { ReactComponent as link } from './../../assets/icons/tool-stack-image-sync.svg';
import { ReactComponent as listBullets } from './../../assets/icons/list-bullets.svg';
import { ReactComponent as lock } from './../../assets/icons/lock.svg';
import { ReactComponent as logoOhifSmall } from './../../assets/icons/logo-ohif-small.svg';
import { ReactComponent as logoDarkBackGround } from './../../assets/icons/ohif-logo-color-darkbg.svg';
import { ReactComponent as magnifier } from './../../assets/icons/magnifier.svg';
import { ReactComponent as notificationwarningDiamond } from './../../assets/icons/notificationwarning-diamond.svg';
import { ReactComponent as pencil } from './../../assets/icons/pencil.svg';
import { ReactComponent as powerOff } from './../../assets/icons/power-off.svg';
import { ReactComponent as profile } from './../../assets/icons/profile.svg';
import { ReactComponent as pushLeft } from './../../assets/icons/push-left.svg';
import { ReactComponent as pushRight } from './../../assets/icons/push-right.svg';
import { ReactComponent as settings } from './../../assets/icons/settings.svg';
import { ReactComponent as sidePanelCloseLeft } from './../../assets/icons/side-panel-close-left.svg';
import { ReactComponent as sidePanelCloseRight } from './../../assets/icons/side-panel-close-right.svg';
import { ReactComponent as sorting } from './../../assets/icons/sorting.svg';
import { ReactComponent as sortingActiveDown } from './../../assets/icons/sorting-active-down.svg';
import { ReactComponent as sortingActiveUp } from './../../assets/icons/sorting-active-up.svg';
import { ReactComponent as statusAlertWarning } from './../../assets/icons/status-alert-warning.svg';
import { ReactComponent as statusAlert } from './../../assets/icons/status-alert.svg';
import { ReactComponent as statusLocked } from './../../assets/icons/status-locked.svg';
import { ReactComponent as statusTracked } from './../../assets/icons/status-tracked.svg';
import { ReactComponent as statusUntracked } from './../../assets/icons/status-untracked.svg';
import { ReactComponent as tracked } from './../../assets/icons/tracked.svg';
import { ReactComponent as unlink } from './../../assets/icons/unlink.svg';
import { ReactComponent as checkboxChecked } from './../../assets/icons/checkbox-checked.svg';
import { ReactComponent as checkboxUnchecked } from './../../assets/icons/checkbox-unchecked.svg';
import { ReactComponent as iconAlertOutline } from './../../assets/icons/icons-alert-outline.svg';
import { ReactComponent as iconAlertSmall } from './../../assets/icons/icon-alert-small.svg';
import { ReactComponent as iconClose } from './../../assets/icons/icon-close.svg';
import { ReactComponent as iconClearField } from './../../assets/icons/icon-clear-field.svg';
import { ReactComponent as iconNextInactive } from './../../assets/icons/icon-next-inactive.svg';
import { ReactComponent as iconNext } from './../../assets/icons/icon-next.svg';
import { ReactComponent as iconPlay } from './../../assets/icons/icon-play.svg';
import { ReactComponent as iconPause } from './../../assets/icons/icon-pause.svg';
import { ReactComponent as iconPrevInactive } from './../../assets/icons/icon-prev-inactive.svg';
import { ReactComponent as iconPrev } from './../../assets/icons/icon-prev.svg';
import { ReactComponent as iconSearch } from './../../assets/icons/icon-search.svg';
import { ReactComponent as iconStatusAlert } from './../../assets/icons/icon-status-alert.svg';
import { ReactComponent as iconTransferring } from './../../assets/icons/icon-transferring.svg';
import { ReactComponent as iconUpload } from './../../assets/icons/icon-upload.svg';
import { ReactComponent as navigationPanelRightHide } from './../../assets/icons/navigation-panel-right-hide.svg';
import { ReactComponent as navigationPanelRightReveal } from './../../assets/icons/navigation-panel-right-reveal.svg';
import { ReactComponent as tabLinear } from './../../assets/icons/tab-linear.svg';
import { ReactComponent as tabPatientInfo } from './../../assets/icons/tab-patient-info.svg';
import { ReactComponent as tabROIThreshold } from './../../assets/icons/tab-roi-threshold.svg';
import { ReactComponent as tabSegmentation } from './../../assets/icons/tab-segmentation.svg';
import { ReactComponent as tabStudies } from './../../assets/icons/tab-studies.svg';
import { ReactComponent as uiArrowDown } from './../../assets/icons/ui-arrow-down.svg';
import { ReactComponent as uiArrowUp } from './../../assets/icons/ui-arrow-up.svg';
import { ReactComponent as uiArrowLeft } from './../../assets/icons/ui-arrow-left.svg';
import { ReactComponent as uiArrowRight } from './../../assets/icons/ui-arrow-right.svg';
import { ReactComponent as loadingOHIFMark } from './../../assets/icons/loading-ohif-mark.svg';
import { ReactComponent as notificationsInfo } from './../../assets/icons/notifications-info.svg';
import { ReactComponent as notificationsWarning } from './../../assets/icons/notifications-warning.svg';
import { ReactComponent as notificationsError } from './../../assets/icons/notifications-error.svg';
import { ReactComponent as notificationsSuccess } from './../../assets/icons/notifications-success.svg';
import { ReactComponent as nextArrow } from './../../assets/icons/next-arrow.svg';
import { ReactComponent as prevArrow } from './../../assets/icons/prev-arrow.svg';
import { ReactComponent as viewportStatusTracked } from './../../assets/icons/viewport-status-tracked.svg';
import { ReactComponent as toggleDicomOverlay } from './../../assets/icons/tool-toggle-dicom-overlay.svg';
import { ReactComponent as toolZoom } from './../../assets/icons/tool-zoom.svg';
import { ReactComponent as toolCapture } from './../../assets/icons/tool-capture.svg';
import { ReactComponent as toolLayout } from './../../assets/icons/tool-layout-default.svg';
import { ReactComponent as toolMore } from './../../assets/icons/tool-more-menu.svg';
import { ReactComponent as toolMove } from './../../assets/icons/tool-move.svg';
import { ReactComponent as toolWindow } from './../../assets/icons/tool-window-level.svg';
import { ReactComponent as toolAnnotate } from './../../assets/icons/tool-annotate.svg';
import { ReactComponent as toolBidirectional } from './../../assets/icons/tool-bidirectional.svg';
import { ReactComponent as toolElipse } from './../../assets/icons/tool-measure-elipse.svg';
import { ReactComponent as toolCircle } from './../../assets/icons/tool-circle.svg';
import { ReactComponent as toolLength } from './../../assets/icons/tool-length.svg';
import { ReactComponent as toolStackScroll } from './../../assets/icons/tool-stack-scroll.svg';
import { ReactComponent as toolMagnify } from './../../assets/icons/tool-quick-magnify.svg';
import { ReactComponent as toolFlipHorizontal } from './../../assets/icons/tool-flip-horizontal.svg';
import { ReactComponent as toolInvert } from './../../assets/icons/tool-invert.svg';
import { ReactComponent as toolRotateRight } from './../../assets/icons/tool-rotate-right.svg';
import { ReactComponent as toolCine } from './../../assets/icons/tool-cine.svg';
import { ReactComponent as toolCrosshair } from './../../assets/icons/tool-crosshair.svg';
import { ReactComponent as toolProbe } from './../../assets/icons/focus-frame-target.svg';
import { ReactComponent as toolAngle } from './../../assets/icons/tool-angle.svg';
import { ReactComponent as toolReset } from './../../assets/icons/tool-reset.svg';
import { ReactComponent as toolRectangle } from './../../assets/icons/tool-rectangle.svg';
import { ReactComponent as toolFusionColor } from './../../assets/icons/tool-fusion-color.svg';
import { ReactComponent as toolCreateThreshold } from './../../assets/icons/tool-create-threshold.svg';
import { ReactComponent as toolCalibration } from './../../assets/icons/tool-calibrate.svg';
import { ReactComponent as toolFreehand } from './../../assets/icons/tool-freehand.svg';
import { ReactComponent as toolFreehandPolygon } from './../../assets/icons/tool-freehand-polygon.svg';
import { ReactComponent as toolPolygon } from './../../assets/icons/tool-polygon.svg';
import { ReactComponent as editPatient } from './../../assets/icons/edit-patient.svg';
import { ReactComponent as panelGroupMore } from './../../assets/icons/panel-group-more.svg';
import { ReactComponent as panelGroupOpenClose } from './../../assets/icons/panel-group-open-close.svg';
import { ReactComponent as rowAdd } from './../../assets/icons/row-add.svg';
import { ReactComponent as rowEdit } from './../../assets/icons/row-edit.svg';
import { ReactComponent as rowHidden } from './../../assets/icons/row-hidden.svg';
import { ReactComponent as rowShown } from './../../assets/icons/row-shown.svg';
import { ReactComponent as rowLock } from './../../assets/icons/row-lock.svg';
import { ReactComponent as rowUnlock } from './../../assets/icons/row-unlock.svg';
import { ReactComponent as iconMPR } from './../../assets/icons/icon-mpr-alt.svg';
import { ReactComponent as checkboxDefault } from './../../assets/icons/checkbox-default.svg';
import { ReactComponent as checkboxActive } from './../../assets/icons/checkbox-active.svg';
import { ReactComponent as referenceLines } from './../../assets/icons/tool-reference-lines.svg';
import { ReactComponent as chevronDownNew } from './../../assets/icons/icon-disclosure-close.svg';
import { ReactComponent as chevronLeftNew } from './../../assets/icons/icon-disclosure-open.svg';
import { ReactComponent as settingsBars } from './../../assets/icons/icon-display-settings.svg';
import { ReactComponent as iconAdd } from './../../assets/icons/icon-add.svg';
import { ReactComponent as iconRename } from './../../assets/icons/icon-rename.svg';
import { ReactComponent as iconDelete } from './../../assets/icons/icon-delete.svg';
import { ReactComponent as iconMoreMenu } from './../../assets/icons/icon-more-menu.svg';
import { ReactComponent as iconToolBrush } from './../../assets/icons/tool-seg-brush.svg';
import { ReactComponent as iconToolEraser } from './../../assets/icons/tool-seg-eraser.svg';
import { ReactComponent as iconToolScissor } from './../../assets/icons/icon-tool-scissor.svg';
import { ReactComponent as iconToolShape } from './../../assets/icons/tool-seg-shape.svg';
import { ReactComponent as iconToolThreshold } from './../../assets/icons/tool-seg-threshold.svg';
import { ReactComponent as viewportWindowLevel } from './../../assets/icons/viewport-window-level.svg';
import { ReactComponent as dicomTagBrowser } from './../../assets/icons/tool-dicom-tag-browser.svg';
import { ReactComponent as iconToolFreehandRoi } from './../../assets/icons/tool-freehand-roi.svg';
import { ReactComponent as iconToolLivewire } from './../../assets/icons/tool-magnetic-roi.svg';
import { ReactComponent as iconToolSplineRoi } from './../../assets/icons/tool-spline-roi.svg';
import { ReactComponent as iconToolUltrasoundBidirectional } from './../../assets/icons/tool-ultrasound-bidirectional.svg';
import { ReactComponent as iconToolLoupe } from './../../assets/icons/tool-magnify.svg';
import { ReactComponent as oldTrash } from './../../assets/icons/old-trash.svg';
import { ReactComponent as oldPlay } from './../../assets/icons/old-play.svg';
import { ReactComponent as oldStop } from './../../assets/icons/old-stop.svg';
import { ReactComponent as iconColorLUT } from './../../assets/icons/icon-color-lut.svg';
import { ReactComponent as iconChevronPatient } from './../../assets/icons/icon-chevron-patient.svg';
import { ReactComponent as iconPatient } from './../../assets/icons/icon-patient.svg';
import { ReactComponent as iconSettings } from './../../assets/icons/icon-settings.svg';
import { ReactComponent as iconToolbarBack } from './../../assets/icons/icon-toolbar-back.svg';
import { ReactComponent as iconMultiplePatients } from './../../assets/icons/icon-multiple-patients.svg';
import { ReactComponent as layoutAdvanced3DFourUp } from './../../assets/icons/layout-advanced-3d-four-up.svg';
import { ReactComponent as layoutAdvanced3DMain } from './../../assets/icons/layout-advanced-3d-main.svg';
import { ReactComponent as layoutAdvanced3DOnly } from './../../assets/icons/layout-advanced-3d-only.svg';
import { ReactComponent as layoutAdvanced3DPrimary } from './../../assets/icons/layout-advanced-3d-primary.svg';
import { ReactComponent as layoutAdvancedAxialPrimary } from './../../assets/icons/layout-advanced-axial-primary.svg';
import { ReactComponent as layoutAdvancedMPR } from './../../assets/icons/layout-advanced-mpr.svg';
import { ReactComponent as layoutCommon1x1 } from './../../assets/icons/layout-common-1x1.svg';
import { ReactComponent as layoutCommon1x2 } from './../../assets/icons/layout-common-1x2.svg';
import { ReactComponent as layoutCommon2x2 } from './../../assets/icons/layout-common-2x2.svg';
import { ReactComponent as layoutCommon2x3 } from './../../assets/icons/layout-common-2x3.svg';
import { ReactComponent as iconToolRotate } from './../../assets/icons/tool-3d-rotate.svg';
import { ReactComponent as tab4D } from './../../assets/icons/tab-4d.svg';
import { ReactComponent as investigationalUse } from './../../assets/icons/illustration-investigational-use.svg';
import { ReactComponent as actionNewDialog } from './../../assets/icons/action-new-dialog.svg';
/** Tools */
import toggleDicomOverlay from './../../assets/icons/tool-toggle-dicom-overlay.svg';
import toolZoom from './../../assets/icons/tool-zoom.svg';
import toolCapture from './../../assets/icons/tool-capture.svg';
import toolLayout from './../../assets/icons/tool-layout-default.svg';
import toolMore from './../../assets/icons/tool-more-menu.svg';
import toolMove from './../../assets/icons/tool-move.svg';
import toolWindow from './../../assets/icons/tool-window-level.svg';
import toolAnnotate from './../../assets/icons/tool-annotate.svg';
import toolBidirectional from './../../assets/icons/tool-bidirectional.svg';
import toolElipse from './../../assets/icons/tool-measure-elipse.svg';
import toolCircle from './../../assets/icons/tool-circle.svg';
import toolLength from './../../assets/icons/tool-length.svg';
import toolStackScroll from './../../assets/icons/tool-stack-scroll.svg';
import toolMagnify from './../../assets/icons/tool-quick-magnify.svg';
import toolFlipHorizontal from './../../assets/icons/tool-flip-horizontal.svg';
import toolInvert from './../../assets/icons/tool-invert.svg';
import toolRotateRight from './../../assets/icons/tool-rotate-right.svg';
import toolCine from './../../assets/icons/tool-cine.svg';
import toolCrosshair from './../../assets/icons/tool-crosshair.svg';
import toolProbe from './../../assets/icons/focus-frame-target.svg';
import toolAngle from './../../assets/icons/tool-angle.svg';
import toolReset from './../../assets/icons/tool-reset.svg';
import toolRectangle from './../../assets/icons/tool-rectangle.svg';
import toolFusionColor from './../../assets/icons/tool-fusion-color.svg';
import toolCreateThreshold from './../../assets/icons/tool-create-threshold.svg';
import toolCalibration from './../../assets/icons/tool-calibrate.svg';
import toolFreehand from './../../assets/icons/tool-freehand.svg';
import toolFreehandPolygon from './../../assets/icons/tool-freehand-polygon.svg';
import toolPolygon from './../../assets/icons/tool-polygon.svg';
import toolBrush from './../../assets/icons/tool-brush.svg';
import toolEraser from './../../assets/icons/tool-eraser.svg';
import toolScissorRect from './../../assets/icons/tool-scissor-rect.svg';
import toolScissorCircle from './../../assets/icons/tool-scissor-circle.svg';
import toolPaintFill from './../../assets/icons/tool-paint-fill.svg';
import editPatient from './../../assets/icons/edit-patient.svg';
import panelGroupMore from './../../assets/icons/panel-group-more.svg';
import panelGroupOpenClose from './../../assets/icons/panel-group-open-close.svg';
import rowAdd from './../../assets/icons/row-add.svg';
import rowEdit from './../../assets/icons/row-edit.svg';
import rowHidden from './../../assets/icons/row-hidden.svg';
import rowShown from './../../assets/icons/row-shown.svg';
import rowLock from './../../assets/icons/row-lock.svg';
import rowUnlock from './../../assets/icons/row-unlock.svg';
import iconMPR from './../../assets/icons/icon-mpr-alt.svg';
import checkboxDefault from './../../assets/icons/checkbox-default.svg';
import checkboxActive from './../../assets/icons/checkbox-active.svg';
import referenceLines from './../../assets/icons/tool-reference-lines.svg';
import chevronDownNew from './../../assets/icons/icon-disclosure-close.svg';
import chevronLeftNew from './../../assets/icons/icon-disclosure-open.svg';
import settingsBars from './../../assets/icons/icon-display-settings.svg';
import iconAdd from './../../assets/icons/icon-add.svg';
import iconRename from './../../assets/icons/icon-rename.svg';
import iconDelete from './../../assets/icons/icon-delete.svg';
import iconMoreMenu from './../../assets/icons/icon-more-menu.svg';
import iconToolBrush from './../../assets/icons/tool-seg-brush.svg';
import iconToolEraser from './../../assets/icons/tool-seg-eraser.svg';
import iconToolScissor from './../../assets/icons/icon-tool-scissor.svg';
import iconToolShape from './../../assets/icons/tool-seg-shape.svg';
import iconToolThreshold from './../../assets/icons/tool-seg-threshold.svg';
import viewportWindowLevel from './../../assets/icons/viewport-window-level.svg';
import dicomTagBrowser from './../../assets/icons/tool-dicom-tag-browser.svg';
import iconToolFreehandRoi from './../../assets/icons/tool-freehand-roi.svg';
import iconToolLivewire from './../../assets/icons/tool-magnetic-roi.svg';
import iconToolSplineRoi from './../../assets/icons/tool-spline-roi.svg';
import iconToolUltrasoundBidirectional from './../../assets/icons/tool-ultrasound-bidirectional.svg';
import iconToolLoupe from './../../assets/icons/tool-magnify.svg';
/** Old OHIF */
import oldTrash from './../../assets/icons/old-trash.svg';
import oldPlay from './../../assets/icons/old-play.svg';
import oldStop from './../../assets/icons/old-stop.svg';
/** ColorLut */
import iconColorLUT from './../../assets/icons/icon-color-lut.svg';
/** New Patient Info Toolbar */
import iconChevronPatient from './../../assets/icons/icon-chevron-patient.svg';
import iconPatient from './../../assets/icons/icon-patient.svg';
import iconSettings from './../../assets/icons/icon-settings.svg';
import iconToolbarBack from './../../assets/icons/icon-toolbar-back.svg';
import iconMultiplePatients from './../../assets/icons/icon-multiple-patients.svg';
/** Volume Rendering */
import CTAAA from './../../assets/icons/CT-AAA.png';
import CTAAA2 from './../../assets/icons/CT-AAA2.png';
import CTAir from './../../assets/icons/CT-Air.png';
@ -201,27 +201,6 @@ import MRDefault from './../../assets/icons/MR-Default.png';
import MRMIP from './../../assets/icons/MR-MIP.png';
import MRT2Brain from './../../assets/icons/MR-T2-Brain.png';
import VolumeRendering from './../../assets/icons/VolumeRendering.png';
import actionNewDialog from './../../assets/icons/action-new-dialog.svg';
/** LAYOUT */
import layoutAdvanced3DFourUp from './../../assets/icons/layout-advanced-3d-four-up.svg';
import layoutAdvanced3DMain from './../../assets/icons/layout-advanced-3d-main.svg';
import layoutAdvanced3DOnly from './../../assets/icons/layout-advanced-3d-only.svg';
import layoutAdvanced3DPrimary from './../../assets/icons/layout-advanced-3d-primary.svg';
import layoutAdvancedAxialPrimary from './../../assets/icons/layout-advanced-axial-primary.svg';
import layoutAdvancedMPR from './../../assets/icons/layout-advanced-mpr.svg';
import layoutCommon1x1 from './../../assets/icons/layout-common-1x1.svg';
import layoutCommon1x2 from './../../assets/icons/layout-common-1x2.svg';
import layoutCommon2x2 from './../../assets/icons/layout-common-2x2.svg';
import layoutCommon2x3 from './../../assets/icons/layout-common-2x3.svg';
import iconToolRotate from './../../assets/icons/tool-3d-rotate.svg';
//
import tab4D from './../../assets/icons/tab-4d.svg';
/** New investigational use */
import investigationalUse from './../../assets/icons/illustration-investigational-use.svg';
const ICONS = {
'arrow-down': arrowDown,

View File

@ -74,14 +74,14 @@ const fullWidthClasses = {
const IconButton = ({
children,
variant,
color,
size,
rounded,
disabled,
type,
fullWidth,
onClick,
variant = 'contained',
color = 'default',
size = 'medium',
rounded = 'medium',
disabled = false,
type = 'button',
fullWidth = false,
onClick = () => {},
className,
id,
...rest
@ -122,17 +122,6 @@ const IconButton = ({
);
};
IconButton.defaultProps = {
onClick: () => {},
color: 'default',
disabled: false,
fullWidth: false,
rounded: 'medium',
size: 'medium',
type: 'button',
variant: 'contained',
};
IconButton.propTypes = {
children: PropTypes.node.isRequired,
size: PropTypes.oneOf(['small', 'medium', 'large', 'initial', 'toolbar', 'toolbox']),

View File

@ -20,10 +20,12 @@ class ImageScrollbar extends PureComponent {
width: `${this.props.height}`,
};
const { onContextMenu = e => e.preventDefault() } = this.props;
return (
<div
className="scroll"
onContextMenu={this.props.onContextMenu}
onContextMenu={onContextMenu}
>
<div className="scroll-holder">
<input
@ -70,8 +72,4 @@ class ImageScrollbar extends PureComponent {
};
}
ImageScrollbar.defaultProps = {
onContextMenu: e => e.preventDefault(),
};
export default ImageScrollbar;

Some files were not shown because too many files have changed in this diff Show More