Skip to content
Closed
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
28 changes: 27 additions & 1 deletion src/components/list/paginated_list/PaginatedList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const PaginatedList = ({
const [isLoading, setIsLoading] = useState(false);
const [loadError, setLoadError] = useState(false);
const [currentResources, setCurrentResources] = useData(currentListId, []);
const [pinnedElements, setPinnedElements] = useState([]);
const [totalElements, setTotalElements] = useState(
currentResources?.length || 0,
);
Expand Down Expand Up @@ -161,6 +162,7 @@ export const PaginatedList = ({
resource,
activeTagId,
perPage,
pinnedElements,
]);

useEffect(() => {
Expand All @@ -170,6 +172,18 @@ export const PaginatedList = ({
}
}, [page]);

// Keep pinned elements (e.g. editorial/selected comments) in sync from the
// shared ListContext, so they always stay at the top of the list whatever
// the active sort/filter.
useEffect(() => {
if (list.addPinnedElements && currentListId in list.addPinnedElements) {
const pinned = list.addPinnedElements[currentListId];
setPinnedElements(
Array.isArray(pinned) ? pinned : pinned != null ? [pinned] : [],
);
}
}, [list.addPinnedElements, currentListId]);

useEffect(() => {
if (list.addElements && currentListId in list.addElements) {
if (list.addElements[currentListId].length > 0) {
Expand Down Expand Up @@ -282,6 +296,8 @@ export const PaginatedList = ({
) {
newElements = [...list.addElements[currentListId], newElements];
}
newElements = placePinnedFirst(newElements);
newElements = uniqueBy(newElements, uniqueIdKey || "id");
addElements(newElements);
setIsLoading(false);
})
Expand All @@ -297,9 +313,19 @@ export const PaginatedList = ({
}
};

const placePinnedFirst = (resources) => {
if (pinnedElements.length === 0) {
return resources;
}
const key = uniqueIdKey || "id";
const pinnedIds = pinnedElements.map((element) => element[key]);
const nonPinned = resources.filter((element) => !pinnedIds.includes(element[key]));
return [...pinnedElements, ...nonPinned];
};

const handleAddElements = (elements) => {
setCurrentResources((prevElements) =>
uniqueBy([...elements, ...prevElements], uniqueIdKey || "id"),
uniqueBy(placePinnedFirst([...elements, ...prevElements]), uniqueIdKey || "id"),
);
if (onElementsLoad) {
onElementsLoad(elements);
Expand Down
65 changes: 64 additions & 1 deletion src/components/list/paginated_list/PaginatedList.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { StandardErrorBoundary } from "@logora/debate/error/standard_error_bound
import { ResponsiveProvider } from "@logora/debate/hooks/use_responsive";
import { IconProvider } from "@logora/debate/icons/icon_provider";
import { ListProvider, useList } from "@logora/debate/list/list_provider";
import { act, render, screen } from "@testing-library/react";
import { act, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";
import { IntlProvider } from "react-intl";
Expand Down Expand Up @@ -1401,4 +1401,67 @@ describe("PaginatedList", () => {

expect(screen.getByText("Updated second item")).toBeTruthy();
});
it("should keep pinned elements at the top regardless of sort", async () => {
let pinnedList;
const pinnedItems = [
{ id: 901, name: "Pinned editor comment A" },
{ id: 902, name: "Pinned editor comment B" },
];

const PinComponent = () => {
const list = useList();
pinnedList = list;
return null;
};

await act(async () => {
render(
<BrowserRouter>
<IntlProvider locale="en">
<ResponsiveProvider>
<DataProviderContext.Provider value={{ dataProvider: data }}>
<IconProvider library={regularIcons}>
<ListProvider>
<PinComponent />
<PaginatedList
currentListId={"itemList"}
resource={"items"}
sort={"-created_at"}
resourcePropName={"item"}
perPage={10}
withPagination={false}
countless={true}
display="column"
sortOptions={[
{ type: "sort", value: "-created_at", name: "recent" },
{ type: "sort", value: "-score", name: "relevance" },
]}
>
<ListItem />
</PaginatedList>
</ListProvider>
</IconProvider>
</DataProviderContext.Provider>
</ResponsiveProvider>
</IntlProvider>
</BrowserRouter>,
);
});

expect(screen.getAllByTestId("list-item")).toHaveLength(3);

await act(async () => {
pinnedList.addPinned("itemList", pinnedItems);
});

await waitFor(() => {
const names = screen
.getAllByTestId("list-item")
.map((node) => node.textContent);
expect(names[0]).toBe("Pinned editor comment A");
expect(names[1]).toBe("Pinned editor comment B");
expect(names).toHaveLength(5);
});
});

});