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..a181167a 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,19 @@ 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 type IconPickerProps = Omit, 'children'> & { + /** + * 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; +} + +export const IconPicker: React.FC = (props) => { + const { value, onChange, label = '', ...rest } = props; const icons = useIcons(); @@ -90,16 +89,6 @@ export const IconPicker = (props) => { ); }; -IconPicker.defaultProps = { - label: '', -}; - -IconPicker.propTypes = { - value: PropTypes.object.isRequired, - onChange: PropTypes.func.isRequired, - label: PropTypes.string, -}; - /** * TooltipContent * @@ -109,7 +98,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 +108,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 +135,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 +169,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 +185,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 +224,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.tsx similarity index 100% rename from components/icon-picker/index.js rename to components/icon-picker/index.tsx 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, -}; 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 +} diff --git a/package-lock.json b/package-lock.json index bd2d33e4..d8b06d11 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", @@ -6310,6 +6311,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",