diff --git a/src/domains/helpers/DomainStatusIcon.tsx b/src/domains/helpers/DomainStatusIcon.tsx index caf97c79..03952883 100644 --- a/src/domains/helpers/DomainStatusIcon.tsx +++ b/src/domains/helpers/DomainStatusIcon.tsx @@ -1,4 +1,4 @@ -import { FC, useRef } from 'react'; +import { FC, useEffect, useRef, useState } from 'react'; import { UncontrolledTooltip } from 'reactstrap'; import { ExternalLink } from 'react-external-link'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -7,10 +7,26 @@ import { faCheck as checkIcon, faCircleNotch as loadingStatusIcon, } from '@fortawesome/free-solid-svg-icons'; +import { MediaMatcher } from '../../utils/types'; import { DomainStatus } from '../data'; -export const DomainStatusIcon: FC<{ status: DomainStatus }> = ({ status }) => { +interface DomainStatusIconProps { + status: DomainStatus; + matchMedia?: MediaMatcher; +} + +export const DomainStatusIcon: FC = ({ status, matchMedia = window.matchMedia }) => { const ref = useRef(); + const matchesMobile = () => matchMedia('(max-width: 991px)').matches; + const [ isMobile, setIsMobile ] = useState(matchesMobile()); + + useEffect(() => { + const listener = () => setIsMobile(matchesMobile()); + + window.addEventListener('resize', listener); + + return () => window.removeEventListener('resize', listener); + }, []); if (status === 'validating') { return ; @@ -27,7 +43,11 @@ export const DomainStatusIcon: FC<{ status: DomainStatus }> = ({ status }) => { ? : } - ref.current) as any} placement="bottom" autohide={status === 'valid'}> + ref.current) as any} + placement={isMobile ? 'top-start' : 'left'} + autohide={status === 'valid'} + > {status === 'valid' ? 'Congratulations! This domain is properly configured.' : ( Oops! There is some missing configuration, and short URLs shared with this domain will not work. diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 00000000..84bab12b --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1 @@ +export type MediaMatcher = (query: string) => MediaQueryList; diff --git a/src/visits/VisitsTable.tsx b/src/visits/VisitsTable.tsx index 842eac34..505f01fe 100644 --- a/src/visits/VisitsTable.tsx +++ b/src/visits/VisitsTable.tsx @@ -12,6 +12,7 @@ import { supportsBotVisits } from '../utils/helpers/features'; import { SelectedServer } from '../servers/data'; import { Time } from '../utils/Time'; import { TableOrderIcon } from '../utils/table/TableOrderIcon'; +import { MediaMatcher } from '../utils/types'; import { NormalizedOrphanVisit, NormalizedVisit } from './types'; import './VisitsTable.scss'; @@ -19,7 +20,7 @@ export interface VisitsTableProps { visits: NormalizedVisit[]; selectedVisits?: NormalizedVisit[]; setSelectedVisits: (visits: NormalizedVisit[]) => void; - matchMedia?: (query: string) => MediaQueryList; + matchMedia?: MediaMatcher; isOrphanVisits?: boolean; selectedServer: SelectedServer; } diff --git a/test/domains/helpers/DomainStatusIcon.test.tsx b/test/domains/helpers/DomainStatusIcon.test.tsx index 91fdd331..03eadb3c 100644 --- a/test/domains/helpers/DomainStatusIcon.test.tsx +++ b/test/domains/helpers/DomainStatusIcon.test.tsx @@ -4,11 +4,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faTimes, faCheck, faCircleNotch } from '@fortawesome/free-solid-svg-icons'; import { DomainStatus } from '../../../src/domains/data'; import { DomainStatusIcon } from '../../../src/domains/helpers/DomainStatusIcon'; +import { MediaMatcher } from '../../../src/utils/types'; +import { Mock } from 'ts-mockery'; describe('', () => { let wrapper: ShallowWrapper; - const createWrapper = (status: DomainStatus) => { - wrapper = shallow(); + const createWrapper = (status: DomainStatus, matchMedia?: MediaMatcher) => { + wrapper = shallow(); return wrapper; }; @@ -54,4 +56,16 @@ describe('', () => { expect(faIcon.prop('icon')).toEqual(expectedIcon); expect(faIcon.prop('spin')).toEqual(false); }); + + it.each([ + [ true, 'top-sart' ], + [ false, 'left' ], + ])('places the tooltip properly based on query match', (isMobile, expectedPlacement) => { + const mediaMatch = jest.fn().mockReturnValue(Mock.of({ matches: isMobile })); + const wrapper = createWrapper('valid', mediaMatch); + const tooltip = wrapper.find(UncontrolledTooltip); + + expect(tooltip).toHaveLength(1); + expect(tooltip.prop('placement')).toEqual(expectedPlacement); + }); });