Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix scoping in the Composite context #3799

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
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
60 changes: 60 additions & 0 deletions examples/composite-menus/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as Ariakit from "@ariakit/react";
import "./style.css";

function App() {
const store = Ariakit.useMenuStore();
return (
<>
<Ariakit.CompositeProvider>
<Ariakit.Composite>
<Ariakit.CompositeRow>
<Ariakit.CompositeItem render={<button>Button A1</button>} />

<Ariakit.CompositeItem
render={
<Ariakit.MenuButton store={store}>Menu A2</Ariakit.MenuButton>
}
/>
<Ariakit.Menu className="menu" store={store}>
<Ariakit.MenuItem className="menu-item">Hello</Ariakit.MenuItem>
</Ariakit.Menu>

<Ariakit.CompositeItem render={<button>Button A3</button>} />
</Ariakit.CompositeRow>

<Ariakit.CompositeRow>
<Ariakit.CompositeItem render={<button>Button B1</button>} />

<Ariakit.MenuProvider>
<Ariakit.CompositeItem
render={<Ariakit.MenuButton>Menu B2</Ariakit.MenuButton>}
/>
<Ariakit.Menu className="menu">
<Ariakit.MenuItem className="menu-item">Hello</Ariakit.MenuItem>
</Ariakit.Menu>
</Ariakit.MenuProvider>

<Ariakit.CompositeItem render={<button>Button B3</button>} />
</Ariakit.CompositeRow>

<Ariakit.CompositeRow>
<Ariakit.CompositeItem render={<button>Button C1</button>} />

<Ariakit.MenuProvider>
<Ariakit.CompositeItem
render={<Ariakit.MenuButton>Menu C2</Ariakit.MenuButton>}
/>
<Ariakit.Menu className="menu">
<Ariakit.MenuItem className="menu-item">Hello</Ariakit.MenuItem>
</Ariakit.Menu>
</Ariakit.MenuProvider>

<Ariakit.CompositeItem render={<button>Button C3</button>} />
</Ariakit.CompositeRow>
</Ariakit.Composite>
</Ariakit.CompositeProvider>
</>
);
}

export default App;
175 changes: 175 additions & 0 deletions examples/composite-menus/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgb(255 255 255 / 87%);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}

a:hover {
color: #535bf2;
}

body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}

h1 {
font-size: 3.2em;
line-height: 1.1;
}

button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}

button:hover {
border-color: #646cff;
}

button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}

@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #fff;
}

a:hover {
color: #747bff;
}

button {
background-color: #f9f9f9;
}
}

#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}

.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}

.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}

@keyframes logo-spin {
from {
transform: rotate(0deg);
}

to {
transform: rotate(360deg);
}
}

@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}

.card {
padding: 2em;
}

.read-the-docs {
color: #888;
}

.menu {
position: relative;
z-index: 50;
display: flex;
max-height: var(--popover-available-height);
min-width: 180px;
flex-direction: column;
overflow: auto;
overscroll-behavior: contain;
border-radius: 0.5rem;
border-width: 1px;
border-style: solid;
border-color: hsl(204deg 20% 88%);
background-color: white;
padding: 0.5rem;
color: black;
box-shadow:
0 10px 15px -3px rgb(0 0 0 / 10%),
0 4px 6px -4px rgb(0 0 0 / 10%);
outline: none !important;
}

.menu:where(.dark, .dark *) {
border-color: hsl(204deg 4% 24%);
background-color: hsl(204deg 4% 16%);
color: white;
box-shadow:
0 10px 15px -3px rgb(0 0 0 / 25%),
0 4px 6px -4px rgb(0 0 0 / 10%);
}

.menu-item {
display: flex;
cursor: default;
scroll-margin: 0.5rem;
align-items: center;
gap: 0.5rem;
border-radius: 0.25rem;
padding: 0.5rem;
outline: none !important;
}

.menu-item[aria-disabled="true"] {
opacity: 0.25;
}

.menu-item[data-active-item] {
background-color: hsl(204deg 100% 40%);
color: white;
}

.menu-item:active,
.menu-item[data-active] {
background-color: hsl(204deg 100% 32%);
padding-top: 9px;
padding-bottom: 7px;
}
87 changes: 87 additions & 0 deletions examples/composite-menus/test-browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { expect, test } from "@playwright/test";

test.beforeEach(async ({ page }) => {
await page.goto("/previews/composite-menus");
});

test("keyboard interactions", async ({ page }) => {
const a1 = page.getByText("Button A1");
const a2Menu = page.getByText("Menu A2");
const b2Menu = page.getByText("Menu B2");
const c2Menu = page.getByText("Menu C2");
const c3 = page.getByText("Button C3");
await expect(a1).toBeVisible();
await expect(a1).not.toBeFocused();
a1.focus();
await expect(a1).toBeFocused();
await page.keyboard.press("ArrowRight");
await expect(a2Menu).toBeVisible();
await expect(a1).not.toBeFocused();
await expect(a2Menu).toBeFocused();
await page.keyboard.press("ArrowRight");
await expect(a2Menu).not.toBeFocused();
await page.keyboard.press("ArrowLeft");
await expect(a2Menu).toBeFocused();
await expect(a2Menu).toHaveAttribute("aria-expanded", "false");
await page.keyboard.press("Enter");
await expect(a2Menu).toHaveAttribute("aria-expanded", "true");
await page.keyboard.press("Escape");
await expect(a2Menu).toHaveAttribute("aria-expanded", "false");
await page.keyboard.press("ArrowUp");
await expect(a2Menu).toHaveAttribute("aria-expanded", "true");
await page.keyboard.press("Escape");
await expect(a2Menu).toHaveAttribute("aria-expanded", "false");
await page.keyboard.press("ArrowDown");
await expect(a2Menu).toHaveAttribute("aria-expanded", "false");
await expect(a2Menu).not.toBeFocused();
await expect(b2Menu).toBeVisible();
await expect(b2Menu).toBeFocused();
await page.keyboard.press("ArrowRight");
await expect(b2Menu).not.toBeFocused();
await page.keyboard.press("ArrowLeft");
await expect(b2Menu).toBeFocused();
await expect(b2Menu).toHaveAttribute("aria-expanded", "false");
await page.keyboard.press("Enter");
await expect(b2Menu).toHaveAttribute("aria-expanded", "true");
await page.keyboard.press("Escape");
await expect(b2Menu).toHaveAttribute("aria-expanded", "false");
await page.keyboard.press("ArrowUp");
await expect(b2Menu).toHaveAttribute("aria-expanded", "false");
await expect(b2Menu).not.toBeFocused();
await expect(a2Menu).toBeFocused();
await page.keyboard.press("ArrowDown");
await expect(a2Menu).not.toBeFocused();
await expect(b2Menu).toBeFocused();
await expect(b2Menu).toHaveAttribute("aria-expanded", "false");
await page.keyboard.press("ArrowDown");
await expect(b2Menu).toHaveAttribute("aria-expanded", "false");
await expect(b2Menu).not.toBeFocused();
await expect(c2Menu).toBeVisible();
await expect(c2Menu).toBeFocused();
await page.keyboard.press("ArrowRight");
await expect(c2Menu).not.toBeFocused();
await page.keyboard.press("ArrowLeft");
await expect(c2Menu).toBeFocused();
await expect(c2Menu).toHaveAttribute("aria-expanded", "false");
await page.keyboard.press("Enter");
await expect(c2Menu).toHaveAttribute("aria-expanded", "true");
await page.keyboard.press("Escape");
await expect(c2Menu).toHaveAttribute("aria-expanded", "false");
await page.keyboard.press("ArrowUp");
await expect(c2Menu).toHaveAttribute("aria-expanded", "false");
await expect(c2Menu).not.toBeFocused();
await expect(b2Menu).toBeFocused();
await page.keyboard.press("ArrowDown");
await expect(b2Menu).not.toBeFocused();
await expect(c2Menu).toBeFocused();
await expect(c2Menu).toHaveAttribute("aria-expanded", "false");
await page.keyboard.press("ArrowDown");
await expect(c2Menu).toHaveAttribute("aria-expanded", "true");
await page.keyboard.press("Escape");
await expect(c2Menu).toHaveAttribute("aria-expanded", "false");
await expect(c2Menu).toBeFocused();
await page.keyboard.press("ArrowRight");
await expect(c2Menu).not.toBeFocused();
await expect(c3).toBeVisible();
await expect(c3).toBeFocused();
});
4 changes: 2 additions & 2 deletions packages/ariakit-react-core/src/composite/composite-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import type { Props } from "../utils/types.ts";
import {
CompositeItemContext,
CompositeRowContext,
useCompositeContext,
useCompositeScopedContext,
} from "./composite-context.tsx";
import type { CompositeStore } from "./composite-store.ts";
import {
Expand Down Expand Up @@ -188,7 +188,7 @@ export const useCompositeItem = createHook<TagName, CompositeItemOptions>(
"aria-posinset": ariaPosInSetProp,
...props
}) {
const context = useCompositeContext();
const context = useCompositeScopedContext();
store = store || context;

const id = useId(props.id);
Expand Down
6 changes: 3 additions & 3 deletions packages/ariakit-react-core/src/composite/composite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
import { createElement, createHook, forwardRef } from "../utils/system.tsx";
import type { Props } from "../utils/types.ts";
import {
CompositeContextProvider,
CompositeScopedContextProvider,
useCompositeProviderContext,
} from "./composite-context.tsx";
import type { CompositeStore, CompositeStoreItem } from "./composite-store.ts";
Expand Down Expand Up @@ -417,9 +417,9 @@ export const useComposite = createHook<TagName, CompositeOptions>(
props = useWrapElement(
props,
(element) => (
<CompositeContextProvider value={store}>
<CompositeScopedContextProvider value={store}>
{element}
</CompositeContextProvider>
</CompositeScopedContextProvider>
),
[store],
);
Expand Down
Loading