mirror of
https://github.com/makeplane/plane.git
synced 2026-09-02 03:59:00 +02:00
refactor: migrate web Avatar to @makeplane/propel Avatar (#9724)
* refactor: migrate web Avatar to @makeplane/propel Avatar
Replace @plane/ui Avatar and AvatarGroup with the published Propel
components across apps/web. 41 files, 51 Avatar sites, 4 group sites.
Propel's size scale is @plane/ui's shifted one step, so every enum maps
with no pixel change: sm(16)->2xs, md(20)->xs, base(24)->sm, lg(28)->md.
Unsized avatars took the old md default and become xs.
Notable mapping decisions:
- name -> alt plus fallback={name?.[0]?.toUpperCase()}, preserving the
initial that @plane/ui derived internally.
- Six numeric px sizes mapped to the nearest enum step. Five are exact;
the notification card avatar moves 42px -> xl (40px).
- Ten classNames removed, as published Propel takes no className. Seven
were already redundant with Propel's base class (shrink-0, bg-layer-1);
the remaining three overrode initials typography, which Propel now sets
per size.
- shape="circle" and fallback colour props dropped; Propel is always
circular and picks initials colours from a fixed palette.
AvatarGroup needs app chrome. @plane/ui defaulted to max={2}, rendering
two avatars plus a +N counter, while Propel's AvatarGroup renders every
child. No call site passed max explicitly, so all four relied on that
default and would have rendered entire member lists. The new
common/avatar-group-overflow.tsx restores the cap around Propel's group.
ButtonAvatars' size prop is narrowed to AvatarGroupSize accordingly;
no caller passes size.
Tooltips are intentionally not migrated. @plane/ui Avatar wrapped itself
in a Tooltip defaulting to on, and Propel has no built-in tooltip, so
roughly 29 direct sites plus the ButtonAvatars subtree lose a tooltip.
ButtonAvatars additionally inverted the flag (showTooltip={!showTooltip}),
so its callers passing false were the ones showing tooltips. Restoring
these is deliberately deferred to a follow-up.
* refactor: use Propel Avatar for AvatarGroup overflow count
Replace the custom CSS +N counter with a Propel Avatar fallback,
matching the Avatar Group overflow pattern. Size still comes from
the group context.
This commit is contained in:
committed by
GitHub
parent
fd68e7d919
commit
12eb6015de
@@ -10,13 +10,14 @@ import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
import { UserRound } from "lucide-react";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { ProjectIcon } from "@plane/propel/icons";
|
||||
// plane package imports
|
||||
import type { AnalyticsTableDataMap, WorkItemInsightColumns } from "@plane/types";
|
||||
// plane web components
|
||||
import { Avatar } from "@plane/ui";
|
||||
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
import { useAnalytics } from "@/hooks/store/use-analytics";
|
||||
@@ -111,10 +112,10 @@ const WorkItemsInsightTable = observer(function WorkItemsInsightTable() {
|
||||
<div className="flex items-center gap-2">
|
||||
{row.original.avatar_url && row.original.avatar_url !== "" ? (
|
||||
<Avatar
|
||||
name={row.original.display_name}
|
||||
alt={row.original.display_name}
|
||||
fallback={row.original.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(row.original.avatar_url)}
|
||||
size={24}
|
||||
shape="circle"
|
||||
size="sm"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-4 w-4 flex-shrink-0 items-center justify-center overflow-hidden rounded-full bg-layer-1 capitalize">
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useCallback, useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
// plane imports
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import type { EditorRefApi } from "@plane/editor";
|
||||
import { useHashScroll } from "@plane/hooks";
|
||||
@@ -22,7 +23,6 @@ import { LiteTextEditor } from "@/components/editor/lite-text";
|
||||
import { CommentReactions } from "../comment-reaction";
|
||||
import { CommentCardEditForm } from "./edit-form";
|
||||
import { EmojiReactionButton, EmojiReactionPicker } from "@plane/propel/emoji-reaction";
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
|
||||
export type TCommentCardDisplayProps = {
|
||||
@@ -118,7 +118,7 @@ export const CommentCardDisplay = observer(function CommentCardDisplay(props: TC
|
||||
</div>
|
||||
)}
|
||||
<div className="relative mb-3 flex w-full items-center gap-2">
|
||||
<Avatar size="sm" name={displayName} src={getFileURL(avatarUrl)} className="shrink-0" />
|
||||
<Avatar alt={displayName} fallback={displayName?.[0]?.toUpperCase()} size="2xs" src={getFileURL(avatarUrl)} />
|
||||
<div className="flex flex-1 flex-wrap items-center gap-1">
|
||||
<div className="text-caption-sm-medium">{displayName}</div>
|
||||
<div className="text-caption-sm-regular text-tertiary">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { CloseIcon } from "@plane/propel/icons";
|
||||
// plane ui
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
// helpers
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// types
|
||||
@@ -36,10 +36,10 @@ export const AppliedMembersFilters = observer(function AppliedMembersFilters(pro
|
||||
return (
|
||||
<div key={memberId} className="flex items-center gap-1 rounded-sm bg-layer-1 px-1.5 py-1 text-11">
|
||||
<Avatar
|
||||
name={memberDetails.display_name}
|
||||
alt={memberDetails.display_name}
|
||||
fallback={memberDetails.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(memberDetails.avatar_url)}
|
||||
showTooltip={false}
|
||||
size={"sm"}
|
||||
size="2xs"
|
||||
/>
|
||||
<span className="normal-case">{memberDetails.display_name}</span>
|
||||
{editable && (
|
||||
|
||||
50
apps/web/core/components/common/avatar-group-overflow.tsx
Normal file
50
apps/web/core/components/common/avatar-group-overflow.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
// plane imports
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { AvatarGroup, type AvatarGroupSize } from "@makeplane/propel/components/avatar-group";
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Shared size for every avatar in the stack.
|
||||
*/
|
||||
size: AvatarGroupSize;
|
||||
/**
|
||||
* The maximum number of avatars to display. If the number of children exceeds this value, the
|
||||
* additional avatars are replaced by a count of the remaining avatars.
|
||||
* @default 2
|
||||
*/
|
||||
max?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* An overlapping avatar stack that collapses the tail into a `+N` counter.
|
||||
*
|
||||
* Propel's `AvatarGroup` renders every child it is given, so the capping behaviour that
|
||||
* `@plane/ui`'s `AvatarGroup` provided through `max` lives here as app chrome. The overflow
|
||||
* indicator is a Propel `Avatar` with a `fallback`, matching the Avatar Group overflow pattern.
|
||||
*/
|
||||
export function AvatarGroupOverflow(props: Props) {
|
||||
const { children, size, max = 2 } = props;
|
||||
|
||||
const avatars = React.Children.toArray(children);
|
||||
const totalAvatars = avatars.length;
|
||||
// If there is only one avatar past the limit, render it instead of a "+1" counter.
|
||||
const maxAvatarsToRender = totalAvatars <= max + 1 ? max + 1 : max;
|
||||
const overflowCount = totalAvatars - max;
|
||||
|
||||
return (
|
||||
<AvatarGroup size={size}>
|
||||
{avatars.slice(0, maxAvatarsToRender)}
|
||||
{maxAvatarsToRender < totalAvatars && (
|
||||
<Avatar alt={`${overflowCount} more members`} fallback={`+${overflowCount}`} />
|
||||
)}
|
||||
</AvatarGroup>
|
||||
);
|
||||
}
|
||||
@@ -8,7 +8,8 @@ import { useMemo, useState } from "react";
|
||||
import { sortBy } from "lodash-es";
|
||||
import { observer } from "mobx-react";
|
||||
// ui
|
||||
import { Avatar, Loader } from "@plane/ui";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { getFileURL } from "@plane/utils";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues/issue-layouts/filters";
|
||||
@@ -78,10 +79,10 @@ export const FilterCreatedBy = observer(function FilterCreatedBy(props: Props) {
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={
|
||||
<Avatar
|
||||
name={member.display_name}
|
||||
alt={member.display_name}
|
||||
fallback={member.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(member.avatar_url)}
|
||||
showTooltip={false}
|
||||
size="md"
|
||||
size="xs"
|
||||
/>
|
||||
}
|
||||
title={currentUser?.id === member.id ? "You" : member?.display_name}
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { TDescriptionVersion } from "@plane/types";
|
||||
import { Avatar, CustomMenu } from "@plane/ui";
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
import { calculateTimeAgo, getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
@@ -31,8 +32,9 @@ export const DescriptionVersionsDropdownItem = observer(function DescriptionVers
|
||||
<CustomMenu.MenuItem key={version.id} className="flex items-center gap-1" onClick={() => onClick(version.id)}>
|
||||
<span className="flex-shrink-0">
|
||||
<Avatar
|
||||
name={versionCreator?.display_name ?? t("common.deactivated_user")}
|
||||
size="sm"
|
||||
alt={versionCreator?.display_name ?? t("common.deactivated_user")}
|
||||
fallback={(versionCreator?.display_name ?? t("common.deactivated_user"))?.[0]?.toUpperCase()}
|
||||
size="2xs"
|
||||
src={getFileURL(versionCreator?.avatar_url ?? "")}
|
||||
/>
|
||||
</span>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { useCallback, useRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import type { EditorRefApi } from "@plane/editor";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
@@ -14,7 +15,7 @@ import { CopyIcon, ChevronLeftIcon, ChevronRightIcon } from "@plane/propel/icons
|
||||
import { setToast, TOAST_TYPE } from "@plane/propel/toast";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import type { TDescriptionVersion } from "@plane/types";
|
||||
import { Avatar, EModalPosition, EModalWidth, Loader, ModalCore } from "@plane/ui";
|
||||
import { EModalPosition, EModalWidth, Loader, ModalCore } from "@plane/ui";
|
||||
import { calculateTimeAgo, cn, getFileURL } from "@plane/utils";
|
||||
// components
|
||||
import { RichTextEditor } from "@/components/editor/rich-text";
|
||||
@@ -85,9 +86,10 @@ export const DescriptionVersionsModal = observer(function DescriptionVersionsMod
|
||||
{t("description_versions.edited_by")}
|
||||
<span className="flex-shrink-0">
|
||||
<Avatar
|
||||
size="sm"
|
||||
alt={versionCreator?.display_name}
|
||||
fallback={versionCreator?.display_name?.[0]?.toUpperCase()}
|
||||
size="2xs"
|
||||
src={getFileURL(versionCreator?.avatar_url ?? "")}
|
||||
name={versionCreator?.display_name}
|
||||
/>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// assets
|
||||
import emptyMembers from "@/app/assets/empty-state/empty_members.svg?url";
|
||||
@@ -43,7 +43,12 @@ export const AssigneeStatComponent = observer(function AssigneeStatComponent(pro
|
||||
key={assignee?.id}
|
||||
title={
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar name={assignee?.title ?? undefined} src={getFileURL(assignee?.avatar_url ?? "")} />
|
||||
<Avatar
|
||||
alt={assignee?.title ?? undefined}
|
||||
fallback={assignee?.title?.[0]?.toUpperCase()}
|
||||
src={getFileURL(assignee?.avatar_url ?? "")}
|
||||
size="xs"
|
||||
/>
|
||||
<span>{assignee?.title ?? ""}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { CalendarCheck } from "lucide-react";
|
||||
// headless ui
|
||||
import { Tab } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { PriorityIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
@@ -19,7 +20,7 @@ import type { TWorkItemFilterCondition } from "@plane/shared-state";
|
||||
import type { ICycle } from "@plane/types";
|
||||
import { EIssuesStoreType } from "@plane/types";
|
||||
// ui
|
||||
import { Loader, Avatar } from "@plane/ui";
|
||||
import { Loader } from "@plane/ui";
|
||||
import { cn, renderFormattedDate, renderFormattedDateWithoutYear, getFileURL } from "@plane/utils";
|
||||
// assets
|
||||
import darkAssigneeAsset from "@/app/assets/empty-state/active-cycle/assignee-dark.webp?url";
|
||||
@@ -273,8 +274,10 @@ export const ActiveCycleStats = observer(function ActiveCycleStats(props: Active
|
||||
title={
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar
|
||||
name={assignee?.display_name ?? undefined}
|
||||
alt={assignee?.display_name ?? undefined}
|
||||
fallback={assignee?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(assignee?.avatar_url ?? "")}
|
||||
size="xs"
|
||||
/>
|
||||
|
||||
<span>{assignee.display_name}</span>
|
||||
|
||||
@@ -11,13 +11,15 @@ import { SquareUser } from "lucide-react";
|
||||
// plane types
|
||||
import { EEstimateSystem } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { MembersPropertyIcon, WorkItemsIcon } from "@plane/propel/icons";
|
||||
import type { ICycle } from "@plane/types";
|
||||
// plane ui
|
||||
import { Avatar, AvatarGroup, TextArea } from "@plane/ui";
|
||||
import { TextArea } from "@plane/ui";
|
||||
// helpers
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
import { AvatarGroupOverflow } from "@/components/common/avatar-group-overflow";
|
||||
import { useProjectEstimates } from "@/hooks/store/estimates";
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
// plane web constants
|
||||
@@ -83,7 +85,12 @@ export const CycleSidebarDetails = observer(function CycleSidebarDetails(props:
|
||||
</div>
|
||||
<div className="flex w-3/5 items-center rounded-xs">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<Avatar name={cycleOwnerDetails?.display_name} src={getFileURL(cycleOwnerDetails?.avatar_url ?? "")} />
|
||||
<Avatar
|
||||
alt={cycleOwnerDetails?.display_name}
|
||||
fallback={cycleOwnerDetails?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(cycleOwnerDetails?.avatar_url ?? "")}
|
||||
size="xs"
|
||||
/>
|
||||
<span className="text-13 text-secondary">{cycleOwnerDetails?.display_name}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -98,19 +105,19 @@ export const CycleSidebarDetails = observer(function CycleSidebarDetails(props:
|
||||
<div className="flex items-center gap-2.5">
|
||||
{cycleDetails?.assignee_ids && cycleDetails.assignee_ids.length > 0 ? (
|
||||
<>
|
||||
<AvatarGroup showTooltip>
|
||||
<AvatarGroupOverflow size="xs">
|
||||
{cycleDetails.assignee_ids.map((member) => {
|
||||
const memberDetails = getUserDetails(member);
|
||||
return (
|
||||
<Avatar
|
||||
key={memberDetails?.id}
|
||||
name={memberDetails?.display_name ?? ""}
|
||||
alt={memberDetails?.display_name ?? ""}
|
||||
fallback={memberDetails?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(memberDetails?.avatar_url ?? "")}
|
||||
showTooltip={false}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</AvatarGroup>
|
||||
</AvatarGroupOverflow>
|
||||
</>
|
||||
) : (
|
||||
<span className="px-1.5 text-13 text-tertiary">{t("no_assignee")}</span>
|
||||
|
||||
@@ -14,13 +14,15 @@ import { Eye, CalendarDays } from "lucide-react";
|
||||
import { EUserPermissions, EUserPermissionsLevel, IS_FAVORITE_MENU_OPEN } from "@plane/constants";
|
||||
import { useLocalStorage } from "@plane/hooks";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { TransferIcon, WorkItemsIcon, MembersPropertyIcon } from "@plane/propel/icons";
|
||||
import { setPromiseToast } from "@plane/propel/toast";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import type { ICycle, TCycleGroups } from "@plane/types";
|
||||
import { Avatar, AvatarGroup, FavoriteStar } from "@plane/ui";
|
||||
import { FavoriteStar } from "@plane/ui";
|
||||
import { getDate, getFileURL, generateQueryParams } from "@plane/utils";
|
||||
// components
|
||||
import { AvatarGroupOverflow } from "@/components/common/avatar-group-overflow";
|
||||
import { DateRangeDropdown } from "@/components/dropdowns/date-range";
|
||||
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
||||
import { MergedDateDisplay } from "@/components/dropdowns/merged-date";
|
||||
@@ -266,14 +268,19 @@ export const CycleListItemAction = observer(function CycleListItemAction(props:
|
||||
<Tooltip label={`${cycleDetails.assignee_ids?.length} Members`} layout="stacked" disabled={isMobile}>
|
||||
<div className="flex w-min cursor-default items-center justify-center">
|
||||
{cycleDetails.assignee_ids && cycleDetails.assignee_ids?.length > 0 ? (
|
||||
<AvatarGroup showTooltip={false}>
|
||||
<AvatarGroupOverflow size="xs">
|
||||
{cycleDetails.assignee_ids?.map((assignee_id) => {
|
||||
const member = getUserDetails(assignee_id);
|
||||
return (
|
||||
<Avatar key={member?.id} name={member?.display_name} src={getFileURL(member?.avatar_url ?? "")} />
|
||||
<Avatar
|
||||
key={member?.id}
|
||||
alt={member?.display_name}
|
||||
fallback={member?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(member?.avatar_url ?? "")}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</AvatarGroup>
|
||||
</AvatarGroupOverflow>
|
||||
) : (
|
||||
<MembersPropertyIcon className="h-4 w-4 text-tertiary" />
|
||||
)}
|
||||
|
||||
@@ -6,38 +6,46 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import type { AvatarGroupSize } from "@makeplane/propel/components/avatar-group";
|
||||
import { MembersPropertyIcon } from "@plane/propel/icons";
|
||||
// plane ui
|
||||
import { Avatar, AvatarGroup } from "@plane/ui";
|
||||
import { cn, getFileURL } from "@plane/utils";
|
||||
// plane utils
|
||||
// helpers
|
||||
// hooks
|
||||
import { AvatarGroupOverflow } from "@/components/common/avatar-group-overflow";
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
|
||||
type AvatarProps = {
|
||||
showTooltip: boolean;
|
||||
userIds: string | string[] | null;
|
||||
icon?: LucideIcon;
|
||||
size?: "sm" | "md" | "base" | "lg" | number;
|
||||
size?: AvatarGroupSize;
|
||||
};
|
||||
|
||||
export const ButtonAvatars = observer(function ButtonAvatars(props: AvatarProps) {
|
||||
const { showTooltip, userIds, icon: Icon, size = "md" } = props;
|
||||
const { userIds, icon: Icon, size = "xs" } = props;
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
|
||||
if (Array.isArray(userIds)) {
|
||||
if (userIds.length > 0)
|
||||
return (
|
||||
<AvatarGroup size={size} showTooltip={!showTooltip}>
|
||||
<AvatarGroupOverflow size={size}>
|
||||
{userIds.map((userId) => {
|
||||
const userDetails = getUserDetails(userId);
|
||||
|
||||
if (!userDetails) return;
|
||||
return <Avatar key={userId} src={getFileURL(userDetails.avatar_url)} name={userDetails.display_name} />;
|
||||
return (
|
||||
<Avatar
|
||||
key={userId}
|
||||
src={getFileURL(userDetails.avatar_url)}
|
||||
alt={userDetails.display_name}
|
||||
fallback={userDetails.display_name?.[0]?.toUpperCase()}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</AvatarGroup>
|
||||
</AvatarGroupOverflow>
|
||||
);
|
||||
} else {
|
||||
if (userIds) {
|
||||
@@ -45,9 +53,9 @@ export const ButtonAvatars = observer(function ButtonAvatars(props: AvatarProps)
|
||||
return (
|
||||
<Avatar
|
||||
src={getFileURL(userDetails?.avatar_url ?? "")}
|
||||
name={userDetails?.display_name}
|
||||
alt={userDetails?.display_name}
|
||||
fallback={userDetails?.display_name?.[0]?.toUpperCase()}
|
||||
size={size}
|
||||
showTooltip={!showTooltip}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@ import { createPortal } from "react-dom";
|
||||
import { usePopper } from "react-popper";
|
||||
import { Combobox } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { CheckIcon, SearchIcon, SuspendedUserIcon } from "@plane/propel/icons";
|
||||
import { EPillSize, EPillVariant, Pill } from "@plane/propel/pill";
|
||||
import type { IUserLite } from "@plane/types";
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { cn, getFileURL, sortByCurrentUserThenSelected } from "@plane/utils";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
@@ -102,7 +102,12 @@ export const MemberOptions = observer(function MemberOptions(props: Props) {
|
||||
{isUserSuspended(userId, workspaceSlug?.toString()) ? (
|
||||
<SuspendedUserIcon className="h-3.5 w-3.5 text-placeholder" />
|
||||
) : (
|
||||
<Avatar name={userDetails?.display_name} src={getFileURL(userDetails?.avatar_url ?? "")} />
|
||||
<Avatar
|
||||
alt={userDetails?.display_name}
|
||||
fallback={userDetails?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(userDetails?.avatar_url ?? "")}
|
||||
size="xs"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<span
|
||||
|
||||
@@ -8,9 +8,9 @@ import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { Link } from "react-router";
|
||||
// plane imports
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { ROLE } from "@plane/constants";
|
||||
import { Popover } from "@plane/propel/popover";
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { cn, getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
@@ -63,11 +63,10 @@ export const EditorUserMention = observer(function EditorUserMention(props: Prop
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="grid size-10 flex-shrink-0 place-items-center">
|
||||
<Avatar
|
||||
alt={userDetails?.display_name}
|
||||
fallback={userDetails?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(userDetails?.avatar_url ?? "")}
|
||||
name={userDetails?.display_name}
|
||||
size={40}
|
||||
className="text-18"
|
||||
showTooltip={false}
|
||||
size="xl"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
*/
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { PageIcon } from "@plane/propel/icons";
|
||||
// plane import
|
||||
import type { TActivityEntityData, TPageEntityData } from "@plane/types";
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { calculateTimeAgo, getFileURL, getPageName } from "@plane/utils";
|
||||
import { ListItem } from "@/components/core/list";
|
||||
// hooks
|
||||
@@ -65,7 +65,12 @@ export function RecentPage(props: BlockProps) {
|
||||
}
|
||||
quickActionElement={
|
||||
<div className="flex gap-4">
|
||||
<Avatar src={getFileURL(ownerDetails?.avatar_url ?? "")} name={ownerDetails?.display_name} />
|
||||
<Avatar
|
||||
alt={ownerDetails?.display_name}
|
||||
fallback={ownerDetails?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(ownerDetails?.avatar_url ?? "")}
|
||||
size="xs"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
parentRef={ref}
|
||||
|
||||
@@ -7,10 +7,9 @@
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
// plane types
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { CloseIcon } from "@plane/propel/icons";
|
||||
import type { TInboxIssueFilterMemberKeys } from "@plane/types";
|
||||
// plane ui
|
||||
import { Avatar } from "@plane/ui";
|
||||
// helpers
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
@@ -49,10 +48,10 @@ export const InboxIssueAppliedFiltersMember = observer(function InboxIssueApplie
|
||||
<div key={value} className="relative flex items-center gap-1 rounded-sm bg-layer-1 p-1 text-11">
|
||||
<div className="relative flex flex-shrink-0 items-center justify-center overflow-hidden">
|
||||
<Avatar
|
||||
name={optionDetail.display_name}
|
||||
alt={optionDetail.display_name}
|
||||
fallback={optionDetail.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(optionDetail.avatar_url)}
|
||||
showTooltip={false}
|
||||
size="sm"
|
||||
size="2xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="truncate text-11">{optionDetail?.display_name}</div>
|
||||
|
||||
@@ -8,9 +8,10 @@ import { useMemo, useState } from "react";
|
||||
import { sortBy } from "lodash-es";
|
||||
import { observer } from "mobx-react";
|
||||
// plane types
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import type { TInboxIssueFilterMemberKeys } from "@plane/types";
|
||||
// plane ui
|
||||
import { Avatar, Loader } from "@plane/ui";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { getFileURL } from "@plane/utils";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues/issue-layouts/filters";
|
||||
@@ -86,10 +87,10 @@ export const FilterMember = observer(function FilterMember(props: Props) {
|
||||
onClick={() => handleInboxIssueFilters(filterKey, handleFilterValue(member.id))}
|
||||
icon={
|
||||
<Avatar
|
||||
name={member.display_name}
|
||||
alt={member.display_name}
|
||||
fallback={member.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(member.avatar_url)}
|
||||
showTooltip={false}
|
||||
size="md"
|
||||
size="xs"
|
||||
/>
|
||||
}
|
||||
title={currentUser?.id === member.id ? "You" : member?.display_name}
|
||||
|
||||
@@ -9,10 +9,11 @@ import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { PriorityIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import { Row, Avatar } from "@plane/ui";
|
||||
import { cn, renderFormattedDate, getFileURL } from "@plane/utils";
|
||||
import { Row } from "@plane/ui";
|
||||
import { cn, renderFormattedDate } from "@plane/utils";
|
||||
// components
|
||||
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
||||
// hooks
|
||||
@@ -123,7 +124,7 @@ export const InboxIssueListItem = observer(function InboxIssueListItem(props: In
|
||||
</div>
|
||||
{/* created by */}
|
||||
{createdByDetails && createdByDetails.email?.includes("intake@plane.so") ? (
|
||||
<Avatar src={getFileURL("")} name={"Plane"} size="md" showTooltip />
|
||||
<Avatar alt="Plane" fallback="P" size="xs" />
|
||||
) : createdByDetails ? (
|
||||
<ButtonAvatars showTooltip={false} userIds={createdByDetails?.id} />
|
||||
) : null}
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { CloseIcon } from "@plane/propel/icons";
|
||||
// plane ui
|
||||
import { Avatar } from "@plane/ui";
|
||||
// helpers
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
@@ -36,10 +35,10 @@ export const AppliedMembersFilters = observer(function AppliedMembersFilters(pro
|
||||
return (
|
||||
<div key={memberId} className="flex items-center gap-1 rounded-sm bg-layer-1 p-1 text-11">
|
||||
<Avatar
|
||||
name={memberDetails.display_name}
|
||||
alt={memberDetails.display_name}
|
||||
fallback={memberDetails.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(memberDetails.avatar_url)}
|
||||
showTooltip={false}
|
||||
size={"sm"}
|
||||
size="2xs"
|
||||
/>
|
||||
<span className="normal-case">{memberDetails.display_name}</span>
|
||||
{editable && (
|
||||
|
||||
@@ -8,7 +8,8 @@ import { useMemo, useState } from "react";
|
||||
import { sortBy } from "lodash-es";
|
||||
import { observer } from "mobx-react";
|
||||
// plane ui
|
||||
import { Avatar, Loader } from "@plane/ui";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { getFileURL } from "@plane/utils";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues/issue-layouts/filters";
|
||||
@@ -78,10 +79,10 @@ export const FilterAssignees = observer(function FilterAssignees(props: Props) {
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={
|
||||
<Avatar
|
||||
name={member.display_name}
|
||||
alt={member.display_name}
|
||||
fallback={member.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(member.avatar_url)}
|
||||
showTooltip={false}
|
||||
size="md"
|
||||
size="xs"
|
||||
/>
|
||||
}
|
||||
title={currentUser?.id === member.id ? "You" : member?.display_name}
|
||||
|
||||
@@ -8,7 +8,8 @@ import { useMemo, useState } from "react";
|
||||
import { sortBy } from "lodash-es";
|
||||
import { observer } from "mobx-react";
|
||||
// plane ui
|
||||
import { Avatar, Loader } from "@plane/ui";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { getFileURL } from "@plane/utils";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues/issue-layouts/filters";
|
||||
@@ -76,7 +77,14 @@ export const FilterCreatedBy = observer(function FilterCreatedBy(props: Props) {
|
||||
key={`created-by-${member.id}`}
|
||||
isChecked={appliedFilters?.includes(member.id) ? true : false}
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={<Avatar name={member.display_name} src={getFileURL(member.avatar_url)} size="md" />}
|
||||
icon={
|
||||
<Avatar
|
||||
alt={member.display_name}
|
||||
fallback={member.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(member.avatar_url)}
|
||||
size="xs"
|
||||
/>
|
||||
}
|
||||
title={currentUser?.id === member.id ? "You" : member?.display_name}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -8,7 +8,8 @@ import { useMemo, useState } from "react";
|
||||
import { sortBy } from "lodash-es";
|
||||
import { observer } from "mobx-react";
|
||||
// plane ui
|
||||
import { Loader, Avatar } from "@plane/ui";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { getFileURL } from "@plane/utils";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues/issue-layouts/filters";
|
||||
@@ -78,10 +79,10 @@ export const FilterMentions = observer(function FilterMentions(props: Props) {
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={
|
||||
<Avatar
|
||||
name={member?.display_name}
|
||||
alt={member?.display_name}
|
||||
fallback={member?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(member?.avatar_url)}
|
||||
showTooltip={false}
|
||||
size={"md"}
|
||||
size="xs"
|
||||
/>
|
||||
}
|
||||
title={currentUser?.id === member.id ? "You" : member?.display_name}
|
||||
|
||||
@@ -12,6 +12,7 @@ import scrollIntoView from "smooth-scroll-into-view-if-needed";
|
||||
import type { FC } from "react";
|
||||
import { CalendarDays, LayersIcon, Paperclip } from "lucide-react";
|
||||
// plane types
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { EIconSize, ISSUE_PRIORITIES, STATE_GROUPS } from "@plane/constants";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import type { ISvgIcons } from "@plane/propel/icons";
|
||||
@@ -47,7 +48,7 @@ import type {
|
||||
} from "@plane/types";
|
||||
import { EIssuesStoreType } from "@plane/types";
|
||||
// plane ui
|
||||
import { Avatar } from "@plane/ui";
|
||||
|
||||
import { renderFormattedDate, getFileURL } from "@plane/utils";
|
||||
// store
|
||||
import { store } from "@/lib/store-context";
|
||||
@@ -314,12 +315,19 @@ const getAssigneeColumns = ({ isWorkspaceLevel, projectId }: TGetColumns): IGrou
|
||||
assigneeColumns.push({
|
||||
id: memberId,
|
||||
name: member?.display_name || "",
|
||||
icon: <Avatar name={member?.display_name} src={getFileURL(member?.avatar_url ?? "")} size="md" />,
|
||||
icon: (
|
||||
<Avatar
|
||||
alt={member?.display_name}
|
||||
fallback={member?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(member?.avatar_url ?? "")}
|
||||
size="xs"
|
||||
/>
|
||||
),
|
||||
payload: { assignee_ids: [memberId] },
|
||||
});
|
||||
});
|
||||
if (includeNone) {
|
||||
assigneeColumns.push({ id: "None", name: "None", icon: <Avatar size="md" />, payload: {} });
|
||||
assigneeColumns.push({ id: "None", name: "None", icon: <Avatar size="xs" />, payload: {} });
|
||||
}
|
||||
|
||||
return assigneeColumns;
|
||||
@@ -337,7 +345,14 @@ const getCreatedByColumns = (): IGroupByColumn[] | undefined => {
|
||||
return {
|
||||
id: memberId,
|
||||
name: member?.display_name || "",
|
||||
icon: <Avatar name={member?.display_name} src={getFileURL(member?.avatar_url ?? "")} size="md" />,
|
||||
icon: (
|
||||
<Avatar
|
||||
alt={member?.display_name}
|
||||
fallback={member?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(member?.avatar_url ?? "")}
|
||||
size="xs"
|
||||
/>
|
||||
),
|
||||
payload: {},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { CloseIcon } from "@plane/propel/icons";
|
||||
// plane ui
|
||||
import { Avatar } from "@plane/ui";
|
||||
// helpers
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
@@ -36,10 +35,10 @@ export const AppliedMembersFilters = observer(function AppliedMembersFilters(pro
|
||||
return (
|
||||
<div key={memberId} className="flex items-center gap-1 rounded-sm bg-layer-1 p-1 text-11">
|
||||
<Avatar
|
||||
name={memberDetails.display_name}
|
||||
alt={memberDetails.display_name}
|
||||
fallback={memberDetails.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(memberDetails.avatar_url)}
|
||||
showTooltip={false}
|
||||
size={"sm"}
|
||||
size="2xs"
|
||||
/>
|
||||
<span className="normal-case">{memberDetails.display_name}</span>
|
||||
{editable && (
|
||||
|
||||
@@ -8,7 +8,8 @@ import { useMemo, useState } from "react";
|
||||
import { sortBy } from "lodash-es";
|
||||
import { observer } from "mobx-react";
|
||||
// plane ui
|
||||
import { Avatar, Loader } from "@plane/ui";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { getFileURL } from "@plane/utils";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues/issue-layouts/filters";
|
||||
@@ -78,10 +79,10 @@ export const FilterLead = observer(function FilterLead(props: Props) {
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={
|
||||
<Avatar
|
||||
name={member.display_name}
|
||||
alt={member.display_name}
|
||||
fallback={member.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(member.avatar_url)}
|
||||
showTooltip={false}
|
||||
size="md"
|
||||
size="xs"
|
||||
/>
|
||||
}
|
||||
title={currentUser?.id === member.id ? "You" : member?.display_name}
|
||||
|
||||
@@ -8,7 +8,8 @@ import { useMemo, useState } from "react";
|
||||
import { sortBy } from "lodash-es";
|
||||
import { observer } from "mobx-react";
|
||||
// plane ui
|
||||
import { Avatar, Loader } from "@plane/ui";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { getFileURL } from "@plane/utils";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues/issue-layouts/filters";
|
||||
@@ -78,10 +79,10 @@ export const FilterMembers = observer(function FilterMembers(props: Props) {
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={
|
||||
<Avatar
|
||||
name={member.display_name}
|
||||
alt={member.display_name}
|
||||
fallback={member.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(member.avatar_url)}
|
||||
showTooltip={false}
|
||||
size="md"
|
||||
size="xs"
|
||||
/>
|
||||
}
|
||||
title={currentUser?.id === member.id ? "You" : member?.display_name}
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { Earth, Info, Minus } from "lucide-react";
|
||||
// plane imports
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { LockIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import { Avatar, FavoriteStar } from "@plane/ui";
|
||||
import { FavoriteStar } from "@plane/ui";
|
||||
import { renderFormattedDate, getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
@@ -44,7 +45,12 @@ export const BlockItemAction = observer(function BlockItemAction(props: Props) {
|
||||
{/* page details */}
|
||||
<div className="cursor-default">
|
||||
<Tooltip label={`Owned by: ${ownerDetails?.display_name ?? ""}`} layout="stacked">
|
||||
<Avatar src={getFileURL(ownerDetails?.avatar_url ?? "")} name={ownerDetails?.display_name} />
|
||||
<Avatar
|
||||
alt={ownerDetails?.display_name}
|
||||
fallback={ownerDetails?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(ownerDetails?.avatar_url ?? "")}
|
||||
size="xs"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="cursor-default text-tertiary">
|
||||
|
||||
@@ -8,8 +8,8 @@ import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { calculateTimeAgoShort, getFileURL, renderFormattedDate } from "@plane/utils";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
@@ -40,10 +40,10 @@ export const PageNavigationPaneInfoTabActorsInfo = observer(function PageNavigat
|
||||
<div className="mt-2 flex items-center justify-between gap-2 text-13 font-medium">
|
||||
<Link href={`/${workspaceSlug?.toString()}/profile/${page.updated_by}`} className="flex items-center gap-1">
|
||||
<Avatar
|
||||
alt={editorInformation?.display_name}
|
||||
fallback={editorInformation?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(editorInformation?.avatar_url ?? "")}
|
||||
name={editorInformation?.display_name}
|
||||
className="flex-shrink-0"
|
||||
size="sm"
|
||||
size="2xs"
|
||||
/>
|
||||
<span>{editorInformation?.display_name ?? t("common.deactivated_user")}</span>
|
||||
</Link>
|
||||
@@ -57,10 +57,10 @@ export const PageNavigationPaneInfoTabActorsInfo = observer(function PageNavigat
|
||||
<div className="mt-2 flex items-center justify-between gap-2 text-13 font-medium">
|
||||
<Link href={`/${workspaceSlug?.toString()}/profile/${page.created_by}`} className="flex items-center gap-1">
|
||||
<Avatar
|
||||
alt={creatorInformation?.display_name}
|
||||
fallback={creatorInformation?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(creatorInformation?.avatar_url ?? "")}
|
||||
name={creatorInformation?.display_name}
|
||||
className="flex-shrink-0"
|
||||
size="sm"
|
||||
size="2xs"
|
||||
/>
|
||||
<span>{creatorInformation?.display_name ?? t("common.deactivated_user")}</span>
|
||||
</Link>
|
||||
|
||||
@@ -10,9 +10,9 @@ import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { TPageVersion } from "@plane/types";
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { cn, getFileURL, renderFormattedDate, renderFormattedTime } from "@plane/utils";
|
||||
// components
|
||||
import type { TPageRootHandlers } from "@/components/pages/editor/page-root";
|
||||
@@ -62,10 +62,10 @@ const VersionHistoryItem = observer(function VersionHistoryItem(props: VersionHi
|
||||
</p>
|
||||
<p className="mt-1 flex items-center gap-1">
|
||||
<Avatar
|
||||
size="sm"
|
||||
alt={versionCreator?.display_name}
|
||||
fallback={versionCreator?.display_name?.[0]?.toUpperCase()}
|
||||
size="2xs"
|
||||
src={getFileURL(versionCreator?.avatar_url ?? "")}
|
||||
name={versionCreator?.display_name}
|
||||
className="shrink-0"
|
||||
/>
|
||||
<span>{versionCreator?.display_name ?? t("common.deactivated_user")}</span>
|
||||
</p>
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
import { Command } from "cmdk";
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { Avatar } from "@plane/ui";
|
||||
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
@@ -37,10 +38,10 @@ export const PowerKMembersMenu = observer(function PowerKMembersMenu(props: Prop
|
||||
key={memberId}
|
||||
iconNode={
|
||||
<Avatar
|
||||
name={memberDetails?.display_name}
|
||||
alt={memberDetails?.display_name}
|
||||
fallback={memberDetails?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(memberDetails?.avatar_url ?? "")}
|
||||
showTooltip={false}
|
||||
className="shrink-0"
|
||||
size="xs"
|
||||
/>
|
||||
}
|
||||
isSelected={value.includes(memberId)}
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { CloseIcon } from "@plane/propel/icons";
|
||||
// ui
|
||||
import { Avatar } from "@plane/ui";
|
||||
// helpers
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// types
|
||||
@@ -36,10 +35,10 @@ export const AppliedMembersFilters = observer(function AppliedMembersFilters(pro
|
||||
return (
|
||||
<div key={memberId} className="flex items-center gap-1 rounded-sm bg-layer-1 px-1.5 py-1 text-11">
|
||||
<Avatar
|
||||
name={memberDetails.display_name}
|
||||
alt={memberDetails.display_name}
|
||||
fallback={memberDetails.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(memberDetails.avatar_url)}
|
||||
showTooltip={false}
|
||||
size={"sm"}
|
||||
size="2xs"
|
||||
/>
|
||||
<span className="normal-case">{memberDetails.display_name}</span>
|
||||
{editable && (
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ArchiveRestoreIcon, Settings, UserPlus } from "lucide-react";
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel, IS_FAVORITE_MENU_OPEN } from "@plane/constants";
|
||||
import { useLocalStorage } from "@plane/hooks";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { LinkIcon, LockIcon, NewTabIcon, TrashIcon, CheckIcon } from "@plane/propel/icons";
|
||||
@@ -19,7 +20,7 @@ import { setPromiseToast, setToast, TOAST_TYPE } from "@plane/propel/toast";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import type { IProject } from "@plane/types";
|
||||
import type { TContextMenuItem } from "@plane/ui";
|
||||
import { Avatar, AvatarGroup, ContextMenu, FavoriteStar } from "@plane/ui";
|
||||
import { ContextMenu, FavoriteStar } from "@plane/ui";
|
||||
import { copyUrlToClipboard, cn, getFileURL, renderFormattedDate } from "@plane/utils";
|
||||
// components
|
||||
// hooks
|
||||
@@ -29,6 +30,7 @@ import { useUserPermissions } from "@/hooks/store/user";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// local imports
|
||||
import { AvatarGroupOverflow } from "@/components/common/avatar-group-overflow";
|
||||
import { CoverImage } from "@/components/common/cover-image";
|
||||
import { DeleteProjectModal } from "./delete-project-modal";
|
||||
import { JoinProjectModal } from "./join-project-modal";
|
||||
@@ -285,15 +287,20 @@ export const ProjectCard = observer(function ProjectCard(props: Props) {
|
||||
>
|
||||
{projectMembersIds && projectMembersIds.length > 0 ? (
|
||||
<div className="flex cursor-pointer items-center gap-2 text-secondary">
|
||||
<AvatarGroup showTooltip={false}>
|
||||
<AvatarGroupOverflow size="xs">
|
||||
{projectMembersIds.map((memberId) => {
|
||||
const member = getUserDetails(memberId);
|
||||
if (!member) return null;
|
||||
return (
|
||||
<Avatar key={member.id} name={member.display_name} src={getFileURL(member.avatar_url)} />
|
||||
<Avatar
|
||||
key={member.id}
|
||||
alt={member.display_name}
|
||||
fallback={member.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(member.avatar_url)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</AvatarGroup>
|
||||
</AvatarGroupOverflow>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-13 text-placeholder italic">No Member Yet</span>
|
||||
|
||||
@@ -8,7 +8,8 @@ import { useMemo, useState } from "react";
|
||||
import { sortBy } from "lodash-es";
|
||||
import { observer } from "mobx-react";
|
||||
// plane ui
|
||||
import { Avatar, Loader } from "@plane/ui";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { getFileURL } from "@plane/utils";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues/issue-layouts/filters";
|
||||
@@ -78,10 +79,10 @@ export const FilterLead = observer(function FilterLead(props: Props) {
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={
|
||||
<Avatar
|
||||
name={member.display_name}
|
||||
alt={member.display_name}
|
||||
fallback={member.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(member.avatar_url)}
|
||||
showTooltip={false}
|
||||
size="md"
|
||||
size="xs"
|
||||
/>
|
||||
}
|
||||
title={currentUser?.id === member.id ? "You" : member?.display_name}
|
||||
|
||||
@@ -8,7 +8,8 @@ import { useMemo, useState } from "react";
|
||||
import { sortBy } from "lodash-es";
|
||||
import { observer } from "mobx-react";
|
||||
// plane ui
|
||||
import { Avatar, Loader } from "@plane/ui";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { getFileURL } from "@plane/utils";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues/issue-layouts/filters";
|
||||
@@ -78,10 +79,10 @@ export const FilterMembers = observer(function FilterMembers(props: Props) {
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={
|
||||
<Avatar
|
||||
name={member.display_name}
|
||||
alt={member.display_name}
|
||||
fallback={member.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(member.avatar_url)}
|
||||
showTooltip={false}
|
||||
size="md"
|
||||
size="xs"
|
||||
/>
|
||||
}
|
||||
title={currentUser?.id === member.id ? "You" : member?.display_name}
|
||||
|
||||
@@ -8,9 +8,10 @@ import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { Ban } from "lucide-react";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { EUserProjectRoles } from "@plane/types";
|
||||
// plane ui
|
||||
import { Avatar, CustomSearchSelect } from "@plane/ui";
|
||||
import { CustomSearchSelect } from "@plane/ui";
|
||||
// helpers
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
@@ -44,7 +45,12 @@ export const MemberSelect = observer(function MemberSelect(props: Props) {
|
||||
query: `${memberDetails?.member.display_name}`,
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar name={memberDetails?.member.display_name} src={getFileURL(memberDetails?.member.avatar_url)} />
|
||||
<Avatar
|
||||
alt={memberDetails?.member.display_name}
|
||||
fallback={memberDetails?.member.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(memberDetails?.member.avatar_url)}
|
||||
size="xs"
|
||||
/>
|
||||
{memberDetails?.member.display_name}
|
||||
</div>
|
||||
),
|
||||
@@ -65,7 +71,12 @@ export const MemberSelect = observer(function MemberSelect(props: Props) {
|
||||
label={
|
||||
<div className="flex h-3.5 items-center gap-2">
|
||||
{selectedOption && (
|
||||
<Avatar name={selectedOption.member?.display_name} src={getFileURL(selectedOption.member?.avatar_url)} />
|
||||
<Avatar
|
||||
alt={selectedOption.member?.display_name}
|
||||
fallback={selectedOption.member?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(selectedOption.member?.avatar_url)}
|
||||
size="xs"
|
||||
/>
|
||||
)}
|
||||
{selectedOption ? (
|
||||
selectedOption.member?.display_name
|
||||
|
||||
@@ -8,12 +8,13 @@ import React, { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useForm, Controller, useFieldArray } from "react-hook-form";
|
||||
// plane imports
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { ROLE, EUserPermissions } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { PlusIcon, CloseIcon, ChevronDownIcon } from "@plane/propel/icons";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { Avatar, CustomSelect, CustomSearchSelect, EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
|
||||
import { CustomSelect, CustomSearchSelect, EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
|
||||
// helpers
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
@@ -140,7 +141,12 @@ export const SendProjectInvitationModal = observer(function SendProjectInvitatio
|
||||
content: (
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<div className="shrink-0 pt-0.5">
|
||||
<Avatar name={memberDetails?.member.display_name} src={getFileURL(memberDetails?.member.avatar_url)} />
|
||||
<Avatar
|
||||
alt={memberDetails?.member.display_name}
|
||||
fallback={memberDetails?.member.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(memberDetails?.member.avatar_url)}
|
||||
size="xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="truncate">
|
||||
{memberDetails?.member.display_name} (
|
||||
@@ -200,8 +206,10 @@ export const SendProjectInvitationModal = observer(function SendProjectInvitatio
|
||||
{value && value !== "" ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar
|
||||
name={selectedMember?.member.display_name}
|
||||
alt={selectedMember?.member.display_name}
|
||||
fallback={selectedMember?.member.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(selectedMember?.member.avatar_url ?? "")}
|
||||
size="xs"
|
||||
/>
|
||||
{selectedMember?.member.display_name}
|
||||
</div>
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store/user";
|
||||
import { getFileURL } from "@plane/utils";
|
||||
|
||||
export const ProfileSettingsSidebarHeader = observer(function ProfileSettingsSidebarHeader() {
|
||||
// store hooks
|
||||
@@ -19,11 +19,10 @@ export const ProfileSettingsSidebarHeader = observer(function ProfileSettingsSid
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<div className="shrink-0">
|
||||
<Avatar
|
||||
alt={currentUser?.display_name}
|
||||
fallback={currentUser?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(currentUser?.avatar_url ?? "")}
|
||||
name={currentUser?.display_name}
|
||||
size={32}
|
||||
shape="circle"
|
||||
className="text-16"
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
<div className="truncate">
|
||||
|
||||
@@ -8,7 +8,8 @@ import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Clock } from "lucide-react";
|
||||
// plane imports
|
||||
import { Avatar, Row } from "@plane/ui";
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { Row } from "@plane/ui";
|
||||
import { cn, calculateTimeAgo, renderFormattedDate, renderFormattedTime, getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
import { useWorkspaceNotifications } from "@/hooks/store/notifications";
|
||||
@@ -87,11 +88,11 @@ export const NotificationItem = observer(function NotificationItem(props: TNotif
|
||||
<div className="relative flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-layer-1">
|
||||
{notificationTriggeredBy && (
|
||||
<Avatar
|
||||
name={notificationTriggeredBy.display_name || notificationTriggeredBy?.first_name}
|
||||
alt={notificationTriggeredBy.display_name || notificationTriggeredBy?.first_name}
|
||||
fallback={(notificationTriggeredBy.display_name ||
|
||||
notificationTriggeredBy?.first_name)?.[0]?.toUpperCase()}
|
||||
src={getFileURL(notificationTriggeredBy.avatar_url)}
|
||||
size={42}
|
||||
shape="circle"
|
||||
className="bg-layer-1 text-body-sm-medium"
|
||||
size="xl"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -9,10 +9,11 @@ import { observer } from "mobx-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { LogOut, Settings, Settings2 } from "lucide-react";
|
||||
// plane imports
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { GOD_MODE_URL } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { Avatar, CustomMenu } from "@plane/ui";
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// components
|
||||
import { CoverImage } from "@/components/common/cover-image";
|
||||
@@ -62,10 +63,10 @@ export const UserMenuRoot = observer(function UserMenuRoot() {
|
||||
item={{
|
||||
icon: (
|
||||
<Avatar
|
||||
name={currentUser?.display_name}
|
||||
alt={currentUser?.display_name}
|
||||
fallback={currentUser?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(currentUser?.avatar_url ?? "")}
|
||||
size={20}
|
||||
shape="circle"
|
||||
size="xs"
|
||||
/>
|
||||
),
|
||||
isActive: isUserMenuOpen,
|
||||
@@ -91,11 +92,10 @@ export const UserMenuRoot = observer(function UserMenuRoot() {
|
||||
<div className="flex flex-col items-center gap-y-2">
|
||||
<div>
|
||||
<Avatar
|
||||
name={currentUser?.display_name}
|
||||
alt={currentUser?.display_name}
|
||||
fallback={currentUser?.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(currentUser?.avatar_url ?? "")}
|
||||
size={40}
|
||||
shape="circle"
|
||||
className="text-18 font-medium"
|
||||
size="xl"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
|
||||
@@ -6,11 +6,10 @@
|
||||
|
||||
import { useCallback } from "react";
|
||||
// plane editor
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import type { TMentionSection, TMentionSuggestion } from "@plane/editor";
|
||||
// plane types
|
||||
import type { TSearchEntities, TSearchEntityRequestPayload, TSearchResponse, TUserSearchResponse } from "@plane/types";
|
||||
// plane ui
|
||||
import { Avatar } from "@plane/ui";
|
||||
// helpers
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// plane web hooks
|
||||
@@ -47,9 +46,10 @@ export const useEditorMention = (args: TArgs) => {
|
||||
const items: TMentionSuggestion[] = (response as TUserSearchResponse[]).map((user) => ({
|
||||
icon: (
|
||||
<Avatar
|
||||
className="flex-shrink-0"
|
||||
alt={user.member__display_name}
|
||||
fallback={user.member__display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(user.member__avatar_url)}
|
||||
name={user.member__display_name}
|
||||
size="xs"
|
||||
/>
|
||||
),
|
||||
id: user.member__id,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { AtSign, Briefcase } from "lucide-react";
|
||||
// plane imports
|
||||
import { Avatar } from "@makeplane/propel/components/avatar";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import {
|
||||
CalendarLayoutIcon,
|
||||
@@ -33,7 +34,7 @@ import type {
|
||||
IProject,
|
||||
TWorkItemFilterProperty,
|
||||
} from "@plane/types";
|
||||
import { Avatar } from "@plane/ui";
|
||||
|
||||
import {
|
||||
getAssigneeFilterConfig,
|
||||
getCreatedAtFilterConfig,
|
||||
@@ -222,10 +223,10 @@ export const useWorkItemFiltersConfig = (props: TUseWorkItemFiltersConfigProps):
|
||||
members: members ?? [],
|
||||
getOptionIcon: (memberDetails) => (
|
||||
<Avatar
|
||||
name={memberDetails.display_name}
|
||||
alt={memberDetails.display_name}
|
||||
fallback={memberDetails.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(memberDetails.avatar_url)}
|
||||
showTooltip={false}
|
||||
size="sm"
|
||||
size="2xs"
|
||||
/>
|
||||
),
|
||||
...operatorConfigs,
|
||||
@@ -242,10 +243,10 @@ export const useWorkItemFiltersConfig = (props: TUseWorkItemFiltersConfigProps):
|
||||
members: members ?? [],
|
||||
getOptionIcon: (memberDetails) => (
|
||||
<Avatar
|
||||
name={memberDetails.display_name}
|
||||
alt={memberDetails.display_name}
|
||||
fallback={memberDetails.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(memberDetails.avatar_url)}
|
||||
showTooltip={false}
|
||||
size="sm"
|
||||
size="2xs"
|
||||
/>
|
||||
),
|
||||
...operatorConfigs,
|
||||
@@ -262,10 +263,10 @@ export const useWorkItemFiltersConfig = (props: TUseWorkItemFiltersConfigProps):
|
||||
members: members ?? [],
|
||||
getOptionIcon: (memberDetails) => (
|
||||
<Avatar
|
||||
name={memberDetails.display_name}
|
||||
alt={memberDetails.display_name}
|
||||
fallback={memberDetails.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(memberDetails.avatar_url)}
|
||||
showTooltip={false}
|
||||
size="sm"
|
||||
size="2xs"
|
||||
/>
|
||||
),
|
||||
...operatorConfigs,
|
||||
@@ -282,10 +283,10 @@ export const useWorkItemFiltersConfig = (props: TUseWorkItemFiltersConfigProps):
|
||||
members: members ?? [],
|
||||
getOptionIcon: (memberDetails) => (
|
||||
<Avatar
|
||||
name={memberDetails.display_name}
|
||||
alt={memberDetails.display_name}
|
||||
fallback={memberDetails.display_name?.[0]?.toUpperCase()}
|
||||
src={getFileURL(memberDetails.avatar_url)}
|
||||
showTooltip={false}
|
||||
size="sm"
|
||||
size="2xs"
|
||||
/>
|
||||
),
|
||||
...operatorConfigs,
|
||||
|
||||
Reference in New Issue
Block a user