aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/design-system/src/components
diff options
context:
space:
mode:
authorstanislavh <stanislav.honcharov@sonarsource.com>2023-11-06 11:13:33 +0100
committersonartech <sonartech@sonarsource.com>2023-11-08 20:02:53 +0000
commit19c131bdfccb98c24bcf432c5e5968bc5d4ffd5c (patch)
tree459256e361a1f8a308e1d244737e11b979dab683 /server/sonar-web/design-system/src/components
parentc8d1b7eb2494d92f20fb8b498efdbb2e3f8ea12c (diff)
downloadsonarqube-19c131bdfccb98c24bcf432c5e5968bc5d4ffd5c.tar.gz
sonarqube-19c131bdfccb98c24bcf432c5e5968bc5d4ffd5c.zip
SONAR-20873 Create new education tour for accepting issues
Diffstat (limited to 'server/sonar-web/design-system/src/components')
-rw-r--r--server/sonar-web/design-system/src/components/Dropdown.tsx4
-rw-r--r--server/sonar-web/design-system/src/components/DropdownMenu.tsx2
-rw-r--r--server/sonar-web/design-system/src/components/DropdownToggler.tsx37
-rw-r--r--server/sonar-web/design-system/src/components/HighlightRing.tsx30
-rw-r--r--server/sonar-web/design-system/src/components/SpotlightTour.tsx46
-rw-r--r--server/sonar-web/design-system/src/components/__tests__/Dropdown-test.tsx54
-rw-r--r--server/sonar-web/design-system/src/components/index.ts1
7 files changed, 148 insertions, 26 deletions
diff --git a/server/sonar-web/design-system/src/components/Dropdown.tsx b/server/sonar-web/design-system/src/components/Dropdown.tsx
index 3bd340bcdf9..cce9579c5cb 100644
--- a/server/sonar-web/design-system/src/components/Dropdown.tsx
+++ b/server/sonar-web/design-system/src/components/Dropdown.tsx
@@ -53,6 +53,8 @@ interface Props {
overlay: React.ReactNode;
placement?: PopupPlacement;
size?: InputSizeKeys;
+ withClickOutHandler?: boolean;
+ withFocusOutHandler?: boolean;
zLevel?: PopupZLevel;
}
@@ -124,6 +126,8 @@ export class Dropdown extends React.PureComponent<Readonly<Props>, State> {
</DropdownMenu>
}
placement={this.props.placement}
+ withClickOutHandler={this.props.withClickOutHandler}
+ withFocusOutHandler={this.props.withFocusOutHandler}
zLevel={zLevel}
>
{children}
diff --git a/server/sonar-web/design-system/src/components/DropdownMenu.tsx b/server/sonar-web/design-system/src/components/DropdownMenu.tsx
index bf983364dc3..3076fd7e535 100644
--- a/server/sonar-web/design-system/src/components/DropdownMenu.tsx
+++ b/server/sonar-web/design-system/src/components/DropdownMenu.tsx
@@ -284,7 +284,7 @@ export const ItemDivider = styled.li`
`;
ItemDivider.defaultProps = { role: 'separator' };
-const DropdownMenuWrapper = styled.ul`
+export const DropdownMenuWrapper = styled.ul`
background-color: ${themeColor('dropdownMenu')};
color: ${themeContrast('dropdownMenu')};
width: var(--inputSize);
diff --git a/server/sonar-web/design-system/src/components/DropdownToggler.tsx b/server/sonar-web/design-system/src/components/DropdownToggler.tsx
index ee0f65e5733..15cabb5337a 100644
--- a/server/sonar-web/design-system/src/components/DropdownToggler.tsx
+++ b/server/sonar-web/design-system/src/components/DropdownToggler.tsx
@@ -27,24 +27,35 @@ type PopupProps = Popup['props'];
interface Props extends PopupProps {
onRequestClose: VoidFunction;
open: boolean;
+ withClickOutHandler?: boolean;
+ withFocusOutHandler?: boolean;
}
export function DropdownToggler(props: Props) {
- const { children, open, onRequestClose, overlay, ...popupProps } = props;
+ const {
+ children,
+ open,
+ onRequestClose,
+ withClickOutHandler = true,
+ withFocusOutHandler = true,
+ overlay,
+ ...popupProps
+ } = props;
+
+ let finalOverlay = <EscKeydownHandler onKeydown={onRequestClose}>{overlay}</EscKeydownHandler>;
+
+ if (withFocusOutHandler) {
+ finalOverlay = <FocusOutHandler onFocusOut={onRequestClose}>{finalOverlay}</FocusOutHandler>;
+ }
+
+ if (withClickOutHandler) {
+ finalOverlay = (
+ <OutsideClickHandler onClickOutside={onRequestClose}>{finalOverlay}</OutsideClickHandler>
+ );
+ }
return (
- <Popup
- overlay={
- open ? (
- <OutsideClickHandler onClickOutside={onRequestClose}>
- <FocusOutHandler onFocusOut={onRequestClose}>
- <EscKeydownHandler onKeydown={onRequestClose}>{overlay}</EscKeydownHandler>
- </FocusOutHandler>
- </OutsideClickHandler>
- ) : undefined
- }
- {...popupProps}
- >
+ <Popup overlay={open && finalOverlay} {...popupProps}>
{children}
</Popup>
);
diff --git a/server/sonar-web/design-system/src/components/HighlightRing.tsx b/server/sonar-web/design-system/src/components/HighlightRing.tsx
new file mode 100644
index 00000000000..864b5de07b7
--- /dev/null
+++ b/server/sonar-web/design-system/src/components/HighlightRing.tsx
@@ -0,0 +1,30 @@
+/*
+ * 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';
+
+export const HighlightRing = styled.div`
+ &.active {
+ box-shadow: 0 0 4px 0 ${themeColor('primary')};
+ background: ${themeColor('highlightRingBackground')};
+ ${tw`sw-rounded-1/2`}
+ }
+`;
diff --git a/server/sonar-web/design-system/src/components/SpotlightTour.tsx b/server/sonar-web/design-system/src/components/SpotlightTour.tsx
index fcc529ca98b..a6018def24b 100644
--- a/server/sonar-web/design-system/src/components/SpotlightTour.tsx
+++ b/server/sonar-web/design-system/src/components/SpotlightTour.tsx
@@ -42,6 +42,7 @@ export interface SpotlightTourProps extends Omit<JoyrideProps, 'steps'> {
skipLabel?: string;
stepXofYLabel?: (x: number, y: number) => string;
steps: SpotlightTourStep[];
+ width?: number;
}
export type SpotlightTourStep = Pick<JoyrideStep, 'target' | 'content' | 'title'> & {
@@ -55,6 +56,7 @@ export type SpotlightTourStep = Pick<JoyrideStep, 'target' | 'content' | 'title'
const PULSE_SIZE = 8;
const ARROW_LENGTH = 40;
const DEFAULT_PLACEMENT = 'bottom';
+const DEFAULT_WIDTH = 315;
function TooltipComponent({
continuous,
@@ -68,9 +70,11 @@ function TooltipComponent({
primaryProps,
stepXofYLabel,
tooltipProps,
+ width = DEFAULT_WIDTH,
}: TooltipRenderProps & {
step: SpotlightTourStep;
stepXofYLabel: SpotlightTourProps['stepXofYLabel'];
+ width?: number;
}) {
const [arrowPosition, setArrowPosition] = React.useState({ left: 0, top: 0, rotate: '0deg' });
const ref = React.useRef<HTMLDivElement | null>(null);
@@ -81,6 +85,17 @@ function TooltipComponent({
const intl = useIntl();
React.useEffect(() => {
+ const target =
+ typeof step.target === 'string' ? document.querySelector(step.target) : step.target;
+ // To show the highlight, target has to be HighlightRing from design system
+ target?.classList.add('active');
+
+ return () => {
+ target?.classList.remove('active');
+ };
+ }, [step]);
+
+ React.useEffect(() => {
// We don't compute for "center"; "center" will simply not show any arrow.
if (placement !== 'center' && ref.current?.parentNode) {
let left = 0;
@@ -115,10 +130,20 @@ function TooltipComponent({
}
}, [step, ref, setArrowPosition, placement]);
+ /**
+ * Preventing click events from bubbling to avoid closing other popups, in cases when the guide
+ * is shown simultaneously with other popups.
+ */
+ function handleClick(e: React.MouseEvent) {
+ e.stopPropagation();
+ }
+
return (
<StyledPopupWrapper
- className="sw-p-3 sw-body-sm sw-w-[315px] sw-relative sw-border-0"
+ className="sw-p-3 sw-body-sm sw-relative sw-border-0"
+ onClick={handleClick}
placement={(step.placement as Placement | undefined) ?? DEFAULT_PLACEMENT}
+ style={{ width }}
zLevel={PopupZLevel.Absolute}
{...tooltipProps}
>
@@ -138,7 +163,7 @@ function TooltipComponent({
</WrapperButton>
</div>
<div>{step.content}</div>
- <div className="sw-flex sw-justify-between sw-items-center sw-mt-3">
+ <div className="sw-flex sw-justify-between sw-items-center sw-mt-4">
{(stepXofYLabel || size > 1) && (
<strong>
{stepXofYLabel
@@ -166,14 +191,23 @@ function TooltipComponent({
}
export function SpotlightTour(props: SpotlightTourProps) {
- const { steps, skipLabel, backLabel, closeLabel, nextLabel, stepXofYLabel, ...otherProps } =
- props;
+ const {
+ steps,
+ skipLabel,
+ backLabel,
+ closeLabel,
+ nextLabel,
+ stepXofYLabel,
+ disableOverlay = true,
+ width,
+ ...otherProps
+ } = props;
const intl = useIntl();
return (
<ReactJoyride
- disableOverlay
+ disableOverlay={disableOverlay}
floaterProps={{
styles: {
floater: {
@@ -202,7 +236,7 @@ export function SpotlightTour(props: SpotlightTourProps) {
}))}
tooltipComponent={(
tooltipProps: React.PropsWithChildren<TooltipRenderProps & { step: SpotlightTourStep }>,
- ) => <TooltipComponent stepXofYLabel={stepXofYLabel} {...tooltipProps} />}
+ ) => <TooltipComponent stepXofYLabel={stepXofYLabel} width={width} {...tooltipProps} />}
{...otherProps}
/>
);
diff --git a/server/sonar-web/design-system/src/components/__tests__/Dropdown-test.tsx b/server/sonar-web/design-system/src/components/__tests__/Dropdown-test.tsx
index 34f43149e08..7843646e777 100644
--- a/server/sonar-web/design-system/src/components/__tests__/Dropdown-test.tsx
+++ b/server/sonar-web/design-system/src/components/__tests__/Dropdown-test.tsx
@@ -24,7 +24,7 @@ import { ButtonSecondary } from '../buttons';
describe('Dropdown', () => {
it('renders', async () => {
- const { user } = setupWithChildren();
+ const { user } = renderDropdown();
expect(screen.getByRole('button')).toBeInTheDocument();
await user.click(screen.getByRole('button'));
@@ -32,17 +32,59 @@ describe('Dropdown', () => {
});
it('toggles with render prop', async () => {
- const { user } = setupWithChildren(({ onToggleClick }) => (
- <ButtonSecondary onClick={onToggleClick} />
- ));
+ const { user } = renderDropdown({
+ children: ({ onToggleClick }) => <ButtonSecondary onClick={onToggleClick} />,
+ });
await user.click(screen.getByRole('button'));
expect(screen.getByRole('menu')).toBeVisible();
});
- function setupWithChildren(children?: Dropdown['props']['children']) {
+ it('closes when clicking outside of menu', async () => {
+ const { user } = renderDropdown();
+
+ await user.click(screen.getByRole('button'));
+ expect(screen.getByRole('menu')).toBeInTheDocument();
+
+ await user.click(document.body);
+ expect(screen.queryByRole('menu')).not.toBeInTheDocument();
+ });
+
+ it('does not close when clicking ouside of menu', async () => {
+ const { user } = renderDropdown({ withClickOutHandler: false });
+
+ await user.click(screen.getByRole('button'));
+ expect(screen.getByRole('menu')).toBeInTheDocument();
+
+ await user.click(document.body);
+ expect(screen.getByRole('menu')).toBeInTheDocument();
+ });
+
+ it('closes when other target gets focus', async () => {
+ const { user } = renderDropdown();
+
+ await user.click(screen.getByRole('button'));
+ expect(screen.getByRole('menu')).toBeInTheDocument();
+
+ await user.tab();
+
+ expect(screen.queryByRole('menu')).not.toBeInTheDocument();
+ });
+
+ it('does not close when other target gets focus', async () => {
+ const { user } = renderDropdown({ withFocusOutHandler: false });
+
+ await user.click(screen.getByRole('button'));
+ expect(screen.getByRole('menu')).toBeInTheDocument();
+
+ await user.tab();
+ expect(screen.getByRole('menu')).toBeInTheDocument();
+ });
+
+ function renderDropdown(props: Partial<Dropdown['props']> = {}) {
+ const { children, ...rest } = props;
return renderWithRouter(
- <Dropdown id="test-menu" overlay={<div id="overlay" />}>
+ <Dropdown id="test-menu" overlay={<div id="overlay" />} {...rest}>
{children ?? <ButtonSecondary />}
</Dropdown>,
);
diff --git a/server/sonar-web/design-system/src/components/index.ts b/server/sonar-web/design-system/src/components/index.ts
index 180db1f3da7..5ed00fbfce7 100644
--- a/server/sonar-web/design-system/src/components/index.ts
+++ b/server/sonar-web/design-system/src/components/index.ts
@@ -41,6 +41,7 @@ export { FailedQGConditionLink } from './FailedQGConditionLink';
export * from './FavoriteButton';
export { DismissableFlagMessage, FlagMessage } from './FlagMessage';
export * from './FlowStep';
+export * from './HighlightRing';
export * from './HighlightedSection';
export { Histogram } from './Histogram';
export { HotspotRating } from './HotspotRating';