Skip to content
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

components: Convert Select to TypeScript #33784

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/components/src/base-control/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
* That element should be passed as a child.
* @property {import('react').ReactNode} help If this property is added, a help text will be
* generated using help property as the content.
* @property {import('react').ReactNode} label If this property is added, a label will be generated
* @property {import('react').ReactNode} [label] If this property is added, a label will be generated
* using label property as the content.
* @property {boolean} [hideLabelFromVision] If true, the label will only be visible to screen readers.
* @property {string} [className] The class that will be added with "components-base-control" to the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/**
* External dependencies
*/
import { isEmpty, noop } from 'lodash';
import { isEmpty, noop, omit } from 'lodash';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realised this — can the omit import be removed now? I don't see any further usages.
We may want to apply this change directly in #33696

import classNames from 'classnames';
// eslint-disable-next-line no-restricted-imports
import type { ChangeEvent, FocusEvent, Ref } from 'react';

/**
* WordPress dependencies
Expand All @@ -16,15 +18,39 @@ import { Icon, chevronDown } from '@wordpress/icons';
*/
import BaseControl from '../base-control';
import InputBase from '../input-control/input-base';
import type { InputBaseProps, LabelPosition } from '../input-control/types';
import { Select, DownArrowWrapper } from './styles/select-control-styles';
import type { Size } from './types';
import type { PolymorphicComponentProps } from '../ui/context';

function useUniqueId( idProp ) {
function useUniqueId( idProp?: string ) {
const instanceId = useInstanceId( SelectControl );
const id = `inspector-select-control-${ instanceId }`;

return idProp || id;
}

export interface SelectControlProps extends Omit< InputBaseProps, 'children' > {
help?: string;
hideLabelFromVision?: boolean;
multiple?: boolean;
onBlur?: ( event: FocusEvent< HTMLSelectElement > ) => void;
onFocus?: ( event: FocusEvent< HTMLSelectElement > ) => void;
onChange?: (
value: string | string[],
extra?: { event?: ChangeEvent< HTMLSelectElement > }
) => void;
options?: {
label: string;
value: string;
id?: string;
disabled?: boolean;
}[];
size?: Size;
value?: string | string[];
labelPosition?: LabelPosition;
}
ciampo marked this conversation as resolved.
Show resolved Hide resolved

function SelectControl(
{
className,
Expand All @@ -42,8 +68,8 @@ function SelectControl(
value: valueProp,
labelPosition = 'top',
...props
},
ref
}: PolymorphicComponentProps< SelectControlProps, 'select', false >,
ref: Ref< HTMLSelectElement >
) {
const [ isFocused, setIsFocused ] = useState( false );
const id = useUniqueId( idProp );
Expand All @@ -52,17 +78,17 @@ function SelectControl(
// Disable reason: A select with an onchange throws a warning
if ( isEmpty( options ) ) return null;

const handleOnBlur = ( event ) => {
const handleOnBlur = ( event: FocusEvent< HTMLSelectElement > ) => {
onBlur( event );
setIsFocused( false );
};

const handleOnFocus = ( event ) => {
const handleOnFocus = ( event: FocusEvent< HTMLSelectElement > ) => {
onFocus( event );
setIsFocused( true );
};

const handleOnChange = ( event ) => {
const handleOnChange = ( event: ChangeEvent< HTMLSelectElement > ) => {
if ( multiple ) {
const selectedOptions = [ ...event.target.options ].filter(
( { selected } ) => selected
Expand All @@ -79,7 +105,7 @@ function SelectControl(

/* eslint-disable jsx-a11y/no-onchange */
return (
<BaseControl help={ help }>
<BaseControl help={ help } id={ id }>
<InputBase
className={ classes }
disabled={ disabled }
Expand All @@ -89,15 +115,17 @@ function SelectControl(
label={ label }
size={ size }
suffix={
<DownArrowWrapper>
<Icon icon={ chevronDown } size={ 18 } />
</DownArrowWrapper>
props.suffix || (
ciampo marked this conversation as resolved.
Show resolved Hide resolved
<DownArrowWrapper>
<Icon icon={ chevronDown } size={ 18 } />
</DownArrowWrapper>
)
}
prefix={ props.prefix }
ciampo marked this conversation as resolved.
Show resolved Hide resolved
labelPosition={ labelPosition }
{ ...props }
>
<Select
{ ...props }
{ ...omit( props, 'prefix', 'suffix' ) }
ciampo marked this conversation as resolved.
Show resolved Hide resolved
aria-describedby={ helpId }
className="components-select-control__input"
disabled={ disabled }
Expand All @@ -107,7 +135,7 @@ function SelectControl(
onChange={ handleOnChange }
onFocus={ handleOnFocus }
ref={ ref }
size={ size }
selectSize={ size }
value={ valueProp }
>
{ options.map( ( option, index ) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,28 @@ import styled from '@emotion/styled';
* Internal dependencies
*/
import { COLORS, rtl } from '../../utils';
import type { Size } from '../types';

const disabledStyles = ( { disabled } ) => {
interface SelectProps {
disabled?: boolean;
selectSize?: Size;
}

const disabledStyles = ( { disabled }: SelectProps ) => {
if ( ! disabled ) return '';

return css( {
color: COLORS.ui.textDisabled,
} );
};

const fontSizeStyles = ( { size } ) => {
const fontSizeStyles = ( { selectSize }: SelectProps ) => {
const sizes = {
default: '13px',
small: '11px',
};

const fontSize = sizes[ size ];
const fontSize = sizes[ selectSize ];
const fontSizeMobile = '16px';

if ( ! fontSize ) return '';
Expand All @@ -37,7 +43,7 @@ const fontSizeStyles = ( { size } ) => {
`;
};

const sizeStyles = ( { size } ) => {
const sizeStyles = ( { selectSize }: SelectProps ) => {
const sizes = {
default: {
height: 30,
Expand All @@ -51,15 +57,15 @@ const sizeStyles = ( { size } ) => {
},
};

const style = sizes[ size ] || sizes.default;
const style = sizes[ selectSize ] || sizes.default;

return css( style );
};

// TODO: Resolve need to use &&& to increase specificity
// https://github.com/WordPress/gutenberg/issues/18483

export const Select = styled.select`
export const Select = styled.select< SelectProps >`
&&& {
appearance: none;
background: transparent;
Expand All @@ -75,7 +81,7 @@ export const Select = styled.select`
${ fontSizeStyles };
${ sizeStyles };

${ rtl( { paddingLeft: 8, paddingRight: 24 } )() }
${ rtl( { paddingLeft: 8, paddingRight: 24 } ) }
ciampo marked this conversation as resolved.
Show resolved Hide resolved
}
`;

Expand All @@ -89,7 +95,7 @@ export const DownArrowWrapper = styled.div`
position: absolute;
top: 0;

${ rtl( { right: 0 } )() }
${ rtl( { right: 0 } ) }

svg {
display: block;
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/select-control/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type Size = 'default' | 'small';