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
3 changes: 1 addition & 2 deletions packages/@react-spectrum/s2/src/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -624,8 +624,7 @@ const ComboboxInner = forwardRef(function ComboboxInner(props: ComboBoxProps<any
paddingStart: 'edge-to-text',
// better way to do this one? it's not actually half, they are
// [9, 4], [12, 6], [15, 8], [18, 8]
// also noticed that our measurement is including the border, making the padding too much
paddingEnd: 'calc(self(height, self(minHeight)) * 3 / 16)'
paddingEnd: 'calc(self(height, self(minHeight)) * 3 / 16 - self(borderEndWidth, 2px))'
})({size})}>
<InputContext.Consumer>
{ctx => (
Expand Down
6 changes: 3 additions & 3 deletions packages/@react-spectrum/s2/src/TableView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export class S2TableLayout<T> extends TableLayout<T> {
// we want the body to be sticky and only as wide as the table so it is always in view if loading/empty
let isEmptyOrLoading = this.virtualizer?.collection.size === 0;
if (isEmptyOrLoading) {
layoutInfo.rect.width = this.virtualizer!.visibleRect.width - 80;
layoutInfo.rect.width = this.virtualizer!.size.width - 80;
}

return [
Expand All @@ -228,7 +228,7 @@ export class S2TableLayout<T> extends TableLayout<T> {
let layoutNode = super.buildLoader(node, x, y);
let {layoutInfo} = layoutNode;
layoutInfo.allowOverflow = true;
layoutInfo.rect.width = this.virtualizer!.visibleRect.width;
layoutInfo.rect.width = this.virtualizer!.size.width;
// If performing first load or empty, the body will be sticky so we don't want to apply sticky to the loader, otherwise it will
// affect the positioning of the empty state renderer
let collection = this.virtualizer!.collection;
Expand All @@ -246,7 +246,7 @@ export class S2TableLayout<T> extends TableLayout<T> {
// If loading or empty, we'll want the body to be sticky and centered
let isEmptyOrLoading = this.virtualizer?.collection.size === 0;
if (isEmptyOrLoading) {
layoutInfo.rect = new Rect(40, 40, this.virtualizer!.visibleRect.width - 80, this.virtualizer!.visibleRect.height - 80);
layoutInfo.rect = new Rect(40, 40, this.virtualizer!.size.width - 80, this.virtualizer!.size.height - 80);
layoutInfo.isSticky = true;
}

Expand Down
7 changes: 3 additions & 4 deletions packages/@react-spectrum/s2/stories/TableView.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,8 @@ import User from '../s2wf-icons/S2_Icon_User_20_N.svg';
import {useTreeData} from 'react-stately/useTreeData';

let onActionFunc = action('onAction');
let noOnAction = null;
let noOnAction = undefined;
const onActionOptions = {onActionFunc, noOnAction};

const events = ['onResizeStart', 'onResize', 'onResizeEnd', 'onSelectionChange', 'onSortChange'];

const meta: Meta<typeof TableView> = {
Expand All @@ -63,7 +62,7 @@ const meta: Meta<typeof TableView> = {
tags: ['autodocs'],
args: {...getActionArgs(events)},
argTypes: {
...categorizeArgTypes('Events', ['onAction', 'onLoadMore', 'onResizeStart', 'onResize', 'onResizeEnd', 'onSelectionChange', 'onSortChange']),
...categorizeArgTypes('Events', ['onAction', 'onLoadMore', ...events]),
children: {table: {disable: true}},
onAction: {
options: Object.keys(onActionOptions), // An array of serializable values
Expand Down Expand Up @@ -1784,7 +1783,7 @@ export const TableWithNestedRows: StoryObj<typeof TableView> = {
<Cell>5/22/1980</Cell>
</Row>
</Row>
<Row id="apps">
<Row id="apps" isDisabled>
<Cell>Applications</Cell>
<Cell>Folder</Cell>
<Cell>4/7/2025</Cell>
Expand Down
8 changes: 4 additions & 4 deletions packages/@react-spectrum/s2/stories/TreeView.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ import {useAsyncList} from 'react-stately/useAsyncList';
import {useListData} from 'react-stately/useListData';

let onActionFunc = action('onAction');
let noOnAction = null;
let noOnAction = undefined;
const onActionOptions = {onActionFunc, noOnAction};
const events = ['onSelectionChange', 'onAction'];
const events = ['onSelectionChange'];

const meta: Meta<typeof TreeView> = {
component: TreeView,
Expand All @@ -57,7 +57,7 @@ const meta: Meta<typeof TreeView> = {
tags: ['autodocs'],
args: {...getActionArgs(events)},
argTypes: {
...categorizeArgTypes('Events', events),
...categorizeArgTypes('Events', ['onAction', ...events]),
children: {table: {disable: true}},
onAction: {
options: Object.keys(onActionOptions), // An array of serializable values
Expand All @@ -81,7 +81,7 @@ const TreeExampleStatic = (args: TreeViewProps<any>): ReactElement => (
<div style={{width: '300px', resize: 'both', height: '320px', overflow: 'auto'}}>
<TreeView
{...args}
disabledKeys={['projects-1']}
disabledKeys={['projects']}
aria-label="test static tree"
onExpandedChange={action('onExpandedChange')}
onSelectionChange={action('onSelectionChange')}>
Expand Down
3 changes: 3 additions & 0 deletions packages/dev/codemods/src/s1-to-s2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ Run `npx @react-spectrum/codemods s1-to-s2` from the directory you want to upgra
### Options

- `-c, --components <components>`: Comma separated list of components to upgrade (ex: `Button,TableView`). If not specified, all available components will be upgraded.
- `--path <path>`: The path to the directory to run the codemod in. Defaults to the current directory (`.`).
- `-d, --dry`: Run the codemod without writing any changes to disk. Use this to preview migrations before applying.
- `--agent`: Run in non-interactive mode. Skips interactive prompts, package installation, and macro setup. Required when running in CI or from an agent tool. Note: `@react-spectrum/s2` must still be installed and resolvable.

## How it works

Expand Down
2 changes: 1 addition & 1 deletion packages/dev/codemods/src/s1-to-s2/UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ Example:

### Border width

Affected style props: `borderWidth`, `borderStartWidth`, `borderEndWidth`, `borderTopWidth`, `orderBottomWidth`, `borderXWidth`, `borderYWidth`.
Affected style props: `borderWidth`, `borderStartWidth`, `borderEndWidth`, `borderTopWidth`, `borderBottomWidth`, `borderXWidth`, `borderYWidth`.

Example:

Expand Down
143 changes: 143 additions & 0 deletions packages/dev/s2-docs/migration-references/focused-manual-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Manual fixes after the codemod

## Icons and illustrations

- If the codemod leaves `TODO(S2-upgrade)` next to an icon or illustration import, pick the nearest S2 replacement manually.

## Layout components

`Flex`, `Grid`, `View`, and `Well` are not part of S2. These should be updated to `div` elements styled with the macro.

### Flex example

Before:

```jsx
<Flex direction="column">
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</Flex>
```

After:

```jsx
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};

<div className={style({display: 'flex', flexDirection: 'column'})}>
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</div>
```

### Grid example

Before:

```jsx
<Grid justifyContent="center">
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</Grid>
```

After:

```jsx
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};

<div className={style({display: 'grid', justifyContent: 'center'})}>
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</div>
```

### View example

Before:

```jsx
<View>
Content
</View>
```

After:

```jsx
<div>
Content
</div>
```

### Well example

Before:

```jsx
<Well>
Content
</Well>
```

After:

```jsx
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};

<div className={style({
display: 'block',
textAlign: 'start',
padding: 16,
minWidth: 160,
marginTop: 4,
borderWidth: 1,
borderRadius: 'sm',
borderStyle: 'solid',
borderColor: 'transparent-black-75',
font: 'body-sm'
})}>
Content
</div>
```

## UNSAFE_style and UNSAFE_className

Move `UNSAFE_style` usage to the S2 style macro when possible.

Move `UNSAFE_className` usage to the S2 style macro when possible.

Reference the S2 styling docs to see the supported CSS properties.

## Dialogs

- `DialogContainer` and `useDialogContainer` still exist in S2, but the dismiss logic may need to move between `Dialog`, `DialogTrigger`, and `DialogContainer`. See the S2 Dialog documentation for more details.

## Collections

- When `Item` survives the codemod, rename it based on its parent component:

| Parent component | v3 child | S2 child |
|---|---|---|
| Menu / ActionMenu | Item | MenuItem |
| Picker | Item | PickerItem |
| ComboBox | Item | ComboBoxItem |
| Tabs | Item | Tab / TabPanel |
| TagGroup | Item | Tag |
| Breadcrumbs | Item | Breadcrumb |

- Preserve React `key` when mapping arrays, but ensure collection data items expose `id` when S2 expects it. See the S2 Collections documentation for more details.
- Table and ListView migrations often need manual review for row headers, nested columns, and explicit item ids.

## Toast migration

- Move `ToastContainer` and `ToastQueue` imports from `@react-spectrum/toast` to `@react-spectrum/s2`.
- Keep a shared `ToastContainer` mounted near the app root or test harness, then update all queue calls to use the S2 import path.
- S2 supports `ToastQueue.neutral`, `positive`, `negative`, and `info`.
- Re-check options such as `timeout`, `actionLabel`, `onAction`, `shouldCloseOnAction`, and `onClose` after the import move.
- The queue methods still return a close function. Keep programmatic dismissal logic when the existing UX depends on it.
- Search for every `ToastContainer` mount and every `ToastQueue` usage after moving imports. Shared app roots, secondary entrypoints, and test harnesses are easy to miss.
23 changes: 23 additions & 0 deletions packages/dev/s2-docs/migration-references/focused-prerequisites.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Inspection checklist

## Minimum tool versions

These tools are not all strictly required, but if the project uses them they must be at these minimum versions to avoid issues with the `with {type: 'macro'}` import syntax:

- **TypeScript 5.3+** — required for the import attributes syntax (`with {type: 'macro'}`).
- **Babel 7.27.0+** or the `@babel/plugin-syntax-import-attributes` plugin — enables Babel to parse import attributes. Alternatively, `@babel/preset-env` with `shippedProposals: true` also enables import attribute parsing.
- **ESLint 9.14.0+** with `@typescript-eslint/parser`.
- **Prettier 3.1.1+** — needed to format `with {type: 'macro'}` import syntax correctly.

## What to look for

- Search package manifests and source for `@adobe/react-spectrum`, `@react-spectrum/*`, and `@spectrum-icons/*`.
- In monorepos or mixed-tooling repos, inspect the target package or app first instead of assuming the root manifest represents the runtime target being migrated.
- Determine the package manager from the relevant lockfile or workspace setup.
- Detect the bundler at the migration target level. The workspace root may include Storybook, Vite, or other tooling that does not represent the runtime bundler for the package being migrated.
- **Parcel v2.12.0+** already supports S2 style macros natively.
- **Vite, webpack, Next.js, Rollup, ESBuild** and similar toolchains need `unplugin-parcel-macros`. Keep plugin ordering correct so macros run before the rest of the toolchain.
- If the repo already has a framework-specific S2 or macro setup, preserve it instead of layering a second macro configuration on top.
- Find **all** app entrypoints, including standalone pages, alternate render roots, embedded sub-apps, utility apps, and test-only render targets. Do not assume there is only one entry.
- Locate root providers, shared test wrappers, toast setup, and any direct `defaultTheme` usage.
- Search for `ToastContainer`, `ToastQueue`, `DialogContainer`, `useDialogContainer`, `ClearSlots`, style props, and `UNSAFE_style`. These are common follow-up areas after the codemod.
2 changes: 2 additions & 0 deletions packages/dev/s2-docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
"json5": "^2.2.3",
"lz-string": "^1.5.0",
"markdown-to-jsx": "^6.11.0",
"mdast-util-mdx": "^1.0.0",
"mdast-util-to-markdown": "^1.0.0",
"react": "^19.2.0",
"react-aria": "^3.40.0",
"react-aria-components": "^1.7.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/dev/s2-docs/pages/s2/CheckboxGroup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {CheckboxGroup, Checkbox} from '@react-spectrum/s2/CheckboxGroup';
<CheckboxGroup/* PROPS */>
<Checkbox value="soccer">Soccer</Checkbox>
<Checkbox value="baseball">Baseball</Checkbox>
<Checkbox value="basketball">Basketball</Checkbox>
<Checkbox value="synchronized swimming">Synchronized Swimming</Checkbox>
</CheckboxGroup>
```

Expand Down
2 changes: 1 addition & 1 deletion packages/dev/s2-docs/pages/s2/RadioGroup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {RadioGroup, Radio} from '@react-spectrum/s2/RadioGroup';
<RadioGroup/* PROPS */>
<Radio value="cats">Cat</Radio>
<Radio value="dogs">Dog</Radio>
<Radio value="dragon">Dragon</Radio>
<Radio value="leopard gecko">Leopard Gecko</Radio>
</RadioGroup>
```

Expand Down
14 changes: 14 additions & 0 deletions packages/dev/s2-docs/pages/s2/migrating.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {Layout} from '../../src/Layout';
import {PendingBadge} from '../../src/PendingBadge';
import {InstallCommand} from '../../src/InstallCommand';
import {StaticTable} from '../../src/StaticTable';
import {Command} from '../../src/Command';
export default Layout;

export const section = 'Guides';
Expand All @@ -12,6 +13,18 @@ export const description = 'How to migrate from React Spectrum v3 to Spectrum 2.

<PageDescription>Learn how to migrate from React Spectrum v3 to Spectrum 2.</PageDescription>

## AI-assisted migration (recommended)

If you're using an AI coding tool that supports [Agent Skills](https://agentskills.io/home), React Spectrum now includes a dedicated skill for guiding v3 to S2 upgrades.

To install the migration skill and the general S2 skill, run:

<Command command="npx skills add https://react-spectrum.adobe.com --skill migrate-react-spectrum-v3-to-s2 --skill react-spectrum-s2" />

Then ask your agent to use the `migrate-react-spectrum-v3-to-s2` skill to migrate your project.

## Command-line tool

An automated upgrade assistant is available by running the following command in the project you want to upgrade:

<InstallCommand isCommand pkg="@react-spectrum/codemods" flags="s1-to-s2" />
Expand All @@ -25,6 +38,7 @@ The following arguments are also available:
- `--path` - Path to apply the upgrade changes to. Defaults to the current directory (`.`)
- `--dry` - Runs the upgrade assistant without making changes to components
- `--ignore-pattern` - Ignore files that match the provided glob expression. Defaults to `'**/node_modules/**'`
- `--agent` - Runs the upgrade assistant non-interactively for AI tools. This skips prompts, package installation, and macro setup, so `@react-spectrum/s2` must already be installed and resolvable

For cases that the upgrade assistant doesn't handle automatically or where you'd rather upgrade some components manually, use the guide below.

Expand Down
Loading
Loading