From 46be2856add60b1d7e1b91b393b9bfa5f002f807 Mon Sep 17 00:00:00 2001 From: Robert Snow Date: Fri, 3 Apr 2026 06:35:09 +1100 Subject: [PATCH] fix: colorwheel stuck and table checkbox not aligned (#9862) * fix: colorwheel stuck and table checkbox not aligned * Fix items visible behind checkboxes * add pending api parity * update migration * update language and codemod * add codemods for Menu as well * fix hidden focus ring --- .../@react-spectrum/s2/src/ActionMenu.tsx | 5 +- packages/@react-spectrum/s2/src/TableView.tsx | 41 ++++++++++++--- .../s2/style/spectrum-theme.ts | 11 ++-- .../__snapshots__/actionmenu.test.ts.snap | 12 +++++ .../__tests__/__snapshots__/menu.test.ts.snap | 13 +++++ .../src/s1-to-s2/__tests__/actionmenu.test.ts | 12 +++++ .../src/s1-to-s2/__tests__/menu.test.ts | 13 +++++ .../components/ActionMenu/transform.ts | 8 +-- .../components/MenuTrigger/transform.ts | 17 +++++++ .../src/codemods/shared/transforms.ts | 51 +++++++++++++++++++ packages/dev/s2-docs/pages/s2/migrating.mdx | 2 +- .../react-aria/src/color/useColorWheel.ts | 32 ++++++------ 12 files changed, 182 insertions(+), 35 deletions(-) create mode 100644 packages/dev/codemods/src/s1-to-s2/src/codemods/components/MenuTrigger/transform.ts diff --git a/packages/@react-spectrum/s2/src/ActionMenu.tsx b/packages/@react-spectrum/s2/src/ActionMenu.tsx index b7b5cb63269..4b84ecb1115 100644 --- a/packages/@react-spectrum/s2/src/ActionMenu.tsx +++ b/packages/@react-spectrum/s2/src/ActionMenu.tsx @@ -26,7 +26,7 @@ import {useSpectrumContextProps} from './useSpectrumContextProps'; export interface ActionMenuProps extends Pick, - Pick, 'children' | 'items' | 'disabledKeys' | 'onAction'>, + Pick, 'children' | 'items' | 'disabledKeys' | 'onAction' | 'shouldCloseOnSelect'>, Pick, StyleProps, DOMProps, AriaLabelingProps { /** @@ -72,7 +72,8 @@ export const ActionMenu = /*#__PURE__*/(forwardRef as forwardRefType)(function A items={props.items} disabledKeys={props.disabledKeys} onAction={props.onAction} - size={props.menuSize}> + size={props.menuSize} + shouldCloseOnSelect={props.shouldCloseOnSelect}> {props.children} diff --git a/packages/@react-spectrum/s2/src/TableView.tsx b/packages/@react-spectrum/s2/src/TableView.tsx index 2b06f0a06d0..cdb2a1ecf04 100644 --- a/packages/@react-spectrum/s2/src/TableView.tsx +++ b/packages/@react-spectrum/s2/src/TableView.tsx @@ -867,16 +867,26 @@ const tableHeader = style({ }); const selectAllCheckbox = style({ - marginStart: 16 // table-edge-to-content, same between mobile and desktop }); const selectAllCheckboxColumn = style({ - padding: 0, + paddingStart: { + default: 0, + ':has([slot="selection"])': 16 + }, + paddingEnd: { + default: 0, + ':has(slot="selection")': 8 + }, + paddingY: 0, height: 'full', boxSizing: 'border-box', outlineStyle: 'none', position: 'relative', + display: 'flex', alignContent: 'center', + alignItems: 'center', + justifyContent: 'start', borderColor: { default: 'gray-300', forcedColors: 'ButtonBorder' @@ -1009,15 +1019,22 @@ const stickyCell = { const checkboxCellStyle = style({ ...commonCellStyles, ...stickyCell, + display: 'flex', paddingStart: 16, + paddingEnd: 8, alignContent: 'center', + alignItems: 'center', + justifyContent: 'start', height: 'calc(100% - 1px)', borderBottomWidth: 0, backgroundColor: '--rowBackgroundColor' }); const cellContent = style({ - truncate: true, + truncate: { + default: true, + isSticky: false + }, whiteSpace: { default: 'nowrap', overflowMode: { @@ -1031,7 +1048,10 @@ const cellContent = style({ end: 'end' } }, - width: 'full', + width: { + default: 'full', + ':has([slot="selection"])': 'unset' + }, isolation: 'isolate', padding: { default: 4, @@ -1058,7 +1078,14 @@ export interface CellProps extends Omit) { - let {children, isSticky, showDivider = false, align, textValue, ...otherProps} = props; + let { + children, + isSticky, + showDivider = false, + align, + textValue, + ...otherProps + } = props; let domRef = useDOMRef(ref); let tableVisualOptions = useContext(InternalTableContext); textValue ||= typeof children === 'string' ? children : undefined; @@ -1079,7 +1106,7 @@ export const Cell = forwardRef(function Cell(props: CellProps, ref: DOMRef {({id, isFocusVisible, hasChildItems, isTreeColumn, isExpanded, isDisabled}) => ( <> - {hasChildItems && isTreeColumn && + {hasChildItems && isTreeColumn && } {children} @@ -1252,7 +1279,7 @@ export const EditableCell = forwardRef(function EditableCell(props: EditableCell {...otherProps}> {({id, isFocusVisible, hasChildItems, isTreeColumn, isExpanded, isDisabled}) => ( <> - {hasChildItems && isTreeColumn && + {hasChildItems && isTreeColumn && } } /> diff --git a/packages/@react-spectrum/s2/style/spectrum-theme.ts b/packages/@react-spectrum/s2/style/spectrum-theme.ts index 0cf5302c724..a961056fd38 100644 --- a/packages/@react-spectrum/s2/style/spectrum-theme.ts +++ b/packages/@react-spectrum/s2/style/spectrum-theme.ts @@ -1112,12 +1112,11 @@ export const style = createTheme({ animationDuration: 150, animationTimingFunction: 'default' }), - // eslint-disable-next-line @typescript-eslint/no-unused-vars - truncate: (_value: true) => ({ - overflowX: 'hidden', - overflowY: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap' + truncate: (value: boolean) => ({ + overflowX: value ? 'hidden' : 'visible', + overflowY: value ? 'hidden' : 'visible', + textOverflow: value ? 'ellipsis' : 'clip', + whiteSpace: value ? 'nowrap' : 'normal' }), font: (value: keyof typeof fontSize) => { let type = value.split('-')[0]; diff --git a/packages/dev/codemods/src/s1-to-s2/__tests__/__snapshots__/actionmenu.test.ts.snap b/packages/dev/codemods/src/s1-to-s2/__tests__/__snapshots__/actionmenu.test.ts.snap index 1bdbbe57620..93df7da197e 100644 --- a/packages/dev/codemods/src/s1-to-s2/__tests__/__snapshots__/actionmenu.test.ts.snap +++ b/packages/dev/codemods/src/s1-to-s2/__tests__/__snapshots__/actionmenu.test.ts.snap @@ -16,6 +16,18 @@ let actionMenuItems = [ " `; +exports[`Renames closeOnSelect to shouldCloseOnSelect 1`] = ` +"import { MenuItem, ActionMenu } from "@react-spectrum/s2"; + +
+ + Cut + Copy + Paste + +
" +`; + exports[`Static - no change 1`] = ` "import { MenuItem, ActionMenu } from "@react-spectrum/s2"; diff --git a/packages/dev/codemods/src/s1-to-s2/__tests__/__snapshots__/menu.test.ts.snap b/packages/dev/codemods/src/s1-to-s2/__tests__/__snapshots__/menu.test.ts.snap index bf4bf187f32..8c6b13b359d 100644 --- a/packages/dev/codemods/src/s1-to-s2/__tests__/__snapshots__/menu.test.ts.snap +++ b/packages/dev/codemods/src/s1-to-s2/__tests__/__snapshots__/menu.test.ts.snap @@ -179,6 +179,19 @@ exports[`Renames ContextualHelpTrigger to UnavailableMenuItemTrigger and Dialog " `; +exports[`Renames closeOnSelect to shouldCloseOnSelect and moves to Menu 1`] = ` +"import { MenuItem, Menu, MenuTrigger, Button } from "@react-spectrum/s2"; + + + + + Cut + Copy + Paste + +" +`; + exports[`Static - Renames Item to MenuItem, Section to MenuSection 1`] = ` "import { MenuItem, diff --git a/packages/dev/codemods/src/s1-to-s2/__tests__/actionmenu.test.ts b/packages/dev/codemods/src/s1-to-s2/__tests__/actionmenu.test.ts index b973167c1af..12fd0ecd0bc 100644 --- a/packages/dev/codemods/src/s1-to-s2/__tests__/actionmenu.test.ts +++ b/packages/dev/codemods/src/s1-to-s2/__tests__/actionmenu.test.ts @@ -33,3 +33,15 @@ let actionMenuItems = [ `); + +test('Renames closeOnSelect to shouldCloseOnSelect', ` +import {ActionMenu, Item} from '@adobe/react-spectrum'; + +
+ + Cut + Copy + Paste + +
+`); diff --git a/packages/dev/codemods/src/s1-to-s2/__tests__/menu.test.ts b/packages/dev/codemods/src/s1-to-s2/__tests__/menu.test.ts index 489289df985..6e18c3c9803 100644 --- a/packages/dev/codemods/src/s1-to-s2/__tests__/menu.test.ts +++ b/packages/dev/codemods/src/s1-to-s2/__tests__/menu.test.ts @@ -217,3 +217,16 @@ import {Menu, MenuTrigger, ContextualHelpTrigger, Item, Button, Dialog, Heading, `); + +test('Renames closeOnSelect to shouldCloseOnSelect and moves to Menu', ` +import {Menu, MenuTrigger, Item, Button} from '@adobe/react-spectrum'; + + + + + Cut + Copy + Paste + + +`); diff --git a/packages/dev/codemods/src/s1-to-s2/src/codemods/components/ActionMenu/transform.ts b/packages/dev/codemods/src/s1-to-s2/src/codemods/components/ActionMenu/transform.ts index 163a7c03ede..75c30cce999 100644 --- a/packages/dev/codemods/src/s1-to-s2/src/codemods/components/ActionMenu/transform.ts +++ b/packages/dev/codemods/src/s1-to-s2/src/codemods/components/ActionMenu/transform.ts @@ -1,15 +1,15 @@ -import {commentOutProp} from '../../shared/transforms'; +import {commentOutProp, updatePropName} from '../../shared/transforms'; import {NodePath} from '@babel/traverse'; import * as t from '@babel/types'; /** * Transforms ActionMenu: - * - Comment out closeOnSelect (it has not been implemented yet). + * - Rename `closeOnSelect` to `shouldCloseOnSelect`. * - Comment out trigger (it has not been implemented yet). */ export default function transformActionMenu(path: NodePath): void { - // Comment out closeOnSelect - commentOutProp(path, {propName: 'closeOnSelect'}); + // Rename `closeOnSelect` to `shouldCloseOnSelect` + updatePropName(path, {oldPropName: 'closeOnSelect', newPropName: 'shouldCloseOnSelect'}); // Comment out trigger commentOutProp(path, {propName: 'trigger'}); diff --git a/packages/dev/codemods/src/s1-to-s2/src/codemods/components/MenuTrigger/transform.ts b/packages/dev/codemods/src/s1-to-s2/src/codemods/components/MenuTrigger/transform.ts new file mode 100644 index 00000000000..870744faaa2 --- /dev/null +++ b/packages/dev/codemods/src/s1-to-s2/src/codemods/components/MenuTrigger/transform.ts @@ -0,0 +1,17 @@ +import {movePropToChildComponent, updatePropName} from '../../shared/transforms'; +import {NodePath} from '@babel/traverse'; +import * as t from '@babel/types'; + +/** + * Transforms MenuTrigger: + * - Rename `closeOnSelect` to `shouldCloseOnSelect` and move it to the child `Menu`. + */ +export default function transformMenuTrigger(path: NodePath): void { + updatePropName(path, {oldPropName: 'closeOnSelect', newPropName: 'shouldCloseOnSelect'}); + + movePropToChildComponent(path, { + parentComponentName: 'MenuTrigger', + childComponentName: 'Menu', + propName: 'shouldCloseOnSelect' + }); +} diff --git a/packages/dev/codemods/src/s1-to-s2/src/codemods/shared/transforms.ts b/packages/dev/codemods/src/s1-to-s2/src/codemods/shared/transforms.ts index d0cf4f5e5c1..71caec0f1d2 100644 --- a/packages/dev/codemods/src/s1-to-s2/src/codemods/shared/transforms.ts +++ b/packages/dev/codemods/src/s1-to-s2/src/codemods/shared/transforms.ts @@ -595,6 +595,57 @@ export function movePropToParentComponent( }); } +/** + * Moves a prop from the parent component onto a direct child JSX element. + * + * Example: + * - MenuTrigger: Remove `shouldCloseOnSelect` and add it to the child `Menu` instead. + */ +export function movePropToChildComponent( + path: NodePath, + options: { + parentComponentName: string, + childComponentName: string, + propName: string + } +): void { + const {parentComponentName, childComponentName, propName} = options; + + if ( + !t.isJSXIdentifier(path.node.openingElement.name) || + getName(path, path.node.openingElement.name) !== parentComponentName + ) { + return; + } + + let attrs = path.node.openingElement.attributes; + let propAttr = attrs.find( + (attr): attr is t.JSXAttribute => t.isJSXAttribute(attr) && attr.name.name === propName + ); + if (!propAttr) { + return; + } + + let childPath = path.get('children').find( + (child) => + child.isJSXElement() && + t.isJSXIdentifier(child.node.openingElement.name) && + getName(path, child.node.openingElement.name) === childComponentName + ); + + if (!childPath?.isJSXElement()) { + return; + } + + childPath.node.openingElement.attributes.push( + t.jsxAttribute(t.jsxIdentifier(propName), propAttr.value) + ); + let index = attrs.indexOf(propAttr); + if (index !== -1) { + attrs.splice(index, 1); + } +} + /** * Update to use a new component. * diff --git a/packages/dev/s2-docs/pages/s2/migrating.mdx b/packages/dev/s2-docs/pages/s2/migrating.mdx index fceb6c4b197..f5dfa069f46 100644 --- a/packages/dev/s2-docs/pages/s2/migrating.mdx +++ b/packages/dev/s2-docs/pages/s2/migrating.mdx @@ -267,7 +267,7 @@ No updates needed. ### MenuTrigger -- Comment out `closeOnSelect` (it has not been implemented yet) +- Rename `closeOnSelect` to `shouldCloseOnSelect` and move to `Menu` ### NumberField diff --git a/packages/react-aria/src/color/useColorWheel.ts b/packages/react-aria/src/color/useColorWheel.ts index 844f080ef15..48f571bf682 100644 --- a/packages/react-aria/src/color/useColorWheel.ts +++ b/packages/react-aria/src/color/useColorWheel.ts @@ -237,21 +237,23 @@ export function useColorWheel(props: AriaColorWheelOptions, state: ColorWheelSta }, movePropsContainer); let thumbInteractions = isDisabled ? {} : mergeProps({ - onMouseDown: (e: React.MouseEvent) => { - if (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey) { - return; - } - onThumbDown(undefined); - }, - onPointerDown: (e: React.PointerEvent) => { - if (e.pointerType === 'mouse' && (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey)) { - return; - } - onThumbDown(e.pointerId); - }, - onTouchStart: (e: React.TouchEvent) => { - onThumbDown(e.changedTouches[0].identifier); - } + ...(typeof PointerEvent !== 'undefined' ? { + onPointerDown: (e: React.PointerEvent) => { + if (e.pointerType === 'mouse' && (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey)) { + return; + } + onThumbDown(e.pointerId); + }} : { + onMouseDown: (e: React.MouseEvent) => { + if (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey) { + return; + } + onThumbDown(undefined); + }, + onTouchStart: (e: React.TouchEvent) => { + onThumbDown(e.changedTouches[0].identifier); + } + }) }, keyboardProps, movePropsThumb); let {x, y} = state.getThumbPosition(thumbRadius);