Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions packages/react/src/ActionList/TrailingAction.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type React from 'react'
import {forwardRef} from 'react'
import {forwardRef, useContext} from 'react'
import {Button, IconButton} from '../Button'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'
import {clsx} from 'clsx'
import classes from './ActionList.module.css'
import {ActionListContainerContext} from './ActionListContainerContext'

type ElementProps =
| {
Expand All @@ -30,6 +31,17 @@

export const TrailingAction = forwardRef(
({as = 'button', icon, label, href = null, className, style, loading, ...props}, forwardedRef) => {
// Use context from ActionList
const {selectionVariant} = useContext(ActionListContainerContext)
// TODO: Rename
const withinMenu = selectionVariant === 'single' || selectionVariant === 'multiple'

if (withinMenu) {
console.log(

Check failure on line 40 in packages/react/src/ActionList/TrailingAction.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
'Warning: ActionList.TrailingAction should not be used within selectable ActionLists. Please remove it to avoid unexpected behavior.',
)
}

return (
<span className={clsx(className, classes.TrailingAction)} style={style}>
{icon ? (
Expand All @@ -38,13 +50,15 @@
aria-label={label}
icon={icon}
variant="invisible"
tooltipDirection="w"
tooltipDirection="e"
href={href}
loading={loading}
data-loading={Boolean(loading)}
// @ts-expect-error StyledButton wants both Anchor and Button refs
ref={forwardedRef}
className={classes.TrailingActionButton}
aria-hidden={!withinMenu ? 'true' : undefined}
tabIndex={!withinMenu ? -1 : undefined}
{...props}
/>
) : (
Expand All @@ -57,6 +71,7 @@
data-loading={Boolean(loading)}
ref={forwardedRef}
className={classes.TrailingActionButton}
aria-hidden={withinMenu ? 'true' : undefined}
{...props}
>
{label}
Expand Down
4 changes: 3 additions & 1 deletion packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,9 @@ export const AnchoredOverlay: React.FC<React.PropsWithChildren<AnchoredOverlayPr
})
useFocusTrap({containerRef: overlayRef, disabled: !open || !position, ...focusTrapSettings})

const showXIcon = onClose && variant.narrow === 'fullscreen' && displayCloseButton
const showXIcon =
(onClose && variant.narrow === 'fullscreen' && displayCloseButton) || (onClose && displayCloseButton)

const XButtonAriaLabelledBy = closeButtonProps['aria-labelledby']
const XButtonAriaLabel = closeButtonProps['aria-label']

Expand Down
4 changes: 4 additions & 0 deletions packages/react/src/Combobox/Combobox.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.TextInput {
margin: var(--base-size-8) var(--base-size-8) 0;
width: calc(100% - var(--base-size-16));
}
52 changes: 52 additions & 0 deletions packages/react/src/Combobox/Combobox.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type {Meta, StoryFn} from '@storybook/react-vite'
import type {ComponentProps} from '../utils/types'
import {Combobox} from '.'

export default {
title: 'Components/Combobox',
component: Combobox,
} as Meta<ComponentProps<typeof Combobox>>

const options = [
{label: 'Apple', id: '1'},
{label: 'Banana', id: '2'},
{label: 'Cherry', id: '3'},
{label: 'Date', id: '4'},
{label: 'Elderberry', id: '5'},
{label: 'Fig', id: '6'},
{label: 'Grape', id: '7'},
]

export const Playground: StoryFn<ComponentProps<typeof Combobox>> = args => <Combobox options={options} {...args} />

Playground.argTypes = {
options: {
control: 'object',
},
}

export const Default = () => {
return <Combobox label="Choose a fruit" options={options} />
}

const groupedOptions = [
{label: 'Apple', id: '1', group: 'Fruits'},
{label: 'Banana', id: '2', group: 'Fruits'},
{label: 'Carrot', id: '3', group: 'Vegetables'},
{label: 'Broccoli', id: '4', group: 'Vegetables'},
]

export const WithGroups = () => {
return <Combobox label="Choose an item" options={groupedOptions} />
}

const optionsWithSelected = [
{label: 'Apple', id: '1'},
{label: 'Banana', id: '2', selected: true},
{label: 'Cherry', id: '3'},
{label: 'Date', id: '4'},
]

export const WithSelection = () => {
return <Combobox label="Choose a fruit" options={optionsWithSelected} />
}
68 changes: 68 additions & 0 deletions packages/react/src/Combobox/Combobox.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {describe, expect, it} from 'vitest'
import {render} from '@testing-library/react'
import {Combobox} from '../Combobox'

describe('Combobox', () => {
it('should support `className` on the root element', () => {
const Element = () => <Combobox className={'test-class-name'} />
expect(render(<Element />).container.firstChild).toHaveClass('test-class-name')

Check failure on line 8 in packages/react/src/Combobox/Combobox.test.tsx

View workflow job for this annotation

GitHub Actions / test (react-19)

src/Combobox/Combobox.test.tsx > Combobox > should support `className` on the root element

Error: expect(element).toHaveClass("test-class-name") Expected the element to have class: test-class-name Received: prc-Combobox-TextInput-a-q0Z prc-Combobox-TextInput-a-q0Z TextInput-wrapper prc-components-TextInputWrapper--nxNz prc-components-TextInputBaseWrapper-fStx7 ❯ toHaveClass src/Combobox/Combobox.test.tsx:8:53
})

it('renders with label', () => {
const {getByText} = render(<Combobox label="Test Label" />)
expect(getByText('Test Label')).toBeInTheDocument()

Check failure on line 13 in packages/react/src/Combobox/Combobox.test.tsx

View workflow job for this annotation

GitHub Actions / test (react-19)

src/Combobox/Combobox.test.tsx > Combobox > renders with label

TestingLibraryElementError: Unable to find an element with the text: Test Label. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. Ignored nodes: comments, script, style <body> <div> <span aria-busy="false" class="prc-Combobox-TextInput-a-q0Z prc-Combobox-TextInput-a-q0Z TextInput-wrapper prc-components-TextInputWrapper--nxNz prc-components-TextInputBaseWrapper-fStx7" > <input aria-autocomplete="list" aria-expanded="false" aria-haspopup="true" class="prc-components-Input-sKHcw" data-component="input" id="_r_4_" placeholder="Search..." role="combobox" tabindex="0" type="text" /> </span> </div> </body> ❯ getByText src/Combobox/Combobox.test.tsx:13:11
})

it('renders Combobox.Input with correct role', () => {
const {container} = render(<Combobox.Input />)
const input = container.querySelector('input')
expect(input).toHaveAttribute('role', 'combobox')
expect(input).toHaveAttribute('aria-autocomplete', 'list')
})

it('renders Combobox.List with correct role', () => {
const {container} = render(
<Combobox.List>
<Combobox.Option>Option 1</Combobox.Option>
</Combobox.List>,
)
const list = container.querySelector('ul')
expect(list).toHaveAttribute('role', 'listbox')
})

it('renders Combobox.Option with correct role', () => {
const {getByRole} = render(<Combobox.Option>Test Option</Combobox.Option>)
expect(getByRole('option')).toBeInTheDocument()
expect(getByRole('option')).toHaveTextContent('Test Option')
})

it('renders selected option with aria-selected', () => {
const {getByRole} = render(<Combobox.Option selected>Selected Option</Combobox.Option>)
expect(getByRole('option')).toHaveAttribute('aria-selected', 'true')
})

it('renders complete combobox structure', () => {
const {container, getByPlaceholderText} = render(
<Combobox label="Test">
<Combobox.Input placeholder="Search..." />
<Combobox.List>
<Combobox.Option>Option 1</Combobox.Option>
<Combobox.Option>Option 2</Combobox.Option>
</Combobox.List>
</Combobox>,
)
expect(getByPlaceholderText('Search...')).toBeInTheDocument()
expect(container.querySelectorAll('[role="option"]')).toHaveLength(2)

Check failure on line 55 in packages/react/src/Combobox/Combobox.test.tsx

View workflow job for this annotation

GitHub Actions / test (react-19)

src/Combobox/Combobox.test.tsx > Combobox > renders complete combobox structure

AssertionError: expected to have a length of 2 but got +0 - Expected + Received - 2 + 0 ❯ toHaveLength src/Combobox/Combobox.test.tsx:55:58
})

it('renders grouped options', () => {
const {getByText} = render(
<Combobox.List>
<Combobox.Group groupLabel="Group 1">
<Combobox.Option>Option 1</Combobox.Option>
</Combobox.Group>
</Combobox.List>,
)
expect(getByText('Group 1')).toBeInTheDocument()
})
})
Loading
Loading