feat(4D): Add 4D dynamic volume rendering and new pre-clinical 4d pt/ct mode (#3664)

Co-authored-by: Alireza <ar.sedghi@gmail.com>
Co-authored-by: Neil <neil.a.macphee@gmail.com>
This commit is contained in:
authored and GitHub committed 2024-04-09 23:57:34 -04:00
1 parent dc0b183dd4
commit d57e8bc157
200 files changed
+9589 -2178

No files matched your search

@@ -0,0 +1,27 @@
import React, { ReactElement } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { ProgressDropdownOption, ProgressDropdownOptionPropType } from './types';
const ProgressDiscreteBar = ({ options }: { options: ProgressDropdownOption[] }): ReactElement => {
return (
<div className="flex">
{options.map((option, i) => (
<div
key={i}
className={classnames('mr-1 h-1 grow first:rounded-l-sm last:mr-0 last:rounded-r-sm', {
'bg-black': !option.activated && !option.completed,
'bg-primary-main': option.activated && !option.completed,
'bg-primary-light': option.completed,
})}
/>
))}
</div>
);
};
ProgressDiscreteBar.propTypes = {
options: PropTypes.arrayOf(ProgressDropdownOptionPropType).isRequired,
};
export default ProgressDiscreteBar;
@@ -0,0 +1,168 @@
import React, { ReactNode, useEffect, useCallback, useState, useMemo, useRef } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import ProgressDiscreteBar from './ProgressDiscreteBar';
import ProgressItemDetail from './ProgressItemDetail';
import ProgressItem from './ProgressItem';
import { Icon } from '../../components';
import { ProgressDropdownOption, ProgressDropdownOptionPropType } from './types';
const ProgressDropdown = ({
options: optionsProps,
value,
children,
dropDownWidth = '170',
onChange,
}: {
options: ProgressDropdownOption[];
value?: string;
children?: ReactNode;
onChange?: ({ selectedOption }) => void;
}): JSX.Element => {
const element = useRef(null);
const [open, setOpen] = useState(false);
const toggleOpen = () => setOpen(s => !s);
const [options, setOptions] = useState(optionsProps);
const [selectedOption, setSelectedOption] = useState(undefined);
const selectedOptionIndex = useMemo(
() => options.findIndex(option => option.value === selectedOption?.value),
[options, selectedOption]
);
const canMoveNext = useMemo(
() => selectedOptionIndex < options.length - 1,
[selectedOptionIndex, options]
);
const handleOptionSelected = useCallback(
(newSelectedOption?: ProgressDropdownOption): void => {
if (newSelectedOption?.value === selectedOption?.value) {
return;
}
setOpen(false);
setSelectedOption(newSelectedOption);
if (newSelectedOption) {
newSelectedOption.activated = true;
newSelectedOption.onSelect?.();
}
if (onChange) {
onChange({ selectedOption: newSelectedOption });
}
},
[selectedOption, onChange]
);
const handleNextButtonClick = useCallback(() => {
if (canMoveNext) {
handleOptionSelected(options[selectedOptionIndex + 1]);
}
}, [options, selectedOptionIndex, canMoveNext, handleOptionSelected]);
// Update the options in case the options from props has changed
useEffect(() => setOptions(optionsProps), [optionsProps]);
// Updates the selected item based on the value from props
useEffect(() => {
if (!value) {
return;
}
const newOption = value ? options.find(option => option.value === value) : undefined;
handleOptionSelected(newOption);
}, [value, options, handleOptionSelected]);
// Listen to any click event outside of the dropdown context to hide the options
useEffect(() => {
const handleDocumentClick = e => {
if (element.current && !element.current.contains(e.target)) {
setOpen(false);
}
};
document.addEventListener('click', handleDocumentClick);
if (!open) {
document.removeEventListener('click', handleDocumentClick);
}
}, [open]);
return (
<div
ref={element}
className="relative grow select-none text-[0px] text-white"
>
<div>
<div className="mb-1.5 flex h-[26px]">
<div
className="bg-secondary-dark border-primary-main flex grow cursor-pointer rounded border"
style={{ width: `${dropDownWidth}px` }}
onClick={toggleOpen}
>
<div className="flex grow">
{selectedOption && <ProgressItemDetail option={selectedOption} />}
{!selectedOption && <div className="ml-1 grow text-base leading-6">{children}</div>}
</div>
<Icon
name="chevron-down"
className="text-primary-active mt-0.5 ml-1"
/>
</div>
<button
className={classnames('ml-1.5 w-[26px] rounded text-base', {
'bg-primary-main': canMoveNext,
'bg-primary-dark pointer-events-none': !canMoveNext,
})}
>
<Icon
name="arrow-right"
className={classnames('relative left-0.5 h-6 w-6 text-white', {
'text-white': canMoveNext,
'text-secondary-light': !canMoveNext,
})}
onClick={handleNextButtonClick}
/>
</button>
</div>
<div
className={classnames(
'absolute top-7 left-0 right-8 z-10 mt-0.5 origin-top',
'bg-primary-dark overflow-hidden transition-[max-height] duration-300 ',
'border-secondary-main rounded border shadow',
{
hidden: !open,
'max-h-[500px]': open,
}
)}
>
{options.map((option, index) => (
<ProgressItem
key={index}
option={option}
onSelect={() => handleOptionSelected(option)}
/>
))}
</div>
<div>
<ProgressDiscreteBar options={options} />
</div>
</div>
</div>
);
};
ProgressDropdown.propTypes = {
options: PropTypes.arrayOf(ProgressDropdownOptionPropType).isRequired,
value: PropTypes.string,
onChange: PropTypes.func,
children: PropTypes.node,
dropDownWidth: PropTypes.string,
};
export default ProgressDropdown;
@@ -0,0 +1,31 @@
import React, { ReactElement } from 'react';
import PropTypes from 'prop-types';
import ProgressItemDetail from './ProgressItemDetail';
import { ProgressDropdownOption, ProgressDropdownOptionPropType } from './types';
const ProgressItem = ({
option,
onSelect,
}: {
option: ProgressDropdownOption;
onSelect: (option: ProgressDropdownOption) => void;
}): ReactElement => {
const { value } = option;
return (
<div
key={value}
className={'hover:bg-secondary-main flex cursor-pointer py-1 transition duration-1000'}
onClick={() => onSelect(option)}
>
<ProgressItemDetail option={option} />
</div>
);
};
ProgressItem.propTypes = {
option: ProgressDropdownOptionPropType.isRequired,
onSelect: PropTypes.func,
};
export default ProgressItem;
@@ -0,0 +1,81 @@
import React, { useState, useMemo, ReactElement } from 'react';
import { Icon, Tooltip } from '../../components';
import { ProgressDropdownOption, ProgressDropdownOptionPropType } from './types';
const MAX_TOOLTIP_LENGTH = 150;
const iconClassNames = 'grow-0 text-primary-light h-4 w-4 mt-1 mr-2 mb-0 ml-1';
const ProgressItemDetail = ({ option }: { option: ProgressDropdownOption }): ReactElement => {
const { label, info, completed } = option;
const [truncate, setTruncate] = useState(true);
const handleOnHideTooltip = () => setTruncate(true);
let icon;
if (completed) {
icon = 'status-tracked';
} else if (info) {
icon = 'launch-info';
}
const tooltipText = useMemo(() => {
if (!truncate || !info || info.length <= MAX_TOOLTIP_LENGTH) {
return info;
}
const handleReadMoreClick = e => {
setTruncate(false);
e.stopPropagation();
e.preventDefault();
};
return (
<>
{info.slice(0, MAX_TOOLTIP_LENGTH)}
<button
className="text-primary-active font-bold"
onClick={handleReadMoreClick}
>
&nbsp;Read more...
</button>
</>
);
}, [info, truncate]);
const iconContent = (
<>
{icon && (
<div>
<Icon
name={icon}
className={iconClassNames}
/>
</div>
)}
{!icon && <div className={iconClassNames} />}
</>
);
return (
<>
{info && (
<Tooltip
content={tooltipText}
position="bottom-left"
tooltipBoxClassName={'max-w-xs'}
onHide={handleOnHideTooltip}
>
{iconContent}
</Tooltip>
)}
{!info && iconContent}
<div className="grow text-base leading-6">{label}</div>
</>
);
};
ProgressItemDetail.propTypes = {
option: ProgressDropdownOptionPropType.isRequired,
};
export default ProgressItemDetail;
@@ -0,0 +1,106 @@
import { ProgressDropdown } from '../../../components';
import { ArgsTable, Story, Canvas, Meta } from '@storybook/addon-docs';
import { createComponentTemplate } from '../../../storybook/functions/create-component-story';
export const argTypes = {
component: ProgressDropdown,
title: 'Components/ProgressDropdown',
};
<Meta
title="Components/ProgressDropdown"
component={ProgressDropdown}
/>
export const ProgressDropdownTemplate = args => (
<div className="mb-2.5 flex h-60 max-w-xs">
<ProgressDropdown {...args} />
</div>
);
export const loremIpsumText = new Array(7)
.join(` Lorem ipsum, dolor sit amet consectetur adipisicing elit`)
.substring(1);
<Heading
title="ProgressDropdown"
componentRelativePath="ProgressDropdown/ProgressDropdown.tsx"
/>
- [Overview](#overview)
- [Props](#props)
- [Usage](#usage)
- [Contribute](#contribute)
## Overview
Dropdown is a modal Dropdown component which enabled to show a modal Dropdown.
<Canvas>
<Story
name="ProgressDropdownOverview"
args={{
options: [],
children: '[empty dropdown]',
}}
>
{ProgressDropdownTemplate.bind({})}
</Story>
</Canvas>
## Props
<ArgsTable of={ProgressDropdown} />
## Usage
You can list the items in the dropdown. The status of each item changes from black to dark blue in
the status bar beneath the dropdown when the item is selected.
<Canvas>
<Story
name="ProgressDropdownList"
onChange={selectedItem => (selectedItem.completed = true)}
args={{
options: [
{
label: 'Item 1',
value: 'item1',
info: `Item 1 - ${loremIpsumText}`,
onClick: () => {
alert('Item 1 clicked');
},
},
{
label: 'Item 2',
value: 'item2',
info: `Item 2 - ${loremIpsumText}`,
onClick: () => {
alert('Item 2 clicked');
},
},
{
label: 'Item 3',
value: 'item3',
info: `Item 3 - ${loremIpsumText}`,
onClick: () => {
alert('Item 3 clicked');
},
},
{
label: 'Item 4',
info: `Item 4 - ${loremIpsumText}`,
onClick: () => {
alert('Item 4 clicked');
},
},
],
}}
>
{ProgressDropdownTemplate.bind({})}
</Story>
</Canvas>
## Contribute
<Footer componentRelativePath="ProgressDropdown/__stories__/progressDropdown.stories.mdx" />
@@ -0,0 +1 @@
export { default } from './ProgressDropdown';
@@ -0,0 +1,19 @@
import PropTypes from 'prop-types';
export type ProgressDropdownOption = {
label: string;
value: string;
info?: string;
activated?: boolean;
completed?: boolean;
onSelect?: () => void;
};
export const ProgressDropdownOptionPropType = PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
info: PropTypes.string,
activated: PropTypes.bool,
completed: PropTypes.bool,
onSelect: PropTypes.func,
});