Skip to content

Commit

Permalink
[uptime] Adjust the theme colors for uptime monitors browser screensh…
Browse files Browse the repository at this point in the history
…ot labels. (elastic#115543)

* Add useBreakpoints reactive hook for uptime. Adjusted color/contrast and alignment for timestamp screenshot popup footer.

elastic#96122
  • Loading branch information
awahab07 authored and kibanamachine committed Oct 21, 2021
1 parent 79b494e commit b42d006
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
import React, { useContext, useEffect, useState } from 'react';
import useIntersection from 'react-use/lib/useIntersection';
import styled from 'styled-components';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';

import {
isScreenshotImageBlob,
isScreenshotRef,
ScreenshotRefImageData,
} from '../../../../../../common/runtime_types/ping';
} from '../../../../../../common/runtime_types';
import { useFetcher, FETCH_STATUS } from '../../../../../../../observability/public';
import { getJourneyScreenshot } from '../../../../../state/api/journey';
import { UptimeSettingsContext } from '../../../../../contexts';

import { NoImageDisplay } from './no_image_display';
import { StepImageCaption } from './step_image_caption';
import { StepImagePopover } from './step_image_popover';
Expand Down Expand Up @@ -129,9 +131,12 @@ export const PingTimestamp = ({ label, checkGroup, initialStepNo = 1 }: Props) =
)}
</StepDiv>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<span className="eui-textNoWrap">{label}</span>
</EuiFlexItem>

{label && (
<EuiFlexItem grow={false}>
<EuiText className="eui-textNoWrap">{label}</EuiText>
</EuiFlexItem>
)}
</EuiFlexGroup>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
*/

import React, { MouseEvent, useEffect } from 'react';
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import { nextAriaLabel, prevAriaLabel } from './translations';
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText, useEuiTheme } from '@elastic/eui';

import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
import { ScreenshotRefImageData } from '../../../../../../common/runtime_types';
import { useBreakpoints } from '../../../../../hooks';

import { nextAriaLabel, prevAriaLabel } from './translations';

export interface StepImageCaptionProps {
captionContent: string;
Expand All @@ -23,13 +26,6 @@ export interface StepImageCaptionProps {
isLoading: boolean;
}

const ImageCaption = euiStyled.div`
background-color: ${(props) => props.theme.eui.euiColorLightestShade};
display: inline-block;
width: 100%;
text-decoration: none;
`;

export const StepImageCaption: React.FC<StepImageCaptionProps> = ({
captionContent,
imgRef,
Expand All @@ -41,6 +37,9 @@ export const StepImageCaption: React.FC<StepImageCaptionProps> = ({
label,
onVisible,
}) => {
const { euiTheme } = useEuiTheme();
const breakpoints = useBreakpoints();

useEffect(() => {
onVisible(true);
return () => {
Expand All @@ -49,8 +48,10 @@ export const StepImageCaption: React.FC<StepImageCaptionProps> = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const isSmall = breakpoints.down('m');

return (
<ImageCaption
<CaptionWrapper
onClick={(evt) => {
// we don't want this to be captured by row click which leads to step list page
evt.stopPropagation();
Expand All @@ -59,8 +60,9 @@ export const StepImageCaption: React.FC<StepImageCaptionProps> = ({
<div className="stepArrowsFullScreen">
{(imgSrc || imgRef) && (
<EuiFlexGroup alignItems="center" justifyContent="center">
<EuiFlexItem grow={false}>
<EuiFlexItem grow={true}>
<EuiButtonEmpty
css={{ marginLeft: isSmall ? 0 : 'auto' }}
disabled={stepNumber === 1}
onClick={(evt: MouseEvent<HTMLButtonElement>) => {
setStepNumber(stepNumber - 1);
Expand All @@ -74,10 +76,11 @@ export const StepImageCaption: React.FC<StepImageCaptionProps> = ({
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText>{captionContent}</EuiText>
<SecondaryText>{captionContent}</SecondaryText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexItem grow={true}>
<EuiButtonEmpty
css={{ marginRight: isSmall ? 0 : 'auto' }}
disabled={stepNumber === maxSteps}
onClick={(evt: MouseEvent<HTMLButtonElement>) => {
setStepNumber(stepNumber + 1);
Expand All @@ -93,8 +96,21 @@ export const StepImageCaption: React.FC<StepImageCaptionProps> = ({
</EuiFlexItem>
</EuiFlexGroup>
)}
<span className="eui-textNoWrap">{label}</span>
<SecondaryText css={{ padding: euiTheme.size.xs }} className="eui-textNoWrap" size="s">
{label}
</SecondaryText>
</div>
</ImageCaption>
</CaptionWrapper>
);
};

const CaptionWrapper = euiStyled.div`
background-color: ${(props) => props.theme.eui.euiColorLightestShade};
display: inline-block;
width: 100%;
text-decoration: none;
`;

const SecondaryText = euiStyled(EuiText)((props) => ({
color: props.theme.eui.euiTextColor,
}));
1 change: 1 addition & 0 deletions x-pack/plugins/uptime/public/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export * from './use_search_text';
export * from './use_cert_status';
export * from './use_telemetry';
export * from './use_url_params';
export * from './use_breakpoints';
export { useIndexPattern } from '../contexts/uptime_index_pattern_context';
96 changes: 96 additions & 0 deletions x-pack/plugins/uptime/public/hooks/use_breakpoints.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { BREAKPOINTS } from '@elastic/eui';
import { renderHook } from '@testing-library/react-hooks';
import { useBreakpoints } from './use_breakpoints';

describe('use_breakpoints', () => {
describe('useBreakpoints', () => {
const width = global.innerWidth;

beforeEach(() => {
jest.useFakeTimers();
});

afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
});

afterAll(() => {
(global as { innerWidth: number }).innerWidth = width;
});

it('should only return up => false and down => true for "xs" when width is less than BREAKPOINTS.xs', () => {
(global as { innerWidth: number }).innerWidth = BREAKPOINTS.xs - 1;
const { result } = renderHook(() => useBreakpoints());

expect(result.current.up('xs')).toBeFalsy();
expect(result.current.down('xs')).toBeTruthy();
});

it('should only return up => true and down => false for "xs" when width is above or equal BREAKPOINTS.xs', () => {
(global as { innerWidth: number }).innerWidth = BREAKPOINTS.xs;
const { result } = renderHook(() => useBreakpoints());

expect(result.current.up('xs')).toBeTruthy();
expect(result.current.down('xs')).toBeFalsy();
});

it('should return down => true for "m" when width equals BREAKPOINTS.l', () => {
(global as { innerWidth: number }).innerWidth = BREAKPOINTS.l;
const { result } = renderHook(() => useBreakpoints());

expect(result.current.up('m')).toBeTruthy();
expect(result.current.down('m')).toBeFalsy();
});

it('should return `between` => true for "m" and "xl" when width equals BREAKPOINTS.l', () => {
(global as { innerWidth: number }).innerWidth = BREAKPOINTS.l;
const { result } = renderHook(() => useBreakpoints());

expect(result.current.between('m', 'xl')).toBeTruthy();
});

it('should return `between` => true for "s" and "m" when width equals BREAKPOINTS.s', () => {
(global as { innerWidth: number }).innerWidth = BREAKPOINTS.s;
const { result } = renderHook(() => useBreakpoints());

expect(result.current.between('s', 'm')).toBeTruthy();
});

it('should return up => true for all when size is > xxxl+', () => {
(global as { innerWidth: number }).innerWidth = 3000;
const { result } = renderHook(() => useBreakpoints());

expect(result.current.up('xs')).toBeTruthy();
expect(result.current.up('s')).toBeTruthy();
expect(result.current.up('m')).toBeTruthy();
expect(result.current.up('l')).toBeTruthy();
expect(result.current.up('xl')).toBeTruthy();
expect(result.current.up('xxl')).toBeTruthy();
expect(result.current.up('xxxl')).toBeTruthy();
});

it('should determine `isIpad (Portrait)', () => {
(global as { innerWidth: number }).innerWidth = 768;
const { result } = renderHook(() => useBreakpoints());

const isIpad = result.current.up('m') && result.current.down('l');
expect(isIpad).toEqual(true);
});

it('should determine `isMobile (Portrait)`', () => {
(global as { innerWidth: number }).innerWidth = 480;
const { result } = renderHook(() => useBreakpoints());

const isMobile = result.current.up('xs') && result.current.down('s');
expect(isMobile).toEqual(true);
});
});
});
114 changes: 114 additions & 0 deletions x-pack/plugins/uptime/public/hooks/use_breakpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { useCallback, useState } from 'react';
import useWindowSize from 'react-use/lib/useWindowSize';
import useDebounce from 'react-use/lib/useDebounce';

import { BREAKPOINTS, EuiBreakpointSize } from '@elastic/eui';

// Custom breakpoints
const BREAKPOINT_XL = 1599; // Overriding the theme's default 'xl' breakpoint
const BREAKPOINT_XXL = 1599;
const BREAKPOINT_XXXL = 2000;

export type BreakpointKey = EuiBreakpointSize | 'xxl' | 'xxxl';

type BreakpointPredicate = (breakpointKey: BreakpointKey) => boolean;
type BreakpointRangePredicate = (from: BreakpointKey, to: BreakpointKey) => boolean;

/**
* Returns the predicates functions used to determine whether the current device's width is above or below the asked
* breakpoint. (Implementation inspired by React Material UI).
*
* @example
* const { breakpoints } = useBreakpoints();
* const isMobile = breakpoint.down('m');
*
* @example
* const { breakpoints } = useBreakpoints();
* const isTablet = breakpoint.between('m', 'l');
*
* @param debounce {number} Debounce interval for optimization
*
* @returns { {up: BreakpointPredicate, down: BreakpointPredicate, between: BreakpointRangePredicate} }
* Returns object containing predicates which determine whether the current device's width lies above, below or
* in-between the given breakpoint(s)
* {
* up => Returns `true` if the current width is equal or above (inclusive) the given breakpoint size,
* or `false` otherwise.
* down => Returns `true` if the current width is below (exclusive) the given breakpoint size, or `false` otherwise.
* between => Returns `true` if the current width is equal or above (inclusive) the corresponding size of
* `fromBreakpointKey` AND is below (exclusive) the corresponding width of `toBreakpointKey`.
* Returns `false` otherwise.
* }
*/
export function useBreakpoints(debounce = 50) {
const { width } = useWindowSize();
const [debouncedWidth, setDebouncedWidth] = useState(width);

const up = useCallback<BreakpointPredicate>(
(breakpointKey: BreakpointKey) => isUp(debouncedWidth, breakpointKey),
[debouncedWidth]
);
const down = useCallback<BreakpointPredicate>(
(breakpointKey: BreakpointKey) => isDown(debouncedWidth, breakpointKey),
[debouncedWidth]
);

const between = useCallback<BreakpointRangePredicate>(
(fromBreakpointKey: BreakpointKey, toBreakpointKey: BreakpointKey) =>
isBetween(debouncedWidth, fromBreakpointKey, toBreakpointKey),
[debouncedWidth]
);

useDebounce(
() => {
setDebouncedWidth(width);
},
debounce,
[width]
);

return { up, down, between, debouncedWidth };
}

/**
* Returns the corresponding device width against the provided breakpoint key, either the overridden value or the
* default value from theme.
* @param key {BreakpointKey} string key representing the device breakpoint e.g. 'xs', 's', 'xxxl'
*/
function getSizeForBreakpointKey(key: BreakpointKey): number {
switch (key) {
case 'xxxl':
return BREAKPOINT_XXXL;
case 'xxl':
return BREAKPOINT_XXL;
case 'xl':
return BREAKPOINT_XL;
case 'l':
return BREAKPOINTS.l;
case 'm':
return BREAKPOINTS.m;
case 's':
return BREAKPOINTS.s;
}

return BREAKPOINTS.xs;
}

function isUp(size: number, breakpointKey: BreakpointKey) {
return size >= getSizeForBreakpointKey(breakpointKey);
}

function isDown(size: number, breakpointKey: BreakpointKey) {
return size < getSizeForBreakpointKey(breakpointKey);
}

function isBetween(size: number, fromBreakpointKey: BreakpointKey, toBreakpointKey: BreakpointKey) {
return isUp(size, fromBreakpointKey) && isDown(size, toBreakpointKey);
}

0 comments on commit b42d006

Please sign in to comment.