mirror of
https://github.com/vector-im/element-call.git
synced 2026-09-22 22:29:30 +00:00
Keep the footer while a menu opened from it is open
- In a short window the footer fades out when the pointer leaves the window. The chevron menus are portalled to the document, so they stayed behind, their trigger faded out beneath them. - The footer already stays for keyboard focus. A menu's focus is outside it, so the trigger's aria-expanded tracks it instead. - Pre-existing: the camera chevron does the same on main today. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,7 +43,10 @@ Please see LICENSE in the repository root for full details.
|
|||||||
inset-inline: 0;
|
inset-inline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer.overlay:has(:focus-visible) {
|
/* Keep the footer while it is in use: focus inside it, or a menu opened from
|
||||||
|
it. The menu is portalled out, so the trigger's aria-expanded tracks it. */
|
||||||
|
.footer.overlay:has(:focus-visible),
|
||||||
|
.footer.overlay:has([aria-expanded="true"]) {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
pointer-events: initial;
|
pointer-events: initial;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { Link } from "@vector-im/compound-web";
|
|||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
import { CallFooter, type FooterSnapshot } from "./CallFooter";
|
import { CallFooter, type FooterSnapshot } from "./CallFooter";
|
||||||
import inCallViewStyles from "../room/InCallView.module.css";
|
import inCallViewStyles from "../room/InCallView.module.css";
|
||||||
|
import styles from "./CallFooter.module.css";
|
||||||
import { useStaticViewModel } from "../state/ViewModel";
|
import { useStaticViewModel } from "../state/ViewModel";
|
||||||
import { ReactionsSenderContext } from "../reactions/useReactionsSender";
|
import { ReactionsSenderContext } from "../reactions/useReactionsSender";
|
||||||
import { type ReactionOption } from "../reactions";
|
import { type ReactionOption } from "../reactions";
|
||||||
@@ -175,6 +176,41 @@ export const WithAudioAndVideoOptions: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const StaysWhileAMenuIsOpen: Story = {
|
||||||
|
...Default,
|
||||||
|
args: {
|
||||||
|
...WithAudioAndVideoOptions.args,
|
||||||
|
// As it is in a short window, where the footer overlays the call and hides
|
||||||
|
// itself once nothing is happening.
|
||||||
|
asOverlay: true,
|
||||||
|
showFooter: true,
|
||||||
|
},
|
||||||
|
play: async ({ canvasElement }): Promise<void> => {
|
||||||
|
const footer = canvasElement.querySelector<HTMLElement>(
|
||||||
|
'[data-testid="footer-container"]',
|
||||||
|
)!;
|
||||||
|
await userEvent.click(
|
||||||
|
within(canvasElement).getByRole("button", { name: "Microphone" }),
|
||||||
|
);
|
||||||
|
await expect(document.body.querySelector('[role="menu"]')).not.toBeNull();
|
||||||
|
|
||||||
|
// The call now decides to hide the footer, which is the class it does it
|
||||||
|
// with. The menu is portalled out of the footer, so the focus inside it is
|
||||||
|
// not something the footer can see: without the trigger's aria-expanded to
|
||||||
|
// go on, this would fade the footer out and take the menu's anchor with it.
|
||||||
|
// The call now decides to hide the footer, which is the class it does that
|
||||||
|
// with. The menu is portalled out of the footer, so the focus inside it is
|
||||||
|
// not something the footer can see: without the trigger's aria-expanded to
|
||||||
|
// go on, this fades the footer out and takes the menu's anchor with it.
|
||||||
|
footer.classList.add(styles.hidden);
|
||||||
|
|
||||||
|
// The footer fades over 0.15s, so a reading taken now is the value it
|
||||||
|
// started from whatever happens next. Let the transition finish first.
|
||||||
|
await new Promise((settled) => setTimeout(settled, 400));
|
||||||
|
await expect(getComputedStyle(footer).opacity).toBe("1");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const AudioBusy: Story = {
|
export const AudioBusy: Story = {
|
||||||
...Default,
|
...Default,
|
||||||
args: {
|
args: {
|
||||||
|
|||||||
Reference in New Issue
Block a user