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 { AvatarProps } from '@components/components/Avatar/types';
|
||||
import getAvatarColor, { getNameInitials } from '@components/components/Avatar/utils';
|
||||
import { Icon } from '@components/components/Icon';
|
||||
|
||||
export const avatarDefaults: AvatarProps = {
|
||||
name: 'User name',
|
||||
size: 'default',
|
||||
showInPill: false,
|
||||
isOutlined: false,
|
||||
isGroup: false,
|
||||
};
|
||||
|
||||
export const Avatar = ({
|
||||
@ -18,23 +20,29 @@ export const Avatar = ({
|
||||
onClick,
|
||||
showInPill = avatarDefaults.showInPill,
|
||||
isOutlined = avatarDefaults.isOutlined,
|
||||
isGroup = avatarDefaults.isGroup,
|
||||
}: AvatarProps) => {
|
||||
const [hasError, setHasError] = useState(false);
|
||||
|
||||
return (
|
||||
<Container onClick={onClick} $hasOnClick={!!onClick} $showInPill={showInPill}>
|
||||
<AvatarImageWrapper
|
||||
$color={getAvatarColor(name)}
|
||||
$size={size}
|
||||
$isOutlined={isOutlined}
|
||||
$hasImage={!!imageUrl}
|
||||
>
|
||||
{!hasError && imageUrl ? (
|
||||
<AvatarImage src={imageUrl} onError={() => setHasError(true)} />
|
||||
) : (
|
||||
<>{getNameInitials(name)} </>
|
||||
)}
|
||||
</AvatarImageWrapper>
|
||||
{(!isGroup || imageUrl) && (
|
||||
<AvatarImageWrapper
|
||||
$color={getAvatarColor(name)}
|
||||
$size={size}
|
||||
$isOutlined={isOutlined}
|
||||
$hasImage={!!imageUrl}
|
||||
>
|
||||
{!hasError && imageUrl ? (
|
||||
<AvatarImage src={imageUrl} onError={() => setHasError(true)} />
|
||||
) : (
|
||||
!isGroup && getNameInitials(name)
|
||||
)}
|
||||
</AvatarImageWrapper>
|
||||
)}
|
||||
{isGroup && !imageUrl && (
|
||||
<Icon icon="UsersThree" source="phosphor" variant="filled" color="gray" size="lg" />
|
||||
)}
|
||||
{showInPill && <AvatarText $size={size}>{name}</AvatarText>}
|
||||
</Container>
|
||||
);
|
||||
|
@ -7,4 +7,5 @@ export interface AvatarProps {
|
||||
size?: AvatarSizeOptions;
|
||||
showInPill?: 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 { EntityType } from '@types';
|
||||
|
||||
export interface AvatarItemProps {
|
||||
name: string;
|
||||
imageUrl?: string | null;
|
||||
type?: EntityType;
|
||||
urn?: string;
|
||||
}
|
||||
|
||||
export type AvatarStackProps = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
export interface SectionType {
|
||||
title: string;
|
||||
title: string | React.ReactNode;
|
||||
titleSuffix?: string | React.ReactNode;
|
||||
content: string | React.ReactNode;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
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 { 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 { capitalizeFirstLetter } from '@app/shared/textUtil';
|
||||
|
||||
import { Owner } from '@types';
|
||||
import { EntityType, Owner } from '@types';
|
||||
|
||||
const PreviewImage = styled(Image)`
|
||||
max-height: 20px;
|
||||
@ -119,6 +119,8 @@ export function OwnerColumn({ owners, entityRegistry }: { owners: Owner[]; entit
|
||||
return {
|
||||
name: entityRegistry.getDisplayName(owner.owner.type, owner.owner),
|
||||
imageUrl: owner.owner.editableProperties?.pictureLink,
|
||||
type: owner.owner.type,
|
||||
urn: owner.owner.urn,
|
||||
};
|
||||
});
|
||||
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)}
|
||||
imageUrl={singleOwner.editableProperties?.pictureLink}
|
||||
showInPill
|
||||
isGroup={singleOwner.type === EntityType.CorpGroup}
|
||||
/>
|
||||
</Link>
|
||||
</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