--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 styled from '@emotion/styled';
+import classNames from 'classnames';
+import { ReactNode } from 'react';
+import tw from 'twin.macro';
+import { themeBorder, themeColor, themeContrast } from '../helpers/theme';
+import { BareButton } from './buttons';
+import { OpenCloseIndicator } from './icons/OpenCloseIndicator';
+
+interface Props {
+ children: ReactNode;
+ expanded?: boolean;
+ header: ReactNode;
+ id: string;
+ innerRef?: (node: HTMLDivElement) => void;
+ onClick?: () => void;
+}
+
+export function ExecutionFlowAccordion(props: Props) {
+ const { children, expanded, header, id, innerRef, onClick } = props;
+
+ return (
+ <Accordion className={classNames({ expanded })} ref={innerRef}>
+ <Expander
+ aria-controls={`${id}-flow-accordion`}
+ aria-expanded={expanded}
+ id={`${id}-flow-accordion-button`}
+ onClick={onClick}
+ >
+ {header}
+ <OpenCloseIndicator open={Boolean(expanded)} />
+ </Expander>
+
+ {expanded && <Body id={`${id}-flow-accordion-body`}>{children}</Body>}
+ </Accordion>
+ );
+}
+
+const Expander = styled(BareButton)`
+ ${tw`sw-flex sw-items-center sw-justify-between`}
+ ${tw`sw-box-border`}
+ ${tw`sw-p-2`}
+ ${tw`sw-w-full`}
+ ${tw`sw-cursor-pointer`}
+
+ color: ${themeContrast('subnavigationExecutionFlow')};
+ background-color: ${themeColor('subnavigationExecutionFlow')};
+`;
+
+const Accordion = styled.div`
+ ${tw`sw-flex sw-flex-col`}
+ ${tw`sw-rounded-1/2`}
+
+ border: ${themeBorder('default', 'subnavigationExecutionFlowBorder')};
+
+ &:hover {
+ border: ${themeBorder('default', 'subnavigationExecutionFlowActive')};
+ }
+
+ &.expanded {
+ border: ${themeBorder('default', 'subnavigationExecutionFlowActive')};
+ outline: ${themeBorder('focus', 'primary')};
+
+ ${Expander} {
+ border-bottom: ${themeBorder('default', 'subnavigationExecutionFlowBorder')};
+ }
+ }
+`;
+
+const Body = styled.div`
+ ${tw`sw-p-2`}
+
+ background-color: ${themeColor('subnavigationExecutionFlow')};
+`;
+
+ExecutionFlowAccordion.displayName = 'ExecutionFlowAccordion';
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 styled from '@emotion/styled';
+import tw from 'twin.macro';
+import { themeColor } from '../helpers/theme';
+import { BaseLink } from './Link';
+import { LocationMarker, StyledMarker } from './LocationMarker';
+
+interface Props {
+ additionalMarkers?: React.ReactNode;
+ className?: string;
+ message?: string;
+ onClick?: () => void;
+ selected: boolean;
+ step?: number;
+}
+
+export function FlowStep(props: Props) {
+ const { additionalMarkers, className, message, selected, step } = props;
+
+ return (
+ <StyledLink className={className} onClick={props.onClick} to={{}}>
+ <>
+ <LocationMarker selected={selected} text={step} />
+ {additionalMarkers}
+ </>
+ <span>{message}</span>
+ </StyledLink>
+ );
+}
+
+const StyledLink = styled(BaseLink)`
+ ${tw`sw-p-1 sw-rounded-1/2`}
+ ${tw`sw-flex sw-items-center sw-flex-wrap sw-gap-2`}
+ ${tw`sw-body-sm`}
+
+ color: ${themeColor('pageContent')};
+ border-bottom: none;
+
+ &.selected,
+ &:hover {
+ background-color: ${themeColor('codeLineLocationSelected')};
+ }
+
+ &:hover ${StyledMarker} {
+ background-color: ${themeColor('codeLineLocationMarkerSelected')};
+ }
+`;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 styled from '@emotion/styled';
+import classNames from 'classnames';
+import { forwardRef, LegacyRef } from 'react';
+import tw from 'twin.macro';
+import { themeColor, themeContrast } from '../helpers/theme';
+import { isDefined } from '../helpers/types';
+import { IssueLocationIcon } from './icons/IssueLocationIcon';
+
+interface Props {
+ className?: string;
+ onClick?: () => void;
+ selected: boolean;
+ text?: number | string;
+}
+
+function InternalLocationMarker(
+ { className, onClick, text, selected }: Props,
+ ref: LegacyRef<HTMLDivElement>
+) {
+ return (
+ <StyledMarker
+ className={classNames(className, {
+ selected,
+ concealed: !isDefined(text),
+ 'sw-cursor-pointer': isDefined(onClick),
+ })}
+ onClick={onClick}
+ ref={ref}
+ >
+ {isDefined(text) ? text : <IssueLocationIcon />}
+ </StyledMarker>
+ );
+}
+
+export const LocationMarker = forwardRef<HTMLDivElement, Props>(InternalLocationMarker);
+
+export const StyledMarker = styled.div`
+ ${tw`sw-flex sw-grow-0 sw-items-center sw-justify-center`}
+ ${tw`sw-body-sm-highlight`}
+ ${tw`sw-rounded-1/2`}
+
+ height: 1.125rem;
+ color: ${themeContrast('codeLineLocationMarker')};
+ background-color: ${themeColor('codeLineLocationMarker')};
+
+ &.selected,
+ &:hover {
+ background-color: ${themeColor('codeLineLocationMarkerSelected')};
+ }
+
+ &:not(.concealed) {
+ ${tw`sw-px-1`}
+ ${tw`sw-self-start`}
+ }
+`;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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, screen } from '@testing-library/react';
+import { FCProps } from '../../types/misc';
+import { ExecutionFlowAccordion } from '../ExecutionFlowAccordion';
+
+it('should render correctly', () => {
+ renderExecutionFlowAccordion({}, <div>flow-accordion-children</div>);
+ expect(screen.queryByText('flow-accordion-children')).not.toBeInTheDocument();
+});
+
+it('should render correctly expanded', () => {
+ renderExecutionFlowAccordion({ expanded: true }, <div>flow-accordion-children</div>);
+ expect(screen.getByText('flow-accordion-children')).toBeVisible();
+});
+
+function renderExecutionFlowAccordion(
+ props: Partial<FCProps<typeof ExecutionFlowAccordion>> = {},
+ children?: React.ReactNode
+) {
+ return render(
+ <ExecutionFlowAccordion header="header" id="id" {...props}>
+ {children}
+ </ExecutionFlowAccordion>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { screen } from '@testing-library/react';
+import { renderWithRouter } from '../../helpers/testUtils';
+import { FCProps } from '../../types/misc';
+import { FlowStep } from '../FlowStep';
+
+it('should render correctly', () => {
+ renderFlowStep({ step: 3, additionalMarkers: <span>additional</span> });
+
+ expect(screen.getByText('3')).toBeInTheDocument();
+ expect(screen.getByText('additional')).toBeInTheDocument();
+});
+
+function renderFlowStep(props: Partial<FCProps<typeof FlowStep>> = {}) {
+ return renderWithRouter(<FlowStep message="" selected={false} {...props} />);
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { useTheme } from '@emotion/react';
+import { themeColor } from '../../helpers/theme';
+import { CustomIcon, IconProps } from './Icon';
+
+export function IssueLocationIcon({ fill = 'currentColor', ...iconProps }: IconProps) {
+ const theme = useTheme();
+ const fillColor = themeColor(fill)({ theme });
+
+ return (
+ <CustomIcon {...iconProps}>
+ <path
+ clipRule="evenodd"
+ d="M8 11C8.79565 11 9.55871 10.6839 10.1213 10.1213C10.6839 9.55871 11 8.79565 11 8C11 7.20435 10.6839 6.44129 10.1213 5.87868C9.55871 5.31607 8.79565 5 8 5C7.20435 5 6.44129 5.31607 5.87868 5.87868C5.31607 6.44129 5 7.20435 5 8C5 8.79565 5.31607 9.55871 5.87868 10.1213C6.44129 10.6839 7.20435 11 8 11ZM8 7C7.73478 7 7.48043 7.10536 7.29289 7.29289C7.10536 7.48043 7 7.73478 7 8C7 8.26522 7.10536 8.51957 7.29289 8.70711C7.48043 8.89464 7.73478 9 8 9C8.26522 9 8.51957 8.89464 8.70711 8.70711C8.89464 8.51957 9 8.26522 9 8C9 7.73478 8.89464 7.48043 8.70711 7.29289C8.51957 7.10536 8.26522 7 8 7Z"
+ fill={fillColor}
+ fillRule="evenodd"
+ />
+ </CustomIcon>
+ );
+}
export { HelperHintIcon } from './HelperHintIcon';
export { HomeFillIcon } from './HomeFillIcon';
export { HomeIcon } from './HomeIcon';
+export { IssueLocationIcon } from './IssueLocationIcon';
export { LinkIcon } from './LinkIcon';
export { LockIcon } from './LockIcon';
export { MainBranchIcon } from './MainBranchIcon';
export * from './DropdownMenu';
export { DropdownToggler } from './DropdownToggler';
export * from './DuplicationsIndicator';
+export * from './ExecutionFlowAccordion';
export * from './FacetBox';
export * from './FacetItem';
export { FailedQGConditionLink } from './FailedQGConditionLink';
export { FlagMessage } from './FlagMessage';
+export * from './FlowStep';
export * from './GenericAvatar';
export * from './HighlightedSection';
export { HotspotRating } from './HotspotRating';
export * from './KeyboardHint';
export * from './Link';
export { StandoutLink as Link } from './Link';
+export * from './LocationMarker';
export * from './MainAppBar';
export * from './MainMenu';
export * from './MainMenuItem';
// code viewer
codeLineIssueIndicator: COLORS.blueGrey[400], // Should be blueGrey[300], to be changed once code viewer is reworked
+ codeLineLocationMarker: COLORS.red[200],
+ codeLineLocationMarkerSelected: danger.lighter,
+ codeLineLocationSelected: COLORS.blueGrey[100],
// checkbox
checkboxHover: COLORS.indigo[50],
subnavigationSeparator: COLORS.grey[50],
subnavigationSubheading: COLORS.blueGrey[25],
subnavigationDisabled: COLORS.blueGrey[300],
+ subnavigationExecutionFlow: COLORS.blueGrey[25],
+ subnavigationExecutionFlowBorder: secondary.default,
+ subnavigationExecutionFlowActive: COLORS.indigo[500],
// footer
footer: COLORS.white,
toggle: secondary.darker,
toggleHover: secondary.darker,
+ // code viewer
+ codeLineLocationMarker: COLORS.red[900],
+ codeLineLocationMarkerSelected: COLORS.red[900],
+
// code snippet
codeSnippetHighlight: danger.default,
// subnavigation sidebar
subnavigation: secondary.darker,
+ subnavigationExecutionFlow: COLORS.blueGrey[700],
subnavigationHover: COLORS.blueGrey[700],
subnavigationSubheading: secondary.dark,