mirror of
https://github.com/datahub-project/datahub.git
synced 2025-06-27 05:03:31 +00:00
feat(ui/ingestion): implement hover state for stacked avatars in owners column (#13703)
This commit is contained in:
parent
8889181b31
commit
16a3211a96
@ -3,12 +3,14 @@ import React, { useState } from 'react';
|
|||||||
import { AvatarImage, AvatarImageWrapper, AvatarText, Container } from '@components/components/Avatar/components';
|
import { AvatarImage, AvatarImageWrapper, AvatarText, Container } from '@components/components/Avatar/components';
|
||||||
import { AvatarProps } from '@components/components/Avatar/types';
|
import { AvatarProps } from '@components/components/Avatar/types';
|
||||||
import getAvatarColor, { getNameInitials } from '@components/components/Avatar/utils';
|
import getAvatarColor, { getNameInitials } from '@components/components/Avatar/utils';
|
||||||
|
import { Icon } from '@components/components/Icon';
|
||||||
|
|
||||||
export const avatarDefaults: AvatarProps = {
|
export const avatarDefaults: AvatarProps = {
|
||||||
name: 'User name',
|
name: 'User name',
|
||||||
size: 'default',
|
size: 'default',
|
||||||
showInPill: false,
|
showInPill: false,
|
||||||
isOutlined: false,
|
isOutlined: false,
|
||||||
|
isGroup: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Avatar = ({
|
export const Avatar = ({
|
||||||
@ -18,23 +20,29 @@ export const Avatar = ({
|
|||||||
onClick,
|
onClick,
|
||||||
showInPill = avatarDefaults.showInPill,
|
showInPill = avatarDefaults.showInPill,
|
||||||
isOutlined = avatarDefaults.isOutlined,
|
isOutlined = avatarDefaults.isOutlined,
|
||||||
|
isGroup = avatarDefaults.isGroup,
|
||||||
}: AvatarProps) => {
|
}: AvatarProps) => {
|
||||||
const [hasError, setHasError] = useState(false);
|
const [hasError, setHasError] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container onClick={onClick} $hasOnClick={!!onClick} $showInPill={showInPill}>
|
<Container onClick={onClick} $hasOnClick={!!onClick} $showInPill={showInPill}>
|
||||||
<AvatarImageWrapper
|
{(!isGroup || imageUrl) && (
|
||||||
$color={getAvatarColor(name)}
|
<AvatarImageWrapper
|
||||||
$size={size}
|
$color={getAvatarColor(name)}
|
||||||
$isOutlined={isOutlined}
|
$size={size}
|
||||||
$hasImage={!!imageUrl}
|
$isOutlined={isOutlined}
|
||||||
>
|
$hasImage={!!imageUrl}
|
||||||
{!hasError && imageUrl ? (
|
>
|
||||||
<AvatarImage src={imageUrl} onError={() => setHasError(true)} />
|
{!hasError && imageUrl ? (
|
||||||
) : (
|
<AvatarImage src={imageUrl} onError={() => setHasError(true)} />
|
||||||
<>{getNameInitials(name)} </>
|
) : (
|
||||||
)}
|
!isGroup && getNameInitials(name)
|
||||||
</AvatarImageWrapper>
|
)}
|
||||||
|
</AvatarImageWrapper>
|
||||||
|
)}
|
||||||
|
{isGroup && !imageUrl && (
|
||||||
|
<Icon icon="UsersThree" source="phosphor" variant="filled" color="gray" size="lg" />
|
||||||
|
)}
|
||||||
{showInPill && <AvatarText $size={size}>{name}</AvatarText>}
|
{showInPill && <AvatarText $size={size}>{name}</AvatarText>}
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
@ -7,4 +7,5 @@ export interface AvatarProps {
|
|||||||
size?: AvatarSizeOptions;
|
size?: AvatarSizeOptions;
|
||||||
showInPill?: boolean;
|
showInPill?: boolean;
|
||||||
isOutlined?: boolean;
|
isOutlined?: boolean;
|
||||||
|
isGroup?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
import { Badge, StructuredPopover, Text } from '@components';
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { AvatarStack } from '@components/components/AvatarStack/AvatarStack';
|
||||||
|
import HoverSectionContent from '@components/components/AvatarStack/HoverSectionContent';
|
||||||
|
import { AvatarStackProps } from '@components/components/AvatarStack/types';
|
||||||
|
|
||||||
|
import EntityRegistry from '@app/entityV2/EntityRegistry';
|
||||||
|
import StopPropagationWrapper from '@app/sharedV2/StopPropagationWrapper';
|
||||||
|
|
||||||
|
import { EntityType } from '@types';
|
||||||
|
|
||||||
|
const HeaderContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface Props extends AvatarStackProps {
|
||||||
|
entityRegistry: EntityRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AvatarStackWithHover = ({
|
||||||
|
avatars,
|
||||||
|
size = 'default',
|
||||||
|
showRemainingNumber = true,
|
||||||
|
maxToShow = 4,
|
||||||
|
entityRegistry,
|
||||||
|
}: Props) => {
|
||||||
|
const users = avatars.filter((avatar) => avatar.type === EntityType.CorpUser);
|
||||||
|
const groups = avatars.filter((avatar) => avatar.type === EntityType.CorpGroup);
|
||||||
|
|
||||||
|
const renderTitle = (headerText, count) => (
|
||||||
|
<HeaderContainer>
|
||||||
|
<Text size="sm" color="gray" weight="bold">
|
||||||
|
{headerText}
|
||||||
|
</Text>
|
||||||
|
<Badge count={count} size="xs" />
|
||||||
|
</HeaderContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StopPropagationWrapper>
|
||||||
|
<StructuredPopover
|
||||||
|
width={280}
|
||||||
|
title="Owners"
|
||||||
|
sections={[
|
||||||
|
...(users.length > 0
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
title: renderTitle('Users', users.length),
|
||||||
|
content: (
|
||||||
|
<HoverSectionContent
|
||||||
|
avatars={users}
|
||||||
|
entityRegistry={entityRegistry}
|
||||||
|
size={size}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(groups.length > 0
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
title: renderTitle('Groups', groups.length),
|
||||||
|
content: (
|
||||||
|
<HoverSectionContent
|
||||||
|
avatars={groups}
|
||||||
|
entityRegistry={entityRegistry}
|
||||||
|
size={size}
|
||||||
|
isGroup
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<AvatarStack avatars={avatars} showRemainingNumber={showRemainingNumber} maxToShow={maxToShow} />
|
||||||
|
</div>
|
||||||
|
</StructuredPopover>
|
||||||
|
</StopPropagationWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AvatarStackWithHover;
|
@ -0,0 +1,65 @@
|
|||||||
|
import { Avatar, Button } from '@components';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { AvatarItemProps } from '@components/components/AvatarStack/types';
|
||||||
|
import { AvatarSizeOptions } from '@components/theme/config';
|
||||||
|
|
||||||
|
import EntityRegistry from '@app/entityV2/EntityRegistry';
|
||||||
|
|
||||||
|
const PillsContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
avatars: AvatarItemProps[];
|
||||||
|
entityRegistry: EntityRegistry;
|
||||||
|
size?: AvatarSizeOptions;
|
||||||
|
maxVisible?: number;
|
||||||
|
isGroup?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HoverSectionContent = ({ avatars, entityRegistry, size, maxVisible = 4, isGroup }: Props) => {
|
||||||
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
|
const visibleAvatars = expanded ? avatars : avatars.slice(0, maxVisible);
|
||||||
|
const hasMore = avatars.length > maxVisible;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PillsContainer>
|
||||||
|
{visibleAvatars.map((user) => {
|
||||||
|
const userAvatar = (
|
||||||
|
<Avatar
|
||||||
|
showInPill
|
||||||
|
size={size}
|
||||||
|
isOutlined
|
||||||
|
imageUrl={user.imageUrl}
|
||||||
|
name={user.name}
|
||||||
|
isGroup={isGroup}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{user.type && user.urn ? (
|
||||||
|
<Link to={entityRegistry.getEntityUrl(user.type, user.urn)}>{userAvatar}</Link>
|
||||||
|
) : (
|
||||||
|
{ userAvatar }
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</PillsContainer>
|
||||||
|
{hasMore && (
|
||||||
|
<Button variant="text" size="sm" color="gray" onClick={() => setExpanded((prev) => !prev)}>
|
||||||
|
{expanded ? 'View less' : 'View more'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HoverSectionContent;
|
@ -1,8 +1,12 @@
|
|||||||
import { AvatarSizeOptions } from '@src/alchemy-components/theme/config';
|
import { AvatarSizeOptions } from '@src/alchemy-components/theme/config';
|
||||||
|
|
||||||
|
import { EntityType } from '@types';
|
||||||
|
|
||||||
export interface AvatarItemProps {
|
export interface AvatarItemProps {
|
||||||
name: string;
|
name: string;
|
||||||
imageUrl?: string | null;
|
imageUrl?: string | null;
|
||||||
|
type?: EntityType;
|
||||||
|
urn?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AvatarStackProps = {
|
export type AvatarStackProps = {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export interface SectionType {
|
export interface SectionType {
|
||||||
title: string;
|
title: string | React.ReactNode;
|
||||||
titleSuffix?: string | React.ReactNode;
|
titleSuffix?: string | React.ReactNode;
|
||||||
content: string | React.ReactNode;
|
content: string | React.ReactNode;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import React from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import styled from 'styled-components/macro';
|
import styled from 'styled-components/macro';
|
||||||
|
|
||||||
import { AvatarStack } from '@components/components/AvatarStack/AvatarStack';
|
import AvatarStackWithHover from '@components/components/AvatarStack/AvatarStackWithHover';
|
||||||
|
|
||||||
import EntityRegistry from '@app/entityV2/EntityRegistry';
|
import EntityRegistry from '@app/entityV2/EntityRegistry';
|
||||||
import { EXECUTION_REQUEST_STATUS_RUNNING } from '@app/ingestV2/executions/constants';
|
import { EXECUTION_REQUEST_STATUS_RUNNING } from '@app/ingestV2/executions/constants';
|
||||||
@ -14,7 +14,7 @@ import useGetSourceLogoUrl from '@app/ingestV2/source/builder/useGetSourceLogoUr
|
|||||||
import { HoverEntityTooltip } from '@app/recommendations/renderer/component/HoverEntityTooltip';
|
import { HoverEntityTooltip } from '@app/recommendations/renderer/component/HoverEntityTooltip';
|
||||||
import { capitalizeFirstLetter } from '@app/shared/textUtil';
|
import { capitalizeFirstLetter } from '@app/shared/textUtil';
|
||||||
|
|
||||||
import { Owner } from '@types';
|
import { EntityType, Owner } from '@types';
|
||||||
|
|
||||||
const PreviewImage = styled(Image)`
|
const PreviewImage = styled(Image)`
|
||||||
max-height: 20px;
|
max-height: 20px;
|
||||||
@ -119,6 +119,8 @@ export function OwnerColumn({ owners, entityRegistry }: { owners: Owner[]; entit
|
|||||||
return {
|
return {
|
||||||
name: entityRegistry.getDisplayName(owner.owner.type, owner.owner),
|
name: entityRegistry.getDisplayName(owner.owner.type, owner.owner),
|
||||||
imageUrl: owner.owner.editableProperties?.pictureLink,
|
imageUrl: owner.owner.editableProperties?.pictureLink,
|
||||||
|
type: owner.owner.type,
|
||||||
|
urn: owner.owner.urn,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const singleOwner = owners.length === 1 ? owners[0].owner : undefined;
|
const singleOwner = owners.length === 1 ? owners[0].owner : undefined;
|
||||||
@ -139,11 +141,14 @@ export function OwnerColumn({ owners, entityRegistry }: { owners: Owner[]; entit
|
|||||||
name={entityRegistry.getDisplayName(singleOwner.type, singleOwner)}
|
name={entityRegistry.getDisplayName(singleOwner.type, singleOwner)}
|
||||||
imageUrl={singleOwner.editableProperties?.pictureLink}
|
imageUrl={singleOwner.editableProperties?.pictureLink}
|
||||||
showInPill
|
showInPill
|
||||||
|
isGroup={singleOwner.type === EntityType.CorpGroup}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</HoverEntityTooltip>
|
</HoverEntityTooltip>
|
||||||
)}
|
)}
|
||||||
{owners.length > 1 && <AvatarStack avatars={ownerAvatars} showRemainingNumber />}
|
{owners.length > 1 && (
|
||||||
|
<AvatarStackWithHover avatars={ownerAvatars} showRemainingNumber entityRegistry={entityRegistry} />
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user