controlLabel?: React.ReactNode | string;
controlSize?: InputSizeKeys;
isDiscreet?: boolean;
+ zLevel?: PopupZLevel;
}
export function SearchSelectDropdown<
minLength,
controlAriaLabel,
menuIsOpen,
+ zLevel = PopupZLevel.Global,
...rest
} = props;
const [open, setOpen] = React.useState(false);
</SearchHighlighterContext.Provider>
}
placement={PopupPlacement.BottomLeft}
- zLevel={PopupZLevel.Global}
+ zLevel={zLevel}
>
<SearchSelectDropdownControl
ariaLabel={controlAriaLabel}
import { createPortal, findDOMNode } from 'react-dom';
import tw from 'twin.macro';
import { THROTTLE_SCROLL_DELAY } from '../helpers/constants';
-import { PopupPlacement, popupPositioning, PopupZLevel } from '../helpers/positioning';
+import { PopupPlacement, PopupZLevel, popupPositioning } from '../helpers/positioning';
import { themeBorder, themeColor, themeContrast, themeShadow } from '../helpers/theme';
import ClickEventBoundary from './ClickEventBoundary';
};
positionPopup = () => {
- if (this.mounted) {
+ if (this.mounted && this.props.zLevel !== PopupZLevel.Absolute) {
// `findDOMNode(this)` will search for the DOM node for the current component
// first it will find a React.Fragment (see `render`),
// so it will get the DOM node of the first child, i.e. DOM node of `this.props.children`
[PopupZLevel.Default]: tw`sw-z-popup`,
[PopupZLevel.Global]: tw`sw-z-global-popup`,
[PopupZLevel.Content]: tw`sw-z-content-popup`,
+ [PopupZLevel.Absolute]: tw`sw-z-global-popup`,
}[zLevel])};
&.is-bottom,
Content = 'content',
Default = 'popup',
Global = 'global',
+ Absolute = 'absolute',
}
export type BasePlacement = Extract<
}
handleIssuePopupToggle = (popupName: string, open?: boolean) => {
- const openPopupState = { issuePopupName: popupName };
-
- const closePopupState = { issuePopupName: undefined };
-
this.setState(({ issuePopupName }) => {
- if (open) {
- return openPopupState;
- } else if (open === false) {
- return closePopupState;
+ const samePopup = popupName && issuePopupName === popupName;
+ if (open !== false && !samePopup) {
+ return { issuePopupName: popupName };
+ } else if (open !== true && samePopup) {
+ return { issuePopupName: undefined };
}
-
- // toggle popup
- return issuePopupName === popupName ? closePopupState : openPopupState;
+ return { issuePopupName };
});
};
}
};
+ selectIssue = (issueKey: string) => {
+ this.setState({
+ selected: issueKey,
+ selectedFlowIndex: undefined,
+ selectedLocationIndex: undefined,
+ });
+ };
+
closeIssue = () => {
if (this.state.query) {
this.props.router.push({
onIssueChange={this.handleIssueChange}
onIssueCheck={currentUser.isLoggedIn ? this.handleIssueCheck : undefined}
onIssueClick={this.openIssue}
+ onIssueSelect={this.selectIssue}
onPopupToggle={this.handlePopupToggle}
openPopup={this.state.openPopup}
selectedIssue={selectedIssue}
onIssueChange: (issue: Issue) => void;
onIssueCheck: ((issueKey: string) => void) | undefined;
onIssueClick: (issueKey: string) => void;
+ onIssueSelect: (issueKey: string) => void;
onPopupToggle: (issue: string, popupName: string, open?: boolean) => void;
openPopup: { issue: string; name: string } | undefined;
selectedIssue: Issue | undefined;
onChange={this.props.onIssueChange}
onCheck={this.props.onIssueCheck}
onClick={this.props.onIssueClick}
+ onSelect={this.props.onIssueSelect}
onFilterChange={this.props.onFilterChange}
onPopupToggle={this.props.onPopupToggle}
openPopup={openPopup && openPopup.issue === issue.key ? openPopup.name : undefined}
onChange: (issue: TypeIssue) => void;
onCheck: ((issueKey: string) => void) | undefined;
onClick: (issueKey: string) => void;
+ onSelect: (issueKey: string) => void;
onFilterChange: (changes: Partial<Query>) => void;
onPopupToggle: (issue: string, popupName: string, open?: boolean) => void;
openPopup: string | undefined;
onChange={this.props.onChange}
onCheck={this.props.onCheck}
onClick={this.props.onClick}
+ onSelect={this.props.onSelect}
onPopupToggle={this.props.onPopupToggle}
openPopup={this.props.openPopup}
selected={this.props.selected}
issue={issue}
key={issue.key}
onChange={props.onIssueChange}
- onClick={props.onIssueClick}
+ onSelect={props.onIssueClick}
onPopupToggle={props.onIssuePopupToggle}
openPopup={issuePopup && issuePopup.issue === issue.key ? issuePopup.name : undefined}
selected={props.selectedIssue === issue.key}
}
key="issue"
onChange={[MockFunction]}
- onClick={[MockFunction]}
onPopupToggle={[MockFunction]}
+ onSelect={[MockFunction]}
selected={true}
/>
</div>
onChange: (issue: TypeIssue) => void;
onCheck?: (issue: string) => void;
onClick?: (issueKey: string) => void;
+ onSelect: (issueKey: string) => void;
onPopupToggle: (issue: string, popupName: string, open?: boolean) => void;
openPopup?: string;
selected: boolean;
onChange={this.props.onChange}
onCheck={this.props.onCheck}
onClick={this.props.onClick}
+ onSelect={this.props.onSelect}
selected={this.props.selected}
togglePopup={this.togglePopup}
/>
const { ui } = getPageObject();
const issue = mockIssue(true, { effort: '2 days', message: 'This is an issue' });
const onClick = jest.fn();
- renderIssue({ issue, onClick });
+ renderIssue({ issue, onSelect: onClick });
expect(ui.effort('2 days').get()).toBeInTheDocument();
await ui.clickIssueMessage();
);
}
- return renderApp('/', <Wrapper issue={mockIssue()} selected={false} {...props} />, {
- currentUser: mockLoggedInUser({ login: 'leia', name: 'Organa' }),
- });
+ return renderApp(
+ '/',
+ <Wrapper onSelect={jest.fn()} issue={mockIssue()} selected={false} {...props} />,
+ {
+ currentUser: mockLoggedInUser({ login: 'leia', name: 'Organa' }),
+ }
+ );
}
<IssueCommentAction
commentAutoTriggered={commentState.commentAutoTriggered}
commentPlaceholder={commentState.commentPlaceholder}
- currentPopup={currentPopup}
+ currentPopup={currentPopup === 'comment'}
issueKey={issue.key}
onChange={onChange}
toggleComment={toggleComment}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { LabelValueSelectOption, SearchSelectDropdown } from 'design-system';
+import { LabelValueSelectOption, PopupZLevel, SearchSelectDropdown } from 'design-system';
import * as React from 'react';
import { Options, SingleValue } from 'react-select';
import { searchUsers } from '../../../api/users';
}
return (
- <SearchSelectDropdown
- size="medium"
- className="it__issue-assign"
- controlAriaLabel={
- assinedUser
- ? translateWithParameters('issue.assign.assigned_to_x_click_to_change', assinedUser)
- : translate('issue.assign.unassigned_click_to_assign')
- }
- defaultOptions={defaultOptions}
- onChange={handleAssign}
- loadOptions={handleSearchAssignees}
- menuIsOpen={props.isOpen}
- minLength={minSearchLength}
- onMenuOpen={() => toggleAssign(true)}
- onMenuClose={handleClose}
- isDiscreet
- controlLabel={controlLabel}
- tooShortText={translateWithParameters('search.tooShort', String(minSearchLength))}
- placeholder={translate('search.search_for_users')}
- aria-label={translate('search.search_for_users')}
- />
+ <div className="sw-relative">
+ <SearchSelectDropdown
+ size="medium"
+ className="it__issue-assign"
+ controlAriaLabel={
+ assinedUser
+ ? translateWithParameters('issue.assign.assigned_to_x_click_to_change', assinedUser)
+ : translate('issue.assign.unassigned_click_to_assign')
+ }
+ defaultOptions={defaultOptions}
+ onChange={handleAssign}
+ loadOptions={handleSearchAssignees}
+ menuIsOpen={props.isOpen}
+ minLength={minSearchLength}
+ onMenuOpen={() => toggleAssign(true)}
+ onMenuClose={handleClose}
+ isDiscreet
+ controlLabel={controlLabel}
+ tooShortText={translateWithParameters('search.tooShort', String(minSearchLength))}
+ placeholder={translate('search.search_for_users')}
+ aria-label={translate('search.search_for_users')}
+ zLevel={PopupZLevel.Absolute}
+ />
+ </div>
);
}
canComment: boolean;
commentAutoTriggered?: boolean;
commentPlaceholder: string;
- currentPopup?: string;
+ currentPopup?: boolean;
issueKey: string;
onChange: (issue: Issue) => void;
toggleComment: (open?: boolean, placeholder?: string, autoTriggered?: boolean) => void;
<Toggler
closeOnClickOutside={false}
onRequestClose={this.handleClose}
- open={this.props.currentPopup === 'comment'}
+ open={!!this.props.currentPopup}
overlay={
showCommentsInPopup ? (
<CommentListPopup
import { translate } from '../../../helpers/l10n';
import { getComponentIssuesUrl } from '../../../helpers/urls';
import { BranchLike } from '../../../types/branch-like';
+import { IssueType } from '../../../types/issues';
import { Issue } from '../../../types/types';
import { IssueMessageHighlighting } from '../IssueMessageHighlighting';
export interface IssueMessageProps {
- onClick?: () => void;
issue: Issue;
branchLike?: BranchLike;
displayWhyIsThisAnIssue?: boolean;
why: '1',
});
+ const issueUrl = getComponentIssuesUrl(issue.project, {
+ ...getBranchLikeQuery(branchLike),
+ open: issue.key,
+ types: issue.type === IssueType.SecurityHotspot ? issue.type : undefined,
+ });
return (
<>
- {props.onClick ? (
- <StandoutLink onClick={props.onClick} className="it__issue-message" preventDefault to={{}}>
+ {issueUrl?.pathname ? (
+ <StandoutLink className="it__issue-message" to={issueUrl}>
<IssueMessageHighlighting message={message} messageFormattings={messageFormattings} />
</StandoutLink>
) : (
export interface IssueTitleBarProps {
currentPopup?: string;
branchLike?: BranchLike;
- onClick?: () => void;
displayWhyIsThisAnIssue?: boolean;
issue: Issue;
onChange: (issue: Issue) => void;
issue={issue}
branchLike={props.branchLike}
displayWhyIsThisAnIssue={displayWhyIsThisAnIssue}
- onClick={props.onClick}
/>
</div>
<div className="js-issue-tags sw-body-sm sw-grow-0 sw-whitespace-nowrap">
onAssign: (login: string) => void;
onChange: (issue: Issue) => void;
onCheck?: (issue: string) => void;
+ onSelect: (issueKey: string) => void;
onClick?: (issueKey: string) => void;
selected: boolean;
togglePopup: (popup: string, show: boolean | void) => void;
componentDidMount() {
const { selected } = this.props;
if (this.nodeRef && selected) {
- this.nodeRef.scrollIntoView({ block: 'center', inline: 'center' });
+ this.nodeRef.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
}
}
componentDidUpdate(prevProps: Props) {
const { selected } = this.props;
if (!prevProps.selected && selected && this.nodeRef) {
- this.nodeRef.scrollIntoView({ block: 'center', inline: 'center' });
+ this.nodeRef.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
}
}
}
};
- handleBoxClick = (event: React.MouseEvent<HTMLDivElement>) => {
- if (!isClickable(event.target as HTMLElement) && this.props.onClick) {
- event.preventDefault();
- this.handleDetailClick();
- }
- };
-
- handleDetailClick = () => {
- if (this.props.onClick) {
- this.props.onClick(this.props.issue.key);
- }
- };
-
editComment = (comment: string, text: string) => {
updateIssue(this.props.onChange, editIssueComment({ comment, text }));
};
return (
<IssueItem
+ onClick={() => this.props.onSelect(issue.key)}
className={issueClass}
role="region"
aria-label={issue.message}
<IssueTitleBar
currentPopup={currentPopup}
branchLike={branchLike}
- onClick={this.handleDetailClick}
displayWhyIsThisAnIssue={displayWhyIsThisAnIssue}
issue={issue}
onChange={this.props.onChange}
}
}
-function isClickable(node: HTMLElement | undefined | null): boolean {
- if (!node) {
- return false;
- }
- const clickableTags = ['A', 'BUTTON', 'INPUT', 'TEXTAREA'];
- const tagName = (node.tagName || '').toUpperCase();
- return clickableTags.includes(tagName) || isClickable(node.parentElement);
-}
-
const IssueItem = styled.li`
box-sizing: border-box;
border: ${themeBorder('default', 'transparent')};