Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Add option to close dropdown on scroll

**Release date:** 2026-07-27

## Summary

Adds an opt-in `data-dropdown-close-on-scroll` attribute to the dropdown component. When present, the
menu closes on the first scroll event instead of staying open and repositioning itself. The default
behaviour is unchanged: without the attribute the menu keeps following its trigger on scroll.

## Highlights

- ✅ **Close on Scroll** - Add `data-dropdown-close-on-scroll` to dismiss the menu as soon as the user scrolls
- ✅ **Opt-in** - Purely additive; dropdowns without the attribute behave exactly as before
- ✅ **Useful for anchored menus** - Handy when the trigger scrolls out of view and a repositioned menu would feel disconnected

## Added

- `data-dropdown-close-on-scroll` attribute on `[data-dropdown]`, read once during initialisation
(`frontend/js/components-core/dropdown.component.ts`)

## Changed

- The dropdown's scroll handler now closes the menu when the attribute is set, after repositioning

## Usage

```html
<button id="btn">Menu</button>
<div data-dropdown data-dropdown-trigger="btn" data-dropdown-close-on-scroll>
<!-- Menu closes when the user scrolls -->
<a href="#">Item 1</a>
<a href="#">Item 2</a>
</div>
```

# Manual intervention

> ⚠️ **ATTENTION**:
> This update force-syncs `frontend/js/components-core/dropdown.component.ts`, which is normally
> excluded from the frontend sync. If your project has local changes to that file, re-apply them
> after updating.
>
> Run `yarn build` (or `yarn dev`) afterwards so the recompiled component ends up in your bundle.

## Docs

You can find the [documentation on our docs](https://statikbe.github.io/craft/frontend/components/dropdown.html).
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"id": "20260727-add-option-to-close-dropdown-on-scroll",
"title": "Add option to close dropdown on scroll",
"description": "Adds an opt-in data-dropdown-close-on-scroll attribute that closes the dropdown menu as soon as the user scrolls, instead of repositioning it.",
"date": "2026-07-27",
"issues": [572],
"pr": 660,
"requires": [],
"frontend": {
"modify": ["js/components-core/dropdown.component.ts"]
}
}
13 changes: 13 additions & 0 deletions clint/updates/index.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"schema": 1,
"generatedAt": "2026-07-27T14:53:29.273Z",
Expand Down Expand Up @@ -90,6 +90,19 @@
"legacyVersion": "1.0.9",
"hasOps": false
},
{
"id": "20260727-add-option-to-close-dropdown-on-scroll",
"seq": 9,
"title": "Add option to close dropdown on scroll",
"date": "2026-07-27",
"issues": [
572
],
"pr": 660,
"requires": [],
"legacyVersion": null,
"hasOps": true
},
{
"id": "20260727-remove-aria-expanded-on-video-toggle",
"seq": 9,
Expand Down
17 changes: 15 additions & 2 deletions docs/src/examples/dropdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,22 @@
<a href="#" class="block hover:bg-light focus:bg-light px-4 py-2">Item 3</a>
</li>
</ul>
<p>Anim laborum in sit sint ea consectetur pariatur ullamco id veniam voluptate mollit enim. Exercitation tempor amet aliqua esse laboris irure incididunt velit elit amet labore mollit nulla. Commodo exercitation non sint consectetur pariatur consequat enim ad commodo. Est consequat dolore exercitation adipisicing voluptate consectetur laboris.</p>
<button type="button" id="menuTrigger3" class="btn cursor-pointer ">Toggle the dropdown with close on scroll</button>
<ul data-dropdown data-dropdown-trigger="menuTrigger3" data-dropdown-close-on-scroll data-dropdown-placement="right-start" class="hidden open:block py-2 bg-white shadow rounded-lg text-black">
<li>
<a href="#" class="block hover:bg-light focus:bg-light px-4 py-2">Item 1</a>
</li>
<li>
<a href="#" class="block hover:bg-light focus:bg-light px-4 py-2">Item 2</a>
</li>
<li>
<a href="#" class="block hover:bg-light focus:bg-light px-4 py-2">Item 3</a>
</li>
</ul>
<p>Ullamco duis cupidatat est aute velit. Eiusmod labore veniam nulla id voluptate. Et laboris officia quis ea. Aliquip reprehenderit reprehenderit quis. Ut eu in irure fugiat laboris reprehenderit cupidatat amet qui incididunt eu. Deserunt labore sit incididunt. Consectetur aliqua cillum aliquip irure irure aliquip enim mollit eu enim ut exercitation anim et duis. Magna ea Lorem quis eu ut laborum minim et.</p>
<button type="button" id="menuTrigger3" class="btn cursor-pointer mt-6">Toggle the dropdown up</button>
<ul data-dropdown data-dropdown-trigger="menuTrigger3" class="hidden open:block py-2 bg-white shadow rounded-lg text-black">
<button type="button" id="menuTrigger4" class="btn cursor-pointer mt-6">Toggle the dropdown up</button>
<ul data-dropdown data-dropdown-trigger="menuTrigger4" class="hidden open:block py-2 bg-white shadow rounded-lg text-black">
<li>
<a href="#" class="block hover:bg-light focus:bg-light px-4 py-2">Item 1</a>
</li>
Expand Down
25 changes: 25 additions & 0 deletions docs/src/frontend/components/dropdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ A fully accessible dropdown component that uses [@floating-ui/dom](https://float
- ✅ **Flexible Positioning** - Absolute or fixed positioning strategies
- ✅ **Min-width Matching** - Menu width matches trigger button by default
- ✅ **Custom Position Element** - Can position relative to different element
- ✅ **Close on Scroll** - Optionally closes the dropdown when the user scrolls

## Use Cases

Expand Down Expand Up @@ -81,6 +82,7 @@ Below is a table describing the attributes you can use with the dropdown compone
| `data-dropdown-placement` | `bottom-start` | Placement of the dropdown relative to trigger. See [@floating-ui placements](https://floating-ui.com/docs/computePosition#placement) for all options. |
| `data-dropdown-position-element` | trigger button | ID of an element to calculate positioning from (defaults to the trigger button). Useful when button is inside a larger container. |
| `data-dropdown-strategy` | `absolute` | Positioning strategy: `"absolute"` (relative to offset parent) or `"fixed"` (relative to viewport). Use `"fixed"` for dropdowns in scrollable containers. |
| `data-dropdown-close-on-scroll` | `false` | When present, closes the dropdown as soon as the user scrolls. By default the menu stays open and repositions on scroll instead. |

::: warning Button Element Requirement
The trigger element **must be a `<button>` element**. The component validates this and logs an error if it's not a button:
Expand Down Expand Up @@ -250,6 +252,29 @@ Position the dropdown relative to a different element than the trigger button:
- Menu should align with container edges
- Complex layouts with nested elements

## Close on Scroll

By default the dropdown stays open while scrolling and repositions itself using @floating-ui/dom's `autoUpdate`. Add `data-dropdown-close-on-scroll` to close the menu as soon as the user scrolls instead:

```html
<button id="btn">Menu</button>
<div data-dropdown data-dropdown-trigger="btn" data-dropdown-close-on-scroll>
<!-- Menu closes when the user scrolls -->
<a href="#">Item 1</a>
<a href="#">Item 2</a>
</div>
```

**Use when:**

- The dropdown is anchored to an element that scrolls out of view
- You prefer dismissing the menu over having it follow the trigger
- Long pages where a lingering repositioned menu would feel disconnected

::: tip Repositioning vs. Closing
Without this attribute the menu follows the trigger on scroll. With it, the menu closes on the first scroll event—choose whichever fits the interaction.
:::

## Keyboard Navigation

The dropdown includes full keyboard support:
Expand Down
13 changes: 10 additions & 3 deletions frontend/js/components-core/dropdown.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
private menuItems: Array<HTMLElement>;
private placement: Placement;
private strategy: 'fixed' | 'absolute' = 'absolute'; // Positioning strategy
private closeOnScroll: boolean = false;

// Key codes for keyboard navigation

Expand All @@ -46,7 +47,7 @@
if (!dropdown.hasAttribute('data-dropdown-trigger')) {
console.error(
'Please add data-dropdown-trigger to the dropdown element. Example: <div data-dropdown data-dropdown-trigger="button-id"><button id="button-id">Toggle</button><div>Menu</div></div>',
dropdown
dropdown,
);
return;
}
Expand Down Expand Up @@ -87,11 +88,13 @@
}
}

this.closeOnScroll = dropdown.hasAttribute('data-dropdown-close-on-scroll');

Check failure on line 91 in frontend/js/components-core/dropdown.component.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `.dataset` over `hasAttribute(…)`.

See more on https://sonarcloud.io/project/issues?id=statikbe_craft&issues=AZ-jdFyFdnI7fSwunBsM&open=AZ-jdFyFdnI7fSwunBsM&pullRequest=660

// Get all interactive elements within the menu
this.menuItems = Array.from(
this.menuElement.querySelectorAll(
'a[href],button:not([disabled]),input:not([disabled]),textarea:not([disabled]),select:not([disabled])'
)
'a[href],button:not([disabled]),input:not([disabled]),textarea:not([disabled]),select:not([disabled])',
),
);

// Initially hide the menu
Expand Down Expand Up @@ -171,6 +174,10 @@
// Handle scroll events
private scrollAction(e: Event) {
this.positionMenu();

if (this.closeOnScroll) {
this.toggleMenu();
}
}

// Handle keyboard navigation
Expand Down
Loading