From e8c479fe307b331dcfa3e57dd2477cf51c3b0118 Mon Sep 17 00:00:00 2001 From: Marcin Lewandowski Date: Tue, 24 Jan 2023 23:10:16 +0100 Subject: [PATCH 1/6] refactor(typescript): convert Dropdown and dependencies to TypeScript --- .../Dropdown/{Dropdown.js => Dropdown.tsx} | 218 ++++++++++++++++-- .../Dropdown/{index.js => index.ts} | 6 +- .../FluidForm/{FluidForm.js => FluidForm.tsx} | 11 +- .../{FormContext.js => FormContext.ts} | 7 +- .../FluidForm/{index.js => index.ts} | 2 +- .../ListBox/{ListBox.js => ListBox.tsx} | 86 +++++-- .../{ListBoxField.js => ListBoxField.tsx} | 18 +- .../{ListBoxMenu.js => ListBoxMenu.tsx} | 26 ++- ...ListBoxMenuIcon.js => ListBoxMenuIcon.tsx} | 28 ++- ...ListBoxMenuItem.js => ListBoxMenuItem.tsx} | 35 ++- .../components/ListBox/ListBoxPropTypes.js | 11 - .../components/ListBox/ListBoxPropTypes.ts | 17 ++ ...stBoxSelection.js => ListBoxSelection.tsx} | 70 ++++-- .../react/src/components/ListBox/index.js | 23 -- .../react/src/components/ListBox/index.ts | 34 +++ 15 files changed, 468 insertions(+), 124 deletions(-) rename packages/react/src/components/Dropdown/{Dropdown.js => Dropdown.tsx} (64%) rename packages/react/src/components/Dropdown/{index.js => index.ts} (72%) rename packages/react/src/components/FluidForm/{FluidForm.js => FluidForm.tsx} (78%) rename packages/react/src/components/FluidForm/{FormContext.js => FormContext.ts} (57%) rename packages/react/src/components/FluidForm/{index.js => index.ts} (89%) rename packages/react/src/components/ListBox/{ListBox.js => ListBox.tsx} (65%) rename packages/react/src/components/ListBox/{ListBoxField.js => ListBoxField.tsx} (76%) rename packages/react/src/components/ListBox/{ListBoxMenu.js => ListBoxMenu.tsx} (66%) rename packages/react/src/components/ListBox/{ListBoxMenuIcon.js => ListBoxMenuIcon.tsx} (65%) rename packages/react/src/components/ListBox/{ListBoxMenuItem.js => ListBoxMenuItem.tsx} (65%) delete mode 100644 packages/react/src/components/ListBox/ListBoxPropTypes.js create mode 100644 packages/react/src/components/ListBox/ListBoxPropTypes.ts rename packages/react/src/components/ListBox/{ListBoxSelection.js => ListBoxSelection.tsx} (67%) delete mode 100644 packages/react/src/components/ListBox/index.js create mode 100644 packages/react/src/components/ListBox/index.ts diff --git a/packages/react/src/components/Dropdown/Dropdown.js b/packages/react/src/components/Dropdown/Dropdown.tsx similarity index 64% rename from packages/react/src/components/Dropdown/Dropdown.js rename to packages/react/src/components/Dropdown/Dropdown.tsx index 190fd320db96..6355be33bb75 100644 --- a/packages/react/src/components/Dropdown/Dropdown.js +++ b/packages/react/src/components/Dropdown/Dropdown.tsx @@ -5,8 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import React, { useRef, useContext, useState } from 'react'; -import { useSelect } from 'downshift'; +import React, { useRef, useContext, useState, FocusEvent, ForwardedRef, MouseEvent, ReactNode } from 'react'; +import { useSelect, UseSelectProps, UseSelectState } from 'downshift'; import cx from 'classnames'; import PropTypes from 'prop-types'; import { @@ -14,22 +14,176 @@ import { WarningAltFilled, WarningFilled, } from '@carbon/icons-react'; -import ListBox, { PropTypes as ListBoxPropTypes } from '../ListBox'; +import ListBox, { ListBoxSize, ListBoxType, PropTypes as ListBoxPropTypes } from '../ListBox'; import mergeRefs from '../../tools/mergeRefs'; import deprecate from '../../prop-types/deprecate'; import { useFeatureFlag } from '../FeatureFlags'; import { usePrefix } from '../../internal/usePrefix'; import { FormContext } from '../FluidForm'; +import { ReactAttr } from '../../types/common'; -const defaultItemToString = (item) => { +const defaultItemToString = (item?: ItemType): string => { if (typeof item === 'string') { return item; } + if (typeof item === 'number') { + return `${item}` + } + if (item !== null && typeof item === 'object' + && 'label' in item && typeof item['label'] === 'string') { + return item['label'] + } + return ''; +}; + +type ExcludedAttributes = 'id' | 'onChange'; + +export interface OnChangeData { + selectedItem: ItemType | null; +} + +export interface DropdownProps + extends Omit, ExcludedAttributes> { + + /** + * 'aria-label' of the ListBox component. + */ + ariaLabel?: string; + + /** + * Specify the direction of the dropdown. Can be either top or bottom. + */ + direction?: 'top' | 'bottom'; + + /** + * Disable the control + */ + disabled?: boolean; + + /** + * Additional props passed to Downshift + */ + downshiftProps: any; // UseSelectProps; + + /** + * Provide helper text that is used alongside the control label for + * additional help + */ + helperText?: React.ReactNode; + + /** + * Specify whether the title text should be hidden or not + */ + hideLabel?: boolean; - return item ? item.label : ''; + /** + * Specify a custom `id` + */ + id: string; + + /** + * Allow users to pass in an arbitrary item or a string (in case their items are an array of strings) + * from their collection that are pre-selected + */ + initialSelectedItem?: ItemType; + + /** + * Specify if the currently selected value is invalid. + */ + invalid?: boolean; + + /** + * Message which is displayed if the value is invalid. + */ + invalidText?: React.ReactNode; + + /** + * Function to render items as custom components instead of strings. + * Defaults to null and is overridden by a getter + */ + itemToElement?: React.JSXElementConstructor | null; + + /** + * Helper function passed to downshift that allows the library to render a + * given item to a string label. By default, it extracts the `label` field + * from a given item to serve as the item label in the list. + */ + itemToString?(item: ItemType): string; + + /** + * We try to stay as generic as possible here to allow individuals to pass + * in a collection of whatever kind of data structure they prefer + */ + items: ItemType[]; + + /** + * Generic `label` that will be used as the textual representation of what + * this field is for + */ + label: NonNullable; + + /** + * `true` to use the light version. + * @deprecated The `light` prop for `Dropdown` has been deprecated + * in favor of the new `Layer` component. It will be removed in the next major release. + */ + light?: boolean; + + /** + * `onChange` is a utility for this controlled component to communicate to a + * consuming component what kind of internal state changes are occurring. + */ + onChange?(data: OnChangeData): void; + + /** + * Whether or not the Dropdown is readonly + */ + readOnly?: boolean; + + /** + * An optional callback to render the currently selected item as a react element instead of only + * as a string. + */ + renderSelectedItem?(item: ItemType): string; + + /** + * In the case you want to control the dropdown selection entirely. + */ + selectedItem?: ItemType; + + /** + * Specify the size of the ListBox. Currently supports either `sm`, `md` or `lg` as an option. + */ + size?: ListBoxSize; + + /** + * Provide the title text that will be read by a screen reader when + * visiting this control + */ + titleText?: React.ReactNode; + + /** + * Callback function for translating ListBoxMenuIcon SVG title + */ + translateWithId?(messageId: string, args?: Record): string; + + /** + * The dropdown type, `default` or `inline` + */ + type?: ListBoxType; + + /** + * Specify whether the control is currently in warning state + */ + warn?: boolean; + + /** + * Provide the text that is displayed when the control is in warning state + */ + warnText?: React.ReactNode; }; -const Dropdown = React.forwardRef(function Dropdown( +const Dropdown = React.forwardRef(( { className: containerClassName, disabled, @@ -37,7 +191,7 @@ const Dropdown = React.forwardRef(function Dropdown( items, label, ariaLabel, - itemToString, + itemToString = defaultItemToString, itemToElement, renderSelectedItem, type, @@ -58,12 +212,12 @@ const Dropdown = React.forwardRef(function Dropdown( downshiftProps, readOnly, ...other - }, - ref -) { + }: DropdownProps, + ref: ForwardedRef +) => { const prefix = usePrefix(); const { isFluid } = useContext(FormContext); - const selectProps = { + const selectProps: UseSelectProps = { ...downshiftProps, items, itemToString, @@ -141,28 +295,32 @@ const Dropdown = React.forwardRef(function Dropdown(
{helperText}
) : null; - function onSelectedItemChange({ selectedItem }) { + function onSelectedItemChange({ selectedItem }: Partial>) { setIsFocused(false); if (onChange) { - onChange({ selectedItem }); + onChange({ selectedItem: selectedItem ?? null }); } } const menuItemOptionRefs = useRef(items.map((_) => React.createRef())); - const handleFocus = (evt) => { + const handleFocus = (evt: FocusEvent) => { setIsFocused(evt.type === 'focus' ? true : false); }; + const mergedRef = mergeRefs(toggleButtonProps.ref, ref); + const readOnlyEventHandlers = readOnly ? { - onClick: (evt) => { + onClick: (evt: MouseEvent) => { // NOTE: does not prevent click evt.preventDefault(); // focus on the element as per readonly input behavior - evt.target.focus(); + if (mergedRef.current !== undefined) { + mergedRef.current.focus(); + } }, - onKeyDown: (evt) => { + onKeyDown: (evt: React.KeyboardEvent) => { const selectAccessKeys = ['ArrowDown', 'ArrowUp', ' ', 'Enter']; // This prevents the select from opening for the above keys if (selectAccessKeys.includes(evt.key)) { @@ -205,10 +363,10 @@ const Dropdown = React.forwardRef(function Dropdown( className={`${prefix}--list-box__field`} disabled={disabled} aria-disabled={readOnly ? true : undefined} // aria-disabled to remain focusable - title={selectedItem ? itemToString(selectedItem) : label} + title={selectedItem && itemToString !== undefined ? itemToString(selectedItem) : label} {...toggleButtonProps} {...readOnlyEventHandlers} - ref={mergeRefs(toggleButtonProps.ref, ref)}> + ref={mergedRef}> {selectedItem ? renderSelectedItem @@ -221,12 +379,15 @@ const Dropdown = React.forwardRef(function Dropdown( {isOpen && items.map((item, index) => { + const isObject = item !== null && typeof item === 'object'; + const disabled = isObject && 'disabled' in item && item.disabled === true; const itemProps = getItemProps({ item, index, - disabled: item.disabled, + disabled, }); - const title = itemToElement ? item.text : itemToString(item); + const text = isObject && 'text' in item ? item.text : itemToString(item); + const title = itemToElement ? text : itemToString(item); return ( - {itemToElement ? ( + {typeof item === 'object' && ItemToElement !== undefined + && ItemToElement !== null ? ( ) : ( itemToString(item) @@ -259,6 +421,14 @@ const Dropdown = React.forwardRef(function Dropdown( ); }); +interface DropdownComponentProps extends + React.PropsWithoutRef> + & React.RefAttributes> {} + +interface DropdownComponent { + (props: DropdownComponentProps): React.ReactElement | null +} + Dropdown.displayName = 'Dropdown'; Dropdown.propTypes = { /** @@ -422,6 +592,6 @@ Dropdown.defaultProps = { titleText: '', helperText: '', direction: 'bottom', -}; +} as DropdownProps; -export default Dropdown; +export default Dropdown as DropdownComponent; diff --git a/packages/react/src/components/Dropdown/index.js b/packages/react/src/components/Dropdown/index.ts similarity index 72% rename from packages/react/src/components/Dropdown/index.js rename to packages/react/src/components/Dropdown/index.ts index d4a911edc773..236faa32cdc6 100644 --- a/packages/react/src/components/Dropdown/index.js +++ b/packages/react/src/components/Dropdown/index.ts @@ -5,8 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import Dropdown from './Dropdown'; +import Dropdown, { OnChangeData } from './Dropdown'; +export type { OnChangeData }; +export { Dropdown } export { default as DropdownSkeleton } from './Dropdown.Skeleton'; + export default Dropdown; -export { Dropdown }; diff --git a/packages/react/src/components/FluidForm/FluidForm.js b/packages/react/src/components/FluidForm/FluidForm.tsx similarity index 78% rename from packages/react/src/components/FluidForm/FluidForm.js rename to packages/react/src/components/FluidForm/FluidForm.tsx index 7e6a7df4aced..04c7ef4c9d1b 100644 --- a/packages/react/src/components/FluidForm/FluidForm.js +++ b/packages/react/src/components/FluidForm/FluidForm.tsx @@ -1,5 +1,5 @@ /** - * Copyright IBM Corp. 2016, 2018 + * Copyright IBM Corp. 2016, 2023 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. @@ -11,8 +11,15 @@ import classnames from 'classnames'; import Form from '../Form'; import { FormContext } from './FormContext'; import { usePrefix } from '../../internal/usePrefix'; +import { ReactAttr } from '../../types/common'; -function FluidForm({ className, children, ...other }) { +export interface FluidFormProps extends ReactAttr {}; + +const FluidForm: React.FC = ({ + className, + children, + ...other +}: FluidFormProps) => { const prefix = usePrefix(); const classNames = classnames(`${prefix}--form--fluid`, className); diff --git a/packages/react/src/components/FluidForm/FormContext.js b/packages/react/src/components/FluidForm/FormContext.ts similarity index 57% rename from packages/react/src/components/FluidForm/FormContext.js rename to packages/react/src/components/FluidForm/FormContext.ts index c99698b123e9..9e52f9fcd202 100644 --- a/packages/react/src/components/FluidForm/FormContext.js +++ b/packages/react/src/components/FluidForm/FormContext.ts @@ -1,5 +1,5 @@ /** - * Copyright IBM Corp. 2016, 2018 + * Copyright IBM Corp. 2016, 2023 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. @@ -7,6 +7,9 @@ import { createContext } from 'react'; -export const FormContext = createContext({ +export interface FormContextProps { + isFluid?: boolean; +} +export const FormContext = createContext({ isFluid: false, }); diff --git a/packages/react/src/components/FluidForm/index.js b/packages/react/src/components/FluidForm/index.ts similarity index 89% rename from packages/react/src/components/FluidForm/index.js rename to packages/react/src/components/FluidForm/index.ts index 26d7982218a7..24ac2bf4cad6 100644 --- a/packages/react/src/components/FluidForm/index.js +++ b/packages/react/src/components/FluidForm/index.ts @@ -1,5 +1,5 @@ /** - * Copyright IBM Corp. 2016, 2018 + * Copyright IBM Corp. 2016, 2023 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. diff --git a/packages/react/src/components/ListBox/ListBox.js b/packages/react/src/components/ListBox/ListBox.tsx similarity index 65% rename from packages/react/src/components/ListBox/ListBox.js rename to packages/react/src/components/ListBox/ListBox.tsx index 447487962360..68052557ed83 100644 --- a/packages/react/src/components/ListBox/ListBox.js +++ b/packages/react/src/components/ListBox/ListBox.tsx @@ -1,39 +1,93 @@ /** - * Copyright IBM Corp. 2016, 2018 + * Copyright IBM Corp. 2016, 2023 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ import cx from 'classnames'; -import React, { useContext } from 'react'; +import React, { KeyboardEvent, MouseEvent, useContext} from 'react'; import PropTypes from 'prop-types'; import deprecate from '../../prop-types/deprecate'; import { ListBoxType, ListBoxSize } from './ListBoxPropTypes'; import { usePrefix } from '../../internal/usePrefix'; -import ListBoxField from './ListBoxField'; -import ListBoxMenu from './ListBoxMenu'; -import ListBoxMenuIcon from './ListBoxMenuIcon'; -import ListBoxMenuItem from './ListBoxMenuItem'; -import ListBoxSelection from './ListBoxSelection'; import { FormContext } from '../FluidForm'; +import { ForwardRefReturn, ReactAttr } from '../../types/common'; -const handleOnKeyDown = (event) => { +const handleOnKeyDown = (event: KeyboardEvent) => { if (event.keyCode === 27) { event.stopPropagation(); } }; -const handleClick = (event) => { +const handleClick = (event: MouseEvent) => { event.preventDefault(); event.stopPropagation(); }; +type ExcludedAttributes = 'onKeyDown' | 'onKeyPress' | 'ref' + +export interface ListBoxProps + extends Omit, ExcludedAttributes> { + + /** + * Specify whether the ListBox is currently disabled + */ + disabled?: boolean; + + /** + * Specify whether the control is currently invalid + */ + invalid?: boolean; + + /** + * Specify the text to be displayed when the control is invalid + */ + invalidText?: React.ReactNode; + + /** + * Specify if the control should render open + */ + isOpen?: boolean; + + /** + * `true` to use the light version. For use on $ui-01 backgrounds only. + * Don't use this to make tile background color same as container background color. + * + * @deprecated The `light` prop for `ListBox` has been deprecated in favor of + * the new `Layer` component. It will be removed in the next major release. + */ + light?: boolean; + + /** + * Specify the size of the ListBox. Currently supports either `sm`, `md` or `lg` as an option. + */ + size?: ListBoxSize; + + /** + * Specify the "type" of the ListBox. Currently supports either `default` or + * `inline` as an option. + */ + type?: ListBoxType; + + /** + * Specify whether the control is currently in warning state + */ + warn?: boolean; + + /** + * Provide the text that is displayed when the control is in warning state + */ + warnText?: React.ReactNode; +}; + +export type ListBoxComponent = ForwardRefReturn + /** * `ListBox` is a generic container component that handles creating the * container class name in response to certain props. */ -const ListBox = React.forwardRef(function ListBox( +const ListBox: ListBoxComponent = React.forwardRef(function ListBox( { children, className: containerClassName, @@ -47,15 +101,15 @@ const ListBox = React.forwardRef(function ListBox( light, isOpen, ...rest - }, - ref + }: ListBoxProps, + ref: React.LegacyRef ) { const prefix = usePrefix(); const { isFluid } = useContext(FormContext); const showWarning = !invalid && warn; const className = cx({ - [containerClassName]: !!containerClassName, + ...(containerClassName && {[containerClassName]: true}), [`${prefix}--list-box`]: true, [`${prefix}--list-box--${size}`]: size, [`${prefix}--list-box--inline`]: type === 'inline', @@ -157,10 +211,4 @@ ListBox.defaultProps = { type: 'default', }; -ListBox.Field = ListBoxField; -ListBox.Menu = ListBoxMenu; -ListBox.MenuIcon = ListBoxMenuIcon; -ListBox.MenuItem = ListBoxMenuItem; -ListBox.Selection = ListBoxSelection; - export default ListBox; diff --git a/packages/react/src/components/ListBox/ListBoxField.js b/packages/react/src/components/ListBox/ListBoxField.tsx similarity index 76% rename from packages/react/src/components/ListBox/ListBoxField.js rename to packages/react/src/components/ListBox/ListBoxField.tsx index d81075f17997..7b86175ff951 100644 --- a/packages/react/src/components/ListBox/ListBoxField.js +++ b/packages/react/src/components/ListBox/ListBoxField.tsx @@ -1,5 +1,5 @@ /** - * Copyright IBM Corp. 2016, 2018 + * Copyright IBM Corp. 2016, 2023 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. @@ -8,16 +8,26 @@ import React from 'react'; import PropTypes from 'prop-types'; import { usePrefix } from '../../internal/usePrefix'; +import { ReactAttr } from '../../types/common'; // No longer used, left export for backward-compatibility export const translationIds = {}; +export interface ListBoxFieldProps extends ReactAttr { + + /** + * Specify if the parent is disabled + */ + disabled?: boolean; + +}; + /** * `ListBoxField` is responsible for creating the containing node for valid * elements inside of a field. It also provides a11y-related attributes like * `role` to make sure a user can focus the given field. */ -function ListBoxField({ children, disabled, tabIndex, ...rest }) { +function ListBoxField({ children, disabled, tabIndex, ...rest }: ListBoxFieldProps) { const prefix = usePrefix(); return ( @@ -59,4 +69,6 @@ ListBoxField.propTypes = { tabIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), }; -export default ListBoxField; +export interface ListBoxFieldComponent extends React.FC {} + +export default ListBoxField as ListBoxFieldComponent; diff --git a/packages/react/src/components/ListBox/ListBoxMenu.js b/packages/react/src/components/ListBox/ListBoxMenu.tsx similarity index 66% rename from packages/react/src/components/ListBox/ListBoxMenu.js rename to packages/react/src/components/ListBox/ListBoxMenu.tsx index ce0ef0d324c5..bd3491cadba9 100644 --- a/packages/react/src/components/ListBox/ListBoxMenu.js +++ b/packages/react/src/components/ListBox/ListBoxMenu.tsx @@ -1,23 +1,37 @@ /** - * Copyright IBM Corp. 2016, 2018 + * Copyright IBM Corp. 2016, 2023 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ -import React from 'react'; +import React, { ForwardedRef } from 'react'; import { usePrefix } from '../../internal/usePrefix'; import PropTypes from 'prop-types'; import ListBoxMenuItem from './ListBoxMenuItem'; +import { ForwardRefReturn, ReactAttr } from '../../types/common'; + +type ExcludedAttributes = 'id'; + +export interface ListBoxMenuProps + extends Omit, ExcludedAttributes> { + + /** + * Specify a custom `id` + */ + id: string; +}; + +export type ListBoxMenuComponent = ForwardRefReturn /** * `ListBoxMenu` is a simple container node that isolates the `list-box__menu` * class into a single component. It is also being used to validate given * `children` components. */ -const ListBoxMenu = React.forwardRef(function ListBoxMenu( - { children, id, ...rest }, - ref +const ListBoxMenu: ListBoxMenuComponent = React.forwardRef(function ListBoxMenu( + { children, id, ...rest }: ListBoxMenuProps, + ref: ForwardedRef, ) { const prefix = usePrefix(); return ( @@ -39,7 +53,7 @@ ListBoxMenu.propTypes = { */ children: PropTypes.oneOfType([ PropTypes.node, - PropTypes.arrayOf(ListBoxMenuItem), + PropTypes.arrayOf(PropTypes.oneOf([ListBoxMenuItem])), /** * allow single item using the workaround for functional components * https://github.com/facebook/react/issues/2979#issuecomment-222379916 diff --git a/packages/react/src/components/ListBox/ListBoxMenuIcon.js b/packages/react/src/components/ListBox/ListBoxMenuIcon.tsx similarity index 65% rename from packages/react/src/components/ListBox/ListBoxMenuIcon.js rename to packages/react/src/components/ListBox/ListBoxMenuIcon.tsx index a9c4c4c70410..529228a58db8 100644 --- a/packages/react/src/components/ListBox/ListBoxMenuIcon.js +++ b/packages/react/src/components/ListBox/ListBoxMenuIcon.tsx @@ -1,5 +1,5 @@ /** - * Copyright IBM Corp. 2016, 2018 + * Copyright IBM Corp. 2016, 2023 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. @@ -21,11 +21,33 @@ const defaultTranslations = { [translationIds['open.menu']]: 'Open menu', }; +const defaultTranslateWithId = (id: string) => defaultTranslations[id] + +export interface ListBoxMenuIconProps { + /** + * Specify whether the menu is currently open, which will influence the + * direction of the menu icon + */ + isOpen: boolean; + + /** + * i18n hook used to provide the appropriate description for the given menu + * icon. This function takes in an id defined in `translationIds` and should + * return a string message for that given message id. + */ + translateWithId?(messageId: string, args?: Record): string; +}; + +export type ListBoxMenuIconComponent = React.FC + /** * `ListBoxMenuIcon` is used to orient the icon up or down depending on the * state of the menu for a given `ListBox` */ -const ListBoxMenuIcon = ({ isOpen, translateWithId: t }) => { +const ListBoxMenuIcon: ListBoxMenuIconComponent = ({ + isOpen, + translateWithId: t = defaultTranslateWithId, +}: ListBoxMenuIconProps) => { const prefix = usePrefix(); const className = cx(`${prefix}--list-box__menu-icon`, { [`${prefix}--list-box__menu-icon--open`]: isOpen, @@ -56,7 +78,7 @@ ListBoxMenuIcon.propTypes = { }; ListBoxMenuIcon.defaultProps = { - translateWithId: (id) => defaultTranslations[id], + translateWithId: defaultTranslateWithId, }; export default ListBoxMenuIcon; diff --git a/packages/react/src/components/ListBox/ListBoxMenuItem.js b/packages/react/src/components/ListBox/ListBoxMenuItem.tsx similarity index 65% rename from packages/react/src/components/ListBox/ListBoxMenuItem.js rename to packages/react/src/components/ListBox/ListBoxMenuItem.tsx index 8287dcbcc296..f274a1c97a5d 100644 --- a/packages/react/src/components/ListBox/ListBoxMenuItem.js +++ b/packages/react/src/components/ListBox/ListBoxMenuItem.tsx @@ -1,14 +1,15 @@ /** - * Copyright IBM Corp. 2016, 2018 + * Copyright IBM Corp. 2016, 2023 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ import cx from 'classnames'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { ForwardedRef, useEffect, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import { usePrefix } from '../../internal/usePrefix'; +import { ForwardRefReturn, ReactAttr } from '../../types/common'; function useIsTruncated(ref) { const [isTruncated, setIsTruncated] = useState(false); @@ -21,14 +22,34 @@ function useIsTruncated(ref) { return isTruncated; } +export interface ListBoxMenuItemProps extends ReactAttr { + + /** + * Specify whether the current menu item is "active". + */ + isActive?: boolean; + + /** + * Specify whether the current menu item is "highlighted". + */ + isHighlighted?: boolean; + +}; + +export type ListBoxMenuItemForwardedRef = ForwardedRef & { + menuItemOptionRef?: React.Ref; + } | null; + +export interface ListBoxMenuItemComponent extends ForwardRefReturn { } + /** * `ListBoxMenuItem` is a helper component for managing the container class * name, alongside any classes for any corresponding states, for a generic list * box menu item. */ -const ListBoxMenuItem = React.forwardRef(function ListBoxMenuItem( - { children, isActive, isHighlighted, title, ...rest }, - forwardedRef +const ListBoxMenuItem = React.forwardRef(function ListBoxMenuItem( + { children, isActive, isHighlighted, title, ...rest }: ListBoxMenuItemProps, + forwardedRef: ListBoxMenuItemForwardedRef ) { const prefix = usePrefix(); const ref = useRef(null); @@ -43,7 +64,7 @@ const ListBoxMenuItem = React.forwardRef(function ListBoxMenuItem( {...rest} className={className} title={isTruncated ? title : undefined} - tabIndex="-1"> + tabIndex={-1}>
@@ -82,4 +103,4 @@ ListBoxMenuItem.defaultProps = { isHighlighted: false, }; -export default ListBoxMenuItem; +export default ListBoxMenuItem as ListBoxMenuItemComponent; diff --git a/packages/react/src/components/ListBox/ListBoxPropTypes.js b/packages/react/src/components/ListBox/ListBoxPropTypes.js deleted file mode 100644 index 767eded95c81..000000000000 --- a/packages/react/src/components/ListBox/ListBoxPropTypes.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Copyright IBM Corp. 2016, 2018 - * - * This source code is licensed under the Apache-2.0 license found in the - * LICENSE file in the root directory of this source tree. - */ - -import PropTypes from 'prop-types'; - -export const ListBoxType = PropTypes.oneOf(['default', 'inline']); -export const ListBoxSize = PropTypes.oneOf(['sm', 'md', 'lg']); diff --git a/packages/react/src/components/ListBox/ListBoxPropTypes.ts b/packages/react/src/components/ListBox/ListBoxPropTypes.ts new file mode 100644 index 000000000000..67e9c4aa245d --- /dev/null +++ b/packages/react/src/components/ListBox/ListBoxPropTypes.ts @@ -0,0 +1,17 @@ +/** + * Copyright IBM Corp. 2016, 2023 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import PropTypes from 'prop-types'; + +const listBoxTypes = ['default', 'inline'] as const +const listBoxSizes = ['sm', 'md', 'lg'] as const + +export type ListBoxType = typeof listBoxTypes[number] +export type ListBoxSize = typeof listBoxSizes[number] + +export const ListBoxType = PropTypes.oneOf(listBoxTypes); +export const ListBoxSize = PropTypes.oneOf(listBoxSizes); diff --git a/packages/react/src/components/ListBox/ListBoxSelection.js b/packages/react/src/components/ListBox/ListBoxSelection.tsx similarity index 67% rename from packages/react/src/components/ListBox/ListBoxSelection.js rename to packages/react/src/components/ListBox/ListBoxSelection.tsx index da6f19a3e12f..dee71cd68bd3 100644 --- a/packages/react/src/components/ListBox/ListBoxSelection.js +++ b/packages/react/src/components/ListBox/ListBoxSelection.tsx @@ -1,5 +1,5 @@ /** - * Copyright IBM Corp. 2016, 2018 + * Copyright IBM Corp. 2016, 2023 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. @@ -11,26 +11,66 @@ import PropTypes from 'prop-types'; import { Close } from '@carbon/icons-react'; import { match, keys } from '../../internal/keyboard'; import { usePrefix } from '../../internal/usePrefix'; +import { KeyboardEvent, MouseEvent } from 'react'; + +export interface ListBoxSelectionProps { + /** + * Specify a function to be invoked when a user interacts with the clear + * selection element. + */ + clearSelection(event: MouseEvent | KeyboardEvent): void; + + /** + * Specify whether or not the clear selection element should be disabled + */ + disabled?: boolean; + + /** + * Specify an optional `onClearSelection` handler that is called when the underlying + * element is cleared + */ + onClearSelection?(event: MouseEvent | KeyboardEvent): void; + + /** + * Whether or not the Dropdown is readonly + */ + readOnly?: boolean; + + /** + * Specify an optional `selectionCount` value that will be used to determine + * whether the selection should display a badge or a single clear icon. + */ + selectionCount?: number; + + /** + * i18n hook used to provide the appropriate description for the given menu + * icon. This function takes in an id defined in `translationIds` and should + * return a string message for that given message id. + */ + translateWithId(messageId: string, args?: Record): string; +}; + +export type ListBoxSelectionComponent = React.FC /** * `ListBoxSelection` is used to provide controls for clearing a selection, in * addition to conditionally rendering a badge if the control has more than one * selection. */ -function ListBoxSelection({ +const ListBoxSelection: ListBoxSelectionComponent = ({ clearSelection, selectionCount, translateWithId: t, disabled, onClearSelection, readOnly, -}) { +}: ListBoxSelectionProps) => { const prefix = usePrefix(); const className = cx(`${prefix}--list-box__selection`, { [`${prefix}--tag--filter`]: selectionCount, [`${prefix}--list-box__selection--multi`]: selectionCount, }); - const handleOnClick = (event) => { + const handleOnClick = (event: MouseEvent) => { event.stopPropagation(); if (disabled || readOnly) { return; @@ -40,14 +80,14 @@ function ListBoxSelection({ onClearSelection(event); } }; - const handleOnKeyDown = (event) => { + const handleOnKeyDown = (event: KeyboardEvent) => { event.stopPropagation(); if (disabled || readOnly) { return; } // When a user hits ENTER, we'll clear the selection - if (match(event, keys.Enter)) { + if (match(event.code, keys.Enter)) { clearSelection(event); if (onClearSelection) { onClearSelection(event); @@ -65,7 +105,7 @@ function ListBoxSelection({ ); return selectionCount ? (
- + {selectionCount}
@@ -124,18 +164,6 @@ ListBoxSelection.propTypes = { */ onClearSelection: PropTypes.func, - /** - * Specify an optional `onClick` handler that is called when the underlying - * clear selection element is clicked - */ - onClick: PropTypes.func, - - /** - * Specify an optional `onKeyDown` handler that is called when the underlying - * clear selection element fires a keydown event - */ - onKeyDown: PropTypes.func, - /** * Whether or not the Dropdown is readonly */ @@ -156,7 +184,7 @@ ListBoxSelection.propTypes = { }; ListBoxSelection.defaultProps = { - translateWithId: (id) => defaultTranslations[id], + translateWithId: (id: string) => defaultTranslations[id], }; export default ListBoxSelection; diff --git a/packages/react/src/components/ListBox/index.js b/packages/react/src/components/ListBox/index.js deleted file mode 100644 index 1bf90ffcfcac..000000000000 --- a/packages/react/src/components/ListBox/index.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright IBM Corp. 2016, 2018 - * - * This source code is licensed under the Apache-2.0 license found in the - * LICENSE file in the root directory of this source tree. - */ - -export * as PropTypes from './ListBoxPropTypes'; - -import ListBox from './ListBox'; -import ListBoxField from './ListBoxField'; -import ListBoxMenu from './ListBoxMenu'; -import ListBoxMenuIcon from './ListBoxMenuIcon'; -import ListBoxMenuItem from './ListBoxMenuItem'; -import ListBoxSelection from './ListBoxSelection'; - -ListBox.Field = ListBoxField; -ListBox.Menu = ListBoxMenu; -ListBox.MenuIcon = ListBoxMenuIcon; -ListBox.MenuItem = ListBoxMenuItem; -ListBox.Selection = ListBoxSelection; - -export default ListBox; diff --git a/packages/react/src/components/ListBox/index.ts b/packages/react/src/components/ListBox/index.ts new file mode 100644 index 000000000000..39a663adb8d9 --- /dev/null +++ b/packages/react/src/components/ListBox/index.ts @@ -0,0 +1,34 @@ +/** + * Copyright IBM Corp. 2016, 2023 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +export * as PropTypes from './ListBoxPropTypes'; +export * from './ListBoxPropTypes'; + +import ListBoxInternal, { ListBoxComponent as ListBoxPartialComponent } from './ListBox'; +import ListBoxField, { ListBoxFieldComponent } from './ListBoxField'; +import ListBoxMenu, { ListBoxMenuComponent } from './ListBoxMenu'; +import ListBoxMenuIcon, { ListBoxMenuIconComponent } from './ListBoxMenuIcon'; +import ListBoxMenuItem, { ListBoxMenuItemComponent } from './ListBoxMenuItem'; +import ListBoxSelection, { ListBoxSelectionComponent } from './ListBoxSelection'; + +export interface ListBoxComponent extends ListBoxPartialComponent { + readonly Field: ListBoxFieldComponent, + readonly Menu: ListBoxMenuComponent, + readonly MenuIcon: ListBoxMenuIconComponent, + readonly MenuItem: ListBoxMenuItemComponent, + readonly Selection: ListBoxSelectionComponent, +} + +const ListBox: ListBoxComponent = Object.assign(ListBoxInternal, { + Field: ListBoxField, + Menu: ListBoxMenu, + MenuIcon: ListBoxMenuIcon, + MenuItem: ListBoxMenuItem, + Selection: ListBoxSelection, +}); + +export default ListBox; From 59e07e8df63400eb9986f3210576f99cb57be8cb Mon Sep 17 00:00:00 2001 From: Marcin Lewandowski Date: Wed, 25 Jan 2023 20:08:10 +0100 Subject: [PATCH 2/6] fix(typescript): set proper type for downshiftProps --- packages/react/src/components/Dropdown/Dropdown.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx index 6355be33bb75..d41714e4972a 100644 --- a/packages/react/src/components/Dropdown/Dropdown.tsx +++ b/packages/react/src/components/Dropdown/Dropdown.tsx @@ -63,7 +63,7 @@ export interface DropdownProps /** * Additional props passed to Downshift */ - downshiftProps: any; // UseSelectProps; + downshiftProps?: Partial>; /** * Provide helper text that is used alongside the control label for @@ -454,7 +454,7 @@ Dropdown.propTypes = { /** * Additional props passed to Downshift */ - downshiftProps: PropTypes.object, + downshiftProps: PropTypes.object as React.Validator>, /** * Provide helper text that is used alongside the control label for From fe3809df11386118d3677751629d6230ae62ca1e Mon Sep 17 00:00:00 2001 From: Marcin Lewandowski Date: Wed, 25 Jan 2023 20:52:59 +0100 Subject: [PATCH 3/6] fix(typescript): simplify text type checks per review --- packages/react/src/components/Dropdown/Dropdown.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx index d41714e4972a..161a37241507 100644 --- a/packages/react/src/components/Dropdown/Dropdown.tsx +++ b/packages/react/src/components/Dropdown/Dropdown.tsx @@ -386,8 +386,7 @@ const Dropdown = React.forwardRef(( index, disabled, }); - const text = isObject && 'text' in item ? item.text : itemToString(item); - const title = itemToElement ? text : itemToString(item); + const title = isObject && 'text' in item && itemToElement ? item.text : itemToString(item); return ( Date: Thu, 26 Jan 2023 07:23:57 +0100 Subject: [PATCH 4/6] fix(lint): fix lint issues --- packages/react/src/components/Dropdown/Dropdown.tsx | 6 +++--- packages/react/src/components/FluidForm/FluidForm.tsx | 2 +- packages/react/src/components/ListBox/ListBox.tsx | 2 +- packages/react/src/components/ListBox/ListBoxField.tsx | 4 ++-- packages/react/src/components/ListBox/ListBoxMenu.tsx | 2 +- packages/react/src/components/ListBox/ListBoxMenuIcon.tsx | 4 ++-- packages/react/src/components/ListBox/ListBoxMenuItem.tsx | 4 ++-- packages/react/src/components/ListBox/ListBoxSelection.tsx | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/react/src/components/Dropdown/Dropdown.tsx b/packages/react/src/components/Dropdown/Dropdown.tsx index 161a37241507..38a87925e2b0 100644 --- a/packages/react/src/components/Dropdown/Dropdown.tsx +++ b/packages/react/src/components/Dropdown/Dropdown.tsx @@ -181,7 +181,7 @@ export interface DropdownProps * Provide the text that is displayed when the control is in warning state */ warnText?: React.ReactNode; -}; +} const Dropdown = React.forwardRef(( { @@ -420,9 +420,9 @@ const Dropdown = React.forwardRef(( ); }); -interface DropdownComponentProps extends +type DropdownComponentProps = React.PropsWithoutRef> - & React.RefAttributes> {} + & React.RefAttributes> interface DropdownComponent { (props: DropdownComponentProps): React.ReactElement | null diff --git a/packages/react/src/components/FluidForm/FluidForm.tsx b/packages/react/src/components/FluidForm/FluidForm.tsx index 04c7ef4c9d1b..e2179c276280 100644 --- a/packages/react/src/components/FluidForm/FluidForm.tsx +++ b/packages/react/src/components/FluidForm/FluidForm.tsx @@ -13,7 +13,7 @@ import { FormContext } from './FormContext'; import { usePrefix } from '../../internal/usePrefix'; import { ReactAttr } from '../../types/common'; -export interface FluidFormProps extends ReactAttr {}; +export type FluidFormProps = ReactAttr const FluidForm: React.FC = ({ className, diff --git a/packages/react/src/components/ListBox/ListBox.tsx b/packages/react/src/components/ListBox/ListBox.tsx index 68052557ed83..d857421502d2 100644 --- a/packages/react/src/components/ListBox/ListBox.tsx +++ b/packages/react/src/components/ListBox/ListBox.tsx @@ -79,7 +79,7 @@ export interface ListBoxProps * Provide the text that is displayed when the control is in warning state */ warnText?: React.ReactNode; -}; +} export type ListBoxComponent = ForwardRefReturn diff --git a/packages/react/src/components/ListBox/ListBoxField.tsx b/packages/react/src/components/ListBox/ListBoxField.tsx index 7b86175ff951..47912db6a641 100644 --- a/packages/react/src/components/ListBox/ListBoxField.tsx +++ b/packages/react/src/components/ListBox/ListBoxField.tsx @@ -20,7 +20,7 @@ export interface ListBoxFieldProps extends ReactAttr { */ disabled?: boolean; -}; +} /** * `ListBoxField` is responsible for creating the containing node for valid @@ -69,6 +69,6 @@ ListBoxField.propTypes = { tabIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), }; -export interface ListBoxFieldComponent extends React.FC {} +export type ListBoxFieldComponent = React.FC export default ListBoxField as ListBoxFieldComponent; diff --git a/packages/react/src/components/ListBox/ListBoxMenu.tsx b/packages/react/src/components/ListBox/ListBoxMenu.tsx index bd3491cadba9..957f58c142b4 100644 --- a/packages/react/src/components/ListBox/ListBoxMenu.tsx +++ b/packages/react/src/components/ListBox/ListBoxMenu.tsx @@ -20,7 +20,7 @@ export interface ListBoxMenuProps * Specify a custom `id` */ id: string; -}; +} export type ListBoxMenuComponent = ForwardRefReturn diff --git a/packages/react/src/components/ListBox/ListBoxMenuIcon.tsx b/packages/react/src/components/ListBox/ListBoxMenuIcon.tsx index 529228a58db8..206d5bc4104a 100644 --- a/packages/react/src/components/ListBox/ListBoxMenuIcon.tsx +++ b/packages/react/src/components/ListBox/ListBoxMenuIcon.tsx @@ -36,9 +36,9 @@ export interface ListBoxMenuIconProps { * return a string message for that given message id. */ translateWithId?(messageId: string, args?: Record): string; -}; +} -export type ListBoxMenuIconComponent = React.FC +export type ListBoxMenuIconComponent = React.FC; /** * `ListBoxMenuIcon` is used to orient the icon up or down depending on the diff --git a/packages/react/src/components/ListBox/ListBoxMenuItem.tsx b/packages/react/src/components/ListBox/ListBoxMenuItem.tsx index f274a1c97a5d..0ef179668def 100644 --- a/packages/react/src/components/ListBox/ListBoxMenuItem.tsx +++ b/packages/react/src/components/ListBox/ListBoxMenuItem.tsx @@ -34,13 +34,13 @@ export interface ListBoxMenuItemProps extends ReactAttr { */ isHighlighted?: boolean; -}; +} export type ListBoxMenuItemForwardedRef = ForwardedRef & { menuItemOptionRef?: React.Ref; } | null; -export interface ListBoxMenuItemComponent extends ForwardRefReturn { } +export type ListBoxMenuItemComponent = ForwardRefReturn; /** * `ListBoxMenuItem` is a helper component for managing the container class diff --git a/packages/react/src/components/ListBox/ListBoxSelection.tsx b/packages/react/src/components/ListBox/ListBoxSelection.tsx index dee71cd68bd3..a6585889547f 100644 --- a/packages/react/src/components/ListBox/ListBoxSelection.tsx +++ b/packages/react/src/components/ListBox/ListBoxSelection.tsx @@ -48,7 +48,7 @@ export interface ListBoxSelectionProps { * return a string message for that given message id. */ translateWithId(messageId: string, args?: Record): string; -}; +} export type ListBoxSelectionComponent = React.FC From 54eb36440ce40a3a0f5cad9ff789eb48fbeb2d41 Mon Sep 17 00:00:00 2001 From: Marcin Lewandowski Date: Thu, 26 Jan 2023 07:31:57 +0100 Subject: [PATCH 5/6] fix(typescript): remove disabled attribute from div in ListBoxSelection --- packages/react/src/components/ListBox/ListBoxSelection.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react/src/components/ListBox/ListBoxSelection.tsx b/packages/react/src/components/ListBox/ListBoxSelection.tsx index a6585889547f..f3c32675d41a 100644 --- a/packages/react/src/components/ListBox/ListBoxSelection.tsx +++ b/packages/react/src/components/ListBox/ListBoxSelection.tsx @@ -114,7 +114,6 @@ const ListBoxSelection: ListBoxSelectionComponent = ({ className={`${prefix}--tag__close-icon`} onClick={handleOnClick} onKeyDown={handleOnKeyDown} - // disabled={disabled} TODO determine if that's always been broken aria-label={t('clear.all')} title={description} aria-disabled={readOnly ? true : undefined}> From b5eabaee9f1dfa86e9a1844d9f31b4ede530086c Mon Sep 17 00:00:00 2001 From: Marcin Lewandowski Date: Thu, 26 Jan 2023 17:58:28 +0100 Subject: [PATCH 6/6] fix(formatting): run through yarn format --- .../react/src/components/Dropdown/index.ts | 2 +- .../src/components/ListBox/ListBoxPropTypes.ts | 8 ++++---- packages/react/src/components/ListBox/index.ts | 18 +++++++++++------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/react/src/components/Dropdown/index.ts b/packages/react/src/components/Dropdown/index.ts index 236faa32cdc6..b51047b15d72 100644 --- a/packages/react/src/components/Dropdown/index.ts +++ b/packages/react/src/components/Dropdown/index.ts @@ -8,7 +8,7 @@ import Dropdown, { OnChangeData } from './Dropdown'; export type { OnChangeData }; -export { Dropdown } +export { Dropdown }; export { default as DropdownSkeleton } from './Dropdown.Skeleton'; export default Dropdown; diff --git a/packages/react/src/components/ListBox/ListBoxPropTypes.ts b/packages/react/src/components/ListBox/ListBoxPropTypes.ts index 67e9c4aa245d..5bc72b729fef 100644 --- a/packages/react/src/components/ListBox/ListBoxPropTypes.ts +++ b/packages/react/src/components/ListBox/ListBoxPropTypes.ts @@ -7,11 +7,11 @@ import PropTypes from 'prop-types'; -const listBoxTypes = ['default', 'inline'] as const -const listBoxSizes = ['sm', 'md', 'lg'] as const +const listBoxTypes = ['default', 'inline'] as const; +const listBoxSizes = ['sm', 'md', 'lg'] as const; -export type ListBoxType = typeof listBoxTypes[number] -export type ListBoxSize = typeof listBoxSizes[number] +export type ListBoxType = typeof listBoxTypes[number]; +export type ListBoxSize = typeof listBoxSizes[number]; export const ListBoxType = PropTypes.oneOf(listBoxTypes); export const ListBoxSize = PropTypes.oneOf(listBoxSizes); diff --git a/packages/react/src/components/ListBox/index.ts b/packages/react/src/components/ListBox/index.ts index 39a663adb8d9..321bc9dd5374 100644 --- a/packages/react/src/components/ListBox/index.ts +++ b/packages/react/src/components/ListBox/index.ts @@ -8,19 +8,23 @@ export * as PropTypes from './ListBoxPropTypes'; export * from './ListBoxPropTypes'; -import ListBoxInternal, { ListBoxComponent as ListBoxPartialComponent } from './ListBox'; +import ListBoxInternal, { + ListBoxComponent as ListBoxPartialComponent, +} from './ListBox'; import ListBoxField, { ListBoxFieldComponent } from './ListBoxField'; import ListBoxMenu, { ListBoxMenuComponent } from './ListBoxMenu'; import ListBoxMenuIcon, { ListBoxMenuIconComponent } from './ListBoxMenuIcon'; import ListBoxMenuItem, { ListBoxMenuItemComponent } from './ListBoxMenuItem'; -import ListBoxSelection, { ListBoxSelectionComponent } from './ListBoxSelection'; +import ListBoxSelection, { + ListBoxSelectionComponent, +} from './ListBoxSelection'; export interface ListBoxComponent extends ListBoxPartialComponent { - readonly Field: ListBoxFieldComponent, - readonly Menu: ListBoxMenuComponent, - readonly MenuIcon: ListBoxMenuIconComponent, - readonly MenuItem: ListBoxMenuItemComponent, - readonly Selection: ListBoxSelectionComponent, + readonly Field: ListBoxFieldComponent; + readonly Menu: ListBoxMenuComponent; + readonly MenuIcon: ListBoxMenuIconComponent; + readonly MenuItem: ListBoxMenuItemComponent; + readonly Selection: ListBoxSelectionComponent; } const ListBox: ListBoxComponent = Object.assign(ListBoxInternal, {