mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
[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",
|
||||
"name",
|
||||
"cover_image",
|
||||
"icon_prop",
|
||||
"emoji",
|
||||
"logo_props",
|
||||
"description",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
@ -1366,10 +1366,6 @@ class WorkspaceUserProfileEndpoint(BaseAPIView):
|
||||
)
|
||||
.values(
|
||||
"id",
|
||||
"name",
|
||||
"identifier",
|
||||
"emoji",
|
||||
"icon_prop",
|
||||
"created_issues",
|
||||
"assigned_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(
|
||||
default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]
|
||||
)
|
||||
logo_props = models.JSONField(default=dict)
|
||||
default_state = models.ForeignKey(
|
||||
"db.State",
|
||||
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 type {
|
||||
IProjectViewProps,
|
||||
IUser,
|
||||
IUserLite,
|
||||
IUserMemberLite,
|
||||
IWorkspace,
|
||||
IWorkspaceLite,
|
||||
TStateGroups,
|
||||
} from ".";
|
||||
|
||||
export type TProjectLogoProps = {
|
||||
in_use: "emoji" | "icon";
|
||||
emoji?: {
|
||||
value?: string;
|
||||
url?: string;
|
||||
};
|
||||
icon?: {
|
||||
name?: string;
|
||||
color?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export interface IProject {
|
||||
archive_in: number;
|
||||
close_in: number;
|
||||
@ -21,24 +35,13 @@ export interface IProject {
|
||||
default_assignee: IUser | string | null;
|
||||
default_state: string | null;
|
||||
description: string;
|
||||
emoji: string | null;
|
||||
emoji_and_icon:
|
||||
| string
|
||||
| {
|
||||
name: string;
|
||||
color: string;
|
||||
}
|
||||
| null;
|
||||
estimate: string | null;
|
||||
icon_prop: {
|
||||
name: string;
|
||||
color: string;
|
||||
} | null;
|
||||
id: string;
|
||||
identifier: string;
|
||||
is_deployed: boolean;
|
||||
is_favorite: boolean;
|
||||
is_member: boolean;
|
||||
logo_props: TProjectLogoProps;
|
||||
member_role: EUserProjectRoles | null;
|
||||
members: IProjectMemberLite[];
|
||||
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;
|
||||
completed_issues: number;
|
||||
created_issues: number;
|
||||
emoji: string | null;
|
||||
icon_prop: null;
|
||||
id: string;
|
||||
identifier: string;
|
||||
name: string;
|
||||
pending_issues: number;
|
||||
}[];
|
||||
user_data: {
|
||||
|
@ -23,6 +23,7 @@
|
||||
"@headlessui/react": "^1.7.17",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"clsx": "^2.0.0",
|
||||
"emoji-picker-react": "^4.5.16",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "^18.2.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";
|
||||
// helpers
|
||||
import { cn } from "../../helpers";
|
||||
|
||||
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
mode?: "primary" | "transparent" | "true-transparent";
|
||||
@ -16,17 +18,20 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
|
||||
ref={ref}
|
||||
type={type}
|
||||
name={name}
|
||||
className={`block rounded-md bg-transparent text-sm placeholder-custom-text-400 focus:outline-none ${
|
||||
mode === "primary"
|
||||
? "rounded-md border-[0.5px] border-custom-border-200"
|
||||
: mode === "transparent"
|
||||
? "rounded border-none bg-transparent ring-0 transition-all focus:ring-1 focus:ring-custom-primary"
|
||||
: 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" : ""
|
||||
} ${className}`}
|
||||
className={cn(
|
||||
`block rounded-md bg-transparent text-sm placeholder-custom-text-400 focus:outline-none ${
|
||||
mode === "primary"
|
||||
? "rounded-md border-[0.5px] border-custom-border-200"
|
||||
: mode === "transparent"
|
||||
? "rounded border-none bg-transparent ring-0 transition-all focus:ring-1 focus:ring-custom-primary"
|
||||
: 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" : ""
|
||||
}`,
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
@ -2,6 +2,7 @@ export * from "./avatar";
|
||||
export * from "./breadcrumbs";
|
||||
export * from "./badge";
|
||||
export * from "./button";
|
||||
export * from "./emoji";
|
||||
export * from "./dropdowns";
|
||||
export * from "./form-fields";
|
||||
export * from "./icons";
|
||||
|
@ -1 +1,2 @@
|
||||
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 Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
// import { NavbarSearch } from "./search";
|
||||
import { NavbarIssueBoardView } from "./issue-board-view";
|
||||
import { NavbarTheme } from "./theme";
|
||||
import { IssueFiltersDropdown } from "components/issues/filters";
|
||||
import { ProjectLogo } from "components/common";
|
||||
// ui
|
||||
import { Avatar, Button } from "@plane/ui";
|
||||
import { Briefcase } from "lucide-react";
|
||||
@ -19,18 +16,6 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
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 {
|
||||
project: projectStore,
|
||||
@ -123,27 +108,15 @@ const IssueNavbar = observer(() => {
|
||||
<div className="relative flex w-full items-center gap-4 px-5">
|
||||
{/* project detail */}
|
||||
<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?.emoji ? (
|
||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
||||
{renderEmoji(projectStore.project.emoji)}
|
||||
</span>
|
||||
) : projectStore.project?.icon_prop ? (
|
||||
<div className="grid h-7 w-7 flex-shrink-0 place-items-center">
|
||||
{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>
|
||||
{projectStore.project ? (
|
||||
<span className="h-7 w-7 flex-shrink-0 grid place-items-center">
|
||||
<ProjectLogo logo={projectStore.project.logo_props} className="text-lg" />
|
||||
</span>
|
||||
) : (
|
||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
||||
<Briefcase className="h-4 w-4" />
|
||||
</span>
|
||||
)}
|
||||
<div className="line-clamp-1 max-w-[300px] overflow-hidden text-lg font-medium">
|
||||
{projectStore?.project?.name || `...`}
|
||||
</div>
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { TProjectLogoProps } from "@plane/types";
|
||||
|
||||
export interface IWorkspace {
|
||||
id: string;
|
||||
name: string;
|
||||
@ -9,10 +11,8 @@ export interface IProject {
|
||||
identifier: string;
|
||||
name: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
cover_image: string | null;
|
||||
icon_prop: string | null;
|
||||
emoji: string | null;
|
||||
logo_props: TProjectLogoProps;
|
||||
}
|
||||
|
||||
export interface IProjectSettings {
|
||||
|
@ -3,9 +3,9 @@ import { observer } from "mobx-react-lite";
|
||||
// icons
|
||||
import { Contrast, LayoutGrid, Users } from "lucide-react";
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
import { useProject } from "hooks/store";
|
||||
import { ProjectLogo } from "components/project";
|
||||
|
||||
type Props = {
|
||||
projectIds: string[];
|
||||
@ -28,15 +28,9 @@ export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = observer((pro
|
||||
return (
|
||||
<div key={projectId} className="w-full">
|
||||
<div className="flex items-center gap-1 text-sm">
|
||||
{project.emoji ? (
|
||||
<span className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.emoji)}</span>
|
||||
) : project.icon_prop ? (
|
||||
<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>
|
||||
)}
|
||||
<div className="h-6 w-6 grid place-items-center">
|
||||
<ProjectLogo logo={project.logo_props} />
|
||||
</div>
|
||||
<h5 className="flex items-center gap-1">
|
||||
<p className="break-words">{truncateText(project.name, 20)}</p>
|
||||
<span className="ml-1 text-xs text-custom-text-200">({project.identifier})</span>
|
||||
|
@ -3,8 +3,9 @@ import { useRouter } from "next/router";
|
||||
// hooks
|
||||
import { NETWORK_CHOICES } from "constants/project";
|
||||
import { renderFormattedDate } from "helpers/date-time.helper";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { useCycle, useMember, useModule, useProject } from "hooks/store";
|
||||
// components
|
||||
import { ProjectLogo } from "components/project";
|
||||
// helpers
|
||||
// constants
|
||||
|
||||
@ -81,15 +82,9 @@ export const CustomAnalyticsSidebarHeader = observer(() => {
|
||||
) : (
|
||||
<div className="h-full overflow-y-auto">
|
||||
<div className="flex items-center gap-1">
|
||||
{projectDetails?.emoji ? (
|
||||
<div className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(projectDetails.emoji)}</div>
|
||||
) : projectDetails?.icon_prop ? (
|
||||
<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)}
|
||||
{projectDetails && (
|
||||
<span className="h-6 w-6 grid place-items-center flex-shrink-0">
|
||||
<ProjectLogo logo={projectDetails.logo_props} />
|
||||
</span>
|
||||
)}
|
||||
<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 { PROJECT_BACKGROUND_COLORS } from "constants/dashboard";
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { useApplication, useEventTracker, useDashboard, useProject, useUser } from "hooks/store";
|
||||
// components
|
||||
// ui
|
||||
// helpers
|
||||
// types
|
||||
import { TRecentProjectsWidgetResponse } from "@plane/types";
|
||||
import { ProjectLogo } from "components/project";
|
||||
// constants
|
||||
|
||||
const WIDGET_KEY = "recent_projects";
|
||||
@ -38,17 +38,9 @@ const ProjectListItem: React.FC<ProjectListItemProps> = observer((props) => {
|
||||
<div
|
||||
className={`h-[3.375rem] w-[3.375rem] grid place-items-center rounded border border-transparent flex-shrink-0 ${randomBgColor}`}
|
||||
>
|
||||
{projectDetails.emoji ? (
|
||||
<span className="grid h-7 w-7 flex-shrink-0 text-2xl place-items-center rounded uppercase">
|
||||
{renderEmoji(projectDetails.emoji)}
|
||||
</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 className="h-7 w-7 grid place-items-center">
|
||||
<ProjectLogo logo={projectDetails.logo_props} className="text-xl" />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
|
@ -5,12 +5,12 @@ import { Combobox } from "@headlessui/react";
|
||||
import { Check, ChevronDown, Search } from "lucide-react";
|
||||
// hooks
|
||||
import { cn } from "helpers/common.helper";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { useProject } from "hooks/store";
|
||||
import { useDropdownKeyDown } from "hooks/use-dropdown-key-down";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// components
|
||||
import { DropdownButton } from "./buttons";
|
||||
import { ProjectLogo } from "components/project";
|
||||
// helpers
|
||||
// types
|
||||
import { BUTTON_VARIANTS_WITH_TEXT } from "./constants";
|
||||
@ -77,13 +77,11 @@ export const ProjectDropdown: React.FC<Props> = observer((props) => {
|
||||
query: `${projectDetails?.name}`,
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="grid place-items-center flex-shrink-0">
|
||||
{projectDetails?.emoji
|
||||
? renderEmoji(projectDetails?.emoji)
|
||||
: projectDetails?.icon_prop
|
||||
? renderEmoji(projectDetails?.icon_prop)
|
||||
: null}
|
||||
</span>
|
||||
{projectDetails && (
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={projectDetails?.logo_props} className="text-sm" />
|
||||
</span>
|
||||
)}
|
||||
<span className="flex-grow truncate">{projectDetails?.name}</span>
|
||||
</div>
|
||||
),
|
||||
@ -169,13 +167,9 @@ export const ProjectDropdown: React.FC<Props> = observer((props) => {
|
||||
showTooltip={showTooltip}
|
||||
variant={buttonVariant}
|
||||
>
|
||||
{!hideIcon && (
|
||||
<span className="grid place-items-center flex-shrink-0">
|
||||
{selectedProject?.emoji
|
||||
? renderEmoji(selectedProject?.emoji)
|
||||
: selectedProject?.icon_prop
|
||||
? renderEmoji(selectedProject?.icon_prop)
|
||||
: null}
|
||||
{!hideIcon && selectedProject && (
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={selectedProject.logo_props} className="text-sm" />
|
||||
</span>
|
||||
)}
|
||||
{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 { EUserProjectRoles } from "constants/project";
|
||||
import { cn } from "helpers/common.helper";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
import {
|
||||
useApplication,
|
||||
@ -33,6 +32,7 @@ import useLocalStorage from "hooks/use-local-storage";
|
||||
// helpers
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
||||
import { ProjectLogo } from "components/project";
|
||||
// constants
|
||||
|
||||
const CycleDropdownOption: React.FC<{ cycleId: string }> = ({ cycleId }) => {
|
||||
@ -163,13 +163,9 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
label={currentProjectDetails?.name ?? "Project"}
|
||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||
icon={
|
||||
currentProjectDetails?.emoji ? (
|
||||
renderEmoji(currentProjectDetails.emoji)
|
||||
) : currentProjectDetails?.icon_prop ? (
|
||||
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)}
|
||||
currentProjectDetails && (
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@ -209,7 +205,9 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
||||
className="ml-1.5 flex-shrink-0"
|
||||
placement="bottom-start"
|
||||
>
|
||||
{currentProjectCycleIds?.map((cycleId) => <CycleDropdownOption key={cycleId} cycleId={cycleId} />)}
|
||||
{currentProjectCycleIds?.map((cycleId) => (
|
||||
<CycleDropdownOption key={cycleId} cycleId={cycleId} />
|
||||
))}
|
||||
</CustomMenu>
|
||||
}
|
||||
/>
|
||||
|
@ -11,14 +11,15 @@ import { BreadcrumbLink } from "components/common";
|
||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||
import { CYCLE_VIEW_LAYOUTS } from "constants/cycle";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { useApplication, useEventTracker, useProject, useUser } from "hooks/store";
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
import { TCycleLayout } from "@plane/types";
|
||||
import { ProjectLogo } from "components/project";
|
||||
|
||||
export const CyclesHeader: FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// store hooks
|
||||
const {
|
||||
commandPalette: { toggleCreateCycleModal },
|
||||
@ -32,9 +33,6 @@ export const CyclesHeader: FC = observer(() => {
|
||||
const canUserCreateCycle =
|
||||
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
|
||||
|
||||
const { workspaceSlug } = router.query as {
|
||||
workspaceSlug: string;
|
||||
};
|
||||
const { setValue: setCycleLayout } = useLocalStorage<TCycleLayout>("cycle_layout", "list");
|
||||
|
||||
const handleCurrentLayout = useCallback(
|
||||
@ -58,13 +56,9 @@ export const CyclesHeader: FC = observer(() => {
|
||||
label={currentProjectDetails?.name ?? "Project"}
|
||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||
icon={
|
||||
currentProjectDetails?.emoji ? (
|
||||
renderEmoji(currentProjectDetails.emoji)
|
||||
) : currentProjectDetails?.icon_prop ? (
|
||||
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)}
|
||||
currentProjectDetails && (
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||
</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 { EUserProjectRoles } from "constants/project";
|
||||
import { cn } from "helpers/common.helper";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
import {
|
||||
useApplication,
|
||||
@ -33,6 +32,7 @@ import useLocalStorage from "hooks/use-local-storage";
|
||||
// helpers
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
||||
import { ProjectLogo } from "components/project";
|
||||
// constants
|
||||
|
||||
const ModuleDropdownOption: React.FC<{ moduleId: string }> = ({ moduleId }) => {
|
||||
@ -64,11 +64,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, moduleId } = router.query as {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
moduleId: string;
|
||||
};
|
||||
const { workspaceSlug, projectId, moduleId } = router.query;
|
||||
// store hooks
|
||||
const {
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
@ -100,7 +96,13 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
const handleLayoutChange = useCallback(
|
||||
(layout: TIssueLayouts) => {
|
||||
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]
|
||||
);
|
||||
@ -119,7 +121,13 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
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]
|
||||
);
|
||||
@ -127,7 +135,13 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
const handleDisplayFilters = useCallback(
|
||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||
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]
|
||||
);
|
||||
@ -135,7 +149,13 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
const handleDisplayProperties = useCallback(
|
||||
(property: Partial<IIssueDisplayProperties>) => {
|
||||
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]
|
||||
);
|
||||
@ -166,13 +186,9 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
label={currentProjectDetails?.name ?? "Project"}
|
||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||
icon={
|
||||
currentProjectDetails?.emoji ? (
|
||||
renderEmoji(currentProjectDetails.emoji)
|
||||
) : currentProjectDetails?.icon_prop ? (
|
||||
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)}
|
||||
currentProjectDetails && (
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@ -212,7 +228,9 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
||||
className="ml-1.5 flex-shrink-0"
|
||||
placement="bottom-start"
|
||||
>
|
||||
{projectModuleIds?.map((moduleId) => <ModuleDropdownOption key={moduleId} moduleId={moduleId} />)}
|
||||
{projectModuleIds?.map((moduleId) => (
|
||||
<ModuleDropdownOption key={moduleId} moduleId={moduleId} />
|
||||
))}
|
||||
</CustomMenu>
|
||||
}
|
||||
/>
|
||||
|
@ -10,11 +10,10 @@ import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-ham
|
||||
// constants
|
||||
import { MODULE_VIEW_LAYOUTS } from "constants/module";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
// helper
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// hooks
|
||||
import { useApplication, useEventTracker, useProject, useUser } from "hooks/store";
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
import { ProjectLogo } from "components/project";
|
||||
|
||||
export const ModulesListHeader: React.FC = observer(() => {
|
||||
// router
|
||||
@ -46,13 +45,9 @@ export const ModulesListHeader: React.FC = observer(() => {
|
||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||
label={currentProjectDetails?.name ?? "Project"}
|
||||
icon={
|
||||
currentProjectDetails?.emoji ? (
|
||||
renderEmoji(currentProjectDetails.emoji)
|
||||
) : currentProjectDetails?.icon_prop ? (
|
||||
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)}
|
||||
currentProjectDetails && (
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ import { Breadcrumbs, Button } from "@plane/ui";
|
||||
// helpers
|
||||
import { BreadcrumbLink } from "components/common";
|
||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// components
|
||||
import { useApplication, usePage, useProject } from "hooks/store";
|
||||
import { ProjectLogo } from "components/project";
|
||||
|
||||
export interface IPagesHeaderProps {
|
||||
showButton?: boolean;
|
||||
@ -42,13 +42,9 @@ export const PageDetailsHeader: FC<IPagesHeaderProps> = observer((props) => {
|
||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||
label={currentProjectDetails?.name ?? "Project"}
|
||||
icon={
|
||||
currentProjectDetails?.emoji ? (
|
||||
renderEmoji(currentProjectDetails.emoji)
|
||||
) : currentProjectDetails?.icon_prop ? (
|
||||
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)}
|
||||
currentProjectDetails && (
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
@ -8,10 +8,10 @@ import { Breadcrumbs, Button } from "@plane/ui";
|
||||
import { BreadcrumbLink } from "components/common";
|
||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// constants
|
||||
// components
|
||||
import { useApplication, useEventTracker, useProject, useUser } from "hooks/store";
|
||||
import { ProjectLogo } from "components/project";
|
||||
|
||||
export const PagesHeader = observer(() => {
|
||||
// router
|
||||
@ -43,13 +43,9 @@ export const PagesHeader = observer(() => {
|
||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||
label={currentProjectDetails?.name ?? "Project"}
|
||||
icon={
|
||||
currentProjectDetails?.emoji ? (
|
||||
renderEmoji(currentProjectDetails.emoji)
|
||||
) : currentProjectDetails?.icon_prop ? (
|
||||
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)}
|
||||
currentProjectDetails && (
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
@ -7,8 +7,9 @@ import { Breadcrumbs, LayersIcon } from "@plane/ui";
|
||||
import { BreadcrumbLink } from "components/common";
|
||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { useProject } from "hooks/store";
|
||||
// components
|
||||
import { ProjectLogo } from "components/project";
|
||||
// ui
|
||||
// types
|
||||
import { IssueArchiveService } from "services/issue";
|
||||
@ -52,13 +53,9 @@ export const ProjectArchivedIssueDetailsHeader: FC = observer(() => {
|
||||
href={`/${workspaceSlug}/projects`}
|
||||
label={currentProjectDetails?.name ?? "Project"}
|
||||
icon={
|
||||
currentProjectDetails?.emoji ? (
|
||||
renderEmoji(currentProjectDetails.emoji)
|
||||
) : currentProjectDetails?.icon_prop ? (
|
||||
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)}
|
||||
currentProjectDetails && (
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
@ -12,10 +12,10 @@ import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-ham
|
||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "components/issues";
|
||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { useIssues, useLabel, useMember, useProject, useProjectState } from "hooks/store";
|
||||
// types
|
||||
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
import { ProjectLogo } from "components/project";
|
||||
|
||||
export const ProjectArchivedIssuesHeader: FC = observer(() => {
|
||||
// router
|
||||
@ -91,13 +91,9 @@ export const ProjectArchivedIssuesHeader: FC = observer(() => {
|
||||
href={`/${workspaceSlug}/projects`}
|
||||
label={currentProjectDetails?.name ?? "Project"}
|
||||
icon={
|
||||
currentProjectDetails?.emoji ? (
|
||||
renderEmoji(currentProjectDetails.emoji)
|
||||
) : currentProjectDetails?.icon_prop ? (
|
||||
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)}
|
||||
currentProjectDetails && (
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
@ -10,9 +10,9 @@ import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelect
|
||||
// ui
|
||||
// helper
|
||||
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 { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
||||
import { ProjectLogo } from "components/project";
|
||||
|
||||
export const ProjectDraftIssueHeader: FC = observer(() => {
|
||||
// router
|
||||
@ -86,13 +86,9 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
|
||||
href={`/${workspaceSlug}/projects`}
|
||||
label={currentProjectDetails?.name ?? "Project"}
|
||||
icon={
|
||||
currentProjectDetails?.emoji ? (
|
||||
renderEmoji(currentProjectDetails.emoji)
|
||||
) : currentProjectDetails?.icon_prop ? (
|
||||
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)}
|
||||
currentProjectDetails && (
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ import { BreadcrumbLink } from "components/common";
|
||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||
import { CreateInboxIssueModal } from "components/inbox";
|
||||
// helper
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { useProject } from "hooks/store";
|
||||
import { ProjectLogo } from "components/project";
|
||||
|
||||
export const ProjectInboxHeader: FC = observer(() => {
|
||||
// states
|
||||
@ -35,13 +35,9 @@ export const ProjectInboxHeader: FC = observer(() => {
|
||||
href={`/${workspaceSlug}/projects`}
|
||||
label={currentProjectDetails?.name ?? "Project"}
|
||||
icon={
|
||||
currentProjectDetails?.emoji ? (
|
||||
renderEmoji(currentProjectDetails.emoji)
|
||||
) : currentProjectDetails?.icon_prop ? (
|
||||
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)}
|
||||
currentProjectDetails && (
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
@ -9,12 +9,12 @@ import { BreadcrumbLink } from "components/common";
|
||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
||||
import { cn } from "helpers/common.helper";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { useApplication, useProject } from "hooks/store";
|
||||
// ui
|
||||
// helpers
|
||||
// services
|
||||
import { IssueService } from "services/issue";
|
||||
import { ProjectLogo } from "components/project";
|
||||
// constants
|
||||
// components
|
||||
|
||||
@ -51,13 +51,9 @@ export const ProjectIssueDetailsHeader: FC = observer(() => {
|
||||
href={`/${workspaceSlug}/projects`}
|
||||
label={currentProjectDetails?.name ?? "Project"}
|
||||
icon={
|
||||
currentProjectDetails?.emoji ? (
|
||||
renderEmoji(currentProjectDetails.emoji)
|
||||
) : currentProjectDetails?.icon_prop ? (
|
||||
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)}
|
||||
currentProjectDetails && (
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelect
|
||||
import { IssuesMobileHeader } from "components/issues/issues-mobile-header";
|
||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import {
|
||||
useApplication,
|
||||
useEventTracker,
|
||||
@ -21,11 +20,12 @@ import {
|
||||
useUser,
|
||||
useMember,
|
||||
} from "hooks/store";
|
||||
import { useIssues } from "hooks/store/use-issues";
|
||||
// components
|
||||
// ui
|
||||
// types
|
||||
import { useIssues } from "hooks/store/use-issues";
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
||||
import { ProjectLogo } from "components/project";
|
||||
// constants
|
||||
// helper
|
||||
|
||||
@ -123,17 +123,9 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
||||
label={currentProjectDetails?.name ?? "Project"}
|
||||
icon={
|
||||
currentProjectDetails ? (
|
||||
currentProjectDetails?.emoji ? (
|
||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
||||
{renderEmoji(currentProjectDetails.emoji)}
|
||||
</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)}
|
||||
currentProjectDetails && (
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||
</span>
|
||||
)
|
||||
) : (
|
||||
|
@ -7,9 +7,9 @@ import { Breadcrumbs, CustomMenu } from "@plane/ui";
|
||||
import { BreadcrumbLink } from "components/common";
|
||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||
import { EUserProjectRoles, PROJECT_SETTINGS_LINKS } from "constants/project";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// hooks
|
||||
import { useProject, useUser } from "hooks/store";
|
||||
import { ProjectLogo } from "components/project";
|
||||
// constants
|
||||
// components
|
||||
|
||||
@ -44,13 +44,9 @@ export const ProjectSettingHeader: FC<IProjectSettingHeader> = observer((props)
|
||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||
label={currentProjectDetails?.name ?? "Project"}
|
||||
icon={
|
||||
currentProjectDetails?.emoji ? (
|
||||
renderEmoji(currentProjectDetails.emoji)
|
||||
) : currentProjectDetails?.icon_prop ? (
|
||||
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)}
|
||||
currentProjectDetails && (
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelect
|
||||
// constants
|
||||
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
import {
|
||||
useApplication,
|
||||
@ -29,6 +28,7 @@ import {
|
||||
useUser,
|
||||
} from "hooks/store";
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
||||
import { ProjectLogo } from "components/project";
|
||||
|
||||
export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
// router
|
||||
@ -119,17 +119,9 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||
label={currentProjectDetails?.name ?? "Project"}
|
||||
icon={
|
||||
currentProjectDetails?.emoji ? (
|
||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
||||
{renderEmoji(currentProjectDetails.emoji)}
|
||||
</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)}
|
||||
currentProjectDetails && (
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ import { BreadcrumbLink } from "components/common";
|
||||
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
|
||||
// helpers
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// constants
|
||||
import { useApplication, useProject, useUser } from "hooks/store";
|
||||
import { ProjectLogo } from "components/project";
|
||||
|
||||
export const ProjectViewsHeader: React.FC = observer(() => {
|
||||
// router
|
||||
@ -42,17 +42,9 @@ export const ProjectViewsHeader: React.FC = observer(() => {
|
||||
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
|
||||
label={currentProjectDetails?.name ?? "Project"}
|
||||
icon={
|
||||
currentProjectDetails?.emoji ? (
|
||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
||||
{renderEmoji(currentProjectDetails.emoji)}
|
||||
</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)}
|
||||
currentProjectDetails && (
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { X } from "lucide-react";
|
||||
// hooks
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { useProject } from "hooks/store";
|
||||
// helpers
|
||||
// components
|
||||
import { ProjectLogo } from "components/project";
|
||||
|
||||
type Props = {
|
||||
handleRemove: (val: string) => void;
|
||||
@ -25,15 +25,9 @@ export const AppliedProjectFilters: React.FC<Props> = observer((props) => {
|
||||
|
||||
return (
|
||||
<div key={projectId} className="flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
|
||||
{projectDetails.emoji ? (
|
||||
<span className="grid flex-shrink-0 place-items-center">{renderEmoji(projectDetails.emoji)}</span>
|
||||
) : projectDetails.icon_prop ? (
|
||||
<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="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={projectDetails.logo_props} className="text-sm" />
|
||||
</span>
|
||||
<span className="normal-case">{projectDetails.name}</span>
|
||||
{editable && (
|
||||
<button
|
||||
|
@ -4,8 +4,9 @@ import { observer } from "mobx-react";
|
||||
import { Loader } from "@plane/ui";
|
||||
import { FilterHeader, FilterOption } from "components/issues";
|
||||
// hooks
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { useProject } from "hooks/store";
|
||||
// components
|
||||
import { ProjectLogo } from "components/project";
|
||||
// ui
|
||||
// helpers
|
||||
|
||||
@ -52,19 +53,9 @@ export const FilterProjects: React.FC<Props> = observer((props) => {
|
||||
isChecked={appliedFilters?.includes(project.id) ? true : false}
|
||||
onClick={() => handleUpdate(project.id)}
|
||||
icon={
|
||||
project.emoji ? (
|
||||
<span className="grid flex-shrink-0 place-items-center text-sm">
|
||||
{renderEmoji(project.emoji)}
|
||||
</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>
|
||||
)
|
||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
||||
<ProjectLogo logo={project.logo_props} className="text-sm" />
|
||||
</span>
|
||||
}
|
||||
title={project.name}
|
||||
/>
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { ContrastIcon } from "lucide-react";
|
||||
import { Avatar, CycleGroupIcon, DiceIcon, PriorityIcon, StateGroupIcon } from "@plane/ui";
|
||||
// components
|
||||
import { ProjectLogo } from "components/project";
|
||||
// stores
|
||||
import { ISSUE_PRIORITIES } from "constants/issue";
|
||||
import { STATE_GROUPS } from "constants/state";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { ICycleStore } from "store/cycle.store";
|
||||
import { ILabelStore } from "store/label.store";
|
||||
import { IMemberRootStore } from "store/member";
|
||||
@ -62,7 +63,11 @@ const getProjectColumns = (project: IProjectStore): IGroupByColumn[] | undefined
|
||||
return {
|
||||
id: project.id,
|
||||
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 },
|
||||
};
|
||||
}) as any;
|
||||
|
@ -13,26 +13,28 @@ import { Loader, Tooltip } from "@plane/ui";
|
||||
import { USER_PROFILE_PROJECT_SEGREGATION } from "constants/fetch-keys";
|
||||
// helpers
|
||||
import { renderFormattedDate } from "helpers/date-time.helper";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// hooks
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
import { useApplication, useProject, useUser } from "hooks/store";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// services
|
||||
import { UserService } from "services/user.service";
|
||||
// components
|
||||
import { ProfileSidebarTime } from "./time";
|
||||
import { ProjectLogo } from "components/project";
|
||||
|
||||
// services
|
||||
const userService = new UserService();
|
||||
|
||||
export const ProfileSidebar = observer(() => {
|
||||
// refs
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, userId } = router.query;
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
const { theme: themeStore } = useApplication();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
const { data: userProjectsData } = useSWR(
|
||||
workspaceSlug && userId ? USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString()) : null,
|
||||
@ -130,6 +132,8 @@ export const ProfileSidebar = observer(() => {
|
||||
</div>
|
||||
<div className="mt-9 divide-y divide-custom-border-100">
|
||||
{userProjectsData.project_data.map((project, index) => {
|
||||
const projectDetails = getProjectById(project.id);
|
||||
|
||||
const totalIssues =
|
||||
project.created_issues + project.assigned_issues + project.pending_issues + project.completed_issues;
|
||||
|
||||
@ -138,26 +142,18 @@ export const ProfileSidebar = observer(() => {
|
||||
? 0
|
||||
: Math.round((project.completed_issues / project.assigned_issues) * 100);
|
||||
|
||||
if (!projectDetails) return null;
|
||||
|
||||
return (
|
||||
<Disclosure key={project.id} as="div" className={`${index === 0 ? "pb-3" : "py-3"}`}>
|
||||
{({ open }) => (
|
||||
<div className="w-full">
|
||||
<Disclosure.Button className="flex w-full items-center justify-between gap-2">
|
||||
<div className="flex w-3/4 items-center gap-2">
|
||||
{project.emoji ? (
|
||||
<div className="grid h-7 w-7 flex-shrink-0 place-items-center">
|
||||
{renderEmoji(project.emoji)}
|
||||
</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>
|
||||
<span className="grid place-items-center flex-shrink-0 h-7 w-7">
|
||||
<ProjectLogo logo={projectDetails.logo_props} />
|
||||
</span>
|
||||
<div className="truncate break-words text-sm font-medium">{projectDetails.name}</div>
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
{project.assigned_issues > 0 && (
|
||||
|
@ -6,14 +6,14 @@ import { LinkIcon, Lock, Pencil, Star } from "lucide-react";
|
||||
// ui
|
||||
import { Avatar, AvatarGroup, Button, Tooltip, TOAST_TYPE, setToast, setPromiseToast } from "@plane/ui";
|
||||
// components
|
||||
import { DeleteProjectModal, JoinProjectModal, EUserProjectRoles } from "components/project";
|
||||
import { DeleteProjectModal, JoinProjectModal, ProjectLogo } from "components/project";
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { copyTextToClipboard } from "helpers/string.helper";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// types
|
||||
import type { IProject } from "@plane/types";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
// constants
|
||||
|
||||
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="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">
|
||||
<span className="flex items-center justify-center">
|
||||
{project.emoji
|
||||
? renderEmoji(project.emoji)
|
||||
: project.icon_prop
|
||||
? renderEmoji(project.icon_prop)
|
||||
: null}
|
||||
<div className="flex item-center justify-center h-9 w-9 flex-shrink-0 rounded bg-white/90">
|
||||
<span className="grid place-items-center">
|
||||
<ProjectLogo logo={project.logo_props} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
@ -4,19 +4,30 @@ import { useForm, Controller } from "react-hook-form";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { X } from "lucide-react";
|
||||
// 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
|
||||
import { ImagePickerPopover } from "components/core";
|
||||
import { MemberDropdown } from "components/dropdowns";
|
||||
import EmojiIconPicker from "components/emoji-icon-picker";
|
||||
// constants
|
||||
import { PROJECT_CREATED } from "constants/event-tracker";
|
||||
import { NETWORK_CHOICES, PROJECT_UNSPLASH_COVERS } from "constants/project";
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
// helpers
|
||||
import { getRandomEmoji, renderEmoji } from "helpers/emoji.helper";
|
||||
import { convertHexEmojiToDecimal, getRandomEmoji } from "helpers/emoji.helper";
|
||||
// hooks
|
||||
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 = {
|
||||
isOpen: boolean;
|
||||
@ -29,6 +40,21 @@ interface IIsGuestCondition {
|
||||
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 }) => {
|
||||
useEffect(() => {
|
||||
onClose();
|
||||
@ -42,19 +68,6 @@ const IsGuestCondition: FC<IIsGuestCondition> = ({ onClose }) => {
|
||||
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) => {
|
||||
const { isOpen, onClose, setToFavorite = false, workspaceSlug } = props;
|
||||
// store
|
||||
@ -66,7 +79,6 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||
// states
|
||||
const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true);
|
||||
// form info
|
||||
const cover_image = PROJECT_UNSPLASH_COVERS[Math.floor(Math.random() * PROJECT_UNSPLASH_COVERS.length)];
|
||||
const {
|
||||
formState: { errors, isSubmitting },
|
||||
handleSubmit,
|
||||
@ -74,28 +86,20 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||
control,
|
||||
watch,
|
||||
setValue,
|
||||
} = useForm<ICreateProjectForm>({
|
||||
defaultValues: {
|
||||
cover_image,
|
||||
description: "",
|
||||
emoji_and_icon: getRandomEmoji(),
|
||||
identifier: "",
|
||||
name: "",
|
||||
network: 2,
|
||||
project_lead: undefined,
|
||||
},
|
||||
} = useForm<IProject>({
|
||||
defaultValues,
|
||||
reValidateMode: "onChange",
|
||||
});
|
||||
|
||||
const currentNetwork = NETWORK_CHOICES.find((n) => n.key === watch("network"));
|
||||
|
||||
if (currentWorkspaceRole && isOpen)
|
||||
if (currentWorkspaceRole < EUserWorkspaceRoles.MEMBER) return <IsGuestCondition onClose={onClose} />;
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
setIsChangeInIdentifierRequired(true);
|
||||
reset();
|
||||
setTimeout(() => {
|
||||
reset();
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const handleAddToFavorites = (projectId: string) => {
|
||||
@ -110,18 +114,11 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = async (formData: ICreateProjectForm) => {
|
||||
// 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;
|
||||
const onSubmit = async (formData: Partial<IProject>) => {
|
||||
// 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) => {
|
||||
const newPayload = {
|
||||
...res,
|
||||
@ -151,7 +148,7 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||
captureProjectEvent({
|
||||
eventName: PROJECT_CREATED,
|
||||
payload: {
|
||||
...payload,
|
||||
...formData,
|
||||
state: "FAILED",
|
||||
},
|
||||
});
|
||||
@ -165,13 +162,13 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
};
|
||||
|
||||
const handleIdentifierChange = (onChange: any) => (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const { value } = e.target;
|
||||
const alphanumericValue = value.replace(/[^ÇŞĞIİÖÜA-Za-z0-9]/g, "");
|
||||
const alphanumericValue = projectIdentifierSanitizer(value);
|
||||
setIsChangeInIdentifierRequired(false);
|
||||
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">
|
||||
<div className="group relative h-44 w-full rounded-lg bg-custom-background-80">
|
||||
{watch("cover_image") !== null && (
|
||||
{watch("cover_image") && (
|
||||
<img
|
||||
src={watch("cover_image")!}
|
||||
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>
|
||||
</div>
|
||||
<div className="absolute bottom-2 right-2">
|
||||
<ImagePickerPopover
|
||||
label="Change Cover"
|
||||
onChange={(image) => {
|
||||
setValue("cover_image", image);
|
||||
}}
|
||||
<Controller
|
||||
name="cover_image"
|
||||
control={control}
|
||||
value={watch("cover_image")}
|
||||
tabIndex={9}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<ImagePickerPopover
|
||||
label="Change Cover"
|
||||
onChange={onChange}
|
||||
control={control}
|
||||
value={value}
|
||||
tabIndex={9}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute -bottom-[22px] left-3">
|
||||
<Controller
|
||||
name="emoji_and_icon"
|
||||
name="logo_props"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<EmojiIconPicker
|
||||
<CustomEmojiIconPicker
|
||||
label={
|
||||
<div className="grid h-[44px] w-[44px] place-items-center rounded-md bg-custom-background-80 text-lg outline-none">
|
||||
{value ? renderEmoji(value) : "Icon"}
|
||||
</div>
|
||||
<span className="grid h-11 w-11 place-items-center rounded-md bg-custom-background-80">
|
||||
<ProjectLogo logo={value} className="text-xl" />
|
||||
</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>
|
||||
<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 className="md:col-span-4">
|
||||
<Controller
|
||||
@ -336,57 +357,65 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||
<Controller
|
||||
name="network"
|
||||
control={control}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<div className="flex-shrink-0" tabIndex={4}>
|
||||
<CustomSelect
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
label={
|
||||
<div className="flex items-center gap-1">
|
||||
{currentNetwork ? (
|
||||
<>
|
||||
<currentNetwork.icon className="h-3 w-3" />
|
||||
{currentNetwork.label}
|
||||
</>
|
||||
) : (
|
||||
<span className="text-custom-text-400">Select Network</span>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
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>
|
||||
render={({ field: { onChange, value } }) => {
|
||||
const currentNetwork = NETWORK_CHOICES.find((n) => n.key === value);
|
||||
|
||||
return (
|
||||
<div className="flex-shrink-0" tabIndex={4}>
|
||||
<CustomSelect
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
label={
|
||||
<div className="flex items-center gap-1">
|
||||
{currentNetwork ? (
|
||||
<>
|
||||
<currentNetwork.icon className="h-3 w-3" />
|
||||
{currentNetwork.label}
|
||||
</>
|
||||
) : (
|
||||
<span className="text-custom-text-400">Select network</span>
|
||||
)}
|
||||
</div>
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
</div>
|
||||
)}
|
||||
}
|
||||
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>
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Controller
|
||||
name="project_lead_member"
|
||||
name="project_lead"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<div className="h-7 flex-shrink-0" tabIndex={5}>
|
||||
<MemberDropdown
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Lead"
|
||||
multiple={false}
|
||||
buttonVariant="border-with-text"
|
||||
tabIndex={5}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
render={({ field: { value, onChange } }) => {
|
||||
if (value === undefined || value === null || typeof value === "string")
|
||||
return (
|
||||
<div className="h-7 flex-shrink-0" tabIndex={5}>
|
||||
<MemberDropdown
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Lead"
|
||||
multiple={false}
|
||||
buttonVariant="border-with-text"
|
||||
tabIndex={5}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
else return <></>;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -396,7 +425,7 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" type="submit" size="sm" loading={isSubmitting} tabIndex={7}>
|
||||
{isSubmitting ? "Creating..." : "Create Project"}
|
||||
{isSubmitting ? "Creating" : "Create project"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -3,22 +3,31 @@ import { Controller, useForm } from "react-hook-form";
|
||||
// icons
|
||||
import { Lock } from "lucide-react";
|
||||
// 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
|
||||
import { ImagePickerPopover } from "components/core";
|
||||
import EmojiIconPicker from "components/emoji-icon-picker";
|
||||
// constants
|
||||
import { PROJECT_UPDATED } from "constants/event-tracker";
|
||||
import { NETWORK_CHOICES } from "constants/project";
|
||||
// helpers
|
||||
import { renderFormattedDate } from "helpers/date-time.helper";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useProject } from "hooks/store";
|
||||
// services
|
||||
import { ProjectService } from "services/project";
|
||||
// types
|
||||
import { IProject, IWorkspace } from "@plane/types";
|
||||
import { ProjectLogo } from "./project-logo";
|
||||
import { convertHexEmojiToDecimal } from "helpers/emoji.helper";
|
||||
export interface IProjectDetailsForm {
|
||||
project: IProject;
|
||||
workspaceSlug: string;
|
||||
@ -46,7 +55,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
} = useForm<IProject>({
|
||||
defaultValues: {
|
||||
...project,
|
||||
emoji_and_icon: project.emoji ?? project.icon_prop,
|
||||
workspace: (project.workspace as IWorkspace).id,
|
||||
},
|
||||
});
|
||||
@ -55,7 +63,6 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
if (project && projectId !== getValues("id")) {
|
||||
reset({
|
||||
...project,
|
||||
emoji_and_icon: project.emoji ?? project.icon_prop,
|
||||
workspace: (project.workspace as IWorkspace).id,
|
||||
});
|
||||
}
|
||||
@ -109,14 +116,9 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
identifier: formData.identifier,
|
||||
description: formData.description,
|
||||
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)
|
||||
await projectService
|
||||
.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="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="grid h-7 w-7 place-items-center">
|
||||
<Controller
|
||||
control={control}
|
||||
name="emoji_and_icon"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<EmojiIconPicker
|
||||
label={value ? renderEmoji(value) : "Icon"}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="logo_props"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<CustomEmojiIconPicker
|
||||
label={
|
||||
<span className="grid h-7 w-7 place-items-center">
|
||||
<ProjectLogo logo={value} className="text-lg" />
|
||||
</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}
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 truncate text-white">
|
||||
<span className="truncate text-lg font-semibold">{watch("name")}</span>
|
||||
|
@ -14,6 +14,7 @@ export * from "./sidebar-list";
|
||||
export * from "./integration-card";
|
||||
export * from "./member-list";
|
||||
export * from "./member-list-item";
|
||||
export * from "./project-logo";
|
||||
export * from "./project-settings-member-defaults";
|
||||
export * from "./send-project-invitation-modal";
|
||||
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,
|
||||
setPromiseToast,
|
||||
} from "@plane/ui";
|
||||
import { LeaveProjectModal, PublishProjectModal } from "components/project";
|
||||
import { LeaveProjectModal, ProjectLogo, PublishProjectModal } from "components/project";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { cn } from "helpers/common.helper";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { getNumberCount } from "helpers/string.helper";
|
||||
// hooks
|
||||
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 [publishModalOpen, setPublishModal] = useState(false);
|
||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||
// refs
|
||||
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId: URLProjectId } = router.query;
|
||||
// derived values
|
||||
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 isViewerOrGuest =
|
||||
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 = () => {
|
||||
if (!workspaceSlug || !project) return;
|
||||
|
||||
@ -178,9 +175,13 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
||||
{({ open }) => (
|
||||
<>
|
||||
<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 ${
|
||||
snapshot?.isDragging ? "opacity-60" : ""
|
||||
} ${isMenuActive ? "!bg-custom-sidebar-background-80" : ""}`}
|
||||
className={cn(
|
||||
"group relative flex w-full items-center rounded-md px-2 py-1 text-custom-sidebar-text-100 hover:bg-custom-sidebar-background-80",
|
||||
{
|
||||
"opacity-60": snapshot?.isDragging,
|
||||
"bg-custom-sidebar-background-80": isMenuActive,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{provided && !disableDrag && (
|
||||
<Tooltip
|
||||
@ -189,11 +190,14 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
||||
>
|
||||
<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 ${
|
||||
isCollapsed ? "" : "group-hover:!flex"
|
||||
} ${project.sort_order === null ? "cursor-not-allowed opacity-60" : ""} ${
|
||||
isMenuActive ? "!flex" : ""
|
||||
}`}
|
||||
className={cn(
|
||||
"absolute -left-2.5 top-1/2 hidden -translate-y-1/2 rounded p-0.5 text-custom-sidebar-text-400",
|
||||
{
|
||||
"group-hover:flex": !isCollapsed,
|
||||
"cursor-not-allowed opacity-60": project.sort_order === null,
|
||||
flex: isMenuActive,
|
||||
}
|
||||
)}
|
||||
{...provided?.dragHandleProps}
|
||||
>
|
||||
<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}>
|
||||
<Disclosure.Button
|
||||
as="div"
|
||||
className={`flex flex-grow cursor-pointer select-none items-center truncate text-left text-sm font-medium ${
|
||||
isCollapsed ? "justify-center" : `justify-between`
|
||||
}`}
|
||||
className={cn(
|
||||
"flex items-center justify-between flex-grow cursor-pointer select-none truncate text-left text-sm font-medium",
|
||||
{
|
||||
"justify-center": isCollapsed,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={`flex w-full flex-grow items-center gap-x-2 truncate ${
|
||||
isCollapsed ? "justify-center" : ""
|
||||
}`}
|
||||
className={cn("w-full flex-grow flex items-center gap-1 truncate", {
|
||||
"justify-center": isCollapsed,
|
||||
})}
|
||||
>
|
||||
{project.emoji ? (
|
||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
||||
{renderEmoji(project.emoji)}
|
||||
</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>
|
||||
)}
|
||||
|
||||
<div className="h-7 w-7 grid place-items-center">
|
||||
<ProjectLogo logo={project.logo_props} />
|
||||
</div>
|
||||
{!isCollapsed && <p className="truncate text-custom-sidebar-text-200">{project.name}</p>}
|
||||
</div>
|
||||
{!isCollapsed && (
|
||||
<ChevronDown
|
||||
className={`hidden h-4 w-4 flex-shrink-0 ${open ? "rotate-180" : ""} ${
|
||||
isMenuActive ? "!block" : ""
|
||||
} mb-0.5 text-custom-sidebar-text-400 duration-300 group-hover:!block`}
|
||||
className={cn(
|
||||
"hidden h-4 w-4 flex-shrink-0 mb-0.5 text-custom-sidebar-text-400 duration-300 group-hover:block",
|
||||
{
|
||||
"rotate-180": open,
|
||||
block: isMenuActive,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</Disclosure.Button>
|
||||
@ -250,7 +250,9 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
||||
<MoreHorizontal className="h-3.5 w-3.5" />
|
||||
</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"
|
||||
ellipsis
|
||||
placement="bottom-start"
|
||||
|
@ -51,3 +51,12 @@ export const groupReactions: (reactions: any[], key: string) => { [key: string]:
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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">
|
||||
<span className="text-xs font-semibold text-custom-sidebar-text-400">SETTINGS</span>
|
||||
<Loader className="flex w-full flex-col gap-2">
|
||||
{[...Array(8)].map(() => (
|
||||
<Loader.Item height="34px" />
|
||||
{[...Array(8)].map((index) => (
|
||||
<Loader.Item key={index} height="34px" />
|
||||
))}
|
||||
</Loader>
|
||||
</div>
|
||||
|
@ -6,6 +6,7 @@ import { ThemeProvider } from "next-themes";
|
||||
import "styles/globals.css";
|
||||
import "styles/command-pallette.css";
|
||||
import "styles/nprogress.css";
|
||||
import "styles/emoji.css";
|
||||
import "styles/react-day-picker.css";
|
||||
// constants
|
||||
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:
|
||||
"@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"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.42.tgz#6f6b11a904f6d96dda3c2920328a97011a00aba7"
|
||||
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"
|
||||
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:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
|
Loading…
Reference in New Issue
Block a user