Skip to content

Commit

Permalink
fix: dropdown overflows
Browse files Browse the repository at this point in the history
  • Loading branch information
mwargan committed Mar 7, 2024
1 parent f0a084f commit 2218f4d
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 5 deletions.
23 changes: 23 additions & 0 deletions src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ label:has(>input[type="radio"]:required)::after {
margin-left: calc(var(--pico-nav-link-spacing-vertical) / 2);
}

/**
* Fix for overflow
* @see https://github.com/picocss/pico/issues/485
*/
[role=button],
[type=button],
[type=file]::file-selector-button,
Expand All @@ -187,4 +191,23 @@ button {

[role=group] input+button {
overflow-wrap: normal;
}

/**
* Limit dropwdown height of each item by limiting lines
* @see https://github.com/picocss/pico/issues/486
*/
details.dropdown summary+ul li,
details.dropdown summary+ul li>a {
white-space: break-spaces;

/* Below is not needed, but its a nicety */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 9;
overflow: hidden;
}

details.dropdown summary:not([role]) {
height: auto;
}
71 changes: 71 additions & 0 deletions src/stories/Accordion.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type { Meta, StoryObj } from "@storybook/vue3";
import overflowFixture from "../../cypress/fixtures/overflowingData.json";
import { expect, within } from "@storybook/test";

// Note this type is re-used in the Dropdown.stories.ts file
export type HTMLDetailsElementCustom = HTMLDetailsElement & {
optionText: string;
role: string;
ariaInvalid: string;
};

const meta: Meta<HTMLDetailsElementCustom> = {
title: "Components/Accordion",

tags: ["autodocs"],

render: ({ title, open, role, optionText }) => ({
template: `<details ${open ? "open" : ""}><summary
${
role ? `role="${role}"` : ""
}>${title}</summary><p>${optionText}</p></details>`,
}),

argTypes: {
role: {
options: [undefined, "button"],
},
},

args: {
title: "Hello World",
open: false,
role: undefined,
optionText: "This is a dropdown option",
},
};

export default meta;
type Story = StoryObj<HTMLDetailsElementCustom>;

export const Default: Story = {};

export const Open: Story = {
args: {
open: true,
},
};

export const ButtonRole: Story = {
args: {
role: "button",
},
};

export const OpenWithOverflow: Story = {
args: {
...Open.args,
title: overflowFixture.text_without_spaces,
optionText: overflowFixture.text_without_spaces,
},
play: async ({ canvasElement }: any) => {
const canvas = within(canvasElement);
const summary = canvas.getByText(overflowFixture.text_without_spaces);

expect(summary).toBeVisible();

// Weird issue caused by font line-height changing size of contents within div means the following fails for now, although no user-impact seems to be visible
// eslint-disable-next-line no-secrets/no-secrets
// checkElementForTextOverflow(summary);
},
};
6 changes: 3 additions & 3 deletions src/stories/BaseButton.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export const OutlineContrast: Story = {
// Use a decorator to show a grouped
export const Grouped: Story = {
decorators: [
(story: any) => ({
() => ({
template: `<div role=group><story /><story /><story /></div>`,
}),
],
Expand All @@ -189,7 +189,7 @@ export const GroupedWithOverflowingText: Story = {
default: overflowFixture.text_without_spaces,
},
decorators: [
(story: any) => ({
() => ({
template: `<div role=group><story /><story /><story /></div>`,
}),
],
Expand All @@ -213,7 +213,7 @@ export const InputAndButton: Story = {
default: "Submit",
},
decorators: [
(story: any) => ({
() => ({
template: `
<form>
<fieldset role="group">
Expand Down
151 changes: 151 additions & 0 deletions src/stories/Dropdown.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import type { Meta, StoryObj } from "@storybook/vue3";
import overflowFixture from "../../cypress/fixtures/overflowingData.json";
import { expect, within } from "@storybook/test";
import { checkElementForTextOverflow } from "./utils";
import type { HTMLDetailsElementCustom } from "./Accordion.stories";

const meta: Meta<HTMLDetailsElementCustom> = {
title: "Components/Dropdown",

tags: ["autodocs"],

render: (args) => ({
template: `<summary
${args.role ? `role="${args.role}"` : ""} ${
args.ariaInvalid ? `aria-invalid="${args.ariaInvalid}"` : ""
}>${args.title}</summary><ul><li><a href="#">${
args.optionText
}</a></li><li><a href="#">${args.optionText}</a></li><li><a href="#">${
args.optionText
}</a></li></ul>`,
}),

decorators: [
(story, { args }) => ({
template: `<details ${
args.open ? "open" : ""
} class="dropdown"><story /></details>
`,
}),
],

argTypes: {
role: {
options: [undefined, "button"],
},
ariaInvalid: {
options: [undefined, "true", "false"],
},
},

args: {
title: "Hello World",
open: false,
role: undefined,
ariaInvalid: undefined,
optionText: "This is a dropdown option",
},
};

export default meta;
type Story = StoryObj<HTMLDetailsElementCustom>;

export const Default: Story = {};

export const Open: Story = {
args: {
open: true,
},
};

export const ButtonRole: Story = {
args: {
role: "button",
},
};

export const OpenWithOverflow: Story = {
args: {
...Open.args,
title: overflowFixture.text_without_spaces,
optionText: overflowFixture.text_without_spaces,
},
play: async ({ canvasElement }: any) => {
const canvas = within(canvasElement);
const summary = canvas.getByText(overflowFixture.text_without_spaces);

expect(summary).toBeVisible();

checkElementForTextOverflow(summary);
},
};

export const WithRadioButtons: Story = {
render: ({ optionText }) => ({
template: `
<summary>
Select a phase of matter...
</summary>
<ul>
<li>
<label>
<input type="radio" name="phase" value="solid" />
Solid
</label>
</li>
<li>
<label>
<input type="radio" name="phase" value="liquid" />
Liquid
</label>
</li>
<li>
<label>
<input type="radio" name="phase" value="gas" />
${optionText}
</label>
</li>
<li>
<label>
<input type="radio" name="phase" value="plasma" />
Plasma
</label>
</li>
</ul>`,
}),
};

export const WithCheckboxes: Story = {
render: ({ optionText }) => ({
template: `
<summary>
Select phases of matter...
</summary>
<ul>
<li>
<label>
<input type="checkbox" name="solid" />
Solid
</label>
</li>
<li>
<label>
<input type="checkbox" name="liquid" />
Liquid
</label>
</li>
<li>
<label>
<input type="checkbox" name="gas" />
${optionText}
</label>
</li>
<li>
<label>
<input type="checkbox" name="plasma" />
Plasma
</label>
</li>
</ul>`,
}),
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// This story tests the body, h1-h6
// We will use just the native h1-h6 tags for this story
import type { Meta, StoryObj } from "@storybook/vue3";

type HTMLInputElementCustom = HTMLTableElement & {
Expand Down
9 changes: 9 additions & 0 deletions src/stories/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ export const checkChildrenForOverflow = (

export const checkElementForTextOverflow = (element: Element) => {
const elementRect = element.getBoundingClientRect();

// If the element handles overflow, we can't check for overflow. It might handle it by setting overflow: hidden and nowrap
if (
window.getComputedStyle(element).overflow === "hidden" &&
window.getComputedStyle(element).whiteSpace === "nowrap"
) {
return;
}

expect(element.scrollWidth).toBeLessThanOrEqual(elementRect.width);
expect(element.scrollHeight).toBeLessThanOrEqual(elementRect.height);
};

0 comments on commit 2218f4d

Please sign in to comment.