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
171 changes: 94 additions & 77 deletions src/components/__tests__/emoji-picker.test.browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -438,23 +438,22 @@ describe("EmojiPicker.Root", () => {
it("should support disabling sticky category headers", async () => {
page.render(
<DefaultPage
sticky={false}
listComponents={{
CategoryHeader: ({ category, ...props }) => (
<div
data-testid="category-header"
{...props}
>
<div data-testid="category-header" {...props}>
{category.label}
</div>
),
}}
sticky={false}
/>,
);

await expect.element(page.getByTestId("category-header").nth(1)).not.toHaveStyle({
position: "sticky",
});
await expect
.element(page.getByTestId("category-header").nth(1))
.not.toHaveStyle({
position: "sticky",
});
});
});

Expand Down Expand Up @@ -531,82 +530,100 @@ describe("EmojiPicker.Search", () => {
});

describe("EmojiPicker.Viewport", () => {
it("should virtualize rows based on the viewport height", async () => {
function Page() {
const [viewportHeight, setViewportHeight] = useState(400);
const [rowHeight, setRowHeight] = useState(30);
const [categoryHeaderHeight, setCategoryHeaderHeight] = useState(30);

return (
<DefaultPage
listComponents={{
Row: ({ children, style, ...props }) => (
<div
data-testid="custom-row"
{...props}
style={{ ...style, height: rowHeight }}
>
{children}
</div>
),
CategoryHeader: ({ category, style, ...props }) => (
<div
data-testid="custom-category-header"
{...props}
style={{ ...style, height: categoryHeaderHeight }}
>
{category.label}
</div>
),
}}
>
<input
data-testid="viewport-height"
onChange={(event) => setViewportHeight(Number(event.target.value))}
type="number"
value={viewportHeight}
/>
<input
data-testid="row-height"
onChange={(event) => setRowHeight(Number(event.target.value))}
type="number"
value={rowHeight}
/>
<input
data-testid="category-header-height"
onChange={(event) =>
setCategoryHeaderHeight(Number(event.target.value))
}
type="number"
value={categoryHeaderHeight}
/>
</DefaultPage>
);
}
it.each([
["with sticky headers", true],
["without sticky headers", false],
])(
"should virtualize rows based on the viewport height %s",
async (_, sticky) => {
function Page() {
const [viewportHeight, setViewportHeight] = useState(400);
const [rowHeight, setRowHeight] = useState(30);
const [categoryHeaderHeight, setCategoryHeaderHeight] = useState(30);

return (
<DefaultPage
listComponents={{
Row: ({ children, style, ...props }) => (
<div
data-testid="custom-row"
{...props}
style={{ ...style, height: rowHeight }}
>
{children}
</div>
),
CategoryHeader: ({ category, style, ...props }) => (
<div
data-testid="custom-category-header"
{...props}
style={{ ...style, height: categoryHeaderHeight }}
>
{category.label}
</div>
),
}}
sticky={sticky}
>
<input
data-testid="viewport-height"
onChange={(event) =>
setViewportHeight(Number(event.target.value))
}
type="number"
value={viewportHeight}
/>
<input
data-testid="row-height"
onChange={(event) => setRowHeight(Number(event.target.value))}
type="number"
value={rowHeight}
/>
<input
data-testid="category-header-height"
onChange={(event) =>
setCategoryHeaderHeight(Number(event.target.value))
}
type="number"
value={categoryHeaderHeight}
/>
</DefaultPage>
);
}

page.render(<Page />);
page.render(<Page />);

await expect.element(page.getByText("😀")).toBeInTheDocument();
await expect.element(page.getByText("😀")).toBeInTheDocument();

await expect.element(page.getByRole("row").nth(10)).toBeInTheDocument();
await expect.element(page.getByRole("row").nth(20)).not.toBeInTheDocument();
await expect.element(page.getByRole("row").nth(10)).toBeInTheDocument();
await expect
.element(page.getByRole("row").nth(20))
.not.toBeInTheDocument();

await page.getByTestId("viewport-height").fill("500");
await page.getByTestId("row-height").fill("20");
await page.getByTestId("category-header-height").fill("20");
await page.getByTestId("viewport-height").fill("500");
await page.getByTestId("row-height").fill("20");
await page.getByTestId("category-header-height").fill("20");

await expect.element(page.getByRole("row").nth(10)).toBeInTheDocument();
await expect.element(page.getByRole("row").nth(20)).toBeInTheDocument();
await expect.element(page.getByRole("row").nth(10)).toBeInTheDocument();
await expect.element(page.getByRole("row").nth(20)).toBeInTheDocument();

await page.getByTestId("viewport-height").fill("200");
await page.getByTestId("row-height").fill("100");
await page.getByTestId("category-header-height").fill("400");
await page.getByTestId("viewport-height").fill("200");
await page.getByTestId("row-height").fill("100");
await page.getByTestId("category-header-height").fill("400");

await expect.element(page.getByRole("row").nth(10)).not.toBeInTheDocument();
await expect.element(page.getByRole("row").nth(20)).not.toBeInTheDocument();
});
await expect
.element(page.getByRole("row").nth(10))
.not.toBeInTheDocument();
await expect
.element(page.getByRole("row").nth(20))
.not.toBeInTheDocument();
},
);

it("should virtualize rows based on scroll", async () => {
it.each([
["with sticky headers", true],
["without sticky headers", false],
])("should virtualize rows based on scroll %s", async (_, sticky) => {
function Page() {
const scrollViewport = () => {
const viewport = document.querySelector("[data-testid='viewport']");
Expand All @@ -618,7 +635,7 @@ describe("EmojiPicker.Viewport", () => {
};

return (
<DefaultPage viewportHeight={200}>
<DefaultPage sticky={sticky} viewportHeight={200}>
<button
data-testid="scroll-viewport"
onClick={scrollViewport}
Expand Down
11 changes: 8 additions & 3 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ export function createEmojiPickerStore(
const {
listRef,
viewportRef,
sticky,
rowHeight,
viewportHeight,
categoryHeaderHeight,
Expand Down Expand Up @@ -244,8 +245,8 @@ export function createEmojiPickerStore(

let viewportStartY = viewportScrollY + rowScrollMarginTop;

// Account for sticky headers if the row is in the upper half of the viewport
if (rowY < viewportScrollY + viewportHeight / 2) {
// Account for headers if they are sticky and if the row is in the upper half of the viewport
if (sticky && rowY < viewportScrollY + viewportHeight / 2) {
viewportStartY += categoryHeaderHeight;
}

Expand All @@ -257,7 +258,11 @@ export function createEmojiPickerStore(
// Align to the viewport's top or bottom based on the row's position
top: Math.max(
rowY < viewportStartY + categoryHeaderHeight
? rowY - Math.max(categoryHeaderHeight, rowScrollMarginTop)
? rowY -
Math.max(
sticky ? categoryHeaderHeight : 0,
rowScrollMarginTop,
)
: rowY - viewportHeight + rowHeight + rowScrollMarginBottom,
0,
),
Expand Down