diff --git a/packages/origin/src/components/Autocomplete/Autocomplete.test-stories.tsx b/packages/origin/src/components/Autocomplete/Autocomplete.test-stories.tsx
index bf93332e3..144425217 100644
--- a/packages/origin/src/components/Autocomplete/Autocomplete.test-stories.tsx
+++ b/packages/origin/src/components/Autocomplete/Autocomplete.test-stories.tsx
@@ -19,6 +19,11 @@ const fruits: Fruit[] = [
{ value: "elderberry", label: "Elderberry" },
];
+const longFruits: Fruit[] = Array.from({ length: 40 }, (_, index) => ({
+ value: `fruit-${index + 1}`,
+ label: `Fruit ${index + 1}`,
+}));
+
const groupedItems = [
{
label: "Fruits",
@@ -61,6 +66,34 @@ export function BasicAutocomplete() {
);
}
+/**
+ * Autocomplete with enough items to require list scrolling.
+ */
+export function LongListAutocomplete() {
+ return (
+
item.label}
+ >
+
+
+
+
+ No results found.
+
+ {(item: Fruit) => (
+
+ {item.label}
+
+ )}
+
+
+
+
+
+ );
+}
+
/**
* Autocomplete with leading icons
*/
diff --git a/packages/origin/src/components/Autocomplete/Autocomplete.test.tsx b/packages/origin/src/components/Autocomplete/Autocomplete.test.tsx
index d5446208f..d4053943e 100644
--- a/packages/origin/src/components/Autocomplete/Autocomplete.test.tsx
+++ b/packages/origin/src/components/Autocomplete/Autocomplete.test.tsx
@@ -1,6 +1,7 @@
import { test, expect } from "@playwright/experimental-ct-react";
import {
BasicAutocomplete,
+ LongListAutocomplete,
WithLeadingIcon,
WithDisabledItems,
DisabledAutocomplete,
@@ -32,6 +33,94 @@ test.describe("Autocomplete", () => {
await expect(page.getByRole("listbox")).toBeVisible();
});
+ test("keeps long-list scrolling on the list", async ({ mount, page }) => {
+ const component = await mount(
);
+ const input = component.getByPlaceholder("Search fruits...");
+
+ await input.focus();
+ await input.press("ArrowDown");
+
+ const popup = page.getByTestId("autocomplete-long-list-popup");
+ const listbox = page.getByTestId("autocomplete-long-list");
+ await expect(listbox).toBeVisible();
+
+ const state = await listbox.evaluate((list) => {
+ const popup = document.querySelector(
+ '[data-testid="autocomplete-long-list-popup"]',
+ );
+ const firstItem = list.querySelector('[role="option"]');
+
+ if (!(popup instanceof HTMLElement)) {
+ throw new Error("Autocomplete long-list popup is missing");
+ }
+
+ if (!(firstItem instanceof HTMLElement)) {
+ throw new Error("Autocomplete list is missing option rows");
+ }
+
+ const listStyles = window.getComputedStyle(list);
+ const popupStyles = window.getComputedStyle(popup);
+ const itemStyles = window.getComputedStyle(firstItem);
+ popup.scrollTop = popup.scrollHeight;
+ list.scrollTop = list.scrollHeight;
+
+ return {
+ itemFlexShrink: itemStyles.flexShrink,
+ itemHeight: itemStyles.height,
+ itemRenderedHeight: firstItem.getBoundingClientRect().height,
+ popupMaxHeight: popupStyles.maxHeight,
+ popupOverflowY: popupStyles.overflowY,
+ popupHasScrollableOverflow: popup.scrollHeight > popup.clientHeight,
+ popupCanScroll: popup.scrollTop > 0,
+ listMaxHeight: listStyles.maxHeight,
+ listOverflowY: listStyles.overflowY,
+ listOverscrollBehaviorY: listStyles.overscrollBehaviorY,
+ listScrollPaddingBlockEnd: listStyles.scrollPaddingBlockEnd,
+ listScrollPaddingBlockStart: listStyles.scrollPaddingBlockStart,
+ listHasScrollableOverflow: list.scrollHeight > list.clientHeight,
+ listCanScroll: list.scrollTop > 0,
+ };
+ });
+
+ expect(state.itemFlexShrink).toBe("0");
+ expect(state.itemHeight).toBe("36px");
+ expect(state.itemRenderedHeight).toBeGreaterThanOrEqual(34);
+ expect(state.popupMaxHeight).toBe("none");
+ expect(state.popupOverflowY).toBe("hidden");
+ expect(state.popupHasScrollableOverflow).toBe(false);
+ expect(state.popupCanScroll).toBe(false);
+ expect(state.listMaxHeight).not.toBe("none");
+ expect(state.listOverflowY).toBe("auto");
+ expect(state.listOverscrollBehaviorY).toBe("contain");
+ expect(
+ Number.parseFloat(state.listScrollPaddingBlockStart),
+ ).toBeGreaterThan(0);
+ expect(
+ Number.parseFloat(state.listScrollPaddingBlockEnd),
+ ).toBeGreaterThan(0);
+ expect(state.listHasScrollableOverflow).toBe(true);
+ expect(state.listCanScroll).toBe(true);
+
+ await expect(popup).toBeVisible();
+ await expect(
+ page.getByRole("option", { name: "Fruit 40" }),
+ ).toBeVisible();
+ });
+
+ test("filters long-list object items by label", async ({ mount, page }) => {
+ const component = await mount(
);
+ const input = component.getByPlaceholder("Search fruits...");
+
+ await input.fill("40");
+
+ await expect(
+ page.getByRole("option", { name: "Fruit 40" }),
+ ).toBeVisible();
+ await expect(
+ page.getByRole("option", { name: "Fruit 1" }),
+ ).not.toBeVisible();
+ });
+
test("filters items as user types", async ({ mount, page }) => {
const component = await mount(
);
const input = component.getByPlaceholder("Search fruits...");
diff --git a/packages/origin/src/components/Button/Button.module.scss b/packages/origin/src/components/Button/Button.module.scss
index 83016c6ea..621af9916 100644
--- a/packages/origin/src/components/Button/Button.module.scss
+++ b/packages/origin/src/components/Button/Button.module.scss
@@ -31,6 +31,10 @@
}
}
+.fullWidth {
+ width: 100%;
+}
+
.dense {
--button-icon-size: 12px;
diff --git a/packages/origin/src/components/Button/Button.stories.tsx b/packages/origin/src/components/Button/Button.stories.tsx
index 88eb01af4..826768124 100644
--- a/packages/origin/src/components/Button/Button.stories.tsx
+++ b/packages/origin/src/components/Button/Button.stories.tsx
@@ -54,6 +54,7 @@ const meta: Meta
= {
},
loading: { control: "boolean" },
disabled: { control: "boolean" },
+ fullWidth: { control: "boolean" },
children: { control: "text" },
},
};
@@ -67,6 +68,7 @@ export const Default: Story = {
size: "default",
loading: false,
disabled: false,
+ fullWidth: false,
children: "Button",
},
};
diff --git a/packages/origin/src/components/Button/Button.test-stories.tsx b/packages/origin/src/components/Button/Button.test-stories.tsx
index 0e0b1cff7..574223a3f 100644
--- a/packages/origin/src/components/Button/Button.test-stories.tsx
+++ b/packages/origin/src/components/Button/Button.test-stories.tsx
@@ -1,4 +1,4 @@
-import { Button } from "./Button";
+import { Button, ButtonLink } from "./Button";
const ChevronLeft = () => (