-
Notifications
You must be signed in to change notification settings - Fork 535
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
ActionList.Item accepts a polymorphic 'as' prop #1463
Conversation
🦋 Changeset detectedLatest commit: 7cfe964 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
src/ActionList/List.tsx
Outdated
return ( | ||
<ItemComponent | ||
showDivider={props.showItemDividers} | ||
selectionVariant={props.selectionVariant} | ||
{...itemProps} | ||
key={key} | ||
sx={{...itemStyle, ...itemProps.sx}} | ||
item={item} |
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.
@dgreif after my changes, tsc
was complaining about this line, and I honestly couldn't figure out what the intent was here. Any ideas? Is it safe to remove?
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.
It's used by the callback handlers for onAction if I remember correctly. This comes back to the object equivalence issue for selection. We couldn't just pass an item "like" the one that was selected, we need the actual item that caused the ItemComponent
to get rendered in the first place. That whole system has made a lot of things more complicated then I would like them to be 😅
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.
Ohh that makes sense. I was wondering about that callback too. Any idea why the item in the callback was necessary?
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.
(that is, I can't picture the usage where this is helping simplify things...why wouldn't the user just keep their item in the closure of the callback?)
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.
I think the item in the callback was for the same reason. Higher components listening to the callback need the exact item to store which thing got selected
size-limit report 📦
|
3cb4eeb
to
e0410f1
Compare
e0410f1
to
d058ce6
Compare
src/ActionList/List.tsx
Outdated
@@ -162,7 +164,7 @@ export const List = React.forwardRef<HTMLDivElement, ListProps>((props, forwarde | |||
const renderItem = (itemProps: ItemInput, item: ItemInput, itemIndex: number) => { | |||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | |||
const ItemComponent = ('renderItem' in itemProps && itemProps.renderItem) || props.renderItem || Item | |||
const key = itemProps.key ?? itemProps.id?.toString() ?? itemIndex.toString() | |||
const key = itemProps.id?.toString() ?? itemIndex.toString() |
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.
Non-blocking: Out of curiosity, why was this change to key
necessary?
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.
Good catch — this was my mistake. TS caught that key
wasn't on ItemInput
so I removed the line intending to circle back to it. Will fix!
src/stories/ActionList.stories.tsx
Outdated
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
return React.cloneElement(child, childProps) |
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.
Does this accomplish the same result without a @ts-ignore
?
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
// @ts-ignore | |
return React.cloneElement(child, childProps) | |
return ( | |
<> | |
{React.isValidElement(child) | |
? React.cloneElement(child, childProps) | |
: null} | |
</> | |
); |
cc08895
to
c629181
Compare
docs/content/ActionList.mdx
Outdated
| showItemDividers | `boolean` | `false` | Optional. If `true` dividers will be displayed above each `ActionList.Item` which does not follow an `ActionList.Header` or `ActionList.Divider` | | ||
| Name | Type | Default | Description | | ||
| :--------------- | :---------------------------------- | :---------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | | ||
| items | `Array<ItemProps | (props: ItemProps) => JSX.Element>` | `undefined` | Required. A list of item objects conforming to the `ActionList.Item` props interface. | |
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.
/** | ||
* An item to pass back in the `onAction` callback, meant as | ||
*/ | ||
item?: ItemInput |
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.
Can you explain why we needed to add these props?
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.
These three fields were because tsc told me to :-) ... but also, they all make sense and I wonder if they only worked before because of gaps in the type coverage
src/ActionList/Item.tsx
Outdated
} | ||
}) as PolymorphicForwardRefComponent<'div', ItemProps> | ||
|
||
export {Item} |
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.
Is there a reason you wrote the export like this instead of doing: export const Item ...
on line 318
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.
Nope, will fix.
export type ItemInput = ItemProps | (Partial<ItemProps> & {renderItem: typeof Item}) | ||
type RenderItemFn = (props: ItemProps) => React.ReactElement | ||
|
||
export type ItemInput = ItemProps | ((Partial<ItemProps> & {renderItem: RenderItemFn}) & {key?: Key}) |
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 curious, what's the reason for including key
here?
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.
Same thing as the new props for item — added because tsc caught issues that it wasn't catching before. in this case, it's something we allow in an ItemInput but remove before the ItemInput becomes ItemProps.
<ActionList | ||
items={[ | ||
{ | ||
text: 'A. Vanilla action', | ||
renderItem: props => <ActionList.Item onAction={() => alert('hi?')} {...props} /> | ||
}, | ||
{ | ||
text: 'B. Vanilla link', | ||
renderItem: props => <ActionList.Item as="a" href="/about" {...props} /> | ||
}, | ||
{ | ||
text: 'C. React Router link', | ||
renderItem: props => <ActionList.Item as={ReactRouterLikeLink} to="/about" {...props} /> | ||
}, | ||
{ | ||
text: 'D. NextJS style', | ||
renderItem: props => ( | ||
<NextJSLikeLink href="/about"> | ||
<ActionList.Item as="a" {...props} /> | ||
</NextJSLikeLink> | ||
) | ||
} | ||
]} | ||
/> |
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.
Could we add a Jest test that will fail CI if ActionList.Item
doesn't render the element passed through the as
prop correctly?
I want to make sure we don't accidentally break this if we refactor ActionList.Item
(which will likely happen soon)
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 added a behavesAsComponent
test
Co-authored-by: Cole Bemis <colebemis@github.com>
Co-authored-by: Cole Bemis <colebemis@github.com>
bd41b2e
to
19c3010
Compare
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.
Looks great. Excellent work, @jfuchs ✨
Co-authored-by: Cole Bemis <colebemis@github.com>
This PR adds an
as
prop to ActionList.Item so that Primer users can put HTMLAnchor
s, Next.jsLink
s, React RouterLink
s, or whatever else in their action lists.I am adding a dependency (@radix-ui/react-polymorphic) for some type definitions. The library is:
Closes #1413
Closes #1454
Merge checklist
Take a look at the What we look for in reviews section of the contributing guidelines for more information on how we review PRs.