-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Spinner: Convert component to TypeScript #41540
Conversation
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!
import type { SpinnerProps } from './types'; | ||
|
||
/** | ||
* @typedef OwnProps | ||
* | ||
* @property {string} [className] Class name | ||
*/ | ||
/** @typedef {import('react').ComponentPropsWithoutRef<'svg'> & OwnProps} Props */ | ||
|
||
/** | ||
* @param {Props} props | ||
* @return {JSX.Element} Element | ||
*/ | ||
export default function Spinner( { className, ...props } ) { | ||
export default function Spinner( { | ||
className, | ||
...props | ||
}: SpinnerProps ): JSX.Element { |
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
WordPressComponentProps
type - don't add any extra props (hence the
{}
) - add all props and attributes of the
svg
element (includingclassName
) - mark the component as non-polymorphic by not accepting the
as
prop (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 :)
const meta: Meta< | ||
typeof Spinner & { size: 'default' | 'medium' | 'large' } | ||
> = { |
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
meta
objects to setup controls and show the docs tab by default - while we're at it, we could actually remove the extra
size
control (as it's not a prop on theSpinner
component), and instead just create a separate story that illustrates how theSpinner
component 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?
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
Co-authored-by: Marco Ciampini <marco.ciampo@gmail.com>
76a4502
to
df3885f
Compare
Thank you again, @opr ! Hope to collaborate with you more often in the future :) |
What?
Converts the
Spinner
component 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 Size
story.Testing Instructions
npm run dev
and ensure no typing errors.npm run storybook:dev
and visit theSpinner
component.Custom Size
story and that the Spinner displays correctly at different sizes without the stroke width changing.Screenshots or screencast
cc @ciampo