Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 677
Introduce TrailingAction to ActionList#4634
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
7f21a16fa708b1e087f8776e2606eab6fd98b4f311614749abfb8fe24b18eedd13668d10d98660939a90cc6a63c1cd9fb13d1daf35999a779f77b09087ab1ff4a5021ba31e408e2921dafa8d9f4b1f2b410cd55165c23c870659ed1a9b3e01f783fa6774f935ed547fcf5ecf92f13c76f32f41684f041e55632b18bc5c15ce707793e8af884c8a4de0d076dcd2faa9a54eab93d1c1d60c4fb3638b70194da5b5a86d0bc54da464e9950ac4a1db6a02b20fc5e87bfa629ad7d86c218363dd91f97dc216ad001fcf8a69a44c35579abc67b0e166e540596a9ef36011c9b50286600bc749c804153b0152e99c238a6bd0355a28802b793362ce5676b276ea88abe088c981294f4d24667520e9147b0305890629fac6aa84033c8a9b2d45539174ec25af5b1f65d69d53defFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | ||
| @@ -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 ? ( | ||
| @@ -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() | ||
| @@ -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
ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @TylerJDev, do we still need this? Member There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| } | ||
| const styles = { | ||
| @@ -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, | ||
| @@ -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', | ||
| @@ -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! | ||
| @@ -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 ( | ||
| @@ -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.
Uh oh!There was an error while loading. Please reload this page. | ||
| DefaultItemWrapper = listSemantics ? React.Fragment : ButtonItemWrapper | ||
| } | ||
| @@ -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 | ||
| @@ -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 : {}, | ||
| @@ -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> | ||
| ) | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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:

There was a problem hiding this comment.
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":
It's similar with the "inactive" button. If we should modify this instance, do we need to adjust the others?