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

Refactor Dropdown component to TypeScript #45787

Merged
merged 34 commits into from
Dec 21, 2022

Conversation

kienstra
Copy link
Contributor

@kienstra kienstra commented Nov 15, 2022

What?

Convert the Dropdown component to TypeScript

Why?

As part of an effort to convert @wordpress/components to TypeScript

How?

Mainly by adding types to the Dropdown component

Testing Instructions

  1. npm run storybook:dev
  2. Expected: The Storybook still works

Screenshots

Screen Shot 2022-12-07 at 7 50 54 PM

@codesandbox
Copy link

codesandbox bot commented Nov 15, 2022

CodeSandbox logoCodeSandbox logo  Open in CodeSandbox Web Editor | VS Code | VS Code Insiders

@@ -165,7 +165,9 @@ const BorderControlDropdown = (
? 'bottom left'
: undefined;

const renderToggle = ( { onToggle = noop } ) => (
const renderToggle: DropdownComponentProps[ 'renderToggle' ] = ( {
onToggle,
Copy link
Contributor Author

@kienstra kienstra Dec 8, 2022

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`
Copy link
Contributor Author

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 ] {
Copy link
Contributor Author

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.

Copy link
Member

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 ✨

Copy link
Contributor Author

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;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is hacky, but without it there are TS errors:

Screen Shot 2022-12-07 at 8 22 48 PM

Copy link
Member

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!

Copy link
Contributor Author

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: {
Copy link
Contributor Author

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

Copy link
Member

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.

Copy link
Contributor Author

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 );
Copy link
Contributor Author

@kienstra kienstra Dec 8, 2022

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.

Copy link
Member

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.

Copy link
Contributor Author

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' } }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This caused a TS error:
Screen Shot 2022-12-07 at 8 36 11 PM

Copy link
Member

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)

Copy link
Contributor Author

@kienstra kienstra Dec 20, 2022

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();
Copy link
Contributor Author

@kienstra kienstra Dec 8, 2022

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>.

Copy link
Member

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 👍

@kienstra
Copy link
Contributor Author

kienstra commented Dec 8, 2022

Hi @ciampo,
Could you use this PR?

Hope you're doing well! It's been a while 😄

Copy link
Member

@mirka mirka left a 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: {
Copy link
Member

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.

packages/components/src/dropdown/stories/index.tsx Outdated Show resolved Hide resolved
function useObservableState(
initialState: boolean,
onStateChange?: ( newState: boolean ) => void
): [ boolean, ( newState: boolean ) => void ] {
Copy link
Member

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 ✨

packages/components/src/dropdown/index.tsx Outdated Show resolved Hide resolved
packages/components/src/dropdown/index.tsx Outdated Show resolved Hide resolved
@@ -134,7 +135,7 @@ export function CustomColorPickerDropdown( {
popoverProps: receivedPopoverProps,
...props
}: CustomColorPickerDropdownProps ) {
const popoverProps = useMemo(
const popoverProps = useMemo< DropdownProps[ 'popoverProps' ] >(
Copy link
Member

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
Copy link
Member

Choose a reason for hiding this comment

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

🎉

packages/components/src/dropdown/README.md Outdated Show resolved Hide resolved
packages/components/src/dropdown/types.ts Outdated Show resolved Hide resolved
packages/components/src/dropdown/types.ts Outdated Show resolved Hide resolved
@kienstra
Copy link
Contributor Author

kienstra commented Dec 20, 2022

Hi @mirka,
Thanks for your great ideas, the as const one is really nice.

I committed all of your suggestions.

Also, thanks for your idea of joining the GitHub Gutenberg team. I just requested that in #core-editor.

Copy link
Member

@mirka mirka left a 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 😄

@mirka mirka merged commit 79f6e5f into WordPress:trunk Dec 21, 2022
@github-actions github-actions bot added this to the Gutenberg 14.9 milestone Dec 21, 2022
@kienstra kienstra deleted the update/dropdown-to-ts branch December 21, 2022 15:41
@kienstra
Copy link
Contributor Author

kienstra commented Dec 21, 2022

Thanks so much, Lena! You had really good ideas, like typeof Popover.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Package] Components /packages/components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants