mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Merge pull request #791 from acelaya-forks/feature/fix-ref-types
Improved types on element ref objects and their usage
This commit is contained in:
commit
aac2832eb7
7 changed files with 26 additions and 31 deletions
|
@ -1,4 +1,4 @@
|
||||||
import { FC, useEffect, useRef, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import { UncontrolledTooltip } from 'reactstrap';
|
import { UncontrolledTooltip } from 'reactstrap';
|
||||||
import { ExternalLink } from 'react-external-link';
|
import { ExternalLink } from 'react-external-link';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
@ -8,8 +8,8 @@ import {
|
||||||
faCircleNotch as loadingStatusIcon,
|
faCircleNotch as loadingStatusIcon,
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
import { MediaMatcher } from '../../utils/types';
|
import { MediaMatcher } from '../../utils/types';
|
||||||
import { mutableRefToElementRef } from '../../utils/helpers/components';
|
|
||||||
import { DomainStatus } from '../data';
|
import { DomainStatus } from '../data';
|
||||||
|
import { useElementRef } from '../../utils/helpers/hooks';
|
||||||
|
|
||||||
interface DomainStatusIconProps {
|
interface DomainStatusIconProps {
|
||||||
status: DomainStatus;
|
status: DomainStatus;
|
||||||
|
@ -17,7 +17,7 @@ interface DomainStatusIconProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DomainStatusIcon: FC<DomainStatusIconProps> = ({ status, matchMedia = window.matchMedia }) => {
|
export const DomainStatusIcon: FC<DomainStatusIconProps> = ({ status, matchMedia = window.matchMedia }) => {
|
||||||
const ref = useRef<HTMLSpanElement>();
|
const ref = useElementRef<HTMLSpanElement>();
|
||||||
const matchesMobile = () => matchMedia('(max-width: 991px)').matches;
|
const matchesMobile = () => matchMedia('(max-width: 991px)').matches;
|
||||||
const [isMobile, setIsMobile] = useState<boolean>(matchesMobile());
|
const [isMobile, setIsMobile] = useState<boolean>(matchesMobile());
|
||||||
|
|
||||||
|
@ -35,13 +35,13 @@ export const DomainStatusIcon: FC<DomainStatusIconProps> = ({ status, matchMedia
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span ref={mutableRefToElementRef(ref)}>
|
<span ref={ref}>
|
||||||
{status === 'valid'
|
{status === 'valid'
|
||||||
? <FontAwesomeIcon fixedWidth icon={checkIcon} className="text-muted" />
|
? <FontAwesomeIcon fixedWidth icon={checkIcon} className="text-muted" />
|
||||||
: <FontAwesomeIcon fixedWidth icon={invalidIcon} className="text-danger" />}
|
: <FontAwesomeIcon fixedWidth icon={invalidIcon} className="text-danger" />}
|
||||||
</span>
|
</span>
|
||||||
<UncontrolledTooltip
|
<UncontrolledTooltip
|
||||||
target={(() => ref.current) as any}
|
target={ref}
|
||||||
placement={isMobile ? 'top-start' : 'left'}
|
placement={isMobile ? 'top-start' : 'left'}
|
||||||
autohide={status === 'valid'}
|
autohide={status === 'valid'}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { useRef, ChangeEvent, useState, useEffect, FC, PropsWithChildren } from 'react';
|
import { ChangeEvent, useState, useEffect, FC, PropsWithChildren } from 'react';
|
||||||
import { Button, UncontrolledTooltip } from 'reactstrap';
|
import { Button, UncontrolledTooltip } from 'reactstrap';
|
||||||
import { complement, pipe } from 'ramda';
|
import { complement, pipe } from 'ramda';
|
||||||
import { faFileUpload as importIcon } from '@fortawesome/free-solid-svg-icons';
|
import { faFileUpload as importIcon } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { useToggle } from '../../utils/helpers/hooks';
|
import { useElementRef, useToggle } from '../../utils/helpers/hooks';
|
||||||
import { mutableRefToElementRef } from '../../utils/helpers/components';
|
|
||||||
import { ServersImporter } from '../services/ServersImporter';
|
import { ServersImporter } from '../services/ServersImporter';
|
||||||
import { ServerData, ServersMap } from '../data';
|
import { ServerData, ServersMap } from '../data';
|
||||||
import { DuplicatedServersModal } from './DuplicatedServersModal';
|
import { DuplicatedServersModal } from './DuplicatedServersModal';
|
||||||
|
@ -34,7 +33,7 @@ export const ImportServersBtn = ({ importServersFromFile }: ServersImporter): FC
|
||||||
tooltipPlacement = 'bottom',
|
tooltipPlacement = 'bottom',
|
||||||
className = '',
|
className = '',
|
||||||
}) => {
|
}) => {
|
||||||
const ref = useRef<HTMLInputElement>();
|
const ref = useElementRef<HTMLInputElement>();
|
||||||
const [serversToCreate, setServersToCreate] = useState<ServerData[] | undefined>();
|
const [serversToCreate, setServersToCreate] = useState<ServerData[] | undefined>();
|
||||||
const [duplicatedServers, setDuplicatedServers] = useState<ServerData[]>([]);
|
const [duplicatedServers, setDuplicatedServers] = useState<ServerData[]>([]);
|
||||||
const [isModalOpen,, showModal, hideModal] = useToggle();
|
const [isModalOpen,, showModal, hideModal] = useToggle();
|
||||||
|
@ -79,7 +78,7 @@ export const ImportServersBtn = ({ importServersFromFile }: ServersImporter): FC
|
||||||
type="file"
|
type="file"
|
||||||
accept="text/csv"
|
accept="text/csv"
|
||||||
className="import-servers-btn__csv-select"
|
className="import-servers-btn__csv-select"
|
||||||
ref={mutableRefToElementRef(ref)}
|
ref={ref}
|
||||||
onChange={onFile}
|
onChange={onFile}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { FC, ReactNode, useRef } from 'react';
|
import { FC, ReactNode } from 'react';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { IconDefinition } from '@fortawesome/fontawesome-common-types';
|
import { IconDefinition } from '@fortawesome/fontawesome-common-types';
|
||||||
import { faLinkSlash, faCalendarXmark, faCheck } from '@fortawesome/free-solid-svg-icons';
|
import { faLinkSlash, faCalendarXmark, faCheck } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { UncontrolledTooltip } from 'reactstrap';
|
import { UncontrolledTooltip } from 'reactstrap';
|
||||||
import { isBefore } from 'date-fns';
|
import { isBefore } from 'date-fns';
|
||||||
import { mutableRefToElementRef } from '../../utils/helpers/components';
|
|
||||||
import { ShortUrl } from '../data';
|
import { ShortUrl } from '../data';
|
||||||
import { formatHumanFriendly, now, parseISO } from '../../utils/helpers/date';
|
import { formatHumanFriendly, now, parseISO } from '../../utils/helpers/date';
|
||||||
|
import { useElementRef } from '../../utils/helpers/hooks';
|
||||||
|
|
||||||
interface ShortUrlStatusProps {
|
interface ShortUrlStatusProps {
|
||||||
shortUrl: ShortUrl;
|
shortUrl: ShortUrl;
|
||||||
|
@ -70,15 +70,15 @@ const resolveShortUrlStatus = (shortUrl: ShortUrl): StatusResult => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ShortUrlStatus: FC<ShortUrlStatusProps> = ({ shortUrl }) => {
|
export const ShortUrlStatus: FC<ShortUrlStatusProps> = ({ shortUrl }) => {
|
||||||
const tooltipRef = useRef<HTMLElement | undefined>();
|
const tooltipRef = useElementRef<HTMLElement>();
|
||||||
const { icon, className, description } = resolveShortUrlStatus(shortUrl);
|
const { icon, className, description } = resolveShortUrlStatus(shortUrl);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span style={{ cursor: !description ? undefined : 'help' }} ref={mutableRefToElementRef(tooltipRef)}>
|
<span style={{ cursor: !description ? undefined : 'help' }} ref={tooltipRef}>
|
||||||
<FontAwesomeIcon icon={icon} className={className} />
|
<FontAwesomeIcon icon={icon} className={className} />
|
||||||
</span>
|
</span>
|
||||||
<UncontrolledTooltip target={(() => tooltipRef.current) as any} placement="bottom">
|
<UncontrolledTooltip target={tooltipRef} placement="bottom">
|
||||||
{description}
|
{description}
|
||||||
</UncontrolledTooltip>
|
</UncontrolledTooltip>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { useRef } from 'react';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
|
import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { UncontrolledTooltip } from 'reactstrap';
|
import { UncontrolledTooltip } from 'reactstrap';
|
||||||
|
@ -7,8 +6,8 @@ import { prettify } from '../../utils/helpers/numbers';
|
||||||
import { ShortUrl } from '../data';
|
import { ShortUrl } from '../data';
|
||||||
import { SelectedServer } from '../../servers/data';
|
import { SelectedServer } from '../../servers/data';
|
||||||
import { ShortUrlDetailLink } from './ShortUrlDetailLink';
|
import { ShortUrlDetailLink } from './ShortUrlDetailLink';
|
||||||
import { mutableRefToElementRef } from '../../utils/helpers/components';
|
|
||||||
import { formatHumanFriendly, parseISO } from '../../utils/helpers/date';
|
import { formatHumanFriendly, parseISO } from '../../utils/helpers/date';
|
||||||
|
import { useElementRef } from '../../utils/helpers/hooks';
|
||||||
import './ShortUrlVisitsCount.scss';
|
import './ShortUrlVisitsCount.scss';
|
||||||
|
|
||||||
interface ShortUrlVisitsCountProps {
|
interface ShortUrlVisitsCountProps {
|
||||||
|
@ -37,20 +36,20 @@ export const ShortUrlVisitsCount = (
|
||||||
return visitsLink;
|
return visitsLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tooltipRef = useRef<HTMLElement | undefined>();
|
const tooltipRef = useElementRef<HTMLElement>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span className="indivisible">
|
<span className="indivisible">
|
||||||
{visitsLink}
|
{visitsLink}
|
||||||
<small className="short-urls-visits-count__max-visits-control" ref={mutableRefToElementRef(tooltipRef)}>
|
<small className="short-urls-visits-count__max-visits-control" ref={tooltipRef}>
|
||||||
{maxVisits && <> / {prettify(maxVisits)}</>}
|
{maxVisits && <> / {prettify(maxVisits)}</>}
|
||||||
<sup className="ms-1">
|
<sup className="ms-1">
|
||||||
<FontAwesomeIcon icon={infoIcon} />
|
<FontAwesomeIcon icon={infoIcon} />
|
||||||
</sup>
|
</sup>
|
||||||
</small>
|
</small>
|
||||||
</span>
|
</span>
|
||||||
<UncontrolledTooltip target={(() => tooltipRef.current) as any} placement="bottom">
|
<UncontrolledTooltip target={tooltipRef} placement="bottom">
|
||||||
<ul className="list-unstyled mb-0">
|
<ul className="list-unstyled mb-0">
|
||||||
{maxVisits && (
|
{maxVisits && (
|
||||||
<li className="short-url-visits-count__tooltip-list-item">
|
<li className="short-url-visits-count__tooltip-list-item">
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { FC, PropsWithChildren, useRef } from 'react';
|
import { FC, PropsWithChildren } from 'react';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
|
import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { UncontrolledTooltip } from 'reactstrap';
|
import { UncontrolledTooltip } from 'reactstrap';
|
||||||
import { Placement } from '@popperjs/core';
|
import { Placement } from '@popperjs/core';
|
||||||
import { mutableRefToElementRef } from './helpers/components';
|
import { useElementRef } from './helpers/hooks';
|
||||||
|
|
||||||
export type InfoTooltipProps = PropsWithChildren<{
|
export type InfoTooltipProps = PropsWithChildren<{
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -11,14 +11,14 @@ export type InfoTooltipProps = PropsWithChildren<{
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const InfoTooltip: FC<InfoTooltipProps> = ({ className = '', placement, children }) => {
|
export const InfoTooltip: FC<InfoTooltipProps> = ({ className = '', placement, children }) => {
|
||||||
const ref = useRef<HTMLSpanElement | undefined>();
|
const ref = useElementRef<HTMLSpanElement>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span className={className} ref={mutableRefToElementRef(ref)}>
|
<span className={className} ref={ref}>
|
||||||
<FontAwesomeIcon icon={infoIcon} />
|
<FontAwesomeIcon icon={infoIcon} />
|
||||||
</span>
|
</span>
|
||||||
<UncontrolledTooltip target={(() => ref.current) as any} placement={placement}>{children}</UncontrolledTooltip>
|
<UncontrolledTooltip target={ref} placement={placement}>{children}</UncontrolledTooltip>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { MutableRefObject, Ref } from 'react';
|
|
||||||
|
|
||||||
export const mutableRefToElementRef = <T>(ref: MutableRefObject<T | undefined>): Ref<T> => (el) => {
|
|
||||||
ref.current = el ?? undefined; // eslint-disable-line no-param-reassign
|
|
||||||
};
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useRef, EffectCallback, DependencyList, useEffect } from 'react';
|
import { DependencyList, EffectCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useSwipeable as useReactSwipeable } from 'react-swipeable';
|
import { useSwipeable as useReactSwipeable } from 'react-swipeable';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
@ -91,3 +91,5 @@ export const useDomId = (): string => {
|
||||||
const { current: id } = useRef(`dom-${uuid()}`);
|
const { current: id } = useRef(`dom-${uuid()}`);
|
||||||
return id;
|
return id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useElementRef = <T>() => useRef<T | null>(null);
|
||||||
|
|
Loading…
Reference in a new issue