Skip to content

Commit

Permalink
feat(protocol-designer): make timeline responsive (#17109)
Browse files Browse the repository at this point in the history
* feat(protocol-designer): make timeline responsive
  • Loading branch information
koji authored Dec 20, 2024
1 parent d36b284 commit 35422b6
Show file tree
Hide file tree
Showing 18 changed files with 343 additions and 71 deletions.
2 changes: 1 addition & 1 deletion protocol-designer/src/assets/localization/en/button.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"add_step": "+ Add Step",
"add_step": "Add Step",
"add_off_deck": "+ Off-deck labware",
"cancel": "cancel",
"close": "close",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { useState, useRef, useCallback, useEffect } from 'react'
import styled from 'styled-components'
import {
Box,
COLORS,
DIRECTION_COLUMN,
DISPLAY_FLEX,
Flex,
JUSTIFY_SPACE_BETWEEN,
} from '@opentrons/components'
import { TimelineToolbox } from './Timeline/TimelineToolbox'

const INITIAL_SIDEBAR_WIDTH = 276
const MIN_SIDEBAR_WIDTH = 80
const MAX_SIDEBAR_WIDTH = 350

interface DraggableSidebarProps {
setTargetWidth: (width: number) => void
}

// Note (kk:2024/12/20 the designer will revisit responsive sidebar design in 2025
// we will need to update the details to align with the updated design
export function DraggableSidebar({
setTargetWidth,
}: DraggableSidebarProps): JSX.Element {
const sidebarRef = useRef<HTMLDivElement>(null)
const [isResizing, setIsResizing] = useState(false)
const [sidebarWidth, setSidebarWidth] = useState(INITIAL_SIDEBAR_WIDTH)

const startResizing = useCallback(() => {
setIsResizing(true)
}, [])

const stopResizing = useCallback(() => {
setIsResizing(false)
}, [])

const resize = useCallback(
(mouseMoveEvent: MouseEvent) => {
if (isResizing && sidebarRef.current != null) {
const newWidth =
mouseMoveEvent.clientX -
sidebarRef.current.getBoundingClientRect().left

if (newWidth >= MIN_SIDEBAR_WIDTH && newWidth <= MAX_SIDEBAR_WIDTH) {
setSidebarWidth(newWidth)
setTargetWidth(newWidth)
}
}
},
[isResizing, setTargetWidth]
)

useEffect(() => {
window.addEventListener('mousemove', resize)
window.addEventListener('mouseup', stopResizing)

return () => {
window.removeEventListener('mousemove', resize)
window.removeEventListener('mouseup', stopResizing)
}
}, [resize, stopResizing])

return (
<Flex
flexDirection={DIRECTION_COLUMN}
justifyContent={JUSTIFY_SPACE_BETWEEN}
height="100%"
>
<SidebarContainer ref={sidebarRef} resizedWidth={sidebarWidth}>
<SidebarContent>
<TimelineToolbox sidebarWidth={sidebarWidth} />
</SidebarContent>
<SidebarResizer dragging={isResizing} onMouseDown={startResizing} />
</SidebarContainer>
</Flex>
)
}

const SidebarContainer = styled(Box)`
display: ${DISPLAY_FLEX};
flex-direction: ${DIRECTION_COLUMN};
border-right: 1px solid #ccc;
position: relative;
/* overflow: hidden; */
height: 100%;
`

const SidebarContent = styled(Flex)`
flex: 1;
`

interface SidebarResizerProps {
dragging: boolean
}

const SidebarResizer = styled(Flex)<SidebarResizerProps>`
user-select: none;
width: 2px;
cursor: ew-resize;
background-color: #ddd;
position: absolute;
top: 0;
right: 0;
bottom: 0;
margin: 0;
padding: 0;
transition: background-color 0.2s ease;
&:hover {
background-color: ${COLORS.blue50}; /* Hover state */
}
${props =>
props.dragging === true &&
`
background-color: ${COLORS.blue55}; /* Dragging state */
`}
`
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,27 @@ import { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { createPortal } from 'react-dom'
import { useTranslation } from 'react-i18next'
import { css } from 'styled-components'

import {
useHoverTooltip,
TOOLTIP_TOP,
TOOLTIP_FIXED,
Tooltip,
ALIGN_CENTER,
BORDERS,
COLORS,
DIRECTION_COLUMN,
DISPLAY_FLEX,
Flex,
POSITION_ABSOLUTE,
BORDERS,
Icon,
JUSTIFY_CENTER,
NO_WRAP,
useOnClickOutside,
POSITION_ABSOLUTE,
SecondaryButton,
SPACING,
StyledText,
TOOLTIP_FIXED,
TOOLTIP_TOP,
Tooltip,
useHoverTooltip,
useOnClickOutside,
} from '@opentrons/components'
import {
ABSORBANCE_READER_TYPE,
Expand All @@ -23,6 +31,7 @@ import {
TEMPERATURE_MODULE_TYPE,
THERMOCYCLER_MODULE_TYPE,
} from '@opentrons/shared-data'

import {
actions as stepsActions,
getIsMultiSelectMode,
Expand All @@ -40,15 +49,18 @@ import {
getEnableAbsorbanceReader,
getEnableComment,
} from '../../../../feature-flags/selectors'

import { AddStepOverflowButton } from './AddStepOverflowButton'

import type { MouseEvent } from 'react'
import type { ThunkDispatch } from 'redux-thunk'
import type { BaseState } from '../../../../types'
import type { StepType } from '../../../../form-types'

export function AddStepButton(): JSX.Element {
interface AddStepButtonProps {
hasText: boolean
}

export function AddStepButton({ hasText }: AddStepButtonProps): JSX.Element {
const { t } = useTranslation(['tooltip', 'button'])
const enableComment = useSelector(getEnableComment)
const dispatch = useDispatch<ThunkDispatch<BaseState, any, any>>()
Expand Down Expand Up @@ -151,16 +163,8 @@ export function AddStepButton(): JSX.Element {

{showStepOverflowMenu ? (
<Flex
position={POSITION_ABSOLUTE}
zIndex={5}
css={STEP_OVERFLOW_MENU_STYLE}
ref={overflowWrapperRef}
left="19.5rem"
whiteSpace={NO_WRAP}
bottom="4.2rem"
borderRadius={BORDERS.borderRadius8}
boxShadow="0px 1px 3px rgba(0, 0, 0, 0.2)"
backgroundColor={COLORS.white}
flexDirection={DIRECTION_COLUMN}
onClick={(e: MouseEvent) => {
e.preventDefault()
e.stopPropagation()
Expand All @@ -176,6 +180,10 @@ export function AddStepButton(): JSX.Element {
</Tooltip>
)}
<SecondaryButton
display={DISPLAY_FLEX}
justifyContent={JUSTIFY_CENTER}
alignItems={ALIGN_CENTER}
gridGap={SPACING.spacing10}
width="100%"
{...targetProps}
id="AddStepButton"
Expand All @@ -184,8 +192,21 @@ export function AddStepButton(): JSX.Element {
}}
disabled={isStepCreationDisabled}
>
{t('button:add_step')}
<Icon name="plus" size="1rem" />
{hasText ? <StyledText>{t('button:add_step')}</StyledText> : null}
</SecondaryButton>
</>
)
}

const STEP_OVERFLOW_MENU_STYLE = css`
position: ${POSITION_ABSOLUTE};
z-index: 5;
right: -7.75rem;
white-space: ${NO_WRAP};
bottom: 4.2rem;
border-radius: ${BORDERS.borderRadius8};
box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.2);
background-color: ${COLORS.white};
flex-direction: ${DIRECTION_COLUMN};
`
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface ConnectedStepInfoProps {
dragHovered?: boolean
openedOverflowMenuId?: string | null
setOpenedOverflowMenuId?: Dispatch<SetStateAction<string | null>>
sidebarWidth: number
}

export function ConnectedStepInfo(props: ConnectedStepInfoProps): JSX.Element {
Expand All @@ -58,6 +59,7 @@ export function ConnectedStepInfo(props: ConnectedStepInfoProps): JSX.Element {
dragHovered = false,
openedOverflowMenuId,
setOpenedOverflowMenuId,
sidebarWidth,
} = props
const { t } = useTranslation('application')
const dispatch = useDispatch<ThunkDispatch<BaseState, any, any>>()
Expand Down Expand Up @@ -227,6 +229,7 @@ export function ConnectedStepInfo(props: ConnectedStepInfoProps): JSX.Element {
step.stepName || t(`stepType.${step.stepType}`)
}`}
dragHovered={dragHovered}
sidebarWidth={sidebarWidth}
/>
</>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ interface DragDropStepProps extends ConnectedStepItemProps {
orderedStepIds: string[]
openedOverflowMenuId?: string | null
setOpenedOverflowMenuId?: Dispatch<SetStateAction<string | null>>
sidebarWidth: number
}

interface DropType {
Expand All @@ -46,6 +47,7 @@ function DragDropStep(props: DragDropStepProps): JSX.Element {
stepNumber,
openedOverflowMenuId,
setOpenedOverflowMenuId,
sidebarWidth,
} = props
const stepRef = useRef<HTMLDivElement>(null)

Expand Down Expand Up @@ -94,6 +96,7 @@ function DragDropStep(props: DragDropStepProps): JSX.Element {
stepNumber={stepNumber}
stepId={stepId}
dragHovered={hovered}
sidebarWidth={sidebarWidth}
/>
</Box>
)
Expand All @@ -102,9 +105,10 @@ function DragDropStep(props: DragDropStepProps): JSX.Element {
interface DraggableStepsProps {
orderedStepIds: StepIdType[]
reorderSteps: (steps: StepIdType[]) => void
sidebarWidth: number
}
export function DraggableSteps(props: DraggableStepsProps): JSX.Element | null {
const { orderedStepIds, reorderSteps } = props
const { orderedStepIds, reorderSteps, sidebarWidth } = props
const { t } = useTranslation('shared')
const [openedOverflowMenuId, setOpenedOverflowMenuId] = useState<
string | null
Expand Down Expand Up @@ -146,14 +150,21 @@ export function DraggableSteps(props: DraggableStepsProps): JSX.Element | null {
orderedStepIds={orderedStepIds}
openedOverflowMenuId={openedOverflowMenuId}
setOpenedOverflowMenuId={setOpenedOverflowMenuId}
sidebarWidth={sidebarWidth}
/>
))}
<StepDragPreview />
<StepDragPreview sidebarWidth={sidebarWidth} />
</Flex>
)
}

function StepDragPreview(): JSX.Element | null {
interface StepDragPreviewProps {
sidebarWidth: number
}

function StepDragPreview({
sidebarWidth,
}: StepDragPreviewProps): JSX.Element | null {
const [{ isDragging, itemType, item, currentOffset }] = useDrag(() => ({
type: DND_TYPES.STEP_ITEM,
collect: (monitor: DragLayerMonitor) => ({
Expand Down Expand Up @@ -182,6 +193,7 @@ function StepDragPreview(): JSX.Element | null {
<StepContainer
iconName={stepIconsByType[stepType]}
title={stepName || ''}
sidebarWidth={sidebarWidth}
/>
</Flex>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ import {
} from '../../../../ui/steps'
import { StepContainer } from './StepContainer'

export function PresavedStep(): JSX.Element | null {
interface PresavedStepProps {
sidebarWidth: number
}

export function PresavedStep({
sidebarWidth,
}: PresavedStepProps): JSX.Element | null {
const { t } = useTranslation('application')
const presavedStepForm = useSelector(stepFormSelectors.getPresavedStepForm)
const stepNumber = useSelector(stepFormSelectors.getOrderedStepIds).length + 1
Expand Down Expand Up @@ -39,6 +45,7 @@ export function PresavedStep(): JSX.Element | null {
hovered={hovered}
iconName={stepIconsByType[stepType]}
title={`${stepNumber}. ${t(`stepType.${stepType}`)}`}
sidebarWidth={sidebarWidth}
/>
)
}
Loading

0 comments on commit 35422b6

Please sign in to comment.