Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import sinon from 'sinon';
describe('Breadcrumbs Component', function () {
afterEach(cleanup);

it('renders nothing when list is empty', function () {
it('renders empty when list is empty', function () {
render(<Breadcrumbs items={[]} />);
expect(screen.getByTestId('breadcrumbs').children.length).to.equal(0);
});
Expand Down
41 changes: 24 additions & 17 deletions packages/compass-components/src/components/breadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,29 +55,27 @@ export const Breadcrumbs = ({
items,
className,
}: {
items: Array<BreadcrumbItem>;
items:
| BreadcrumbItem[]
| [...BreadcrumbItem[], Omit<BreadcrumbItem, 'onClick'>];
className?: string;
}) => {
const darkMode = useDarkMode();
if (items.length === 0) {
return (
<div
className={cx(breadcrumbStyles, className)}
data-testid="breadcrumbs"
/>
);
}
const lastItem = items[items.length - 1];
const clickableItems = items.slice(0, -1) as BreadcrumbItem[];
return (
<div className={cx(breadcrumbStyles, className)} data-testid="breadcrumbs">
{items.map((item, index) => {
const isLast = index === items.length - 1;
if (isLast) {
return (
<Body
key={[index, item.name].join('')}
className={cx(
textStyles,
darkMode ? lastItemStylesDark : lastItemStylesLight
)}
>
{item.name}
</Body>
);
}
{clickableItems.map((item, index) => {
return (
<Fragment key={item.name}>
<Fragment key={`${index}-${item.name}`}>
<Link
as="button"
hideExternalIcon={true}
Expand All @@ -103,6 +101,15 @@ export const Breadcrumbs = ({
</Fragment>
);
})}
<Body
key={[items.length - 1, lastItem.name].join('')}
className={cx(
textStyles,
darkMode ? lastItemStylesDark : lastItemStylesLight
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A styling question, I'm finding the darkmode a bit dark for the last item. Does it look dark to you? Maybe we could ask design about a lighter text color there.
Not a blocker, it's something that's already impacting the other breadcrumbs, and it's also a small change if we ever want to.

Image Image

Copy link
Collaborator Author

@paula-stacho paula-stacho Sep 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, it does looks dark 🤔 According to devtools the contrast ratio is above 5, and given that we're generally trying to avoid high contrast, it should be okay.
Screenshot 2025-09-24 at 10 51 47

)}
>
{lastItem.name}
</Body>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,45 @@ import { expect } from 'chai';
import { render, screen, userEvent } from '@mongodb-js/testing-library-compass';
import { DiagramEditorToolbar } from './diagram-editor-toolbar';
import sinon from 'sinon';
import {
type WorkspacesService,
WorkspacesServiceProvider,
} from '@mongodb-js/compass-workspaces/provider';

const workspacesService = {
openDataModelingWorkspace: () => {},
} as WorkspacesService;

function renderDiagramEditorToolbar(
props: Partial<React.ComponentProps<typeof DiagramEditorToolbar>> = {}
) {
render(
<DiagramEditorToolbar
step="EDITING"
hasUndo={true}
hasRedo={true}
isInRelationshipDrawingMode={false}
onUndoClick={() => {}}
onRedoClick={() => {}}
onExportClick={() => {}}
onRelationshipDrawingToggle={() => {}}
onAddCollectionClick={() => {}}
{...props}
/>
<WorkspacesServiceProvider value={workspacesService}>
<DiagramEditorToolbar
step="EDITING"
hasUndo={true}
hasRedo={true}
isInRelationshipDrawingMode={false}
onUndoClick={() => {}}
onRedoClick={() => {}}
onExportClick={() => {}}
onRelationshipDrawingToggle={() => {}}
onAddCollectionClick={() => {}}
{...props}
/>
</WorkspacesServiceProvider>
);
}

describe('DiagramEditorToolbar', function () {
beforeEach(function () {
workspacesService.openDataModelingWorkspace = sinon.spy();
});

afterEach(function () {
sinon.reset();
});

it('renders nothing if step is NO_DIAGRAM_SELECTED', function () {
renderDiagramEditorToolbar({ step: 'NO_DIAGRAM_SELECTED' });
expect(() => screen.getByTestId('diagram-editor-toolbar')).to.throw();
Expand All @@ -34,6 +52,23 @@ describe('DiagramEditorToolbar', function () {
expect(() => screen.getByTestId('diagram-editor-toolbar')).to.throw();
});

context('breadcrumbs', function () {
it('includes "diagrams" breadcrumb', function () {
renderDiagramEditorToolbar();
const diagrams = screen.getByRole('button', { name: 'diagrams' });
expect(diagrams).to.be.visible;
userEvent.click(diagrams);
expect(
workspacesService.openDataModelingWorkspace
).to.have.been.calledOnce;
});

it('includes diagram name breadcrumb', function () {
renderDiagramEditorToolbar({ diagramName: 'My Diagram' });
expect(screen.getByText('My Diagram')).to.be.visible;
});
});

context('undo button', function () {
it('renders it disabled if hasUndo is false', function () {
renderDiagramEditorToolbar({ hasUndo: false });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import { connect } from 'react-redux';
import type { DataModelingState } from '../store/reducer';
import { redoEdit, undoEdit } from '../store/diagram';
Expand All @@ -14,13 +14,21 @@ import {
useDarkMode,
transparentize,
Tooltip,
Breadcrumbs,
type BreadcrumbItem,
} from '@mongodb-js/compass-components';
import AddCollection from './icons/add-collection';
const containerStyles = css({
import { useOpenWorkspace } from '@mongodb-js/compass-workspaces/provider';

const breadcrumbsStyles = css({
padding: `${spacing[300]}px ${spacing[400]}px`,
});

const editorToolbarStyles = css({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: `${spacing[150]}px ${spacing[200]}px`,
padding: `${spacing[150]}px ${spacing[300]}px`,
backgroundColor: palette.gray.light3,
borderBottom: `1px solid ${palette.gray.light2}`,
marginBottom: spacing[50],
Expand All @@ -29,7 +37,7 @@ const containerStyles = css({
}px ${transparentize(0.85, palette.black)}`,
});

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

export const DiagramEditorToolbar: React.FunctionComponent<{
step: DataModelingState['step'];
diagramName?: string;
hasUndo: boolean;
hasRedo: boolean;
isInRelationshipDrawingMode: boolean;
Expand All @@ -53,6 +62,7 @@ export const DiagramEditorToolbar: React.FunctionComponent<{
onAddCollectionClick: () => void;
}> = ({
step,
diagramName,
hasUndo,
onUndoClick,
hasRedo,
Expand All @@ -63,47 +73,75 @@ export const DiagramEditorToolbar: React.FunctionComponent<{
isInRelationshipDrawingMode,
}) => {
const darkmode = useDarkMode();
const { openDataModelingWorkspace } = useOpenWorkspace();

const breadcrumbItems: [
...BreadcrumbItem[],
Omit<BreadcrumbItem, 'onClick'>
] = useMemo(
() => [
{ name: 'diagrams', onClick: () => openDataModelingWorkspace() },
{ name: diagramName || 'untitled' },
],
[diagramName, openDataModelingWorkspace]
);

if (step !== 'EDITING') {
return null;
}

return (
<div
className={cx(containerStyles, darkmode && containerDarkStyles)}
data-testid="diagram-editor-toolbar"
>
<div className={toolbarGroupStyles}>
<IconButton aria-label="Undo" disabled={!hasUndo} onClick={onUndoClick}>
<Icon glyph="Undo"></Icon>
</IconButton>
<IconButton aria-label="Redo" disabled={!hasRedo} onClick={onRedoClick}>
<Icon glyph="Redo"></Icon>
</IconButton>
<IconButton aria-label="Add Collection" onClick={onAddCollectionClick}>
<AddCollection />
</IconButton>
<Tooltip
trigger={
<IconButton
aria-label={
!isInRelationshipDrawingMode
? 'Add Relationship'
: 'Exit Relationship Drawing Mode'
}
onClick={onRelationshipDrawingToggle}
active={isInRelationshipDrawingMode}
aria-pressed={isInRelationshipDrawingMode}
>
<Icon glyph="Relationship"></Icon>
</IconButton>
}
>
Drag from one collection to another to create a relationship.
</Tooltip>
</div>
<div className={toolbarGroupStyles}>
<Button size="xsmall" aria-label="Export" onClick={onExportClick}>
<Icon glyph="Export"></Icon>
</Button>
<div>
<Breadcrumbs className={breadcrumbsStyles} items={breadcrumbItems} />
<div
className={cx(editorToolbarStyles, darkmode && editorToolbarDarkStyles)}
data-testid="diagram-editor-toolbar"
>
<div className={toolbarGroupStyles}>
<IconButton
aria-label="Undo"
disabled={!hasUndo}
onClick={onUndoClick}
>
<Icon glyph="Undo"></Icon>
</IconButton>
<IconButton
aria-label="Redo"
disabled={!hasRedo}
onClick={onRedoClick}
>
<Icon glyph="Redo"></Icon>
</IconButton>
<IconButton
aria-label="Add Collection"
onClick={onAddCollectionClick}
>
<AddCollection />
</IconButton>
<Tooltip
trigger={
<IconButton
aria-label={
!isInRelationshipDrawingMode
? 'Add Relationship'
: 'Exit Relationship Drawing Mode'
}
onClick={onRelationshipDrawingToggle}
active={isInRelationshipDrawingMode}
aria-pressed={isInRelationshipDrawingMode}
>
<Icon glyph="Relationship"></Icon>
</IconButton>
}
>
Drag from one collection to another to create a relationship.
</Tooltip>
</div>
<div className={toolbarGroupStyles}>
<Button size="xsmall" aria-label="Export" onClick={onExportClick}>
<Icon glyph="Export"></Icon>
</Button>
</div>
</div>
</div>
);
Expand All @@ -116,6 +154,7 @@ export default connect(
step: step,
hasUndo: (diagram?.edits.prev.length ?? 0) > 0,
hasRedo: (diagram?.edits.next.length ?? 0) > 0,
diagramName: diagram?.name,
};
},
{
Expand Down
Loading