-
Notifications
You must be signed in to change notification settings - Fork 1.2k
[Page] Add ReactNode as an accepted primaryAction prop value #3002
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
Conversation
|
👋 Thanks for opening your first pull request. A contributor should give feedback soon. If you haven’t already, please check out the contributing guidelines. |
|
🟢 This pull request modifies 5 files and might impact 1 other files. Details:All files potentially affected (total: 1)📄
|
src/components/Page/Page.tsx
Outdated
| title, | ||
| buttons: transformActions(appBridge, { | ||
| primaryAction, | ||
| primaryAction: isPrimaryAction(primaryAction) |
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.
@BPScott Thoughts on how I'm handing the typing here & the helper function in general? Will probably ping you for review too, but was curious on your initial thoughts.
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.
This seems alright at first glance. AppBridge integration is going away in v5. The PR has been open for an age and hopefully we'll get around to releasing it in 2 months, so I'd prioritize how to handle this stulf in non-AppBridge-land which you're doing anyway.
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.
There's a fancy way to add optional object params
{
...(isPrimaryAction(primaryAction) && {primaryAction})
}| const primary = | ||
| primaryAction && | ||
| (primaryAction.primary === undefined ? true : primaryAction.primary); | ||
| function getPrimaryActionMarkup() { |
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.
Nested function definitions tend to feel odd to me - you need to go check for any closed over variables that appear in the scope. Can this be pulled out to the top level?
| } | ||
|
|
||
| return ( | ||
| <ConditionalWrapper |
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.
If you're writing if statements above then doing an early return of if (newDesignLanguage) { return content; } might be more readable than using ConditionalWrapper
b677a49 to
cfa3aec
Compare
BPScott
left a comment
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.
Two little things, but no major concerns. I'm gonna duck out of reviewing for now as I'll be on holiday till the 4th.
| export function isPrimaryAction( | ||
| x: PrimaryAction | React.ReactNode, | ||
| ): x is PrimaryAction { | ||
| return !React.isValidElement(x) && x !== undefined; |
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.
This feels a little odd to me - this condition is more "this is not a react node" rather than "this is a primary action", which is alright for now but might get confusing if we want to pass more sorts of things into this. Is there a way to check for action's shape rather than not ReactNode's shape?
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 agree that it feels a little odd, the reason I did it this way is there are no required properties on primaryAction
| return layout.slots; | ||
| } | ||
|
|
||
| function getPrimaryActionMarkup( |
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.
A function that returns some JSX is a React component. What do you think to naming it and treating it as such? PrimaryActionMarkup() and then calling it like a react component above const primaryActionMarkup = primaryAction ? <PrimaryActionMarkup ... /> : null
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.
Ah yeah this is the right decision, i'll do this.
AndrewMusgrave
left a comment
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.
Left a few comments but otherwise looks good to me 🎉 💯
| it('renders a `ReactNode`', () => { | ||
| const primaryAction = <div>Hello</div>; | ||
|
|
||
| const header = mountWithAppProvider( | ||
| <Header {...mockProps} primaryAction={primaryAction} />, | ||
| ); | ||
|
|
||
| expect(header.contains(primaryAction)).toBeTruthy(); | ||
| }); |
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.
Another cool way this could be handled is using react-testing matchers!
const PrimaryAction = () => null;
const header = mountWithApp(
<Header {...mockProps} primaryAction={<PrimaryAction />} />,
);
expect(header).toContainReactComponent(PrimaryAction);
src/components/Page/Page.tsx
Outdated
| title, | ||
| buttons: transformActions(appBridge, { | ||
| primaryAction, | ||
| primaryAction: isPrimaryAction(primaryAction) |
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.
There's a fancy way to add optional object params
{
...(isPrimaryAction(primaryAction) && {primaryAction})
}| }, | ||
| )} | ||
| </ConditionalWrapper> | ||
| <PrimaryActionMarkup primaryAction={primaryAction} /> |
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.
We usually create an if statement rather than a component in situations like these, but the separation of concern is nice. Curious what others think?
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 this is fine.
| let content = primaryAction; | ||
| if (isPrimaryAction(primaryAction)) { |
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.
Nitpick: With an early return we don't have to wrap the entire component 😄
| let content = primaryAction; | |
| if (isPrimaryAction(primaryAction)) { | |
| let content = primaryAction; | |
| if (!isPrimaryAction(primaryAction)) return null | |
| const primary = ... |
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 never want to return null with this component. This is wrapped in the condition because it changes content to be the button instead of primaryAction (which could be a React node). The ConditionalWrapper should always be returned with whatever content is as the children.
| const {isNavigationCollapsed} = useMediaQuery(); | ||
| const {newDesignLanguage} = useFeatures(); |
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.
Curious if anyone thinks we should pass these in as 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.
Yeah I was curious about this too.
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'd leave it. It will be easier to clean-up and really doesn't become part of the API 🤷
dleroux
left a comment
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 good! ![]()
35d4ec6 to
18d21db
Compare
…m primary actions
5245417 to
090bc61
Compare
|
This is good to merge 🚀 |
|
🎉 Thanks for your contribution to Polaris React! |
WHY are these changes introduced?
The current
primaryActiononPageonly allows for one button, I'm currently working on a project that has a need for more flexible primary actions.WHAT is this pull request doing?
This PR maintains the existing
primaryActionprop, while adding the option to pass aReactNodeinstead for customizability.What other approaches were considered?
We had considered building the API/UI for
primaryActionto suit our specific need. We decided against this because we are not sure it's the right solution for everyone. This way if the provided optionPrimaryActiondoesn't cover every scenario, a custom option can be implemented.How to 🎩
🖥 Local development instructions
🗒 General tophatting guidelines
📄 Changelog guidelines
Copy-paste this code in
playground/Playground.tsx:🎩 checklist
README.mdwith documentation changes