diff --git a/INSTRUCTIONS.md b/INSTRUCTIONS.md new file mode 100644 index 0000000..4557616 --- /dev/null +++ b/INSTRUCTIONS.md @@ -0,0 +1,59 @@ +# Edit Component + +A block's `Edit` component is what renders in the block editor. + +It's a React component that's similar to non-WordPress React components. + +Though WordPress has custom APIs and hooks. + +WordPress also has a big component library in [@wordpress/components](https://github.com/WordPress/gutenberg/tree/trunk/packages/components) that we'll use. + +Most of the time, you won't need to write a custom control. That component library will usually have what you need. + +We'll render the block controls in [InspectorControls](https://github.com/WordPress/gutenberg/tree/57da3c91a166d917a2a9de98177be9c3dfe07ee5/packages/block-editor/src/components/inspector-controls). + +By rendering components inside ``, they appear in the Inspector. + +## Exercise + +You're going to write the `Edit` component. + +It's what that the user sees in the block editor. + +This component is very interactive. + +Its function is to update the block attributes and markup as the user edits the block. + +One of its `props` is `setAttributes`: + +```jsx +export default function Edit( { + attributes, + setAttributes +} ) { +``` + +That sets an attribute to a new value. + +For example, when the user selects a color, it can set the `color` attribute: + +```jsx +onChange={ ( newValue ) => + setAttributes( { color: newValue } ) +} +``` + +That handler accepts a `newValue` parameter, which is the value that the user just selected. + +And `setAttributes` accepts an object. + +That object only needs one property, which is the name of the attribute: + +```jsx +setAttributes( { color: newValue } ) +``` + +### File +- [js/src/edit.exercise.js](js/src/edit.exercise.js) + +[Solution video](https://bit.ly/3HJCDVg) diff --git a/js/src/edit.exercise.js b/js/src/edit.exercise.js new file mode 100644 index 0000000..9b35416 --- /dev/null +++ b/js/src/edit.exercise.js @@ -0,0 +1,61 @@ +/** + * External dependencies + */ +import * as React from 'react'; + + /** + * WordPress dependencies + */ +// @ts-ignore The declaration file is outdated. +// 🚧 Also import useBlockProps from '@wordpress/block-editor' +import { InspectorControls } from '@wordpress/block-editor'; +// 🚧 Also import a component from '@wordpress/components' to choose a color, and another component to choose a number: https://github.com/WordPress/gutenberg/tree/trunk/packages/components/src +import { PanelBody } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; + +/** + * The component for the editor. + * + * @param {{ + * attributes: import('./').Attributes, + * setAttributes: Function + * }} props + */ +export default function Edit( { attributes, setAttributes } ) { + const blockProps = // 🚧 Call useBlockProps. + const { colors } = useSelect( + // 🚧 Add an anonymous function here as an argument of useSelect(). + // The function should accept select as a parameter. + // Then, it should get the data store with: + // select( 'core/block-editor' ) + // Call .getSettings() on that data store, and return that. + ); + + return
+

{ __( 'This is a placeholder for the Progress Indicator block', 'progress-indicator' ) }

+ + { /* + 🚧 Add 2 components here, and give each of them a title prop that's translated with __(). + + Inside the 1st + Add the component to choose colors that you imported above from '@wordpress/components'. + That will be for attributes.color. + Inside the 2nd + Add 2 components to choose numbers that you imported above from '@wordpress/components'. + Those will be for attribute.currentStep and attributes.numberOfSteps. + + The value prop of those components should be the attribute for it. + For example, the value of the color component should be attributes.color. + The onChange prop should be like: + + onChange={ ( newValue ) => + setAttributes( { color: newValue } ) + } + + The setAttributes() call should have an object with its property being the attribute name. + For example, if the attribute name is color, it would be setAttributes( { color: newValue } ) + */ } + +
; +} diff --git a/js/src/edit.final.js b/js/src/edit.final.js new file mode 100644 index 0000000..b49d137 --- /dev/null +++ b/js/src/edit.final.js @@ -0,0 +1,63 @@ +/** + * External dependencies + */ +import * as React from 'react'; + +/** + * WordPress dependencies + */ +// @ts-ignore The declaration file is outdated. +import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; +import { ColorPalette, PanelBody, RangeControl } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; + +/** + * The component for the editor. + * + * @param {{ + * attributes: import('./index').Attributes, + * setAttributes: Function + * }} props + */ +export default function Edit( { attributes, setAttributes } ) { + const blockProps = useBlockProps(); + const { colors } = useSelect( + ( select ) => select( 'core/block-editor' ).getSettings() + ); + + return
+

{ __( 'This is a placeholder for the Progress Indicator block', 'progress-indicator' ) }

+ + + + setAttributes( { color: newValue } ) + } + /> + + + + setAttributes( { currentStep: Number( newValue ) } ) + } + min={ 1 } + max={ attributes.numberOfSteps } + /> + + setAttributes( { numberOfSteps: Number( newValue ) } ) + } + min={ 1 } + max={ 10 } + /> + + +
; +} diff --git a/js/src/edit.js b/js/src/edit.js index 2bbce03..863a366 100644 --- a/js/src/edit.js +++ b/js/src/edit.js @@ -1,68 +1,7 @@ -/** - * External dependencies - */ -import * as React from 'react'; - -/** - * WordPress dependencies - */ -import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; -import { ColorPalette, PanelBody, RangeControl } from '@wordpress/components'; -import { useSelect } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; - /** * Internal dependencies */ -import ProgressIndicator from './progress-indicator'; - -/** - * The component for the editor. - * - * @param {{ - * attributes: import('./index').Attributes, - * setAttributes: Function - * }} props - * @return {React.ReactElement} The component. - */ -export default function Edit( { attributes, setAttributes } ) { - const blockProps = useBlockProps(); - const { colors } = useSelect( - ( select ) => select( 'core/block-editor' ).getSettings() - ); +import Edit from './edit.exercise'; +// import Edit from './edit.final'; - return
- - - - - setAttributes( { color: newValue } ) - } - /> - - - - setAttributes( { currentStep: Number( newValue ) } ) - } - min={ 1 } - max={ attributes.numberOfSteps } - /> - - setAttributes( { numberOfSteps: Number( newValue ) } ) - } - min={ 1 } - max={ 10 } - /> - - -
; -} +export default Edit; diff --git a/js/src/progress-indicator.js b/js/src/progress-indicator.js index 2b373b4..9cdddc1 100644 --- a/js/src/progress-indicator.js +++ b/js/src/progress-indicator.js @@ -8,7 +8,6 @@ import tinycolor2 from 'tinycolor2'; * The progress indicator component. * * @param {{attributes: import('./').Attributes}} props - * @return {React.ReactElement} The component. */ export default function ProgressIndicator( { attributes } ) { const color = tinycolor2( attributes.color ); diff --git a/js/src/progress-indicator.test.js b/js/src/progress-indicator.test.js deleted file mode 100644 index b93d704..0000000 --- a/js/src/progress-indicator.test.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * External dependencies - */ -import '@testing-library/jest-dom/extend-expect'; -import { render, screen } from '@testing-library/react'; - -/** - * Internal dependencies - */ -import ProgressIndicator from './progress-indicator'; - -test( 'ProgressIndicator with current step of 2/5', () => { - render( - - ); - - it.each( [ 2, 3, 4, 5 ], ( number ) => { - expect( screen.getByText( number ) ).toBeInTheDocument(); - } ); - - expect( screen.getAllByRole( 'img' ).length ).toEqual( 1 ); -} ); - -test( 'ProgressIndicator with current step of 5/5', () => { - render( - - ); - - expect( screen.queryByText( 4 ) ).not.toBeInTheDocument(); - expect( screen.getByText( 5 ) ).toBeInTheDocument(); - expect( screen.getAllByRole( 'img' ).length ).toEqual( 4 ); -} ); diff --git a/js/src/save.js b/js/src/save.js index 83cb567..60d0fa8 100644 --- a/js/src/save.js +++ b/js/src/save.js @@ -6,23 +6,11 @@ import * as React from 'react'; /** * WordPress dependencies */ -import { useBlockProps } from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; -/** - * Internal dependencies - */ -import ProgressIndicator from './progress-indicator'; - -/** - * The component to save the markup. - * - * @param {{attributes: import('./').Attributes}} props - * @return {React.ReactElement} The component. - */ -export default function Save( { attributes } ) { - const blockProps = useBlockProps.save(); - - return
- -
; +/** The component to save the markup. */ +export default function Save() { + return + { __( 'This is a placeholder for the Progress Indicator block', 'progress-indicator' ) } + ; }