From 02fb31ff8a33b098b4b66e292eb0ba5a841d7d28 Mon Sep 17 00:00:00 2001 From: Joseph Schultz Date: Thu, 21 Mar 2024 18:18:21 -0500 Subject: [PATCH] feat: add types to `ComboButton` Convert `ComboButton` to Typescript and export newly added interface `ComboButtonProps`. --- .../react/src/components/ComboButton/index.js | 216 -------------- .../src/components/ComboButton/index.tsx | 268 ++++++++++++++++++ 2 files changed, 268 insertions(+), 216 deletions(-) delete mode 100644 packages/react/src/components/ComboButton/index.js create mode 100644 packages/react/src/components/ComboButton/index.tsx diff --git a/packages/react/src/components/ComboButton/index.js b/packages/react/src/components/ComboButton/index.js deleted file mode 100644 index d6b2577bfcff..000000000000 --- a/packages/react/src/components/ComboButton/index.js +++ /dev/null @@ -1,216 +0,0 @@ -/** - * Copyright IBM Corp. 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, { useRef, useState } from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; - -import { ChevronDown } from '@carbon/icons-react'; -import { IconButton } from '../IconButton'; -import Button from '../Button'; -import { Menu } from '../Menu'; - -import { useAttachedMenu } from '../../internal/useAttachedMenu'; -import { useId } from '../../internal/useId'; -import { useMergedRefs } from '../../internal/useMergedRefs'; -import { usePrefix } from '../../internal/usePrefix'; - -const spacing = 0; // top and bottom spacing between the button and the menu. in px -const defaultTranslations = { - 'carbon.combo-button.additional-actions': 'Additional actions', -}; - -function defaultTranslateWithId(messageId) { - return defaultTranslations[messageId]; -} - -const ComboButton = React.forwardRef(function ComboButton( - { - children, - className, - disabled, - label, - onClick, - size = 'lg', - menuAlignment = 'bottom', - tooltipAlignment, - translateWithId: t = defaultTranslateWithId, - ...rest - }, - forwardRef -) { - const id = useId('combobutton'); - const prefix = usePrefix(); - const containerRef = useRef(null); - const menuRef = useRef(null); - const ref = useMergedRefs([forwardRef, containerRef]); - const [width, setWidth] = useState(0); - const { - open, - x, - y, - handleClick: hookOnClick, - handleMousedown: handleTriggerMousedown, - handleClose, - } = useAttachedMenu(containerRef); - - function handleTriggerClick() { - if (containerRef.current) { - const { width: w } = containerRef.current.getBoundingClientRect(); - setWidth(w); - hookOnClick(); - } - } - - function handlePrimaryActionClick(e) { - if (onClick) { - onClick(e); - } - } - - function handleOpen() { - menuRef.current.style.inlineSize = `${width}px`; - menuRef.current.style.minInlineSize = `${width}px`; - - if (menuAlignment !== 'bottom' && menuAlignment !== 'top') { - menuRef.current.style.inlineSize = `fit-content`; - } - } - - const containerClasses = classNames( - `${prefix}--combo-button__container`, - `${prefix}--combo-button__container--${size}`, - { - [`${prefix}--combo-button__container--open`]: open, - }, - className - ); - - const menuClasses = classNames(`${prefix}--combo-button__${menuAlignment}`); - - const primaryActionClasses = classNames( - `${prefix}--combo-button__primary-action` - ); - const triggerClasses = classNames(`${prefix}--combo-button__trigger`); - - return ( -
-
- -
- - - - - {children} - -
- ); -}); - -ComboButton.propTypes = { - /** - * A collection of MenuItems to be rendered as additional actions for this ComboButton. - */ - children: PropTypes.node.isRequired, - - /** - * Additional CSS class names. - */ - className: PropTypes.string, - - /** - * Specify whether the ComboButton should be disabled, or not. - */ - disabled: PropTypes.bool, - - /** - * Provide the label to be renderd on the primary action button. - */ - label: PropTypes.string.isRequired, - - /** - * Experimental property. Specify how the menu should align with the button element - */ - menuAlignment: PropTypes.oneOf([ - 'top', - 'top-start', - 'top-end', - 'bottom', - 'bottom-start', - 'bottom-end', - ]), - - /** - * Provide an optional function to be called when the primary action element is clicked. - */ - onClick: PropTypes.func, - - /** - * Specify the size of the buttons and menu. - */ - size: PropTypes.oneOf(['sm', 'md', 'lg']), - - /** - * Specify how the trigger tooltip should be aligned. - */ - tooltipAlignment: PropTypes.oneOf([ - 'top', - 'top-left', - 'top-start', - 'top-right', - 'top-end', - 'bottom', - 'bottom-left', - 'bottom-start', - 'bottom-right', - 'bottom-end', - 'left', - 'right', - ]), - - /** - * Optional method that takes in a message id and returns an - * internationalized string. - */ - translateWithId: PropTypes.func, -}; - -export { ComboButton }; diff --git a/packages/react/src/components/ComboButton/index.tsx b/packages/react/src/components/ComboButton/index.tsx new file mode 100644 index 000000000000..a2123440fa9e --- /dev/null +++ b/packages/react/src/components/ComboButton/index.tsx @@ -0,0 +1,268 @@ +/** + * Copyright IBM Corp. 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, { useRef, useState } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +import { ChevronDown } from '@carbon/icons-react'; +import { IconButton } from '../IconButton'; +import Button from '../Button'; +import { Menu } from '../Menu'; + +import { useAttachedMenu } from '../../internal/useAttachedMenu'; +import { useId } from '../../internal/useId'; +import { useMergedRefs } from '../../internal/useMergedRefs'; +import { usePrefix } from '../../internal/usePrefix'; + +const spacing = 0; // top and bottom spacing between the button and the menu. in px +const defaultTranslations = { + 'carbon.combo-button.additional-actions': 'Additional actions', +}; + +function defaultTranslateWithId(messageId: string) { + return defaultTranslations[messageId]; +} + +interface ComboButtonProps { + /** + * A collection of `MenuItems` to be rendered as additional actions for this `ComboButton`. + */ + children: React.ComponentProps['children']; + + /** + * Additional CSS class names. + */ + className?: string; + + /** + * Specify whether the `ComboButton` should be disabled, or not. + */ + disabled?: boolean; + + /** + * Provide the label to be rendered on the primary action button. + */ + label: React.ComponentProps['title']; + + /** + * Experimental property. Specify how the menu should align with the button element + */ + menuAlignment?: React.ComponentProps['menuAlignment']; + + /** + * Provide an optional function to be called when the primary action element is clicked. + */ + onClick?: React.ComponentProps['onClick']; + + /** + * Specify the size of the buttons and menu. + */ + size?: 'sm' | 'md' | 'lg'; + + /** + * Specify how the trigger tooltip should be aligned. + */ + tooltipAlignment?: React.ComponentProps['align']; + + /** + * Optional method that takes in a message `id` and returns an + * internationalized string. + */ + translateWithId?: (id: string) => string; +} + +const ComboButton = React.forwardRef( + function ComboButton( + { + children, + className, + disabled, + label, + onClick, + size = 'lg', + menuAlignment = 'bottom', + tooltipAlignment, + translateWithId: t = defaultTranslateWithId, + ...rest + }, + forwardRef + ) { + const id = useId('combobutton'); + const prefix = usePrefix(); + const containerRef = useRef(null); + const menuRef = useRef>(null); + const ref = useMergedRefs([forwardRef, containerRef]); + const [width, setWidth] = useState(0); + const { + open, + x, + y, + handleClick: hookOnClick, + handleMousedown: handleTriggerMousedown, + handleClose, + } = useAttachedMenu(containerRef); + + function handleTriggerClick() { + if (containerRef.current) { + const { width: w } = containerRef.current.getBoundingClientRect(); + setWidth(w); + hookOnClick(); + } + } + + function handlePrimaryActionClick(e: React.MouseEvent) { + if (onClick) { + onClick(e); + } + } + + function handleOpen() { + if (menuRef.current) { + menuRef.current.style.inlineSize = `${width}px`; + menuRef.current.style.minInlineSize = `${width}px`; + + if (menuAlignment !== 'bottom' && menuAlignment !== 'top') { + menuRef.current.style.inlineSize = `fit-content`; + } + } + } + + const containerClasses = classNames( + `${prefix}--combo-button__container`, + `${prefix}--combo-button__container--${size}`, + { + [`${prefix}--combo-button__container--open`]: open, + }, + className + ); + + const menuClasses = classNames(`${prefix}--combo-button__${menuAlignment}`); + + const primaryActionClasses = classNames( + `${prefix}--combo-button__primary-action` + ); + const triggerClasses = classNames(`${prefix}--combo-button__trigger`); + + return ( +
+
+ +
+ + + + + {children} + +
+ ); + } +); + +ComboButton.propTypes = { + /** + * A collection of MenuItems to be rendered as additional actions for this ComboButton. + */ + children: PropTypes.node.isRequired, + + /** + * Additional CSS class names. + */ + className: PropTypes.string, + + /** + * Specify whether the ComboButton should be disabled, or not. + */ + disabled: PropTypes.bool, + + /** + * Provide the label to be rendered on the primary action button. + */ + label: PropTypes.string.isRequired, + + /** + * Experimental property. Specify how the menu should align with the button element + */ + menuAlignment: PropTypes.oneOf([ + 'top', + 'top-start', + 'top-end', + 'bottom', + 'bottom-start', + 'bottom-end', + ]), + + /** + * Provide an optional function to be called when the primary action element is clicked. + */ + onClick: PropTypes.func, + + /** + * Specify the size of the buttons and menu. + */ + size: PropTypes.oneOf(['sm', 'md', 'lg']), + + /** + * Specify how the trigger tooltip should be aligned. + */ + tooltipAlignment: PropTypes.oneOf([ + 'top', + 'top-left', + 'top-start', + 'top-right', + 'top-end', + 'bottom', + 'bottom-left', + 'bottom-start', + 'bottom-right', + 'bottom-end', + 'left', + 'right', + ]), + + /** + * Optional method that takes in a message id and returns an + * internationalized string. + */ + translateWithId: PropTypes.func, +}; + +export { ComboButton, type ComboButtonProps };