mirror of
https://github.com/strapi/strapi.git
synced 2025-09-25 16:29:34 +00:00
Merge pull request #14801 from strapi/fix/relations/scroll-to-end
This commit is contained in:
commit
1daf8dd67a
@ -21,6 +21,7 @@ import { RelationItem } from './components/RelationItem';
|
|||||||
import { RelationList } from './components/RelationList';
|
import { RelationList } from './components/RelationList';
|
||||||
import { Option } from './components/Option';
|
import { Option } from './components/Option';
|
||||||
import { RELATION_ITEM_HEIGHT } from './constants';
|
import { RELATION_ITEM_HEIGHT } from './constants';
|
||||||
|
import { usePrev } from '../../hooks';
|
||||||
|
|
||||||
const LinkEllipsis = styled(Link)`
|
const LinkEllipsis = styled(Link)`
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@ -204,6 +205,31 @@ const RelationInput = ({
|
|||||||
onSearch();
|
onSearch();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const previewRelationsLength = usePrev(relations.length);
|
||||||
|
/**
|
||||||
|
* @type {React.MutableRefObject<'onChange' | 'loadMore'>}
|
||||||
|
*/
|
||||||
|
const updatedRelationsWith = useRef();
|
||||||
|
|
||||||
|
const handleLoadMore = () => {
|
||||||
|
updatedRelationsWith.current = 'loadMore';
|
||||||
|
onRelationLoadMore();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
updatedRelationsWith.current === 'onChange' &&
|
||||||
|
relations.length !== previewRelationsLength
|
||||||
|
) {
|
||||||
|
listRef.current.scrollToItem(relations.length, 'end');
|
||||||
|
} else if (
|
||||||
|
updatedRelationsWith.current === 'loadMore' &&
|
||||||
|
relations.length !== previewRelationsLength
|
||||||
|
) {
|
||||||
|
listRef.current.scrollToItem(0, 'start');
|
||||||
|
}
|
||||||
|
}, [previewRelationsLength, relations]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Field error={error} name={name} hint={description} id={id}>
|
<Field error={error} name={name} hint={description} id={id}>
|
||||||
<Relation
|
<Relation
|
||||||
@ -231,13 +257,7 @@ const RelationInput = ({
|
|||||||
onChange={(relation) => {
|
onChange={(relation) => {
|
||||||
setValue(null);
|
setValue(null);
|
||||||
onRelationConnect(relation);
|
onRelationConnect(relation);
|
||||||
|
updatedRelationsWith.current = 'onChange';
|
||||||
// scroll to the end of the list
|
|
||||||
if (relations.length > 0) {
|
|
||||||
setTimeout(() => {
|
|
||||||
listRef.current.scrollToItem(relations.length, 'end');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onInputChange={(value) => {
|
onInputChange={(value) => {
|
||||||
setValue(value);
|
setValue(value);
|
||||||
@ -262,7 +282,7 @@ const RelationInput = ({
|
|||||||
shouldDisplayLoadMoreButton && (
|
shouldDisplayLoadMoreButton && (
|
||||||
<TextButton
|
<TextButton
|
||||||
disabled={paginatedRelations.isLoading || paginatedRelations.isFetchingNextPage}
|
disabled={paginatedRelations.isLoading || paginatedRelations.isFetchingNextPage}
|
||||||
onClick={() => onRelationLoadMore()}
|
onClick={handleLoadMore}
|
||||||
loading={paginatedRelations.isLoading || paginatedRelations.isFetchingNextPage}
|
loading={paginatedRelations.isLoading || paginatedRelations.isFetchingNextPage}
|
||||||
startIcon={<Refresh />}
|
startIcon={<Refresh />}
|
||||||
>
|
>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { render, screen, fireEvent } from '@testing-library/react';
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||||
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
||||||
|
|
||||||
import { RelationInput } from '../index';
|
import { RelationInput } from '../index';
|
||||||
@ -45,40 +45,41 @@ const FIXTURES_SEARCH = {
|
|||||||
isSuccess: true,
|
isSuccess: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const setup = (props) =>
|
const Component = (props) => (
|
||||||
render(
|
<MemoryRouter>
|
||||||
<MemoryRouter>
|
<ThemeProvider theme={lightTheme}>
|
||||||
<ThemeProvider theme={lightTheme}>
|
<IntlProvider locale="en">
|
||||||
<IntlProvider locale="en">
|
<RelationInput
|
||||||
<RelationInput
|
description="this is a description"
|
||||||
description="this is a description"
|
id="1"
|
||||||
id="1"
|
name="some-relation-1"
|
||||||
name="some-relation-1"
|
label="Some Relation"
|
||||||
label="Some Relation"
|
labelLoadMore="Load more"
|
||||||
labelLoadMore="Load more"
|
loadingMessage="Relations are loading"
|
||||||
loadingMessage="Relations are loading"
|
labelDisconnectRelation="Remove"
|
||||||
labelDisconnectRelation="Remove"
|
numberOfRelationsToDisplay={5}
|
||||||
numberOfRelationsToDisplay={5}
|
noRelationsMessage="No relations available"
|
||||||
noRelationsMessage="No relations available"
|
onRelationConnect={() => jest.fn()}
|
||||||
onRelationConnect={() => jest.fn()}
|
onRelationDisconnect={() => jest.fn()}
|
||||||
onRelationDisconnect={() => jest.fn()}
|
onRelationLoadMore={() => jest.fn()}
|
||||||
onRelationLoadMore={() => jest.fn()}
|
onSearch={() => jest.fn()}
|
||||||
onSearch={() => jest.fn()}
|
onSearchNextPage={() => jest.fn()}
|
||||||
onSearchNextPage={() => jest.fn()}
|
placeholder="Select..."
|
||||||
placeholder="Select..."
|
publicationStateTranslations={{
|
||||||
publicationStateTranslations={{
|
draft: 'Draft',
|
||||||
draft: 'Draft',
|
published: 'Published',
|
||||||
published: 'Published',
|
}}
|
||||||
}}
|
relations={FIXTURES_RELATIONS}
|
||||||
relations={FIXTURES_RELATIONS}
|
searchResults={FIXTURES_SEARCH}
|
||||||
searchResults={FIXTURES_SEARCH}
|
size={8}
|
||||||
size={8}
|
{...props}
|
||||||
{...props}
|
/>
|
||||||
/>
|
</IntlProvider>
|
||||||
</IntlProvider>
|
</ThemeProvider>
|
||||||
</ThemeProvider>
|
</MemoryRouter>
|
||||||
</MemoryRouter>
|
);
|
||||||
);
|
|
||||||
|
const setup = (props) => render(<Component {...props} />);
|
||||||
|
|
||||||
describe('Content-Manager || RelationInput', () => {
|
describe('Content-Manager || RelationInput', () => {
|
||||||
test('should render and match snapshot', () => {
|
test('should render and match snapshot', () => {
|
||||||
@ -146,6 +147,65 @@ describe('Content-Manager || RelationInput', () => {
|
|||||||
expect(spy).toHaveBeenCalled();
|
expect(spy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should scroll to the bottom when a new relation has been added & scroll to the top when load more is clicked', async () => {
|
||||||
|
const data = [
|
||||||
|
...FIXTURES_RELATIONS.data,
|
||||||
|
{ id: 4, mainField: 'Relation 4', publicationState: 'draft' },
|
||||||
|
{ id: 5, mainField: 'Relation 5', publicationState: 'draft' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const newRelation = { id: 6, mainField: 'Relation 6', publicationState: 'draft' };
|
||||||
|
|
||||||
|
const { rerender } = setup({
|
||||||
|
relations: {
|
||||||
|
...FIXTURES_RELATIONS,
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
searchResults: {
|
||||||
|
...FIXTURES_SEARCH,
|
||||||
|
data: [newRelation],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const el = screen.getByRole('list');
|
||||||
|
|
||||||
|
expect(el.parentNode.scrollTop).toBe(0);
|
||||||
|
|
||||||
|
fireEvent.mouseDown(screen.getByText(/select\.\.\./i));
|
||||||
|
|
||||||
|
await waitFor(() => expect(screen.getByText('Relation 6')).toBeInTheDocument());
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('Relation 6'));
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<Component
|
||||||
|
relations={{
|
||||||
|
...FIXTURES_RELATIONS,
|
||||||
|
data: [...data, newRelation],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => expect(el.parentNode.scrollTop).toBeGreaterThan(0));
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('Load more'));
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<Component
|
||||||
|
relations={{
|
||||||
|
...FIXTURES_RELATIONS,
|
||||||
|
data: [
|
||||||
|
{ id: 7, mainField: 'Relation 7', publicationState: false },
|
||||||
|
...data,
|
||||||
|
newRelation,
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => expect(el.parentNode.scrollTop).toBe(0));
|
||||||
|
});
|
||||||
|
|
||||||
// TODO: check if it is possible to fire scroll event here
|
// TODO: check if it is possible to fire scroll event here
|
||||||
// test.only('should call onSearchNextPage', () => {
|
// test.only('should call onSearchNextPage', () => {
|
||||||
// const spy = jest.fn();
|
// const spy = jest.fn();
|
||||||
|
@ -42,7 +42,7 @@ export const RelationInputDataManager = ({
|
|||||||
|
|
||||||
const relationsFromModifiedData = get(modifiedData, name) ?? [];
|
const relationsFromModifiedData = get(modifiedData, name) ?? [];
|
||||||
|
|
||||||
const currentLastPage = Math.ceil(relationsFromModifiedData.length / RELATIONS_TO_DISPLAY);
|
const currentLastPage = Math.ceil(get(initialData, name, []).length / RELATIONS_TO_DISPLAY);
|
||||||
|
|
||||||
const { relations, search, searchFor } = useRelation(`${slug}-${name}-${initialData?.id ?? ''}`, {
|
const { relations, search, searchFor } = useRelation(`${slug}-${name}-${initialData?.id ?? ''}`, {
|
||||||
name,
|
name,
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
|
|
||||||
|
import { usePrev } from '../usePrev';
|
||||||
|
|
||||||
|
describe('usePrev', () => {
|
||||||
|
const setup = () => renderHook(({ state }) => usePrev(state), { initialProps: { state: 0 } });
|
||||||
|
|
||||||
|
it('should return undefined on initial render', () => {
|
||||||
|
const { result } = setup();
|
||||||
|
|
||||||
|
expect(result.current).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should always return previous state after each update', () => {
|
||||||
|
const { result, rerender } = setup();
|
||||||
|
|
||||||
|
rerender({ state: 2 });
|
||||||
|
expect(result.current).toBe(0);
|
||||||
|
|
||||||
|
rerender({ state: 4 });
|
||||||
|
expect(result.current).toBe(2);
|
||||||
|
|
||||||
|
rerender({ state: 6 });
|
||||||
|
expect(result.current).toBe(4);
|
||||||
|
});
|
||||||
|
});
|
@ -5,3 +5,4 @@ export { default as useLayoutDnd } from './useLayoutDnd';
|
|||||||
export { default as usePluginsQueryParams } from './usePluginsQueryParams';
|
export { default as usePluginsQueryParams } from './usePluginsQueryParams';
|
||||||
export { default as useSyncRbac } from './useSyncRbac';
|
export { default as useSyncRbac } from './useSyncRbac';
|
||||||
export { default as useWysiwyg } from './useWysiwyg';
|
export { default as useWysiwyg } from './useWysiwyg';
|
||||||
|
export { usePrev } from './usePrev';
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {<T>(value: T) => T | undefined}
|
||||||
|
*/
|
||||||
|
export const usePrev = (value) => {
|
||||||
|
const ref = useRef();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ref.current = value;
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return ref.current;
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user