import { Component, forwardRef, isValidElement } from 'react'; import styles from '@patternfly/react-styles/css/components/MenuToggle/menu-toggle'; import { css } from '@patternfly/react-styles'; import RhMicronsCaretDownFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-microns-caret-down-fill-icon'; import { BadgeProps } from '../Badge'; import RhUiSettingsFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-settings-fill-icon'; import RhUiCheckCircleFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-check-circle-fill-icon'; import RhUiErrorFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-error-fill-icon'; import RhUiWarningFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-warning-fill-icon'; import { OUIAProps, getOUIAProps } from '../../helpers'; import { SSRSafeIds } from '../../helpers/SSRSafeIds/SSRSafeIds'; export enum MenuToggleStatus { success = 'success', danger = 'danger', warning = 'warning' } export enum MenuToggleSize { default = 'default', sm = 'sm' } export type MenuToggleElement = HTMLDivElement | HTMLButtonElement; export interface MenuToggleProps extends Omit< React.DetailedHTMLProps< React.ButtonHTMLAttributes & React.HTMLAttributes, MenuToggleElement >, 'ref' >, OUIAProps { /** Content rendered inside the toggle */ children?: React.ReactNode; /** Additional classes added to the toggle */ className?: string; /** Flag indicating the toggle has expanded styling */ isExpanded?: boolean; /** Flag indicating the toggle is disabled */ isDisabled?: boolean; /** Flag indicating the toggle is full height */ isFullHeight?: boolean; /** Flag indicating the toggle takes up the full width of its parent */ isFullWidth?: boolean; /** @beta Flag indicating the toggle is placed inside a form */ isInForm?: boolean; /** Flag indicating the toggle contains placeholder text */ isPlaceholder?: boolean; /** @beta Flag indicating the toggle has circular styling. Can only be applied to plain toggles. */ isCircle?: boolean; /** Flag indicating whether the toggle is a settings toggle. This will override the icon property */ isSettings?: boolean; /** @beta Flag indicating the menu toggle is a docked variant. For use in docked navigation. */ isDocked?: boolean; /** @beta Flag indicating the docked toggle should display text. Only applies when isDocked is true. */ isTextExpanded?: boolean; /** Elements to display before the toggle button. When included, renders the menu toggle as a split button. */ splitButtonItems?: React.ReactNode[]; /** Variant styles of the menu toggle */ variant?: 'default' | 'plain' | 'primary' | 'plainText' | 'secondary' | 'typeahead'; /** Status styles of the menu toggle */ status?: 'success' | 'warning' | 'danger'; /** Overrides the status icon */ statusIcon?: React.ReactNode; /** Optional icon or image rendered inside the toggle, before the children content. It is * recommended to wrap most basic icons in our icon component. */ icon?: React.ReactNode; /** Optional badge rendered inside the toggle, after the children content */ badge?: BadgeProps | React.ReactNode; /** Adds styling which affects the size of the menu toggle */ size?: 'default' | 'sm'; /** @hide Forwarded ref */ innerRef?: React.Ref; /** Value to overwrite the randomly generated data-ouia-component-id. It will always target the toggle button. */ ouiaId?: number | string; /** Set the value of data-ouia-safe. Only set to true when the component is in a static state, i.e. no animations are occurring. At all other times, this value must be false. */ ouiaSafe?: boolean; } class MenuToggleBase extends Component { displayName = 'MenuToggleBase'; static defaultProps: MenuToggleProps = { className: '', isExpanded: false, isDisabled: false, isFullWidth: false, isFullHeight: false, isInForm: false, isPlaceholder: false, isCircle: false, isDocked: false, isTextExpanded: false, size: 'default', ouiaSafe: true }; render() { const { children, className, icon, badge, isExpanded, isDisabled, isFullHeight, isFullWidth, isInForm, isPlaceholder, isCircle, isSettings, isDocked, isTextExpanded, splitButtonItems, variant, status, statusIcon, innerRef, onClick, 'aria-label': ariaLabel, ouiaId, ouiaSafe, size, ...otherProps } = this.props; const isPlain = variant === 'plain'; const isPlainText = variant === 'plainText'; const isTypeahead = variant === 'typeahead'; return ( {(_, generatedOuiaId) => { const ouiaProps = getOUIAProps(MenuToggle.displayName, ouiaId ?? generatedOuiaId, ouiaSafe); let _statusIcon = statusIcon; if (!statusIcon) { switch (status) { case MenuToggleStatus.success: _statusIcon = ; break; case MenuToggleStatus.warning: _statusIcon = ; break; case MenuToggleStatus.danger: _statusIcon = ; break; } } const toggleControls = ( {status !== undefined && {_statusIcon}} ); const content = ( <> {(icon || isSettings) && ( {isSettings ? : icon} )} {isTypeahead ? children : children && {children}} {isValidElement(badge) && {badge}} {isTypeahead ? ( ) : ( !isPlain && toggleControls )} ); const commonStyles = css( styles.menuToggle, isExpanded && styles.modifiers.expanded, variant === 'primary' && styles.modifiers.primary, variant === 'secondary' && styles.modifiers.secondary, status && styles.modifiers[status], (isPlain || isPlainText) && styles.modifiers.plain, isPlainText && styles.modifiers.text, isFullHeight && styles.modifiers.fullHeight, isFullWidth && styles.modifiers.fullWidth, isInForm && styles.modifiers.form, isDisabled && styles.modifiers.disabled, isPlaceholder && styles.modifiers.placeholder, isSettings && styles.modifiers.settings, isDocked && styles.modifiers.docked, isDocked && isTextExpanded && styles.modifiers.textExpanded, size === MenuToggleSize.sm && styles.modifiers.small, className ); const componentProps = { children: content, ...(isDisabled && { disabled: true }), ...otherProps }; if (isTypeahead) { return (
} className={css(commonStyles, styles.modifiers.typeahead)} {...componentProps} /> ) as React.ReactElement; } if (splitButtonItems) { return (
} className={css(commonStyles, styles.modifiers.splitButton)} > {splitButtonItems}
) as React.ReactElement; } return (