refactor: refactors both connector and node labels to be single component

This commit is contained in:
Mark Mankarious
2023-10-28 16:03:15 +01:00
parent 804b4f66c7
commit 49c9f9b70e
5 changed files with 134 additions and 173 deletions

View File

@@ -0,0 +1,104 @@
import React, { useEffect, useRef, useState } from 'react';
import { Box } from '@mui/material';
import { ExpandButton } from './ExpandButton';
interface Props {
labelHeight?: number;
maxWidth: number;
maxHeight?: number;
expandDirection?: 'CENTER' | 'BOTTOM';
children: React.ReactNode;
connectorDotSize: number;
}
export const Label = ({
children,
maxWidth,
maxHeight,
expandDirection = 'CENTER',
labelHeight = 0,
connectorDotSize
}: Props) => {
const [isExpanded, setIsExpanded] = useState(false);
const contentRef = useRef<HTMLDivElement>();
useEffect(() => {
contentRef.current?.scrollTo({ top: 0 });
}, [isExpanded]);
return (
<Box
sx={{
position: 'absolute',
width: maxWidth
}}
>
{labelHeight > 0 && (
<Box
component="svg"
viewBox={`0 0 ${connectorDotSize} ${labelHeight}`}
width={connectorDotSize}
sx={{
position: 'absolute',
top: -labelHeight,
left: -connectorDotSize / 2
}}
>
<line
x1={connectorDotSize / 2}
y1={0}
x2={connectorDotSize / 2}
y2={labelHeight}
strokeDasharray={`0, ${connectorDotSize * 2}`}
stroke="black"
strokeWidth={connectorDotSize}
strokeLinecap="round"
/>
</Box>
)}
<Box
ref={contentRef}
sx={{
position: 'absolute',
display: 'inline-block',
bgcolor: 'common.white',
border: '1px solid',
borderColor: 'grey.400',
borderRadius: 2,
py: 1,
px: 1.5,
transformOrigin: 'bottom center',
transform: `translate(-50%, ${
expandDirection === 'BOTTOM' ? '-100%' : '-50%'
})`,
overflow: 'hidden'
}}
style={{
maxHeight,
top: -labelHeight
}}
>
{children}
<Box
sx={{
position: 'absolute',
bottom: 0,
right: 0
}}
>
{isExpanded && (
<ExpandButton
isExpanded={isExpanded}
onClick={() => {
setIsExpanded(false);
}}
/>
)}
{/* </Box> */}
</Box>
</Box>
</Box>
);
};

View File

@@ -4,6 +4,7 @@ import { useScene } from 'src/hooks/useScene';
import { connectorPathTileToGlobal, getTilePosition } from 'src/utils';
import { PROJECTED_TILE_SIZE } from 'src/config';
import { useUiStateStore } from 'src/stores/uiStateStore';
import { Label } from 'src/components/Label/Label';
interface Props {
connector: ReturnType<typeof useScene>['connectors'][0];
@@ -23,6 +24,23 @@ export const ConnectorLabel = ({ connector }: Props) => {
});
}, [connector.path]);
return (
<Box
sx={{ position: 'absolute', pointerEvents: 'none' }}
style={{
maxWidth: PROJECTED_TILE_SIZE.width,
left: labelPosition.x,
top: labelPosition.y
}}
>
<Label maxWidth={150} labelHeight={0} connectorDotSize={0}>
<Typography color="text.secondary" variant="body2">
{connector.description}
</Typography>
</Label>
</Box>
);
return (
<Box
sx={{

View File

@@ -1,148 +0,0 @@
import React, { useEffect, useRef, useMemo, useState } from 'react';
import { Box } from '@mui/material';
import { useResizeObserver } from 'src/hooks/useResizeObserver';
import { PROJECTED_TILE_SIZE } from 'src/config';
import { Gradient } from 'src/components/Gradient/Gradient';
import { ExpandButton } from './ExpandButton';
const STANDARD_LABEL_HEIGHT = 80;
const EXPANDED_LABEL_HEIGHT = 200;
interface Props {
labelHeight: number;
children: React.ReactNode;
connectorDotSize: number;
}
export const LabelContainer = ({
children,
labelHeight,
connectorDotSize
}: Props) => {
const [isExpanded, setIsExpanded] = useState(false);
const contentRef = useRef<HTMLDivElement>();
const { observe, size: contentSize } = useResizeObserver();
const yOffset = useMemo(() => {
return PROJECTED_TILE_SIZE.height / 2;
}, []);
useEffect(() => {
if (!contentRef.current) return;
observe(contentRef.current);
}, [observe]);
const containerMaxHeight = useMemo(() => {
return isExpanded ? EXPANDED_LABEL_HEIGHT : STANDARD_LABEL_HEIGHT;
}, [isExpanded]);
const isContentTruncated = useMemo(() => {
return !isExpanded && contentSize.height >= STANDARD_LABEL_HEIGHT - 10;
}, [isExpanded, contentSize.height]);
useEffect(() => {
contentRef.current?.scrollTo({ top: 0 });
}, [isExpanded]);
return (
<Box
sx={{
position: 'absolute',
transformOrigin: 'top center'
}}
>
<Box
component="svg"
viewBox={`0 0 ${connectorDotSize} ${labelHeight}`}
width={connectorDotSize}
sx={{
position: 'absolute',
top: -(labelHeight + yOffset),
left: -connectorDotSize / 2
}}
>
<line
x1={connectorDotSize / 2}
y1={0}
x2={connectorDotSize / 2}
y2={labelHeight}
strokeDasharray={`0, ${connectorDotSize * 2}`}
stroke="black"
strokeWidth={connectorDotSize}
strokeLinecap="round"
/>
</Box>
<Box
sx={{
position: 'absolute',
left: -contentSize.width * 0.5,
top: -(contentSize.height + labelHeight + yOffset),
overflow: 'hidden',
width: 250
}}
>
<Box
ref={contentRef}
sx={{
position: 'relative',
display: 'inline-block',
bgcolor: 'common.white',
border: '1px solid',
borderColor: 'grey.400',
borderRadius: 2,
py: 1,
px: 1.5
}}
style={{
overflowY: isExpanded ? 'scroll' : 'hidden',
maxHeight: containerMaxHeight
}}
>
{children}
{isContentTruncated && (
<Box
sx={{
position: 'absolute',
height: 60,
width: '100%',
bottom: 0,
left: 0,
overflow: 'hidden'
}}
>
<Gradient
sx={{ position: 'absolute', width: '100%', height: '100%' }}
/>
</Box>
)}
<Box
sx={{
position: 'absolute',
bottom: 0,
right: 0
}}
>
{isContentTruncated && (
<ExpandButton
isExpanded={isExpanded}
onClick={() => {
setIsExpanded(true);
}}
/>
)}
{isExpanded && (
<ExpandButton
isExpanded={isExpanded}
onClick={() => {
setIsExpanded(false);
}}
/>
)}
</Box>
</Box>
</Box>
</Box>
);
};

View File

@@ -1,12 +1,11 @@
import React, { useMemo } from 'react';
import { Box, Typography, useTheme } from '@mui/material';
import { Box, Typography } from '@mui/material';
import { PROJECTED_TILE_SIZE, DEFAULT_LABEL_HEIGHT } from 'src/config';
import { getTilePosition } from 'src/utils';
import { useIcon } from 'src/hooks/useIcon';
import { ViewItem } from 'src/types';
import { MarkdownEditor } from 'src/components/MarkdownEditor/MarkdownEditor';
import { useModelItem } from 'src/hooks/useModelItem';
import { LabelContainer } from './LabelContainer/LabelContainer';
import { Label } from 'src/components/Label/Label';
interface Props {
node: ViewItem;
@@ -14,7 +13,6 @@ interface Props {
}
export const Node = ({ node, order }: Props) => {
const theme = useTheme();
const modelItem = useModelItem(node.id);
const { iconComponent } = useIcon(modelItem.icon);
@@ -50,33 +48,22 @@ export const Node = ({ node, order }: Props) => {
}}
>
{(modelItem.name || description) && (
<>
<Box
style={{
position: 'absolute',
top: -PROJECTED_TILE_SIZE.height
}}
/>
<LabelContainer
<Box
sx={{ position: 'absolute' }}
style={{ bottom: PROJECTED_TILE_SIZE.height / 2 }}
>
<Label
maxWidth={250}
maxHeight={100}
expandDirection="BOTTOM"
labelHeight={node.labelHeight ?? DEFAULT_LABEL_HEIGHT}
connectorDotSize={3}
>
{modelItem.name && (
<Typography fontWeight={600}>{modelItem.name}</Typography>
)}
{description && (
<Box sx={{ pt: 0.2, width: 200 }}>
<MarkdownEditor
readOnly
value={modelItem.description}
styles={{
color: theme.palette.text.secondary
}}
/>
</Box>
)}
</LabelContainer>
</>
</Label>
</Box>
)}
{iconComponent && (
<Box