diff --git a/src/assets/main.css b/src/assets/main.css index e04f10e..fc3f418 100644 --- a/src/assets/main.css +++ b/src/assets/main.css @@ -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, @@ -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; } \ No newline at end of file diff --git a/src/stories/Accordion.stories.ts b/src/stories/Accordion.stories.ts new file mode 100644 index 0000000..b344e3a --- /dev/null +++ b/src/stories/Accordion.stories.ts @@ -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 = { + title: "Components/Accordion", + + tags: ["autodocs"], + + render: ({ title, open, role, optionText }) => ({ + template: `
${title}

${optionText}

`, + }), + + 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; + +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); + }, +}; diff --git a/src/stories/BaseButton.stories.ts b/src/stories/BaseButton.stories.ts index 02cf23e..3bdedf5 100644 --- a/src/stories/BaseButton.stories.ts +++ b/src/stories/BaseButton.stories.ts @@ -178,7 +178,7 @@ export const OutlineContrast: Story = { // Use a decorator to show a grouped export const Grouped: Story = { decorators: [ - (story: any) => ({ + () => ({ template: `
`, }), ], @@ -189,7 +189,7 @@ export const GroupedWithOverflowingText: Story = { default: overflowFixture.text_without_spaces, }, decorators: [ - (story: any) => ({ + () => ({ template: `
`, }), ], @@ -213,7 +213,7 @@ export const InputAndButton: Story = { default: "Submit", }, decorators: [ - (story: any) => ({ + () => ({ template: `
diff --git a/src/stories/Dropdown.stories.ts b/src/stories/Dropdown.stories.ts new file mode 100644 index 0000000..a367b52 --- /dev/null +++ b/src/stories/Dropdown.stories.ts @@ -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 = { + title: "Components/Dropdown", + + tags: ["autodocs"], + + render: (args) => ({ + template: `${args.title}`, + }), + + decorators: [ + (story, { args }) => ({ + template: ` + `, + }), + ], + + 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; + +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: ` + + Select a phase of matter... + +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
`, + }), +}; + +export const WithCheckboxes: Story = { + render: ({ optionText }) => ({ + template: ` + + Select phases of matter... + +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
`, + }), +}; diff --git a/src/stories/Tables.stories.ts b/src/stories/Table.stories.ts similarity index 94% rename from src/stories/Tables.stories.ts rename to src/stories/Table.stories.ts index 75acd27..564052d 100644 --- a/src/stories/Tables.stories.ts +++ b/src/stories/Table.stories.ts @@ -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 & { diff --git a/src/stories/utils.ts b/src/stories/utils.ts index 329b9f7..c4575c1 100644 --- a/src/stories/utils.ts +++ b/src/stories/utils.ts @@ -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); };