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
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"]
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 @@ -121,19 +122,19 @@ export const NoRoot: Story = {
};

const longItems = [
{ '@id': '/folder', label: 'Folder' },
{ '@id': '/folder/folderB', label: 'Folder with long name' },
{ '@id': '/folder', title: 'Folder' },
{ '@id': '/folder/folderB', title: 'Folder with long name' },
{
'@id': '/folder/folderB/folderC',
label: 'Folder with long name and a bit more',
title: 'Folder with long name and a bit more',
},
{
'@id': '/folder/folderB/folderC/folderD',
label: 'Folder with long name even more long',
title: 'Folder with long name even more long',
},
{
'@id': '/folder/folderB/folderC/folderD/folderE',
label: 'Folder',
title: 'Folder',
},
{ '@id': '/folder/page', title: 'Page' },
];
Expand All @@ -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.title}
</MenuItem>
))}
</Menu>
</MenuTrigger>
</Breadcrumb>
<Breadcrumb id={last?.['@id']} href={last?.['@id']}>
{last?.title}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import { ColorSlider } from '../ColorSlider/ColorSlider';
import { ColorArea } from '../ColorArea/ColorArea';
import { ColorField } from '../ColorField/ColorField';

export interface ColorPickerProps extends RACColorPickerProps {
export interface ColorPickerProps
extends Omit<RACColorPickerProps, 'children'> {
label?: string;
children: React.ReactNode;
children?: React.ReactNode;
}

export function ColorPicker({ label, children, ...props }: ColorPickerProps) {
Expand Down
Loading
Loading