Skip to content

Commit 55c1d2e

Browse files
authored
feat: add diagram breadcrumbs COMPASS-9800 (#7358)
1 parent f9c9b27 commit 55c1d2e

File tree

4 files changed

+152
-71
lines changed

4 files changed

+152
-71
lines changed

packages/compass-components/src/components/breadcrumb.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import sinon from 'sinon';
1212
describe('Breadcrumbs Component', function () {
1313
afterEach(cleanup);
1414

15-
it('renders nothing when list is empty', function () {
15+
it('renders empty when list is empty', function () {
1616
render(<Breadcrumbs items={[]} />);
1717
expect(screen.getByTestId('breadcrumbs').children.length).to.equal(0);
1818
});

packages/compass-components/src/components/breadcrumb.tsx

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,29 +55,27 @@ export const Breadcrumbs = ({
5555
items,
5656
className,
5757
}: {
58-
items: Array<BreadcrumbItem>;
58+
items:
59+
| BreadcrumbItem[]
60+
| [...BreadcrumbItem[], Omit<BreadcrumbItem, 'onClick'>];
5961
className?: string;
6062
}) => {
6163
const darkMode = useDarkMode();
64+
if (items.length === 0) {
65+
return (
66+
<div
67+
className={cx(breadcrumbStyles, className)}
68+
data-testid="breadcrumbs"
69+
/>
70+
);
71+
}
72+
const lastItem = items[items.length - 1];
73+
const clickableItems = items.slice(0, -1) as BreadcrumbItem[];
6274
return (
6375
<div className={cx(breadcrumbStyles, className)} data-testid="breadcrumbs">
64-
{items.map((item, index) => {
65-
const isLast = index === items.length - 1;
66-
if (isLast) {
67-
return (
68-
<Body
69-
key={[index, item.name].join('')}
70-
className={cx(
71-
textStyles,
72-
darkMode ? lastItemStylesDark : lastItemStylesLight
73-
)}
74-
>
75-
{item.name}
76-
</Body>
77-
);
78-
}
76+
{clickableItems.map((item, index) => {
7977
return (
80-
<Fragment key={item.name}>
78+
<Fragment key={`${index}-${item.name}`}>
8179
<Link
8280
as="button"
8381
hideExternalIcon={true}
@@ -103,6 +101,15 @@ export const Breadcrumbs = ({
103101
</Fragment>
104102
);
105103
})}
104+
<Body
105+
key={[items.length - 1, lastItem.name].join('')}
106+
className={cx(
107+
textStyles,
108+
darkMode ? lastItemStylesDark : lastItemStylesLight
109+
)}
110+
>
111+
{lastItem.name}
112+
</Body>
106113
</div>
107114
);
108115
};

packages/compass-data-modeling/src/components/diagram-editor-toolbar.spec.tsx

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,45 @@ import { expect } from 'chai';
33
import { render, screen, userEvent } from '@mongodb-js/testing-library-compass';
44
import { DiagramEditorToolbar } from './diagram-editor-toolbar';
55
import sinon from 'sinon';
6+
import {
7+
type WorkspacesService,
8+
WorkspacesServiceProvider,
9+
} from '@mongodb-js/compass-workspaces/provider';
10+
11+
const workspacesService = {
12+
openDataModelingWorkspace: () => {},
13+
} as WorkspacesService;
614

715
function renderDiagramEditorToolbar(
816
props: Partial<React.ComponentProps<typeof DiagramEditorToolbar>> = {}
917
) {
1018
render(
11-
<DiagramEditorToolbar
12-
step="EDITING"
13-
hasUndo={true}
14-
hasRedo={true}
15-
isInRelationshipDrawingMode={false}
16-
onUndoClick={() => {}}
17-
onRedoClick={() => {}}
18-
onExportClick={() => {}}
19-
onRelationshipDrawingToggle={() => {}}
20-
onAddCollectionClick={() => {}}
21-
{...props}
22-
/>
19+
<WorkspacesServiceProvider value={workspacesService}>
20+
<DiagramEditorToolbar
21+
step="EDITING"
22+
hasUndo={true}
23+
hasRedo={true}
24+
isInRelationshipDrawingMode={false}
25+
onUndoClick={() => {}}
26+
onRedoClick={() => {}}
27+
onExportClick={() => {}}
28+
onRelationshipDrawingToggle={() => {}}
29+
onAddCollectionClick={() => {}}
30+
{...props}
31+
/>
32+
</WorkspacesServiceProvider>
2333
);
2434
}
2535

2636
describe('DiagramEditorToolbar', function () {
37+
beforeEach(function () {
38+
workspacesService.openDataModelingWorkspace = sinon.spy();
39+
});
40+
41+
afterEach(function () {
42+
sinon.reset();
43+
});
44+
2745
it('renders nothing if step is NO_DIAGRAM_SELECTED', function () {
2846
renderDiagramEditorToolbar({ step: 'NO_DIAGRAM_SELECTED' });
2947
expect(() => screen.getByTestId('diagram-editor-toolbar')).to.throw();
@@ -34,6 +52,23 @@ describe('DiagramEditorToolbar', function () {
3452
expect(() => screen.getByTestId('diagram-editor-toolbar')).to.throw();
3553
});
3654

55+
context('breadcrumbs', function () {
56+
it('includes "diagrams" breadcrumb', function () {
57+
renderDiagramEditorToolbar();
58+
const diagrams = screen.getByRole('button', { name: 'diagrams' });
59+
expect(diagrams).to.be.visible;
60+
userEvent.click(diagrams);
61+
expect(
62+
workspacesService.openDataModelingWorkspace
63+
).to.have.been.calledOnce;
64+
});
65+
66+
it('includes diagram name breadcrumb', function () {
67+
renderDiagramEditorToolbar({ diagramName: 'My Diagram' });
68+
expect(screen.getByText('My Diagram')).to.be.visible;
69+
});
70+
});
71+
3772
context('undo button', function () {
3873
it('renders it disabled if hasUndo is false', function () {
3974
renderDiagramEditorToolbar({ hasUndo: false });

packages/compass-data-modeling/src/components/diagram-editor-toolbar.tsx

Lines changed: 80 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useMemo } from 'react';
22
import { connect } from 'react-redux';
33
import type { DataModelingState } from '../store/reducer';
44
import { redoEdit, undoEdit } from '../store/diagram';
@@ -14,13 +14,21 @@ import {
1414
useDarkMode,
1515
transparentize,
1616
Tooltip,
17+
Breadcrumbs,
18+
type BreadcrumbItem,
1719
} from '@mongodb-js/compass-components';
1820
import AddCollection from './icons/add-collection';
19-
const containerStyles = css({
21+
import { useOpenWorkspace } from '@mongodb-js/compass-workspaces/provider';
22+
23+
const breadcrumbsStyles = css({
24+
padding: `${spacing[300]}px ${spacing[400]}px`,
25+
});
26+
27+
const editorToolbarStyles = css({
2028
display: 'flex',
2129
justifyContent: 'space-between',
2230
alignItems: 'center',
23-
padding: `${spacing[150]}px ${spacing[200]}px`,
31+
padding: `${spacing[150]}px ${spacing[300]}px`,
2432
backgroundColor: palette.gray.light3,
2533
borderBottom: `1px solid ${palette.gray.light2}`,
2634
marginBottom: spacing[50],
@@ -29,7 +37,7 @@ const containerStyles = css({
2937
}px ${transparentize(0.85, palette.black)}`,
3038
});
3139

32-
const containerDarkStyles = css({
40+
const editorToolbarDarkStyles = css({
3341
backgroundColor: palette.gray.dark3,
3442
borderBottom: `1px solid ${palette.gray.dark2}`,
3543
boxShadow: `0px ${spacing[50]}px ${spacing[100]}px -${
@@ -43,6 +51,7 @@ const toolbarGroupStyles = css({
4351

4452
export const DiagramEditorToolbar: React.FunctionComponent<{
4553
step: DataModelingState['step'];
54+
diagramName?: string;
4655
hasUndo: boolean;
4756
hasRedo: boolean;
4857
isInRelationshipDrawingMode: boolean;
@@ -53,6 +62,7 @@ export const DiagramEditorToolbar: React.FunctionComponent<{
5362
onAddCollectionClick: () => void;
5463
}> = ({
5564
step,
65+
diagramName,
5666
hasUndo,
5767
onUndoClick,
5868
hasRedo,
@@ -63,47 +73,75 @@ export const DiagramEditorToolbar: React.FunctionComponent<{
6373
isInRelationshipDrawingMode,
6474
}) => {
6575
const darkmode = useDarkMode();
76+
const { openDataModelingWorkspace } = useOpenWorkspace();
77+
78+
const breadcrumbItems: [
79+
...BreadcrumbItem[],
80+
Omit<BreadcrumbItem, 'onClick'>
81+
] = useMemo(
82+
() => [
83+
{ name: 'diagrams', onClick: () => openDataModelingWorkspace() },
84+
{ name: diagramName || 'untitled' },
85+
],
86+
[diagramName, openDataModelingWorkspace]
87+
);
88+
6689
if (step !== 'EDITING') {
6790
return null;
6891
}
92+
6993
return (
70-
<div
71-
className={cx(containerStyles, darkmode && containerDarkStyles)}
72-
data-testid="diagram-editor-toolbar"
73-
>
74-
<div className={toolbarGroupStyles}>
75-
<IconButton aria-label="Undo" disabled={!hasUndo} onClick={onUndoClick}>
76-
<Icon glyph="Undo"></Icon>
77-
</IconButton>
78-
<IconButton aria-label="Redo" disabled={!hasRedo} onClick={onRedoClick}>
79-
<Icon glyph="Redo"></Icon>
80-
</IconButton>
81-
<IconButton aria-label="Add Collection" onClick={onAddCollectionClick}>
82-
<AddCollection />
83-
</IconButton>
84-
<Tooltip
85-
trigger={
86-
<IconButton
87-
aria-label={
88-
!isInRelationshipDrawingMode
89-
? 'Add Relationship'
90-
: 'Exit Relationship Drawing Mode'
91-
}
92-
onClick={onRelationshipDrawingToggle}
93-
active={isInRelationshipDrawingMode}
94-
aria-pressed={isInRelationshipDrawingMode}
95-
>
96-
<Icon glyph="Relationship"></Icon>
97-
</IconButton>
98-
}
99-
>
100-
Drag from one collection to another to create a relationship.
101-
</Tooltip>
102-
</div>
103-
<div className={toolbarGroupStyles}>
104-
<Button size="xsmall" aria-label="Export" onClick={onExportClick}>
105-
<Icon glyph="Export"></Icon>
106-
</Button>
94+
<div>
95+
<Breadcrumbs className={breadcrumbsStyles} items={breadcrumbItems} />
96+
<div
97+
className={cx(editorToolbarStyles, darkmode && editorToolbarDarkStyles)}
98+
data-testid="diagram-editor-toolbar"
99+
>
100+
<div className={toolbarGroupStyles}>
101+
<IconButton
102+
aria-label="Undo"
103+
disabled={!hasUndo}
104+
onClick={onUndoClick}
105+
>
106+
<Icon glyph="Undo"></Icon>
107+
</IconButton>
108+
<IconButton
109+
aria-label="Redo"
110+
disabled={!hasRedo}
111+
onClick={onRedoClick}
112+
>
113+
<Icon glyph="Redo"></Icon>
114+
</IconButton>
115+
<IconButton
116+
aria-label="Add Collection"
117+
onClick={onAddCollectionClick}
118+
>
119+
<AddCollection />
120+
</IconButton>
121+
<Tooltip
122+
trigger={
123+
<IconButton
124+
aria-label={
125+
!isInRelationshipDrawingMode
126+
? 'Add Relationship'
127+
: 'Exit Relationship Drawing Mode'
128+
}
129+
onClick={onRelationshipDrawingToggle}
130+
active={isInRelationshipDrawingMode}
131+
aria-pressed={isInRelationshipDrawingMode}
132+
>
133+
<Icon glyph="Relationship"></Icon>
134+
</IconButton>
135+
}
136+
>
137+
Drag from one collection to another to create a relationship.
138+
</Tooltip>
139+
</div>
140+
<div className={toolbarGroupStyles}>
141+
<Button size="xsmall" aria-label="Export" onClick={onExportClick}>
142+
<Icon glyph="Export"></Icon>
143+
</Button>
144+
</div>
107145
</div>
108146
</div>
109147
);
@@ -116,6 +154,7 @@ export default connect(
116154
step: step,
117155
hasUndo: (diagram?.edits.prev.length ?? 0) > 0,
118156
hasRedo: (diagram?.edits.next.length ?? 0) > 0,
157+
diagramName: diagram?.name,
119158
};
120159
},
121160
{

0 commit comments

Comments
 (0)