From 0334575743f2e13ed2dfba04f8e159c41346d924 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Mon, 13 May 2024 09:32:49 +0200 Subject: [PATCH 1/6] Add react window types --- package-lock.json | 10 ++++++++++ package.json | 1 + 2 files changed, 11 insertions(+) diff --git a/package-lock.json b/package-lock.json index f8eeb52b..d3aafb9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "devDependencies": { "@10up/cypress-wp-utils": "github:10up/cypress-wp-utils#build", "@types/jest": "^29.5.12", + "@types/react-window": "^1.8.8", "@wordpress/core-data": "^6.34.0", "@wordpress/data": "^9.27.0", "@wordpress/dependency-extraction-webpack-plugin": "^5.8.0", @@ -5962,6 +5963,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-window": { + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz", + "integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/responselike": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", diff --git a/package.json b/package.json index f2a96c89..2763b431 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "devDependencies": { "@10up/cypress-wp-utils": "github:10up/cypress-wp-utils#build", "@types/jest": "^29.5.12", + "@types/react-window": "^1.8.8", "@wordpress/core-data": "^6.34.0", "@wordpress/data": "^9.27.0", "@wordpress/dependency-extraction-webpack-plugin": "^5.8.0", From 7e193ba61fd2045db9413ab7a6de7567aab26467 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Mon, 13 May 2024 09:34:10 +0200 Subject: [PATCH 2/6] Converting IconPicker to TypeScript #314 #295 --- ...tton.js => icon-picker-toolbar-button.tsx} | 43 +++--- .../{icon-picker.js => icon-picker.tsx} | 132 +++++++++++------- components/icon-picker/{icon.js => icon.tsx} | 43 +++--- components/icon-picker/index.js | 4 - components/icon-picker/index.tsx | 4 + ...-icon-picker.js => inline-icon-picker.tsx} | 38 ++--- 6 files changed, 133 insertions(+), 131 deletions(-) rename components/icon-picker/{icon-picker-toolbar-button.js => icon-picker-toolbar-button.tsx} (51%) rename components/icon-picker/{icon-picker.js => icon-picker.tsx} (62%) rename components/icon-picker/{icon.js => icon.tsx} (57%) delete mode 100644 components/icon-picker/index.js create mode 100644 components/icon-picker/index.tsx rename components/icon-picker/{inline-icon-picker.js => inline-icon-picker.tsx} (59%) diff --git a/components/icon-picker/icon-picker-toolbar-button.js b/components/icon-picker/icon-picker-toolbar-button.tsx similarity index 51% rename from components/icon-picker/icon-picker-toolbar-button.js rename to components/icon-picker/icon-picker-toolbar-button.tsx index 76b5111d..33ef706e 100644 --- a/components/icon-picker/icon-picker-toolbar-button.js +++ b/components/icon-picker/icon-picker-toolbar-button.tsx @@ -1,9 +1,8 @@ -import PropTypes from 'prop-types'; import { __ } from '@wordpress/i18n'; import { Dropdown, ToolbarButton } from '@wordpress/components'; import styled from '@emotion/styled'; -import { IconPicker } from './icon-picker'; +import { IconPicker, IconPickerProps } from './icon-picker'; import { Icon } from './icon'; const StyledIconPickerDropdown = styled(IconPicker)` @@ -12,17 +11,15 @@ const StyledIconPickerDropdown = styled(IconPicker)` height: 248px; `; -/** - * IconPickerToolbarButton - * - * @typedef IconPickerToolbarButtonProps - * @property {string} buttonLabel label - * - * @param {IconPickerToolbarButtonProps} props IconPickerToolbarButtonProps - * @returns {*} - */ -export const IconPickerToolbarButton = (props) => { - const { value, buttonLabel } = props; +interface IconPickerToolbarButtonProps extends IconPickerProps { + /** + * Label for the button + */ + buttonLabel?: string; +} + +export const IconPickerToolbarButton: React.FC = (props) => { + const { value, buttonLabel = __('Select Icon') } = props; const buttonIcon = value?.name && value?.iconSet ? : null; @@ -35,20 +32,18 @@ export const IconPickerToolbarButton = (props) => { placement: 'bottom-start', }} renderToggle={({ isOpen, onToggle }) => ( - - {buttonLabel ?? __('Select Icon')} + + { buttonLabel } )} renderContent={() => } /> ); }; - -IconPickerToolbarButton.defaultProps = { - buttonLabel: __('Select Icon'), -}; - -IconPickerToolbarButton.propTypes = { - buttonLabel: PropTypes.string, - value: PropTypes.object.isRequired, -}; diff --git a/components/icon-picker/icon-picker.js b/components/icon-picker/icon-picker.tsx similarity index 62% rename from components/icon-picker/icon-picker.js rename to components/icon-picker/icon-picker.tsx index 5818a01e..55155695 100644 --- a/components/icon-picker/icon-picker.js +++ b/components/icon-picker/icon-picker.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import styled from '@emotion/styled'; import { __ } from '@wordpress/i18n'; import { @@ -29,9 +28,9 @@ const StyledIconGrid = styled(Grid)` `; const StyledIconButton = styled(Icon)` - background-color: ${({ selected }) => (selected ? 'black' : 'white')}; - color: ${({ selected }) => (selected ? 'white' : 'black')}; - fill: ${({ selected }) => (selected ? 'white' : 'black')}; + background-color: ${({ selected } : { selected: boolean }) => (selected ? 'black' : 'white')}; + color: ${({ selected } : { selected: boolean }) => (selected ? 'white' : 'black')}; + fill: ${({ selected } : { selected: boolean }) => (selected ? 'white' : 'black')}; padding: 5px; border: none; border-radius: 4px; @@ -54,19 +53,23 @@ const StyledIconButton = styled(Icon)` } `; -/** - * IconPicker - * - * @typedef IconPickerProps - * @property {object} value value of the selected icon - * @property {Function} onChange change handler for when a new icon is selected - * @property {string} label label of the icon picker - * - * @param {IconPickerProps} props IconPicker Props - * @returns {*} React Element - */ -export const IconPicker = (props) => { - const { value, onChange, label, ...rest } = props; +export interface IconPickerProps { + /** + * Value of the selected icon + */ + value: { name: string; iconSet: string }; + /** + * Change handler for when a new icon is selected + */ + onChange: (icon: { name: string; iconSet: string }) => void; + /** + * Label of the icon picker + */ + label?: string; +} + +export const IconPicker: React.FC = (props) => { + const { value, onChange, label = '', ...rest } = props; const icons = useIcons(); @@ -90,16 +93,6 @@ export const IconPicker = (props) => { ); }; -IconPicker.defaultProps = { - label: '', -}; - -IconPicker.propTypes = { - value: PropTypes.object.isRequired, - onChange: PropTypes.func.isRequired, - label: PropTypes.string, -}; - /** * TooltipContent * @@ -109,7 +102,7 @@ IconPicker.propTypes = { * workaround for that. It will just wrap the children in a div and pass that to the * Tooltip component. */ -const TooltipContent = forwardRef(function TooltipContent(props, ref) { +const TooltipContent = forwardRef>(function TooltipContent(props, ref) { const { children } = props; return ( @@ -119,17 +112,18 @@ const TooltipContent = forwardRef(function TooltipContent(props, ref) { ); }); -/** - * IconLabel - * - * @typedef IconLabelProps - * @property {object} icon icon object - * @property {boolean} isChecked whether the icon is checked - * - * @param {IconLabelProps} props IconLabel Props - * @returns {*} React Element - */ -const IconLabel = (props) => { +interface IconLabelProps { + /** + * Icon object + */ + icon: { name: string; iconSet: string; label: string }; + /** + * Whether the icon is checked + */ + isChecked: boolean; +} + +const IconLabel: React.FC = (props) => { const { icon, isChecked } = props; return ( @@ -145,14 +139,32 @@ const IconLabel = (props) => { ); }; -IconLabel.propTypes = { - icon: PropTypes.object.isRequired, - isChecked: PropTypes.bool.isRequired, -}; - -const IconGridItem = memo((props) => { +interface IconGridItemProps { + /** + * Column index + */ + columnIndex: number; + /** + * Row index + */ + rowIndex: number; + /** + * Style object + */ + style: React.CSSProperties; + /** + * Data object + */ + data: unknown; +} + +const IconGridItem = memo((props) => { const { columnIndex, rowIndex, style, data } = props; - const { icons, selectedIcon, onChange } = data; + const { icons, selectedIcon, onChange } = data as { + icons: { name: string; iconSet: string; label: string }[]; + selectedIcon: { name: string; iconSet: string }; + onChange: (icon: { name: string; iconSet: string }) => void; + }; const index = rowIndex * 5 + columnIndex; const icon = icons[index]; const isChecked = selectedIcon?.name === icon?.name && selectedIcon?.iconSet === icon?.iconSet; @@ -161,11 +173,14 @@ const IconGridItem = memo((props) => { return null; } + // We need to cast the IconLabel to a string because types in WP are not correct + const label = as unknown as string; + return (
} + label={label} checked={isChecked} onChange={() => onChange(icon)} className="component-icon-picker__checkbox-control" @@ -174,7 +189,22 @@ const IconGridItem = memo((props) => { ); }, areEqual); -const IconGrid = (props) => { +interface IconGridProps { + /** + * List of icons + */ + icons: { name: string; iconSet: string; label: string }[]; + /** + * Selected icon + */ + selectedIcon: { name: string; iconSet: string }; + /** + * Change handler for when a new icon is selected + */ + onChange: (icon: { name: string; iconSet: string }) => void; +} + +const IconGrid: React.FC = (props) => { const { icons, selectedIcon, onChange } = props; const itemData = useMemo( @@ -198,9 +228,3 @@ const IconGrid = (props) => { ); }; - -IconGrid.propTypes = { - icons: PropTypes.array.isRequired, - selectedIcon: PropTypes.object.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/components/icon-picker/icon.js b/components/icon-picker/icon.tsx similarity index 57% rename from components/icon-picker/icon.js rename to components/icon-picker/icon.tsx index 4c1d24e9..6b2b65d9 100644 --- a/components/icon-picker/icon.js +++ b/components/icon-picker/icon.tsx @@ -1,28 +1,33 @@ -import PropTypes from 'prop-types'; import { Spinner } from '@wordpress/components'; import { forwardRef } from '@wordpress/element'; import { useIcon } from '../../hooks/use-icons'; -/** - * Icon - * - * @typedef IconProps - * @property {string } name name of the icon - * @property {string } iconSet name of the icon set - * - * @param {IconProps} props IconProps - * @returns {*} - */ -export const Icon = forwardRef(function Icon(props, ref) { +interface IconProps { + /** + * Name of the icon + */ + name: string; + /** + * Name of the icon set + */ + iconSet: string; + /** + * Click handler + */ + onClick?: () => void; +} + +export const Icon: React.FC = forwardRef(function Icon(props, ref) { const { name, iconSet, onClick, ...rest } = props; const icon = useIcon(iconSet, name); - if (!icon) { + if (!icon || Array.isArray(icon)) { + // @ts-ignore -- Types on WP seem to require onPointerEnterCapture and onPointerLeaveCapture return ; } // only add interactive props to component if a onClick handler was provided - const iconProps = {}; + const iconProps: React.JSX.IntrinsicElements['div'] = {}; if (typeof onClick === 'function') { iconProps.role = 'button'; iconProps.tabIndex = 0; @@ -35,13 +40,3 @@ export const Icon = forwardRef(function Icon(props, ref) {
); }); - -Icon.defaultProps = { - onClick: undefined, -}; - -Icon.propTypes = { - name: PropTypes.string.isRequired, - onClick: PropTypes.func, - iconSet: PropTypes.string.isRequired, -}; diff --git a/components/icon-picker/index.js b/components/icon-picker/index.js deleted file mode 100644 index 1636cafa..00000000 --- a/components/icon-picker/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export { Icon } from './icon'; -export { IconPicker } from './icon-picker'; -export { IconPickerToolbarButton } from './icon-picker-toolbar-button'; -export { InlineIconPicker } from './inline-icon-picker'; diff --git a/components/icon-picker/index.tsx b/components/icon-picker/index.tsx new file mode 100644 index 00000000..37868943 --- /dev/null +++ b/components/icon-picker/index.tsx @@ -0,0 +1,4 @@ +export { Icon } from './icon'; +export { IconPicker } from './icon-picker'; +export { IconPickerToolbarButton } from 'components/icon-picker/icon-picker-toolbar-button'; +export { InlineIconPicker } from 'components/icon-picker/inline-icon-picker'; diff --git a/components/icon-picker/inline-icon-picker.js b/components/icon-picker/inline-icon-picker.tsx similarity index 59% rename from components/icon-picker/inline-icon-picker.js rename to components/icon-picker/inline-icon-picker.tsx index 64513f56..350c9e68 100644 --- a/components/icon-picker/inline-icon-picker.js +++ b/components/icon-picker/inline-icon-picker.tsx @@ -1,9 +1,8 @@ -import PropTypes from 'prop-types'; import styled from '@emotion/styled'; import { Dropdown } from '@wordpress/components'; import { useCallback } from '@wordpress/element'; -import { IconPicker } from './icon-picker'; +import { IconPicker, IconPickerProps } from './icon-picker'; import { Icon } from './icon'; const StyledIconPickerDropdown = styled(IconPicker)` @@ -12,36 +11,29 @@ const StyledIconPickerDropdown = styled(IconPicker)` height: 248px; `; -/** - * InlineIconPicker - * - * @typedef InlineIconPickerProps - * @property {string} buttonLabel label - * - * @param {InlineIconPickerProps} props InlineIconPickerProps - * @returns {*} - */ -export const InlineIconPicker = (props) => { +export const InlineIconPicker: React.FC = (props) => { const { value, ...rest } = props; const IconButton = useCallback( - ({ onToggle }) => ( + ({ onToggle }: { + onToggle: () => void; + }) => ( ), [value, rest], ); - IconButton.propTypes = { - onToggle: PropTypes.func.isRequired, - }; - return ; }; -InlineIconPicker.propTypes = { - value: PropTypes.object.isRequired, -}; +interface InlineIconPickerProps extends IconPickerProps { + /** + * Render function for the toggle button + * @param props + */ + renderToggle: (props: { onToggle: () => void }) => React.JSX.Element; +} -export const IconPickerDropdown = (props) => { +export const IconPickerDropdown: React.FC = (props) => { const { renderToggle, ...iconPickerProps } = props; return ( { /> ); }; - -IconPickerDropdown.propTypes = { - renderToggle: PropTypes.func.isRequired, -}; From 29b4659e58b44a1ce96ef73b9b48df544eb1f234 Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Mon, 13 May 2024 09:41:53 +0200 Subject: [PATCH 3/6] Fixing broken paths --- components/icon-picker/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/icon-picker/index.tsx b/components/icon-picker/index.tsx index 37868943..1636cafa 100644 --- a/components/icon-picker/index.tsx +++ b/components/icon-picker/index.tsx @@ -1,4 +1,4 @@ export { Icon } from './icon'; export { IconPicker } from './icon-picker'; -export { IconPickerToolbarButton } from 'components/icon-picker/icon-picker-toolbar-button'; -export { InlineIconPicker } from 'components/icon-picker/inline-icon-picker'; +export { IconPickerToolbarButton } from './icon-picker-toolbar-button'; +export { InlineIconPicker } from './inline-icon-picker'; From da1cb5f10f7baf3244e24290c546396ff5e8b5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Ka=CC=88gy?= Date: Wed, 15 May 2024 11:30:16 +0200 Subject: [PATCH 4/6] add ts-check to icon block example --- example/src/blocks/icon-picker-example/edit.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/example/src/blocks/icon-picker-example/edit.js b/example/src/blocks/icon-picker-example/edit.js index a5581aaf..72e4de85 100644 --- a/example/src/blocks/icon-picker-example/edit.js +++ b/example/src/blocks/icon-picker-example/edit.js @@ -1,3 +1,5 @@ +// @ts-check +import React from 'react'; import { ToolbarGroup, PanelBody } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { InspectorControls, BlockControls, useBlockProps } from '@wordpress/block-editor'; From 047208af2daed6b367612771dd6146f01ad5f5cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Ka=CC=88gy?= Date: Wed, 15 May 2024 12:15:55 +0200 Subject: [PATCH 5/6] fix use unknown instead of any for unknown additional props --- components/author/index.tsx | 12 ++++++------ components/post-author/index.tsx | 2 +- components/post-date/index.tsx | 2 +- components/post-meta/index.tsx | 2 +- hooks/use-block-parent-attributes/index.ts | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/components/author/index.tsx b/components/author/index.tsx index 5e324fb9..d6b51f2e 100644 --- a/components/author/index.tsx +++ b/components/author/index.tsx @@ -4,7 +4,7 @@ import { useAuthor } from './context'; interface NameProps { tagName?: keyof JSX.IntrinsicElements; - [key: string]: any; + [key: string]: unknown; } export const Name: React.FC = (props) => { @@ -22,7 +22,7 @@ export const Name: React.FC = (props) => { interface FirstNameProps { tagName?: keyof JSX.IntrinsicElements; - [key: string]: any; + [key: string]: unknown; } export const FirstName: React.FC = (props) => { @@ -34,7 +34,7 @@ export const FirstName: React.FC = (props) => { interface LastNameProps { tagName?: keyof JSX.IntrinsicElements; - [key: string]: any; + [key: string]: unknown; } export const LastName: React.FC = (props) => { @@ -54,7 +54,7 @@ function useDefaultAvatar() { } interface AvatarProps { - [key: string]: any; + [key: string]: unknown; } export const Avatar: React.FC = (props) => { @@ -71,7 +71,7 @@ export const Avatar: React.FC = (props) => { interface BioProps { tagName?: keyof JSX.IntrinsicElements; - [key: string]: any; + [key: string]: unknown; } export const Bio: React.FC = (props) => { @@ -82,7 +82,7 @@ export const Bio: React.FC = (props) => { }; interface EmailProps { - [key: string]: any; + [key: string]: unknown; } export const Email: React.FC = (props) => { diff --git a/components/post-author/index.tsx b/components/post-author/index.tsx index b25e8c17..282f2423 100644 --- a/components/post-author/index.tsx +++ b/components/post-author/index.tsx @@ -10,7 +10,7 @@ import type { Author } from '../author/context'; interface PostAuthorProps { children?: React.ReactNode | ((author: Author) => React.ReactNode); - [key: string]: any; + [key: string]: unknown; } export const PostAuthor: React.FC & { diff --git a/components/post-date/index.tsx b/components/post-date/index.tsx index 602d6e1a..315b1e6e 100644 --- a/components/post-date/index.tsx +++ b/components/post-date/index.tsx @@ -41,7 +41,7 @@ interface PostDateProps { /** * Remaining props to pass to the time element. */ - [key: string]: any; + [key: string]: unknown; } export const PostDate: React.FC = ({ placeholder = __('No date set', 'tenup'), format, ...rest}) => { diff --git a/components/post-meta/index.tsx b/components/post-meta/index.tsx index 49e25867..cfdbabc9 100644 --- a/components/post-meta/index.tsx +++ b/components/post-meta/index.tsx @@ -18,7 +18,7 @@ interface PostMetaProps { /** * Additional props to pass to the component. */ - [key: string]: any; + [key: string]: unknown; } export const PostMeta: React.FC & { diff --git a/hooks/use-block-parent-attributes/index.ts b/hooks/use-block-parent-attributes/index.ts index ac450088..57e17aff 100644 --- a/hooks/use-block-parent-attributes/index.ts +++ b/hooks/use-block-parent-attributes/index.ts @@ -18,7 +18,7 @@ export function useBlockParentAttributes() { const { updateBlockAttributes } = useDispatch(blockEditorStore); - const setParentAttributes = (attributes: {[key: string]: any}) => { + const setParentAttributes = (attributes: {[key: string]: unknown}) => { updateBlockAttributes(parentBlockClientId, attributes); }; From 93138c769ca2dee3e9766a40385f76a9a8f8657b Mon Sep 17 00:00:00 2001 From: Antonio Laguna Date: Mon, 20 May 2024 17:19:10 +0200 Subject: [PATCH 6/6] Solidifying types --- components/icon-picker/icon-picker.tsx | 6 +----- example/src/blocks/icon-picker-example/edit.tsx | 7 +++++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/components/icon-picker/icon-picker.tsx b/components/icon-picker/icon-picker.tsx index 55155695..a181167a 100644 --- a/components/icon-picker/icon-picker.tsx +++ b/components/icon-picker/icon-picker.tsx @@ -53,7 +53,7 @@ const StyledIconButton = styled(Icon)` } `; -export interface IconPickerProps { +export type IconPickerProps = Omit, 'children'> & { /** * Value of the selected icon */ @@ -62,10 +62,6 @@ export interface IconPickerProps { * Change handler for when a new icon is selected */ onChange: (icon: { name: string; iconSet: string }) => void; - /** - * Label of the icon picker - */ - label?: string; } export const IconPicker: React.FC = (props) => { diff --git a/example/src/blocks/icon-picker-example/edit.tsx b/example/src/blocks/icon-picker-example/edit.tsx index 40e3a292..11570b00 100644 --- a/example/src/blocks/icon-picker-example/edit.tsx +++ b/example/src/blocks/icon-picker-example/edit.tsx @@ -18,7 +18,10 @@ export function BlockEdit(props) { const { icon } = attributes; const blockProps = useBlockProps(); - const handleIconSelection = value => setAttributes({icon: { name: value.name, iconSet: value.iconSet }}); + const handleIconSelection = (value: { + name: string; + iconSet: string; + }) => setAttributes({icon: { name: value.name, iconSet: value.iconSet }}); return ( <> @@ -44,4 +47,4 @@ export function BlockEdit(props) {
) -} \ No newline at end of file +}