From f8c6d6bf9e51c6c2e6852ed8092dc6aadf80e686 Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Thu, 31 Aug 2023 11:13:00 -0400 Subject: [PATCH 1/3] Add `matchAnchorWidth` property to FloatingUI --- web/app/components/floating-u-i/content.ts | 6 ++++ web/app/components/floating-u-i/index.hbs | 1 + web/app/components/floating-u-i/index.ts | 1 + web/app/components/x/dropdown-list/index.hbs | 3 +- web/app/components/x/dropdown-list/index.ts | 1 + .../components/floating-u-i/index-test.ts | 34 ++++++++++++++++++- 6 files changed, 44 insertions(+), 2 deletions(-) diff --git a/web/app/components/floating-u-i/content.ts b/web/app/components/floating-u-i/content.ts index 78c819a45..a821a839c 100644 --- a/web/app/components/floating-u-i/content.ts +++ b/web/app/components/floating-u-i/content.ts @@ -22,6 +22,7 @@ interface FloatingUIContentSignature { placement?: Placement | null; renderOut?: boolean; offset?: OffsetOptions; + matchAnchorWidth?: boolean; }; Blocks: { default: []; @@ -42,6 +43,11 @@ export default class FloatingUIContent extends Component {{yield diff --git a/web/app/components/floating-u-i/index.ts b/web/app/components/floating-u-i/index.ts index 2966b6693..88f6026cf 100644 --- a/web/app/components/floating-u-i/index.ts +++ b/web/app/components/floating-u-i/index.ts @@ -28,6 +28,7 @@ interface FloatingUIComponentSignature { placement?: Placement | null; disableClose?: boolean; offset?: OffsetOptions; + matchAnchorWidth?: boolean; }; Blocks: { anchor: [dd: FloatingUIAnchorAPI]; diff --git a/web/app/components/x/dropdown-list/index.hbs b/web/app/components/x/dropdown-list/index.hbs index c48ed4850..b5e4e93fa 100644 --- a/web/app/components/x/dropdown-list/index.hbs +++ b/web/app/components/x/dropdown-list/index.hbs @@ -10,6 +10,7 @@ @placement={{@placement}} @offset={{@offset}} @disableClose={{@disableClose}} + @matchAnchorWidth={{@matchAnchorWidth}} class="{{unless (eq @placement null) 'hermes-popover'}} x-dropdown-list" data-test-x-dropdown-list-content {{will-destroy this.onDestroy}} @@ -95,7 +96,7 @@ {{else}}
{{#if this.inputIsShown}} diff --git a/web/app/components/x/dropdown-list/index.ts b/web/app/components/x/dropdown-list/index.ts index ef65c7e56..552cebbf6 100644 --- a/web/app/components/x/dropdown-list/index.ts +++ b/web/app/components/x/dropdown-list/index.ts @@ -53,6 +53,7 @@ interface XDropdownListComponentSignature { disabled?: boolean; offset?: OffsetOptions; label?: string; + matchAnchorWidth?: boolean; /** * Whether an asynchronous list is loading. diff --git a/web/tests/integration/components/floating-u-i/index-test.ts b/web/tests/integration/components/floating-u-i/index-test.ts index 5c1eb5e5d..481c99fdb 100644 --- a/web/tests/integration/components/floating-u-i/index-test.ts +++ b/web/tests/integration/components/floating-u-i/index-test.ts @@ -1,4 +1,4 @@ -import { module, test, todo } from "qunit"; +import { module, test } from "qunit"; import { setupRenderingTest } from "ember-qunit"; import { click, render } from "@ember/test-helpers"; import { hbs } from "ember-cli-htmlbars"; @@ -90,4 +90,36 @@ module("Integration | Component | floating-u-i/index", function (hooks) { assert.dom(".close-button").exists('the "close" action was disabled'); }); + + test("the popover can match the anchor width", async function (assert) { + await render(hbs` + + <:anchor as |f|> + + Open + + + <:content as |f|> +
+ Content +
+ +
+ `); + + await click(".open-button"); + + const contentWidth = htmlElement(".content").offsetWidth; + + assert.equal( + contentWidth, + 500, + "the content width matches the anchor width" + ); + }); }); From 8e70a009478c527b569c5e1fa5eeffd8798c20c8 Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Thu, 31 Aug 2023 11:46:53 -0400 Subject: [PATCH 2/3] Add `additionalWidth`; write tests --- web/app/components/floating-u-i/content.ts | 53 +++++++++++---- web/app/components/floating-u-i/index.ts | 3 +- web/app/components/header/search.hbs | 5 +- web/app/components/x/dropdown-list/index.ts | 3 +- web/app/styles/components/header/search.scss | 12 ++-- .../components/floating-u-i/index-test.ts | 66 +++++++++++++++++-- 6 files changed, 113 insertions(+), 29 deletions(-) diff --git a/web/app/components/floating-u-i/content.ts b/web/app/components/floating-u-i/content.ts index a821a839c..dc1aa05ec 100644 --- a/web/app/components/floating-u-i/content.ts +++ b/web/app/components/floating-u-i/content.ts @@ -13,6 +13,11 @@ import { import Component from "@glimmer/component"; import { tracked } from "@glimmer/tracking"; +export interface MatchAnchorWidthOptions { + enabled: boolean; + additionalWidth: number; +} + interface FloatingUIContentSignature { Element: HTMLDivElement; Args: { @@ -22,7 +27,7 @@ interface FloatingUIContentSignature { placement?: Placement | null; renderOut?: boolean; offset?: OffsetOptions; - matchAnchorWidth?: boolean; + matchAnchorWidth?: boolean | MatchAnchorWidthOptions; }; Blocks: { default: []; @@ -43,36 +48,56 @@ export default class FloatingUIContent extends Component {}; return; } let updatePosition = async () => { - let placement = this.args.placement || "bottom-start"; + let _placement = placement || "bottom-start"; - computePosition(this.args.anchor, this.content, { + computePosition(anchor, content, { platform, - placement: placement as Placement, + placement: _placement as Placement, middleware: [offset(this.offset), flip(), shift()], }).then(({ x, y, placement }) => { - this.content.setAttribute("data-floating-ui-placement", placement); + this.maybeMatchAnchorWidth(); + content.setAttribute("data-floating-ui-placement", placement); - Object.assign(this.content.style, { + Object.assign(content.style, { left: `${x}px`, top: `${y}px`, }); }); }; - this.cleanup = autoUpdate(this.args.anchor, this.content, updatePosition); + this.cleanup = autoUpdate(anchor, content, updatePosition); + } + + private maybeMatchAnchorWidth() { + const { matchAnchorWidth, anchor } = this.args; + const { content } = this; + + if (!matchAnchorWidth) { + return; + } + + if (typeof matchAnchorWidth === "boolean") { + content.style.width = `${anchor.offsetWidth}px`; + } else { + content.style.width = `${ + anchor.offsetWidth + matchAnchorWidth.additionalWidth + }px`; + } + + content.style.maxWidth = "none"; } } diff --git a/web/app/components/floating-u-i/index.ts b/web/app/components/floating-u-i/index.ts index 88f6026cf..30cef1974 100644 --- a/web/app/components/floating-u-i/index.ts +++ b/web/app/components/floating-u-i/index.ts @@ -4,6 +4,7 @@ import { guidFor } from "@ember/object/internals"; import { OffsetOptions, Placement } from "@floating-ui/dom"; import Component from "@glimmer/component"; import { tracked } from "@glimmer/tracking"; +import { MatchAnchorWidthOptions } from "./content"; interface FloatingUIAnchorAPI { contentIsShown: boolean; @@ -28,7 +29,7 @@ interface FloatingUIComponentSignature { placement?: Placement | null; disableClose?: boolean; offset?: OffsetOptions; - matchAnchorWidth?: boolean; + matchAnchorWidth?: boolean | MatchAnchorWidthOptions; }; Blocks: { anchor: [dd: FloatingUIAnchorAPI]; diff --git a/web/app/components/header/search.hbs b/web/app/components/header/search.hbs index 8466b6c4b..9250d54dd 100644 --- a/web/app/components/header/search.hbs +++ b/web/app/components/header/search.hbs @@ -1,11 +1,12 @@ {{on-document "keydown" this.maybeFocusInput}}
-
+ diff --git a/web/app/components/x/dropdown-list/index.ts b/web/app/components/x/dropdown-list/index.ts index 552cebbf6..56cbec495 100644 --- a/web/app/components/x/dropdown-list/index.ts +++ b/web/app/components/x/dropdown-list/index.ts @@ -17,6 +17,7 @@ import XDropdownListToggleButtonComponent from "./toggle-button"; import { XDropdownListItemAPI } from "./item"; import { restartableTask, timeout } from "ember-concurrency"; import maybeScrollIntoView from "hermes/utils/maybe-scroll-into-view"; +import { MatchAnchorWidthOptions } from "hermes/components/floating-u-i/content"; export type XDropdownListToggleComponentBoundArgs = | "contentIsShown" @@ -53,7 +54,7 @@ interface XDropdownListComponentSignature { disabled?: boolean; offset?: OffsetOptions; label?: string; - matchAnchorWidth?: boolean; + matchAnchorWidth?: boolean | MatchAnchorWidthOptions; /** * Whether an asynchronous list is loading. diff --git a/web/app/styles/components/header/search.scss b/web/app/styles/components/header/search.scss index bd0f478d0..60ef3f715 100644 --- a/web/app/styles/components/header/search.scss +++ b/web/app/styles/components/header/search.scss @@ -1,5 +1,5 @@ .x-dropdown-list.search-popover { - @apply w-[calc(100%+4px)] max-h-[none] min-w-[420px] max-w-[none]; + @apply max-h-[none] min-w-[420px]; &.no-best-matches { .x-dropdown-list-items { @@ -10,7 +10,7 @@ @apply items-start; &.global-search-popover-header-link { - @apply pt-2.5 pb-[10px] items-center; + @apply items-center pt-2.5 pb-[10px]; } &.is-aria-selected { @@ -28,15 +28,15 @@ } .global-search-best-matches-header { - @apply border-t border-t-color-border-primary pt-2 px-3; + @apply border-t border-t-color-border-primary px-3 pt-2; h5 { - @apply text-display-100 text-color-foreground-faint font-medium; + @apply text-display-100 font-medium text-color-foreground-faint; } } .global-search-result { - @apply flex items-center space-x-3 py-2 px-3 h-20; + @apply flex h-20 items-center space-x-3 py-2 px-3; } .global-search-result-text-content { @@ -44,5 +44,5 @@ } .global-search-result-title { - @apply text-body-200 font-semibold text-color-foreground-strong truncate; + @apply truncate text-body-200 font-semibold text-color-foreground-strong; } diff --git a/web/tests/integration/components/floating-u-i/index-test.ts b/web/tests/integration/components/floating-u-i/index-test.ts index 481c99fdb..0b72af49c 100644 --- a/web/tests/integration/components/floating-u-i/index-test.ts +++ b/web/tests/integration/components/floating-u-i/index-test.ts @@ -96,7 +96,7 @@ module("Integration | Component | floating-u-i/index", function (hooks) { <:anchor as |f|> - <:content as |f|> -
+ <:content> +
+ Content +
+ + + + + <:anchor as |f|> + + Open + + + <:content> +
+ Content +
+ +
+ + + <:anchor as |f|> + + Open + + + <:content> +
Content
`); - await click(".open-button"); + await click("#open-button-1"); - const contentWidth = htmlElement(".content").offsetWidth; + let contentWidth = htmlElement("#content-1").offsetWidth; assert.equal( contentWidth, 500, "the content width matches the anchor width" ); + + await click("#open-button-2"); + + contentWidth = htmlElement("#content-2").offsetWidth; + + assert.equal( + contentWidth, + 600, + "the content width matches the anchor width plus the additional width" + ); + + await click("#open-button-3"); + + contentWidth = htmlElement("#content-3").offsetWidth; + + assert.equal( + contentWidth, + 400, + "the content width matches the anchor width minus the additional width" + ); }); }); From 27f5bbc7aeaebcbb644ed2a7353b00f4e5466c6b Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Thu, 31 Aug 2023 11:55:54 -0400 Subject: [PATCH 3/3] Improve type --- web/app/components/floating-u-i/content.ts | 12 +++++++----- web/app/components/floating-u-i/index.ts | 2 +- web/app/components/x/dropdown-list/index.ts | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/web/app/components/floating-u-i/content.ts b/web/app/components/floating-u-i/content.ts index dc1aa05ec..0e9ab1629 100644 --- a/web/app/components/floating-u-i/content.ts +++ b/web/app/components/floating-u-i/content.ts @@ -13,10 +13,12 @@ import { import Component from "@glimmer/component"; import { tracked } from "@glimmer/tracking"; -export interface MatchAnchorWidthOptions { - enabled: boolean; - additionalWidth: number; -} +export type MatchAnchorWidthOptions = + | boolean + | { + enabled: boolean; + additionalWidth: number; + }; interface FloatingUIContentSignature { Element: HTMLDivElement; @@ -27,7 +29,7 @@ interface FloatingUIContentSignature { placement?: Placement | null; renderOut?: boolean; offset?: OffsetOptions; - matchAnchorWidth?: boolean | MatchAnchorWidthOptions; + matchAnchorWidth?: MatchAnchorWidthOptions; }; Blocks: { default: []; diff --git a/web/app/components/floating-u-i/index.ts b/web/app/components/floating-u-i/index.ts index 30cef1974..d57e90649 100644 --- a/web/app/components/floating-u-i/index.ts +++ b/web/app/components/floating-u-i/index.ts @@ -29,7 +29,7 @@ interface FloatingUIComponentSignature { placement?: Placement | null; disableClose?: boolean; offset?: OffsetOptions; - matchAnchorWidth?: boolean | MatchAnchorWidthOptions; + matchAnchorWidth?: MatchAnchorWidthOptions; }; Blocks: { anchor: [dd: FloatingUIAnchorAPI]; diff --git a/web/app/components/x/dropdown-list/index.ts b/web/app/components/x/dropdown-list/index.ts index 56cbec495..069c417ba 100644 --- a/web/app/components/x/dropdown-list/index.ts +++ b/web/app/components/x/dropdown-list/index.ts @@ -54,7 +54,7 @@ interface XDropdownListComponentSignature { disabled?: boolean; offset?: OffsetOptions; label?: string; - matchAnchorWidth?: boolean | MatchAnchorWidthOptions; + matchAnchorWidth?: MatchAnchorWidthOptions; /** * Whether an asynchronous list is loading.