Skip to content
Merged
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
15 changes: 0 additions & 15 deletions src/core/container-query/__tests__/container-query.test.tsx

This file was deleted.

1 change: 0 additions & 1 deletion src/core/container-query/index.ts

This file was deleted.

44 changes: 44 additions & 0 deletions src/core/menu/menu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,47 @@ export const PreventingClosure: Story = {
),
},
}

/**
* If a menu item prevents the automatic closure of the menu, it's likely it will need to manually close
* the menu itself at some point (e.g., after an asynchrous action has settled). To do this, the menu
* element must be used.
*
* The `Menu.getClosestMenuElement` helper is available to assist with this. For example,
*
* ```ts
* const handleClick = (event: React.MouseEvent) => {
* // Prevent the menu from closing automatically
* event.preventDefault()
* const menu = Menu.getClosestMenuElement(event.currentTarget)
*
* await doSomethingAsync()
*
* // Close the menu after the async action has completed
* menu?.hidePopover()
* }
* ```
*/
export const ImperativeClosure: Story = {
args: {
...Example.args,
children: (
<>
<Menu.Item
onClick={async (event) => {
event.preventDefault()
const menu = Menu.getClosestMenuElement(event.currentTarget)

// Simulate an async action
await new Promise((resolve) => setTimeout(resolve, 1000))

menu?.hidePopover()
}}
>
I&apos;ll close the menu in a second
</Menu.Item>
<Menu.Item>But I will</Menu.Item>
</>
),
},
}
2 changes: 1 addition & 1 deletion src/core/menu/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,5 @@ Menu.Divider = MenuDivider
Menu.Group = MenuGroup
Menu.Item = MenuItem

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: make the new helper for menu's available. It's just a reexport of the popover one for now.

// Make this helper available for convenience.
Menu.getClosestMenuElement = Popover.getClosestPopoverElement
Menu.getTriggerProps = Popover.getTriggerProps
7 changes: 5 additions & 2 deletions src/core/menu/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ export const elMenu = css`

padding: var(--spacing-2);

& > ${ElMenuDivider} {
/* NOTE: We do NOT use the immediate child selector here because we want to allow
for dividers and menu groups to be nested within other elements. This is primarily
to support the use of CSSContainerQuery and it's reliance on a wrapping <div> */
& ${ElMenuDivider} {
margin-inline: calc(0px - var(--spacing-2));
}

& > ${ElMenuGroup} {
& ${ElMenuGroup} {
margin-inline: calc(0px - var(--spacing-2));

&:first-child {
Expand Down
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ export * from './core/checkbox-group'
export * from './core/chip'
export * from './core/chip-group'
export * from './core/compact-select-native'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: now handled by the export * from './utils' later on in this barrel file

export * from './core/container-query'
export * from './core/dialog'
export * from './core/drawer'
export * from './core/empty-data'
Expand Down
3 changes: 3 additions & 0 deletions src/storybook/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ Beta versions should be relatively stable but subject to occssional breaking cha
### **5.0.0-beta.44 - ??/??/25**

- **fix:** App Switcher now accepts standard button attributes and fowards them to the underlying trigger button.
- **fix:** Menu groups and dividers will be correctly styled even if they are not direct descendants of the Menu.
- **feat:** Added `Menu.getClosestMenuElement` helper function to allow menu items to more easily find their parent menu. This is particularly useful for menu items that need to manually close the menu.
- **chore!:** Moves `@reapit/elements/core/container-query` to `@reapit/elements/utils/css-container-query` to align with it's actual name (`CSSContainerQuery`) and the fact its not a "core" component.

### **5.0.0-beta.43 - 12/08/25**

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`ContainerQuery > renders correctly and matches snapshot 1`] = `
exports[`renders correctly and matches snapshot 1`] = `
<DocumentFragment>
<style>

.el-container-query-1 {
.el-css-container-query-1 {
display: contents;

@container (max-width: 600px) {
display: none;
}
}
}

</style>
<div
class="el-container-query-1"
class="el-css-container-query-1"
>
<p>
Snapshot Test Content
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { render } from '@testing-library/react'
import { CSSContainerQuery } from '../css-container-query'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: same test as previously implemented, just no describe or it usage.

test('renders correctly and matches snapshot', () => {
const { asFragment } = render(
<CSSContainerQuery condition="(max-width: 600px)">
<p>Snapshot Test Content</p>
</CSSContainerQuery>,
)

expect(asFragment()).toMatchSnapshot()
})
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,16 @@ export const CSSContainerQuery: FC<CSSContainerQueryProps> = ({ children, condit
const id = useRef(baseId++)
return (
<>
<style>
{`
.el-container-query-${id.current} {
<style>{`
.el-css-container-query-${id.current} {
display: contents;

@container ${condition} {
display: none;
}
}`}
</style>
<div className={`el-container-query-${id.current}`}>{children}</div>
}
`}</style>
<div className={`el-css-container-query-${id.current}`}>{children}</div>
</>
)
}
1 change: 1 addition & 0 deletions src/utils/css-container-query/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './css-container-query'
3 changes: 3 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export * from './breakpoints'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: export CSS container query, plus other ones that have been missed in recent work.

export * from './css-container-query'
export * from './keyboard-navigation'
export * from './match-media'
export * from './path'
export * from './popover'
export * from './url-search-params'
52 changes: 52 additions & 0 deletions src/utils/popover/__tests__/get-closest-popover-element.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { getClosestPopoverElement } from '../get-closest-popover-element'

test('returns the element itself if it has a popover attribute', () => {
const element = document.createElement('div')
element.setAttribute('popover', 'auto')

expect(getClosestPopoverElement(element)).toBe(element)
})

test('returns the closest ancestor with popover attribute', () => {
const grandparent = document.createElement('div')
grandparent.setAttribute('popover', 'auto')

const parent = document.createElement('div')
const child = document.createElement('div')

grandparent.appendChild(parent)
parent.appendChild(child)

expect(getClosestPopoverElement(child)).toBe(grandparent)
})

test('returns null when no ancestor has popover attribute', () => {
const parent = document.createElement('div')
const child = document.createElement('div')

parent.appendChild(child)

expect(getClosestPopoverElement(child)).toBeNull()
})

test('works with popover="manual"', () => {
const element = document.createElement('div')
element.setAttribute('popover', 'manual')

expect(getClosestPopoverElement(element)).toBe(element)
})

test('returns the first popover ancestor when multiple exist', () => {
const outerPopover = document.createElement('div')
outerPopover.setAttribute('popover', 'auto')

const innerPopover = document.createElement('div')
innerPopover.setAttribute('popover', 'manual')

const child = document.createElement('div')

outerPopover.appendChild(innerPopover)
innerPopover.appendChild(child)

expect(getClosestPopoverElement(child)).toBe(innerPopover)
})
7 changes: 7 additions & 0 deletions src/utils/popover/get-closest-popover-element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Returns the closest ancestor acting as a popover for the given element. Useful in event
* handlers when needing to imperatively control the popover from one of its descendants.
*/
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: new helper function. This is reexported by Menu

export function getClosestPopoverElement(element: HTMLElement): HTMLElement | null {
return element.closest('[popover]')
}
2 changes: 2 additions & 0 deletions src/utils/popover/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { applyCSSAnchorPositioningPolyfill } from '#src/polyfills/css-anchor-pos
import { buildAnchorPositioningCSS } from './build-anchor-positioning-css'
import { cx } from '@linaria/core'
import { elPopover } from './styles'
import { getClosestPopoverElement } from './get-closest-popover-element'
import { getPopoverTriggerProps } from './get-popover-trigger-props'
import { useLayoutEffect, useRef } from 'react'

Expand Down Expand Up @@ -150,3 +151,4 @@ export function Popover({
}

Popover.getTriggerProps = getPopoverTriggerProps
Popover.getClosestPopoverElement = getClosestPopoverElement
Loading