-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Spinner: Convert component to TypeScript #41540
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
ciampo
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.
Hey @opr , thank you for your effort — it's really appreciated!
Could you also add an entry to the CHANGELOG ? Thank you!
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 we use the WordPressComponentProps utility type instead, we can avoid specifying the className prop (and therefore, adding the types.ts file altogether):
diff --git a/packages/components/src/spinner/index.tsx b/packages/components/src/spinner/index.tsx
index f1493a6c1c..ebe47628a2 100644
--- a/packages/components/src/spinner/index.tsx
+++ b/packages/components/src/spinner/index.tsx
@@ -7,12 +7,12 @@ import classNames from 'classnames';
* Internal dependencies
*/
import { StyledSpinner, SpinnerTrack, SpinnerIndicator } from './styles';
-import type { SpinnerProps } from './types';
+import type { WordPressComponentProps } from '../ui/context';
export default function Spinner( {
className,
...props
-}: SpinnerProps ): JSX.Element {
+}: WordPressComponentProps< {}, 'svg', false > ) {
return (
<StyledSpinner
className={ classNames( 'components-spinner', className ) }
diff --git a/packages/components/src/spinner/types.ts b/packages/components/src/spinner/types.ts
deleted file mode 100644
index 596dafdb7a..0000000000
--- a/packages/components/src/spinner/types.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export interface SpinnerProps extends React.ComponentPropsWithoutRef< 'svg' > {
- className: string;
-}The WordPressComponentProps< {}, 'svg', false > ) expression means:
- use the
WordPressComponentPropstype - don't add any extra props (hence the
{}) - add all props and attributes of the
svgelement (includingclassName) - mark the component as non-polymorphic by not accepting the
asprop (hence thefalse), since it should always be rendered as an SVG
We also don't need to explicitly add JSX.Element as a return value.
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 for taking the time to explain this @ciampo this information is really valuable. I'll take this into account on future PRs.
We also don't need to explicitly add JSX.Element as a return value.
Good to know, thank you :)
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.
Let's make a few changes to how this story is written:
- we need to add a snippet of code to the
metaobjects to setup controls and show the docs tab by default - while we're at it, we could actually remove the extra
sizecontrol (as it's not a prop on theSpinnercomponent), and instead just create a separate story that illustrates how theSpinnercomponent can be resized to any size, while the stroke width stays unaffected
Sample diff of the changes
diff --git a/packages/components/src/spinner/stories/index.tsx b/packages/components/src/spinner/stories/index.tsx
index e4f8d9c026..2ffbf680d8 100644
--- a/packages/components/src/spinner/stories/index.tsx
+++ b/packages/components/src/spinner/stories/index.tsx
@@ -1,51 +1,34 @@
/**
* External dependencies
*/
-import { css } from '@emotion/react';
import type { Meta, Story } from '@storybook/react';
/**
* Internal dependencies
*/
-import { useCx } from '../../utils';
import { space } from '../../ui/utils/space';
import Spinner from '../';
-const sizes = {
- default: undefined,
- medium: space( 20 ),
- large: space( 40 ),
-};
-
-const meta: Meta<
- typeof Spinner & { size: 'default' | 'medium' | 'large' }
-> = {
- title: 'Components/Spinner',
+const meta: Meta< typeof Spinner > = {
component: Spinner,
- argTypes: {
- size: {
- options: Object.keys( sizes ),
- mapping: sizes,
- control: { type: 'select' },
- },
+ title: 'Components/Spinner',
+ parameters: {
+ controls: { expanded: true },
+ docs: { source: { state: 'open' } },
},
};
export default meta;
-const Template: Story<
- typeof Spinner & { size: 'default' | 'medium' | 'large' }
-> = ( { size } ) => {
- const cx = useCx();
- const spinnerClassname = cx( css`
- width: ${ size };
- height: ${ size };
- ` );
- return <Spinner className={ spinnerClassname } />;
-};
+const Template: Story< typeof Spinner > = ( args ) => <Spinner { ...args } />;
+
+export const Default: Story< typeof Spinner > = Template.bind( {} );
+Default.args = {};
-export const Default: Story<
- typeof Spinner & { size: 'default' | 'medium' | 'large' }
-> = Template.bind( {} );
-Default.args = {
- size: 'default',
+// The width of the Spinner's border is not affected by the overall component's dimensions.
+export const CustomSize: Story< typeof Spinner > = Template.bind( {} );
+CustomSize.args = {
+ style: {
+ width: space( 20 ),
+ height: space( 20 ),
+ },
};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 much better thanks for the tip! Btw I used ComponentMeta and ComponentStory as they seem more correct since we're documenting React components, other components' stories use these types 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.
Apart from a small comment above, code changes LGTM!
Just wanted to double-check with @mirka about the props inherited from WordPressComponentProps not showing up in Storybook — is that expected?
chad1008
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.
It's expected that the inherited props (i.e. second arg of To get the docgen to work, we need a named export: // This named export is necessary for the docgen to work
export function Spinner() { /* ... */ }
export default Spinner;Then, doing that shows me this in the props table: Apparently this is suppressed by taking a |
|
Good catch, @mirka !
I wasn't expecting any props to show in the table and therefore I didn't look for the named export 😅
Yes, agreed. Let's wrap the component in a |
|
Sorry, changed my mind. Given that adding ref forwarding could be a breaking change, and we don't have a solid use case for forwarding the ref, I think it may be better to |
I'm not sure about how this can introduce a breaking change in the case of the I think we may be able to introduce |
|
Ok, yeah... I think I'm convinced that it's safe, and we might as well do it here instead of omitting. |
|
@ciampo and @mirka - I added fda74b1 - I copied other components that were using I also note that the Let me know if I should have done it differently or if this is fine. Thanks! 😁 |
|
Thank you for the update, @opr ! I had a look at your latest changes:
Here's how I would apply the feedback to this PRdiff --git a/packages/components/src/spinner/index.tsx b/packages/components/src/spinner/index.tsx
index c36778161f..60a1d787cd 100644
--- a/packages/components/src/spinner/index.tsx
+++ b/packages/components/src/spinner/index.tsx
@@ -1,17 +1,23 @@
/**
* External dependencies
*/
+import type { ForwardedRef } from 'react';
import classNames from 'classnames';
+/**
+ * WordPress dependencies
+ */
+import { forwardRef } from '@wordpress/element';
+
/**
* Internal dependencies
*/
import { StyledSpinner, SpinnerTrack, SpinnerIndicator } from './styles';
-import { WordPressComponentProps, contextConnect } from '../ui/context';
+import type { WordPressComponentProps } from '../ui/context';
-export function Spinner(
+function UnforwardedSpinner(
{ className, ...props }: WordPressComponentProps< {}, 'svg', false >,
- forwardedRef: React.ForwardedRef< any >
+ forwardedRef: ForwardedRef< any >
) {
return (
<StyledSpinner
@@ -40,6 +46,18 @@ export function Spinner(
);
}
-const ConnectedSpinner = contextConnect( Spinner, 'Spinner' );
+/**
+ * `Spinner` is a component used to notify users that their action is being processed.
+ *
+ * @example
+ * ```js
+ * import { Spinner } from '@wordpress/components';
+ *
+ * function Example() {
+ * return <Spinner />;
+ * }
+ * ```
+ */
+export const Spinner = forwardRef( UnforwardedSpinner );
-export default ConnectedSpinner;
+export default Spinner;Finally, I'm sorry if this aspect of the TypeScript migration was not super clear, but we really appreciate your help on highlighting any friction and allow us to improve the process — in fact we're working on enhancing the guidelines in #41669! |
|
@ciampo thanks for your feedback! I have applied the changes you suggested. Your comment was super helpful. |
It is no longer needed since we'll be using WordPressComponentProps
76a4502 to
df3885f
Compare
|
Thank you again, @opr ! Hope to collaborate with you more often in the future :) |


What?
Converts the
Spinnercomponent to TypeScriptPart of #35744
Why?
Brings us closer to completely typing the components package!
How?
I converted the relevant files to TS. I changed the story so there is now a default one, and one to allow editing of the height and width in a
Custom Sizestory.Testing Instructions
npm run devand ensure no typing errors.npm run storybook:devand visit theSpinnercomponent.Custom Sizestory and that the Spinner displays correctly at different sizes without the stroke width changing.Screenshots or screencast
cc @ciampo