From 70a3cf3ae1899d330f1dc8830c1f8c790c73e67a Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 28 May 2025 16:04:59 -0500 Subject: [PATCH 1/4] init AI rules --- rules/react-spectrum-s2.mdc | 268 ++++++++++ rules/react-spectrum-v3-to-s2-migration.mdc | 530 ++++++++++++++++++++ rules/react-spectrum-v3.mdc | 194 +++++++ rules/style-macro.mdc | 405 +++++++++++++++ 4 files changed, 1397 insertions(+) create mode 100644 rules/react-spectrum-s2.mdc create mode 100644 rules/react-spectrum-v3-to-s2-migration.mdc create mode 100644 rules/react-spectrum-v3.mdc create mode 100644 rules/style-macro.mdc diff --git a/rules/react-spectrum-s2.mdc b/rules/react-spectrum-s2.mdc new file mode 100644 index 00000000000..93580794be9 --- /dev/null +++ b/rules/react-spectrum-s2.mdc @@ -0,0 +1,268 @@ +--- +description: Using React Spectrum (S2) components from `@react-spectrum/s2` +globs: +alwaysApply: false +--- +# React Spectrum (with Spectrum 2) + +When implementing UI components in React, use the React Spectrum (S2) library to ensure that your components are accessible and follow Adobe's Spectrum 2 design system. Components should be imported from the `@react-spectrum/s2` package. Using the older `@adobe/react-spectrum` package (also called React Spectrum v3) should be avoided unless explicitly requested. +If you are able to browse the web, the full docs for React Spectrum (S2) are available at [https://react-spectrum.adobe.com/s2/](mdc:https:/react-spectrum.adobe.com/s2). + +## Configuring your bundler + +React Spectrum supports styling via macros, a new bundler feature that enables functions to run at build time. Currently, Parcel v2.12.0 and newer supports macros out of the box. When using other build tools, you can install a plugin to enable them. +See the styling section to learn more about using the React Spectrum `style` macro. + +### Webpack, Next.js, Vite, Rollup, or ESBuild + +First, ensure `unplugin-parcel-macros` is installed (it can be a dev dependency). + +Then, ensure your bundler is configured according to the examples below. Note that plugin order is important: `unplugin-parcel-macros` must run before other plugins like Babel. + +#### webpack + +```js +// webpack.config.js +const macros = require('unplugin-parcel-macros'); +module.exports = { + // ... + plugins: [ + macros.webpack() + ] +}; +``` + +#### Next.js + +```js +// next.config.js +const macros = require('unplugin-parcel-macros'); +// Create a single instance of the plugin that's shared between server and client builds. +let plugin = macros.webpack(); +module.exports = { + webpack(config) { + config.plugins.push(plugin); + return config; + } +}; +``` + +#### Vite + +```js +// vite.config.js +import macros from 'unplugin-parcel-macros'; +export default { + plugins: [ + macros.vite() + ] +}; +``` + +#### Rollup + +```js +// rollup.config.js +import macros from 'unplugin-parcel-macros'; +export default { + plugins: [ + macros.rollup() + ] +}; +``` + +#### Esbuild + +```js +import {build} from 'esbuild'; +import macros from 'unplugin-parcel-macros'; +build({ + plugins: [ + macros.esbuild() + ] +}); +``` + +## App setup + +IMPORTANT: `Provider` is not required (unlike in React Spectrum v3). Instead, import `@react-spectrum/s2/page.css` in the entry component of your app to apply the background color and color scheme to the `` element. This ensures that the entire page has the proper styles even before your JavaScript runs. + +```jsx +import '@react-spectrum/s2/page.css'; +import {Button} from '@react-spectrum/s2'; +function App() { + return ( + + ); +} +``` + +**Note**: If you're embedding Spectrum 2 as a section of a larger page rather than taking over the whole window, follow the 'Embedded sections' section below instead of using `page.css`. + +### Overriding the color scheme + +By default, the page follows the user's operating system color scheme setting, supporting both light and dark mode. The page background is set to the `base` Spectrum background layer by default. This can be configured by setting the `data-color-scheme` and `data-background` attributes on the `` element. For example, to force the application to only render in light mode, set `data-color-scheme="light"`. +```html + + + +``` + +### Overriding the locale + +By default, React Spectrum uses the browser/operating system language setting for localized strings, date and number formatting, and to determine the layout direction (left-to-right or right-to-left). This can be overridden by rendering a `` component at the root of your app, and setting the `locale` prop. + +```jsx +import {Provider} from '@react-spectrum/s2'; + + {/* your app */} + +``` + +### Embedded sections + +If you're building an embedded section of a larger page using Spectrum 2, use the `` component to set the background instead of importing `page.css`. The `background` prop should be set to the Spectrum background layer appropriate for your app, and the `colorScheme` can be overridden as well. + +```jsx +import {Provider} from '@react-spectrum/s2'; + + {/* your app */} + +``` + +## Collection components + +For dynamic collections, instead of using `Array.map`, use the following JSX-based interface, which maps over your data and applies a function for each item to render it. + +```jsx + + {(item) => {item.name}} + +``` + +## Pressable components + +For pressable components such as Button, use `onPress` instead of `onClick`. Similarly, use `onPressStart`, `onPressEnd`, `onPressChange`, and `onPressUp` where appropriate. +Press event handlers are type `(e: PressEvent) => void` where `PressEvent` is defined as: + +```ts +interface PressEvent { + type: 'pressstart' | 'pressend' | 'pressup' | 'press'; + pointerType: 'mouse' | 'pen' | 'touch' | 'keyboard' | 'virtual'; + target: Element; + shiftKey: boolean; + ctrlKey: boolean; + metaKey: boolean; + altKey: boolean; + x: number; + y: number; +} +``` + +## Input components + +For input field components such as TextField, the `onChange` event handler is type `(value: string) => void`. + +## Styling + +React Spectrum v3 supported a limited set of style props for layout and positioning using Spectrum-defined values. In Spectrum 2, there is a more flexible style macro. This offers additional Spectrum tokens, improves performance by generating CSS at build time rather than runtime, and works with any DOM element for use in custom components. + +Macros are a new bundler feature that enable functions to run at build time. The React Spectrum `style` macro uses this to generate CSS that can be applied to any DOM element or component. Import the `style` macro using the with `{type: 'macro'}` import attribute, and pass the result to the `styles` prop of any React Spectrum component to provide it with styles. + +```jsx +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; +import {ActionButton} from '@react-spectrum/s2'; + + Edit + +``` + +The `styles` prop accepts a limited set of CSS properties, including layout, spacing, sizing, and positioning. Other styles such as colors and internal padding cannot be customized within Spectrum components. + +### Supported CSS properties on Spectrum components +- `margin` +- `marginStart` +- `marginEnd` +- `marginTop` +- `marginBottom` +- `marginX` +- `marginY` +- `width` +- `minWidth` +- `maxWidth` +- `flexGrow` +- `flexShrink` +- `flexBasis` +- `justifySelf` +- `alignSelf` +- `order` +- `gridArea` +- `gridRow` +- `gridRowStart` +- `gridRowEnd` +- `gridColumn` +- `gridColumnStart` +- `gridColumnEnd` +- `position` +- `zIndex` +- `top` +- `bottom` +- `inset` +- `insetX` +- `insetY` +- `insetStart` +- `insetEnd` + +### UNSAFE Style Overrides + +We highly discourage overriding the styles of React Spectrum components because it may break at any time when we change our implementation, making it difficult for you to update in the future. Consider using React Aria Components with our style macro to build a custom component with Spectrum styles instead. +That said, just like in React Spectrum v3, the `UNSAFE_className` and `UNSAFE_style` props are supported on Spectrum 2 components as last-resort escape hatches. + +```jsx +/* YourComponent.tsx */ +import {Button} from '@react-spectrum/s2'; +import './YourComponent.css'; +function YourComponent() { + return ; +} +``` + +```css +/* YourComponent.css */ +.your-unsafe-class { + background: red; +} +``` + +Note that the style macro being passed to UNSAFE_className will result in a TypeScript error. This is not allowed because UNSAFE_className is appended to the component's own styles, not merged. + +### CSS Resets + +CSS resets are strongly discouraged. Global CSS selectors can unintentionally affect elements that were not intended to have their styles be modified, leading to style clashes. Since Spectrum 2 uses [CSS Cascade Layers](mdc:https:/developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Cascade_layers), global CSS outside a `@layer` will override S2's CSS. Therefore, if you cannot remove your CSS reset, it must be placed in a lower layer. This can be done by declaring your reset layer before the `_` layer used by S2. + +```css +/* App.css */ +@layer reset, _; +@import "reset.css" layer(reset); +``` + +## Other tips + +### Virtualized collections + +When using virtualized components such as `TableView` or `CardView`, ensure the height is constrained. + +### Tooltips on elements + +Tooltips need to be accessible to keyboard and screen reader users, so we want to ensure that they are only placed on focusable and hoverable elements. For example, plain text on a page isn't focusable, so keyboard and screen reader users would be unable to access the information in that tooltip. + +If you need to display some additional context, consider using the `ContextualHelp` component. + +### Typography + +Note that the Text and Heading components are not general typography components, but instead are meant to be used WITHIN other component so default styles can be inherited (i.e. a Heading inside a Dialog or MenuItem). These components should not be used standalone. Spectrum 2 does not include specific components for typography. Instead, you can use the style macro to apply Spectrum typography to any HTML element or component. + diff --git a/rules/react-spectrum-v3-to-s2-migration.mdc b/rules/react-spectrum-v3-to-s2-migration.mdc new file mode 100644 index 00000000000..f4d346151b8 --- /dev/null +++ b/rules/react-spectrum-v3-to-s2-migration.mdc @@ -0,0 +1,530 @@ +--- +description: Upgrading React Spectrum (v3) components to Spectrum 2 (S2) +globs: +alwaysApply: false +--- +# Migrating to Spectrum 2 + +The user may ask to upgrade React Spectrum components from Spectrum 1 to Spectrum 2. + +Spectrum 1: Uses the `@adobe/react-spectrum` monopackage or `@react-spectrum/*` individual packages. Also called "v3". +Spectrum 2: Uses the `@react-spectrum/s2` package. Also called "S2". + +If they haven't already specified, you should ask the user if they want you to upgrade these components manually using the migration guide found below, or if they want to use the automated mods. + +An automated upgrade assistant is available by running the following command in the project you want to upgrade: + +```bash +npx @react-spectrum/codemods s1-to-s2 +``` + +To only upgrade specific components, provide a `--components` argument with a comma-separated list of components to upgrade: + +```bash +npx @react-spectrum/codemods s1-to-s2 --components=Button,TextField +``` + +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/**'` + +If the codemod can't handle some cases, a comment labelled `TODO(S2-upgrade)` will be added, and you can try to fix these manually. + +For cases that the upgrade assistant doesn't handle automatically or the user would rather upgrade components manually, use the guide below. + +Note that [PENDING] indicates that future changes will occur before the final release, and the current solution should be considered temporary. + +## Components + +### All components +- Update imports to use the `@react-spectrum/s2` package instead of `@adobe/react-spectrum` or individual packages like `@react-spectrum/button` +- Update [style props](mdc:https:/react-spectrum.adobe.com/react-spectrum/styling.html#style-props) to use the [style macro](mdc:?path=/docs/style-macro--docs) instead. See the 'Style props' section below + +### Accordion +- Update `Item` to be `Disclosure`. `Disclosure` should now consist of two children: `DisclosureTitle` and `DisclosurePanel`. Note that you can now add interactive elements inside the header and adjacent to the title by using the `DisclosureHeader` component with the `DisclosureTitle` and interactive elements inside. +- Update `Item`'s title prop to be a child of `DisclosureTitle` +- Update children of `Item` to be children of `DisclosurePanel` +- Update `key` to be `id` (and keep `key` if rendered inside `array.map`) +- Remove `disabledKeys` and add `isDisabled` to individual `Disclosure` components +- Add `allowsMultipleExpanded` to allow multiple `Disclosure` components to be expanded at once (previously default behavior) + +### ActionBar +- Remove `ActionBarContainer` and move `ActionBar` to `renderActionBar` prop of `TableView` or `CardView` +- Update `Item` to `ActionButton` +- Update root level `onAction` to be called via `onPress` on each `ActionButton` +- Apply `isDisabled` directly on each `ActionButton` or `ToggleButton` instead of root level `disabledKeys` +- Update `key` to be `id` (and keep `key` if rendered inside `array.map`) +- Convert dynamic collections render function to `items.map` +- [PENDING] Comment out `buttonLabelBehavior` (it has not been implemented yet) + +### ActionButton +No updates needed. + +### ActionMenu +- [PENDING] Comment out `closeOnSelect` (it has not been implemented yet) +- [PENDING] Comment out `trigger` (it has not been implemented yet) +- Update `Item` to be a `MenuItem` + +### ActionGroup +- Use `ActionButtonGroup` if you are migrating from an `ActionGroup` that didn't allow for selection. `ActionButtonGroup` takes `ActionButtons` as children. +- Use `ToggleButtonGroup` if you are migrating from an `ActionGroup` that used single or multiple selection. `ToggleButtonGroup` takes `ToggleButtons` as children. +- [PENDING] Comment out `overflowMode` (it has not been implemented yet) +- [PENDING] Comment out `buttonLabelBehavior` (it has not been implemented yet) +- [PENDING] Comment out `summaryIcon` (it has not been implemented yet) +- Update root level `onAction` to be called via `onPress` on each `ActionButton` +- Apply `isDisabled` directly on each `ActionButton` or `ToggleButton` instead of root level `disabledKeys` +- Update `key` to be `id` (and keep `key` if rendered inside `array.map`) +- Convert dynamic collections render function to `items.map` + +### AlertDialog +No updates needed. + +### Avatar +- [PENDING] Comment out `isDisabled` (it has not been implemented yet) +- Update `size` to be a pixel value if it currently matches `'avatar-size-*'` + +### Badge +- Change `variant="info"` to `variant="informative"` + +### Breadcrumbs +- [PENDING] Comment out `showRoot` (it has not been implemented yet) +- [PENDING] Comment out `isMultiline` (it has not been implemented yet) +- [PENDING] Comment out `autoFocusCurrent` (it has not been implemented yet) +- Remove `size="S"` (Small is no longer a supported size in Spectrum 2) +- Update `Item` to be a `Breadcrumb` + +### Button +- Change `variant="cta"` to `variant="accent"` +- Change `variant="overBackground"` to `variant="primary" staticColor="white"` +- Change `style` to `fillStyle` +- [PENDING] Comment out `isPending` (it has not been implemented yet) +- Remove `isQuiet` (it is no longer supported in Spectrum 2) +- If `href` is present, the `Button` should be converted to a `LinkButton` +- Remove `elementType` (it is no longer supported in Spectrum 2) + +### ButtonGroup +No updates needed. + +### Checkbox +No updates needed. + +### CheckboxGroup +- Remove `showErrorIcon` (it has been removed due to accessibility issues) + +### ColorArea +- Remove `size` and instead provide a size via the style macro (i.e. `styles={style({size: 20})}`) + +### ColorField +- Remove `isQuiet` (it is no longer supported in Spectrum 2) +- Remove `placeholder` (it has been removed due to accessibility issues) +- Change `validationState="invalid"` to `isInvalid` +- Remove `validationState="valid"` (it is no longer supported in Spectrum 2) + +### ColorSlider +- Remove `showValueLabel` (it has been removed due to accessibility issues) + +### ColorSwatch +No updates needed. + +### ColorWheel +- Remove `size` and instead provide a size via the style macro (i.e. `styles={style({size: 20})}`) + +### ComboBox +- Change `menuWidth` value from a `DimensionValue` to a pixel value +- Remove `isQuiet` (it is no longer supported in Spectrum 2) +- [PENDING] Comment out `loadingState` (it has not been implemented yet) +- Remove `placeholder` (it is no longer supported in Spectrum 2) +- Change `validationState="invalid"` to `isInvalid` +- Remove `validationState="valid"` (it is no longer supported in Spectrum 2) +- [PENDING] Comment out `onLoadMore` (it has not been implemented yet) +- Update `Item` to be a `ComboBoxItem` + +### Dialog +- Update children to move render props from being the second child of `DialogTrigger` to being a child of `Dialog` +- Remove `onDismiss` and use `onOpenChange` on the `DialogTrigger`, or `onDismiss` on the `DialogContainer` instead +- `Dialog` is now meant specifically for rendering modal dialogs only and follows the same preset layout as before +- If you are trying to create a dialog with a custom layout use `CustomDialog` +- If you are trying to create a fullscreen dialog use `FullscreenDialog` +- If you are trying to create a popover dialog use `Popover` +- Supports `isKeyboardDismissDisabled` in place of `DialogTrigger` +- Supports `isDismissible` in place of `DialogTrigger`. Note the fixed spelling from previous `isDismissible` prop. +- Supports `role: "dialog" | "alertdialog"` + +### DialogContainer +- Remove `type`, this is dependent on the dialog level child that you use (e.g. `Dialog`, `FullscreenDialog`, `Popover`) +- Remove `isDismissable`, prop now exists on the dialog level component as `isDismissible` +- Remove `isKeyboardDismissDisabled` on the dialog level component + +### DialogTrigger +- [PENDING] Comment out `type="tray"` (`Tray` has not been implemented yet) +- [PENDING] Comment out `mobileType` (`Tray` and other types have not been implemented yet for `Popover`) +- Remove `targetRef` (it is no longer supported in Spectrum 2) +- Update `children` to remove render props usage, and note that the `close` function was moved from `DialogTrigger` to `Dialog` +- Remove `containerPadding`, prop now exists on the `Popover` component +- Remove `crossOffset`, prop now exists on the `Popover` component +- Remove `hideArrow`, prop now exists on the `Popover` component +- Remove `isDismissable`, prop now exists on the dialog level component as `isDismissible` +- Remove `isKeyboardDismissDisabled`, prop now exists on the dialog level component +- Remove `offset`, prop now exists on the `Popover` component +- Remove `placement`, prop now exists on the `Popover` component +- Remove `shouldFlip`, prop now exists on the `Popover` component +- Remove `type`, this is dependent on the dialog level child that you use (e.g. `Dialog`, `FullscreenDialog`, `Popover`) + +### Divider +- Remove Divider component if within a Dialog (Updated design for Dialog in Spectrum 2) + +### Flex +- Update `Flex` to be a `div` and apply flex styles using the style macro (i.e. `
`) + +### Form +- Remove `isQuiet` (it is no longer supported in Spectrum 2) +- Remove `isReadOnly` (it is no longer supported in Spectrum 2) +- Remove `validationState` (it is no longer supported in Spectrum 2) +- Remove `validationBehavior` (it is no longer supported in Spectrum 2) + +### Grid +- Update `Grid` to be a `div` and apply grid styles using the style macro (i.e. `
`) + +### IllustratedMessage +- Update illustrations to be from `@react-spectrum/s2/illustrations`. See [Illustrations](mdc:?path=/docs/illustrations--docs) + +### InlineAlert +- Change `variant="info"` to `variant="informative"` + +### Item +- If within `Menu`: Update `Item` to be a `MenuItem` +- If within `ActionMenu`: Update `Item` to be a `MenuItem` +- If within `TagGroup`: Update `Item` to be a `Tag` +- If within `Breadcrumbs`: Update `Item` to be a `Breadcrumb` +- If within `Picker`: Update `Item` to be a `PickerItem` +- If within `ComboBox`: Update `Item` to be a `ComboBoxItem` +- If within `ListBox`: Update `Item` to be a `ListBoxItem` +- If within `TabList`: Update `Item` to be a `Tab` +- If within `TabPanels`: Update `Item` to be a `TabPanel` and remove surrounding `TabPanels` +- Update `key` to be `id` (and keep `key` if rendered inside `array.map`) + +### Link +- Change `variant="overBackground"` to `staticColor="white"` +- If `a` was used inside `Link` (legacy API), remove the `a` and apply props (i.e `href`) directly to `Link` + +### ListBox +- Update `Item` to be a `ListBoxItem` + +### Menu +- Update `Item` to be a `MenuItem` + +### MenuTrigger +- [PENDING] Comment out `closeOnSelect` (it has not been implemented yet) + +### NumberField +- Remove `isQuiet` (it is no longer supported in Spectrum 2) +- Change `validationState="invalid"` to `isInvalid` +- Remove `validationState="valid"` (it is no longer supported in Spectrum 2) + +### Picker +- Change `menuWidth` value from a `DimensionValue` to a pixel value +- Remove `isQuiet` (it is no longer supported in Spectrum 2) +- Change `validationState="invalid"` to `isInvalid` +- Remove `validationState="valid"` (it is no longer supported in Spectrum 2) +- [PENDING] Comment out `isLoading` (it has not been implemented yet) +- [PENDING] Comment out `onLoadMore` (it has not been implemented yet) +- Update `Item` to be a `PickerItem` + +### ProgressBar +- Change `variant="overBackground"` to `staticColor="white"` +- [PENDING] Comment out `labelPosition` (it has not been implemented yet) +- [PENDING] Comment out `showValueLabel` (it has not been implemented yet) + +### ProgressCircle +- Change `variant="overBackground"` to `staticColor="white"` + +### Radio +No updates needed. + +### RadioGroup +- Change `validationState="invalid"` to `isInvalid` +- Remove `validationState="valid"` (it is no longer supported in Spectrum 2) +- Remove `showErrorIcon` (it has been removed due to accessibility issues) + +### RangeSlider +- Remove `showValueLabel` (it has been removed due to accessibility issues) +- [PENDING] Comment out `getValueLabel` (it has not been implemented yet) +- [PENDING] Comment out `orientation` (it has not been implemented yet) + +### SearchField +- Remove `placeholder` (it has been removed due to accessibility issues) +- [PENDING] Comment out icon (it has not been implemented yet) +- Remove `isQuiet` (it is no longer supported in Spectrum 2) +- Change `validationState="invalid"` to `isInvalid` +- Remove `validationState="valid"` (it is no longer supported in Spectrum 2) + +### Section +- If within `Menu`: Update `Section` to be a `MenuSection` +- If within `Picker`: Update `Section` to be a `PickerSection` + +### Slider +- Remove `isFilled` (Slider is always filled in Spectrum 2) +- Remove `trackGradient` (Not supported in S2 design) +- Remove `showValueLabel` (it has been removed due to accessibility issues) +- [PENDING] Comment out `getValueLabel` (it has not been implemented yet) +- [PENDING] Comment out `orientation` (it has not been implemented yet) + +### StatusLight +- Remove `isDisabled` (it is no longer supported in Spectrum 2) +- Change `variant="info"` to `variant="informative"` + +### SubmenuTrigger +No updates needed. + +### Switch +No updates needed. + +### TableView +- For `Column` and `Row`: Update `key` to be `id` (and keep `key` if rendered inside `array.map`) +- For dynamic tables, pass a `columns` prop into `Row` +- For `Row`: Update dynamic render function to pass in `column` instead of `columnKey` +- [PENDING] Comment out `UNSTABLE_allowsExpandableRows` (it has not been implemented yet) +- [PENDING] Comment out `UNSTABLE_onExpandedChange` (it has not been implemented yet) +- [PENDING] Comment out `UNSTABLE_expandedKeys` (it has not been implemented yet) +- [PENDING] Comment out `UNSTABLE_defaultExpandedKeys` (it has not been implemented yet) + +### Tabs +- Inside `TabList`: Update `Item` to be `Tab` +- Update `items` on `Tabs` to be on `TabList` +- Inside `TabPanels`: Update `Item` to be a `TabPanel` and remove the surrounding `TabPanels` +- Remove `isEmphasized` (it is no longer supported in Spectrum 2) +- Remove `isQuiet` (it is no longer supported in Spectrum 2) + +### TagGroup +- Rename `actionLabel` to `groupActionLabel` +- Rename `onAction` to `onGroupAction` +- Change `validationState="invalid"` to `isInvalid` +- Update `Item` to be `Tag` + +### TextArea +- [PENDING] Comment out `icon` prop (it has not been implemented yet) +- Remove `isQuiet` (it is no longer supported in Spectrum 2) +- Remove `placeholder` (it has been removed due to accessibility issues) +- Change `validationState="invalid"` to `isInvalid` +- Remove `validationState="valid"` (it is no longer supported in Spectrum 2) + +### TextField +- [PENDING] Comment out `icon` prop (it has not been implemented yet) +- Remove `isQuiet` (it is no longer supported in Spectrum 2) +- Remove `placeholder` (it has been removed due to accessibility issues) +- Change `validationState="invalid"` to `isInvalid` +- Remove `validationState="valid"` (it is no longer supported in Spectrum 2) + +### ToggleButton +No updates needed. + +### Tooltip +- Remove `variant` (it is no longer supported in Spectrum 2) +- Remove `placement` and add to the parent `TooltipTrigger` instead +- Remove `showIcon` (it is no longer supported in Spectrum 2) +- Remove `isOpen` and add to the parent `TooltipTrigger` instead + +### TooltipTrigger +- Update placement prop to have one value (i.e. Update `placement="bottom left"` to be `placement="bottom"`) + +### TreeView +If migrating from TreeView version 3.0.0-beta.3 or before, please do the following. Otherwise, no updates needed. +- Update content within `TreeViewItem` to be wrapped in `TreeViewContentItem` + +### View +- Update `View` to be a `div` and apply styles using the style macro + +### Well +- Update `Well` to be a `div` and apply styles using the style macro: +```jsx +
+``` + +## Style props + +React Spectrum v3 supported a limited set of [style props](mdc:https:/react-spectrum.adobe.com/react-spectrum/styling.html#style-props) for layout and positioning using Spectrum-defined values. Usage of these should be updated to instead use the [style macro](mdc:?path=/docs/style-macro--docs). + +Example: + +```diff +- import {ActionButton} from '@adobe/react-spectrum'; ++ import {ActionButton} from '@react-spectrum/s2'; ++ import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +- ++ + Edit + +``` + +### Border width + +Affected style props: `borderWidth`, `borderStartWidth`, `borderEndWidth`, `borderTopWidth`, `borderBottomWidth`, `borderXWidth`, `borderYWidth`. + +Example: + +```diff +- ++
+``` + +Border widths should be updated to use pixel values. Use the following mappings: + +| Spectrum 1 | Spectrum 2 | +|------------|------------| +| `'none'` | `0` | +| `'thin'` | `1` | +| `'thick'` | `2` | +| `'thicker'` | `4` | +| `'thickest'` | `'[8px]'` | + +### Border radius + +Affected style props: `borderRadius`, `borderTopStartRadius`, `borderTopEndRadius`, `borderBottomStartRadius`, `borderBottomEndRadius`. + +Example: + +```diff +- ++
+``` + +Border radius values should be updated to use pixel values. Use the following mappings: + +| Spectrum 1 | Spectrum 2 | +|------------|------------| +| `'xsmall'` | `'[1px]'` | +| `'small'` | `'sm'` | +| `'regular'` | `'default'` | +| `'medium'` | `'lg'` | +| `'large'` | `'xl'` | + +### Dimension values + +Affected style props: `width`, `minWidth`, `maxWidth`, `height`, `minHeight`, `maxHeight`, `margin`, `marginStart`, `marginEnd`, `marginTop`, `marginBottom`, `marginX`, `marginY`, `top`, `bottom`, `left`, `right`, `start`, `end`, `flexBasis`, `gap`, `columnGap`, `rowGap`, `padding`, `paddingX`, `paddingY`, `paddingStart`, `paddingEnd`, `paddingTop`, `paddingBottom`. + +Example: + +```diff +- ++ + Edit + +``` + +Dimension values should be converted to pixel values. Use the following mappings: + +| Spectrum 1 | Spectrum 2 | +|------------|------------| +| `'size-0'` | `0` | +| `'size-10'` | `1` | +| `'size-25'` | `2` | +| `'size-40'` | `3` | +| `'size-50'` | `4` | +| `'size-65'` | `5` | +| `'size-75'` | `6` | +| `'size-85'` | `7` | +| `'size-100'` | `8` | +| `'size-115'` | `9` | +| `'size-125'` | `10` | +| `'size-130'` | `11` | +| `'size-150'` | `12` | +| `'size-160'` | `13` | +| `'size-175'` | `14` | +| `'size-200'` | `16` | +| `'size-225'` | `18` | +| `'size-250'` | `20` | +| `'size-275'` | `22` | +| `'size-300'` | `24` | +| `'size-325'` | `26` | +| `'size-350'` | `28` | +| `'size-400'` | `32` | +| `'size-450'` | `36` | +| `'size-500'` | `40` | +| `'size-550'` | `44` | +| `'size-600'` | `48` | +| `'size-675'` | `54` | +| `'size-700'` | `56` | +| `'size-800'` | `64` | +| `'size-900'` | `72` | +| `'size-1000'` | `80` | +| `'size-1200'` | `96` | +| `'size-1250'` | `100` | +| `'size-1600'` | `128` | +| `'size-1700'` | `136` | +| `'size-2000'` | `160` | +| `'size-2400'` | `192` | +| `'size-3000'` | `240` | +| `'size-3400'` | `272` | +| `'size-3600'` | `288` | +| `'size-4600'` | `368` | +| `'size-5000'` | `400` | +| `'size-6000'` | `480` | +| `'static-size-0'` | `0` | +| `'static-size-10'` | `1` | +| `'static-size-25'` | `2` | +| `'static-size-40'` | `3` | +| `'static-size-50'` | `4` | +| `'static-size-65'` | `5` | +| `'static-size-100'` | `8` | +| `'static-size-115'` | `9` | +| `'static-size-125'` | `10` | +| `'static-size-130'` | `11` | +| `'static-size-150'` | `12` | +| `'static-size-160'` | `13` | +| `'static-size-175'` | `14` | +| `'static-size-200'` | `16` | +| `'static-size-225'` | `18` | +| `'static-size-250'` | `20` | +| `'static-size-300'` | `24` | +| `'static-size-400'` | `32` | +| `'static-size-450'` | `36` | +| `'static-size-500'` | `40` | +| `'static-size-550'` | `44` | +| `'static-size-600'` | `48` | +| `'static-size-700'` | `56` | +| `'static-size-800'` | `64` | +| `'static-size-900'` | `72` | +| `'static-size-1000'` | `80` | +| `'static-size-1200'` | `96` | +| `'static-size-1700'` | `136` | +| `'static-size-2400'` | `192` | +| `'static-size-2600'` | `208` | +| `'static-size-3400'` | `272` | +| `'static-size-3600'` | `288` | +| `'static-size-4600'` | `368` | +| `'static-size-5000'` | `400` | +| `'static-size-6000'` | `480` | +| `'single-line-height'` | `32` | +| `'single-line-width'` | `192` | + +### Break points + +Break points previously used in style props can be used in the style macro with updated keys. Use the following mappings: + +| Spectrum 1 | Spectrum 2 | +|------------|------------| +| `base` | `default` | +| `S` | `sm` | +| `M` | `md` | +| `L` | `lg` | + +Example: + +```diff +- ++
+``` \ No newline at end of file diff --git a/rules/react-spectrum-v3.mdc b/rules/react-spectrum-v3.mdc new file mode 100644 index 00000000000..031ae9d9076 --- /dev/null +++ b/rules/react-spectrum-v3.mdc @@ -0,0 +1,194 @@ +--- +description: Using React Spectrum (v3) components from `@adobe/react-spectrum` +globs: +alwaysApply: false +--- +# React Spectrum + +When implementing UI components in React, use the React Spectrum library to ensure that your components are accessible and follow Adobe's Spectrum design system. Components should be imported from the `@adobe/react-spectrum` package. This library might also be referred to as "React Spectrum v3". + +If you are able to browse the web, the entire documentation for React Spectrum is available at [react-spectrum.adobe.com/react-spectrum](mdc:https:/react-spectrum.adobe.com/react-spectrum/index.html). + +## App setup + +If initializing a new app, ensure a React Spectrum Provider is used. The Provider specifies the theme to use, along with application level settings like the locale. Inside the Provider, you should render your application, including all React Spectrum components. + +```jsx +import {Button, defaultTheme, Provider} from '@adobe/react-spectrum'; +function App() { + return ( + + + + ); +} +``` + +When using server side rendering, the locale prop should be set explicitly on the Provider rather than relying on automatic locale selection. + +[Read more here](mdc:https:/react-spectrum.adobe.com/react-spectrum/getting-started.html) + +## Collection components + +For dynamic collections, instead of using `Array.map`, use the following JSX-based interface, which maps over your data and applies a function for each item to render it. + +```jsx + + {(item) => {item.name}} + +``` + +[Read more here](mdc:https:/react-spectrum.adobe.com/react-stately/collections.html) + +## Pressable components + +For pressable components such as Button, use `onPress` instead of `onClick`. Likewise use `onPressStart`, `onPressEnd`, `onPressChange`, and `onPressUp` where appropriate. + +Press event handlers are type `(e: PressEvent) => void` where `PressEvent` is defined as: + +```ts +interface PressEvent { + type: 'pressstart' | 'pressend' | 'pressup' | 'press'; + pointerType: 'mouse' | 'pen' | 'touch' | 'keyboard' | 'virtual'; + target: Element; + shiftKey: boolean; + ctrlKey: boolean; + metaKey: boolean; + altKey: boolean; + x: number; + y: number; +} +``` + +## Input components + +For input field components such as TextField, the `onChange` event handler is type `(value: string) => void`. + +## Styling + +All React Spectrum components support a limited set of styling options, including layout, spacing, sizing, and positioning options. While internal component styles such as padding, colors, borders and text styles are included in Spectrum and not available to override, external styles like margins and sizes can be set on all components. + +Supported styling options are available as props on every React Spectrum component. The following example shows a text field and a button. The text field has a custom width set on it, and the button has a margin before it. + +```jsx + +Submit +``` + +All of the available style props are listed below. + +The `Responsive` interface is defined as `T | ResponsiveProp` and `ResponsiveProp` is defined as `{base?: T, S?: T, M?: T, L?: T, [custom: string]: T | undefined}`: + +### Layout + +`flex` has the type `Responsive`. + +`flexBasis` has the type `Responsive`. + +`alignSelf` has the type `Responsive<'auto' | 'normal' | 'start' | 'end' | 'center' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'stretch'>`. + +`justifySelf` has the type `Responsive<'auto' | 'normal' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'center' | 'left' | 'right' | 'stretch'>`. + +`order`, `flexShrink`, `flexGrow` all have the type `Responsive`. + +`gridArea`, `gridColumn`, `gridRow`, `gridColumnStart`, `gridColumnEnd`, `gridRowStart`, `gridRowEnd` all have the type `Responsive`. + +### Spacing + +`margin`, `marginTop`, `marginBottom`, `marginStart`, `marginEnd`, `marginX`, `marginY` all have the type `Responsive`. + +### Sizing + +`width`, `minWidth`, `maxWidth`, `height`, `minHeight`, `maxHeight` have the type `Responsive`. + +### Positioning + +`top`, `bottom`, `left`, `right`, `start`, `end` all have the type `Responsive`. + +`zIndex` has type `Responsive`. + +`isHidden` has type `Responsive`. + +### Dimension values + +Where applicable, each style property accepts Spectrum defined variables, which conform to Spectrum design defined sizing and spacing requirements, and also automatically adapt to different devices. + +The following tokens values are available for sizing. Medium scale is used automatically for Desktop and Large scale is used for mobile. + +``` +| Token | Medium Scale (px) | Large Scale (px) | +|-------|------------------|-----------------| +| size-0 | 0 | 0 | +| size-10 | 1 | 1 | +| size-25 | 2 | 2 | +| size-40 | 3 | 4 | +| size-50 | 4 | 5 | +| size-65 | 5 | 6 | +| size-75 | 6 | 8 | +| size-85 | 7 | 9 | +| size-100 | 8 | 10 | +| size-115 | 9 | 11 | +| size-125 | 10 | 13 | +| size-130 | 11 | 14 | +| size-150 | 12 | 15 | +| size-160 | 13 | 16 | +| size-175 | 14 | 18 | +| size-200 | 16 | 20 | +| size-225 | 18 | 22 | +| size-250 | 20 | 25 | +| size-275 | 22 | 28 | +| size-300 | 24 | 30 | +| size-325 | 26 | 32 | +| size-350 | 28 | 35 | +| size-400 | 32 | 40 | +| size-450 | 36 | 45 | +| size-500 | 40 | 50 | +| size-550 | 44 | 56 | +| size-600 | 48 | 60 | +| size-675 | 54 | 68 | +| size-700 | 56 | 70 | +| size-800 | 64 | 80 | +| size-900 | 72 | 90 | +| size-1000 | 80 | 100 | +| size-1200 | 96 | 120 | +| size-1250 | 100 | 125 | +| size-1600 | 128 | 160 | +| size-1700 | 136 | 170 | +| size-2000 | 160 | 200 | +| size-2400 | 192 | 240 | +| size-3000 | 240 | 300 | +| size-3400 | 272 | 340 | +| size-3600 | 288 | 360 | +| size-4600 | 368 | 460 | +| size-5000 | 400 | 500 | +| size-6000 | 480 | 600 | +| single-line-height | 32 | 40 | +| single-line-width | 192 | 240 | +``` + +[Read more](mdc:https:/react-spectrum.adobe.com/react-spectrum/styling.html) + +### UNSAFE Style Overrides + +We highly discourage overriding the styles of React Spectrum components because it may break at any time when we change our implementation, making it difficult for you to update in the future. That said, the `UNSAFE_className` and `UNSAFE_style` props are supported as last-resort escape hatches. + +## Other tips + +### Virtualized collections + +When using virtualized collection components such as `TableView`, `ListBox`, and `ListView`, ensure the height is constrained. + +### Tooltips on elements + +Tooltips need to be accessible to keyboard and screen reader users, so we want to ensure that they are only placed on focusable and hoverable elements. For example, plain text on a page isn't focusable, so keyboard and screen reader users would be unable to access the information in that tooltip. + +If you need to display some additional context, consider using the `ContextualHelp` component. + +### Typography + +Note that the Text and Heading components are not general typography components, but instead are meant to be used WITHIN other component so default styles can be inherited (i.e. a Heading inside a Dialog or Item). These components should not be used standalone. React Spectrum does not include specific components for typography. \ No newline at end of file diff --git a/rules/style-macro.mdc b/rules/style-macro.mdc new file mode 100644 index 00000000000..58772879be0 --- /dev/null +++ b/rules/style-macro.mdc @@ -0,0 +1,405 @@ +--- +description: Using the React Spectrum S2 Style Macro +globs: +alwaysApply: false +--- +# React Spectrum S2 Style Macro + +The React Spectrum `style` macro generates atomic CSS at build time, which can be applied to any DOM element or Spectrum component. Style properties use Spectrum tokens such as colors, spacing, sizing, and typography, helping you work more quickly with TypeScript autocomplete, reduce the design choices you need to make, and improve consistency between Adobe applications. + +```jsx +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; +
+ {/* ... */} +
+``` + +## Values +The `style` macro supports a constrained set of values for each CSS property, which conform to the Spectrum design system. For example, the `backgroundColor` property supports Spectrum colors, and does not allow arbitrary hex or rgb values by default. This helps make it easier to build consistent UIs that are maintainable over time. + +### Colors + +The Spectrum 2 color palette is available across all color properties. See the following sections for color values available for each property. + +#### Background Colors + +These colors are specifically intended for use with the `backgroundColor` property. Prefer these over global colors for backgrounds where applicable. Some automatically adjust based on interaction states (e.g., hover). + +- `base` +- `layer-1` +- `layer-2` +- `pasteboard` +- `elevated` +- `accent` +- `accent-subtle` +- `neutral` +- `neutral-subdued` +- `neutral-subtle` +- `negative` +- `negative-subtle` +- `informative` +- `informative-subtle` +- `positive` +- `positive-subtle` +- `notice` +- `notice-subtle` +- `gray` +- `gray-subtle` +- `red` +- `red-subtle` +- `orange` +- `orange-subtle` +- `yellow` +- `yellow-subtle` +- `chartreuse` +- `chartreuse-subtle` +- `celery` +- `celery-subtle` +- `green` +- `green-subtle` +- `seafoam` +- `seafoam-subtle` +- `cyan` +- `cyan-subtle` +- `blue` +- `blue-subtle` +- `indigo` +- `indigo-subtle` +- `purple` +- `purple-subtle` +- `fuchsia` +- `fuchsia-subtle` +- `magenta` +- `magenta-subtle` +- `pink` +- `pink-subtle` +- `turquoise` +- `turquoise-subtle` +- `cinnamon` +- `cinnamon-subtle` +- `brown` +- `brown-subtle` +- `silver` +- `silver-subtle` +- `disabled` + +#### Text Colors + +These colors are specifically intended for use with the `color` property for text elements. Prefer these over global colors for text where applicable. Some automatically adjust based on interaction states. + +- `accent` +- `neutral` +- `neutral-subdued` +- `negative` +- `disabled` +- `heading` +- `title` +- `body` +- `detail` +- `code` + +#### Semantic Colors + +These colors convey specific meanings (e.g., success, error) and are available across all color properties. Prefer semantic colors over global colors when the color represents a specific meaning. They typically come in a scale from 100 to 1600 (e.g., `accent-color-100` to `accent-color-1600`). + +- `accent-color` (range: 100-1600) +- `informative-color` (range: 100-1600) +- `negative-color` (range: 100-1600) +- `notice-color` (range: 100-1600) +- `positive-color` (range: 100-1600) + +#### Global Colors + +These are general-purpose colors available across all color properties. They come in a scale, typically from 25 or 50 up to 1600 (e.g., `gray-50` to `gray-1000`, `blue-100` to `blue-1600`). + +- `gray` (range: 25-1000) +- `blue` (range: 100-1600) +- `red` (range: 100-1600) +- `orange` (range: 100-1600) +- `yellow` (range: 100-1600) +- `chartreuse` (range: 100-1600) +- `celery` (range: 100-1600) +- `green` (range: 100-1600) +- `seafoam` (range: 100-1600) +- `cyan` (range: 100-1600) +- `indigo` (range: 100-1600) +- `purple` (range: 100-1600) +- `fuchsia` (range: 100-1600) +- `magenta` (range: 100-1600) +- `pink` (range: 100-1600) +- `turquoise` (range: 100-1600) +- `brown` (range: 100-1600) +- `silver` (range: 100-1600) +- `cinnamon` (range: 100-1600) + +### Spacing + +Spacing properties such as `margin` and `padding` support a limited set of values. The API is represented in pixels, however, only values conforming to a 4px grid are allowed. This helps ensure that spacing and sizing are visually consistent. Spacing values are automatically converted to rems, which scale according to the user's font size preference. + +In addition to numeric values, the following spacing options are available: +- `text-to-control` – The default horizontal spacing between text and a UI control, for example between a label and input. This value automatically adjusts based on the font size. +- `text-to-visual` – The default horizontal spacing between text and a visual element, such as an icon. This value automatically adjusts based on the font size. +- `edge-to-text` – The default horizontal spacing between the edge of a UI control and text within it. This value is calculated relative to the height of the control. +- `pill` – The default horizontal spacing between the edge of a pill-shaped UI control (e.g. a fully rounded button) and text within it. This value is calculated relative to the height of the control. + +### Sizing + +Sizing properties such as `width` and `height` accept arbitrary pixel values. Internally, sizes are converted to rems, which scale according to the user's font size preference. Additionally, size values are multiplied by 1.25x on touch screen devices to help increase the size of hit targets. + +### Typography + +Spectrum 2 does not include specific components for typography. Instead, you can use the style macro to apply Spectrum typography to any HTML element or component. + +The `font` shorthand applies default values for the `fontFamily`, `fontSize`, `fontWeight`, `lineHeight`, and `color` properties, following Spectrum design pairings. These individual properties can also be set to override the default set by the shorthand. + +```jsx +
+

Heading

+

Body

+
    +
  • List item
  • +
+
+``` + +There are several different type scales. +- **Body** – use for the content of pages that are primarily text. +- **Heading** – use for headings in content pages. +- **Title** – use for titles within UI components such as cards or panels. +- **Detail** – use for less important metadata. +- **Code** – use for source code. + +Each type scale has a default size, and several t-shirt size modifiers for additional sizes. +- ui-xs +- ui-sm +- ui +- ui-lg +- ui-xl +- ui-2xl +- ui-3xl +- body-2xs +- body-xs +- body-sm +- body +- body-lg +- body-xl +- body-2xl +- body-3xl +- heading-2xs +- heading-xs +- heading-sm +- heading +- heading-lg +- heading-xl +- heading-2xl +- heading-3xl +- title-xs +- title-sm +- title +- title-lg +- title-xl +- title-2xl +- title-3xl +- detail-sm +- detail +- detail-lg +- detail-xl +- code-sm +- code +- code-lg +- code-xl + +> **Important Note** +> +> Only use `` and `` inside other Spectrum components with predefined styles, such as `` and ``. They do not include any styles by default, and should not be used standalone. Use HTML elements with the style macro directly instead. + +## Conditional styles + +The `style` macro also supports conditional styles, such as media queries, UI states such as hover and press, and component variants. Conditional values are defined as an object where each key is a condition. This keeps all values for each property together in one place so it is easy to see where overrides are coming from. +This example sets the padding of a div to 8px by default, and 32px at the large media query breakpoint (1024px) defined by Spectrum. + +```jsx +
+``` + +Conditions are mutually exclusive, following object property order. The `style` macro uses [CSS cascade layers](mdc:https:/developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Cascade_layers) to ensure that there are no specificity issues to worry about. The last matching condition always wins. + +### Runtime conditions + +The `style` macro also supports conditions that are resolved in JavaScript at runtime, such as variant props and UI states. When a runtime condition is detected, the `style` macro returns a function that can be called at runtime to apply. + +Runtime conditions can be named however you like, and values are defined as an object. This example changes the background color depending on a `variant` prop: + +```jsx +let styles = style({ + backgroundColor: { + variant: { + primary: 'accent', + secondary: 'neutral' + } + } +}); +function MyComponent({variant}) { + return
+} +``` + +Boolean conditions starting with `is` do not need to be nested in an object: + +```jsx +let styles = style({ + backgroundColor: { + default: 'gray-100', + isSelected: 'gray-900' + } +}); +
+``` + +Runtime conditions also work well with the @render props in React Aria Components. If you define your styles inline, you'll even get autocomplete for all of the available conditions. + +```jsx +import {Checkbox} from 'react-aria-components'; + +``` + +### Nesting conditions + +Conditions can be nested to apply styles when multiple conditions are true. Keep in mind that conditions at the same level are mutually exclusive, with the last matching condition winning. Since only one value can apply at a time, there are no specificity issues to worry about. + +```jsx +let styles = style({ + backgroundColor: { + default: 'gray-25', + isSelected: { + default: 'neutral', + isEmphasized: 'accent', + forcedColors: 'Highlight', + isDisabled: { + default: 'gray-400', + forcedColors: 'GrayText' + } + } + } +}); + +
+``` + +The above example has three runtime conditions (`isSelected`, `isEmphasized`, and `isDisabled`), and uses the `forcedColors` condition to apply styles for [Windows High Contrast Mode](mdc:https:/developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors). The order of precedence follows the order the conditions are defined in the object, with the `isSelected` + `isDisabled` + `forcedColors` state having the highest priority. + +## Reusing styles + +Styles can be reused by extracting common properties into objects, and spreading them into `style` calls. These must either be constants (declared with `const`) in the same file, or imported from another file as a macro (`with {type: 'macro'}`). Properties can be overridden just like normal JS objects – the last value always wins. + +```jsx +const horizontalStack = { + display: 'flex', + alignItems: 'center', + columnGap: 8 +}; +const styles = style({ + ...horizontalStack, + columnGap: 4 +}); +``` + +You can also create custom utilities by defining your own macros. These are normal functions whatever computations you like to generate styles. + +```jsx +// style-utils.ts +export function horizontalStack(gap: number) { + return { + display: 'flex', + alignItems: 'center', + columnGap: gap + }; +} +``` + +Then, import your macro and use it in a component. + +```jsx +// component.tsx +import {horizontalStack} from './style-utils' with {type: 'macro'}; +const styles = style({ + ...horizontalStack(4), + backgroundColor: 'base' +}); +``` + +### Built-in Utilities + +The `focusRing` utility generates styles for the standard Spectrum focus ring, allowing you to reuse it in custom components. + +```jsx +import {style, focusRing} from '@react-spectrum/s2/style' with {type: 'macro'}; +import {Button} from 'react-aria-components'; + +const buttonStyle = style({ + ...focusRing(), + // ... +}); + +export function CustomButton(props) { + return