[WEB-4605] feat: initiative logo and icon implementation (#4075)
This commit is contained in:
@@ -8,6 +8,7 @@ import { useTranslation } from "@plane/i18n";
|
||||
// plane imports
|
||||
import { EmojiPicker } from "@plane/propel/emoji-icon-picker";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { EmojiPicker } from "@plane/propel/emoji-icon-picker";
|
||||
import { IProject, IWorkspace } from "@plane/types";
|
||||
import { Button, CustomSelect, Input, TextArea, TOAST_TYPE, setToast, EmojiIconPickerTypes } from "@plane/ui";
|
||||
import { renderFormattedDate, getFileURL } from "@plane/utils";
|
||||
|
||||
@@ -2,17 +2,19 @@ import { FC, useState } from "react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmojiPicker } from "@plane/propel/emoji-icon-picker";
|
||||
import { EFileAssetType } from "@plane/types";
|
||||
import { Button, Input, setToast, TOAST_TYPE } from "@plane/ui";
|
||||
import { cn, getDate, getDescriptionPlaceholderI18n, renderFormattedPayloadDate } from "@plane/utils";
|
||||
import { Button, Input, setToast, TOAST_TYPE, InitiativeIcon, EmojiIconPickerTypes } from "@plane/ui";
|
||||
import { getDate, getDescriptionPlaceholderI18n, renderFormattedPayloadDate } from "@plane/utils";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { DateRangeDropdown } from "@/components/dropdowns/date-range";
|
||||
import { MemberDropdown } from "@/components/dropdowns/member/dropdown";
|
||||
import { ProjectDropdown } from "@/components/dropdowns/project/dropdown";
|
||||
import { RichTextEditor } from "@/components/editor/rich-text";
|
||||
// hooks
|
||||
import { useEditorAsset } from "@/hooks/store/use-editor-asset"
|
||||
import { useMember } from "@/hooks/store/use-member"
|
||||
import { useEditorAsset } from "@/hooks/store/use-editor-asset";
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
// plane web hooks
|
||||
import { useEditorMentionSearch } from "@/plane-web/hooks/use-editor-mention-search";
|
||||
@@ -32,6 +34,8 @@ type Props = {
|
||||
|
||||
export const CreateUpdateInitiativeForm: FC<Props> = (props) => {
|
||||
const { initiativeDetail, formData, isSubmitting, handleFormDataChange, handleClose, handleFormSubmit } = props;
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
// state
|
||||
@@ -66,6 +70,8 @@ export const CreateUpdateInitiativeForm: FC<Props> = (props) => {
|
||||
|
||||
if (!workspaceSlug || !currentWorkspace || !formData) return null;
|
||||
|
||||
const logoValue = formData?.logo_props;
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
@@ -85,7 +91,46 @@ export const CreateUpdateInitiativeForm: FC<Props> = (props) => {
|
||||
<h3 className="text-xl font-medium text-custom-text-200">
|
||||
{formData.id ? t("initiatives.update_initiative") : t("initiatives.create_initiative")}
|
||||
</h3>
|
||||
<div className={cn("flex items-center gap-2 w-full", errors.name && "items-start")}>
|
||||
<div className="flex items-start gap-2 w-full">
|
||||
<EmojiPicker
|
||||
iconType="lucide"
|
||||
isOpen={isOpen}
|
||||
handleToggle={(val: boolean) => setIsOpen(val)}
|
||||
className="flex items-center justify-center flex-shrink0"
|
||||
buttonClassName="flex items-center justify-center"
|
||||
label={
|
||||
<span className="grid h-9 w-9 place-items-center rounded-md bg-custom-background-90">
|
||||
<>
|
||||
{logoValue?.in_use ? (
|
||||
<Logo logo={logoValue} size={18} type="lucide" />
|
||||
) : (
|
||||
<InitiativeIcon className="h-4 w-4 text-custom-text-300" />
|
||||
)}
|
||||
</>
|
||||
</span>
|
||||
}
|
||||
onChange={(val: any) => {
|
||||
let logoValue = {};
|
||||
|
||||
if (val?.type === "emoji")
|
||||
logoValue = {
|
||||
value: val.value,
|
||||
};
|
||||
else if (val?.type === "icon") logoValue = val.value;
|
||||
|
||||
handleFormDataChange("logo_props", {
|
||||
in_use: val?.type,
|
||||
[val?.type]: logoValue,
|
||||
});
|
||||
setIsOpen(false);
|
||||
}}
|
||||
defaultIconColor={logoValue?.in_use && logoValue?.in_use === "icon" ? logoValue?.icon?.color : undefined}
|
||||
defaultOpen={
|
||||
logoValue?.in_use && logoValue?.in_use === "emoji"
|
||||
? EmojiIconPickerTypes.EMOJI
|
||||
: EmojiIconPickerTypes.ICON
|
||||
}
|
||||
/>
|
||||
<div className="space-y-1 flew-grow w-full">
|
||||
<Input
|
||||
id="name"
|
||||
|
||||
@@ -7,6 +7,7 @@ import { EUserWorkspaceRoles } from "@plane/types";
|
||||
import { ControlLink, InitiativeIcon } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { ListItem } from "@/components/core/list";
|
||||
// hooks
|
||||
import { useAppTheme } from "@/hooks/store/use-app-theme"
|
||||
@@ -57,9 +58,13 @@ export const InitiativeBlock = observer((props: Props) => {
|
||||
title={initiative.name}
|
||||
itemLink={`/${workspaceSlug}/initiatives/${initiative.id}`}
|
||||
prependTitleElement={
|
||||
<div className="flex flex-shrink-0 size-8 items-center justify-center rounded-md bg-custom-background-90">
|
||||
<InitiativeIcon className="size-4 text-custom-text-300" />
|
||||
</div>
|
||||
<>
|
||||
{initiative?.logo_props?.in_use ? (
|
||||
<Logo logo={initiative?.logo_props} size={16} type="lucide" />
|
||||
) : (
|
||||
<InitiativeIcon className="h-4 w-4 text-custom-text-300" />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
quickActionElement={
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import React, { FC } from "react";
|
||||
import React, { FC, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { EmojiPicker } from "@plane/propel/emoji-icon-picker";
|
||||
import { EFileAssetType } from "@plane/types";
|
||||
import { InitiativeIcon } from "@plane/ui";
|
||||
import { EmojiIconPickerTypes, InitiativeIcon } from "@plane/ui";
|
||||
// plane web components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { InfoSection } from "@/plane-web/components/common/layout/main/sections/info-root";
|
||||
import { UpdateStatusPills } from "@/plane-web/components/initiatives/common/update-status";
|
||||
import { useInitiatives } from "@/plane-web/hooks/store/use-initiatives";
|
||||
@@ -24,6 +26,7 @@ type Props = {
|
||||
|
||||
export const InitiativeInfoSection: FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, initiativeId, disabled = false, toggleProjectModal, toggleEpicModal } = props;
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
// store hooks
|
||||
const {
|
||||
initiative: { getInitiativeById, updateInitiative, getInitiativeAnalyticsById },
|
||||
@@ -36,6 +39,8 @@ export const InitiativeInfoSection: FC<Props> = observer((props) => {
|
||||
|
||||
if (!initiative) return <></>;
|
||||
|
||||
const logoValue = initiative?.logo_props;
|
||||
|
||||
return (
|
||||
<InfoSection
|
||||
workspaceSlug={workspaceSlug}
|
||||
@@ -58,7 +63,45 @@ export const InitiativeInfoSection: FC<Props> = observer((props) => {
|
||||
disabled={disabled}
|
||||
iconElement={
|
||||
<div className="flex-shrink-0 size-11 bg-custom-background-80 rounded-md flex items-center justify-center text-custom-text-300">
|
||||
<InitiativeIcon width={24} height={24} />
|
||||
<EmojiPicker
|
||||
iconType="lucide"
|
||||
isOpen={isOpen}
|
||||
handleToggle={(val: boolean) => setIsOpen(val)}
|
||||
className="flex items-center justify-center flex-shrink0"
|
||||
buttonClassName="flex items-center justify-center"
|
||||
label={
|
||||
<>
|
||||
{logoValue?.in_use ? (
|
||||
<Logo logo={logoValue} size={18} type="lucide" />
|
||||
) : (
|
||||
<InitiativeIcon className="h-4 w-4 flex-shrink-0 text-custom-text-300" />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
onChange={(val: any) => {
|
||||
let logoValue = {};
|
||||
|
||||
if (val?.type === "emoji")
|
||||
logoValue = {
|
||||
value: val.value,
|
||||
};
|
||||
else if (val?.type === "icon") logoValue = val.value;
|
||||
|
||||
updateInitiative(workspaceSlug, initiativeId, {
|
||||
logo_props: {
|
||||
in_use: val?.type,
|
||||
[val?.type]: logoValue,
|
||||
},
|
||||
});
|
||||
setIsOpen(false);
|
||||
}}
|
||||
defaultIconColor={logoValue?.in_use && logoValue?.in_use === "icon" ? logoValue?.icon?.color : undefined}
|
||||
defaultOpen={
|
||||
logoValue?.in_use && logoValue?.in_use === "emoji"
|
||||
? EmojiIconPickerTypes.EMOJI
|
||||
: EmojiIconPickerTypes.ICON
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
titleElement={
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from "@plane/ui";
|
||||
// components
|
||||
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { SwitcherLabel } from "@/components/common/switcher-label";
|
||||
// hooks
|
||||
import { useUserPermissions } from "@/hooks/store/user/user-permissions";
|
||||
@@ -64,7 +65,12 @@ export const InitiativesDetailsHeader = observer((props: TInitiativesDetailsHead
|
||||
query: _initiative.name,
|
||||
content: (
|
||||
<Link href={`/${workspaceSlug}/initiatives/${_initiative.id}`}>
|
||||
<SwitcherLabel name={_initiative.name} LabelIcon={InitiativeIcon} />
|
||||
<SwitcherLabel
|
||||
name={_initiative.name}
|
||||
logo_props={_initiative.logo_props}
|
||||
LabelIcon={InitiativeIcon}
|
||||
type="lucide"
|
||||
/>
|
||||
</Link>
|
||||
),
|
||||
};
|
||||
@@ -131,7 +137,11 @@ export const InitiativesDetailsHeader = observer((props: TInitiativesDetailsHead
|
||||
title={initiativesDetails?.name}
|
||||
icon={
|
||||
<Breadcrumbs.Icon>
|
||||
<InitiativeIcon className="size-4 flex-shrink-0 text-custom-text-300" />
|
||||
{initiativesDetails?.logo_props?.in_use ? (
|
||||
<Logo logo={initiativesDetails?.logo_props} size={16} type="lucide" />
|
||||
) : (
|
||||
<InitiativeIcon className="size-4 flex-shrink-0 text-custom-text-300" />
|
||||
)}
|
||||
</Breadcrumbs.Icon>
|
||||
}
|
||||
isLast
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
InitiativeComment,
|
||||
TIssueActivityUserDetail,
|
||||
TIssueComment,
|
||||
TLogoProps,
|
||||
} from "@plane/types";
|
||||
|
||||
export type TInitiative = {
|
||||
@@ -28,6 +29,7 @@ export type TInitiative = {
|
||||
lead: string | null;
|
||||
project_ids: string[] | null;
|
||||
epic_ids: string[] | null;
|
||||
logo_props: TLogoProps | undefined;
|
||||
};
|
||||
|
||||
export type TInitiativeProject = {
|
||||
|
||||
@@ -9,6 +9,9 @@ export const GROUP_WORKSPACE_TRACKER_EVENT = "workspace_metrics";
|
||||
export const GITHUB_REDIRECTED_TRACKER_EVENT = "github_redirected";
|
||||
export const HEADER_GITHUB_ICON = "header_github_icon";
|
||||
|
||||
export const GITHUB_REDIRECTED_TRACKER_EVENT = "github_redirected";
|
||||
export const HEADER_GITHUB_ICON = "header_github_icon";
|
||||
|
||||
/**
|
||||
* ===========================================================================
|
||||
* Command palette tracker
|
||||
|
||||
Reference in New Issue
Block a user