Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
3 changes: 3 additions & 0 deletions .codex/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[mcp_servers.react-aria]
command = "npx"
args = ["@react-aria/mcp@latest"]
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public/critical.css
packages/volto/data
storybook-static/
/playwright
/racTailwind
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What is this for?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I have the RAC starter locally, for agents consume it as reference. I used also RAC MCP on this one.

I need to polish it stil... I haven't made a thorough review yet.


# Documentation
_build/
Expand Down
70 changes: 70 additions & 0 deletions packages/components/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# AGENTS.md

This file applies only to `packages/components` and its subdirectories.

## What This Package Is

- `@plone/components` is a thin wrapper layer around `react-aria-components`.
- Components should be usable out of the box in Seven and in Volto.
- Keep components presentational and lightweight. Avoid adding app-specific behavior, data logic, or i18n machinery here.

## Component Model

- Prefer staying very close to the underlying React Aria Components API.
- Do not reinvent component behavior that RAC already provides.
- Add Plone value mainly through packaging, small ergonomic wrappers, and styling.
- Some components, such as `Breadcrumbs`, are intentionally adapted for Seven/Volto and REST API use cases. In those cases, the wrapper props and helpers may shape data for that environment, but the underlying RAC behavior should remain intact.
- Even adapted components should still behave like thin proxies: keep forwarding supported props through to the underlying RAC component so upstream RAC documentation and expectations continue to apply.

## Two Flavours

- Components may exist in two flavours:
- basic: CSS-styled components
- Quanta styles for a few CSS-styled components in `src/styles/quanta/`: CSS assets for Quanta-styled output
- Quanta components: the Tailwind-styled React components, named with the `.quanta.tsx` suffix
- Both flavours live under the same component folder in `src/components/<ComponentName>/`.
- Basic components are exported from `src/index.ts`.
- Tailwind Quanta components are exported from `src/quanta/index.ts` and are the real Quanta component implementation.
- Keep tree-shaking in mind when adding exports or shared helpers.

## Styles

- Built CSS lives under `src/styles`.
- Basic component styles live in `src/styles/basic/`, usually one CSS file per component, and are bundled from `src/styles/basic/main.css`.
- `src/styles/quanta/` contains CSS-based Quanta style definitions and is bundled from `src/styles/quanta/main.css`.
- Do not confuse `src/styles/quanta/` with the Tailwind Quanta component implementation. The Tailwind components are the `.quanta.tsx` files in `src/components`.
- Other `src/styles` folders are for shared assets such as static files and fonts.
- When adding or renaming a styled component, make sure the corresponding style entry is wired into the appropriate `main.css`.

## Stories

- Every public component should have a Storybook story.
- Keep stories colocated with the component in the same folder.
- If both basic and Quanta variants are public, prefer stories for both.

## Icons

- Raw SVG icons live in `src/icons`.
- Ready-to-use React icon components live in `src/components/icons`.
- When adding an SVG icon, also add its React component counterpart and export it from the relevant index when needed.
- Keep SVG and React component names aligned.

## Editing Rules

- Keep changes minimal and package-local.
- Prefer extending existing component folders and patterns over introducing new abstractions.
- If adding a new public component, check all relevant pieces:
- component file
- optional `.quanta.tsx` variant
- stories
- styles
- exports
- tests when behavior is non-trivial

## Validation

- Prefer targeted checks from this package:
- `pnpm --filter @plone/components test --run`
- `pnpm --filter @plone/components lint`
- `pnpm --filter @plone/components build`
- Run `pnpm --filter @plone/components eslint:fix` after editing component code. This package uses formatting/lint tooling that reorders Tailwind utilities, so apply it before finishing changes.
37 changes: 37 additions & 0 deletions packages/components/news/+refactorComponents.breaking
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Quanta Menu API cleanup

The Quanta `Menu` component was refactored to behave as a thin wrapper around `react-aria-components` instead of exposing a custom data-driven API.

## Removed

- The custom `menuItems` prop.
- The bundled trigger API based on `button`, `onPress`, and `placement` props on `Menu`.
- The internal item schema used to describe menu entries with fields such as `label`, `description`, `keyboard`, `icon`, `separator`, `section`, `header`, and nested `children`.
- Automatic rendering of sections and separators from that custom schema.
- Automatic rendering of text slots, icons, keyboard shortcuts, and similar item content from custom item objects.
- The custom `title` prop on `MenuSection`.

## Added

- RAC-aligned Quanta primitives:
- `Menu`
- `MenuItem`
- `MenuTrigger`
- `SubmenuTrigger`
- `MenuSection`
- `MenuSeparator`

## Migration

Consumers should now compose Quanta menus using the standard `react-aria-components` structure:

- Wrap menus with `MenuTrigger` instead of passing trigger props to `Menu`.
- Pass `MenuItem`, `MenuSection`, and `MenuSeparator` as children instead of a `menuItems` array.
- Use `Header` inside `MenuSection`, or `aria-label` when there is no visible header, instead of a custom `title` prop.
- Render icons, descriptions, labels, keyboard shortcuts, and links explicitly in `MenuItem` children using RAC patterns and slots.

This removes opinionated behavior from Quanta `Menu` and makes the component API match upstream RAC usage more closely.

The same alignment was also applied to the basic `Menu` component so both basic and Quanta flavours now follow the same RAC composition model.

@sneridagh
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import React from 'react';
import { BlockToolbar } from './BlockToolbar';
import { Group, Separator, Text, ToggleButton } from 'react-aria-components';
import { Menu, MenuItem } from '../Menu/Menu';
import {
Button,
Group,
Separator,
Text,
ToggleButton,
} from 'react-aria-components';
import { Menu, MenuItem, MenuTrigger } from '../Menu/Menu';

import { BoldIcon } from '../icons/BoldIcon';
import { ItalicIcon } from '../icons/ItalicIcon';
Expand Down Expand Up @@ -39,24 +45,29 @@ export const Example = (args: any) => (
</ToggleButton>
</Group>
<Separator orientation="vertical" />
<Menu button={<MoreoptionsIcon />}>
<MenuItem>
<SettingsIcon />
<Text slot="label">Settings</Text>
</MenuItem>
<MenuItem>
<RowbeforeIcon />
<Text slot="label">Insert block before</Text>
</MenuItem>
<MenuItem>
<RowafterIcon />
<Text slot="label">Insert block after</Text>
</MenuItem>
<Separator />
<MenuItem>
<BinIcon />
<Text slot="label">Remove block</Text>
</MenuItem>
</Menu>
<MenuTrigger>
<Button>
<MoreoptionsIcon />
</Button>
<Menu>
<MenuItem>
<SettingsIcon />
<Text slot="label">Settings</Text>
</MenuItem>
<MenuItem>
<RowbeforeIcon />
<Text slot="label">Insert block before</Text>
</MenuItem>
<MenuItem>
<RowafterIcon />
<Text slot="label">Insert block after</Text>
</MenuItem>
<Separator />
<MenuItem>
<BinIcon />
<Text slot="label">Remove block</Text>
</MenuItem>
</Menu>
</MenuTrigger>
</BlockToolbar>
);
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
MoreoptionsIcon,
PageIcon,
} from '../../components/icons';
import { Menu, MenuItem } from '../Menu/Menu';
import { Button } from '../Button/Button';
import { Menu, MenuItem, MenuTrigger } from '../Menu/Menu';

import type { Meta, StoryObj } from '@storybook/react-vite';

Expand Down Expand Up @@ -159,13 +160,18 @@ export const LotsOfItems: Story = {
{first?.title}
</Breadcrumb>
<Breadcrumb>
<Menu items={inner} button={<MoreoptionsIcon />} placement="bottom">
{(item) => (
<MenuItem id={item['@id']} href={item['@id']}>
{item.title}
</MenuItem>
)}
</Menu>
<MenuTrigger placement="bottom">
<Button>
<MoreoptionsIcon />
</Button>
<Menu items={inner}>
{(item) => (
<MenuItem id={item['@id']} href={item['@id']}>
{item.title}
</MenuItem>
)}
</Menu>
</MenuTrigger>
</Breadcrumb>
<Breadcrumb id={last?.['@id']} href={last?.['@id']}>
{last?.title}
Expand Down Expand Up @@ -204,13 +210,18 @@ export const LotsOfItemsWithSeparator: Story = {
{first?.title}
</Breadcrumb>
<Breadcrumb separator={<ChevronrightIcon size="sm" />}>
<Menu items={inner} button={<MoreoptionsIcon />} placement="bottom">
{(item) => (
<MenuItem id={item['@id']} href={item['@id']}>
{item.title}
</MenuItem>
)}
</Menu>
<MenuTrigger placement="bottom">
<Button>
<MoreoptionsIcon />
</Button>
<Menu items={inner}>
{(item) => (
<MenuItem id={item['@id']} href={item['@id']}>
{item.title}
</MenuItem>
)}
</Menu>
</MenuTrigger>
</Breadcrumb>
<Breadcrumb
id={last?.['@id']}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { Breadcrumb, Breadcrumbs } from './Breadcrumbs.quanta';
import { Menu } from '../Menu/Menu.quanta';
import { Button } from '../Button/Button.quanta';
import { Menu, MenuItem, MenuTrigger } from '../Menu/Menu.quanta';
import {
FolderIcon,
HomeIcon,
Expand Down Expand Up @@ -156,11 +157,18 @@ export const LotsOfItems: Story = {
{first?.title}
</Breadcrumb>
<Breadcrumb>
<Menu
menuItems={inner}
button={<MoreoptionsIcon />}
placement="bottom"
></Menu>
<MenuTrigger placement="bottom">
<Button variant="icon" aria-label="More breadcrumb items">
<MoreoptionsIcon className="h-4 w-4" />
</Button>
<Menu>
{inner.map((item) => (
<MenuItem key={item['@id']} id={item['@id']} href={item['@id']}>
{item.label}
</MenuItem>
))}
</Menu>
</MenuTrigger>
</Breadcrumb>
<Breadcrumb id={last?.['@id']} href={last?.['@id']}>
{last?.title}
Expand Down
Loading
Loading