Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(compass-crud): allows nested elements to also expand in steps #6009

Merged
merged 14 commits into from
Jul 23, 2024
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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import React, { useState } from 'react';
import { expect } from 'chai';
import { render, cleanup, screen, within } from '@testing-library/react';
import {
render,
cleanup,
screen,
within,
waitFor,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import HadronDocument from 'hadron-document';
import Document from './document';
Expand Down Expand Up @@ -244,4 +250,52 @@ describe('Document', function () {
expect(() => screen.getByText('firstName')).to.throw;
expect(() => screen.getByText('lastName')).to.throw;
});

it('should render "Show more" toggle when number of fields are more than allowed visible fields', async function () {
const hadronDoc = new HadronDocument({
prop1: 'prop1',
prop2: 'prop2',
prop3: 'prop3',
prop4: 'prop4',
});
hadronDoc.setMaxVisibleElementsCount(2);
render(<Document value={hadronDoc}></Document>);
expect(screen.getByText('prop1')).to.exist;
expect(screen.getByText('prop2')).to.exist;
expect(() => screen.getByText('prop3')).to.throw;
expect(() => screen.getByText('prop4')).to.throw;
expect(screen.getByText('Show 2 more fields')).to.exist;

hadronDoc.setMaxVisibleElementsCount(25);
await waitFor(() => {
expect(screen.getByText('prop3')).to.exist;
expect(screen.getByText('prop4')).to.exist;
});
});

it('should render "Show more" toggle on element when its nested fields are more than allowed visible fields', async function () {
const hadronDoc = new HadronDocument({
nested: {
prop1: 'prop1',
prop2: 'prop2',
prop3: 'prop3',
prop4: 'prop4',
},
});
const [nestedElement] = [...hadronDoc.elements];
hadronDoc.expand();
render(<Document value={hadronDoc}></Document>);
expect(screen.getByText('nested')).to.exist;
expect(screen.getByText('prop1')).to.exist;
expect(screen.getByText('prop2')).to.exist;
expect(screen.getByText('prop3')).to.exist;
expect(screen.getByText('prop4')).to.exist;

nestedElement.setMaxVisibleElementsCount(2);
await waitFor(() => {
expect(() => screen.getByText('prop3')).to.throw;
expect(() => screen.getByText('prop4')).to.throw;
});
expect(screen.getByText('Show 2 more fields in nested')).to.exist;
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,31 @@ import type {
default as HadronDocumentType,
Element as HadronElementType,
} from 'hadron-document';
import { ElementEvents } from 'hadron-document';
import {
DEFAULT_VISIBLE_DOCUMENT_ELEMENTS,
DocumentEvents,
ElementEvents,
} from 'hadron-document';
import { AutoFocusContext } from './auto-focus-context';
import { useForceUpdate } from './use-force-update';
import { HadronElement } from './element';
import { calculateShowMoreToggleOffset, HadronElement } from './element';
import { usePrevious } from './use-previous';
import DocumentFieldsToggleGroup from './document-fields-toggle-group';
import VisibleFieldsToggle from './visible-field-toggle';
import { documentTypography } from './typography';

function useHadronDocument(doc: HadronDocumentType) {
const prevDoc = usePrevious(doc);
const forceUpdate = useForceUpdate();

const onVisibleElementsChanged = useCallback(
(document: HadronDocumentType) => {
if (document === doc) {
forceUpdate();
}
},
[doc, forceUpdate]
);

const onDocumentFieldsAddedOrRemoved = useCallback(
(
_el: HadronElementType,
Expand All @@ -39,17 +52,20 @@ function useHadronDocument(doc: HadronDocumentType) {
}, [prevDoc, doc, forceUpdate]);

useEffect(() => {
doc.on(DocumentEvents.VisibleElementsChanged, onVisibleElementsChanged);
doc.on(ElementEvents.Added, onDocumentFieldsAddedOrRemoved);
doc.on(ElementEvents.Removed, onDocumentFieldsAddedOrRemoved);

return () => {
doc.off(DocumentEvents.VisibleElementsChanged, onVisibleElementsChanged);
doc.off(ElementEvents.Added, onDocumentFieldsAddedOrRemoved);
doc.off(ElementEvents.Removed, onDocumentFieldsAddedOrRemoved);
};
}, [doc, onDocumentFieldsAddedOrRemoved]);
}, [doc, onDocumentFieldsAddedOrRemoved, onVisibleElementsChanged]);

return {
elements: [...doc.elements],
visibleElements: doc.getVisibleElements(),
};
}

Expand All @@ -61,8 +77,6 @@ const hadronDocument = css({
counterReset: 'line-number',
});

const INITIAL_FIELD_LIMIT = 25;

// TODO: This element should implement treegrid aria role to be accessible
// https://www.w3.org/TR/wai-aria-practices/examples/treegrid/treegrid-1.html
// https://jira.mongodb.org/browse/COMPASS-5614
Expand All @@ -72,12 +86,7 @@ const HadronDocument: React.FunctionComponent<{
editing?: boolean;
onEditStart?: () => void;
}> = ({ value: document, editable = false, editing = false, onEditStart }) => {
const { elements } = useHadronDocument(document);
const [visibleFieldsCount, setVisibleFieldsCount] =
useState(INITIAL_FIELD_LIMIT);
const visibleElements = useMemo(() => {
return elements.filter(Boolean).slice(0, visibleFieldsCount);
}, [elements, visibleFieldsCount]);
const { elements, visibleElements } = useHadronDocument(document);
const [autoFocus, setAutoFocus] = useState<{
id: string;
type: 'key' | 'value' | 'type';
Expand All @@ -89,6 +98,25 @@ const HadronDocument: React.FunctionComponent<{
}
}, [editing]);

const handleVisibleFieldsChanged = useCallback(
(totalVisibleFields: number) => {
document.setMaxVisibleElementsCount(totalVisibleFields);
},
[document]
);

// To render the "Show more" toggle for the document we need to calculate a
// proper offset so that it aligns with the expand icon of top level fields
const showMoreToggleOffset = useMemo(
() =>
calculateShowMoreToggleOffset({
editable,
level: 0,
alignWithNestedExpandIcon: false,
}),
[editable]
);

return (
<div>
<div
Expand Down Expand Up @@ -124,20 +152,23 @@ const HadronDocument: React.FunctionComponent<{
})}
</AutoFocusContext.Provider>
</div>
<DocumentFieldsToggleGroup
<VisibleFieldsToggle
// TODO: "Hide items" button will only be shown when document is not
// edited because it's not decided how to handle changes to the fields
// that are changed but then hidden
// https://jira.mongodb.org/browse/COMPASS-5587
showHideButton={!editing}
currentSize={visibleFieldsCount}
currentSize={document.maxVisibleElementsCount}
totalSize={elements.length}
minSize={INITIAL_FIELD_LIMIT}
minSize={DEFAULT_VISIBLE_DOCUMENT_ELEMENTS}
// In the editing mode we allow to show / hide less fields because
// historically Compass was doing this for "performance" reasons
step={editing ? 100 : 1000}
onSizeChange={setVisibleFieldsCount}
></DocumentFieldsToggleGroup>
onSizeChange={handleVisibleFieldsChanged}
Copy link
Member

Choose a reason for hiding this comment

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

This is existing functionality, but I figured I'd bring it up here, no action needed. A small UX improvement here for folks with lots of fields in their documents could be to adjust the scroll position when they click to hide a lot of fields to the relative scroll height after the fields are hidden. Currently it's easy to lose a document if it's not the last document and you hide a bunch of fields.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea I noticed this after I implemented virtual list. I will see what I can do there to modify this behaviour a little.

style={{
paddingLeft: showMoreToggleOffset,
}}
></VisibleFieldsToggle>
</div>
);
};
Expand Down
Loading
Loading