diff --git a/.changeset/datatable-stable-selectors.md b/.changeset/datatable-stable-selectors.md new file mode 100644 index 00000000000..d7de8fe3ceb --- /dev/null +++ b/.changeset/datatable-stable-selectors.md @@ -0,0 +1,20 @@ +--- +'@primer/react': minor +--- + +Add stable `data-component` selectors to multiple components following ADR-023: + +- **ActionBar** +- **ActionList** and friends +- **Button** +- **FilteredActionList** and friends +- **Link** +- **LinkButton** +- **Pagination** +- **SelectPanel** and friends +- **Table** and friends +- **TextInput** +- **TextInputwithTokens** +- **TooltipV2** + +This enables consumers to query and test components using stable selectors like `[data-component="Table.Row"]`. diff --git a/package-lock.json b/package-lock.json index 874612288c0..34694f896dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,8 +82,8 @@ "react-dom": "^18.3.1" }, "devDependencies": { - "@primer/react": "38.19.0", - "@primer/styled-react": "1.0.5", + "@primer/react": "38.20.0", + "@primer/styled-react": "1.0.6", "@types/react": "^18.3.11", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.3", @@ -96,8 +96,8 @@ "name": "example-nextjs", "version": "0.0.0", "dependencies": { - "@primer/react": "38.19.0", - "@primer/styled-react": "1.0.5", + "@primer/react": "38.20.0", + "@primer/styled-react": "1.0.6", "next": "^16.1.7", "react": "^19.2.0", "react-dom": "^19.2.0", @@ -139,8 +139,8 @@ "version": "0.0.0", "dependencies": { "@primer/octicons-react": "^19.21.0", - "@primer/react": "38.19.0", - "@primer/styled-react": "1.0.5", + "@primer/react": "38.20.0", + "@primer/styled-react": "1.0.6", "clsx": "^2.1.1", "next": "^16.1.7", "react": "^19.2.0", @@ -4906,6 +4906,7 @@ "version": "1.7.4", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "dev": true, "license": "MIT", "dependencies": { "@floating-ui/utils": "^0.2.10" @@ -4915,6 +4916,7 @@ "version": "1.7.5", "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "dev": true, "license": "MIT", "dependencies": { "@floating-ui/core": "^1.7.4", @@ -4939,6 +4941,7 @@ "version": "0.2.10", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "dev": true, "license": "MIT" }, "node_modules/@github-ui/storybook-addon-performance-panel": { @@ -6689,55 +6692,6 @@ "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/@oddbird/css-anchor-positioning": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@oddbird/css-anchor-positioning/-/css-anchor-positioning-0.9.0.tgz", - "integrity": "sha512-G5nfb4sU0auxJH7VHafPwVJjr1GhH5uPSkmytGqhNftCpT3QEh8pFtMd4YHt1dRwb4o9qVZxlGSKUIc4TIrysQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@floating-ui/dom": "^1.7.5", - "@types/css-tree": "^2.3.11", - "css-tree": "^3.1.0", - "nanoid": "^5.1.6" - } - }, - "node_modules/@oddbird/css-anchor-positioning/node_modules/css-tree": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", - "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/@oddbird/css-anchor-positioning/node_modules/mdn-data": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", - "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", - "license": "CC0-1.0" - }, - "node_modules/@oddbird/css-anchor-positioning/node_modules/nanoid": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.7.tgz", - "integrity": "sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "dev": true, @@ -6835,9 +6789,9 @@ } }, "node_modules/@primer/octicons-react": { - "version": "19.21.1", - "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-19.21.1.tgz", - "integrity": "sha512-DHaErpn2ZLMOJRxTdLf73eCDRvvil3xRmsOvKzg+I5KY0PsclYvEs/OX/Xb6fTK+PTteehidFvHBxrLYyEQ9Mg==", + "version": "19.24.1", + "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-19.24.1.tgz", + "integrity": "sha512-Y4EqqNb6VrFWfs9HDu3EUpFNm2xbOWMh6uEyXn968gQgXDxfRjBKhWoogrMsbo74S3mGAP0KYSxL8ZebWHDRtA==", "license": "MIT", "engines": { "node": ">=8" @@ -9391,12 +9345,6 @@ "@types/node": "*" } }, - "node_modules/@types/css-tree": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/@types/css-tree/-/css-tree-2.3.11.tgz", - "integrity": "sha512-aEokibJOI77uIlqoBOkVbaQGC9zII0A+JH1kcTNKW2CwyYWD8KM6qdo+4c77wD3wZOQfJuNWAr9M4hdk+YhDIg==", - "license": "MIT" - }, "node_modules/@types/debug": { "version": "4.1.12", "dev": true, @@ -27585,13 +27533,13 @@ }, "packages/mcp": { "name": "@primer/mcp", - "version": "0.3.1", + "version": "0.3.2", "dependencies": { "@babel/runtime": "^7.28.6", "@modelcontextprotocol/sdk": "^1.24.0", "@primer/octicons": "^19.15.5", "@primer/primitives": "10.x || 11.x", - "@primer/react": "^38.15.0", + "@primer/react": "^38.20.0", "cheerio": "^1.0.0", "turndown": "^7.2.0", "zod": "^4.3.5" @@ -27743,18 +27691,17 @@ }, "packages/react": { "name": "@primer/react", - "version": "38.19.0", + "version": "38.20.0", "license": "MIT", "dependencies": { "@github/mini-throttle": "^2.1.1", "@github/relative-time-element": "^5.0.0", "@github/tab-container-element": "^4.8.2", "@lit-labs/react": "1.2.1", - "@oddbird/css-anchor-positioning": "^0.9.0", "@oddbird/popover-polyfill": "^0.5.2", "@primer/behaviors": "^1.10.2", "@primer/live-region-element": "^0.7.1", - "@primer/octicons-react": "^19.21.0", + "@primer/octicons-react": "^19.24.1", "@primer/primitives": "10.x || 11.x", "@tanstack/react-virtual": "^3.13.18", "clsx": "^2.1.1", @@ -28117,7 +28064,7 @@ }, "packages/styled-react": { "name": "@primer/styled-react", - "version": "1.0.5", + "version": "1.0.6", "dependencies": { "@styled-system/css": "^5.1.5", "@styled-system/props": "^5.1.5", @@ -28134,7 +28081,7 @@ "@babel/preset-react": "^7.28.5", "@babel/preset-typescript": "^7.28.5", "@primer/primitives": "10.x || 11.x", - "@primer/react": "^38.19.0", + "@primer/react": "^38.20.0", "@rollup/plugin-babel": "^6.1.0", "@storybook/react-vite": "^10.3.3", "@types/react": "18.3.11", diff --git a/packages/react/package.json b/packages/react/package.json index 4e1594917b3..bf354b115d6 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -81,7 +81,7 @@ "@oddbird/popover-polyfill": "^0.5.2", "@primer/behaviors": "^1.10.2", "@primer/live-region-element": "^0.7.1", - "@primer/octicons-react": "^19.21.0", + "@primer/octicons-react": "^19.24.1", "@primer/primitives": "10.x || 11.x", "@tanstack/react-virtual": "^3.13.18", "clsx": "^2.1.1", diff --git a/packages/react/src/ActionBar/ActionBar.test.tsx b/packages/react/src/ActionBar/ActionBar.test.tsx index f176d5e7add..bce7afda03f 100644 --- a/packages/react/src/ActionBar/ActionBar.test.tsx +++ b/packages/react/src/ActionBar/ActionBar.test.tsx @@ -417,3 +417,65 @@ describe('ActionBar.Menu returnFocusRef', () => { expect(document.activeElement).toEqual(menuButton) }) }) + +describe('ActionBar data-component attributes', () => { + it('renders ActionBar with data-component attribute', () => { + const {container} = render( + + + , + ) + + const actionBar = container.querySelector('[data-component="ActionBar"]') + expect(actionBar).toBeInTheDocument() + }) + + it('renders ActionBar.IconButton with data-component attribute', () => { + const {container} = render( + + + , + ) + + const iconButton = container.querySelector('[data-component="ActionBar"] [data-component="IconButton"]') + expect(iconButton).toBeInTheDocument() + }) + + it('renders ActionBar.VerticalDivider with data-component attribute', () => { + const {container} = render( + + + + + , + ) + + const divider = container.querySelector('[data-component="ActionBar.VerticalDivider"]') + expect(divider).toBeInTheDocument() + }) + + it('renders ActionBar.Group with data-component attribute', () => { + const {container} = render( + + + + + + , + ) + + const group = container.querySelector('[data-component="ActionBar.Group"]') + expect(group).toBeInTheDocument() + }) + + it('renders ActionBar.Menu.IconButton with data-component attribute', () => { + render( + + + , + ) + + const menuButton = screen.getByRole('button', {name: 'More options'}) + expect(menuButton).toHaveAttribute('data-component', 'ActionBar.Menu.IconButton') + }) +}) diff --git a/packages/react/src/ActionBar/ActionBar.tsx b/packages/react/src/ActionBar/ActionBar.tsx index 83712ce21fd..34f7d51ca78 100644 --- a/packages/react/src/ActionBar/ActionBar.tsx +++ b/packages/react/src/ActionBar/ActionBar.tsx @@ -353,7 +353,7 @@ export const ActionBar: React.FC> = prop return ( -
+
-
+
{children}
@@ -571,7 +571,14 @@ export const ActionBarMenu = forwardRef( return ( - + {items.map((item, index) => renderMenuItem(item, index))} diff --git a/packages/react/src/ActionList/ActionList.test.tsx b/packages/react/src/ActionList/ActionList.test.tsx index 49e44022c7f..f9d514c562c 100644 --- a/packages/react/src/ActionList/ActionList.test.tsx +++ b/packages/react/src/ActionList/ActionList.test.tsx @@ -258,3 +258,180 @@ describe('ActionList', () => { expect(linkElements[2]).toHaveAttribute('data-size', 'medium') // default should be medium }) }) + +describe('ActionList data-component attributes', () => { + it('renders ActionList with data-component attribute', () => { + const {container} = HTMLRender( + + Item + , + ) + + const actionList = container.querySelector('[data-component="ActionList"]') + expect(actionList).toBeInTheDocument() + }) + + it('renders ActionList.Item with data-component attribute', () => { + const {container} = HTMLRender( + + Item + , + ) + + const item = container.querySelector('[data-component="ActionList.Item"]') + expect(item).toBeInTheDocument() + }) + + it('renders ActionList.Item.Wrapper with data-component attribute', () => { + const {container} = HTMLRender( + + Item + , + ) + + const wrapper = container.querySelector('[data-component="ActionList.Item.Wrapper"]') + expect(wrapper).toBeInTheDocument() + }) + + it('renders ActionList.Item.Label with data-component attribute', () => { + const {container} = HTMLRender( + + Item + , + ) + + const label = container.querySelector('[data-component="ActionList.Item.Label"]') + expect(label).toBeInTheDocument() + }) + + it('renders ActionList.Item--DividerContainer with data-component attribute', () => { + const {container} = HTMLRender( + + Item + , + ) + + const dividerContainer = container.querySelector('[data-component="ActionList.Item--DividerContainer"]') + expect(dividerContainer).toBeInTheDocument() + }) + + it('renders ActionList.Group with data-component attribute', () => { + const {container} = HTMLRender( + + + Group + Item + + , + ) + + const group = container.querySelector('[data-component="ActionList.Group"]') + expect(group).toBeInTheDocument() + }) + + it('renders ActionList.GroupHeading with data-component attribute', () => { + const {container} = HTMLRender( + + + Group Heading + Item + + , + ) + + const groupHeading = container.querySelector('[data-component="GroupHeadingWrap"]') + expect(groupHeading).toBeInTheDocument() + }) + + it('renders ActionList.Divider with data-component attribute', () => { + const {container} = HTMLRender( + + Item 1 + + Item 2 + , + ) + + const divider = container.querySelector('[data-component="ActionList.Divider"]') + expect(divider).toBeInTheDocument() + }) + + it('renders ActionList.Description with data-component attribute', () => { + const {container} = HTMLRender( + + + Item + Description + + , + ) + + const description = container.querySelector('[data-component="ActionList.Description"]') + expect(description).toBeInTheDocument() + }) + + it('renders ActionList.LeadingVisual with data-component attribute', () => { + const {container} = HTMLRender( + + + Icon + Item + + , + ) + + const leadingVisual = container.querySelector('[data-component="ActionList.LeadingVisual"]') + expect(leadingVisual).toBeInTheDocument() + }) + + it('renders ActionList.TrailingVisual with data-component attribute', () => { + const {container} = HTMLRender( + + + Item + Icon + + , + ) + + const trailingVisual = container.querySelector('[data-component="ActionList.TrailingVisual"]') + expect(trailingVisual).toBeInTheDocument() + }) + + it('renders ActionList.Selection with data-component attribute', () => { + const {container} = HTMLRender( + + Item + , + ) + + const selection = container.querySelector('[data-component="ActionList.Selection"]') + expect(selection).toBeInTheDocument() + }) + + it('renders ActionList.Heading with data-component attribute', () => { + const {container} = HTMLRender( + + Heading + Item + , + ) + + const heading = container.querySelector('[data-component="ActionList.Heading"]') + expect(heading).toBeInTheDocument() + }) + + it('renders ActionList.TrailingAction with data-component attribute', () => { + const {container} = HTMLRender( + + + Item + + + , + ) + + const trailingAction = container.querySelector('[data-component="ActionList.TrailingAction"]') + expect(trailingAction).toBeInTheDocument() + }) +}) diff --git a/packages/react/src/ActionList/Group.tsx b/packages/react/src/ActionList/Group.tsx index 441e79302c9..dbfae9838db 100644 --- a/packages/react/src/ActionList/Group.tsx +++ b/packages/react/src/ActionList/Group.tsx @@ -98,7 +98,12 @@ export const Group: FCWithSlotMarker +
  • {title && !slots.groupHeading ? ( // Escape hatch: supports old API in a non breaking way @@ -177,6 +182,7 @@ export const GroupHeading: FCWithSlotMarker
  • ( {...wrapperProps} className={classes.ActionListContent} data-size={size} + data-component="ActionList.Item.Wrapper" // @ts-ignore: ItemWrapper is polymorphic and the ref type depends on the rendered element ('button' or 'li') ref={forwardedRef} > @@ -347,13 +355,14 @@ const UnwrappedItem = ( > {slots.leadingVisual} + {/* TODO: next-major: change to data-component="ActionList.Item.DividerContainer" next major version */} - + {childrenWithoutSlots} {/* Loading message needs to be in here so it is read with the label */} {/* If the item is inactive, we do not simultaneously announce that it is loading */} diff --git a/packages/react/src/ActionList/List.tsx b/packages/react/src/ActionList/List.tsx index ee9213fcb50..00ef52462a5 100644 --- a/packages/react/src/ActionList/List.tsx +++ b/packages/react/src/ActionList/List.tsx @@ -75,6 +75,7 @@ const UnwrappedList = ( role={listRole} aria-labelledby={ariaLabelledBy} ref={listRef} + data-component="ActionList" data-dividers={showDividers} data-variant={variant} {...restProps} diff --git a/packages/react/src/ActionList/TrailingAction.tsx b/packages/react/src/ActionList/TrailingAction.tsx index 933c3a6f584..585a0f4aa14 100644 --- a/packages/react/src/ActionList/TrailingAction.tsx +++ b/packages/react/src/ActionList/TrailingAction.tsx @@ -31,7 +31,11 @@ export type ActionListTrailingActionProps = ElementProps & { export const TrailingAction = forwardRef( ({as = 'button', icon, label, href = null, className, style, loading, ...props}, forwardedRef) => { return ( - + {icon ? ( > = ( export type ActionListLeadingVisualProps = VisualProps export const LeadingVisual: FCWithSlotMarker> = ({className, ...props}) => { return ( - + {props.children} ) @@ -26,7 +30,12 @@ export type ActionListTrailingVisualProps = VisualProps export const TrailingVisual: FCWithSlotMarker> = ({className, ...props}) => { const {trailingVisualId} = React.useContext(ItemContext) return ( - + {props.children} ) diff --git a/packages/react/src/Button/ButtonBase.tsx b/packages/react/src/Button/ButtonBase.tsx index 9600a6f86b8..3aeeff2dc95 100644 --- a/packages/react/src/Button/ButtonBase.tsx +++ b/packages/react/src/Button/ButtonBase.tsx @@ -86,6 +86,7 @@ const ButtonBase = forwardRef(({children, as: Component = 'button', ...props}, f > { return ( - + {children} ) diff --git a/packages/react/src/Button/__tests__/Button.test.tsx b/packages/react/src/Button/__tests__/Button.test.tsx index a2724d950d2..1ade78f3fbb 100644 --- a/packages/react/src/Button/__tests__/Button.test.tsx +++ b/packages/react/src/Button/__tests__/Button.test.tsx @@ -318,3 +318,63 @@ describe('Button', () => { expect(triggerEl).toHaveAccessibleDescription('Love is all around (command h)') }) }) + +describe('data-component attributes', () => { + describe('Button', () => { + it('should have data-component="Button" on the button element', () => { + const {container} = render() + expect(container.querySelector('[data-component="Button"]')).toBeInTheDocument() + }) + + it('should have data-component="buttonContent" on the content wrapper', () => { + const {container} = render() + expect(container.querySelector('[data-component="buttonContent"]')).toBeInTheDocument() + }) + + it('should have data-component="text" on the text element', () => { + const {container} = render() + expect(container.querySelector('[data-component="text"]')).toBeInTheDocument() + }) + + it('should have data-component="leadingVisual" on leading visual', () => { + const {container} = render() + expect(container.querySelector('[data-component="leadingVisual"]')).toBeInTheDocument() + }) + + it('should have data-component="trailingVisual" on trailing visual', () => { + const {container} = render() + expect(container.querySelector('[data-component="trailingVisual"]')).toBeInTheDocument() + }) + + it('should have data-component="ButtonCounter" on count element', () => { + const {container} = render() + expect(container.querySelector('[data-component="ButtonCounter"]')).toBeInTheDocument() + }) + + it('should have data-component="loadingSpinner" when loading without visuals', () => { + const {container} = render() + expect(container.querySelector('[data-component="loadingSpinner"]')).toBeInTheDocument() + }) + }) + + describe('IconButton', () => { + it('should have data-component="IconButton" on the button element', () => { + const {container} = render() + expect(container.querySelector('[data-component="IconButton"]')).toBeInTheDocument() + }) + }) + + describe('LinkButton', () => { + it('should have data-component="LinkButton" on the link element', () => { + const {container} = render(Link) + expect(container.querySelector('[data-component="LinkButton"]')).toBeInTheDocument() + }) + + it('should have data-component="buttonContent" on the content wrapper', () => { + const {container} = render(Link) + expect( + container.querySelector('[data-component="LinkButton"] [data-component="buttonContent"]'), + ).toBeInTheDocument() + }) + }) +}) diff --git a/packages/react/src/Button/__tests__/__snapshots__/Button.test.tsx.snap b/packages/react/src/Button/__tests__/__snapshots__/Button.test.tsx.snap index 0e24734daa4..1051eac1018 100644 --- a/packages/react/src/Button/__tests__/__snapshots__/Button.test.tsx.snap +++ b/packages/react/src/Button/__tests__/__snapshots__/Button.test.tsx.snap @@ -4,6 +4,7 @@ exports[`Button > respects block prop 1`] = ` , + }} + items={[]} + placeholder="Select items" + placeholderText="Filter items" + selected={selected} + onSelectedChange={setSelected} + filterValue={filter} + onFilterChange={setFilter} + open={open} + onOpenChange={setOpen} + /> + ) + } + + renderWithProp(, usingRemoveActiveDescendant) + + await user.click(screen.getByText('Select items')) + + expect( + screen.getByRole('dialog').querySelector('[data-component="SelectPanel.MessageAction"]'), + ).toBeInTheDocument() + }) + + it('allows accessing nested ActionList data-component attributes from SelectPanel', async () => { + const user = userEvent.setup() + + const itemsWithLeadingVisual = [ + {text: 'item one', leadingVisual: () => Icon}, + {text: 'item two', leadingVisual: () => Icon}, + ] + + function SelectPanelWithLeadingVisuals() { + const [selected, setSelected] = React.useState([]) + const [filter, setFilter] = React.useState('') + const [open, setOpen] = React.useState(false) + + return ( + + ) + } + + renderWithProp(, usingRemoveActiveDescendant) + + await user.click(screen.getByText('Select items')) + + // Test that you can query for ActionList primitives nested within SelectPanel + expect( + screen.getByRole('dialog').querySelector('[data-component="SelectPanel"] [data-component="ActionList.Item"]'), + ).toBeInTheDocument() + + expect( + screen + .getByRole('dialog') + .querySelector('[data-component="SelectPanel"] [data-component="ActionList.LeadingVisual"]'), + ).toBeInTheDocument() + + expect( + screen + .getByRole('dialog') + .querySelector('[data-component="SelectPanel"] [data-component="ActionList.Item.Label"]'), + ).toBeInTheDocument() + + // Test that you can query for the TextInput nested within SelectPanel + expect( + screen + .getByRole('dialog') + .querySelector('[data-component="SelectPanel"] [data-component="TextInput"] [data-component="input"]'), + ).toBeInTheDocument() + }) + + it('renders SelectPanel.CloseButton with data-component attribute on wide viewports in modal variant', async () => { + await page.viewport(1400, 728) + const user = userEvent.setup() + + renderWithProp( {}} />, usingRemoveActiveDescendant) + + await user.click(screen.getByText('Select items')) + + expect( + screen.getByRole('dialog').querySelector('[data-component="SelectPanel.CloseButton"]'), + ).toBeInTheDocument() + }) + + it('renders SelectPanel.SaveAndCloseButton with data-component attribute on narrow viewports', async () => { + await page.viewport(320, 568) + const user = userEvent.setup() + + function MultiSelectPanelWithoutCancel() { + const [selected, setSelected] = React.useState([]) + const [filter, setFilter] = React.useState('') + const [open, setOpen] = React.useState(false) + + return ( + + + + ) + } + + render() + + await user.click(screen.getByText('Select items')) + + expect( + screen.getByRole('dialog').querySelector('[data-component="SelectPanel.SaveAndCloseButton"]'), + ).toBeInTheDocument() + }) + }) }) describe('Event propagation', () => { diff --git a/packages/react/src/SelectPanel/SelectPanel.tsx b/packages/react/src/SelectPanel/SelectPanel.tsx index 0b8162ca50b..3f5d07809ab 100644 --- a/packages/react/src/SelectPanel/SelectPanel.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.tsx @@ -879,14 +879,14 @@ function Panel({ closeButtonProps={closeButtonProps} displayInViewport={displayInViewport} > -
    -
    +
    +
    - + {title} {subtitle ? ( -
    +
    {subtitle}
    ) : null} @@ -899,6 +899,7 @@ function Panel({ icon={XIcon} aria-label="Cancel and close" className={classes.ResponsiveCloseButton} + data-component="SelectPanel.CloseButton" onClick={() => { onCancel?.() onCancelRequested() @@ -907,7 +908,7 @@ function Panel({ ) : null}
    {notice && ( -
    +
    {footer ? ( -
    {footer}
    +
    + {footer} +
    ) : renderFooter ? (
    -
    +
    {secondaryAction}
    {showPermanentCancelSaveButtons || showResponsiveCancelSaveButtons ? ( @@ -973,6 +981,7 @@ function Panel({ >
    - {variant === 'modal' && open ?
    : null} + {variant === 'modal' && open ?
    : null} ) } const SecondaryButton: React.FC = props => { return ( - ) @@ -1029,7 +1040,7 @@ const SecondaryButton: React.FC = props => { const SecondaryLink: React.FC = props => { return ( - + {props.children} ) diff --git a/packages/react/src/SelectPanel/SelectPanelMessage.tsx b/packages/react/src/SelectPanel/SelectPanelMessage.tsx index 64fc56f1bf0..458c42883be 100644 --- a/packages/react/src/SelectPanel/SelectPanelMessage.tsx +++ b/packages/react/src/SelectPanel/SelectPanelMessage.tsx @@ -36,11 +36,26 @@ export const SelectPanelMessage: React.FC = ({ const IconComponent = CustomIcon || (variant !== 'empty' ? AlertIcon : undefined) return ( -
    - {IconComponent && } - {title} - {children} - {action &&
    {action}
    } +
    + {IconComponent && ( + + )} + + {title} + + + {children} + + {action && ( +
    + {action} +
    + )}
    ) } diff --git a/packages/react/src/StateLabel/__tests__/__snapshots__/StateLabel.test.tsx.snap b/packages/react/src/StateLabel/__tests__/__snapshots__/StateLabel.test.tsx.snap index 51901219268..a6e81a2fcbb 100644 --- a/packages/react/src/StateLabel/__tests__/__snapshots__/StateLabel.test.tsx.snap +++ b/packages/react/src/StateLabel/__tests__/__snapshots__/StateLabel.test.tsx.snap @@ -10,6 +10,7 @@ exports[`StateLabel > prefers the size prop over deprecated variant prop 1`] = ` renders children 1`] = ` respects the deprecated variant prop 1`] = ` respects the deprecated variant prop 2`] = ` respects the size prop 1`] = ` respects the size prop 2`] = ` respects the status prop 1`] = ` respects the status prop 2`] = ` respects the status prop 3`] = ` respects the status prop 4`] = ` respects the status prop 5`] = ` respects the status prop 6`] = ` respects the status prop 7`] = ` respects the status prop 8`] = ` respects the status prop 9`] = ` respects the status prop 10`] = ` { expect(srElement?.textContent).toBe('') }) }) + + describe('data-component attributes', () => { + it('renders TextInput with data-component attribute', () => { + const {container} = render() + + expect(container.querySelector('[data-component="TextInput"]')).toBeInTheDocument() + }) + + it('renders input with data-component attribute', () => { + const {container} = render() + + expect(container.querySelector('[data-component="input"]')).toBeInTheDocument() + }) + + it('renders TextInput.LeadingVisual with data-component attribute', () => { + const {container} = render() + + expect(container.querySelector('[data-component="TextInput.LeadingVisual"]')).toBeInTheDocument() + }) + + it('renders TextInput.TrailingVisual with data-component attribute', () => { + const {container} = render() + + expect(container.querySelector('[data-component="TextInput.TrailingVisual"]')).toBeInTheDocument() + }) + + it('renders TextInput.Action with data-component attribute', () => { + const {container} = render( + Clear} />, + ) + + expect(container.querySelector('[data-component="TextInput.Action"]')).toBeInTheDocument() + }) + + it('renders TextInput.Icon with data-component attribute', () => { + const {container} = render() + + expect(container.querySelector('[data-component="TextInput.Icon"]')).toBeInTheDocument() + }) + + it('renders TextInput.CharacterCounter with data-component attribute', () => { + const {container} = render() + + expect(container.querySelector('[data-component="TextInput.CharacterCounter"]')).toBeInTheDocument() + }) + }) }) diff --git a/packages/react/src/TextInput/TextInput.tsx b/packages/react/src/TextInput/TextInput.tsx index 88a4c716226..5c0026ce841 100644 --- a/packages/react/src/TextInput/TextInput.tsx +++ b/packages/react/src/TextInput/TextInput.tsx @@ -248,7 +248,7 @@ const TextInput = React.forwardRef( onClick={focusInput} aria-busy={Boolean(loading)} > - {IconComponent && } + {IconComponent && } ( ? [characterCountStaticMessageId, inputDescribedBy].filter(Boolean).join(' ') || undefined : inputDescribedBy } + // TODO: next-major: Remove in favor of data-component="TextInput.Input" data-component="input" /> {loading && {loaderText}} @@ -306,6 +307,7 @@ const TextInput = React.forwardRef( id={characterCountId} size="small" className={clsx(classes.CharacterCounter, isOverLimit && classes['CharacterCounter--error'])} + data-component="TextInput.CharacterCounter" > {isOverLimit && } {characterCount} diff --git a/packages/react/src/TextInput/__snapshots__/TextInput.test.tsx.snap b/packages/react/src/TextInput/__snapshots__/TextInput.test.tsx.snap index b491ab7d0bf..0e5938a06cf 100644 --- a/packages/react/src/TextInput/__snapshots__/TextInput.test.tsx.snap +++ b/packages/react/src/TextInput/__snapshots__/TextInput.test.tsx.snap @@ -5,6 +5,7 @@ exports[`TextInput > renders contrast 1`] = ` renders error 1`] = ` renders monospace 1`] = ` renders placeholder 1`] = ` should render a password input 1`] = ` { fireEvent.keyDown(inputNode, {key: 'a'}) expect(onKeyDownMock).toHaveBeenCalled() }) + + describe('data-component attributes', () => { + it('renders TextInputWithTokens with data-component attribute', () => { + const onRemoveMock = vi.fn() + const {container} = render() + + expect(container.querySelector('[data-component="TextInputWithTokens"]')).toBeInTheDocument() + }) + + it('renders TextInputWithTokens.Input with data-component attribute', () => { + const onRemoveMock = vi.fn() + const {container} = render() + + expect(container.querySelector('[data-component="TextInputWithTokens.Input"]')).toBeInTheDocument() + }) + + it('renders TextInputWithTokens.Token with data-component attribute', () => { + const onRemoveMock = vi.fn() + const {container} = render() + + expect(container.querySelector('[data-component="TextInputWithTokens.Token"]')).toBeInTheDocument() + }) + + it('renders TextInputWithTokens.Icon with data-component attribute', () => { + const onRemoveMock = vi.fn() + const {container} = render() + + expect(container.querySelector('[data-component="TextInputWithTokens.Icon"]')).toBeInTheDocument() + }) + + it('renders TextInputWithTokens.LeadingVisual with data-component attribute', () => { + const onRemoveMock = vi.fn() + const {container} = render( + , + ) + + expect(container.querySelector('[data-component="TextInputWithTokens.LeadingVisual"]')).toBeInTheDocument() + }) + + it('renders TextInputWithTokens.TrailingVisual with data-component attribute', () => { + const onRemoveMock = vi.fn() + const {container} = render( + , + ) + + expect(container.querySelector('[data-component="TextInputWithTokens.TrailingVisual"]')).toBeInTheDocument() + }) + + it('renders TextInputWithTokens.OverflowCount with data-component attribute when tokens are truncated', () => { + const onRemoveMock = vi.fn() + const {container} = render( + , + ) + + expect(container.querySelector('[data-component="TextInputWithTokens.OverflowCount"]')).toBeInTheDocument() + }) + }) }) diff --git a/packages/react/src/TextInputWithTokens/TextInputWithTokens.tsx b/packages/react/src/TextInputWithTokens/TextInputWithTokens.tsx index 8031d87a4d5..0d3ef273eec 100644 --- a/packages/react/src/TextInputWithTokens/TextInputWithTokens.tsx +++ b/packages/react/src/TextInputWithTokens/TextInputWithTokens.tsx @@ -294,12 +294,16 @@ function TextInputWithTokensInnerComponent - {IconComponent && !LeadingVisual && } + {IconComponent && !LeadingVisual && ( + + )} {typeof LeadingVisual !== 'string' && isValidElementType(LeadingVisual) ? : LeadingVisual} @@ -320,6 +324,7 @@ function TextInputWithTokensInnerComponent {shouldExposeSelectedValuesDescription ? ( @@ -341,17 +346,21 @@ function TextInputWithTokensInnerComponent ))} {tokensAreTruncated && tokens.length - visibleTokens.length ? ( - +{tokens.length - visibleTokens.length} + + +{tokens.length - visibleTokens.length} + ) : null}
    {typeof TrailingVisual !== 'string' && isValidElementType(TrailingVisual) ? : TrailingVisual} diff --git a/packages/react/src/TextInputWithTokens/__snapshots__/TextInputWithTokens.test.tsx.snap b/packages/react/src/TextInputWithTokens/__snapshots__/TextInputWithTokens.test.tsx.snap index a30da5ee849..b3e86a19527 100644 --- a/packages/react/src/TextInputWithTokens/__snapshots__/TextInputWithTokens.test.tsx.snap +++ b/packages/react/src/TextInputWithTokens/__snapshots__/TextInputWithTokens.test.tsx.snap @@ -11,6 +11,7 @@ exports[`TextInputWithTokens > renders a leadingVisual and trailingVisual 1`] =
    renders a leadingVisual and trailingVisual 1`] = @@ -46,12 +49,14 @@ exports[`TextInputWithTokens > renders a leadingVisual and trailingVisual 1`] =
    renders a leadingVisual and trailingVisual 1`] = renders a leadingVisual and trailingVisual 1`] = renders a leadingVisual and trailingVisual 1`] = renders a leadingVisual and trailingVisual 1`] = renders a leadingVisual and trailingVisual 1`] = renders a leadingVisual and trailingVisual 1`] = renders a leadingVisual and trailingVisual 1`] = renders a leadingVisual and trailingVisual 1`] = @@ -438,6 +460,7 @@ exports[`TextInputWithTokens > renders a leadingVisual and trailingVisual 1`] = "container":
    renders a leadingVisual and trailingVisual 1`] = @@ -473,12 +498,14 @@ exports[`TextInputWithTokens > renders a leadingVisual and trailingVisual 1`] =
    renders a leadingVisual and trailingVisual 1`] = renders a leadingVisual and trailingVisual 1`] = renders a leadingVisual and trailingVisual 1`] = renders a leadingVisual and trailingVisual 1`] = renders a leadingVisual and trailingVisual 1`] = renders a leadingVisual and trailingVisual 1`] = renders a leadingVisual and trailingVisual 1`] = renders a leadingVisual and trailingVisual 1`] = @@ -926,6 +970,7 @@ exports[`TextInputWithTokens > renders a truncated set of tokens 1`] = `
    renders a truncated set of tokens 1`] = `
    renders a truncated set of tokens 1`] = ` renders a truncated set of tokens 1`] = ` + 6 @@ -1045,6 +1096,7 @@ exports[`TextInputWithTokens > renders a truncated set of tokens 1`] = ` "container":
    renders a truncated set of tokens 1`] = `
    renders a truncated set of tokens 1`] = ` renders a truncated set of tokens 1`] = ` + 6 @@ -1226,6 +1284,7 @@ exports[`TextInputWithTokens > renders as block layout 1`] = `
    renders as block layout 1`] = ` @@ -1250,6 +1310,7 @@ exports[`TextInputWithTokens > renders as block layout 1`] = `
    renders as block layout 1`] = ` @@ -1334,6 +1396,7 @@ exports[`TextInputWithTokens > renders at a maximum height when specified 1`] =
    renders at a maximum height when specified 1`] =
    renders at a maximum height when specified 1`] = renders at a maximum height when specified 1`] = renders at a maximum height when specified 1`] = renders at a maximum height when specified 1`] = renders at a maximum height when specified 1`] = renders at a maximum height when specified 1`] = renders at a maximum height when specified 1`] = renders at a maximum height when specified 1`] =
    renders at a maximum height when specified 1`] =
    renders at a maximum height when specified 1`] = renders at a maximum height when specified 1`] = renders at a maximum height when specified 1`] = renders at a maximum height when specified 1`] = renders at a maximum height when specified 1`] = renders at a maximum height when specified 1`] = renders at a maximum height when specified 1`] = renders at a maximum height when specified 1`] =
    renders tokens at the specified sizes 1`] = `
    renders tokens at the specified sizes 1`] = ` renders tokens at the specified sizes 1`] = ` renders tokens at the specified sizes 1`] = ` renders tokens at the specified sizes 1`] = ` renders tokens at the specified sizes 1`] = ` renders tokens at the specified sizes 1`] = ` renders tokens at the specified sizes 1`] = ` renders tokens at the specified sizes 1`] = `
    renders tokens at the specified sizes 1`] = `
    renders tokens at the specified sizes 1`] = ` renders tokens at the specified sizes 1`] = ` renders tokens at the specified sizes 1`] = ` renders tokens at the specified sizes 1`] = ` renders tokens at the specified sizes 1`] = ` renders tokens at the specified sizes 1`] = ` renders tokens at the specified sizes 1`] = ` renders tokens at the specified sizes 1`] = `
    renders tokens at the specified sizes 2`] = `
    renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = `
    renders tokens at the specified sizes 2`] = `
    renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = `
    renders tokens at the specified sizes 2`] = `
    renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = ` renders tokens at the specified sizes 2`] = `
    renders tokens at the specified sizes 3`] = `
    renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = `
    renders tokens at the specified sizes 3`] = `
    renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = `
    renders tokens at the specified sizes 3`] = `
    renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = `
    renders tokens at the specified sizes 3`] = `
    renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = ` renders tokens at the specified sizes 3`] = `
    renders tokens at the specified sizes 4`] = `
    renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = `
    renders tokens at the specified sizes 4`] = `
    renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = `
    renders tokens at the specified sizes 4`] = `
    renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = `
    renders tokens at the specified sizes 4`] = `
    renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = `
    renders tokens at the specified sizes 4`] = `
    renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = ` renders tokens at the specified sizes 4`] = `
    @@ -7778,12 +8129,14 @@ exports[`TextInputWithTokens > renders tokens on a single line when specified 1`
    renders tokens on a single line when specified 1` renders tokens on a single line when specified 1` renders tokens on a single line when specified 1` renders tokens on a single line when specified 1` renders tokens on a single line when specified 1` renders tokens on a single line when specified 1` renders tokens on a single line when specified 1` renders tokens on a single line when specified 1`
    @@ -8162,12 +8531,14 @@ exports[`TextInputWithTokens > renders tokens on a single line when specified 1`
    renders tokens on a single line when specified 1` renders tokens on a single line when specified 1` renders tokens on a single line when specified 1` renders tokens on a single line when specified 1` renders tokens on a single line when specified 1` renders tokens on a single line when specified 1` renders tokens on a single line when specified 1` renders tokens on a single line when specified 1`
    renders tokens without a remove button when speci
    renders tokens without a remove button when speci renders tokens without a remove button when speci renders tokens without a remove button when speci renders tokens without a remove button when speci renders tokens without a remove button when speci renders tokens without a remove button when speci renders tokens without a remove button when speci renders tokens without a remove button when speci "container":
    renders tokens without a remove button when speci
    renders tokens without a remove button when speci renders tokens without a remove button when speci renders tokens without a remove button when speci renders tokens without a remove button when speci renders tokens without a remove button when speci renders tokens without a remove button when speci renders tokens without a remove button when speci renders with a loading indicator 1`] = `
    @@ -9050,12 +9457,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` @@ -9926,12 +10372,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -10403,12 +10870,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -10880,12 +11368,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -11358,12 +11867,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` @@ -11777,12 +12305,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -12203,12 +12750,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -12681,6 +13249,7 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    @@ -12694,12 +13263,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -13120,12 +13708,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -13192,12 +13783,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -13618,12 +14228,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -13690,12 +14303,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -14116,12 +14748,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -14188,12 +14823,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -14617,6 +15271,7 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = ` "container":
    @@ -14630,12 +15285,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` @@ -15506,12 +16200,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -15983,12 +16698,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -16460,12 +17196,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -16938,12 +17695,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` @@ -17357,12 +18133,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -17783,12 +18578,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -18261,6 +19077,7 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    @@ -18274,12 +19091,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -18700,12 +19536,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -18772,12 +19611,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -19198,12 +20056,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -19270,12 +20131,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -19696,12 +20576,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -19768,12 +20651,14 @@ exports[`TextInputWithTokens > renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = ` renders with a loading indicator 1`] = `
    renders with a loading indicator 1`] = `
    @@ -20258,6 +21160,7 @@ exports[`TextInputWithTokens > renders with tokens 1`] = `
    renders with tokens 1`] = `
    renders with tokens 1`] = ` renders with tokens 1`] = ` renders with tokens 1`] = ` renders with tokens 1`] = ` renders with tokens 1`] = ` renders with tokens 1`] = ` renders with tokens 1`] = ` renders with tokens 1`] = `
    renders with tokens 1`] = `
    renders with tokens 1`] = ` renders with tokens 1`] = ` renders with tokens 1`] = ` renders with tokens 1`] = ` renders with tokens 1`] = ` renders with tokens 1`] = ` renders with tokens 1`] = ` renders with tokens 1`] = `
    renders with tokens using a custom token componen
    renders with tokens using a custom token componen renders with tokens using a custom token componen renders with tokens using a custom token componen renders with tokens using a custom token componen renders with tokens using a custom token componen renders with tokens using a custom token componen renders with tokens using a custom token componen renders with tokens using a custom token componen
    renders with tokens using a custom token componen
    renders with tokens using a custom token componen renders with tokens using a custom token componen renders with tokens using a custom token componen renders with tokens using a custom token componen renders with tokens using a custom token componen renders with tokens using a custom token componen renders with tokens using a custom token componen renders with tokens using a custom token componen
    renders without tokens 1`] = ` @@ -21871,6 +22847,7 @@ exports[`TextInputWithTokens > renders without tokens 1`] = ` "container":
    renders without tokens 1`] = ` diff --git a/packages/react/src/TooltipV2/Tooltip.tsx b/packages/react/src/TooltipV2/Tooltip.tsx index 92a516d75c5..b74928ff703 100644 --- a/packages/react/src/TooltipV2/Tooltip.tsx +++ b/packages/react/src/TooltipV2/Tooltip.tsx @@ -349,6 +349,7 @@ export const Tooltip: ForwardRefExoticComponent< className={clsx(className, classes.Tooltip)} ref={tooltipElRef} data-direction={calculatedDirection} + data-component="Tooltip" {...rest} // Only need tooltip role if the tooltip is a description for supplementary information role={type === 'description' ? 'tooltip' : undefined} @@ -380,6 +381,7 @@ export const Tooltip: ForwardRefExoticComponent< keybindingHints.length > 1 && classes.HasMultipleHints, )} aria-hidden + data-component="Tooltip.KeybindingHintContainer" > {keybindingHints.map((hint, i) => ( diff --git a/packages/react/src/TooltipV2/__tests__/Tooltip.test.tsx b/packages/react/src/TooltipV2/__tests__/Tooltip.test.tsx index 9d914c6b007..a2755af56f0 100644 --- a/packages/react/src/TooltipV2/__tests__/Tooltip.test.tsx +++ b/packages/react/src/TooltipV2/__tests__/Tooltip.test.tsx @@ -202,3 +202,17 @@ describe('Tooltip', () => { expect(describedBy).toContain(tooltipEl.id) }) }) + +describe('Tooltip data-component attributes', () => { + it('renders Tooltip with data-component attribute', () => { + const {getByText} = HTMLRender() + const tooltip = getByText('Tooltip text') + expect(tooltip).toHaveAttribute('data-component', 'Tooltip') + }) + + it('renders Tooltip.KeybindingHintContainer with data-component attribute when keybindingHint is provided', () => { + const {container} = HTMLRender() + const keybindingHintContainer = container.querySelector('[data-component="Tooltip.KeybindingHintContainer"]') + expect(keybindingHintContainer).toBeInTheDocument() + }) +}) diff --git a/packages/react/src/internal/components/ButtonReset.tsx b/packages/react/src/internal/components/ButtonReset.tsx index b68987bb9e8..0cd0bfacff3 100644 --- a/packages/react/src/internal/components/ButtonReset.tsx +++ b/packages/react/src/internal/components/ButtonReset.tsx @@ -6,7 +6,7 @@ import {clsx} from 'clsx' */ export const Button = ({children, className, ...rest}: React.ComponentPropsWithoutRef<'button'>) => { return ( - ) diff --git a/packages/react/src/internal/components/TextInputInnerAction.tsx b/packages/react/src/internal/components/TextInputInnerAction.tsx index 22d45d0b6d0..e6d67cc63c9 100644 --- a/packages/react/src/internal/components/TextInputInnerAction.tsx +++ b/packages/react/src/internal/components/TextInputInnerAction.tsx @@ -75,7 +75,7 @@ const TextInputAction = forwardRef( } return ( - + {icon && !children && ariaLabel ? ( -> = ({children, hasLoadingIndicator, showLoadingIndicator, visualPosition, id}) => { +> = ({children, hasLoadingIndicator, showLoadingIndicator, visualPosition, id, componentPrefix = 'TextInput'}) => { const isLeading = visualPosition === 'leading' if ((!children && !hasLoadingIndicator) || (isLeading && !children && !showLoadingIndicator)) { return null @@ -24,14 +28,22 @@ const TextInputInnerVisualSlot: React.FC< if (!hasLoadingIndicator) { return ( -