diff --git a/frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx b/frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx index d7051ea2655..f1c9eb0d1a1 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsx @@ -643,7 +643,7 @@ export type UninstallOperatorModalProps = { resource: K8sResourceKind, data: { op: string; path: string; value: any }[], ) => Promise; - subscription: SubscriptionKind; + subscription: SubscriptionKind | K8sResourceKind; csv?: ClusterServiceVersionKind; blocking?: boolean; }; diff --git a/frontend/packages/operator-lifecycle-manager/src/components/subscription.tsx b/frontend/packages/operator-lifecycle-manager/src/components/subscription.tsx index facd0613273..a30b0bdbac8 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/subscription.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/subscription.tsx @@ -18,6 +18,7 @@ import { RowFunctionArgs, } from '@console/internal/components/factory'; import { + KebabAction, FieldLevelHelp, Kebab, LoadingInline, @@ -190,7 +191,7 @@ export const SubscriptionStatus: React.FC<{ subscription: SubscriptionKind }> = } }; -const menuActions = [ +const menuActions: KebabAction[] = [ Kebab.factory.Edit, (kind, obj) => ({ // t('olm~Remove Subscription') diff --git a/frontend/public/components/utils/dropdown.jsx b/frontend/public/components/utils/dropdown.jsx index 1669873842e..3153412981a 100644 --- a/frontend/public/components/utils/dropdown.jsx +++ b/frontend/public/components/utils/dropdown.jsx @@ -642,49 +642,104 @@ Dropdown.propTypes = { dataTest: PropTypes.string, }; -class ActionsMenuDropdown_ extends DropdownMixin { - render() { - const { actions, title = undefined, t } = this.props; - const onClick = (event, option) => { - event.preventDefault(); +const ActionsMenuDropdown = (props) => { + const { t } = useTranslation(); + const [active, setActive] = React.useState(!!props.active); + + const dropdownElement = React.useRef(); + + const show = () => { + setActive(true); + }; + + const hide = (e) => { + e?.stopPropagation(); + setActive(false); + }; + + const listener = React.useCallback( + (event) => { + if (!active) { + return; + } - if (option.callback) { - option.callback(); + const { current } = dropdownElement; + if (!current) { + return; } - if (option.href) { - history.push(option.href); + if (event.target === current || current.contains(event.target)) { + return; } - this.hide(); + hide(event); + }, + [active, dropdownElement], + ); + + React.useEffect(() => { + if (active) { + window.addEventListener('click', listener); + } else { + window.removeEventListener('click', listener); + } + return () => { + window.removeEventListener('click', listener); }; - return ( -
- - {this.state.active && } -
- ); - } -} + }, [active, listener]); + + const toggle = (e) => { + e.preventDefault(); + + if (props.disabled) { + return; + } + + if (active) { + hide(e); + } else { + show(e); + } + }; -const ActionsMenuDropdown = withTranslation()(ActionsMenuDropdown_); + const onClick = (event, option) => { + event.preventDefault(); + + if (option.callback) { + option.callback(); + } + + if (option.href) { + history.push(option.href); + } + + hide(); + }; + + return ( +
+ + {active && } +
+ ); +}; const ActionsMenu_ = ({ actions, impersonate, title = undefined }) => { const [isVisible, setVisible] = useSafetyFirst(false); diff --git a/frontend/public/components/utils/kebab.tsx b/frontend/public/components/utils/kebab.tsx index cd954e841b7..bb0d7db81ac 100644 --- a/frontend/public/components/utils/kebab.tsx +++ b/frontend/public/components/utils/kebab.tsx @@ -4,8 +4,8 @@ import * as React from 'react'; import * as classNames from 'classnames'; import { connect } from 'react-redux'; /* eslint-disable import/named */ -import { useTranslation, withTranslation, WithTranslation } from 'react-i18next'; -import i18next, { TFunction } from 'i18next'; +import { useTranslation } from 'react-i18next'; +import i18next from 'i18next'; import { KEY_CODES, Tooltip, FocusTrap } from '@patternfly/react-core'; import { AngleRightIcon, EllipsisVIcon } from '@patternfly/react-icons'; import { subscribeToExtensions } from '@console/plugin-sdk/src/api/pluginSubscriptionService'; @@ -486,53 +486,37 @@ export const ResourceKebab = connectToModel((props: ResourceKebabProps) => { ); }); -class KebabWithTranslation extends React.Component< - KebabProps & WithTranslation, - { active: boolean } -> { - static factory: KebabFactory = kebabFactory; - - // public static columnClass: string = 'pf-c-table__action'; - public static columnClass: string = 'dropdown-kebab-pf pf-c-table__action'; - - private dropdownElement = React.createRef(); - - private divElement = React.createRef(); - - constructor(props) { - super(props); - this.state = { - active: false, - }; - } +export const Kebab: KebabComponent = (props) => { + const { t } = useTranslation(); + const { options, isDisabled, terminatingTooltip } = props; + const dropdownElement = React.useRef(); + const divElement = React.useRef(); + const [active, setActive] = React.useState(false); - onClick = (event, option: KebabOption) => { + const onClick = (event, option: KebabOption) => { event.preventDefault(); - if (option.callback) { option.callback(); } - - this.hide(); - + hide(); if (option.href) { history.push(option.href); } }; - hide = () => { - this.dropdownElement.current && this.dropdownElement.current.focus(); - this.setState({ active: false }); + const hide = () => { + dropdownElement.current && dropdownElement.current.focus(); + setActive(false); }; - toggle = () => { - this.setState((state) => ({ active: !state.active })); + const toggle = () => { + setActive((prev) => !prev); }; - onHover = () => { + const onHover = () => { // Check access when hovering over a kebab to minimize flicker when opened. // This depends on `checkAccess` being memoized. - _.each(this.props.options, (option: KebabOption) => { + _.each(options, (option: KebabOption) => { if (option.accessReview) { checkAccess(option.accessReview).catch((e) => { // eslint-disable-next-line no-console @@ -542,90 +526,77 @@ class KebabWithTranslation extends React.Component< }); }; - handleRequestClose = (e?: MouseEvent) => { - if ( - !e || - !this.dropdownElement.current || - !this.dropdownElement.current.contains(e.target as Node) - ) { - this.hide(); + const handleRequestClose = (e?: MouseEvent) => { + if (!e || !dropdownElement.current || !dropdownElement.current.contains(e.target as Node)) { + hide(); } }; - getPopperReference = () => this.dropdownElement.current; + const getPopperReference = () => dropdownElement.current; - getDivReference = () => this.divElement.current; + const getDivReference = () => divElement.current; - render() { - const { options, isDisabled, t, terminatingTooltip } = this.props; - - const menuOptions = kebabOptionsToMenu(options); + const menuOptions = kebabOptionsToMenu(options); - return ( - +
-
- - + + + - -
- -
-
-
-
- - ); - } -} - -function restoreStaticProperties(Kebab) { - Kebab.factory = KebabWithTranslation.factory; - Kebab.getExtensionsActionsForKind = getExtensionsKebabActionsForKind; - Kebab.columnClass = KebabWithTranslation.columnClass; - return Kebab; -} - -export const Kebab = restoreStaticProperties(withTranslation()(KebabWithTranslation)); +
+ +
+ + +
+
+ ); +}; +Kebab.factory = kebabFactory; +Kebab.columnClass = 'dropdown-kebab-pf pf-c-table__action'; +Kebab.getExtensionsActionsForKind = getExtensionsKebabActionsForKind; export type KebabOption = { hidden?: boolean; @@ -672,12 +643,11 @@ type KebabSubMenu = { export type KebabMenuOption = KebabSubMenu | KebabOption; type KebabProps = { - factory: any; - t: TFunction; options: KebabOption[]; isDisabled?: boolean; - columnClass?: string; terminatingTooltip?: string; + active?: boolean; + id?: string; }; type KebabItemProps = { @@ -696,5 +666,12 @@ export type KebabItemsProps = { export type KebabFactory = { [name: string]: KebabAction } & { common?: KebabAction[] }; +type KebabStaticProperties = { + columnClass: string; + factory: KebabFactory; + getExtensionsActionsForKind: (kind: K8sKind) => KebabAction[]; +}; + +type KebabComponent = React.FC & KebabStaticProperties; KebabItems.displayName = 'KebabItems'; ResourceKebab.displayName = 'ResourceKebab'; diff --git a/frontend/public/components/volumes-table.tsx b/frontend/public/components/volumes-table.tsx index 2904bf35ac9..8dd9ccce91e 100644 --- a/frontend/public/components/volumes-table.tsx +++ b/frontend/public/components/volumes-table.tsx @@ -250,7 +250,9 @@ const VolumeKebab = connectToModel((props: VolumeKebabProps) => { return ( 0 + } /> ); });