diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2024-03-04 14:55:28 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-03-05 20:02:30 +0000 |
commit | 645082de0eba5c5a043328b2d55f3682f95a785d (patch) | |
tree | 2c7defa2beb6a5707410a252e1265324fc330b4e | |
parent | 3c3832f4bba46afe1a864321405cc7ac3a4c8f43 (diff) | |
download | sonarqube-645082de0eba5c5a043328b2d55f3682f95a785d.tar.gz sonarqube-645082de0eba5c5a043328b2d55f3682f95a785d.zip |
SONAR-21656 Remove legacy Dropdown component
7 files changed, 0 insertions, 636 deletions
diff --git a/server/sonar-web/src/main/js/components/controls/Dropdown.tsx b/server/sonar-web/src/main/js/components/controls/Dropdown.tsx deleted file mode 100644 index 96cf2efb049..00000000000 --- a/server/sonar-web/src/main/js/components/controls/Dropdown.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { Popup, PopupPlacement } from '../ui/popups'; -import ScreenPositionFixer from './ScreenPositionFixer'; - -interface OverlayProps { - className?: string; - children: React.ReactNode; - noPadding?: boolean; - placement?: PopupPlacement; - useEventBoundary?: boolean; -} - -export class DropdownOverlay extends React.Component<OverlayProps> { - get placement() { - return this.props.placement || PopupPlacement.Bottom; - } - - renderPopup = (leftFix?: number, topFix?: number) => ( - <Popup - arrowStyle={ - leftFix !== undefined && topFix !== undefined - ? { transform: `translate(${-leftFix}px, ${-topFix}px)` } - : undefined - } - className={this.props.className} - noPadding={this.props.noPadding} - placement={this.placement} - style={ - leftFix !== undefined && topFix !== undefined - ? { marginLeft: `calc(50% + ${leftFix}px)` } - : undefined - } - useEventBoundary={this.props.useEventBoundary} - > - {this.props.children} - </Popup> - ); - - render() { - if (this.placement === PopupPlacement.Bottom) { - return ( - <ScreenPositionFixer> - {({ leftFix, topFix }) => this.renderPopup(leftFix, topFix)} - </ScreenPositionFixer> - ); - } - return this.renderPopup(); - } -} diff --git a/server/sonar-web/src/main/js/components/controls/Toggler.tsx b/server/sonar-web/src/main/js/components/controls/Toggler.tsx deleted file mode 100644 index 125d7a63b7b..00000000000 --- a/server/sonar-web/src/main/js/components/controls/Toggler.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import DocumentClickHandler from './DocumentClickHandler'; -import EscKeydownHandler from './EscKeydownHandler'; -import FocusOutHandler from './FocusOutHandler'; -import OutsideClickHandler from './OutsideClickHandler'; -import UpDownKeyboardHanlder from './UpDownKeyboardHandler'; - -interface Props { - children?: React.ReactNode; - closeOnClick?: boolean; - closeOnClickOutside?: boolean; - closeOnEscape?: boolean; - closeOnFocusOut?: boolean; - navigateWithKeyboard?: boolean; - onRequestClose: () => void; - open: boolean; - overlay: React.ReactNode; -} - -export default class Toggler extends React.Component<Props> { - renderOverlay() { - const { - closeOnClick = false, - closeOnClickOutside = true, - closeOnEscape = true, - closeOnFocusOut = true, - navigateWithKeyboard = true, - onRequestClose, - overlay, - } = this.props; - - let renderedOverlay = overlay; - - if (navigateWithKeyboard) { - renderedOverlay = <UpDownKeyboardHanlder>{renderedOverlay}</UpDownKeyboardHanlder>; - } - - if (closeOnFocusOut) { - renderedOverlay = ( - <FocusOutHandler onFocusOut={onRequestClose}>{renderedOverlay}</FocusOutHandler> - ); - } - - if (closeOnEscape) { - renderedOverlay = ( - <EscKeydownHandler onKeydown={onRequestClose}>{renderedOverlay}</EscKeydownHandler> - ); - } - - if (closeOnClick) { - return ( - <DocumentClickHandler onClick={onRequestClose}>{renderedOverlay}</DocumentClickHandler> - ); - } else if (closeOnClickOutside) { - return ( - <OutsideClickHandler onClickOutside={onRequestClose}>{renderedOverlay}</OutsideClickHandler> - ); - } - return renderedOverlay; - } - - render() { - return ( - <> - {this.props.children} - {this.props.open && this.renderOverlay()} - </> - ); - } -} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/Toggler-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/Toggler-test.tsx deleted file mode 100644 index 4b8352ff59b..00000000000 --- a/server/sonar-web/src/main/js/components/controls/__tests__/Toggler-test.tsx +++ /dev/null @@ -1,237 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { render } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup'; -import * as React from 'react'; -import { byRole } from '../../../helpers/testSelector'; -import Toggler from '../Toggler'; - -const ui = { - toggleButton: byRole('button', { name: 'toggle' }), - outButton: byRole('button', { name: 'out' }), - overlayButton: byRole('button', { name: 'overlay' }), - nextOverlayButton: byRole('button', { name: 'next overlay' }), - overlayTextarea: byRole('textbox'), - overlayLatButton: byRole('button', { name: 'last' }), -}; - -async function openToggler(user: UserEvent) { - await user.click(ui.toggleButton.get()); - expect(ui.overlayButton.get()).toBeInTheDocument(); -} - -async function focusOut() { - await userEvent.click(ui.outButton.get()); -} - -it('should handle key up/down', async () => { - const user = userEvent.setup({ delay: null }); - const rerender = renderToggler( - {}, - <> - <textarea name="test-area" /> - <button type="button">last</button> - </>, - ); - - await openToggler(user); - await user.keyboard('{ArrowUp}'); - expect(ui.overlayLatButton.get()).toHaveFocus(); - - await user.keyboard('{ArrowUp}'); - expect(ui.overlayTextarea.get()).toHaveFocus(); - - // Focus does not escape multiline input - await user.keyboard('{ArrowDown}'); - expect(ui.overlayTextarea.get()).toHaveFocus(); - await user.keyboard('{ArrowUp}'); - expect(ui.overlayTextarea.get()).toHaveFocus(); - - // Escapt textarea - await user.keyboard('{Tab}'); - - // No focus change when using shortcut - await user.keyboard('{Control>}{ArrowUp}{/Control}'); - expect(ui.overlayLatButton.get()).toHaveFocus(); - - await user.keyboard('{ArrowDown}'); - expect(ui.overlayButton.get()).toHaveFocus(); - - await user.keyboard('{ArrowDown}'); - expect(ui.nextOverlayButton.get()).toHaveFocus(); - - rerender(); - await openToggler(user); - await user.keyboard('{ArrowDown}'); - expect(ui.overlayButton.get()).toHaveFocus(); -}); - -it('should handle escape correclty', async () => { - const user = userEvent.setup({ delay: null }); - const rerender = renderToggler({ - closeOnEscape: true, - closeOnClick: false, - closeOnClickOutside: false, - closeOnFocusOut: false, - }); - - await openToggler(user); - - await user.keyboard('{Escape}'); - expect(ui.overlayButton.query()).not.toBeInTheDocument(); - - rerender({ closeOnEscape: false }); - await openToggler(user); - - await user.keyboard('{Escape}'); - expect(ui.overlayButton.get()).toBeInTheDocument(); -}); - -it('should handle focus correctly', async () => { - const user = userEvent.setup({ delay: null }); - const rerender = renderToggler({ - closeOnEscape: false, - closeOnClick: false, - closeOnClickOutside: false, - closeOnFocusOut: true, - }); - - await openToggler(user); - - await focusOut(); - expect(ui.overlayButton.query()).not.toBeInTheDocument(); - - rerender({ closeOnFocusOut: false }); - await openToggler(user); - - await focusOut(); - expect(ui.overlayButton.get()).toBeInTheDocument(); -}); - -it('should handle click correctly', async () => { - const user = userEvent.setup({ delay: null }); - const rerender = renderToggler({ - closeOnEscape: false, - closeOnClick: true, - closeOnClickOutside: false, - closeOnFocusOut: false, - }); - - await openToggler(user); - - await user.click(ui.outButton.get()); - expect(ui.overlayButton.query()).not.toBeInTheDocument(); - - await openToggler(user); - - await user.click(ui.overlayButton.get()); - expect(ui.overlayButton.query()).not.toBeInTheDocument(); - - rerender({ closeOnClick: false }); - await openToggler(user); - - await user.click(ui.outButton.get()); - expect(ui.overlayButton.get()).toBeInTheDocument(); -}); - -it('should handle click outside correctly', async () => { - const user = userEvent.setup({ delay: null }); - const rerender = renderToggler({ - closeOnEscape: false, - closeOnClick: false, - closeOnClickOutside: true, - closeOnFocusOut: false, - }); - - await openToggler(user); - - await user.click(ui.overlayButton.get()); - expect(await ui.overlayButton.find()).toBeInTheDocument(); - - await user.click(ui.outButton.get()); - expect(ui.overlayButton.query()).not.toBeInTheDocument(); - - rerender({ closeOnClickOutside: false }); - await openToggler(user); - - await user.click(ui.outButton.get()); - expect(ui.overlayButton.get()).toBeInTheDocument(); -}); - -it('should open/close correctly when default props is applied', async () => { - const user = userEvent.setup({ delay: null }); - renderToggler(); - - await openToggler(user); - - // Should not close when on overlay - await user.click(ui.overlayButton.get()); - expect(await ui.overlayButton.find()).toBeInTheDocument(); - - // Focus out should close - await focusOut(); - expect(ui.overlayButton.query()).not.toBeInTheDocument(); - - await openToggler(user); - - // Escape should close - await user.keyboard('{Escape}'); - expect(ui.overlayButton.query()).not.toBeInTheDocument(); - - await openToggler(user); - - // Click should close (focus out is trigger first) - await user.click(ui.outButton.get()); - expect(ui.overlayButton.query()).not.toBeInTheDocument(); -}); - -function renderToggler(override?: Partial<Toggler['props']>, additionalOverlay?: React.ReactNode) { - function App(props: Partial<Toggler['props']>) { - const [open, setOpen] = React.useState(false); - - return ( - <> - <Toggler - onRequestClose={() => setOpen(false)} - open={open} - overlay={ - <div className="popup"> - <button type="button">overlay</button> - <button type="button">next overlay</button> - {additionalOverlay} - </div> - } - {...props} - > - <button onClick={() => setOpen(true)} type="button"> - toggle - </button> - </Toggler> - <button type="button">out</button> - </> - ); - } - - const { rerender } = render(<App {...override} />); - return function (reoverride?: Partial<Toggler['props']>) { - return rerender(<App {...override} {...reoverride} />); - }; -} diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx index 2e14e9daf2d..6daa8e98ec9 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx @@ -22,7 +22,6 @@ import * as React from 'react'; import { IssueActions } from '../../../types/issues'; import { Issue } from '../../../types/types'; import IssueAssign from './IssueAssign'; -import IssueCommentAction from './IssueCommentAction'; import IssueTags from './IssueTags'; import IssueTransition from './IssueTransition'; import SonarLintBadge from './SonarLintBadge'; @@ -50,16 +49,7 @@ export default function IssueActionsBar(props: Readonly<Props>) { canSetTags, } = props; - const [commentPlaceholder, setCommentPlaceholder] = React.useState(''); - - const toggleComment = (open: boolean, placeholder = '') => { - setCommentPlaceholder(placeholder); - - togglePopup('comment', open); - }; - const canAssign = issue.actions.includes(IssueActions.Assign); - const canComment = issue.actions.includes(IssueActions.Comment); const tagsPopupOpen = currentPopup === 'edit-tags' && canSetTags; return ( @@ -107,16 +97,6 @@ export default function IssueActionsBar(props: Readonly<Props>) { </li> )} </ul> - - {canComment && ( - <IssueCommentAction - commentPlaceholder={commentPlaceholder} - currentPopup={currentPopup === 'comment'} - issueKey={issue.key} - onChange={onChange} - toggleComment={toggleComment} - /> - )} </div> ); } diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueCommentAction.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueCommentAction.tsx deleted file mode 100644 index cae4574ae17..00000000000 --- a/server/sonar-web/src/main/js/components/issue/components/IssueCommentAction.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { addIssueComment, deleteIssueComment, editIssueComment } from '../../../api/issues'; -import Toggler from '../../../components/controls/Toggler'; -import { Issue } from '../../../types/types'; -import { updateIssue } from '../actions'; -import CommentPopup from '../popups/CommentPopup'; - -interface Props { - commentPlaceholder: string; - currentPopup?: boolean; - issueKey: string; - onChange: (issue: Issue) => void; - toggleComment: (open: boolean, placeholder?: string, autoTriggered?: boolean) => void; -} - -export default class IssueCommentAction extends React.PureComponent<Props> { - addComment = (text: string) => { - updateIssue(this.props.onChange, addIssueComment({ issue: this.props.issueKey, text })); - this.handleClose(); - }; - - handleEditComment = (comment: string, text: string) => { - updateIssue(this.props.onChange, editIssueComment({ comment, text })); - }; - - handleDeleteComment = (comment: string) => { - updateIssue(this.props.onChange, deleteIssueComment({ comment })); - }; - - handleClose = () => { - this.props.toggleComment(false); - }; - - render() { - return ( - <div className="issue-meta dropdown"> - <Toggler - closeOnClickOutside={false} - onRequestClose={this.handleClose} - open={!!this.props.currentPopup} - overlay={ - <CommentPopup - onComment={this.addComment} - placeholder={this.props.commentPlaceholder} - toggleComment={this.props.toggleComment} - /> - } - /> - </div> - ); - } -} diff --git a/server/sonar-web/src/main/js/components/issue/popups/CommentForm.tsx b/server/sonar-web/src/main/js/components/issue/popups/CommentForm.tsx deleted file mode 100644 index 617bbedef21..00000000000 --- a/server/sonar-web/src/main/js/components/issue/popups/CommentForm.tsx +++ /dev/null @@ -1,95 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { KeyboardKeys } from '../../../helpers/keycodes'; -import { translate } from '../../../helpers/l10n'; -import FormattingTips from '../../common/FormattingTips'; -import { Button, ResetButtonLink } from '../../controls/buttons'; - -export interface CommentFormProps { - comment?: string; - onCancel: () => void; - onSaveComment: (comment: string) => void; - placeholder?: string; - showFormatHelp: boolean; - autoTriggered?: boolean; -} - -export default function CommentForm(props: CommentFormProps) { - const { comment, placeholder, showFormatHelp, autoTriggered } = props; - const [editComment, setEditComment] = React.useState(comment || ''); - - return ( - <> - <div className="issue-comment-form-text"> - <textarea - autoFocus - className="sw-w-full" - style={{ resize: 'vertical' }} - placeholder={placeholder} - aria-label={translate('issue.comment.enter_comment')} - onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) => { - setEditComment(event.target.value); - }} - onKeyDown={(event: React.KeyboardEvent) => { - if (event.nativeEvent.key === KeyboardKeys.Enter && (event.metaKey || event.ctrlKey)) { - props.onSaveComment(editComment); - setEditComment(''); - } - }} - rows={2} - value={editComment} - /> - </div> - <div className="sw-flex sw-justify-between issue-comment-form-footer"> - {showFormatHelp && ( - <div className="issue-comment-form-tips"> - <FormattingTips /> - </div> - )} - <div> - <div className="issue-comment-form-actions"> - <Button - className="js-issue-comment-submit" - disabled={editComment.trim().length < 1} - onClick={() => { - props.onSaveComment(editComment); - setEditComment(''); - }} - > - {comment ? translate('save') : translate('issue.comment.formlink')} - </Button> - <ResetButtonLink - className="js-issue-comment-cancel little-spacer-left" - aria-label={ - comment - ? translate('issue.comment.edit.cancel') - : translate('issue.comment.add_comment.cancel') - } - onClick={props.onCancel} - > - {autoTriggered ? translate('skip') : translate('cancel')} - </ResetButtonLink> - </div> - </div> - </div> - </> - ); -} diff --git a/server/sonar-web/src/main/js/components/issue/popups/CommentPopup.tsx b/server/sonar-web/src/main/js/components/issue/popups/CommentPopup.tsx deleted file mode 100644 index 4c7bc87e4a4..00000000000 --- a/server/sonar-web/src/main/js/components/issue/popups/CommentPopup.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { DropdownOverlay } from '../../../components/controls/Dropdown'; -import { PopupPlacement } from '../../../components/ui/popups'; -import { IssueComment } from '../../../types/types'; -import CommentForm from './CommentForm'; - -export interface CommentPopupProps { - comment?: Pick<IssueComment, 'markdown'>; - onComment: (text: string) => void; - toggleComment: (visible: boolean) => void; - placeholder: string; - placement?: PopupPlacement; -} - -export default class CommentPopup extends React.PureComponent<CommentPopupProps> { - handleCancelClick = () => { - this.props.toggleComment(false); - }; - - render() { - const { comment } = this.props; - - return ( - <DropdownOverlay placement={this.props.placement}> - <div className="sw-min-w-abs-500 issue-comment-bubble-popup"> - <CommentForm - placeholder={this.props.placeholder} - onCancel={this.handleCancelClick} - onSaveComment={this.props.onComment} - showFormatHelp - comment={comment?.markdown} - /> - </div> - </DropdownOverlay> - ); - } -} |