Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce TrailingAction to ActionList #4634

Merged
merged 85 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from 84 commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
7f21a16
Add fixes for ActionList
TylerJDev Feb 14, 2024
fa708b1
More type fixes
TylerJDev Feb 15, 2024
e087f87
temp type fixes
TylerJDev Feb 15, 2024
76e2606
Add condition for inactive items
TylerJDev Feb 15, 2024
eab6fd9
add yet another condition
TylerJDev Feb 16, 2024
8b4f311
Merge branch 'main' into tylerjdev/action-list-a11y-fixes
TylerJDev Feb 20, 2024
614749a
chore(deps): bump changesets/action from 1.4.5 to 1.4.6 (#4282)
dependabot[bot] Feb 20, 2024
bfb8fe2
test(e2e): add e2e test for SelectPanel2 default story (#4279)
joshblack Feb 20, 2024
4b18eed
Address a few v8 color bugs (#4278)
langermank Feb 20, 2024
d13668d
chore(deps-dev): bump ip from 2.0.0 to 2.0.1 (#4291)
dependabot[bot] Feb 20, 2024
10d9866
Change how `ref` is handled
TylerJDev Feb 20, 2024
0939a90
Merge branch 'main' into tylerjdev/action-list-a11y-fixes
TylerJDev Feb 20, 2024
cc6a63c
Merge branch 'main' into tylerjdev/action-list-a11y-fixes
TylerJDev Feb 29, 2024
1cd9fb1
Merge branch 'main' into tylerjdev/action-list-a11y-fixes
TylerJDev Mar 4, 2024
3d1daf3
Change types
TylerJDev Mar 8, 2024
5999a77
Update storybook example types
TylerJDev Mar 8, 2024
9f77b09
Update types on component
TylerJDev Mar 11, 2024
087ab1f
Add another type
TylerJDev Mar 11, 2024
f4a5021
Update type in `SegmentedControl`
TylerJDev Mar 11, 2024
ba31e40
Add back `li`-only type
TylerJDev Mar 13, 2024
8e2921d
Add onto `onSelect` type
TylerJDev Mar 14, 2024
afa8d9f
Merge branch 'main' into tylerjdev/action-list-a11y-fixes
TylerJDev Mar 14, 2024
4b1f2b4
Update more types
TylerJDev Mar 14, 2024
10cd551
Type fixes for `LinkItem`
TylerJDev Mar 14, 2024
65c23c8
Changes from feedback
TylerJDev Mar 18, 2024
70659ed
Change types
TylerJDev Mar 20, 2024
1a9b3e0
Merge branch 'main' into tylerjdev/action-list-a11y-fixes
TylerJDev Mar 20, 2024
1f783fa
Merge branch 'main' into tylerjdev/action-list-a11y-fixes
TylerJDev Apr 9, 2024
6774f93
Replace `role="list"` with context
TylerJDev Apr 11, 2024
5ed547f
Merge branch 'main' into tylerjdev/action-list-a11y-fixes
TylerJDev May 17, 2024
cf5ecf9
Add feature flag to `ActionList.Item
TylerJDev May 18, 2024
2f13c76
Add back forwardedRef in cases where valid role is true
TylerJDev May 20, 2024
f32f416
Update FF name
TylerJDev May 20, 2024
84f041e
Add lint disable
TylerJDev May 20, 2024
55632b1
Update FF name
TylerJDev May 21, 2024
8bc5c15
Merge branch 'main' into tylerjdev/action-list-a11y-fixes
TylerJDev May 21, 2024
ce70779
Add changeset
TylerJDev May 22, 2024
3e8af88
Remove `list` condition
TylerJDev May 23, 2024
4c8a4de
Rename FF
TylerJDev May 23, 2024
0d076dc
Address feedback
TylerJDev May 29, 2024
d2faa9a
Add feature flag story
TylerJDev May 29, 2024
54eab93
Add new test
TylerJDev May 30, 2024
d1c1d60
Merge branch 'main' into tylerjdev/action-list-a11y-fixes
TylerJDev May 31, 2024
c4fb363
Add export
khiga8 May 31, 2024
8b70194
Updated forwardRef to be polymorphic
khiga8 May 31, 2024
da5b5a8
Spike: Add trailingAction to ActionList.Item
khiga8 May 31, 2024
6d0bc54
Updated forwardRef to be polymorphic
khiga8 May 31, 2024
da464e9
TrailingAction style adjustments
TylerJDev Jun 6, 2024
950ac4a
test(vrt): update snapshots
khiga8 Jun 6, 2024
1db6a02
Remove unused story
khiga8 Jun 6, 2024
b20fc5e
Limit TrailingAction props
khiga8 Jun 6, 2024
87bfa62
Fix missing href for as link
khiga8 Jun 6, 2024
9ad7d86
Add Inactive example
khiga8 Jun 6, 2024
c218363
Add styles for `showOnHover`
TylerJDev Jun 6, 2024
dd91f97
Fixed href overload issue
khiga8 Jun 6, 2024
dc216ad
Made sure that TrailingAction doesn't show when inactive state
khiga8 Jun 6, 2024
001fcf8
test(vrt): update snapshots
khiga8 Jun 7, 2024
a69a44c
Merge branch 'tylerjdev/action-list-a11y-fixes' into v-team-trailing-…
khiga8 Jun 7, 2024
35579ab
Add fixes for button styles
TylerJDev Jun 7, 2024
c67b0e1
Fix for flex styles
TylerJDev Jun 7, 2024
66e5405
Update storybookd descriptions
khiga8 Jun 10, 2024
96a9ef3
Add block description example
khiga8 Jun 10, 2024
6011c9b
Add tests
TylerJDev Jun 10, 2024
5028660
Remove FF from tests
TylerJDev Jun 10, 2024
0bc749c
update e2e stories
TylerJDev Jun 10, 2024
804153b
spike on allowing text (#4659)
khiga8 Jun 10, 2024
0152e99
Add visual tests
TylerJDev Jun 10, 2024
c238a6b
Update from display to visibility
khiga8 Jun 11, 2024
d0355a2
Create calm-crabs-raise.md
khiga8 Jun 11, 2024
8802b79
Disallow usage in `ActionMenu`
TylerJDev Jun 11, 2024
3362ce5
Update ActionList.test.tsx
khiga8 Jun 12, 2024
676b276
Remove hover/focus styles
TylerJDev Jun 14, 2024
ea88abe
test(vrt): update snapshots
khiga8 Jun 14, 2024
088c981
Merge branch 'main' into v-team-trailing-action-action-list
khiga8 Jun 19, 2024
294f4d2
Apply suggestions from code review
khiga8 Jun 19, 2024
4667520
Apply suggestions from code review
khiga8 Jun 19, 2024
e9147b0
Fix merge conflict
khiga8 Jun 19, 2024
3058906
Remove Hover examples
khiga8 Jun 19, 2024
29fac6a
Remove showOnHover code
khiga8 Jun 19, 2024
a84033c
Add FF const back
TylerJDev Jun 19, 2024
8a9b2d4
Remove some usage of `hoverStyles`
TylerJDev Jun 19, 2024
5539174
test(vrt): update snapshots
TylerJDev Jun 20, 2024
ec25af5
Add props to TrailingAction
TylerJDev Jun 20, 2024
b1f65d6
update types
khiga8 Jun 20, 2024
9d53def
update docs
khiga8 Jun 21, 2024
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
5 changes: 5 additions & 0 deletions .changeset/calm-crabs-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": minor
---

Introduce ActionList.TrailingAction to support secondary action on ActionList.Item
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions e2e/components/ActionList.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,4 +626,32 @@ test.describe('ActionList', () => {
})
}
})

test.describe('With Trailing Action', () => {
for (const theme of themes) {
test.describe(theme, () => {
test('default @vrt', async ({page}) => {
await visit(page, {
id: 'components-actionlist-features--with-trailing-action',
globals: {
colorScheme: theme,
},
})

// Default state
expect(await page.screenshot()).toMatchSnapshot(`ActionList.With Trailing Action.${theme}.png`)
})

test('axe @aat', async ({page}) => {
await visit(page, {
id: 'components-actionlist-features--with-trailing-action',
globals: {
colorScheme: theme,
},
})
await expect(page).toHaveNoViolations()
})
})
}
})
})
58 changes: 58 additions & 0 deletions packages/react/src/ActionList/ActionList.features.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -734,3 +734,61 @@ export const ActionListWithButtonSemantics = () => {
}

ActionListWithButtonSemantics.storyName = 'With Button Semantics (Behind feature flag)'

export const WithTrailingAction = () => {
return (
<FeatureFlags flags={{primer_react_action_list_item_as_button: true}}>
<ActionList>
<ActionList.Item>
<ActionList.LeadingVisual>
<FileDirectoryIcon />
</ActionList.LeadingVisual>
Item 1 (with default TrailingAction)
<ActionList.TrailingAction label="Expand sidebar" icon={ArrowLeftIcon} />
</ActionList.Item>
<ActionList.Item>
Item 2 (with link TrailingAction)
<ActionList.TrailingAction as="a" href="#" label="Some action 1" icon={ArrowRightIcon} />
</ActionList.Item>
<ActionList.Item>
Item 3<ActionList.Description>This is an inline description.</ActionList.Description>
<ActionList.TrailingAction label="Some action 2" icon={BookIcon} />
</ActionList.Item>
<ActionList.Item>
Item 4<ActionList.Description variant="block">This is a block description.</ActionList.Description>
Copy link
Contributor Author

@khiga8 khiga8 Jun 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realized block variant is supported for Description so added this ActionList.Description variant="block" example!

@langermank Is the Trailing Action alignment okay as is, or should it be vertically centered?

From storybook draft:
Screenshot of an ActionItem with a block description, and a TrailingAction. The TrailingAction has a default, start alignment..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference, this is with a "Trailing Visual":

Focused Action List item with a trailing visual to the right of the text, the trailing visual is positioned up vertically

It's similar with the "inactive" button. If we should modify this instance, do we need to adjust the others?

<ActionList.TrailingAction label="Some action 3" icon={BookIcon} />
</ActionList.Item>
<ActionList.Item>
Item 5<ActionList.Description variant="block">This is a block description.</ActionList.Description>
<ActionList.TrailingAction label="Some action 4" />
</ActionList.Item>
<ActionList.Item>
Item 6
<ActionList.TrailingAction href="#" as="a" label="Some action 5" />
</ActionList.Item>
<ActionList.LinkItem href="#">
LinkItem 1
<ActionList.Description>
with TrailingAction this is a long description and should not cause horizontal scroll on smaller screen
sizes
</ActionList.Description>
<ActionList.TrailingAction label="Another action" />
</ActionList.LinkItem>
<ActionList.LinkItem href="#">
LinkItem 2
<ActionList.Description>
with TrailingVisual this is a long description and should not cause horizontal scroll on smaller screen
sizes
</ActionList.Description>
<ActionList.TrailingVisual>
<TableIcon />
</ActionList.TrailingVisual>
</ActionList.LinkItem>
<ActionList.Item inactiveText="Unavailable due to an outage">
Inactive Item<ActionList.Description>With TrailingAction</ActionList.Description>
<ActionList.TrailingAction as="a" href="#" label="Some action 8" icon={ArrowRightIcon} />
</ActionList.Item>
</ActionList>
</FeatureFlags>
)
}
61 changes: 61 additions & 0 deletions packages/react/src/ActionList/ActionList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import axe from 'axe-core'
import React from 'react'
import theme from '../theme'
import {ActionList} from '.'
import {BookIcon} from '@primer/octicons-react'
import {behavesAsComponent, checkExports} from '../utils/testing'
import {BaseStyles, ThemeProvider, SSRProvider, ActionMenu} from '..'
import {FeatureFlags} from '../FeatureFlags'
Expand Down Expand Up @@ -445,4 +446,64 @@ describe('ActionList', () => {
const listItems = container.querySelectorAll('li')
expect(listItems.length).toBe(2)
})

it('should render the trailing action as a button (default)', async () => {
const {container} = HTMLRender(
<ActionList>
<ActionList.Item>
Item 1
<ActionList.TrailingAction icon={BookIcon} label="Action" />
</ActionList.Item>
</ActionList>,
)

const action = container.querySelector('button[aria-labelledby]')
expect(action).toHaveAccessibleName('Action')
})

it('should render the trailing action as a link', async () => {
const {container} = HTMLRender(
<ActionList>
<ActionList.Item>
Item 1
<ActionList.TrailingAction as="a" href="#" icon={BookIcon} label="Action" />
</ActionList.Item>
</ActionList>,
)

const action = container.querySelector('a[href="#"][aria-labelledby]')
expect(action).toHaveAccessibleName('Action')
})

it('should do action when trailing action is clicked', async () => {
const onClick = jest.fn()
const component = HTMLRender(
<ActionList>
<ActionList.Item>
Item 1
<ActionList.TrailingAction icon={BookIcon} label="Action" onClick={onClick} />
</ActionList.Item>
</ActionList>,
)

const trailingAction = await waitFor(() => component.getByRole('button', {name: 'Action'}))
fireEvent.click(trailingAction)
expect(onClick).toHaveBeenCalled()
})

it('should focus the trailing action', async () => {
HTMLRender(
<ActionList>
<ActionList.Item>
Item 1
<ActionList.TrailingAction icon={BookIcon} label="Action" />
</ActionList.Item>
</ActionList>,
)

await userEvent.tab()
expect(document.activeElement).toHaveTextContent('Item 1')
await userEvent.tab()
expect(document.activeElement).toHaveAccessibleName('Action')
})
})
50 changes: 41 additions & 9 deletions packages/react/src/ActionList/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import {Selection} from './Selection'
import {getVariantStyles, ItemContext, TEXT_ROW_HEIGHT, ListContext} from './shared'
import type {VisualProps} from './Visuals'
import {LeadingVisual, TrailingVisual} from './Visuals'
import {TrailingAction} from './TrailingAction'
import {ConditionalWrapper} from '../internal/components/ConditionalWrapper'
import {invariant} from '../utils/invariant'
import {useFeatureFlag} from '../FeatureFlags'

const LiBox = styled.li<SxProp>(sx)
Expand Down Expand Up @@ -71,14 +73,15 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
const [slots, childrenWithoutSlots] = useSlots(props.children, {
leadingVisual: LeadingVisual,
trailingVisual: TrailingVisual,
trailingAction: TrailingAction,
blockDescription: [Description, props => props.variant === 'block'],
inlineDescription: [Description, props => props.variant !== 'block'],
})

const {container, afterSelect, selectionAttribute, defaultTrailingVisual} =
React.useContext(ActionListContainerContext)

const buttonSemantics = useFeatureFlag('primer_react_action_list_item_as_button')
const buttonSemanticsFeatureFlag = useFeatureFlag('primer_react_action_list_item_as_button')

// Be sure to avoid rendering the container unless there is a default
const wrappedDefaultTrailingVisual = defaultTrailingVisual ? (
Expand Down Expand Up @@ -125,12 +128,19 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(

const itemRole = role || inferredItemRole

if (slots.trailingAction) {
invariant(!container, `ActionList.TrailingAction can not be used within a ${container}.`)
}

/** Infer the proper selection attribute based on the item's role */
let inferredSelectionAttribute: 'aria-selected' | 'aria-checked' | undefined
if (itemRole === 'menuitemradio' || itemRole === 'menuitemcheckbox') inferredSelectionAttribute = 'aria-checked'
else if (itemRole === 'option') inferredSelectionAttribute = 'aria-selected'

const itemSelectionAttribute = selectionAttribute || inferredSelectionAttribute
// Ensures ActionList.Item retains list item semantics if a valid ARIA role is applied, or if item is inactive
const listSemantics = listRole === 'listbox' || listRole === 'menu' || inactive || container === 'NavList'
const buttonSemantics = !listSemantics && !_PrivateItemWrapper && buttonSemanticsFeatureFlag

const {theme} = useTheme()

Expand All @@ -149,10 +159,32 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
},
}

const hoverStyles = {
'@media (hover: hover) and (pointer: fine)': {
':hover:not([aria-disabled]):not([data-inactive])': {
backgroundColor: `actionListItem.${variant}.hoverBg`,
color: getVariantStyles(variant, disabled, inactive).hoverColor,
boxShadow: `inset 0 0 0 max(1px, 0.0625rem) ${theme?.colors.actionListItem.default.activeBorder}`,
},
'&:focus-visible, > a.focus-visible, &:focus.focus-visible': {
outline: 'none',
border: `2 solid`,
boxShadow: `0 0 0 2px ${theme?.colors.accent.emphasis}`,
},
':active:not([aria-disabled]):not([data-inactive])': {
backgroundColor: `actionListItem.${variant}.activeBg`,
color: getVariantStyles(variant, disabled, inactive).hoverColor,
},
},
}

const listItemStyles = {
display: 'flex',
// show between 2 items
':not(:first-of-type)': {'--divider-color': theme?.colors.actionListItem.inlineDivider},
width: 'calc(100% - 16px)',
marginX: buttonSemantics ? '2' : '0',
...(buttonSemantics ? hoverStyles : {}),
Comment on lines +185 to +187
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TylerJDev, do we still need this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda, this gives consistency between the link items and the regular items when you hover over the <li>.

}

const styles = {
Expand All @@ -163,7 +195,7 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
paddingY: '6px', // custom value off the scale
lineHeight: TEXT_ROW_HEIGHT,
minHeight: 5,
marginX: listVariant === 'inset' ? 2 : 0,
marginX: listVariant === 'inset' && !buttonSemantics ? 2 : 0,
borderRadius: 2,
transition: 'background 33.333ms linear',
color: getVariantStyles(variant, disabled, inactive).color,
Expand All @@ -181,7 +213,7 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
appearance: 'none',
background: 'unset',
border: 'unset',
width: listVariant === 'inset' ? 'calc(100% - 16px)' : '100%',
width: listVariant === 'inset' && !buttonSemantics ? 'calc(100% - 16px)' : '100%',
fontFamily: 'unset',
textAlign: 'unset',
marginY: 'unset',
Expand Down Expand Up @@ -224,6 +256,7 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
borderTopWidth: showDividers ? `1px` : '0',
borderColor: 'var(--divider-color, transparent)',
},

// show between 2 items
':not(:first-of-type)': {'--divider-color': theme?.colors.actionListItem.inlineDivider},
// hide divider after dividers & group header, with higher importance!
Expand Down Expand Up @@ -268,8 +301,6 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
const inlineDescriptionId = `${itemId}--inline-description`
const blockDescriptionId = `${itemId}--block-description`
const inactiveWarningId = inactive && !showInactiveIndicator ? `${itemId}--warning-message` : undefined
// Ensures ActionList.Item retains list item semantics if a valid ARIA role is applied, or if item is inactive
const listSemantics = listRole === 'listbox' || listRole === 'menu' || inactive || container === 'NavList'

const ButtonItemWrapper = React.forwardRef(({as: Component = 'button', children, ...props}, forwardedRef) => {
return (
Expand All @@ -285,7 +316,7 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
}) as PolymorphicForwardRefComponent<React.ElementType, ActionListItemProps>

let DefaultItemWrapper = React.Fragment
if (buttonSemantics) {
if (buttonSemanticsFeatureFlag) {
khiga8 marked this conversation as resolved.
Show resolved Hide resolved
DefaultItemWrapper = listSemantics ? React.Fragment : ButtonItemWrapper
}

Expand Down Expand Up @@ -313,7 +344,7 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
let containerProps
let wrapperProps

if (buttonSemantics) {
if (buttonSemanticsFeatureFlag) {
containerProps = _PrivateItemWrapper
? {role: itemRole ? 'none' : undefined, ...props}
: // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
Expand All @@ -337,9 +368,9 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
value={{variant, disabled, inactive: Boolean(inactiveText), inlineDescriptionId, blockDescriptionId}}
>
<LiBox
ref={buttonSemantics || listSemantics ? forwardedRef : null}
ref={buttonSemanticsFeatureFlag || listSemantics ? forwardedRef : null}
sx={
buttonSemantics
buttonSemanticsFeatureFlag
? merge<BetterSystemStyleObject>(
listSemantics || _PrivateItemWrapper ? styles : listItemStyles,
listSemantics || _PrivateItemWrapper ? sxProp : {},
Expand Down Expand Up @@ -424,6 +455,7 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
{slots.blockDescription}
</Box>
</ItemWrapper>
{!inactive && Boolean(slots.trailingAction) && !container && slots.trailingAction}
</LiBox>
</ItemContext.Provider>
)
Expand Down
63 changes: 63 additions & 0 deletions packages/react/src/ActionList/TrailingAction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, {forwardRef} from 'react'
import Box from '../Box'
import {Button, IconButton} from '../Button'
import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic'

type ElementProps =
| {
as?: 'button'
href?: never
}
| {
as: 'a'
href: string
}

export type ActionListTrailingActionProps = ElementProps & {
icon?: React.ElementType
label: string
}

export const TrailingAction = forwardRef(({as = 'button', icon, label, href = null, ...props}, forwardedRef) => {
if (!icon) {
return (
<Box
data-component="ActionList.TrailingAction"
as="span"
sx={{
flexShrink: 0,
}}
>
{/* @ts-expect-error TODO: Fix this */}
<Button variant="invisible" as={as} href={href} ref={forwardedRef} {...props}>
{label}
</Button>
</Box>
)
} else {
return (
<Box
as="span"
data-component="ActionList.TrailingAction"
sx={{
flexShrink: 0,
}}
>
<IconButton
as={as}
aria-label={label}
icon={icon}
variant="invisible"
unsafeDisableTooltip={false}
tooltipDirection="w"
href={href}
// @ts-expect-error StyledButton wants both Anchor and Button refs
ref={forwardedRef}
{...props}
/>
</Box>
)
}
}) as PolymorphicForwardRefComponent<'button' | 'a', ActionListTrailingActionProps>

TrailingAction.displayName = 'ActionList.TrailingAction'
Loading
Loading