-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Refactor Dropdown component to TypeScript #45787
Conversation
Open in CodeSandbox Web Editor | VS Code | VS Code Insiders |
packages/components/src/border-control/border-control-dropdown/component.tsx
Outdated
Show resolved
Hide resolved
@@ -165,7 +165,9 @@ const BorderControlDropdown = ( | |||
? 'bottom left' | |||
: undefined; | |||
|
|||
const renderToggle = ( { onToggle = noop } ) => ( | |||
const renderToggle: DropdownComponentProps[ 'renderToggle' ] = ( { | |||
onToggle, |
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.
renderToggle
always has an onToggle
prop, so no need for the default of noop
.
- Required: No | ||
|
||
### position | ||
### `expandOnMobile`: `boolean` |
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.
The props are now alphabetized, so this diff is a little hard to read.
function useObservableState( | ||
initialState: boolean, | ||
onStateChange?: ( newState: boolean ) => void | ||
): [ boolean, ( newState: boolean ) => void ] { |
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.
You'd hope that TS would infer the return type from the return
statement below, but it doesn't.
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.
return [
state,
+ ( value: boolean ) => {
setState( value );
if ( onStateChange ) {
onStateChange( value );
}
},
+ ] as const;
☝️ The trick is to append an as const
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.
Whoa, thanks! So much cleaner to let TS infer the type.
Committed in 1f9ff85
@@ -70,8 +77,13 @@ export default function Dropdown( props ) { | |||
* case a dialog has opened, allowing focus to return when it's dismissed. | |||
*/ | |||
function closeIfFocusOutside() { | |||
if ( ! containerRef.current ) { | |||
return; |
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 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.
FWIW I don't think this is hacky. It's a proper guard 🙂 I love early returns!
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.
Thanks!
@@ -21,20 +27,27 @@ export default { | |||
renderContent: { control: { type: null } }, | |||
renderToggle: { control: { type: null } }, | |||
}, | |||
parameters: { |
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.
Without parameters
, there's an error with npm run storybook:build
:
ERR! => Failed to build the preview
ERR! Module build failed (from ./storybook/webpack/source-link-loader.js):
ERR! TypeError: wp-content/plugins/gutenberg/packages/components/src/dropdown/stories/index.tsx: Property value of ObjectProperty expected node to be of a type ["Expression","PatternLike"] but instead got 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.
Ah, you caught a bug, thank you! I prepped a fix in #46670.
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, thanks for finding that!
* ); | ||
* ``` | ||
*/ | ||
export const Dropdown = forwardRef( UnforwardedDropdown ); |
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.
Maybe I did this wrong.
But <Dropdown>
is passed a ref
prop here.
So the Dropdown needs to be wrapped in forwardRef()
to accept that ref
.
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 👍 Looks good to me.
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.
Oh thanks!
@@ -24,14 +24,13 @@ describe( 'Dropdown', () => { | |||
</button> | |||
) } | |||
renderContent={ () => <span>test</span> } | |||
popoverProps={ { 'data-testid': 'popover' } } |
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 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.
Huh, interesting! Looks like other component libraries are also experiencing this issue.
mui/material-ui#20160 (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.
Oh, good to see we're not the only ones to find that problem
/> | ||
); | ||
|
||
const button = screen.getByRole( 'button', { expanded: false } ); | ||
|
||
expect( button ).toBeVisible(); | ||
expect( screen.queryByTestId( 'popover' ) ).not.toBeInTheDocument(); | ||
expect( screen.queryByText( 'test' ) ).not.toBeInTheDocument(); |
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.
Changed because popoverProps
can't accept { 'data-testid': 'popover' }
.
Instead of doing a @ts-ignore
for that type error, I changed this.
This is testing the same thing as before: whether <Popover>
is rendering.
If <Popover>
is rendering, it will call renderContent()
, which will render <span>test</span>
.
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 call. Works for me 👍
Hi @ciampo, Hope you're doing well! It's been a while 😄 |
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.
Sorry this went unnoticed for a while, @ciampo has been away on vacation for a few weeks!
Anyway, awesome work on this one 🎉 I really appreciate your attention to detail, and the code comments you left made it easy to review.
FYI you already meet the criteria to join the Gutenberg team, which will make life easier for you when you contribute, like push branches directly to the repo, edit issues, or merge your own PRs once they're approved. See the "Teams" section in this doc if you'd like to join 🙌
@@ -21,20 +27,27 @@ export default { | |||
renderContent: { control: { type: null } }, | |||
renderToggle: { control: { type: null } }, | |||
}, | |||
parameters: { |
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, you caught a bug, thank you! I prepped a fix in #46670.
function useObservableState( | ||
initialState: boolean, | ||
onStateChange?: ( newState: boolean ) => void | ||
): [ boolean, ( newState: boolean ) => void ] { |
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.
return [
state,
+ ( value: boolean ) => {
setState( value );
if ( onStateChange ) {
onStateChange( value );
}
},
+ ] as const;
☝️ The trick is to append an as const
here ✨
@@ -134,7 +135,7 @@ export function CustomColorPickerDropdown( { | |||
popoverProps: receivedPopoverProps, | |||
...props | |||
}: CustomColorPickerDropdownProps ) { | |||
const popoverProps = useMemo( | |||
const popoverProps = useMemo< DropdownProps[ 'popoverProps' ] >( |
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 to me 👍
@@ -37,15 +37,8 @@ export type MultiplePalettesProps = PaletteProps & { | |||
colors: PaletteObject[]; | |||
}; | |||
|
|||
// TODO: should extend `Dropdown`'s props once it gets refactored to TypeScript |
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.
🎉
…own/index.tsx Co-authored-by: Lena Morita <lena@jaguchi.com>
…own/stories/index.tsx Co-authored-by: Lena Morita <lena@jaguchi.com>
…own/index.tsx Co-authored-by: Lena Morita <lena@jaguchi.com>
…own/types.ts Co-authored-by: Lena Morita <lena@jaguchi.com>
…own/types.ts Co-authored-by: Lena Morita <lena@jaguchi.com>
…own/README.md Co-authored-by: Lena Morita <lena@jaguchi.com>
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.
Really great work here, thank you! Looking forward to see more of your contributions 😄
Thanks so much, Lena! You had really good ideas, like |
What?
Convert the
Dropdown
component to TypeScriptWhy?
As part of an effort to convert
@wordpress/components
to TypeScriptHow?
Mainly by adding types to the
Dropdown
componentTesting Instructions
npm run storybook:dev
Screenshots