fix(ui) Improve HoverEntityTooltip and truncate parent glossary nodes (#6417)

This commit is contained in:
Chris Collins 2022-11-14 13:05:55 -05:00 committed by GitHub
parent 7ea97974ba
commit 78c3bf9ab6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 123 additions and 75 deletions

View File

@ -0,0 +1,91 @@
import React from 'react';
import styled from 'styled-components';
import { Typography, Tooltip } from 'antd';
import { FolderOutlined, RightOutlined } from '@ant-design/icons';
import { ANTD_GRAY } from '../../../../constants';
import { EntityType, GlossaryNode } from '../../../../../../../types.generated';
import useContentTruncation from '../../../../../../shared/useContentTruncation';
import { useEntityRegistry } from '../../../../../../useEntityRegistry';
export const StyledRightOutlined = styled(RightOutlined)`
color: ${ANTD_GRAY[7]};
font-size: 8px;
margin: 0 10px;
`;
// must display content in reverse to have ellipses at the beginning of content
export const ParentNodesWrapper = styled.div`
align-items: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex-direction: row-reverse;
display: flex;
`;
export const Ellipsis = styled.span`
color: ${ANTD_GRAY[7]};
margin-right: 2px;
`;
export const StyledTooltip = styled(Tooltip)`
display: flex;
white-space: nowrap;
overflow: hidden;
`;
const GlossaryNodeText = styled(Typography.Text)<{ color: string }>`
font-size: 12px;
line-height: 20px;
color: ${(props) => props.color};
`;
const GlossaryNodeIcon = styled(FolderOutlined)<{ color: string }>`
color: ${(props) => props.color};
&&& {
font-size: 12px;
margin-right: 4px;
}
`;
interface Props {
parentNodes?: GlossaryNode[] | null;
}
export default function ParentNodesView({ parentNodes }: Props) {
const entityRegistry = useEntityRegistry();
const { contentRef, isContentTruncated } = useContentTruncation(parentNodes);
return (
<StyledTooltip
title={
<>
{[...(parentNodes || [])]?.reverse()?.map((parentNode, idx) => (
<>
<GlossaryNodeIcon color="white" />
<GlossaryNodeText color="white">
{entityRegistry.getDisplayName(EntityType.GlossaryNode, parentNode)}
</GlossaryNodeText>
{idx + 1 !== parentNodes?.length && <StyledRightOutlined data-testid="right-arrow" />}
</>
))}
</>
}
overlayStyle={isContentTruncated ? {} : { display: 'none' }}
>
{isContentTruncated && <Ellipsis>...</Ellipsis>}
<ParentNodesWrapper ref={contentRef}>
{[...(parentNodes || [])]?.map((parentNode, idx) => (
<>
<GlossaryNodeText color={ANTD_GRAY[7]}>
{entityRegistry.getDisplayName(EntityType.GlossaryNode, parentNode)}
</GlossaryNodeText>
<GlossaryNodeIcon color={ANTD_GRAY[7]} />
{idx + 1 !== parentNodes?.length && <StyledRightOutlined data-testid="right-arrow" />}
</>
))}
</ParentNodesWrapper>
</StyledTooltip>
);
}

View File

@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import React from 'react';
import { useEntityRegistry } from '../../../../../../useEntityRegistry';
import { IconStyleType } from '../../../../../Entity';
import { useEntityData } from '../../../../EntityContext';
@ -8,6 +8,7 @@ import PlatformContentView from './PlatformContentView';
import { GenericEntityProperties } from '../../../../types';
import EntityRegistry from '../../../../../EntityRegistry';
import { EntityType } from '../../../../../../../types.generated';
import useContentTruncation from '../../../../../../shared/useContentTruncation';
export function getDisplayedEntityType(
entityData: GenericEntityProperties | null,
@ -20,23 +21,6 @@ export function getDisplayedEntityType(
return entityData?.entityTypeOverride || entityTypeCased || '';
}
export function useParentContainersTruncation(dataDependency: any) {
const parentContainersRef = useRef<HTMLDivElement>(null);
const [areContainersTruncated, setAreContainersTruncated] = useState(false);
useEffect(() => {
if (
parentContainersRef &&
parentContainersRef.current &&
parentContainersRef.current.scrollWidth > parentContainersRef.current.clientWidth
) {
setAreContainersTruncated(true);
}
}, [dataDependency]);
return { parentContainersRef, areContainersTruncated };
}
function PlatformContentContainer() {
const { entityType, entityData } = useEntityData();
const entityRegistry = useEntityRegistry();
@ -50,7 +34,7 @@ function PlatformContentContainer() {
const displayedEntityType = getDisplayedEntityType(entityData, entityRegistry, entityType);
const instanceId = entityData?.dataPlatformInstance?.instanceId;
const { parentContainersRef, areContainersTruncated } = useParentContainersTruncation(entityData);
const { contentRef, isContentTruncated } = useContentTruncation(entityData);
return (
<PlatformContentView
@ -65,8 +49,8 @@ function PlatformContentContainer() {
typeIcon={typeIcon}
entityType={displayedEntityType}
parentContainers={entityData?.parentContainers?.containers}
parentContainersRef={parentContainersRef}
areContainersTruncated={areContainersTruncated}
parentContainersRef={contentRef}
areContainersTruncated={isContentTruncated}
/>
);
}

View File

@ -1,12 +1,17 @@
import React from 'react';
import styled from 'styled-components';
import { Typography, Image, Tooltip } from 'antd';
import { FolderOutlined, RightOutlined } from '@ant-design/icons';
import { Typography, Image } from 'antd';
import { Maybe } from 'graphql/jsutils/Maybe';
import { Container, GlossaryNode } from '../../../../../../../types.generated';
import { ANTD_GRAY } from '../../../../constants';
import ContainerLink from './ContainerLink';
import { capitalizeFirstLetterOnly } from '../../../../../../shared/textUtil';
import ParentNodesView, {
StyledRightOutlined,
ParentNodesWrapper as ParentContainersWrapper,
Ellipsis,
StyledTooltip,
} from './ParentNodesView';
const LogoIcon = styled.span`
display: flex;
@ -45,46 +50,6 @@ const PlatformDivider = styled.div`
vertical-align: text-top;
`;
const StyledRightOutlined = styled(RightOutlined)`
color: ${ANTD_GRAY[7]};
font-size: 8px;
margin: 0 10px;
`;
const ParentContainersWrapper = styled.div`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex-direction: row-reverse;
display: flex;
`;
const Ellipsis = styled.span`
color: ${ANTD_GRAY[7]};
margin-right: 2px;
`;
const StyledTooltip = styled(Tooltip)`
display: flex;
white-space: nowrap;
overflow: hidden;
`;
const GlossaryNodeText = styled(Typography.Text)`
font-size: 12px;
line-height: 20px;
color: ${ANTD_GRAY[7]};
`;
const GlossaryNodeIcon = styled(FolderOutlined)`
color: ${ANTD_GRAY[7]};
&&& {
font-size: 12px;
margin-right: 4px;
}
`;
export function getParentContainerNames(containers?: Maybe<Container>[] | null) {
let parentNames = '';
if (containers) {
@ -181,13 +146,7 @@ function PlatformContentView(props: Props) {
</ParentContainersWrapper>
{directParentContainer && <ContainerLink container={directParentContainer} />}
</StyledTooltip>
{[...(parentNodes || [])]?.reverse()?.map((parentNode, idx) => (
<>
<GlossaryNodeIcon />
<GlossaryNodeText>{parentNode?.properties?.name}</GlossaryNodeText>
{idx + 1 !== parentNodes?.length && <StyledRightOutlined data-testid="right-arrow" />}
</>
))}
<ParentNodesView parentNodes={parentNodes} />
</PlatformContentWrapper>
);
}

View File

@ -23,7 +23,7 @@ import NoMarkdownViewer from '../entity/shared/components/styled/StripMarkdownTe
import { getNumberWithOrdinal } from '../entity/shared/utils';
import { useEntityData } from '../entity/shared/EntityContext';
import PlatformContentView from '../entity/shared/containers/profile/header/PlatformContent/PlatformContentView';
import { useParentContainersTruncation } from '../entity/shared/containers/profile/header/PlatformContent/PlatformContentContainer';
import useContentTruncation from '../shared/useContentTruncation';
import EntityCount from '../entity/shared/containers/profile/header/EntityCount';
import { ExpandedActorGroup } from '../entity/shared/components/styled/ExpandedActorGroup';
import { DeprecationPill } from '../entity/shared/components/styled/DeprecationPill';
@ -242,7 +242,7 @@ export default function DefaultPreviewCard({
}
const [descriptionExpanded, setDescriptionExpanded] = useState(false);
const { parentContainersRef, areContainersTruncated } = useParentContainersTruncation(container);
const { contentRef, isContentTruncated } = useContentTruncation(container);
const onPreventMouseDown = (event) => {
event.preventDefault();
@ -266,8 +266,8 @@ export default function DefaultPreviewCard({
entityType={type}
parentContainers={parentContainers?.containers}
parentNodes={parentNodes?.nodes}
parentContainersRef={parentContainersRef}
areContainersTruncated={areContainersTruncated}
parentContainersRef={contentRef}
areContainersTruncated={isContentTruncated}
/>
<EntityTitleContainer>
<Link to={url}>

View File

@ -24,7 +24,7 @@ export const HoverEntityTooltip = ({ entity, canOpen = true, children }: Props)
visible={canOpen ? undefined : false}
color="white"
placement="topRight"
overlayStyle={{ minWidth: 300, maxWidth: 500, width: 'fit-content' }}
overlayStyle={{ minWidth: 300, maxWidth: 600, width: 'fit-content' }}
overlayInnerStyle={{ padding: 12 }}
title={<a href={url}>{entityRegistry.renderPreview(entity.type, PreviewType.HOVER_CARD, entity)}</a>}
>

View File

@ -0,0 +1,14 @@
import { useEffect, useRef, useState } from 'react';
export default function useParentContainersTruncation(dataDependency: any) {
const contentRef = useRef<HTMLDivElement>(null);
const [isContentTruncated, setAreContainersTruncated] = useState(false);
useEffect(() => {
if (contentRef && contentRef.current && contentRef.current.scrollWidth > contentRef.current.clientWidth) {
setAreContainersTruncated(true);
}
}, [dataDependency]);
return { contentRef, isContentTruncated };
}