forked from github/plane
[WEB-393] feat: new emoji picker using emoji-picker-react
(#3868)
* chore: emoji-picker-react package added * chore: emoji and emoji picker component added * chore: emoji picker custom style added * chore: migration of the emoji's * chore: migration changes * chore: project logo prop * chore: added logo props in the serializer * chore: removed unused keys * chore: implement emoji picker throughout the web app * style: emoji icon picker * chore: update project logo renderer in the space app * chore: migrations fixes --------- Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so> Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
This commit is contained in:
parent
b3d3c0fb06
commit
e4f48d6878
@ -95,8 +95,7 @@ class ProjectLiteSerializer(BaseSerializer):
|
|||||||
"identifier",
|
"identifier",
|
||||||
"name",
|
"name",
|
||||||
"cover_image",
|
"cover_image",
|
||||||
"icon_prop",
|
"logo_props",
|
||||||
"emoji",
|
|
||||||
"description",
|
"description",
|
||||||
]
|
]
|
||||||
read_only_fields = fields
|
read_only_fields = fields
|
||||||
|
@ -1366,10 +1366,6 @@ class WorkspaceUserProfileEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
.values(
|
.values(
|
||||||
"id",
|
"id",
|
||||||
"name",
|
|
||||||
"identifier",
|
|
||||||
"emoji",
|
|
||||||
"icon_prop",
|
|
||||||
"created_issues",
|
"created_issues",
|
||||||
"assigned_issues",
|
"assigned_issues",
|
||||||
"completed_issues",
|
"completed_issues",
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 4.2.7 on 2024-03-01 07:16
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('db', '0060_cycle_progress_snapshot'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='issuelink',
|
|
||||||
name='url',
|
|
||||||
field=models.TextField(),
|
|
||||||
),
|
|
||||||
]
|
|
54
apiserver/plane/db/migrations/0061_project_logo_props.py
Normal file
54
apiserver/plane/db/migrations/0061_project_logo_props.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# Generated by Django 4.2.7 on 2024-03-03 16:25
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
def update_project_logo_props(apps, schema_editor):
|
||||||
|
Project = apps.get_model("db", "Project")
|
||||||
|
|
||||||
|
bulk_update_project_logo = []
|
||||||
|
# Iterate through projects and update logo_props
|
||||||
|
for project in Project.objects.all():
|
||||||
|
project.logo_props["in_use"] = "emoji" if project.emoji else "icon"
|
||||||
|
project.logo_props["emoji"] = {
|
||||||
|
"value": project.emoji if project.emoji else "",
|
||||||
|
"url": "",
|
||||||
|
}
|
||||||
|
project.logo_props["icon"] = {
|
||||||
|
"name": (
|
||||||
|
project.icon_prop.get("name", "")
|
||||||
|
if project.icon_prop
|
||||||
|
else ""
|
||||||
|
),
|
||||||
|
"color": (
|
||||||
|
project.icon_prop.get("color", "")
|
||||||
|
if project.icon_prop
|
||||||
|
else ""
|
||||||
|
),
|
||||||
|
}
|
||||||
|
bulk_update_project_logo.append(project)
|
||||||
|
|
||||||
|
# Bulk update logo_props for all projects
|
||||||
|
Project.objects.bulk_update(
|
||||||
|
bulk_update_project_logo, ["logo_props"], batch_size=1000
|
||||||
|
)
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("db", "0060_cycle_progress_snapshot"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="issuelink",
|
||||||
|
name="url",
|
||||||
|
field=models.TextField(),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="project",
|
||||||
|
name="logo_props",
|
||||||
|
field=models.JSONField(default=dict),
|
||||||
|
),
|
||||||
|
migrations.RunPython(update_project_logo_props),
|
||||||
|
]
|
@ -107,6 +107,7 @@ class Project(BaseModel):
|
|||||||
close_in = models.IntegerField(
|
close_in = models.IntegerField(
|
||||||
default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]
|
default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]
|
||||||
)
|
)
|
||||||
|
logo_props = models.JSONField(default=dict)
|
||||||
default_state = models.ForeignKey(
|
default_state = models.ForeignKey(
|
||||||
"db.State",
|
"db.State",
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
|
27
packages/types/src/projects.d.ts
vendored
27
packages/types/src/projects.d.ts
vendored
@ -1,12 +1,26 @@
|
|||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import type {
|
import type {
|
||||||
|
IProjectViewProps,
|
||||||
IUser,
|
IUser,
|
||||||
IUserLite,
|
IUserLite,
|
||||||
|
IUserMemberLite,
|
||||||
IWorkspace,
|
IWorkspace,
|
||||||
IWorkspaceLite,
|
IWorkspaceLite,
|
||||||
TStateGroups,
|
TStateGroups,
|
||||||
} from ".";
|
} from ".";
|
||||||
|
|
||||||
|
export type TProjectLogoProps = {
|
||||||
|
in_use: "emoji" | "icon";
|
||||||
|
emoji?: {
|
||||||
|
value?: string;
|
||||||
|
url?: string;
|
||||||
|
};
|
||||||
|
icon?: {
|
||||||
|
name?: string;
|
||||||
|
color?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export interface IProject {
|
export interface IProject {
|
||||||
archive_in: number;
|
archive_in: number;
|
||||||
close_in: number;
|
close_in: number;
|
||||||
@ -21,24 +35,13 @@ export interface IProject {
|
|||||||
default_assignee: IUser | string | null;
|
default_assignee: IUser | string | null;
|
||||||
default_state: string | null;
|
default_state: string | null;
|
||||||
description: string;
|
description: string;
|
||||||
emoji: string | null;
|
|
||||||
emoji_and_icon:
|
|
||||||
| string
|
|
||||||
| {
|
|
||||||
name: string;
|
|
||||||
color: string;
|
|
||||||
}
|
|
||||||
| null;
|
|
||||||
estimate: string | null;
|
estimate: string | null;
|
||||||
icon_prop: {
|
|
||||||
name: string;
|
|
||||||
color: string;
|
|
||||||
} | null;
|
|
||||||
id: string;
|
id: string;
|
||||||
identifier: string;
|
identifier: string;
|
||||||
is_deployed: boolean;
|
is_deployed: boolean;
|
||||||
is_favorite: boolean;
|
is_favorite: boolean;
|
||||||
is_member: boolean;
|
is_member: boolean;
|
||||||
|
logo_props: TProjectLogoProps;
|
||||||
member_role: EUserProjectRoles | null;
|
member_role: EUserProjectRoles | null;
|
||||||
members: IProjectMemberLite[];
|
members: IProjectMemberLite[];
|
||||||
name: string;
|
name: string;
|
||||||
|
4
packages/types/src/users.d.ts
vendored
4
packages/types/src/users.d.ts
vendored
@ -132,11 +132,7 @@ export interface IUserProfileProjectSegregation {
|
|||||||
assigned_issues: number;
|
assigned_issues: number;
|
||||||
completed_issues: number;
|
completed_issues: number;
|
||||||
created_issues: number;
|
created_issues: number;
|
||||||
emoji: string | null;
|
|
||||||
icon_prop: null;
|
|
||||||
id: string;
|
id: string;
|
||||||
identifier: string;
|
|
||||||
name: string;
|
|
||||||
pending_issues: number;
|
pending_issues: number;
|
||||||
}[];
|
}[];
|
||||||
user_data: {
|
user_data: {
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
"@headlessui/react": "^1.7.17",
|
"@headlessui/react": "^1.7.17",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
|
"emoji-picker-react": "^4.5.16",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-popper": "^2.3.0",
|
"react-popper": "^2.3.0",
|
||||||
|
169
packages/ui/src/emoji/emoji-icon-picker.tsx
Normal file
169
packages/ui/src/emoji/emoji-icon-picker.tsx
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { usePopper } from "react-popper";
|
||||||
|
import EmojiPicker, { EmojiClickData, Theme } from "emoji-picker-react";
|
||||||
|
import { Popover, Tab } from "@headlessui/react";
|
||||||
|
import { Placement } from "@popperjs/core";
|
||||||
|
// components
|
||||||
|
import { IconsList } from "./icons-list";
|
||||||
|
// helpers
|
||||||
|
import { cn } from "../../helpers";
|
||||||
|
|
||||||
|
export enum EmojiIconPickerTypes {
|
||||||
|
EMOJI = "emoji",
|
||||||
|
ICON = "icon",
|
||||||
|
}
|
||||||
|
|
||||||
|
type TChangeHandlerProps =
|
||||||
|
| {
|
||||||
|
type: EmojiIconPickerTypes.EMOJI;
|
||||||
|
value: EmojiClickData;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: EmojiIconPickerTypes.ICON;
|
||||||
|
value: {
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TCustomEmojiPicker = {
|
||||||
|
buttonClassName?: string;
|
||||||
|
className?: string;
|
||||||
|
closeOnSelect?: boolean;
|
||||||
|
defaultIconColor?: string;
|
||||||
|
defaultOpen?: EmojiIconPickerTypes;
|
||||||
|
disabled?: boolean;
|
||||||
|
dropdownClassName?: string;
|
||||||
|
label: React.ReactNode;
|
||||||
|
onChange: (value: TChangeHandlerProps) => void;
|
||||||
|
placement?: Placement;
|
||||||
|
searchPlaceholder?: string;
|
||||||
|
theme?: Theme;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TABS_LIST = [
|
||||||
|
{
|
||||||
|
key: EmojiIconPickerTypes.EMOJI,
|
||||||
|
title: "Emojis",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: EmojiIconPickerTypes.ICON,
|
||||||
|
title: "Icons",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const CustomEmojiIconPicker: React.FC<TCustomEmojiPicker> = (props) => {
|
||||||
|
const {
|
||||||
|
buttonClassName,
|
||||||
|
className,
|
||||||
|
closeOnSelect = true,
|
||||||
|
defaultIconColor = "#5f5f5f",
|
||||||
|
defaultOpen = EmojiIconPickerTypes.EMOJI,
|
||||||
|
disabled = false,
|
||||||
|
dropdownClassName,
|
||||||
|
label,
|
||||||
|
onChange,
|
||||||
|
placement = "bottom-start",
|
||||||
|
searchPlaceholder = "Search",
|
||||||
|
theme,
|
||||||
|
} = props;
|
||||||
|
// refs
|
||||||
|
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||||
|
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||||
|
// popper-js
|
||||||
|
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||||
|
placement,
|
||||||
|
modifiers: [
|
||||||
|
{
|
||||||
|
name: "preventOverflow",
|
||||||
|
options: {
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover as="div" className={cn("relative", className)}>
|
||||||
|
{({ close }) => (
|
||||||
|
<>
|
||||||
|
<Popover.Button as={React.Fragment}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
ref={setReferenceElement}
|
||||||
|
className={cn("outline-none", buttonClassName)}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
</Popover.Button>
|
||||||
|
<Popover.Panel className="fixed z-10">
|
||||||
|
<div
|
||||||
|
ref={setPopperElement}
|
||||||
|
style={styles.popper}
|
||||||
|
{...attributes.popper}
|
||||||
|
className={cn(
|
||||||
|
"h-80 w-80 bg-custom-background-100 rounded-md border-[0.5px] border-custom-border-300 overflow-hidden",
|
||||||
|
dropdownClassName
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Tab.Group
|
||||||
|
as="div"
|
||||||
|
className="h-full w-full flex flex-col overflow-hidden"
|
||||||
|
defaultIndex={TABS_LIST.findIndex((tab) => tab.key === defaultOpen)}
|
||||||
|
>
|
||||||
|
<Tab.List as="div" className="grid grid-cols-2 gap-1 p-2">
|
||||||
|
{TABS_LIST.map((tab) => (
|
||||||
|
<Tab
|
||||||
|
key={tab.key}
|
||||||
|
className={({ selected }) =>
|
||||||
|
cn("py-1 text-sm rounded border border-custom-border-200", {
|
||||||
|
"bg-custom-background-80": selected,
|
||||||
|
"hover:bg-custom-background-90 focus:bg-custom-background-90": !selected,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{tab.title}
|
||||||
|
</Tab>
|
||||||
|
))}
|
||||||
|
</Tab.List>
|
||||||
|
<Tab.Panels as="div" className="h-full w-full overflow-y-auto">
|
||||||
|
<Tab.Panel>
|
||||||
|
<EmojiPicker
|
||||||
|
onEmojiClick={(val) => {
|
||||||
|
onChange({
|
||||||
|
type: EmojiIconPickerTypes.EMOJI,
|
||||||
|
value: val,
|
||||||
|
});
|
||||||
|
if (closeOnSelect) close();
|
||||||
|
}}
|
||||||
|
height="20rem"
|
||||||
|
width="100%"
|
||||||
|
theme={theme}
|
||||||
|
searchPlaceholder={searchPlaceholder}
|
||||||
|
previewConfig={{
|
||||||
|
showPreview: false,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tab.Panel>
|
||||||
|
<Tab.Panel>
|
||||||
|
<IconsList
|
||||||
|
defaultColor={defaultIconColor}
|
||||||
|
onChange={(val) => {
|
||||||
|
onChange({
|
||||||
|
type: EmojiIconPickerTypes.ICON,
|
||||||
|
value: val,
|
||||||
|
});
|
||||||
|
if (closeOnSelect) close();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tab.Panel>
|
||||||
|
</Tab.Panels>
|
||||||
|
</Tab.Group>
|
||||||
|
</div>
|
||||||
|
</Popover.Panel>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
110
packages/ui/src/emoji/icons-list.tsx
Normal file
110
packages/ui/src/emoji/icons-list.tsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
// components
|
||||||
|
import { Input } from "../form-fields";
|
||||||
|
// helpers
|
||||||
|
import { cn } from "../../helpers";
|
||||||
|
// constants
|
||||||
|
import { MATERIAL_ICONS_LIST } from "./icons";
|
||||||
|
|
||||||
|
type TIconsListProps = {
|
||||||
|
defaultColor: string;
|
||||||
|
onChange: (val: { name: string; color: string }) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_COLORS = ["#ff6b00", "#8cc1ff", "#fcbe1d", "#18904f", "#adf672", "#05c3ff", "#5f5f5f"];
|
||||||
|
|
||||||
|
export const IconsList: React.FC<TIconsListProps> = (props) => {
|
||||||
|
const { defaultColor, onChange } = props;
|
||||||
|
// states
|
||||||
|
const [activeColor, setActiveColor] = useState(defaultColor);
|
||||||
|
const [showHexInput, setShowHexInput] = useState(false);
|
||||||
|
const [hexValue, setHexValue] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (DEFAULT_COLORS.includes(defaultColor.toLowerCase())) setShowHexInput(false);
|
||||||
|
else {
|
||||||
|
setHexValue(defaultColor.slice(1, 7));
|
||||||
|
setShowHexInput(true);
|
||||||
|
}
|
||||||
|
}, [defaultColor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="grid grid-cols-8 gap-2 items-center justify-items-center px-2.5 h-9">
|
||||||
|
{showHexInput ? (
|
||||||
|
<div className="col-span-7 flex items-center gap-1 justify-self-stretch ml-2">
|
||||||
|
<span
|
||||||
|
className="h-4 w-4 flex-shrink-0 rounded-full mr-1"
|
||||||
|
style={{
|
||||||
|
backgroundColor: `#${hexValue}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="text-xs text-custom-text-300 flex-shrink-0">HEX</span>
|
||||||
|
<span className="text-xs text-custom-text-200 flex-shrink-0 -mr-1">#</span>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={hexValue}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setHexValue(value);
|
||||||
|
if (/^[0-9A-Fa-f]{6}$/.test(value)) setActiveColor(`#${value}`);
|
||||||
|
}}
|
||||||
|
className="flex-grow pl-0 text-xs text-custom-text-200"
|
||||||
|
mode="true-transparent"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
DEFAULT_COLORS.map((curCol) => (
|
||||||
|
<button
|
||||||
|
key={curCol}
|
||||||
|
type="button"
|
||||||
|
className="grid place-items-center"
|
||||||
|
onClick={() => {
|
||||||
|
setActiveColor(curCol);
|
||||||
|
setHexValue(curCol.slice(1, 7));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="h-4 w-4 cursor-pointer rounded-full" style={{ backgroundColor: curCol }} />
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={cn("grid place-items-center h-4 w-4 rounded-full border border-transparent", {
|
||||||
|
"border-custom-border-400": !showHexInput,
|
||||||
|
})}
|
||||||
|
onClick={() => {
|
||||||
|
setShowHexInput((prevData) => !prevData);
|
||||||
|
setHexValue(activeColor.slice(1, 7));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{showHexInput ? (
|
||||||
|
<span className="conical-gradient h-4 w-4 rounded-full" />
|
||||||
|
) : (
|
||||||
|
<span className="text-custom-text-300 text-[0.6rem] grid place-items-center">#</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-8 gap-2 px-2.5 justify-items-center mt-2">
|
||||||
|
{MATERIAL_ICONS_LIST.map((icon) => (
|
||||||
|
<button
|
||||||
|
key={icon.name}
|
||||||
|
type="button"
|
||||||
|
className="h-6 w-6 select-none text-lg grid place-items-center rounded hover:bg-custom-background-80"
|
||||||
|
onClick={() => {
|
||||||
|
onChange({
|
||||||
|
name: icon.name,
|
||||||
|
color: activeColor,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ color: activeColor }} className="material-symbols-rounded text-base">
|
||||||
|
{icon.name}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
605
packages/ui/src/emoji/icons.ts
Normal file
605
packages/ui/src/emoji/icons.ts
Normal file
@ -0,0 +1,605 @@
|
|||||||
|
export const MATERIAL_ICONS_LIST = [
|
||||||
|
{
|
||||||
|
name: "search",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "home",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "menu",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "close",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "settings",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "done",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "check_circle",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "favorite",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arrow_back",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "star",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "logout",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add_circle",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cancel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arrow_drop_down",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "more_vert",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "check",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "check_box",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "toggle_on",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "open_in_new",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "refresh",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "login",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "radio_button_unchecked",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "more_horiz",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "apps",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "radio_button_checked",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "download",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remove",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "toggle_off",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bolt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arrow_upward",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filter_list",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete_forever",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "autorenew",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sort",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sync",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add_box",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "block",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "restart_alt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "menu_open",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "shopping_cart_checkout",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "expand_circle_down",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "backspace",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "undo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "done_all",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "do_not_disturb_on",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "open_in_full",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "double_arrow",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sync_alt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zoom_in",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "done_outline",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "drag_indicator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fullscreen",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "star_half",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "settings_accessibility",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reply",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exit_to_app",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unfold_more",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "library_add",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cached",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "select_check_box",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "terminal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change_circle",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "disabled_by_default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "swap_horiz",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "swap_vert",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "app_registration",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "download_for_offline",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "close_fullscreen",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "file_open",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "minimize",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "open_with",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dataset",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add_task",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "start",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "keyboard_voice",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create_new_folder",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forward",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "download",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "settings_applications",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "compare_arrows",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "redo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zoom_out",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "publish",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "html",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "token",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "switch_access_shortcut",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fullscreen_exit",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sort_by_alpha",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete_sweep",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "indeterminate_check_box",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "view_timeline",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "settings_backup_restore",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arrow_drop_down_circle",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "assistant_navigation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sync_problem",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "clear_all",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "density_medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "heart_plus",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filter_alt_off",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "expand",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "subdirectory_arrow_right",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "download_done",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arrow_outward",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "swipe_left",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "auto_mode",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "saved_search",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "place_item",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "system_update_alt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "javascript",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "search_off",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "output",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "select_all",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fit_screen",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "swipe_up",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dynamic_form",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hide_source",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "swipe_right",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "switch_access_shortcut_add",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "browse_gallery",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "css",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "density_small",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "assistant_direction",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "check_small",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "youtube_searched_for",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "move_up",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "swap_horizontal_circle",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "data_thresholding",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "install_mobile",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "move_down",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dataset_linked",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "keyboard_command_key",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "view_kanban",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "swipe_down",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key_off",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "transcribe",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "send_time_extension",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "swipe_down_alt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "swipe_left_alt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "swipe_right_alt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "swipe_up_alt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "keyboard_option_key",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cycle",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rebase",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rebase_edit",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty_dashboard",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "magic_exchange",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "acute",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "point_scan",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "step_into",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cheer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "emoticon",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "explosion",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "water_bottle",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "weather_hail",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "syringe",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pill",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "genetics",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allergy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "medical_mask",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "body_fat",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "barefoot",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "infrared",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrist",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metabolism",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "conditions",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "taunt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "altitude",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tibia",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "footprint",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "eyeglasses",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "man_3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "woman_2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rheumatology",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tornado",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "landslide",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "foggy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "severe_cold",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tsunami",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vape_free",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sign_language",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "emoji_symbols",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "clear_night",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "emoji_food_beverage",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "thunderstorm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "communication",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rocket",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pets",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "public",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "quiz",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mood",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gavel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "eco",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diamond",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forest",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rainy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "skull",
|
||||||
|
},
|
||||||
|
];
|
1
packages/ui/src/emoji/index.ts
Normal file
1
packages/ui/src/emoji/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./emoji-icon-picker";
|
@ -1,4 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
// helpers
|
||||||
|
import { cn } from "../../helpers";
|
||||||
|
|
||||||
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||||
mode?: "primary" | "transparent" | "true-transparent";
|
mode?: "primary" | "transparent" | "true-transparent";
|
||||||
@ -16,17 +18,20 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
type={type}
|
type={type}
|
||||||
name={name}
|
name={name}
|
||||||
className={`block rounded-md bg-transparent text-sm placeholder-custom-text-400 focus:outline-none ${
|
className={cn(
|
||||||
mode === "primary"
|
`block rounded-md bg-transparent text-sm placeholder-custom-text-400 focus:outline-none ${
|
||||||
? "rounded-md border-[0.5px] border-custom-border-200"
|
mode === "primary"
|
||||||
: mode === "transparent"
|
? "rounded-md border-[0.5px] border-custom-border-200"
|
||||||
? "rounded border-none bg-transparent ring-0 transition-all focus:ring-1 focus:ring-custom-primary"
|
: mode === "transparent"
|
||||||
: mode === "true-transparent"
|
? "rounded border-none bg-transparent ring-0 transition-all focus:ring-1 focus:ring-custom-primary"
|
||||||
? "rounded border-none bg-transparent ring-0"
|
: mode === "true-transparent"
|
||||||
: ""
|
? "rounded border-none bg-transparent ring-0"
|
||||||
} ${hasError ? "border-red-500" : ""} ${hasError && mode === "primary" ? "bg-red-500/20" : ""} ${
|
: ""
|
||||||
inputSize === "sm" ? "px-3 py-2" : inputSize === "md" ? "p-3" : ""
|
} ${hasError ? "border-red-500" : ""} ${hasError && mode === "primary" ? "bg-red-500/20" : ""} ${
|
||||||
} ${className}`}
|
inputSize === "sm" ? "px-3 py-2" : inputSize === "md" ? "p-3" : ""
|
||||||
|
}`,
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -2,6 +2,7 @@ export * from "./avatar";
|
|||||||
export * from "./breadcrumbs";
|
export * from "./breadcrumbs";
|
||||||
export * from "./badge";
|
export * from "./badge";
|
||||||
export * from "./button";
|
export * from "./button";
|
||||||
|
export * from "./emoji";
|
||||||
export * from "./dropdowns";
|
export * from "./dropdowns";
|
||||||
export * from "./form-fields";
|
export * from "./form-fields";
|
||||||
export * from "./icons";
|
export * from "./icons";
|
||||||
|
@ -1 +1,2 @@
|
|||||||
export * from "./latest-feature-block";
|
export * from "./latest-feature-block";
|
||||||
|
export * from "./project-logo";
|
||||||
|
34
space/components/common/project-logo.tsx
Normal file
34
space/components/common/project-logo.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// helpers
|
||||||
|
import { cn } from "helpers/common.helper";
|
||||||
|
// types
|
||||||
|
import { TProjectLogoProps } from "@plane/types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
logo: TProjectLogoProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProjectLogo: React.FC<Props> = (props) => {
|
||||||
|
const { className, logo } = props;
|
||||||
|
|
||||||
|
if (logo.in_use === "icon" && logo.icon)
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: logo.icon.color,
|
||||||
|
}}
|
||||||
|
className={cn("material-symbols-rounded text-base", className)}
|
||||||
|
>
|
||||||
|
{logo.icon.name}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (logo.in_use === "emoji" && logo.emoji)
|
||||||
|
return (
|
||||||
|
<span className={cn("text-base", className)}>
|
||||||
|
{logo.emoji.value?.split("-").map((emoji) => String.fromCodePoint(parseInt(emoji, 10)))}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <span />;
|
||||||
|
};
|
@ -1,15 +1,12 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
// mobx
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// components
|
// components
|
||||||
// import { NavbarSearch } from "./search";
|
|
||||||
import { NavbarIssueBoardView } from "./issue-board-view";
|
import { NavbarIssueBoardView } from "./issue-board-view";
|
||||||
import { NavbarTheme } from "./theme";
|
import { NavbarTheme } from "./theme";
|
||||||
import { IssueFiltersDropdown } from "components/issues/filters";
|
import { IssueFiltersDropdown } from "components/issues/filters";
|
||||||
|
import { ProjectLogo } from "components/common";
|
||||||
// ui
|
// ui
|
||||||
import { Avatar, Button } from "@plane/ui";
|
import { Avatar, Button } from "@plane/ui";
|
||||||
import { Briefcase } from "lucide-react";
|
import { Briefcase } from "lucide-react";
|
||||||
@ -19,18 +16,6 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
|||||||
import { RootStore } from "store/root";
|
import { RootStore } from "store/root";
|
||||||
import { TIssueBoardKeys } from "types/issue";
|
import { TIssueBoardKeys } from "types/issue";
|
||||||
|
|
||||||
const renderEmoji = (emoji: string | { name: string; color: string }) => {
|
|
||||||
if (!emoji) return;
|
|
||||||
|
|
||||||
if (typeof emoji === "object")
|
|
||||||
return (
|
|
||||||
<span style={{ color: emoji.color }} className="material-symbols-rounded text-lg">
|
|
||||||
{emoji.name}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
else return isNaN(parseInt(emoji)) ? emoji : String.fromCodePoint(parseInt(emoji));
|
|
||||||
};
|
|
||||||
|
|
||||||
const IssueNavbar = observer(() => {
|
const IssueNavbar = observer(() => {
|
||||||
const {
|
const {
|
||||||
project: projectStore,
|
project: projectStore,
|
||||||
@ -123,27 +108,15 @@ const IssueNavbar = observer(() => {
|
|||||||
<div className="relative flex w-full items-center gap-4 px-5">
|
<div className="relative flex w-full items-center gap-4 px-5">
|
||||||
{/* project detail */}
|
{/* project detail */}
|
||||||
<div className="flex flex-shrink-0 items-center gap-2">
|
<div className="flex flex-shrink-0 items-center gap-2">
|
||||||
<div className="flex h-4 w-4 items-center justify-center">
|
{projectStore.project ? (
|
||||||
{projectStore.project ? (
|
<span className="h-7 w-7 flex-shrink-0 grid place-items-center">
|
||||||
projectStore.project?.emoji ? (
|
<ProjectLogo logo={projectStore.project.logo_props} className="text-lg" />
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
</span>
|
||||||
{renderEmoji(projectStore.project.emoji)}
|
) : (
|
||||||
</span>
|
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
||||||
) : projectStore.project?.icon_prop ? (
|
<Briefcase className="h-4 w-4" />
|
||||||
<div className="grid h-7 w-7 flex-shrink-0 place-items-center">
|
</span>
|
||||||
{renderEmoji(projectStore.project.icon_prop)}
|
)}
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{projectStore.project?.name.charAt(0)}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
|
||||||
<Briefcase className="h-4 w-4" />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="line-clamp-1 max-w-[300px] overflow-hidden text-lg font-medium">
|
<div className="line-clamp-1 max-w-[300px] overflow-hidden text-lg font-medium">
|
||||||
{projectStore?.project?.name || `...`}
|
{projectStore?.project?.name || `...`}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { TProjectLogoProps } from "@plane/types";
|
||||||
|
|
||||||
export interface IWorkspace {
|
export interface IWorkspace {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -9,10 +11,8 @@ export interface IProject {
|
|||||||
identifier: string;
|
identifier: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
icon: string;
|
|
||||||
cover_image: string | null;
|
cover_image: string | null;
|
||||||
icon_prop: string | null;
|
logo_props: TProjectLogoProps;
|
||||||
emoji: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProjectSettings {
|
export interface IProjectSettings {
|
||||||
|
@ -3,9 +3,9 @@ import { observer } from "mobx-react-lite";
|
|||||||
// icons
|
// icons
|
||||||
import { Contrast, LayoutGrid, Users } from "lucide-react";
|
import { Contrast, LayoutGrid, Users } from "lucide-react";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { truncateText } from "helpers/string.helper";
|
import { truncateText } from "helpers/string.helper";
|
||||||
import { useProject } from "hooks/store";
|
import { useProject } from "hooks/store";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
projectIds: string[];
|
projectIds: string[];
|
||||||
@ -28,15 +28,9 @@ export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = observer((pro
|
|||||||
return (
|
return (
|
||||||
<div key={projectId} className="w-full">
|
<div key={projectId} className="w-full">
|
||||||
<div className="flex items-center gap-1 text-sm">
|
<div className="flex items-center gap-1 text-sm">
|
||||||
{project.emoji ? (
|
<div className="h-6 w-6 grid place-items-center">
|
||||||
<span className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.emoji)}</span>
|
<ProjectLogo logo={project.logo_props} />
|
||||||
) : project.icon_prop ? (
|
</div>
|
||||||
<div className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.icon_prop)}</div>
|
|
||||||
) : (
|
|
||||||
<span className="mr-1 grid h-6 w-6 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{project?.name.charAt(0)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<h5 className="flex items-center gap-1">
|
<h5 className="flex items-center gap-1">
|
||||||
<p className="break-words">{truncateText(project.name, 20)}</p>
|
<p className="break-words">{truncateText(project.name, 20)}</p>
|
||||||
<span className="ml-1 text-xs text-custom-text-200">({project.identifier})</span>
|
<span className="ml-1 text-xs text-custom-text-200">({project.identifier})</span>
|
||||||
|
@ -3,8 +3,9 @@ import { useRouter } from "next/router";
|
|||||||
// hooks
|
// hooks
|
||||||
import { NETWORK_CHOICES } from "constants/project";
|
import { NETWORK_CHOICES } from "constants/project";
|
||||||
import { renderFormattedDate } from "helpers/date-time.helper";
|
import { renderFormattedDate } from "helpers/date-time.helper";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { useCycle, useMember, useModule, useProject } from "hooks/store";
|
import { useCycle, useMember, useModule, useProject } from "hooks/store";
|
||||||
|
// components
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
// helpers
|
// helpers
|
||||||
// constants
|
// constants
|
||||||
|
|
||||||
@ -81,15 +82,9 @@ export const CustomAnalyticsSidebarHeader = observer(() => {
|
|||||||
) : (
|
) : (
|
||||||
<div className="h-full overflow-y-auto">
|
<div className="h-full overflow-y-auto">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{projectDetails?.emoji ? (
|
{projectDetails && (
|
||||||
<div className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(projectDetails.emoji)}</div>
|
<span className="h-6 w-6 grid place-items-center flex-shrink-0">
|
||||||
) : projectDetails?.icon_prop ? (
|
<ProjectLogo logo={projectDetails.logo_props} />
|
||||||
<div className="grid h-6 w-6 flex-shrink-0 place-items-center">
|
|
||||||
{renderEmoji(projectDetails.icon_prop)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span className="mr-1 grid h-6 w-6 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{projectDetails?.name.charAt(0)}
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<h4 className="break-words font-medium">{projectDetails?.name}</h4>
|
<h4 className="break-words font-medium">{projectDetails?.name}</h4>
|
||||||
|
@ -7,13 +7,13 @@ import { Avatar, AvatarGroup } from "@plane/ui";
|
|||||||
import { WidgetLoader, WidgetProps } from "components/dashboard/widgets";
|
import { WidgetLoader, WidgetProps } from "components/dashboard/widgets";
|
||||||
import { PROJECT_BACKGROUND_COLORS } from "constants/dashboard";
|
import { PROJECT_BACKGROUND_COLORS } from "constants/dashboard";
|
||||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { useApplication, useEventTracker, useDashboard, useProject, useUser } from "hooks/store";
|
import { useApplication, useEventTracker, useDashboard, useProject, useUser } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
// ui
|
// ui
|
||||||
// helpers
|
// helpers
|
||||||
// types
|
// types
|
||||||
import { TRecentProjectsWidgetResponse } from "@plane/types";
|
import { TRecentProjectsWidgetResponse } from "@plane/types";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
// constants
|
// constants
|
||||||
|
|
||||||
const WIDGET_KEY = "recent_projects";
|
const WIDGET_KEY = "recent_projects";
|
||||||
@ -38,17 +38,9 @@ const ProjectListItem: React.FC<ProjectListItemProps> = observer((props) => {
|
|||||||
<div
|
<div
|
||||||
className={`h-[3.375rem] w-[3.375rem] grid place-items-center rounded border border-transparent flex-shrink-0 ${randomBgColor}`}
|
className={`h-[3.375rem] w-[3.375rem] grid place-items-center rounded border border-transparent flex-shrink-0 ${randomBgColor}`}
|
||||||
>
|
>
|
||||||
{projectDetails.emoji ? (
|
<div className="h-7 w-7 grid place-items-center">
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 text-2xl place-items-center rounded uppercase">
|
<ProjectLogo logo={projectDetails.logo_props} className="text-xl" />
|
||||||
{renderEmoji(projectDetails.emoji)}
|
</div>
|
||||||
</span>
|
|
||||||
) : projectDetails.icon_prop ? (
|
|
||||||
<div className="grid h-7 w-7 flex-shrink-0 place-items-center">{renderEmoji(projectDetails.icon_prop)}</div>
|
|
||||||
) : (
|
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{projectDetails.name.charAt(0)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow truncate">
|
<div className="flex-grow truncate">
|
||||||
<h6 className="text-sm text-custom-text-300 font-medium group-hover:underline group-hover:text-custom-text-100 truncate">
|
<h6 className="text-sm text-custom-text-300 font-medium group-hover:underline group-hover:text-custom-text-100 truncate">
|
||||||
|
@ -5,12 +5,12 @@ import { Combobox } from "@headlessui/react";
|
|||||||
import { Check, ChevronDown, Search } from "lucide-react";
|
import { Check, ChevronDown, Search } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { cn } from "helpers/common.helper";
|
import { cn } from "helpers/common.helper";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { useProject } from "hooks/store";
|
import { useProject } from "hooks/store";
|
||||||
import { useDropdownKeyDown } from "hooks/use-dropdown-key-down";
|
import { useDropdownKeyDown } from "hooks/use-dropdown-key-down";
|
||||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||||
// components
|
// components
|
||||||
import { DropdownButton } from "./buttons";
|
import { DropdownButton } from "./buttons";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
// helpers
|
// helpers
|
||||||
// types
|
// types
|
||||||
import { BUTTON_VARIANTS_WITH_TEXT } from "./constants";
|
import { BUTTON_VARIANTS_WITH_TEXT } from "./constants";
|
||||||
@ -77,13 +77,11 @@ export const ProjectDropdown: React.FC<Props> = observer((props) => {
|
|||||||
query: `${projectDetails?.name}`,
|
query: `${projectDetails?.name}`,
|
||||||
content: (
|
content: (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="grid place-items-center flex-shrink-0">
|
{projectDetails && (
|
||||||
{projectDetails?.emoji
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
? renderEmoji(projectDetails?.emoji)
|
<ProjectLogo logo={projectDetails?.logo_props} className="text-sm" />
|
||||||
: projectDetails?.icon_prop
|
</span>
|
||||||
? renderEmoji(projectDetails?.icon_prop)
|
)}
|
||||||
: null}
|
|
||||||
</span>
|
|
||||||
<span className="flex-grow truncate">{projectDetails?.name}</span>
|
<span className="flex-grow truncate">{projectDetails?.name}</span>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@ -169,13 +167,9 @@ export const ProjectDropdown: React.FC<Props> = observer((props) => {
|
|||||||
showTooltip={showTooltip}
|
showTooltip={showTooltip}
|
||||||
variant={buttonVariant}
|
variant={buttonVariant}
|
||||||
>
|
>
|
||||||
{!hideIcon && (
|
{!hideIcon && selectedProject && (
|
||||||
<span className="grid place-items-center flex-shrink-0">
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
{selectedProject?.emoji
|
<ProjectLogo logo={selectedProject.logo_props} className="text-sm" />
|
||||||
? renderEmoji(selectedProject?.emoji)
|
|
||||||
: selectedProject?.icon_prop
|
|
||||||
? renderEmoji(selectedProject?.icon_prop)
|
|
||||||
: null}
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
|
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,26 +0,0 @@
|
|||||||
export const saveRecentEmoji = (emoji: string) => {
|
|
||||||
const recentEmojis = localStorage.getItem("recentEmojis");
|
|
||||||
if (recentEmojis) {
|
|
||||||
const recentEmojisArray = recentEmojis.split(",");
|
|
||||||
if (recentEmojisArray.includes(emoji)) {
|
|
||||||
const index = recentEmojisArray.indexOf(emoji);
|
|
||||||
recentEmojisArray.splice(index, 1);
|
|
||||||
}
|
|
||||||
recentEmojisArray.unshift(emoji);
|
|
||||||
if (recentEmojisArray.length > 18) {
|
|
||||||
recentEmojisArray.pop();
|
|
||||||
}
|
|
||||||
localStorage.setItem("recentEmojis", recentEmojisArray.join(","));
|
|
||||||
} else {
|
|
||||||
localStorage.setItem("recentEmojis", emoji);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getRecentEmojis = () => {
|
|
||||||
const recentEmojis = localStorage.getItem("recentEmojis");
|
|
||||||
if (recentEmojis) {
|
|
||||||
const recentEmojisArray = recentEmojis.split(",");
|
|
||||||
return recentEmojisArray;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
};
|
|
@ -1,607 +0,0 @@
|
|||||||
{
|
|
||||||
"material_rounded": [
|
|
||||||
{
|
|
||||||
"name": "search"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "home"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "menu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "close"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "settings"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "done"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "check_circle"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "favorite"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "add"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "delete"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "arrow_back"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "star"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "logout"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "add_circle"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "cancel"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "arrow_drop_down"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "more_vert"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "check"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "check_box"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "toggle_on"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "open_in_new"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "refresh"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "login"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "radio_button_unchecked"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "more_horiz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "apps"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "radio_button_checked"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "download"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "remove"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "toggle_off"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "bolt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "arrow_upward"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "filter_list"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "delete_forever"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "autorenew"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "key"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "sort"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "sync"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "add_box"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "block"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "restart_alt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "menu_open"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "shopping_cart_checkout"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "expand_circle_down"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "backspace"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "undo"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "done_all"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "do_not_disturb_on"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "open_in_full"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "double_arrow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "sync_alt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "zoom_in"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "done_outline"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "drag_indicator"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "fullscreen"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "star_half"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "settings_accessibility"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "reply"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "exit_to_app"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "unfold_more"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "library_add"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "cached"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "select_check_box"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "terminal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "change_circle"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "disabled_by_default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "swap_horiz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "swap_vert"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "app_registration"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "download_for_offline"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "close_fullscreen"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "file_open"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "minimize"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "open_with"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dataset"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "add_task"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "start"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyboard_voice"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "create_new_folder"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "forward"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "download"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "settings_applications"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "compare_arrows"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "redo"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "zoom_out"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "publish"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "html"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "token"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "switch_access_shortcut"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "fullscreen_exit"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "sort_by_alpha"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "delete_sweep"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "indeterminate_check_box"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "view_timeline"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "settings_backup_restore"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "arrow_drop_down_circle"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "assistant_navigation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "sync_problem"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "clear_all"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "density_medium"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "heart_plus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "filter_alt_off"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "expand"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "subdirectory_arrow_right"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "download_done"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "arrow_outward"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "123"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "swipe_left"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "auto_mode"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "saved_search"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "place_item"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "system_update_alt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "javascript"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "search_off"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "output"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "select_all"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "fit_screen"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "swipe_up"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dynamic_form"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hide_source"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "swipe_right"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "switch_access_shortcut_add"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "browse_gallery"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "css"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "density_small"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "assistant_direction"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "check_small"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "youtube_searched_for"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "move_up"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "swap_horizontal_circle"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "data_thresholding"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "install_mobile"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "move_down"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dataset_linked"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyboard_command_key"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "view_kanban"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "swipe_down"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "key_off"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "transcribe"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "send_time_extension"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "swipe_down_alt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "swipe_left_alt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "swipe_right_alt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "swipe_up_alt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "keyboard_option_key"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "cycle"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "rebase"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "rebase_edit"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "empty_dashboard"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "magic_exchange"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "acute"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "point_scan"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "step_into"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "cheer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "emoticon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "explosion"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "water_bottle"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "weather_hail"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "syringe"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pill"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "genetics"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "allergy"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "medical_mask"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "body_fat"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "barefoot"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "infrared"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "wrist"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "metabolism"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "conditions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "taunt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "altitude"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tibia"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "footprint"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "eyeglasses"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "man_3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "woman_2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "rheumatology"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tornado"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "landslide"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "foggy"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "severe_cold"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tsunami"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "vape_free"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "sign_language"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "emoji_symbols"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "clear_night"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "emoji_food_beverage"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hive"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "thunderstorm"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "communication"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "rocket"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pets"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "public"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "quiz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "mood"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "gavel"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "eco"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "diamond"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "forest"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "rainy"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "skull"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,204 +0,0 @@
|
|||||||
import React, { useEffect, useState, useRef } from "react";
|
|
||||||
// headless ui
|
|
||||||
import { TwitterPicker } from "react-color";
|
|
||||||
import { Tab, Transition, Popover } from "@headlessui/react";
|
|
||||||
// react colors
|
|
||||||
// hooks
|
|
||||||
import { getRandomEmoji, renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
|
|
||||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
|
||||||
// types
|
|
||||||
// emojis
|
|
||||||
import emojis from "./emojis.json";
|
|
||||||
import { getRecentEmojis, saveRecentEmoji } from "./helpers";
|
|
||||||
import icons from "./icons.json";
|
|
||||||
// helpers
|
|
||||||
import { Props } from "./types";
|
|
||||||
|
|
||||||
const tabOptions = [
|
|
||||||
{
|
|
||||||
key: "emoji",
|
|
||||||
title: "Emoji",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "icon",
|
|
||||||
title: "Icon",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const EmojiIconPicker: React.FC<Props> = (props) => {
|
|
||||||
const { label, value, onChange, onIconColorChange, disabled = false } = props;
|
|
||||||
// states
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const [openColorPicker, setOpenColorPicker] = useState(false);
|
|
||||||
const [activeColor, setActiveColor] = useState<string>("rgb(var(--color-text-200))");
|
|
||||||
const [recentEmojis, setRecentEmojis] = useState<string[]>([]);
|
|
||||||
|
|
||||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
||||||
const emojiPickerRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setRecentEmojis(getRecentEmojis());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!value || value?.length === 0) onChange(getRandomEmoji());
|
|
||||||
}, [value, onChange]);
|
|
||||||
|
|
||||||
useOutsideClickDetector(emojiPickerRef, () => setIsOpen(false));
|
|
||||||
useDynamicDropdownPosition(isOpen, () => setIsOpen(false), buttonRef, emojiPickerRef);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover className="relative z-[1]">
|
|
||||||
<Popover.Button
|
|
||||||
ref={buttonRef}
|
|
||||||
onClick={() => setIsOpen((prev) => !prev)}
|
|
||||||
className="outline-none flex items-center justify-center"
|
|
||||||
disabled={disabled}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Popover.Button>
|
|
||||||
<Transition
|
|
||||||
show={isOpen}
|
|
||||||
static
|
|
||||||
as={React.Fragment}
|
|
||||||
enter="transition ease-out duration-100"
|
|
||||||
enterFrom="transform opacity-0 scale-95"
|
|
||||||
enterTo="transform opacity-100 scale-100"
|
|
||||||
leave="transition ease-in duration-75"
|
|
||||||
leaveFrom="transform opacity-100 scale-100"
|
|
||||||
leaveTo="transform opacity-0 scale-95"
|
|
||||||
>
|
|
||||||
<Popover.Panel
|
|
||||||
ref={emojiPickerRef}
|
|
||||||
className="fixed z-10 mt-2 w-[250px] rounded-[4px] border border-custom-border-200 bg-custom-background-80 shadow-lg"
|
|
||||||
>
|
|
||||||
<div className="h-[230px] w-[250px] overflow-auto rounded-[4px] border border-custom-border-200 bg-custom-background-80 p-2 shadow-xl">
|
|
||||||
<Tab.Group as="div" className="flex h-full w-full flex-col">
|
|
||||||
<Tab.List className="flex-0 -mx-2 flex justify-around gap-1 p-1">
|
|
||||||
{tabOptions.map((tab) => (
|
|
||||||
<Tab key={tab.key} as={React.Fragment}>
|
|
||||||
{({ selected }) => (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
setOpenColorPicker(false);
|
|
||||||
}}
|
|
||||||
className={`-my-1 w-1/2 border-b pb-2 text-center text-sm font-medium outline-none transition-colors ${
|
|
||||||
selected ? "" : "border-transparent text-custom-text-200"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{tab.title}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Tab>
|
|
||||||
))}
|
|
||||||
</Tab.List>
|
|
||||||
<Tab.Panels className="flex-1 overflow-y-auto vertical-scrollbar scrollbar-sm">
|
|
||||||
<Tab.Panel>
|
|
||||||
{recentEmojis.length > 0 && (
|
|
||||||
<div className="py-2">
|
|
||||||
<h3 className="mb-2 text-xs text-custom-text-200">Recent</h3>
|
|
||||||
<div className="grid grid-cols-8 gap-2">
|
|
||||||
{recentEmojis.map((emoji) => (
|
|
||||||
<button
|
|
||||||
key={emoji}
|
|
||||||
type="button"
|
|
||||||
className="flex h-4 w-4 select-none items-center justify-between text-sm"
|
|
||||||
onClick={() => {
|
|
||||||
onChange(emoji);
|
|
||||||
setIsOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{renderEmoji(emoji)}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<hr className="mb-2 h-[1px] w-full border-custom-border-200" />
|
|
||||||
<div>
|
|
||||||
<div className="grid grid-cols-8 gap-x-2 gap-y-3">
|
|
||||||
{emojis.map((emoji) => (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="mb-1 flex h-4 w-4 select-none items-center text-sm"
|
|
||||||
key={emoji}
|
|
||||||
onClick={() => {
|
|
||||||
onChange(emoji);
|
|
||||||
saveRecentEmoji(emoji);
|
|
||||||
setIsOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{renderEmoji(emoji)}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tab.Panel>
|
|
||||||
<div className="py-2">
|
|
||||||
<Tab.Panel className="flex h-full w-full flex-col justify-center">
|
|
||||||
<div className="relative">
|
|
||||||
<div className="flex items-center justify-between px-1 pb-2">
|
|
||||||
{["#FF6B00", "#8CC1FF", "#FCBE1D", "#18904F", "#ADF672", "#05C3FF", "#000000"].map((curCol) => (
|
|
||||||
<span
|
|
||||||
key={curCol}
|
|
||||||
className="h-4 w-4 cursor-pointer rounded-full"
|
|
||||||
style={{ backgroundColor: curCol }}
|
|
||||||
onClick={() => setActiveColor(curCol)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setOpenColorPicker((prev) => !prev)}
|
|
||||||
className="flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="conical-gradient h-4 w-4 rounded-full"
|
|
||||||
style={{ backgroundColor: activeColor }}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TwitterPicker
|
|
||||||
className={`!absolute left-4 top-4 z-10 m-2 ${openColorPicker ? "block" : "hidden"}`}
|
|
||||||
color={activeColor}
|
|
||||||
onChange={(color) => {
|
|
||||||
setActiveColor(color.hex);
|
|
||||||
if (onIconColorChange) onIconColorChange(color.hex);
|
|
||||||
}}
|
|
||||||
triangle="hide"
|
|
||||||
width="205px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr className="mb-1 h-[1px] w-full border-custom-border-200" />
|
|
||||||
<div className="ml-1 mt-1 grid grid-cols-8 gap-x-2 gap-y-3">
|
|
||||||
{icons.material_rounded.map((icon, index) => (
|
|
||||||
<button
|
|
||||||
key={`${icon.name}-${index}`}
|
|
||||||
type="button"
|
|
||||||
className="mb-1 flex h-4 w-4 select-none items-center text-lg"
|
|
||||||
onClick={() => {
|
|
||||||
onChange({ name: icon.name, color: activeColor });
|
|
||||||
setIsOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span style={{ color: activeColor }} className="material-symbols-rounded text-lg">
|
|
||||||
{icon.name}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Tab.Panel>
|
|
||||||
</div>
|
|
||||||
</Tab.Panels>
|
|
||||||
</Tab.Group>
|
|
||||||
</div>
|
|
||||||
</Popover.Panel>
|
|
||||||
</Transition>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EmojiIconPicker;
|
|
15
web/components/emoji-icon-picker/types.d.ts
vendored
15
web/components/emoji-icon-picker/types.d.ts
vendored
@ -1,15 +0,0 @@
|
|||||||
export type Props = {
|
|
||||||
label: React.ReactNode;
|
|
||||||
value: any;
|
|
||||||
onChange: (
|
|
||||||
data:
|
|
||||||
| string
|
|
||||||
| {
|
|
||||||
name: string;
|
|
||||||
color: string;
|
|
||||||
}
|
|
||||||
) => void;
|
|
||||||
onIconColorChange?: (data: any) => void;
|
|
||||||
disabled?: boolean;
|
|
||||||
tabIndex?: number;
|
|
||||||
};
|
|
@ -13,7 +13,6 @@ import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelect
|
|||||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { cn } from "helpers/common.helper";
|
import { cn } from "helpers/common.helper";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { truncateText } from "helpers/string.helper";
|
import { truncateText } from "helpers/string.helper";
|
||||||
import {
|
import {
|
||||||
useApplication,
|
useApplication,
|
||||||
@ -33,6 +32,7 @@ import useLocalStorage from "hooks/use-local-storage";
|
|||||||
// helpers
|
// helpers
|
||||||
// types
|
// types
|
||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
// constants
|
// constants
|
||||||
|
|
||||||
const CycleDropdownOption: React.FC<{ cycleId: string }> = ({ cycleId }) => {
|
const CycleDropdownOption: React.FC<{ cycleId: string }> = ({ cycleId }) => {
|
||||||
@ -163,13 +163,9 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||||||
label={currentProjectDetails?.name ?? "Project"}
|
label={currentProjectDetails?.name ?? "Project"}
|
||||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||||
icon={
|
icon={
|
||||||
currentProjectDetails?.emoji ? (
|
currentProjectDetails && (
|
||||||
renderEmoji(currentProjectDetails.emoji)
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
) : currentProjectDetails?.icon_prop ? (
|
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||||
renderEmoji(currentProjectDetails.icon_prop)
|
|
||||||
) : (
|
|
||||||
<span className="flex h-4 w-4 items-center justify-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{currentProjectDetails?.name.charAt(0)}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -209,7 +205,9 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||||||
className="ml-1.5 flex-shrink-0"
|
className="ml-1.5 flex-shrink-0"
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
>
|
>
|
||||||
{currentProjectCycleIds?.map((cycleId) => <CycleDropdownOption key={cycleId} cycleId={cycleId} />)}
|
{currentProjectCycleIds?.map((cycleId) => (
|
||||||
|
<CycleDropdownOption key={cycleId} cycleId={cycleId} />
|
||||||
|
))}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -11,14 +11,15 @@ import { BreadcrumbLink } from "components/common";
|
|||||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||||
import { CYCLE_VIEW_LAYOUTS } from "constants/cycle";
|
import { CYCLE_VIEW_LAYOUTS } from "constants/cycle";
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { useApplication, useEventTracker, useProject, useUser } from "hooks/store";
|
import { useApplication, useEventTracker, useProject, useUser } from "hooks/store";
|
||||||
import useLocalStorage from "hooks/use-local-storage";
|
import useLocalStorage from "hooks/use-local-storage";
|
||||||
import { TCycleLayout } from "@plane/types";
|
import { TCycleLayout } from "@plane/types";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
|
|
||||||
export const CyclesHeader: FC = observer(() => {
|
export const CyclesHeader: FC = observer(() => {
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { workspaceSlug } = router.query;
|
||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const {
|
||||||
commandPalette: { toggleCreateCycleModal },
|
commandPalette: { toggleCreateCycleModal },
|
||||||
@ -32,9 +33,6 @@ export const CyclesHeader: FC = observer(() => {
|
|||||||
const canUserCreateCycle =
|
const canUserCreateCycle =
|
||||||
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
|
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
|
||||||
|
|
||||||
const { workspaceSlug } = router.query as {
|
|
||||||
workspaceSlug: string;
|
|
||||||
};
|
|
||||||
const { setValue: setCycleLayout } = useLocalStorage<TCycleLayout>("cycle_layout", "list");
|
const { setValue: setCycleLayout } = useLocalStorage<TCycleLayout>("cycle_layout", "list");
|
||||||
|
|
||||||
const handleCurrentLayout = useCallback(
|
const handleCurrentLayout = useCallback(
|
||||||
@ -58,13 +56,9 @@ export const CyclesHeader: FC = observer(() => {
|
|||||||
label={currentProjectDetails?.name ?? "Project"}
|
label={currentProjectDetails?.name ?? "Project"}
|
||||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||||
icon={
|
icon={
|
||||||
currentProjectDetails?.emoji ? (
|
currentProjectDetails && (
|
||||||
renderEmoji(currentProjectDetails.emoji)
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
) : currentProjectDetails?.icon_prop ? (
|
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||||
renderEmoji(currentProjectDetails.icon_prop)
|
|
||||||
) : (
|
|
||||||
<span className="flex h-4 w-4 items-center justify-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{currentProjectDetails?.name.charAt(0)}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@ import { ModuleMobileHeader } from "components/modules/module-mobile-header";
|
|||||||
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { cn } from "helpers/common.helper";
|
import { cn } from "helpers/common.helper";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { truncateText } from "helpers/string.helper";
|
import { truncateText } from "helpers/string.helper";
|
||||||
import {
|
import {
|
||||||
useApplication,
|
useApplication,
|
||||||
@ -33,6 +32,7 @@ import useLocalStorage from "hooks/use-local-storage";
|
|||||||
// helpers
|
// helpers
|
||||||
// types
|
// types
|
||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
// constants
|
// constants
|
||||||
|
|
||||||
const ModuleDropdownOption: React.FC<{ moduleId: string }> = ({ moduleId }) => {
|
const ModuleDropdownOption: React.FC<{ moduleId: string }> = ({ moduleId }) => {
|
||||||
@ -64,11 +64,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, moduleId } = router.query as {
|
const { workspaceSlug, projectId, moduleId } = router.query;
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
moduleId: string;
|
|
||||||
};
|
|
||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const {
|
||||||
issuesFilter: { issueFilters, updateFilters },
|
issuesFilter: { issueFilters, updateFilters },
|
||||||
@ -100,7 +96,13 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||||||
const handleLayoutChange = useCallback(
|
const handleLayoutChange = useCallback(
|
||||||
(layout: TIssueLayouts) => {
|
(layout: TIssueLayouts) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, moduleId);
|
updateFilters(
|
||||||
|
workspaceSlug.toString(),
|
||||||
|
projectId.toString(),
|
||||||
|
EIssueFilterType.DISPLAY_FILTERS,
|
||||||
|
{ layout: layout },
|
||||||
|
moduleId?.toString()
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[workspaceSlug, projectId, moduleId, updateFilters]
|
[workspaceSlug, projectId, moduleId, updateFilters]
|
||||||
);
|
);
|
||||||
@ -119,7 +121,13 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||||||
else newValues.push(value);
|
else newValues.push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }, moduleId);
|
updateFilters(
|
||||||
|
workspaceSlug.toString(),
|
||||||
|
projectId.toString(),
|
||||||
|
EIssueFilterType.FILTERS,
|
||||||
|
{ [key]: newValues },
|
||||||
|
moduleId?.toString()
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[workspaceSlug, projectId, moduleId, issueFilters, updateFilters]
|
[workspaceSlug, projectId, moduleId, issueFilters, updateFilters]
|
||||||
);
|
);
|
||||||
@ -127,7 +135,13 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||||||
const handleDisplayFilters = useCallback(
|
const handleDisplayFilters = useCallback(
|
||||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, moduleId);
|
updateFilters(
|
||||||
|
workspaceSlug.toString(),
|
||||||
|
projectId.toString(),
|
||||||
|
EIssueFilterType.DISPLAY_FILTERS,
|
||||||
|
updatedDisplayFilter,
|
||||||
|
moduleId?.toString()
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[workspaceSlug, projectId, moduleId, updateFilters]
|
[workspaceSlug, projectId, moduleId, updateFilters]
|
||||||
);
|
);
|
||||||
@ -135,7 +149,13 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||||||
const handleDisplayProperties = useCallback(
|
const handleDisplayProperties = useCallback(
|
||||||
(property: Partial<IIssueDisplayProperties>) => {
|
(property: Partial<IIssueDisplayProperties>) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, moduleId);
|
updateFilters(
|
||||||
|
workspaceSlug.toString(),
|
||||||
|
projectId.toString(),
|
||||||
|
EIssueFilterType.DISPLAY_PROPERTIES,
|
||||||
|
property,
|
||||||
|
moduleId?.toString()
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[workspaceSlug, projectId, moduleId, updateFilters]
|
[workspaceSlug, projectId, moduleId, updateFilters]
|
||||||
);
|
);
|
||||||
@ -166,13 +186,9 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||||||
label={currentProjectDetails?.name ?? "Project"}
|
label={currentProjectDetails?.name ?? "Project"}
|
||||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||||
icon={
|
icon={
|
||||||
currentProjectDetails?.emoji ? (
|
currentProjectDetails && (
|
||||||
renderEmoji(currentProjectDetails.emoji)
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
) : currentProjectDetails?.icon_prop ? (
|
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||||
renderEmoji(currentProjectDetails.icon_prop)
|
|
||||||
) : (
|
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{currentProjectDetails?.name.charAt(0)}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -212,7 +228,9 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||||||
className="ml-1.5 flex-shrink-0"
|
className="ml-1.5 flex-shrink-0"
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
>
|
>
|
||||||
{projectModuleIds?.map((moduleId) => <ModuleDropdownOption key={moduleId} moduleId={moduleId} />)}
|
{projectModuleIds?.map((moduleId) => (
|
||||||
|
<ModuleDropdownOption key={moduleId} moduleId={moduleId} />
|
||||||
|
))}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -10,11 +10,10 @@ import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-ham
|
|||||||
// constants
|
// constants
|
||||||
import { MODULE_VIEW_LAYOUTS } from "constants/module";
|
import { MODULE_VIEW_LAYOUTS } from "constants/module";
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
// helper
|
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useApplication, useEventTracker, useProject, useUser } from "hooks/store";
|
import { useApplication, useEventTracker, useProject, useUser } from "hooks/store";
|
||||||
import useLocalStorage from "hooks/use-local-storage";
|
import useLocalStorage from "hooks/use-local-storage";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
|
|
||||||
export const ModulesListHeader: React.FC = observer(() => {
|
export const ModulesListHeader: React.FC = observer(() => {
|
||||||
// router
|
// router
|
||||||
@ -46,13 +45,9 @@ export const ModulesListHeader: React.FC = observer(() => {
|
|||||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||||
label={currentProjectDetails?.name ?? "Project"}
|
label={currentProjectDetails?.name ?? "Project"}
|
||||||
icon={
|
icon={
|
||||||
currentProjectDetails?.emoji ? (
|
currentProjectDetails && (
|
||||||
renderEmoji(currentProjectDetails.emoji)
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
) : currentProjectDetails?.icon_prop ? (
|
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||||
renderEmoji(currentProjectDetails.icon_prop)
|
|
||||||
) : (
|
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{currentProjectDetails?.name.charAt(0)}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,9 @@ import { Breadcrumbs, Button } from "@plane/ui";
|
|||||||
// helpers
|
// helpers
|
||||||
import { BreadcrumbLink } from "components/common";
|
import { BreadcrumbLink } from "components/common";
|
||||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
// components
|
// components
|
||||||
import { useApplication, usePage, useProject } from "hooks/store";
|
import { useApplication, usePage, useProject } from "hooks/store";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
|
|
||||||
export interface IPagesHeaderProps {
|
export interface IPagesHeaderProps {
|
||||||
showButton?: boolean;
|
showButton?: boolean;
|
||||||
@ -42,13 +42,9 @@ export const PageDetailsHeader: FC<IPagesHeaderProps> = observer((props) => {
|
|||||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||||
label={currentProjectDetails?.name ?? "Project"}
|
label={currentProjectDetails?.name ?? "Project"}
|
||||||
icon={
|
icon={
|
||||||
currentProjectDetails?.emoji ? (
|
currentProjectDetails && (
|
||||||
renderEmoji(currentProjectDetails.emoji)
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
) : currentProjectDetails?.icon_prop ? (
|
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||||
renderEmoji(currentProjectDetails.icon_prop)
|
|
||||||
) : (
|
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{currentProjectDetails?.name.charAt(0)}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,10 @@ import { Breadcrumbs, Button } from "@plane/ui";
|
|||||||
import { BreadcrumbLink } from "components/common";
|
import { BreadcrumbLink } from "components/common";
|
||||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
// constants
|
// constants
|
||||||
// components
|
// components
|
||||||
import { useApplication, useEventTracker, useProject, useUser } from "hooks/store";
|
import { useApplication, useEventTracker, useProject, useUser } from "hooks/store";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
|
|
||||||
export const PagesHeader = observer(() => {
|
export const PagesHeader = observer(() => {
|
||||||
// router
|
// router
|
||||||
@ -43,13 +43,9 @@ export const PagesHeader = observer(() => {
|
|||||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||||
label={currentProjectDetails?.name ?? "Project"}
|
label={currentProjectDetails?.name ?? "Project"}
|
||||||
icon={
|
icon={
|
||||||
currentProjectDetails?.emoji ? (
|
currentProjectDetails && (
|
||||||
renderEmoji(currentProjectDetails.emoji)
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
) : currentProjectDetails?.icon_prop ? (
|
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||||
renderEmoji(currentProjectDetails.icon_prop)
|
|
||||||
) : (
|
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{currentProjectDetails?.name.charAt(0)}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,9 @@ import { Breadcrumbs, LayersIcon } from "@plane/ui";
|
|||||||
import { BreadcrumbLink } from "components/common";
|
import { BreadcrumbLink } from "components/common";
|
||||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||||
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { useProject } from "hooks/store";
|
import { useProject } from "hooks/store";
|
||||||
|
// components
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
// ui
|
// ui
|
||||||
// types
|
// types
|
||||||
import { IssueArchiveService } from "services/issue";
|
import { IssueArchiveService } from "services/issue";
|
||||||
@ -52,13 +53,9 @@ export const ProjectArchivedIssueDetailsHeader: FC = observer(() => {
|
|||||||
href={`/${workspaceSlug}/projects`}
|
href={`/${workspaceSlug}/projects`}
|
||||||
label={currentProjectDetails?.name ?? "Project"}
|
label={currentProjectDetails?.name ?? "Project"}
|
||||||
icon={
|
icon={
|
||||||
currentProjectDetails?.emoji ? (
|
currentProjectDetails && (
|
||||||
renderEmoji(currentProjectDetails.emoji)
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
) : currentProjectDetails?.icon_prop ? (
|
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||||
renderEmoji(currentProjectDetails.icon_prop)
|
|
||||||
) : (
|
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{currentProjectDetails?.name.charAt(0)}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,10 @@ import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-ham
|
|||||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "components/issues";
|
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "components/issues";
|
||||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { useIssues, useLabel, useMember, useProject, useProjectState } from "hooks/store";
|
import { useIssues, useLabel, useMember, useProject, useProjectState } from "hooks/store";
|
||||||
// types
|
// types
|
||||||
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
|
|
||||||
export const ProjectArchivedIssuesHeader: FC = observer(() => {
|
export const ProjectArchivedIssuesHeader: FC = observer(() => {
|
||||||
// router
|
// router
|
||||||
@ -91,13 +91,9 @@ export const ProjectArchivedIssuesHeader: FC = observer(() => {
|
|||||||
href={`/${workspaceSlug}/projects`}
|
href={`/${workspaceSlug}/projects`}
|
||||||
label={currentProjectDetails?.name ?? "Project"}
|
label={currentProjectDetails?.name ?? "Project"}
|
||||||
icon={
|
icon={
|
||||||
currentProjectDetails?.emoji ? (
|
currentProjectDetails && (
|
||||||
renderEmoji(currentProjectDetails.emoji)
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
) : currentProjectDetails?.icon_prop ? (
|
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||||
renderEmoji(currentProjectDetails.icon_prop)
|
|
||||||
) : (
|
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{currentProjectDetails?.name.charAt(0)}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,9 @@ import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelect
|
|||||||
// ui
|
// ui
|
||||||
// helper
|
// helper
|
||||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { useIssues, useLabel, useMember, useProject, useProjectState } from "hooks/store";
|
import { useIssues, useLabel, useMember, useProject, useProjectState } from "hooks/store";
|
||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
|
|
||||||
export const ProjectDraftIssueHeader: FC = observer(() => {
|
export const ProjectDraftIssueHeader: FC = observer(() => {
|
||||||
// router
|
// router
|
||||||
@ -86,13 +86,9 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
|
|||||||
href={`/${workspaceSlug}/projects`}
|
href={`/${workspaceSlug}/projects`}
|
||||||
label={currentProjectDetails?.name ?? "Project"}
|
label={currentProjectDetails?.name ?? "Project"}
|
||||||
icon={
|
icon={
|
||||||
currentProjectDetails?.emoji ? (
|
currentProjectDetails && (
|
||||||
renderEmoji(currentProjectDetails.emoji)
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
) : currentProjectDetails?.icon_prop ? (
|
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||||
renderEmoji(currentProjectDetails.icon_prop)
|
|
||||||
) : (
|
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{currentProjectDetails?.name.charAt(0)}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@ import { BreadcrumbLink } from "components/common";
|
|||||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||||
import { CreateInboxIssueModal } from "components/inbox";
|
import { CreateInboxIssueModal } from "components/inbox";
|
||||||
// helper
|
// helper
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { useProject } from "hooks/store";
|
import { useProject } from "hooks/store";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
|
|
||||||
export const ProjectInboxHeader: FC = observer(() => {
|
export const ProjectInboxHeader: FC = observer(() => {
|
||||||
// states
|
// states
|
||||||
@ -35,13 +35,9 @@ export const ProjectInboxHeader: FC = observer(() => {
|
|||||||
href={`/${workspaceSlug}/projects`}
|
href={`/${workspaceSlug}/projects`}
|
||||||
label={currentProjectDetails?.name ?? "Project"}
|
label={currentProjectDetails?.name ?? "Project"}
|
||||||
icon={
|
icon={
|
||||||
currentProjectDetails?.emoji ? (
|
currentProjectDetails && (
|
||||||
renderEmoji(currentProjectDetails.emoji)
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
) : currentProjectDetails?.icon_prop ? (
|
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||||
renderEmoji(currentProjectDetails.icon_prop)
|
|
||||||
) : (
|
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{currentProjectDetails?.name.charAt(0)}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,12 @@ import { BreadcrumbLink } from "components/common";
|
|||||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||||
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
||||||
import { cn } from "helpers/common.helper";
|
import { cn } from "helpers/common.helper";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { useApplication, useProject } from "hooks/store";
|
import { useApplication, useProject } from "hooks/store";
|
||||||
// ui
|
// ui
|
||||||
// helpers
|
// helpers
|
||||||
// services
|
// services
|
||||||
import { IssueService } from "services/issue";
|
import { IssueService } from "services/issue";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
// constants
|
// constants
|
||||||
// components
|
// components
|
||||||
|
|
||||||
@ -51,13 +51,9 @@ export const ProjectIssueDetailsHeader: FC = observer(() => {
|
|||||||
href={`/${workspaceSlug}/projects`}
|
href={`/${workspaceSlug}/projects`}
|
||||||
label={currentProjectDetails?.name ?? "Project"}
|
label={currentProjectDetails?.name ?? "Project"}
|
||||||
icon={
|
icon={
|
||||||
currentProjectDetails?.emoji ? (
|
currentProjectDetails && (
|
||||||
renderEmoji(currentProjectDetails.emoji)
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
) : currentProjectDetails?.icon_prop ? (
|
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||||
renderEmoji(currentProjectDetails.icon_prop)
|
|
||||||
) : (
|
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{currentProjectDetails?.name.charAt(0)}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelect
|
|||||||
import { IssuesMobileHeader } from "components/issues/issues-mobile-header";
|
import { IssuesMobileHeader } from "components/issues/issues-mobile-header";
|
||||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import {
|
import {
|
||||||
useApplication,
|
useApplication,
|
||||||
useEventTracker,
|
useEventTracker,
|
||||||
@ -21,11 +20,12 @@ import {
|
|||||||
useUser,
|
useUser,
|
||||||
useMember,
|
useMember,
|
||||||
} from "hooks/store";
|
} from "hooks/store";
|
||||||
|
import { useIssues } from "hooks/store/use-issues";
|
||||||
// components
|
// components
|
||||||
// ui
|
// ui
|
||||||
// types
|
// types
|
||||||
import { useIssues } from "hooks/store/use-issues";
|
|
||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
// constants
|
// constants
|
||||||
// helper
|
// helper
|
||||||
|
|
||||||
@ -123,17 +123,9 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
|||||||
label={currentProjectDetails?.name ?? "Project"}
|
label={currentProjectDetails?.name ?? "Project"}
|
||||||
icon={
|
icon={
|
||||||
currentProjectDetails ? (
|
currentProjectDetails ? (
|
||||||
currentProjectDetails?.emoji ? (
|
currentProjectDetails && (
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
{renderEmoji(currentProjectDetails.emoji)}
|
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||||
</span>
|
|
||||||
) : currentProjectDetails?.icon_prop ? (
|
|
||||||
<div className="grid h-7 w-7 flex-shrink-0 place-items-center">
|
|
||||||
{renderEmoji(currentProjectDetails.icon_prop)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{currentProjectDetails?.name.charAt(0)}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
|
@ -7,9 +7,9 @@ import { Breadcrumbs, CustomMenu } from "@plane/ui";
|
|||||||
import { BreadcrumbLink } from "components/common";
|
import { BreadcrumbLink } from "components/common";
|
||||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||||
import { EUserProjectRoles, PROJECT_SETTINGS_LINKS } from "constants/project";
|
import { EUserProjectRoles, PROJECT_SETTINGS_LINKS } from "constants/project";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useProject, useUser } from "hooks/store";
|
import { useProject, useUser } from "hooks/store";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
// constants
|
// constants
|
||||||
// components
|
// components
|
||||||
|
|
||||||
@ -44,13 +44,9 @@ export const ProjectSettingHeader: FC<IProjectSettingHeader> = observer((props)
|
|||||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||||
label={currentProjectDetails?.name ?? "Project"}
|
label={currentProjectDetails?.name ?? "Project"}
|
||||||
icon={
|
icon={
|
||||||
currentProjectDetails?.emoji ? (
|
currentProjectDetails && (
|
||||||
renderEmoji(currentProjectDetails.emoji)
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
) : currentProjectDetails?.icon_prop ? (
|
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||||
renderEmoji(currentProjectDetails.icon_prop)
|
|
||||||
) : (
|
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{currentProjectDetails?.name.charAt(0)}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelect
|
|||||||
// constants
|
// constants
|
||||||
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { truncateText } from "helpers/string.helper";
|
import { truncateText } from "helpers/string.helper";
|
||||||
import {
|
import {
|
||||||
useApplication,
|
useApplication,
|
||||||
@ -29,6 +28,7 @@ import {
|
|||||||
useUser,
|
useUser,
|
||||||
} from "hooks/store";
|
} from "hooks/store";
|
||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
|
|
||||||
export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||||
// router
|
// router
|
||||||
@ -119,17 +119,9 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
|||||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||||
label={currentProjectDetails?.name ?? "Project"}
|
label={currentProjectDetails?.name ?? "Project"}
|
||||||
icon={
|
icon={
|
||||||
currentProjectDetails?.emoji ? (
|
currentProjectDetails && (
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
{renderEmoji(currentProjectDetails.emoji)}
|
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||||
</span>
|
|
||||||
) : currentProjectDetails?.icon_prop ? (
|
|
||||||
<div className="grid h-7 w-7 flex-shrink-0 place-items-center">
|
|
||||||
{renderEmoji(currentProjectDetails.icon_prop)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{currentProjectDetails?.name.charAt(0)}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,9 @@ import { BreadcrumbLink } from "components/common";
|
|||||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||||
// helpers
|
// helpers
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
// constants
|
// constants
|
||||||
import { useApplication, useProject, useUser } from "hooks/store";
|
import { useApplication, useProject, useUser } from "hooks/store";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
|
|
||||||
export const ProjectViewsHeader: React.FC = observer(() => {
|
export const ProjectViewsHeader: React.FC = observer(() => {
|
||||||
// router
|
// router
|
||||||
@ -42,17 +42,9 @@ export const ProjectViewsHeader: React.FC = observer(() => {
|
|||||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||||
label={currentProjectDetails?.name ?? "Project"}
|
label={currentProjectDetails?.name ?? "Project"}
|
||||||
icon={
|
icon={
|
||||||
currentProjectDetails?.emoji ? (
|
currentProjectDetails && (
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
{renderEmoji(currentProjectDetails.emoji)}
|
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||||
</span>
|
|
||||||
) : currentProjectDetails?.icon_prop ? (
|
|
||||||
<div className="grid h-7 w-7 flex-shrink-0 place-items-center">
|
|
||||||
{renderEmoji(currentProjectDetails.icon_prop)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{currentProjectDetails?.name.charAt(0)}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { useProject } from "hooks/store";
|
import { useProject } from "hooks/store";
|
||||||
// helpers
|
// components
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
handleRemove: (val: string) => void;
|
handleRemove: (val: string) => void;
|
||||||
@ -25,15 +25,9 @@ export const AppliedProjectFilters: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={projectId} className="flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
|
<div key={projectId} className="flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
|
||||||
{projectDetails.emoji ? (
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
<span className="grid flex-shrink-0 place-items-center">{renderEmoji(projectDetails.emoji)}</span>
|
<ProjectLogo logo={projectDetails.logo_props} className="text-sm" />
|
||||||
) : projectDetails.icon_prop ? (
|
</span>
|
||||||
<div className="-my-1 grid flex-shrink-0 place-items-center">{renderEmoji(projectDetails.icon_prop)}</div>
|
|
||||||
) : (
|
|
||||||
<span className="mr-1 grid flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{projectDetails?.name.charAt(0)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<span className="normal-case">{projectDetails.name}</span>
|
<span className="normal-case">{projectDetails.name}</span>
|
||||||
{editable && (
|
{editable && (
|
||||||
<button
|
<button
|
||||||
|
@ -4,8 +4,9 @@ import { observer } from "mobx-react";
|
|||||||
import { Loader } from "@plane/ui";
|
import { Loader } from "@plane/ui";
|
||||||
import { FilterHeader, FilterOption } from "components/issues";
|
import { FilterHeader, FilterOption } from "components/issues";
|
||||||
// hooks
|
// hooks
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { useProject } from "hooks/store";
|
import { useProject } from "hooks/store";
|
||||||
|
// components
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
// ui
|
// ui
|
||||||
// helpers
|
// helpers
|
||||||
|
|
||||||
@ -52,19 +53,9 @@ export const FilterProjects: React.FC<Props> = observer((props) => {
|
|||||||
isChecked={appliedFilters?.includes(project.id) ? true : false}
|
isChecked={appliedFilters?.includes(project.id) ? true : false}
|
||||||
onClick={() => handleUpdate(project.id)}
|
onClick={() => handleUpdate(project.id)}
|
||||||
icon={
|
icon={
|
||||||
project.emoji ? (
|
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||||
<span className="grid flex-shrink-0 place-items-center text-sm">
|
<ProjectLogo logo={project.logo_props} className="text-sm" />
|
||||||
{renderEmoji(project.emoji)}
|
</span>
|
||||||
</span>
|
|
||||||
) : project.icon_prop ? (
|
|
||||||
<div className="-my-1 grid flex-shrink-0 place-items-center">
|
|
||||||
{renderEmoji(project.icon_prop)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span className="mr-1 grid flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{project?.name.charAt(0)}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
title={project.name}
|
title={project.name}
|
||||||
/>
|
/>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { ContrastIcon } from "lucide-react";
|
import { ContrastIcon } from "lucide-react";
|
||||||
import { Avatar, CycleGroupIcon, DiceIcon, PriorityIcon, StateGroupIcon } from "@plane/ui";
|
import { Avatar, CycleGroupIcon, DiceIcon, PriorityIcon, StateGroupIcon } from "@plane/ui";
|
||||||
|
// components
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
// stores
|
// stores
|
||||||
import { ISSUE_PRIORITIES } from "constants/issue";
|
import { ISSUE_PRIORITIES } from "constants/issue";
|
||||||
import { STATE_GROUPS } from "constants/state";
|
import { STATE_GROUPS } from "constants/state";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { ICycleStore } from "store/cycle.store";
|
import { ICycleStore } from "store/cycle.store";
|
||||||
import { ILabelStore } from "store/label.store";
|
import { ILabelStore } from "store/label.store";
|
||||||
import { IMemberRootStore } from "store/member";
|
import { IMemberRootStore } from "store/member";
|
||||||
@ -62,7 +63,11 @@ const getProjectColumns = (project: IProjectStore): IGroupByColumn[] | undefined
|
|||||||
return {
|
return {
|
||||||
id: project.id,
|
id: project.id,
|
||||||
name: project.name,
|
name: project.name,
|
||||||
icon: <div className="h-6 w-6">{renderEmoji(project.emoji || "")}</div>,
|
icon: (
|
||||||
|
<div className="w-6 h-6 grid place-items-center flex-shrink-0">
|
||||||
|
<ProjectLogo logo={project.logo_props} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
payload: { project_id: project.id },
|
payload: { project_id: project.id },
|
||||||
};
|
};
|
||||||
}) as any;
|
}) as any;
|
||||||
|
@ -13,26 +13,28 @@ import { Loader, Tooltip } from "@plane/ui";
|
|||||||
import { USER_PROFILE_PROJECT_SEGREGATION } from "constants/fetch-keys";
|
import { USER_PROFILE_PROJECT_SEGREGATION } from "constants/fetch-keys";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderFormattedDate } from "helpers/date-time.helper";
|
import { renderFormattedDate } from "helpers/date-time.helper";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useApplication, useUser } from "hooks/store";
|
import { useApplication, useProject, useUser } from "hooks/store";
|
||||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||||
// services
|
// services
|
||||||
import { UserService } from "services/user.service";
|
import { UserService } from "services/user.service";
|
||||||
// components
|
// components
|
||||||
import { ProfileSidebarTime } from "./time";
|
import { ProfileSidebarTime } from "./time";
|
||||||
|
import { ProjectLogo } from "components/project";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
const userService = new UserService();
|
const userService = new UserService();
|
||||||
|
|
||||||
export const ProfileSidebar = observer(() => {
|
export const ProfileSidebar = observer(() => {
|
||||||
|
// refs
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, userId } = router.query;
|
const { workspaceSlug, userId } = router.query;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { currentUser } = useUser();
|
const { currentUser } = useUser();
|
||||||
const { theme: themeStore } = useApplication();
|
const { theme: themeStore } = useApplication();
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const { getProjectById } = useProject();
|
||||||
|
|
||||||
const { data: userProjectsData } = useSWR(
|
const { data: userProjectsData } = useSWR(
|
||||||
workspaceSlug && userId ? USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString()) : null,
|
workspaceSlug && userId ? USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString()) : null,
|
||||||
@ -130,6 +132,8 @@ export const ProfileSidebar = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-9 divide-y divide-custom-border-100">
|
<div className="mt-9 divide-y divide-custom-border-100">
|
||||||
{userProjectsData.project_data.map((project, index) => {
|
{userProjectsData.project_data.map((project, index) => {
|
||||||
|
const projectDetails = getProjectById(project.id);
|
||||||
|
|
||||||
const totalIssues =
|
const totalIssues =
|
||||||
project.created_issues + project.assigned_issues + project.pending_issues + project.completed_issues;
|
project.created_issues + project.assigned_issues + project.pending_issues + project.completed_issues;
|
||||||
|
|
||||||
@ -138,26 +142,18 @@ export const ProfileSidebar = observer(() => {
|
|||||||
? 0
|
? 0
|
||||||
: Math.round((project.completed_issues / project.assigned_issues) * 100);
|
: Math.round((project.completed_issues / project.assigned_issues) * 100);
|
||||||
|
|
||||||
|
if (!projectDetails) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Disclosure key={project.id} as="div" className={`${index === 0 ? "pb-3" : "py-3"}`}>
|
<Disclosure key={project.id} as="div" className={`${index === 0 ? "pb-3" : "py-3"}`}>
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Disclosure.Button className="flex w-full items-center justify-between gap-2">
|
<Disclosure.Button className="flex w-full items-center justify-between gap-2">
|
||||||
<div className="flex w-3/4 items-center gap-2">
|
<div className="flex w-3/4 items-center gap-2">
|
||||||
{project.emoji ? (
|
<span className="grid place-items-center flex-shrink-0 h-7 w-7">
|
||||||
<div className="grid h-7 w-7 flex-shrink-0 place-items-center">
|
<ProjectLogo logo={projectDetails.logo_props} />
|
||||||
{renderEmoji(project.emoji)}
|
</span>
|
||||||
</div>
|
<div className="truncate break-words text-sm font-medium">{projectDetails.name}</div>
|
||||||
) : project.icon_prop ? (
|
|
||||||
<div className="grid h-7 w-7 flex-shrink-0 place-items-center">
|
|
||||||
{renderEmoji(project.icon_prop)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-custom-background-90 text-xs uppercase text-custom-text-100">
|
|
||||||
{project?.name.charAt(0)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="truncate break-words text-sm font-medium">{project.name}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-shrink-0 items-center gap-2">
|
<div className="flex flex-shrink-0 items-center gap-2">
|
||||||
{project.assigned_issues > 0 && (
|
{project.assigned_issues > 0 && (
|
||||||
|
@ -6,14 +6,14 @@ import { LinkIcon, Lock, Pencil, Star } from "lucide-react";
|
|||||||
// ui
|
// ui
|
||||||
import { Avatar, AvatarGroup, Button, Tooltip, TOAST_TYPE, setToast, setPromiseToast } from "@plane/ui";
|
import { Avatar, AvatarGroup, Button, Tooltip, TOAST_TYPE, setToast, setPromiseToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { DeleteProjectModal, JoinProjectModal, EUserProjectRoles } from "components/project";
|
import { DeleteProjectModal, JoinProjectModal, ProjectLogo } from "components/project";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import { copyTextToClipboard } from "helpers/string.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProject } from "hooks/store";
|
import { useProject } from "hooks/store";
|
||||||
// types
|
// types
|
||||||
import type { IProject } from "@plane/types";
|
import type { IProject } from "@plane/types";
|
||||||
|
import { EUserProjectRoles } from "constants/project";
|
||||||
// constants
|
// constants
|
||||||
|
|
||||||
export type ProjectCardProps = {
|
export type ProjectCardProps = {
|
||||||
@ -123,13 +123,9 @@ export const ProjectCard: React.FC<ProjectCardProps> = observer((props) => {
|
|||||||
|
|
||||||
<div className="absolute bottom-4 z-10 flex h-10 w-full items-center justify-between gap-3 px-4">
|
<div className="absolute bottom-4 z-10 flex h-10 w-full items-center justify-between gap-3 px-4">
|
||||||
<div className="flex flex-grow items-center gap-2.5 truncate">
|
<div className="flex flex-grow items-center gap-2.5 truncate">
|
||||||
<div className="item-center flex h-9 w-9 flex-shrink-0 justify-center rounded bg-white/90">
|
<div className="flex item-center justify-center h-9 w-9 flex-shrink-0 rounded bg-white/90">
|
||||||
<span className="flex items-center justify-center">
|
<span className="grid place-items-center">
|
||||||
{project.emoji
|
<ProjectLogo logo={project.logo_props} />
|
||||||
? renderEmoji(project.emoji)
|
|
||||||
: project.icon_prop
|
|
||||||
? renderEmoji(project.icon_prop)
|
|
||||||
: null}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -4,19 +4,30 @@ import { useForm, Controller } from "react-hook-form";
|
|||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
// ui
|
// ui
|
||||||
import { Button, CustomSelect, Input, TextArea, TOAST_TYPE, setToast } from "@plane/ui";
|
import {
|
||||||
|
Button,
|
||||||
|
CustomEmojiIconPicker,
|
||||||
|
CustomSelect,
|
||||||
|
EmojiIconPickerTypes,
|
||||||
|
Input,
|
||||||
|
setToast,
|
||||||
|
TextArea,
|
||||||
|
TOAST_TYPE,
|
||||||
|
} from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ImagePickerPopover } from "components/core";
|
import { ImagePickerPopover } from "components/core";
|
||||||
import { MemberDropdown } from "components/dropdowns";
|
import { MemberDropdown } from "components/dropdowns";
|
||||||
import EmojiIconPicker from "components/emoji-icon-picker";
|
|
||||||
// constants
|
// constants
|
||||||
import { PROJECT_CREATED } from "constants/event-tracker";
|
import { PROJECT_CREATED } from "constants/event-tracker";
|
||||||
import { NETWORK_CHOICES, PROJECT_UNSPLASH_COVERS } from "constants/project";
|
import { NETWORK_CHOICES, PROJECT_UNSPLASH_COVERS } from "constants/project";
|
||||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
// helpers
|
// helpers
|
||||||
import { getRandomEmoji, renderEmoji } from "helpers/emoji.helper";
|
import { convertHexEmojiToDecimal, getRandomEmoji } from "helpers/emoji.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useProject, useUser } from "hooks/store";
|
import { useEventTracker, useProject, useUser } from "hooks/store";
|
||||||
|
import { projectIdentifierSanitizer } from "helpers/project.helper";
|
||||||
|
import { ProjectLogo } from "./project-logo";
|
||||||
|
import { IProject } from "@plane/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -29,6 +40,21 @@ interface IIsGuestCondition {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultValues: Partial<IProject> = {
|
||||||
|
cover_image: PROJECT_UNSPLASH_COVERS[Math.floor(Math.random() * PROJECT_UNSPLASH_COVERS.length)],
|
||||||
|
description: "",
|
||||||
|
logo_props: {
|
||||||
|
in_use: "emoji",
|
||||||
|
emoji: {
|
||||||
|
value: getRandomEmoji(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
identifier: "",
|
||||||
|
name: "",
|
||||||
|
network: 2,
|
||||||
|
project_lead: null,
|
||||||
|
};
|
||||||
|
|
||||||
const IsGuestCondition: FC<IIsGuestCondition> = ({ onClose }) => {
|
const IsGuestCondition: FC<IIsGuestCondition> = ({ onClose }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onClose();
|
onClose();
|
||||||
@ -42,19 +68,6 @@ const IsGuestCondition: FC<IIsGuestCondition> = ({ onClose }) => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ICreateProjectForm {
|
|
||||||
name: string;
|
|
||||||
identifier: string;
|
|
||||||
description: string;
|
|
||||||
emoji_and_icon: string;
|
|
||||||
network: number;
|
|
||||||
project_lead_member: string;
|
|
||||||
project_lead: string;
|
|
||||||
cover_image: string;
|
|
||||||
icon_prop: any;
|
|
||||||
emoji: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CreateProjectModal: FC<Props> = observer((props) => {
|
export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||||
const { isOpen, onClose, setToFavorite = false, workspaceSlug } = props;
|
const { isOpen, onClose, setToFavorite = false, workspaceSlug } = props;
|
||||||
// store
|
// store
|
||||||
@ -66,7 +79,6 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
|||||||
// states
|
// states
|
||||||
const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true);
|
const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true);
|
||||||
// form info
|
// form info
|
||||||
const cover_image = PROJECT_UNSPLASH_COVERS[Math.floor(Math.random() * PROJECT_UNSPLASH_COVERS.length)];
|
|
||||||
const {
|
const {
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@ -74,28 +86,20 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
|||||||
control,
|
control,
|
||||||
watch,
|
watch,
|
||||||
setValue,
|
setValue,
|
||||||
} = useForm<ICreateProjectForm>({
|
} = useForm<IProject>({
|
||||||
defaultValues: {
|
defaultValues,
|
||||||
cover_image,
|
|
||||||
description: "",
|
|
||||||
emoji_and_icon: getRandomEmoji(),
|
|
||||||
identifier: "",
|
|
||||||
name: "",
|
|
||||||
network: 2,
|
|
||||||
project_lead: undefined,
|
|
||||||
},
|
|
||||||
reValidateMode: "onChange",
|
reValidateMode: "onChange",
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentNetwork = NETWORK_CHOICES.find((n) => n.key === watch("network"));
|
|
||||||
|
|
||||||
if (currentWorkspaceRole && isOpen)
|
if (currentWorkspaceRole && isOpen)
|
||||||
if (currentWorkspaceRole < EUserWorkspaceRoles.MEMBER) return <IsGuestCondition onClose={onClose} />;
|
if (currentWorkspaceRole < EUserWorkspaceRoles.MEMBER) return <IsGuestCondition onClose={onClose} />;
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
onClose();
|
onClose();
|
||||||
setIsChangeInIdentifierRequired(true);
|
setIsChangeInIdentifierRequired(true);
|
||||||
reset();
|
setTimeout(() => {
|
||||||
|
reset();
|
||||||
|
}, 300);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddToFavorites = (projectId: string) => {
|
const handleAddToFavorites = (projectId: string) => {
|
||||||
@ -110,18 +114,11 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async (formData: ICreateProjectForm) => {
|
const onSubmit = async (formData: Partial<IProject>) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { emoji_and_icon, project_lead_member, ...payload } = formData;
|
|
||||||
|
|
||||||
if (typeof formData.emoji_and_icon === "object") payload.icon_prop = formData.emoji_and_icon;
|
|
||||||
else payload.emoji = formData.emoji_and_icon;
|
|
||||||
|
|
||||||
payload.project_lead = formData.project_lead_member;
|
|
||||||
// Upper case identifier
|
// Upper case identifier
|
||||||
payload.identifier = payload.identifier.toUpperCase();
|
formData.identifier = formData.identifier?.toUpperCase();
|
||||||
|
|
||||||
return createProject(workspaceSlug.toString(), payload)
|
return createProject(workspaceSlug.toString(), formData)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const newPayload = {
|
const newPayload = {
|
||||||
...res,
|
...res,
|
||||||
@ -151,7 +148,7 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
|||||||
captureProjectEvent({
|
captureProjectEvent({
|
||||||
eventName: PROJECT_CREATED,
|
eventName: PROJECT_CREATED,
|
||||||
payload: {
|
payload: {
|
||||||
...payload,
|
...formData,
|
||||||
state: "FAILED",
|
state: "FAILED",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -165,13 +162,13 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.target.value === "") setValue("identifier", "");
|
if (e.target.value === "") setValue("identifier", "");
|
||||||
else setValue("identifier", e.target.value.replace(/[^ÇŞĞIİÖÜA-Za-z0-9]/g, "").substring(0, 5));
|
else setValue("identifier", projectIdentifierSanitizer(e.target.value).substring(0, 5));
|
||||||
onChange(e);
|
onChange(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleIdentifierChange = (onChange: any) => (e: ChangeEvent<HTMLInputElement>) => {
|
const handleIdentifierChange = (onChange: any) => (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const { value } = e.target;
|
const { value } = e.target;
|
||||||
const alphanumericValue = value.replace(/[^ÇŞĞIİÖÜA-Za-z0-9]/g, "");
|
const alphanumericValue = projectIdentifierSanitizer(value);
|
||||||
setIsChangeInIdentifierRequired(false);
|
setIsChangeInIdentifierRequired(false);
|
||||||
onChange(alphanumericValue);
|
onChange(alphanumericValue);
|
||||||
};
|
};
|
||||||
@ -204,11 +201,11 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
|||||||
>
|
>
|
||||||
<Dialog.Panel className="w-full transform rounded-lg bg-custom-background-100 p-3 text-left shadow-custom-shadow-md transition-all sm:w-3/5 lg:w-1/2 xl:w-2/5">
|
<Dialog.Panel className="w-full transform rounded-lg bg-custom-background-100 p-3 text-left shadow-custom-shadow-md transition-all sm:w-3/5 lg:w-1/2 xl:w-2/5">
|
||||||
<div className="group relative h-44 w-full rounded-lg bg-custom-background-80">
|
<div className="group relative h-44 w-full rounded-lg bg-custom-background-80">
|
||||||
{watch("cover_image") !== null && (
|
{watch("cover_image") && (
|
||||||
<img
|
<img
|
||||||
src={watch("cover_image")!}
|
src={watch("cover_image")!}
|
||||||
className="absolute left-0 top-0 h-full w-full rounded-lg object-cover"
|
className="absolute left-0 top-0 h-full w-full rounded-lg object-cover"
|
||||||
alt="Cover Image"
|
alt="Cover image"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -218,30 +215,50 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute bottom-2 right-2">
|
<div className="absolute bottom-2 right-2">
|
||||||
<ImagePickerPopover
|
<Controller
|
||||||
label="Change Cover"
|
name="cover_image"
|
||||||
onChange={(image) => {
|
|
||||||
setValue("cover_image", image);
|
|
||||||
}}
|
|
||||||
control={control}
|
control={control}
|
||||||
value={watch("cover_image")}
|
render={({ field: { value, onChange } }) => (
|
||||||
tabIndex={9}
|
<ImagePickerPopover
|
||||||
|
label="Change Cover"
|
||||||
|
onChange={onChange}
|
||||||
|
control={control}
|
||||||
|
value={value}
|
||||||
|
tabIndex={9}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute -bottom-[22px] left-3">
|
<div className="absolute -bottom-[22px] left-3">
|
||||||
<Controller
|
<Controller
|
||||||
name="emoji_and_icon"
|
name="logo_props"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<EmojiIconPicker
|
<CustomEmojiIconPicker
|
||||||
label={
|
label={
|
||||||
<div className="grid h-[44px] w-[44px] place-items-center rounded-md bg-custom-background-80 text-lg outline-none">
|
<span className="grid h-11 w-11 place-items-center rounded-md bg-custom-background-80">
|
||||||
{value ? renderEmoji(value) : "Icon"}
|
<ProjectLogo logo={value} className="text-xl" />
|
||||||
</div>
|
</span>
|
||||||
|
}
|
||||||
|
onChange={(val) => {
|
||||||
|
let logoValue = {};
|
||||||
|
|
||||||
|
if (val.type === "emoji")
|
||||||
|
logoValue = {
|
||||||
|
value: convertHexEmojiToDecimal(val.value.unified),
|
||||||
|
url: val.value.imageUrl,
|
||||||
|
};
|
||||||
|
else if (val.type === "icon") logoValue = val.value;
|
||||||
|
|
||||||
|
onChange({
|
||||||
|
in_use: val.type,
|
||||||
|
[val.type]: logoValue,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
defaultIconColor={value.in_use === "icon" ? value.icon?.color : undefined}
|
||||||
|
defaultOpen={
|
||||||
|
value.in_use === "emoji" ? EmojiIconPickerTypes.EMOJI : EmojiIconPickerTypes.ICON
|
||||||
}
|
}
|
||||||
onChange={onChange}
|
|
||||||
value={value}
|
|
||||||
tabIndex={10}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -275,7 +292,9 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<span className="text-xs text-red-500">{errors?.name?.message}</span>
|
<span className="text-xs text-red-500">
|
||||||
|
<>{errors?.name?.message}</>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Controller
|
<Controller
|
||||||
@ -310,7 +329,9 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<span className="text-xs text-red-500">{errors?.identifier?.message}</span>
|
<span className="text-xs text-red-500">
|
||||||
|
<>{errors?.identifier?.message}</>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="md:col-span-4">
|
<div className="md:col-span-4">
|
||||||
<Controller
|
<Controller
|
||||||
@ -336,57 +357,65 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
|||||||
<Controller
|
<Controller
|
||||||
name="network"
|
name="network"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field: { onChange, value } }) => (
|
render={({ field: { onChange, value } }) => {
|
||||||
<div className="flex-shrink-0" tabIndex={4}>
|
const currentNetwork = NETWORK_CHOICES.find((n) => n.key === value);
|
||||||
<CustomSelect
|
|
||||||
value={value}
|
return (
|
||||||
onChange={onChange}
|
<div className="flex-shrink-0" tabIndex={4}>
|
||||||
label={
|
<CustomSelect
|
||||||
<div className="flex items-center gap-1">
|
value={value}
|
||||||
{currentNetwork ? (
|
onChange={onChange}
|
||||||
<>
|
label={
|
||||||
<currentNetwork.icon className="h-3 w-3" />
|
<div className="flex items-center gap-1">
|
||||||
{currentNetwork.label}
|
{currentNetwork ? (
|
||||||
</>
|
<>
|
||||||
) : (
|
<currentNetwork.icon className="h-3 w-3" />
|
||||||
<span className="text-custom-text-400">Select Network</span>
|
{currentNetwork.label}
|
||||||
)}
|
</>
|
||||||
</div>
|
) : (
|
||||||
}
|
<span className="text-custom-text-400">Select network</span>
|
||||||
placement="bottom-start"
|
)}
|
||||||
noChevron
|
|
||||||
tabIndex={4}
|
|
||||||
>
|
|
||||||
{NETWORK_CHOICES.map((network) => (
|
|
||||||
<CustomSelect.Option key={network.key} value={network.key}>
|
|
||||||
<div className="flex items-start gap-2">
|
|
||||||
<network.icon className="h-3.5 w-3.5" />
|
|
||||||
<div className="-mt-1">
|
|
||||||
<p>{network.label}</p>
|
|
||||||
<p className="text-xs text-custom-text-400">{network.description}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</CustomSelect.Option>
|
}
|
||||||
))}
|
placement="bottom-start"
|
||||||
</CustomSelect>
|
noChevron
|
||||||
</div>
|
tabIndex={4}
|
||||||
)}
|
>
|
||||||
|
{NETWORK_CHOICES.map((network) => (
|
||||||
|
<CustomSelect.Option key={network.key} value={network.key}>
|
||||||
|
<div className="flex items-start gap-2">
|
||||||
|
<network.icon className="h-3.5 w-3.5" />
|
||||||
|
<div className="-mt-1">
|
||||||
|
<p>{network.label}</p>
|
||||||
|
<p className="text-xs text-custom-text-400">{network.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CustomSelect.Option>
|
||||||
|
))}
|
||||||
|
</CustomSelect>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
name="project_lead_member"
|
name="project_lead"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => {
|
||||||
<div className="h-7 flex-shrink-0" tabIndex={5}>
|
if (value === undefined || value === null || typeof value === "string")
|
||||||
<MemberDropdown
|
return (
|
||||||
value={value}
|
<div className="h-7 flex-shrink-0" tabIndex={5}>
|
||||||
onChange={onChange}
|
<MemberDropdown
|
||||||
placeholder="Lead"
|
value={value}
|
||||||
multiple={false}
|
onChange={onChange}
|
||||||
buttonVariant="border-with-text"
|
placeholder="Lead"
|
||||||
tabIndex={5}
|
multiple={false}
|
||||||
/>
|
buttonVariant="border-with-text"
|
||||||
</div>
|
tabIndex={5}
|
||||||
)}
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
else return <></>;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -396,7 +425,7 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
|||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="primary" type="submit" size="sm" loading={isSubmitting} tabIndex={7}>
|
<Button variant="primary" type="submit" size="sm" loading={isSubmitting} tabIndex={7}>
|
||||||
{isSubmitting ? "Creating..." : "Create Project"}
|
{isSubmitting ? "Creating" : "Create project"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -3,22 +3,31 @@ import { Controller, useForm } from "react-hook-form";
|
|||||||
// icons
|
// icons
|
||||||
import { Lock } from "lucide-react";
|
import { Lock } from "lucide-react";
|
||||||
// ui
|
// ui
|
||||||
import { Button, CustomSelect, Input, TextArea, TOAST_TYPE, setToast } from "@plane/ui";
|
import {
|
||||||
|
Button,
|
||||||
|
CustomSelect,
|
||||||
|
Input,
|
||||||
|
TextArea,
|
||||||
|
TOAST_TYPE,
|
||||||
|
setToast,
|
||||||
|
CustomEmojiIconPicker,
|
||||||
|
EmojiIconPickerTypes,
|
||||||
|
} from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ImagePickerPopover } from "components/core";
|
import { ImagePickerPopover } from "components/core";
|
||||||
import EmojiIconPicker from "components/emoji-icon-picker";
|
|
||||||
// constants
|
// constants
|
||||||
import { PROJECT_UPDATED } from "constants/event-tracker";
|
import { PROJECT_UPDATED } from "constants/event-tracker";
|
||||||
import { NETWORK_CHOICES } from "constants/project";
|
import { NETWORK_CHOICES } from "constants/project";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderFormattedDate } from "helpers/date-time.helper";
|
import { renderFormattedDate } from "helpers/date-time.helper";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useProject } from "hooks/store";
|
import { useEventTracker, useProject } from "hooks/store";
|
||||||
// services
|
// services
|
||||||
import { ProjectService } from "services/project";
|
import { ProjectService } from "services/project";
|
||||||
// types
|
// types
|
||||||
import { IProject, IWorkspace } from "@plane/types";
|
import { IProject, IWorkspace } from "@plane/types";
|
||||||
|
import { ProjectLogo } from "./project-logo";
|
||||||
|
import { convertHexEmojiToDecimal } from "helpers/emoji.helper";
|
||||||
export interface IProjectDetailsForm {
|
export interface IProjectDetailsForm {
|
||||||
project: IProject;
|
project: IProject;
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
@ -46,7 +55,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
} = useForm<IProject>({
|
} = useForm<IProject>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
...project,
|
...project,
|
||||||
emoji_and_icon: project.emoji ?? project.icon_prop,
|
|
||||||
workspace: (project.workspace as IWorkspace).id,
|
workspace: (project.workspace as IWorkspace).id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -55,7 +63,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
if (project && projectId !== getValues("id")) {
|
if (project && projectId !== getValues("id")) {
|
||||||
reset({
|
reset({
|
||||||
...project,
|
...project,
|
||||||
emoji_and_icon: project.emoji ?? project.icon_prop,
|
|
||||||
workspace: (project.workspace as IWorkspace).id,
|
workspace: (project.workspace as IWorkspace).id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -109,14 +116,9 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
identifier: formData.identifier,
|
identifier: formData.identifier,
|
||||||
description: formData.description,
|
description: formData.description,
|
||||||
cover_image: formData.cover_image,
|
cover_image: formData.cover_image,
|
||||||
|
logo_props: formData.logo_props,
|
||||||
};
|
};
|
||||||
if (typeof formData.emoji_and_icon === "object") {
|
|
||||||
payload.emoji = null;
|
|
||||||
payload.icon_prop = formData.emoji_and_icon;
|
|
||||||
} else {
|
|
||||||
payload.emoji = formData.emoji_and_icon;
|
|
||||||
payload.icon_prop = null;
|
|
||||||
}
|
|
||||||
if (project.identifier !== formData.identifier)
|
if (project.identifier !== formData.identifier)
|
||||||
await projectService
|
await projectService
|
||||||
.checkProjectIdentifierAvailability(workspaceSlug as string, payload.identifier ?? "")
|
.checkProjectIdentifierAvailability(workspaceSlug as string, payload.identifier ?? "")
|
||||||
@ -139,20 +141,37 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||||||
<div className="z-5 absolute bottom-4 flex w-full items-end justify-between gap-3 px-4">
|
<div className="z-5 absolute bottom-4 flex w-full items-end justify-between gap-3 px-4">
|
||||||
<div className="flex flex-grow gap-3 truncate">
|
<div className="flex flex-grow gap-3 truncate">
|
||||||
<div className="flex h-[52px] w-[52px] flex-shrink-0 items-center justify-center rounded-lg bg-custom-background-90">
|
<div className="flex h-[52px] w-[52px] flex-shrink-0 items-center justify-center rounded-lg bg-custom-background-90">
|
||||||
<div className="grid h-7 w-7 place-items-center">
|
<Controller
|
||||||
<Controller
|
control={control}
|
||||||
control={control}
|
name="logo_props"
|
||||||
name="emoji_and_icon"
|
render={({ field: { value, onChange } }) => (
|
||||||
render={({ field: { value, onChange } }) => (
|
<CustomEmojiIconPicker
|
||||||
<EmojiIconPicker
|
label={
|
||||||
label={value ? renderEmoji(value) : "Icon"}
|
<span className="grid h-7 w-7 place-items-center">
|
||||||
value={value}
|
<ProjectLogo logo={value} className="text-lg" />
|
||||||
onChange={onChange}
|
</span>
|
||||||
disabled={!isAdmin}
|
}
|
||||||
/>
|
onChange={(val) => {
|
||||||
)}
|
let logoValue = {};
|
||||||
/>
|
|
||||||
</div>
|
if (val.type === "emoji")
|
||||||
|
logoValue = {
|
||||||
|
value: convertHexEmojiToDecimal(val.value.unified),
|
||||||
|
url: val.value.imageUrl,
|
||||||
|
};
|
||||||
|
else if (val.type === "icon") logoValue = val.value;
|
||||||
|
|
||||||
|
onChange({
|
||||||
|
in_use: val.type,
|
||||||
|
[val.type]: logoValue,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
defaultIconColor={value.in_use === "icon" ? value.icon?.color : undefined}
|
||||||
|
defaultOpen={value.in_use === "emoji" ? EmojiIconPickerTypes.EMOJI : EmojiIconPickerTypes.ICON}
|
||||||
|
disabled={!isAdmin}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1 truncate text-white">
|
<div className="flex flex-col gap-1 truncate text-white">
|
||||||
<span className="truncate text-lg font-semibold">{watch("name")}</span>
|
<span className="truncate text-lg font-semibold">{watch("name")}</span>
|
||||||
|
@ -14,6 +14,7 @@ export * from "./sidebar-list";
|
|||||||
export * from "./integration-card";
|
export * from "./integration-card";
|
||||||
export * from "./member-list";
|
export * from "./member-list";
|
||||||
export * from "./member-list-item";
|
export * from "./member-list-item";
|
||||||
|
export * from "./project-logo";
|
||||||
export * from "./project-settings-member-defaults";
|
export * from "./project-settings-member-defaults";
|
||||||
export * from "./send-project-invitation-modal";
|
export * from "./send-project-invitation-modal";
|
||||||
export * from "./confirm-project-member-remove";
|
export * from "./confirm-project-member-remove";
|
||||||
|
34
web/components/project/project-logo.tsx
Normal file
34
web/components/project/project-logo.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// helpers
|
||||||
|
import { cn } from "helpers/common.helper";
|
||||||
|
// types
|
||||||
|
import { TProjectLogoProps } from "@plane/types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
logo: TProjectLogoProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProjectLogo: React.FC<Props> = (props) => {
|
||||||
|
const { className, logo } = props;
|
||||||
|
|
||||||
|
if (logo.in_use === "icon" && logo.icon)
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: logo.icon.color,
|
||||||
|
}}
|
||||||
|
className={cn("material-symbols-rounded text-base", className)}
|
||||||
|
>
|
||||||
|
{logo.icon.name}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (logo.in_use === "emoji" && logo.emoji)
|
||||||
|
return (
|
||||||
|
<span className={cn("text-base", className)}>
|
||||||
|
{logo.emoji.value?.split("-").map((emoji) => String.fromCodePoint(parseInt(emoji, 10)))}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <span />;
|
||||||
|
};
|
@ -29,10 +29,9 @@ import {
|
|||||||
LayersIcon,
|
LayersIcon,
|
||||||
setPromiseToast,
|
setPromiseToast,
|
||||||
} from "@plane/ui";
|
} from "@plane/ui";
|
||||||
import { LeaveProjectModal, PublishProjectModal } from "components/project";
|
import { LeaveProjectModal, ProjectLogo, PublishProjectModal } from "components/project";
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { cn } from "helpers/common.helper";
|
import { cn } from "helpers/common.helper";
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
|
||||||
import { getNumberCount } from "helpers/string.helper";
|
import { getNumberCount } from "helpers/string.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useApplication, useEventTracker, useInbox, useProject } from "hooks/store";
|
import { useApplication, useEventTracker, useInbox, useProject } from "hooks/store";
|
||||||
@ -100,23 +99,21 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
|||||||
const [leaveProjectModalOpen, setLeaveProjectModal] = useState(false);
|
const [leaveProjectModalOpen, setLeaveProjectModal] = useState(false);
|
||||||
const [publishModalOpen, setPublishModal] = useState(false);
|
const [publishModalOpen, setPublishModal] = useState(false);
|
||||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||||
|
// refs
|
||||||
|
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId: URLProjectId } = router.query;
|
const { workspaceSlug, projectId: URLProjectId } = router.query;
|
||||||
// derived values
|
// derived values
|
||||||
const project = getProjectById(projectId);
|
const project = getProjectById(projectId);
|
||||||
|
const isCollapsed = themeStore.sidebarCollapsed;
|
||||||
|
const inboxesMap = project?.inbox_view ? getInboxesByProjectId(projectId) : undefined;
|
||||||
|
const inboxDetails = inboxesMap && inboxesMap.length > 0 ? getInboxById(inboxesMap[0]) : undefined;
|
||||||
|
// auth
|
||||||
const isAdmin = project?.member_role === EUserProjectRoles.ADMIN;
|
const isAdmin = project?.member_role === EUserProjectRoles.ADMIN;
|
||||||
const isViewerOrGuest =
|
const isViewerOrGuest =
|
||||||
project?.member_role && [EUserProjectRoles.VIEWER, EUserProjectRoles.GUEST].includes(project.member_role);
|
project?.member_role && [EUserProjectRoles.VIEWER, EUserProjectRoles.GUEST].includes(project.member_role);
|
||||||
|
|
||||||
const isCollapsed = themeStore.sidebarCollapsed;
|
|
||||||
|
|
||||||
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
|
|
||||||
const inboxesMap = project?.inbox_view ? getInboxesByProjectId(projectId) : undefined;
|
|
||||||
const inboxDetails = inboxesMap && inboxesMap.length > 0 ? getInboxById(inboxesMap[0]) : undefined;
|
|
||||||
|
|
||||||
const handleAddToFavorites = () => {
|
const handleAddToFavorites = () => {
|
||||||
if (!workspaceSlug || !project) return;
|
if (!workspaceSlug || !project) return;
|
||||||
|
|
||||||
@ -178,9 +175,13 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
|||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={`group relative flex w-full items-center rounded-md px-2 py-1 text-custom-sidebar-text-10 hover:bg-custom-sidebar-background-80 ${
|
className={cn(
|
||||||
snapshot?.isDragging ? "opacity-60" : ""
|
"group relative flex w-full items-center rounded-md px-2 py-1 text-custom-sidebar-text-100 hover:bg-custom-sidebar-background-80",
|
||||||
} ${isMenuActive ? "!bg-custom-sidebar-background-80" : ""}`}
|
{
|
||||||
|
"opacity-60": snapshot?.isDragging,
|
||||||
|
"bg-custom-sidebar-background-80": isMenuActive,
|
||||||
|
}
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{provided && !disableDrag && (
|
{provided && !disableDrag && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@ -189,11 +190,14 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`absolute -left-2.5 top-1/2 hidden -translate-y-1/2 rounded p-0.5 text-custom-sidebar-text-400 ${
|
className={cn(
|
||||||
isCollapsed ? "" : "group-hover:!flex"
|
"absolute -left-2.5 top-1/2 hidden -translate-y-1/2 rounded p-0.5 text-custom-sidebar-text-400",
|
||||||
} ${project.sort_order === null ? "cursor-not-allowed opacity-60" : ""} ${
|
{
|
||||||
isMenuActive ? "!flex" : ""
|
"group-hover:flex": !isCollapsed,
|
||||||
}`}
|
"cursor-not-allowed opacity-60": project.sort_order === null,
|
||||||
|
flex: isMenuActive,
|
||||||
|
}
|
||||||
|
)}
|
||||||
{...provided?.dragHandleProps}
|
{...provided?.dragHandleProps}
|
||||||
>
|
>
|
||||||
<MoreVertical className="h-3.5" />
|
<MoreVertical className="h-3.5" />
|
||||||
@ -204,36 +208,32 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
|||||||
<Tooltip tooltipContent={`${project.name}`} position="right" className="ml-2" disabled={!isCollapsed}>
|
<Tooltip tooltipContent={`${project.name}`} position="right" className="ml-2" disabled={!isCollapsed}>
|
||||||
<Disclosure.Button
|
<Disclosure.Button
|
||||||
as="div"
|
as="div"
|
||||||
className={`flex flex-grow cursor-pointer select-none items-center truncate text-left text-sm font-medium ${
|
className={cn(
|
||||||
isCollapsed ? "justify-center" : `justify-between`
|
"flex items-center justify-between flex-grow cursor-pointer select-none truncate text-left text-sm font-medium",
|
||||||
}`}
|
{
|
||||||
|
"justify-center": isCollapsed,
|
||||||
|
}
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex w-full flex-grow items-center gap-x-2 truncate ${
|
className={cn("w-full flex-grow flex items-center gap-1 truncate", {
|
||||||
isCollapsed ? "justify-center" : ""
|
"justify-center": isCollapsed,
|
||||||
}`}
|
})}
|
||||||
>
|
>
|
||||||
{project.emoji ? (
|
<div className="h-7 w-7 grid place-items-center">
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
<ProjectLogo logo={project.logo_props} />
|
||||||
{renderEmoji(project.emoji)}
|
</div>
|
||||||
</span>
|
|
||||||
) : project.icon_prop ? (
|
|
||||||
<div className="grid h-7 w-7 flex-shrink-0 place-items-center">
|
|
||||||
{renderEmoji(project.icon_prop)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
|
||||||
{project?.name.charAt(0)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isCollapsed && <p className="truncate text-custom-sidebar-text-200">{project.name}</p>}
|
{!isCollapsed && <p className="truncate text-custom-sidebar-text-200">{project.name}</p>}
|
||||||
</div>
|
</div>
|
||||||
{!isCollapsed && (
|
{!isCollapsed && (
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
className={`hidden h-4 w-4 flex-shrink-0 ${open ? "rotate-180" : ""} ${
|
className={cn(
|
||||||
isMenuActive ? "!block" : ""
|
"hidden h-4 w-4 flex-shrink-0 mb-0.5 text-custom-sidebar-text-400 duration-300 group-hover:block",
|
||||||
} mb-0.5 text-custom-sidebar-text-400 duration-300 group-hover:!block`}
|
{
|
||||||
|
"rotate-180": open,
|
||||||
|
block: isMenuActive,
|
||||||
|
}
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
@ -250,7 +250,9 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
|||||||
<MoreHorizontal className="h-3.5 w-3.5" />
|
<MoreHorizontal className="h-3.5 w-3.5" />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
className={`hidden flex-shrink-0 group-hover:block ${isMenuActive ? "!block" : ""}`}
|
className={cn("hidden flex-shrink-0 group-hover:block", {
|
||||||
|
"!block": isMenuActive,
|
||||||
|
})}
|
||||||
buttonClassName="!text-custom-sidebar-text-400"
|
buttonClassName="!text-custom-sidebar-text-400"
|
||||||
ellipsis
|
ellipsis
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
|
@ -51,3 +51,12 @@ export const groupReactions: (reactions: any[], key: string) => { [key: string]:
|
|||||||
|
|
||||||
return groupedReactions;
|
return groupedReactions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const convertHexEmojiToDecimal = (emojiUnified: string): string => {
|
||||||
|
if (!emojiUnified) return "";
|
||||||
|
|
||||||
|
return emojiUnified
|
||||||
|
.split("-")
|
||||||
|
.map((e) => parseInt(e, 16))
|
||||||
|
.join("-");
|
||||||
|
};
|
||||||
|
@ -43,3 +43,6 @@ export const orderJoinedProjects = (
|
|||||||
|
|
||||||
return updatedSortOrder;
|
return updatedSortOrder;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const projectIdentifierSanitizer = (identifier: string): string =>
|
||||||
|
identifier.replace(/[^ÇŞĞIİÖÜA-Za-z0-9]/g, "");
|
||||||
|
@ -24,8 +24,8 @@ export const ProjectSettingsSidebar = () => {
|
|||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<span className="text-xs font-semibold text-custom-sidebar-text-400">SETTINGS</span>
|
<span className="text-xs font-semibold text-custom-sidebar-text-400">SETTINGS</span>
|
||||||
<Loader className="flex w-full flex-col gap-2">
|
<Loader className="flex w-full flex-col gap-2">
|
||||||
{[...Array(8)].map(() => (
|
{[...Array(8)].map((index) => (
|
||||||
<Loader.Item height="34px" />
|
<Loader.Item key={index} height="34px" />
|
||||||
))}
|
))}
|
||||||
</Loader>
|
</Loader>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,6 +6,7 @@ import { ThemeProvider } from "next-themes";
|
|||||||
import "styles/globals.css";
|
import "styles/globals.css";
|
||||||
import "styles/command-pallette.css";
|
import "styles/command-pallette.css";
|
||||||
import "styles/nprogress.css";
|
import "styles/nprogress.css";
|
||||||
|
import "styles/emoji.css";
|
||||||
import "styles/react-day-picker.css";
|
import "styles/react-day-picker.css";
|
||||||
// constants
|
// constants
|
||||||
import { THEMES } from "constants/themes";
|
import { THEMES } from "constants/themes";
|
||||||
|
52
web/styles/emoji.css
Normal file
52
web/styles/emoji.css
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
.EmojiPickerReact {
|
||||||
|
--epr-category-navigation-button-size: 1.25rem !important;
|
||||||
|
--epr-category-label-height: 1.5rem !important;
|
||||||
|
--epr-emoji-size: 1.25rem !important;
|
||||||
|
--epr-picker-border-radius: 0.25rem !important;
|
||||||
|
--epr-horizontal-padding: 0.5rem !important;
|
||||||
|
--epr-emoji-padding: 0.5rem !important;
|
||||||
|
background-color: rgba(var(--color-background-100)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.epr-main {
|
||||||
|
border: none !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.epr-emoji-category-label {
|
||||||
|
font-size: 0.7875rem !important;
|
||||||
|
color: rgba(var(--color-text-300)) !important;
|
||||||
|
background-color: rgba(var(--color-background-100), 0.8) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.epr-category-nav,
|
||||||
|
.epr-header-overlay {
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.epr-emoji:hover > *,
|
||||||
|
button.epr-emoji:focus > * {
|
||||||
|
background-color: rgba(var(--color-background-80)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.epr-search {
|
||||||
|
font-size: 0.7875rem !important;
|
||||||
|
height: 2rem !important;
|
||||||
|
background: transparent !important;
|
||||||
|
border-color: rgba(var(--color-border-200)) !important;
|
||||||
|
border-radius: 0.25rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.epr-search::placeholder {
|
||||||
|
color: rgba(var(--color-text-400)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.epr-btn-clear-search:hover {
|
||||||
|
background-color: rgba(var(--color-background-80)) !important;
|
||||||
|
color: rgba(var(--color-text-300)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.epr-emoji-variation-picker {
|
||||||
|
background-color: rgba(var(--color-background-100)) !important;
|
||||||
|
border-color: rgba(var(--color-border-200)) !important;
|
||||||
|
}
|
@ -2722,7 +2722,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react@*", "@types/react@^18.2.42":
|
"@types/react@*", "@types/react@18.2.42", "@types/react@^18.2.42":
|
||||||
version "18.2.42"
|
version "18.2.42"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.42.tgz#6f6b11a904f6d96dda3c2920328a97011a00aba7"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.42.tgz#6f6b11a904f6d96dda3c2920328a97011a00aba7"
|
||||||
integrity sha512-c1zEr96MjakLYus/wPnuWDo1/zErfdU9rNsIGmE+NV71nx88FG9Ttgo5dqorXTu/LImX2f63WBP986gJkMPNbA==
|
integrity sha512-c1zEr96MjakLYus/wPnuWDo1/zErfdU9rNsIGmE+NV71nx88FG9Ttgo5dqorXTu/LImX2f63WBP986gJkMPNbA==
|
||||||
@ -4064,6 +4064,11 @@ electron-to-chromium@^1.4.601:
|
|||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.616.tgz#4bddbc2c76e1e9dbf449ecd5da3d8119826ea4fb"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.616.tgz#4bddbc2c76e1e9dbf449ecd5da3d8119826ea4fb"
|
||||||
integrity sha512-1n7zWYh8eS0L9Uy+GskE0lkBUNK83cXTVJI0pU3mGprFsbfSdAc15VTFbo+A+Bq4pwstmL30AVcEU3Fo463lNg==
|
integrity sha512-1n7zWYh8eS0L9Uy+GskE0lkBUNK83cXTVJI0pU3mGprFsbfSdAc15VTFbo+A+Bq4pwstmL30AVcEU3Fo463lNg==
|
||||||
|
|
||||||
|
emoji-picker-react@^4.5.16:
|
||||||
|
version "4.5.16"
|
||||||
|
resolved "https://registry.yarnpkg.com/emoji-picker-react/-/emoji-picker-react-4.5.16.tgz#12111f89a7fd2bd74965337d53806f4153d65dc6"
|
||||||
|
integrity sha512-RXaOH1EapmqbtRSMaHnwJWMfA6kiPipg/gN4cFOQRQKvrTQIA3K5+yUyzFuq8O7umIEtXUi1C1tf2dPvyyn44Q==
|
||||||
|
|
||||||
emoji-regex@^8.0.0:
|
emoji-regex@^8.0.0:
|
||||||
version "8.0.0"
|
version "8.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||||
|
Loading…
Reference in New Issue
Block a user