Skip to content

Commit 29fb8da

Browse files
authored
feature/MIG-6992 Change appearance of node according to zoom level (#92)
* feature/MIG-6992 Change zoom level * feature/MIG-6992 Remove unused font size * feature/MIG-6992 Remove extra condition * feature/MIG-6992 Keep height
1 parent c746812 commit 29fb8da

File tree

5 files changed

+147
-59
lines changed

5 files changed

+147
-59
lines changed

src/components/diagram.stories.tsx

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { Meta, StoryObj } from '@storybook/react';
2-
import { useState } from 'react';
3-
import { Connection } from '@xyflow/react';
42

53
import { Diagram } from '@/components/diagram';
64
import { EMPLOYEE_TERRITORIES_NODE, EMPLOYEES_NODE, ORDERS_NODE } from '@/mocks/datasets/nodes';
75
import { EMPLOYEES_TO_EMPLOYEES_EDGE, ORDERS_TO_EMPLOYEES_EDGE } from '@/mocks/datasets/edges';
8-
import { EdgeProps } from '@/types';
6+
import { DiagramStressTestDecorator } from '@/mocks/decorators/diagram-stress-test.decorator';
7+
import { DiagramConnectableDecorator } from '@/mocks/decorators/diagram-connectable.decorator';
98

109
const diagram: Meta<typeof Diagram> = {
1110
title: 'Diagram',
@@ -22,37 +21,7 @@ export default diagram;
2221
type Story = StoryObj<typeof Diagram>;
2322
export const BasicDiagram: Story = {};
2423
export const DiagramWithConnectableNodes: Story = {
25-
decorators: [
26-
(Story, context) => {
27-
const [edges, setEdges] = useState<EdgeProps[]>(context.args.edges);
28-
const onConnect = (connection: Connection) => {
29-
setEdges([
30-
...edges.filter(edge => edge.source === connection.source && edge.source === connection.target),
31-
{
32-
...ORDERS_TO_EMPLOYEES_EDGE,
33-
source: connection.source,
34-
target: connection.target,
35-
animated: true,
36-
selected: true,
37-
},
38-
]);
39-
};
40-
41-
const onPaneClick = () => {
42-
setEdges(edges.filter(edge => edge.id !== ORDERS_TO_EMPLOYEES_EDGE.id));
43-
};
44-
45-
return Story({
46-
...context,
47-
args: {
48-
...context.args,
49-
edges,
50-
onPaneClick,
51-
onConnect,
52-
},
53-
});
54-
},
55-
],
24+
decorators: [DiagramConnectableDecorator],
5625
args: {
5726
title: 'MongoDB Diagram',
5827
isDarkMode: true,
@@ -63,3 +32,13 @@ export const DiagramWithConnectableNodes: Story = {
6332
],
6433
},
6534
};
35+
36+
export const DiagramStressTest: Story = {
37+
decorators: [DiagramStressTestDecorator],
38+
args: {
39+
title: 'MongoDB Diagram',
40+
isDarkMode: true,
41+
edges: [],
42+
nodes: [],
43+
},
44+
};

src/components/node/node.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ describe('node', () => {
7373
/>,
7474
);
7575
expect(screen.queryByRole('img', { name: 'Drag Icon' })).not.toBeInTheDocument();
76-
expect(screen.getByText('employees')).toBeInTheDocument();
77-
expect(screen.queryByText('employeeId')).not.toBeInTheDocument();
78-
expect(screen.queryByText('string')).not.toBeInTheDocument();
76+
expect(screen.getAllByText('employees')).toHaveLength(2);
77+
expect(screen.queryByText('employeeId')).not.toBeVisible();
78+
expect(screen.queryByText('string')).not.toBeVisible();
7979
});
8080
});

src/components/node/node.tsx

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,34 @@ import { useTheme } from '@emotion/react';
55
import Icon from '@leafygreen-ui/icon';
66
import { useState } from 'react';
77

8-
import { ellipsisTruncation } from '@/styles/styles';
9-
import {
10-
DEFAULT_FIELD_HEIGHT,
11-
DEFAULT_FIELD_PADDING,
12-
DEFAULT_NODE_HEADER_HEIGHT,
13-
ZOOM_THRESHOLD,
14-
} from '@/utilities/constants';
8+
import { DEFAULT_NODE_HEADER_HEIGHT, ZOOM_THRESHOLD } from '@/utilities/constants';
159
import { InternalNode } from '@/types/internal';
1610
import { NodeBorder } from '@/components/node/node-border';
1711
import { FieldList } from '@/components/field/field-list';
1812
import { NodeType } from '@/types';
1913

20-
const NodeZoomedOut = styled.div<{ height: number }>`
14+
const NodeZoomedOut = styled.div`
2115
display: flex;
2216
align-items: center;
2317
justify-content: center;
24-
height: ${props => props.height}px;
18+
position: absolute;
19+
width: 100%;
20+
height: 100%;
2521
`;
2622

2723
const NodeZoomedOutInner = styled.div`
2824
font-size: 20px;
2925
text-align: center;
26+
min-width: 0;
3027
padding-left: ${spacing[300]}px;
3128
padding-right: ${spacing[300]}px;
32-
${ellipsisTruncation}
29+
display: -webkit-box;
30+
-webkit-line-clamp: 3;
31+
-webkit-box-orient: vertical;
32+
overflow: hidden;
33+
text-overflow: ellipsis;
34+
word-break: break-word;
35+
overflow-wrap: break-word;
3336
`;
3437

3538
const NodeWrapper = styled.div<{ accent: string; color: string; background: string }>`
@@ -90,6 +93,10 @@ const NodeHandle = styled(Handle)<{ ['z-index']?: number }>`
9093
z-index: ${props => props['z-index']};
9194
`;
9295

96+
const NodeWithFields = styled.div<{ visibility: string }>`
97+
visibility: ${props => props.visibility};
98+
`;
99+
93100
export const Node = ({
94101
type,
95102
selected,
@@ -164,22 +171,20 @@ export const Node = ({
164171
z-index={fromHandle ? 1 : 0}
165172
/>
166173
<NodeWrapper accent={getAccent()} color={getNodeColor()} background={getNodeBackground()}>
167-
<NodeHeader background={getHeaderBackground()}>
168-
{!isContextualZoom && (
169-
<>
170-
<NodeHeaderIcon>
171-
<Icon fill={theme.node.headerIcon} glyph="Drag" />
172-
</NodeHeaderIcon>
173-
<NodeHeaderTitle>{title}</NodeHeaderTitle>
174-
</>
175-
)}
176-
</NodeHeader>
177174
{isContextualZoom && (
178-
<NodeZoomedOut height={fields.length * DEFAULT_FIELD_HEIGHT + DEFAULT_FIELD_PADDING * 2}>
175+
<NodeZoomedOut>
179176
<NodeZoomedOutInner title={title}>{title}</NodeZoomedOutInner>
180177
</NodeZoomedOut>
181178
)}
182-
{!isContextualZoom && <FieldList nodeType={type as NodeType} isHovering={isHovering} fields={fields} />}
179+
<NodeWithFields visibility={isContextualZoom ? 'hidden' : 'none'}>
180+
<NodeHeader background={getHeaderBackground()}>
181+
<NodeHeaderIcon>
182+
<Icon fill={theme.node.headerIcon} glyph="Drag" />
183+
</NodeHeaderIcon>
184+
<NodeHeaderTitle>{title}</NodeHeaderTitle>
185+
</NodeHeader>
186+
<FieldList nodeType={type as NodeType} isHovering={isHovering} fields={fields} />
187+
</NodeWithFields>
183188
</NodeWrapper>
184189
</NodeBorder>
185190
</div>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { useState } from 'react';
2+
import { Connection } from '@xyflow/react';
3+
import { Decorator } from '@storybook/react';
4+
5+
import { DiagramProps, EdgeProps } from '@/types';
6+
import { ORDERS_TO_EMPLOYEES_EDGE } from '@/mocks/datasets/edges';
7+
8+
export const DiagramConnectableDecorator: Decorator<DiagramProps> = (Story, context) => {
9+
const [edges, setEdges] = useState<EdgeProps[]>(context.args.edges);
10+
const onConnect = (connection: Connection) => {
11+
setEdges([
12+
...edges.filter(edge => edge.source === connection.source && edge.source === connection.target),
13+
{
14+
...ORDERS_TO_EMPLOYEES_EDGE,
15+
source: connection.source,
16+
target: connection.target,
17+
animated: true,
18+
selected: true,
19+
},
20+
]);
21+
};
22+
23+
const onPaneClick = () => {
24+
setEdges(edges.filter(edge => edge.id !== ORDERS_TO_EMPLOYEES_EDGE.id));
25+
};
26+
27+
return Story({
28+
...context,
29+
args: {
30+
...context.args,
31+
edges,
32+
onPaneClick,
33+
onConnect,
34+
},
35+
});
36+
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { useEffect, useState } from 'react';
2+
import { Decorator } from '@storybook/react';
3+
4+
import { DiagramProps, EdgeProps, NodeProps } from '@/types';
5+
import { applyLayout } from '@/utilities/apply-layout';
6+
7+
const names = [
8+
'users',
9+
'logs',
10+
'orders_2025_q3',
11+
'product_inventory',
12+
'customer_feedback_surveys',
13+
'internal_metrics_weekly_rollup',
14+
'application_event_stream_error_logs_high_priority',
15+
'enterprise_tenant_billing_transactions_us_east_1',
16+
'enterprise_user_authentication_logs_2025_q3_us_east_1_region_backup',
17+
'api_keys',
18+
];
19+
20+
export const DiagramStressTestDecorator: Decorator<DiagramProps> = (Story, context) => {
21+
const [nodes, setNodes] = useState<NodeProps[]>([]);
22+
const [edges, setEdges] = useState<EdgeProps[]>([]);
23+
24+
useEffect(() => {
25+
const nodes = generateNodes(100);
26+
const edges = generateEdges(nodes);
27+
28+
applyLayout<NodeProps, EdgeProps>(nodes, edges, 'STAR').then(result => {
29+
setNodes(result.nodes);
30+
setEdges(result.edges);
31+
});
32+
}, []);
33+
34+
const generateEdges = (nodes: NodeProps[]): EdgeProps[] => {
35+
return nodes.map(node => ({
36+
id: `edge_${node.id}`,
37+
source: nodes[Math.floor(Math.random() * nodes.length)].id,
38+
target: nodes[Math.floor(Math.random() * nodes.length)].id,
39+
markerStart: 'many',
40+
markerEnd: 'one',
41+
}));
42+
};
43+
44+
const generateNodes = (count: number): NodeProps[] => {
45+
return Array.from(Array(count).keys()).map(i => ({
46+
id: `node_${i}`,
47+
type: 'table',
48+
position: {
49+
x: 0,
50+
y: 0,
51+
},
52+
title: names[Math.floor(Math.random() * names.length)],
53+
fields: Array.from(Array(1 + (i % 9)).keys()).map(_ => ({
54+
name: names[Math.floor(Math.random() * names.length)],
55+
type: 'varchar',
56+
})),
57+
}));
58+
};
59+
60+
return Story({
61+
...context,
62+
args: {
63+
...context.args,
64+
nodes,
65+
edges,
66+
},
67+
});
68+
};

0 commit comments

Comments
 (0)