diff --git a/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx b/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx
index 43ec800c455..34970d5ffd1 100644
--- a/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx
+++ b/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx
@@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react';
+import { SmartScrollbar } from '@ohif/ui';
import ViewportImageScrollbar from './ViewportImageScrollbar';
import CustomizableViewportOverlay from './CustomizableViewportOverlay';
@@ -45,7 +46,7 @@ function CornerstoneOverlays(props: withAppTypes) {
return (
-
{
+ scroll: ({ direction, isSmartScrolling = false }) => {
const enabledElement = _getActiveViewportEnabledElement();
if (!enabledElement) {
@@ -566,6 +567,16 @@ function commandsModule({
const { viewport } = enabledElement;
const options = { delta: direction };
+ if (
+ shouldPreventScroll(
+ !isSmartScrolling,
+ viewport.getCurrentImageIdIndex() + direction,
+ servicesManager
+ )
+ ) {
+ return;
+ }
+
cstUtils.scroll(viewport, options);
},
setViewportColormap: ({
diff --git a/extensions/cornerstone/src/index.tsx b/extensions/cornerstone/src/index.tsx
index e74a0de9ffc..5a16c4573bb 100644
--- a/extensions/cornerstone/src/index.tsx
+++ b/extensions/cornerstone/src/index.tsx
@@ -40,6 +40,7 @@ import getSOPInstanceAttributes from './utils/measurementServiceMappings/utils/g
import { findNearbyToolData } from './utils/findNearbyToolData';
import { createFrameViewSynchronizer } from './synchronizers/frameViewSynchronizer';
import { getSopClassHandlerModule } from './getSopClassHandlerModule';
+import shouldPreventScroll from './utils/shouldPreventScroll';
const { helpers: volumeLoaderHelpers } = csStreamingImageVolumeLoader;
const { getDynamicVolumeInfo } = volumeLoaderHelpers ?? {};
@@ -209,6 +210,8 @@ const cornerstoneExtension: Types.Extensions.Extension = {
exports: {
toolNames,
Enums: cs3DToolsEnums,
+ shouldPreventScroll: (keyPressed, imageIdIndex) =>
+ shouldPreventScroll(keyPressed, imageIdIndex, servicesManager),
},
},
{
diff --git a/extensions/cornerstone/src/initCornerstoneTools.js b/extensions/cornerstone/src/initCornerstoneTools.js
index bfabc4a0142..9a34a90acfe 100644
--- a/extensions/cornerstone/src/initCornerstoneTools.js
+++ b/extensions/cornerstone/src/initCornerstoneTools.js
@@ -42,6 +42,8 @@ import {
import CalibrationLineTool from './tools/CalibrationLineTool';
import ImageOverlayViewerTool from './tools/ImageOverlayViewerTool';
+import SmartStackScrollMouseWheelTool from './tools/SmartStackScrollMouseWheelTool';
+import SmartStackScrollTool from './tools/SmartStackScrollTool';
export default function initCornerstoneTools(configuration = {}) {
CrosshairsTool.isAnnotation = false;
@@ -87,6 +89,8 @@ export default function initCornerstoneTools(configuration = {}) {
addTool(OrientationMarkerTool);
addTool(WindowLevelRegionTool);
addTool(PlanarFreehandContourSegmentationTool);
+ addTool(SmartStackScrollMouseWheelTool);
+ addTool(SmartStackScrollTool);
// Modify annotation tools to use dashed lines on SR
const annotationStyle = {
@@ -142,6 +146,8 @@ const toolNames = {
OrientationMarker: OrientationMarkerTool.toolName,
WindowLevelRegion: WindowLevelRegionTool.toolName,
PlanarFreehandContourSegmentation: PlanarFreehandContourSegmentationTool.toolName,
+ SmartStackScrollMouseWheel: SmartStackScrollMouseWheelTool.toolName,
+ SmartStackScroll: SmartStackScrollTool.toolName,
};
export { toolNames };
diff --git a/extensions/cornerstone/src/services/ToolGroupService/ToolGroupService.ts b/extensions/cornerstone/src/services/ToolGroupService/ToolGroupService.ts
index 298f28612d6..6ad38e6bc35 100644
--- a/extensions/cornerstone/src/services/ToolGroupService/ToolGroupService.ts
+++ b/extensions/cornerstone/src/services/ToolGroupService/ToolGroupService.ts
@@ -252,8 +252,8 @@ export default class ToolGroupService {
}
if (passive) {
- passive.forEach(({ toolName }) => {
- toolGroup.setToolPassive(toolName);
+ passive.forEach(({ toolName, bindings }) => {
+ toolGroup.setToolPassive(toolName, { bindings });
});
}
diff --git a/extensions/cornerstone/src/tools/SmartStackScrollMouseWheelTool.ts b/extensions/cornerstone/src/tools/SmartStackScrollMouseWheelTool.ts
new file mode 100644
index 00000000000..58e91d845b9
--- /dev/null
+++ b/extensions/cornerstone/src/tools/SmartStackScrollMouseWheelTool.ts
@@ -0,0 +1,29 @@
+import { getEnabledElement } from '@cornerstonejs/core';
+import { StackScrollMouseWheelTool, Types } from '@cornerstonejs/tools';
+
+class SmartStackScrollMouseWheelTool extends StackScrollMouseWheelTool {
+ parentMouseWheelCallback: (evt: Types.EventTypes.MouseWheelEventType) => void;
+
+ constructor(toolProps, defaultToolProps) {
+ super(toolProps, defaultToolProps);
+ this.parentMouseWheelCallback = this.mouseWheelCallback;
+ this.mouseWheelCallback = this.smartMouseWheelCallback;
+ }
+
+ smartMouseWheelCallback(evt: Types.EventTypes.MouseWheelEventType): void {
+ const { wheel, element } = evt.detail;
+ const { direction } = wheel;
+ const { invert, shouldPreventScroll } = this.configuration;
+ const { viewport } = getEnabledElement(element);
+ const delta = direction * (invert ? -1 : 1);
+
+ if (shouldPreventScroll(evt.detail.event.ctrlKey, viewport.getCurrentImageIdIndex() + delta)) {
+ return;
+ }
+
+ this.parentMouseWheelCallback(evt);
+ }
+}
+
+SmartStackScrollMouseWheelTool.toolName = 'SmartStackScrollMouseWheel';
+export default SmartStackScrollMouseWheelTool;
diff --git a/extensions/cornerstone/src/tools/SmartStackScrollTool.ts b/extensions/cornerstone/src/tools/SmartStackScrollTool.ts
new file mode 100644
index 00000000000..d94bf62e115
--- /dev/null
+++ b/extensions/cornerstone/src/tools/SmartStackScrollTool.ts
@@ -0,0 +1,32 @@
+import { getEnabledElementByIds } from '@cornerstonejs/core';
+import { StackScrollTool, Types } from '@cornerstonejs/tools';
+
+class SmartStackScrollTool extends StackScrollTool {
+ parentDragCallback: (evt: Types.EventTypes.InteractionEventType) => void;
+
+ constructor(toolProps, defaultToolProps) {
+ super(toolProps, defaultToolProps);
+ this.parentDragCallback = this._dragCallback;
+ this._dragCallback = this._smartDragCallback;
+ }
+
+ _smartDragCallback(evt: Types.EventTypes.InteractionEventType) {
+ const { deltaPoints, viewportId, renderingEngineId } = evt.detail;
+ const { viewport } = getEnabledElementByIds(viewportId, renderingEngineId);
+ const { invert, shouldPreventScroll } = this.configuration;
+ const deltaPointY = deltaPoints.canvas[1];
+ const pixelsPerImage = this._getPixelPerImage(viewport);
+ const deltaY = deltaPointY + this.deltaY;
+ const imageIdIndexOffset = Math.round(deltaY / pixelsPerImage);
+ const delta = invert ? -imageIdIndexOffset : imageIdIndexOffset;
+
+ if (shouldPreventScroll(evt.detail.event.ctrlKey, viewport.getCurrentImageIdIndex() + delta)) {
+ return;
+ }
+
+ return this.parentDragCallback(evt);
+ }
+}
+
+SmartStackScrollTool.toolName = 'SmartStackScroll';
+export default SmartStackScrollTool;
diff --git a/extensions/cornerstone/src/utils/shouldPreventScroll.ts b/extensions/cornerstone/src/utils/shouldPreventScroll.ts
new file mode 100644
index 00000000000..22c5a25e8a2
--- /dev/null
+++ b/extensions/cornerstone/src/utils/shouldPreventScroll.ts
@@ -0,0 +1,18 @@
+export default function shouldPreventScroll(
+ keyPressed: boolean,
+ imageIdIndex: number,
+ servicesManager
+): boolean {
+ const { stateSyncService, viewportGridService } = servicesManager.services;
+ const { cachedSlicesPerSeries } = stateSyncService.getState();
+ const { activeViewportId, viewports } = viewportGridService.getState();
+ const cachedSlices = cachedSlicesPerSeries[
+ viewports.get(activeViewportId).displaySetInstanceUIDs[0]
+ ] as number[];
+
+ if (!cachedSlices) {
+ return false;
+ }
+
+ return !keyPressed && !cachedSlices.includes(imageIdIndex);
+}
diff --git a/extensions/default/src/init.ts b/extensions/default/src/init.ts
index 2e5e831c8e0..613ac9b12d0 100644
--- a/extensions/default/src/init.ts
+++ b/extensions/default/src/init.ts
@@ -61,6 +61,9 @@ export default function init({
// afterwards.
stateSyncService.register('viewportsByPosition', { clearOnModeExit: true });
+ // Stores the cached frames of each series so that we can prevent scrolling to a slice that is not cached
+ stateSyncService.register('cachedSlicesPerSeries', { clearOnModeExit: true });
+
// Adds extra custom attributes for use by hanging protocols
registerHangingProtocolAttributes({ servicesManager });
diff --git a/modes/longitudinal/src/initToolGroups.js b/modes/longitudinal/src/initToolGroups.js
index d50bf4fe9c8..4634dbfd323 100644
--- a/modes/longitudinal/src/initToolGroups.js
+++ b/modes/longitudinal/src/initToolGroups.js
@@ -21,7 +21,7 @@ function initDefaultToolGroup(
'@ohif/extension-cornerstone.utilityModule.tools'
);
- const { toolNames, Enums } = utilityModule.exports;
+ const { toolNames, Enums, shouldPreventScroll } = utilityModule.exports;
const tools = {
active: [
@@ -37,7 +37,11 @@ function initDefaultToolGroup(
toolName: toolNames.Zoom,
bindings: [{ mouseButton: Enums.MouseBindings.Secondary }],
},
- { toolName: toolNames.StackScrollMouseWheel, bindings: [] },
+ {
+ toolName: toolNames.SmartStackScrollMouseWheel,
+ bindings: [],
+ configuration: { shouldPreventScroll },
+ },
],
passive: [
{ toolName: toolNames.Length },
@@ -71,7 +75,13 @@ function initDefaultToolGroup(
{ toolName: toolNames.EllipticalROI },
{ toolName: toolNames.CircleROI },
{ toolName: toolNames.RectangleROI },
- { toolName: toolNames.StackScroll },
+ {
+ toolName: toolNames.SmartStackScroll,
+ bindings: [
+ { mouseButton: Enums.MouseBindings.Primary, modifierKey: Enums.KeyboardBindings.Ctrl },
+ ],
+ configuration: { shouldPreventScroll },
+ },
{ toolName: toolNames.Angle },
{ toolName: toolNames.CobbAngle },
{ toolName: toolNames.Magnify },
diff --git a/modes/longitudinal/src/moreTools.ts b/modes/longitudinal/src/moreTools.ts
index 3c4ca9310d8..4c146fbb2c4 100644
--- a/modes/longitudinal/src/moreTools.ts
+++ b/modes/longitudinal/src/moreTools.ts
@@ -96,7 +96,7 @@ const moreTools = [
evaluate: 'evaluate.cornerstoneTool.toggle',
}),
createButton({
- id: 'StackScroll',
+ id: 'SmartStackScroll',
icon: 'tool-stack-scroll',
label: 'Stack Scroll',
tooltip: 'Stack Scroll',
diff --git a/modes/segmentation/src/initToolGroups.ts b/modes/segmentation/src/initToolGroups.ts
index babfe4a1136..e75891a5d77 100644
--- a/modes/segmentation/src/initToolGroups.ts
+++ b/modes/segmentation/src/initToolGroups.ts
@@ -11,13 +11,17 @@ const colorsByOrientation = {
};
function createTools(utilityModule) {
- const { toolNames, Enums } = utilityModule.exports;
+ const { toolNames, Enums, shouldPreventScroll } = utilityModule.exports;
return {
active: [
{ toolName: toolNames.WindowLevel, bindings: [{ mouseButton: Enums.MouseBindings.Primary }] },
{ toolName: toolNames.Pan, bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }] },
{ toolName: toolNames.Zoom, bindings: [{ mouseButton: Enums.MouseBindings.Secondary }] },
- { toolName: toolNames.StackScrollMouseWheel, bindings: [] },
+ {
+ toolName: toolNames.SmartStackScrollMouseWheel,
+ bindings: [],
+ configuration: { shouldPreventScroll },
+ },
],
passive: [
{
@@ -84,7 +88,13 @@ function createTools(utilityModule) {
{ toolName: toolNames.CircleScissors },
{ toolName: toolNames.RectangleScissors },
{ toolName: toolNames.SphereScissors },
- { toolName: toolNames.StackScroll },
+ {
+ toolName: toolNames.SmartStackScroll,
+ bindings: [
+ { mouseButton: Enums.MouseBindings.Primary, modifierKey: Enums.KeyboardBindings.Ctrl },
+ ],
+ configuration: { shouldPreventScroll },
+ },
{ toolName: toolNames.Magnify },
{ toolName: toolNames.SegmentationDisplay },
{ toolName: toolNames.WindowLevelRegion },
diff --git a/modes/segmentation/src/toolbarButtons.ts b/modes/segmentation/src/toolbarButtons.ts
index 89b4092c668..52f0dc6c569 100644
--- a/modes/segmentation/src/toolbarButtons.ts
+++ b/modes/segmentation/src/toolbarButtons.ts
@@ -165,7 +165,7 @@ const toolbarButtons: Button[] = [
evaluate: 'evaluate.cornerstoneTool.toggle',
}),
createButton({
- id: 'StackScroll',
+ id: 'SmartStackScroll',
icon: 'tool-stack-scroll',
label: 'Stack Scroll',
tooltip: 'Stack Scroll',
diff --git a/platform/core/src/defaults/hotkeyBindings.js b/platform/core/src/defaults/hotkeyBindings.js
index b4c6098936c..d8f44f8d429 100644
--- a/platform/core/src/defaults/hotkeyBindings.js
+++ b/platform/core/src/defaults/hotkeyBindings.js
@@ -111,12 +111,26 @@ const bindings = [
{
commandName: 'nextImage',
label: 'Next Image',
- keys: ['down'],
+ keys: ['ctrl+down'],
isEditable: true,
},
{
commandName: 'previousImage',
label: 'Previous Image',
+ keys: ['ctrl+up'],
+ isEditable: true,
+ },
+ {
+ commandName: 'nextImage',
+ commandOptions: { isSmartScrolling: true },
+ label: 'Smart Next Image',
+ keys: ['down'],
+ isEditable: true,
+ },
+ {
+ commandName: 'previousImage',
+ commandOptions: { isSmartScrolling: true },
+ label: 'Smart Previous Image',
keys: ['up'],
isEditable: true,
},
diff --git a/platform/ui/src/components/ImageScrollbar/ImageScrollbar.css b/platform/ui/src/components/ImageScrollbar/ImageScrollbar.css
index 695e11e9cb5..50f495a14f0 100644
--- a/platform/ui/src/components/ImageScrollbar/ImageScrollbar.css
+++ b/platform/ui/src/components/ImageScrollbar/ImageScrollbar.css
@@ -1,6 +1,7 @@
.scroll {
height: calc(100% - 30px);
padding: 5px;
+ padding-left: 0;
position: absolute;
right: 0;
top: 30px;
diff --git a/platform/ui/src/components/SmartScrollbar/SmartScrollbar.tsx b/platform/ui/src/components/SmartScrollbar/SmartScrollbar.tsx
new file mode 100644
index 00000000000..0756e5bc7d8
--- /dev/null
+++ b/platform/ui/src/components/SmartScrollbar/SmartScrollbar.tsx
@@ -0,0 +1,200 @@
+import React, { useState, useEffect } from 'react';
+import PropTypes from 'prop-types';
+import { Enums, cache, eventTarget } from '@cornerstonejs/core';
+import { utilities as csToolsUtils } from '@cornerstonejs/tools';
+import { ImageScrollbar } from '@ohif/ui';
+import classNames from 'classnames';
+
+const KEYS = { Ctrl: 17 };
+
+function SmartImageScrollbar({
+ viewportData,
+ viewportId,
+ element,
+ imageSliceData,
+ setImageSliceData,
+ scrollbarHeight,
+ servicesManager,
+}: withAppTypes) {
+ const [cachedSlices, setCachedSlices] = useState([]);
+ const [isKeyPressed, setIsKeyPressed] = useState(false);
+
+ const { cineService, cornerstoneViewportService, stateSyncService } = servicesManager.services;
+ const numOfSlices = imageSliceData.numberOfSlices;
+ const scrollbarHeightValue = scrollbarHeight.split('px')[0];
+
+ const onImageScrollbarChange = (imageIndex, viewportId) => {
+ if (!isKeyPressed && !cachedSlices.includes(imageIndex)) {
+ return;
+ }
+
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
+
+ const { isCineEnabled } = cineService.getState();
+
+ if (isCineEnabled) {
+ // on image scrollbar change, stop the CINE if it is playing
+ cineService.stopClip(element, { viewportId });
+ cineService.setCine({ id: viewportId, isPlaying: false });
+ }
+
+ csToolsUtils.jumpToSlice(viewport.element, {
+ imageIndex,
+ debounceLoading: true,
+ });
+ };
+
+ useEffect(() => {
+ if (!viewportData) {
+ return;
+ }
+
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
+
+ if (!viewport) {
+ return;
+ }
+
+ const imageIndex = viewport.getCurrentImageIdIndex();
+ const numberOfSlices = viewport.getNumberOfSlices();
+
+ setImageSliceData({
+ imageIndex: imageIndex,
+ numberOfSlices,
+ });
+ }, [viewportId, viewportData]);
+
+ useEffect(() => {
+ if (!viewportData) {
+ return;
+ }
+ const { viewportType } = viewportData;
+ const eventId =
+ (viewportType === Enums.ViewportType.STACK && Enums.Events.STACK_VIEWPORT_SCROLL) ||
+ (viewportType === Enums.ViewportType.ORTHOGRAPHIC && Enums.Events.VOLUME_NEW_IMAGE) ||
+ Enums.Events.IMAGE_RENDERED;
+
+ const updateIndex = event => {
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
+ const { imageIndex, newImageIdIndex = imageIndex } = event.detail;
+ const numberOfSlices = viewport.getNumberOfSlices();
+ // find the index of imageId in the imageIds
+ setImageSliceData({
+ imageIndex: newImageIdIndex,
+ numberOfSlices,
+ });
+ };
+
+ element.addEventListener(eventId, updateIndex);
+
+ return () => {
+ element.removeEventListener(eventId, updateIndex);
+ };
+ }, [viewportData, element]);
+
+ useEffect(() => {
+ if (viewportData?.viewportType !== Enums.ViewportType.STACK) {
+ return;
+ }
+
+ updateCachedSlices();
+
+ eventTarget.addEventListener(Enums.Events.IMAGE_CACHE_IMAGE_ADDED, updateCachedSlices);
+ eventTarget.addEventListener(Enums.Events.VOLUME_CACHE_VOLUME_ADDED, updateCachedSlices);
+ eventTarget.addEventListener(Enums.Events.IMAGE_CACHE_IMAGE_REMOVED, updateCachedSlices);
+ eventTarget.addEventListener(Enums.Events.VOLUME_CACHE_VOLUME_REMOVED, updateCachedSlices);
+
+ return () => {
+ eventTarget.removeEventListener(Enums.Events.IMAGE_CACHE_IMAGE_ADDED, updateCachedSlices);
+ eventTarget.removeEventListener(Enums.Events.VOLUME_CACHE_VOLUME_ADDED, updateCachedSlices);
+ eventTarget.removeEventListener(Enums.Events.IMAGE_CACHE_IMAGE_REMOVED, updateCachedSlices);
+ eventTarget.removeEventListener(Enums.Events.VOLUME_CACHE_VOLUME_REMOVED, updateCachedSlices);
+ };
+ }, [viewportData, numOfSlices]);
+
+ useEffect(() => {
+ const onKeyDown = evt => {
+ // Checking the pressed key is Ctrl key
+ evt.keyCode === KEYS.Ctrl && setIsKeyPressed(true);
+ };
+
+ const onKeyUp = evt => {
+ // Checking the pressed key is Ctrl key
+ evt.keyCode === KEYS.Ctrl && setIsKeyPressed(false);
+ };
+
+ window.addEventListener('keydown', onKeyDown);
+ window.addEventListener('keyup', onKeyUp);
+
+ return () => {
+ window.removeEventListener('keydown', onKeyDown);
+ window.removeEventListener('keyup', onKeyUp);
+ };
+ }, []);
+
+ function updateCachedSlices() {
+ if (!viewportData?.data) {
+ return;
+ }
+
+ const { cachedSlicesPerSeries } = stateSyncService.getState();
+ const { imageIds, displaySetInstanceUID } = viewportData.data[0];
+
+ const cachedImages = [];
+ imageIds.forEach((imageId, index) => {
+ if (cache.isLoaded(imageId)) {
+ cachedImages.push(index);
+ }
+ });
+
+ stateSyncService.store({
+ cachedSlicesPerSeries: { ...cachedSlicesPerSeries, [displaySetInstanceUID]: cachedImages },
+ });
+ setCachedSlices(cachedImages);
+ }
+
+ return (
+ <>
+ {numOfSlices && (
+
+ {[...Array(numOfSlices)].map((_, index) => (
+ 0 && 'border-t-[0.5px]',
+ index < numOfSlices - 1 && 'border-b-[0.5px]'
+ )}
+ style={{ height: `${(+scrollbarHeightValue + 2) / numOfSlices}px` }}
+ onClick={() => onImageScrollbarChange(index, viewportId)}
+ >
+ ))}
+
+ )}
+ onImageScrollbarChange(imageIndex, viewportId)}
+ max={numOfSlices ? numOfSlices - 1 : 0}
+ height={scrollbarHeight}
+ value={imageSliceData.imageIndex || 0}
+ />
+ >
+ );
+}
+
+SmartImageScrollbar.propTypes = {
+ viewportData: PropTypes.object,
+ viewportId: PropTypes.string.isRequired,
+ element: PropTypes.instanceOf(Element),
+ scrollbarHeight: PropTypes.string,
+ imageSliceData: PropTypes.object.isRequired,
+ setImageSliceData: PropTypes.func.isRequired,
+ servicesManager: PropTypes.object.isRequired,
+};
+
+export default SmartImageScrollbar;
diff --git a/platform/ui/src/components/SmartScrollbar/index.js b/platform/ui/src/components/SmartScrollbar/index.js
new file mode 100644
index 00000000000..22038173708
--- /dev/null
+++ b/platform/ui/src/components/SmartScrollbar/index.js
@@ -0,0 +1,2 @@
+import SmartScrollbar from './SmartScrollbar';
+export default SmartScrollbar;
diff --git a/platform/ui/src/components/index.js b/platform/ui/src/components/index.js
index 52020efd55a..f5d7ea69a4a 100644
--- a/platform/ui/src/components/index.js
+++ b/platform/ui/src/components/index.js
@@ -96,6 +96,7 @@ import MeasurementItem from './MeasurementTable/MeasurementItem';
import LayoutPreset from './LayoutPreset';
import ActionButtons from './ActionButtons';
import StudyBrowserSort from './StudyBrowserSort';
+import SmartScrollbar from './SmartScrollbar';
export {
ActionButtons,
@@ -153,6 +154,7 @@ export {
SegmentationGroupTable,
SegmentationGroupTableExpanded,
SidePanel,
+ SmartScrollbar,
SplitButton,
StudyBrowser,
StudyItem,
diff --git a/platform/ui/src/index.js b/platform/ui/src/index.js
index b240a6e4b5a..575873f723f 100644
--- a/platform/ui/src/index.js
+++ b/platform/ui/src/index.js
@@ -85,6 +85,7 @@ export {
SegmentationGroupTable,
SegmentationGroupTableExpanded,
SidePanel,
+ SmartScrollbar,
SplitButton,
ProgressDropdown,
LegacySplitButton,