From e4f48d687830621cdebbbbbe83abaa93a14cdc25 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Wed, 6 Mar 2024 19:15:48 +0530 Subject: [PATCH] [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 Co-authored-by: NarayanBavisetti --- apiserver/plane/app/serializers/project.py | 3 +- apiserver/plane/app/views/workspace.py | 4 - .../db/migrations/0061_alter_issuelink_url.py | 18 - .../db/migrations/0061_project_logo_props.py | 54 + apiserver/plane/db/models/project.py | 1 + packages/types/src/projects.d.ts | 27 +- packages/types/src/users.d.ts | 4 - packages/ui/package.json | 1 + packages/ui/src/emoji/emoji-icon-picker.tsx | 169 +++ packages/ui/src/emoji/icons-list.tsx | 110 ++ packages/ui/src/emoji/icons.ts | 605 +++++++++ packages/ui/src/emoji/index.ts | 1 + packages/ui/src/form-fields/input.tsx | 27 +- packages/ui/src/index.ts | 1 + space/components/common/index.ts | 1 + space/components/common/project-logo.tsx | 34 + space/components/issues/navbar/index.tsx | 47 +- space/types/project.ts | 6 +- .../sidebar/projects-list.tsx | 14 +- .../sidebar/sidebar-header.tsx | 15 +- .../dashboard/widgets/recent-projects.tsx | 16 +- web/components/dropdowns/project.tsx | 24 +- web/components/emoji-icon-picker/emojis.json | 1090 ----------------- web/components/emoji-icon-picker/helpers.ts | 26 - web/components/emoji-icon-picker/icons.json | 607 --------- web/components/emoji-icon-picker/index.tsx | 204 --- web/components/emoji-icon-picker/types.d.ts | 15 - web/components/headers/cycle-issues.tsx | 16 +- web/components/headers/cycles.tsx | 16 +- web/components/headers/module-issues.tsx | 54 +- web/components/headers/modules-list.tsx | 13 +- web/components/headers/page-details.tsx | 12 +- web/components/headers/pages.tsx | 12 +- .../project-archived-issue-details.tsx | 13 +- .../headers/project-archived-issues.tsx | 12 +- .../headers/project-draft-issues.tsx | 12 +- web/components/headers/project-inbox.tsx | 12 +- .../headers/project-issue-details.tsx | 12 +- web/components/headers/project-issues.tsx | 18 +- web/components/headers/project-settings.tsx | 12 +- .../headers/project-view-issues.tsx | 16 +- web/components/headers/project-views.tsx | 16 +- .../filters/applied-filters/project.tsx | 16 +- .../filters/header/filters/project.tsx | 19 +- web/components/issues/issue-layouts/utils.tsx | 9 +- web/components/profile/sidebar.tsx | 30 +- web/components/project/card.tsx | 14 +- .../project/create-project-modal.tsx | 249 ++-- web/components/project/form.tsx | 71 +- web/components/project/index.ts | 1 + web/components/project/project-logo.tsx | 34 + web/components/project/sidebar-list-item.tsx | 86 +- web/helpers/emoji.helper.tsx | 9 + web/helpers/project.helper.ts | 3 + .../settings-layout/project/sidebar.tsx | 4 +- web/pages/_app.tsx | 1 + web/styles/emoji.css | 52 + yarn.lock | 7 +- 58 files changed, 1513 insertions(+), 2462 deletions(-) delete mode 100644 apiserver/plane/db/migrations/0061_alter_issuelink_url.py create mode 100644 apiserver/plane/db/migrations/0061_project_logo_props.py create mode 100644 packages/ui/src/emoji/emoji-icon-picker.tsx create mode 100644 packages/ui/src/emoji/icons-list.tsx create mode 100644 packages/ui/src/emoji/icons.ts create mode 100644 packages/ui/src/emoji/index.ts create mode 100644 space/components/common/project-logo.tsx delete mode 100644 web/components/emoji-icon-picker/emojis.json delete mode 100644 web/components/emoji-icon-picker/helpers.ts delete mode 100644 web/components/emoji-icon-picker/icons.json delete mode 100644 web/components/emoji-icon-picker/index.tsx delete mode 100644 web/components/emoji-icon-picker/types.d.ts create mode 100644 web/components/project/project-logo.tsx create mode 100644 web/styles/emoji.css diff --git a/apiserver/plane/app/serializers/project.py b/apiserver/plane/app/serializers/project.py index 999233442..6840fa8f7 100644 --- a/apiserver/plane/app/serializers/project.py +++ b/apiserver/plane/app/serializers/project.py @@ -95,8 +95,7 @@ class ProjectLiteSerializer(BaseSerializer): "identifier", "name", "cover_image", - "icon_prop", - "emoji", + "logo_props", "description", ] read_only_fields = fields diff --git a/apiserver/plane/app/views/workspace.py b/apiserver/plane/app/views/workspace.py index 84ba125ba..7c4a5db8d 100644 --- a/apiserver/plane/app/views/workspace.py +++ b/apiserver/plane/app/views/workspace.py @@ -1366,10 +1366,6 @@ class WorkspaceUserProfileEndpoint(BaseAPIView): ) .values( "id", - "name", - "identifier", - "emoji", - "icon_prop", "created_issues", "assigned_issues", "completed_issues", diff --git a/apiserver/plane/db/migrations/0061_alter_issuelink_url.py b/apiserver/plane/db/migrations/0061_alter_issuelink_url.py deleted file mode 100644 index 1aca84a80..000000000 --- a/apiserver/plane/db/migrations/0061_alter_issuelink_url.py +++ /dev/null @@ -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(), - ), - ] diff --git a/apiserver/plane/db/migrations/0061_project_logo_props.py b/apiserver/plane/db/migrations/0061_project_logo_props.py new file mode 100644 index 000000000..d8752d9dd --- /dev/null +++ b/apiserver/plane/db/migrations/0061_project_logo_props.py @@ -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), + ] diff --git a/apiserver/plane/db/models/project.py b/apiserver/plane/db/models/project.py index b93174724..bb4885d14 100644 --- a/apiserver/plane/db/models/project.py +++ b/apiserver/plane/db/models/project.py @@ -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, diff --git a/packages/types/src/projects.d.ts b/packages/types/src/projects.d.ts index 86b352482..a93734186 100644 --- a/packages/types/src/projects.d.ts +++ b/packages/types/src/projects.d.ts @@ -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; diff --git a/packages/types/src/users.d.ts b/packages/types/src/users.d.ts index c428dc7d2..5920f0b49 100644 --- a/packages/types/src/users.d.ts +++ b/packages/types/src/users.d.ts @@ -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: { diff --git a/packages/ui/package.json b/packages/ui/package.json index 91a010a1e..f80bcc6ae 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -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", diff --git a/packages/ui/src/emoji/emoji-icon-picker.tsx b/packages/ui/src/emoji/emoji-icon-picker.tsx new file mode 100644 index 000000000..42c367938 --- /dev/null +++ b/packages/ui/src/emoji/emoji-icon-picker.tsx @@ -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 = (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(null); + const [popperElement, setPopperElement] = useState(null); + // popper-js + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement, + modifiers: [ + { + name: "preventOverflow", + options: { + padding: 20, + }, + }, + ], + }); + + return ( + + {({ close }) => ( + <> + + + + +
+ tab.key === defaultOpen)} + > + + {TABS_LIST.map((tab) => ( + + 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} + + ))} + + + + { + onChange({ + type: EmojiIconPickerTypes.EMOJI, + value: val, + }); + if (closeOnSelect) close(); + }} + height="20rem" + width="100%" + theme={theme} + searchPlaceholder={searchPlaceholder} + previewConfig={{ + showPreview: false, + }} + /> + + + { + onChange({ + type: EmojiIconPickerTypes.ICON, + value: val, + }); + if (closeOnSelect) close(); + }} + /> + + + +
+
+ + )} +
+ ); +}; diff --git a/packages/ui/src/emoji/icons-list.tsx b/packages/ui/src/emoji/icons-list.tsx new file mode 100644 index 000000000..f55da881b --- /dev/null +++ b/packages/ui/src/emoji/icons-list.tsx @@ -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 = (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 ( + <> +
+ {showHexInput ? ( +
+ + HEX + # + { + 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 + /> +
+ ) : ( + DEFAULT_COLORS.map((curCol) => ( + + )) + )} + +
+
+ {MATERIAL_ICONS_LIST.map((icon) => ( + + ))} +
+ + ); +}; diff --git a/packages/ui/src/emoji/icons.ts b/packages/ui/src/emoji/icons.ts new file mode 100644 index 000000000..72aacf18b --- /dev/null +++ b/packages/ui/src/emoji/icons.ts @@ -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", + }, +]; diff --git a/packages/ui/src/emoji/index.ts b/packages/ui/src/emoji/index.ts new file mode 100644 index 000000000..973454139 --- /dev/null +++ b/packages/ui/src/emoji/index.ts @@ -0,0 +1 @@ +export * from "./emoji-icon-picker"; diff --git a/packages/ui/src/form-fields/input.tsx b/packages/ui/src/form-fields/input.tsx index 6688d6778..f73467621 100644 --- a/packages/ui/src/form-fields/input.tsx +++ b/packages/ui/src/form-fields/input.tsx @@ -1,4 +1,6 @@ import * as React from "react"; +// helpers +import { cn } from "../../helpers"; export interface InputProps extends React.InputHTMLAttributes { mode?: "primary" | "transparent" | "true-transparent"; @@ -16,17 +18,20 @@ const Input = React.forwardRef((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} /> ); diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 218d375fa..24b76c3e0 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -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"; diff --git a/space/components/common/index.ts b/space/components/common/index.ts index f1c0b088e..36cc3c898 100644 --- a/space/components/common/index.ts +++ b/space/components/common/index.ts @@ -1 +1,2 @@ export * from "./latest-feature-block"; +export * from "./project-logo"; diff --git a/space/components/common/project-logo.tsx b/space/components/common/project-logo.tsx new file mode 100644 index 000000000..3d5887b28 --- /dev/null +++ b/space/components/common/project-logo.tsx @@ -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) => { + const { className, logo } = props; + + if (logo.in_use === "icon" && logo.icon) + return ( + + {logo.icon.name} + + ); + + if (logo.in_use === "emoji" && logo.emoji) + return ( + + {logo.emoji.value?.split("-").map((emoji) => String.fromCodePoint(parseInt(emoji, 10)))} + + ); + + return ; +}; diff --git a/space/components/issues/navbar/index.tsx b/space/components/issues/navbar/index.tsx index 0bc493b16..feb11ed13 100644 --- a/space/components/issues/navbar/index.tsx +++ b/space/components/issues/navbar/index.tsx @@ -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 ( - - {emoji.name} - - ); - else return isNaN(parseInt(emoji)) ? emoji : String.fromCodePoint(parseInt(emoji)); -}; - const IssueNavbar = observer(() => { const { project: projectStore, @@ -123,27 +108,15 @@ const IssueNavbar = observer(() => {
{/* project detail */}
-
- {projectStore.project ? ( - projectStore.project?.emoji ? ( - - {renderEmoji(projectStore.project.emoji)} - - ) : projectStore.project?.icon_prop ? ( -
- {renderEmoji(projectStore.project.icon_prop)} -
- ) : ( - - {projectStore.project?.name.charAt(0)} - - ) - ) : ( - - - - )} -
+ {projectStore.project ? ( + + + + ) : ( + + + + )}
{projectStore?.project?.name || `...`}
diff --git a/space/types/project.ts b/space/types/project.ts index e0e1bba9e..7e81d366c 100644 --- a/space/types/project.ts +++ b/space/types/project.ts @@ -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 { diff --git a/web/components/analytics/custom-analytics/sidebar/projects-list.tsx b/web/components/analytics/custom-analytics/sidebar/projects-list.tsx index 9a0eec227..31812cb00 100644 --- a/web/components/analytics/custom-analytics/sidebar/projects-list.tsx +++ b/web/components/analytics/custom-analytics/sidebar/projects-list.tsx @@ -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 = observer((pro return (
- {project.emoji ? ( - {renderEmoji(project.emoji)} - ) : project.icon_prop ? ( -
{renderEmoji(project.icon_prop)}
- ) : ( - - {project?.name.charAt(0)} - - )} +
+ +

{truncateText(project.name, 20)}

({project.identifier}) diff --git a/web/components/analytics/custom-analytics/sidebar/sidebar-header.tsx b/web/components/analytics/custom-analytics/sidebar/sidebar-header.tsx index fb9ab90fa..26f97e8f9 100644 --- a/web/components/analytics/custom-analytics/sidebar/sidebar-header.tsx +++ b/web/components/analytics/custom-analytics/sidebar/sidebar-header.tsx @@ -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(() => { ) : (
- {projectDetails?.emoji ? ( -
{renderEmoji(projectDetails.emoji)}
- ) : projectDetails?.icon_prop ? ( -
- {renderEmoji(projectDetails.icon_prop)} -
- ) : ( - - {projectDetails?.name.charAt(0)} + {projectDetails && ( + + )}

{projectDetails?.name}

diff --git a/web/components/dashboard/widgets/recent-projects.tsx b/web/components/dashboard/widgets/recent-projects.tsx index 72129df3f..22e561ac8 100644 --- a/web/components/dashboard/widgets/recent-projects.tsx +++ b/web/components/dashboard/widgets/recent-projects.tsx @@ -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 = observer((props) => {
- {projectDetails.emoji ? ( - - {renderEmoji(projectDetails.emoji)} - - ) : projectDetails.icon_prop ? ( -
{renderEmoji(projectDetails.icon_prop)}
- ) : ( - - {projectDetails.name.charAt(0)} - - )} +
+ +
diff --git a/web/components/dropdowns/project.tsx b/web/components/dropdowns/project.tsx index 05b455e5e..719b89802 100644 --- a/web/components/dropdowns/project.tsx +++ b/web/components/dropdowns/project.tsx @@ -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 = observer((props) => { query: `${projectDetails?.name}`, content: (
- - {projectDetails?.emoji - ? renderEmoji(projectDetails?.emoji) - : projectDetails?.icon_prop - ? renderEmoji(projectDetails?.icon_prop) - : null} - + {projectDetails && ( + + + + )} {projectDetails?.name}
), @@ -169,13 +167,9 @@ export const ProjectDropdown: React.FC = observer((props) => { showTooltip={showTooltip} variant={buttonVariant} > - {!hideIcon && ( - - {selectedProject?.emoji - ? renderEmoji(selectedProject?.emoji) - : selectedProject?.icon_prop - ? renderEmoji(selectedProject?.icon_prop) - : null} + {!hideIcon && selectedProject && ( + + )} {BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && ( diff --git a/web/components/emoji-icon-picker/emojis.json b/web/components/emoji-icon-picker/emojis.json deleted file mode 100644 index 73b9b800f..000000000 --- a/web/components/emoji-icon-picker/emojis.json +++ /dev/null @@ -1,1090 +0,0 @@ -[ - "8986", - "8987", - "9193", - "9194", - "9195", - "9196", - "9197", - "9198", - "9199", - "9200", - "9201", - "9202", - "9203", - "9208", - "9209", - "9210", - "9410", - "9748", - "9749", - "9757", - "9800", - "9801", - "9802", - "9803", - "9804", - "9805", - "9806", - "9807", - "9808", - "9809", - "9810", - "9811", - "9823", - "9855", - "9875", - "9889", - "9898", - "9899", - "9917", - "9918", - "9924", - "9925", - "9934", - "9935", - "9937", - "9939", - "9940", - "9961", - "9962", - "9968", - "9969", - "9970", - "9971", - "9972", - "9973", - "9975", - "9976", - "9977", - "9978", - "9981", - "9986", - "9989", - "9992", - "9993", - "9994", - "9995", - "9996", - "9997", - "9999", - "10002", - "10004", - "10006", - "10013", - "10017", - "10024", - "10035", - "10036", - "10052", - "10055", - "10060", - "10062", - "10067", - "10068", - "10069", - "10071", - "10083", - "10084", - "10133", - "10134", - "10135", - "10145", - "10160", - "10175", - "10548", - "10549", - "11013", - "11014", - "11015", - "11035", - "11036", - "11088", - "11093", - "12336", - "12349", - "12951", - "12953", - "126980", - "127183", - "127344", - "127345", - "127358", - "127359", - "127374", - "127377", - "127378", - "127379", - "127380", - "127381", - "127382", - "127383", - "127384", - "127385", - "127386", - "127489", - "127490", - "127514", - "127535", - "127538", - "127539", - "127540", - "127541", - "127542", - "127543", - "127544", - "127545", - "127546", - "127568", - "127569", - "127744", - "127745", - "127746", - "127747", - "127748", - "127749", - "127750", - "127751", - "127752", - "127753", - "127754", - "127755", - "127756", - "127757", - "127758", - "127759", - "127760", - "127761", - "127762", - "127763", - "127764", - "127765", - "127766", - "127767", - "127768", - "127769", - "127770", - "127771", - "127772", - "127773", - "127774", - "127775", - "127776", - "127777", - "127780", - "127781", - "127782", - "127783", - "127784", - "127785", - "127786", - "127787", - "127788", - "127789", - "127790", - "127791", - "127792", - "127793", - "127794", - "127795", - "127796", - "127797", - "127798", - "127799", - "127800", - "127801", - "127802", - "127803", - "127804", - "127805", - "127806", - "127807", - "127808", - "127809", - "127810", - "127811", - "127812", - "127813", - "127814", - "127815", - "127816", - "127817", - "127818", - "127819", - "127820", - "127821", - "127822", - "127823", - "127824", - "127825", - "127826", - "127827", - "127828", - "127829", - "127830", - "127831", - "127832", - "127833", - "127834", - "127835", - "127836", - "127837", - "127838", - "127839", - "127840", - "127841", - "127842", - "127843", - "127844", - "127845", - "127846", - "127847", - "127848", - "127849", - "127850", - "127851", - "127852", - "127853", - "127854", - "127855", - "127856", - "127857", - "127858", - "127859", - "127860", - "127861", - "127862", - "127863", - "127864", - "127865", - "127866", - "127867", - "127868", - "127869", - "127870", - "127871", - "127872", - "127873", - "127874", - "127875", - "127876", - "127877", - "127878", - "127879", - "127880", - "127881", - "127882", - "127883", - "127884", - "127885", - "127886", - "127887", - "127888", - "127889", - "127890", - "127891", - "127894", - "127895", - "127897", - "127898", - "127899", - "127902", - "127903", - "127904", - "127905", - "127906", - "127907", - "127908", - "127909", - "127910", - "127911", - "127912", - "127913", - "127914", - "127915", - "127916", - "127917", - "127918", - "127919", - "127920", - "127921", - "127922", - "127923", - "127924", - "127925", - "127926", - "127927", - "127928", - "127929", - "127930", - "127931", - "127932", - "127933", - "127934", - "127935", - "127936", - "127937", - "127938", - "127939", - "127940", - "127941", - "127942", - "127943", - "127944", - "127945", - "127946", - "127947", - "127948", - "127949", - "127950", - "127951", - "127952", - "127953", - "127954", - "127955", - "127956", - "127957", - "127958", - "127959", - "127960", - "127961", - "127962", - "127963", - "127964", - "127965", - "127966", - "127967", - "127968", - "127969", - "127970", - "127971", - "127972", - "127973", - "127974", - "127975", - "127976", - "127977", - "127978", - "127979", - "127980", - "127981", - "127982", - "127983", - "127984", - "127987", - "127988", - "127989", - "127991", - "127992", - "127993", - "127994", - "127995", - "127996", - "127997", - "127998", - "127999", - "128000", - "128001", - "128002", - "128003", - "128004", - "128005", - "128006", - "128007", - "128008", - "128009", - "128010", - "128011", - "128012", - "128013", - "128014", - "128015", - "128016", - "128017", - "128018", - "128019", - "128020", - "128021", - "128022", - "128023", - "128024", - "128025", - "128026", - "128027", - "128028", - "128029", - "128030", - "128031", - "128032", - "128033", - "128034", - "128035", - "128036", - "128037", - "128038", - "128039", - "128040", - "128041", - "128042", - "128043", - "128044", - "128045", - "128046", - "128047", - "128048", - "128049", - "128050", - "128051", - "128052", - "128053", - "128054", - "128055", - "128056", - "128057", - "128058", - "128059", - "128060", - "128061", - "128062", - "128063", - "128064", - "128065", - "128066", - "128067", - "128068", - "128069", - "128070", - "128071", - "128072", - "128073", - "128074", - "128075", - "128076", - "128077", - "128078", - "128079", - "128080", - "128081", - "128082", - "128083", - "128084", - "128085", - "128086", - "128087", - "128088", - "128089", - "128090", - "128091", - "128092", - "128093", - "128094", - "128095", - "128096", - "128097", - "128098", - "128099", - "128100", - "128101", - "128102", - "128103", - "128104", - "128105", - "128106", - "128107", - "128108", - "128109", - "128110", - "128111", - "128112", - "128113", - "128114", - "128115", - "128116", - "128117", - "128118", - "128119", - "128120", - "128121", - "128122", - "128123", - "128124", - "128125", - "128126", - "128127", - "128128", - "128129", - "128130", - "128131", - "128132", - "128133", - "128134", - "128135", - "128136", - "128137", - "128138", - "128139", - "128140", - "128141", - "128142", - "128143", - "128144", - "128145", - "128146", - "128147", - "128148", - "128149", - "128150", - "128151", - "128152", - "128153", - "128154", - "128155", - "128156", - "128157", - "128158", - "128159", - "128160", - "128161", - "128162", - "128163", - "128164", - "128165", - "128166", - "128167", - "128168", - "128169", - "128170", - "128171", - "128172", - "128173", - "128174", - "128175", - "128176", - "128177", - "128178", - "128179", - "128180", - "128181", - "128182", - "128183", - "128184", - "128185", - "128186", - "128187", - "128188", - "128189", - "128190", - "128191", - "128192", - "128193", - "128194", - "128195", - "128196", - "128197", - "128198", - "128199", - "128200", - "128201", - "128202", - "128203", - "128204", - "128205", - "128206", - "128207", - "128208", - "128209", - "128210", - "128211", - "128212", - "128213", - "128214", - "128215", - "128216", - "128217", - "128218", - "128219", - "128220", - "128221", - "128222", - "128223", - "128224", - "128225", - "128226", - "128227", - "128228", - "128229", - "128230", - "128231", - "128232", - "128233", - "128234", - "128235", - "128236", - "128237", - "128238", - "128239", - "128240", - "128241", - "128242", - "128243", - "128244", - "128245", - "128246", - "128247", - "128248", - "128249", - "128250", - "128251", - "128252", - "128253", - "128255", - "128256", - "128257", - "128258", - "128259", - "128260", - "128261", - "128262", - "128263", - "128264", - "128265", - "128266", - "128267", - "128268", - "128269", - "128270", - "128271", - "128272", - "128273", - "128274", - "128275", - "128276", - "128277", - "128278", - "128279", - "128280", - "128281", - "128282", - "128283", - "128284", - "128285", - "128286", - "128287", - "128288", - "128289", - "128290", - "128291", - "128292", - "128293", - "128294", - "128295", - "128296", - "128297", - "128298", - "128299", - "128300", - "128301", - "128302", - "128303", - "128304", - "128305", - "128306", - "128307", - "128308", - "128309", - "128310", - "128311", - "128312", - "128313", - "128314", - "128315", - "128316", - "128317", - "128329", - "128330", - "128331", - "128332", - "128333", - "128334", - "128336", - "128337", - "128338", - "128339", - "128340", - "128341", - "128342", - "128343", - "128344", - "128345", - "128346", - "128347", - "128348", - "128349", - "128350", - "128351", - "128352", - "128353", - "128354", - "128355", - "128356", - "128357", - "128358", - "128359", - "128367", - "128368", - "128371", - "128372", - "128373", - "128374", - "128375", - "128376", - "128377", - "128378", - "128391", - "128394", - "128395", - "128396", - "128397", - "128400", - "128405", - "128406", - "128420", - "128421", - "128424", - "128433", - "128434", - "128444", - "128450", - "128451", - "128452", - "128465", - "128466", - "128467", - "128476", - "128477", - "128478", - "128481", - "128483", - "128488", - "128495", - "128499", - "128506", - "128507", - "128508", - "128509", - "128510", - "128511", - "128512", - "128513", - "128514", - "128515", - "128516", - "128517", - "128518", - "128519", - "128520", - "128521", - "128522", - "128523", - "128524", - "128525", - "128526", - "128527", - "128528", - "128529", - "128530", - "128531", - "128532", - "128533", - "128534", - "128535", - "128536", - "128537", - "128538", - "128539", - "128540", - "128541", - "128542", - "128543", - "128544", - "128545", - "128546", - "128547", - "128548", - "128549", - "128550", - "128551", - "128552", - "128553", - "128554", - "128555", - "128556", - "128557", - "128558", - "128559", - "128560", - "128561", - "128562", - "128563", - "128564", - "128565", - "128566", - "128567", - "128568", - "128569", - "128570", - "128571", - "128572", - "128573", - "128574", - "128575", - "128576", - "128577", - "128578", - "128579", - "128580", - "128581", - "128582", - "128583", - "128584", - "128585", - "128586", - "128587", - "128588", - "128589", - "128590", - "128591", - "128640", - "128641", - "128642", - "128643", - "128644", - "128645", - "128646", - "128647", - "128648", - "128649", - "128650", - "128651", - "128652", - "128653", - "128654", - "128655", - "128656", - "128657", - "128658", - "128659", - "128660", - "128661", - "128662", - "128663", - "128664", - "128665", - "128666", - "128667", - "128668", - "128669", - "128670", - "128671", - "128672", - "128673", - "128674", - "128675", - "128676", - "128677", - "128678", - "128679", - "128680", - "128681", - "128682", - "128683", - "128684", - "128685", - "128686", - "128687", - "128688", - "128689", - "128690", - "128691", - "128692", - "128693", - "128694", - "128695", - "128696", - "128697", - "128698", - "128699", - "128700", - "128701", - "128702", - "128703", - "128704", - "128705", - "128706", - "128707", - "128708", - "128709", - "128715", - "128716", - "128717", - "128718", - "128719", - "128720", - "128721", - "128722", - "128736", - "128737", - "128738", - "128739", - "128740", - "128741", - "128745", - "128747", - "128748", - "128752", - "128755", - "128756", - "128757", - "128758", - "128759", - "128760", - "128761", - "128762", - "129296", - "129297", - "129298", - "129299", - "129300", - "129301", - "129302", - "129303", - "129304", - "129305", - "129306", - "129307", - "129308", - "129309", - "129310", - "129311", - "129312", - "129313", - "129314", - "129315", - "129316", - "129317", - "129318", - "129319", - "129320", - "129321", - "129322", - "129323", - "129324", - "129325", - "129326", - "129327", - "129328", - "129329", - "129330", - "129331", - "129332", - "129333", - "129334", - "129335", - "129336", - "129337", - "129338", - "129340", - "129341", - "129342", - "129344", - "129345", - "129346", - "129347", - "129348", - "129349", - "129351", - "129352", - "129353", - "129354", - "129355", - "129356", - "129357", - "129358", - "129359", - "129360", - "129361", - "129362", - "129363", - "129364", - "129365", - "129366", - "129367", - "129368", - "129369", - "129370", - "129371", - "129372", - "129373", - "129374", - "129375", - "129376", - "129377", - "129378", - "129379", - "129380", - "129381", - "129382", - "129383", - "129384", - "129385", - "129386", - "129387", - "129408", - "129409", - "129410", - "129411", - "129412", - "129413", - "129414", - "129415", - "129416", - "129417", - "129418", - "129419", - "129420", - "129421", - "129422", - "129423", - "129424", - "129425", - "129426", - "129427", - "129428", - "129429", - "129430", - "129431", - "129472", - "129488", - "129489", - "129490", - "129491", - "129492", - "129493", - "129494", - "129495", - "129496", - "129497", - "129498", - "129499", - "129500", - "129501", - "129502", - "129503", - "129504", - "129505", - "129506", - "129507", - "129508", - "129509", - "129510" -] diff --git a/web/components/emoji-icon-picker/helpers.ts b/web/components/emoji-icon-picker/helpers.ts deleted file mode 100644 index ab59a7b07..000000000 --- a/web/components/emoji-icon-picker/helpers.ts +++ /dev/null @@ -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 []; -}; diff --git a/web/components/emoji-icon-picker/icons.json b/web/components/emoji-icon-picker/icons.json deleted file mode 100644 index f844f22d4..000000000 --- a/web/components/emoji-icon-picker/icons.json +++ /dev/null @@ -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" - } - ] -} diff --git a/web/components/emoji-icon-picker/index.tsx b/web/components/emoji-icon-picker/index.tsx deleted file mode 100644 index b9211a718..000000000 --- a/web/components/emoji-icon-picker/index.tsx +++ /dev/null @@ -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) => { - const { label, value, onChange, onIconColorChange, disabled = false } = props; - // states - const [isOpen, setIsOpen] = useState(false); - const [openColorPicker, setOpenColorPicker] = useState(false); - const [activeColor, setActiveColor] = useState("rgb(var(--color-text-200))"); - const [recentEmojis, setRecentEmojis] = useState([]); - - const buttonRef = useRef(null); - const emojiPickerRef = useRef(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 ( - - setIsOpen((prev) => !prev)} - className="outline-none flex items-center justify-center" - disabled={disabled} - > - {label} - - - -
- - - {tabOptions.map((tab) => ( - - {({ selected }) => ( - - )} - - ))} - - - - {recentEmojis.length > 0 && ( -
-

Recent

-
- {recentEmojis.map((emoji) => ( - - ))} -
-
- )} -
-
-
- {emojis.map((emoji) => ( - - ))} -
-
-
-
- -
-
- {["#FF6B00", "#8CC1FF", "#FCBE1D", "#18904F", "#ADF672", "#05C3FF", "#000000"].map((curCol) => ( - setActiveColor(curCol)} - /> - ))} - -
-
- { - setActiveColor(color.hex); - if (onIconColorChange) onIconColorChange(color.hex); - }} - triangle="hide" - width="205px" - /> -
-
-
-
- {icons.material_rounded.map((icon, index) => ( - - ))} -
-
-
-
-
-
-
-
-
- ); -}; - -export default EmojiIconPicker; diff --git a/web/components/emoji-icon-picker/types.d.ts b/web/components/emoji-icon-picker/types.d.ts deleted file mode 100644 index 8a0b54342..000000000 --- a/web/components/emoji-icon-picker/types.d.ts +++ /dev/null @@ -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; -}; diff --git a/web/components/headers/cycle-issues.tsx b/web/components/headers/cycle-issues.tsx index 18d0543c0..468900110 100644 --- a/web/components/headers/cycle-issues.tsx +++ b/web/components/headers/cycle-issues.tsx @@ -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) - ) : ( - - {currentProjectDetails?.name.charAt(0)} + currentProjectDetails && ( + + ) } @@ -209,7 +205,9 @@ export const CycleIssuesHeader: React.FC = observer(() => { className="ml-1.5 flex-shrink-0" placement="bottom-start" > - {currentProjectCycleIds?.map((cycleId) => )} + {currentProjectCycleIds?.map((cycleId) => ( + + ))} } /> diff --git a/web/components/headers/cycles.tsx b/web/components/headers/cycles.tsx index a0ab19ec7..22637147f 100644 --- a/web/components/headers/cycles.tsx +++ b/web/components/headers/cycles.tsx @@ -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("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) - ) : ( - - {currentProjectDetails?.name.charAt(0)} + currentProjectDetails && ( + + ) } diff --git a/web/components/headers/module-issues.tsx b/web/components/headers/module-issues.tsx index ca3a84e3b..b42b8774a 100644 --- a/web/components/headers/module-issues.tsx +++ b/web/components/headers/module-issues.tsx @@ -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) => { 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) => { 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) - ) : ( - - {currentProjectDetails?.name.charAt(0)} + currentProjectDetails && ( + + ) } @@ -212,7 +228,9 @@ export const ModuleIssuesHeader: React.FC = observer(() => { className="ml-1.5 flex-shrink-0" placement="bottom-start" > - {projectModuleIds?.map((moduleId) => )} + {projectModuleIds?.map((moduleId) => ( + + ))} } /> diff --git a/web/components/headers/modules-list.tsx b/web/components/headers/modules-list.tsx index b942b7b13..a1233ae52 100644 --- a/web/components/headers/modules-list.tsx +++ b/web/components/headers/modules-list.tsx @@ -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) - ) : ( - - {currentProjectDetails?.name.charAt(0)} + currentProjectDetails && ( + + ) } diff --git a/web/components/headers/page-details.tsx b/web/components/headers/page-details.tsx index 0eed72178..2c05d95fa 100644 --- a/web/components/headers/page-details.tsx +++ b/web/components/headers/page-details.tsx @@ -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 = 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) - ) : ( - - {currentProjectDetails?.name.charAt(0)} + currentProjectDetails && ( + + ) } diff --git a/web/components/headers/pages.tsx b/web/components/headers/pages.tsx index b5ce74fc5..e45d1a9fe 100644 --- a/web/components/headers/pages.tsx +++ b/web/components/headers/pages.tsx @@ -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) - ) : ( - - {currentProjectDetails?.name.charAt(0)} + currentProjectDetails && ( + + ) } diff --git a/web/components/headers/project-archived-issue-details.tsx b/web/components/headers/project-archived-issue-details.tsx index 8752e7396..86dae643d 100644 --- a/web/components/headers/project-archived-issue-details.tsx +++ b/web/components/headers/project-archived-issue-details.tsx @@ -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) - ) : ( - - {currentProjectDetails?.name.charAt(0)} + currentProjectDetails && ( + + ) } diff --git a/web/components/headers/project-archived-issues.tsx b/web/components/headers/project-archived-issues.tsx index 8ade61aae..db208aa21 100644 --- a/web/components/headers/project-archived-issues.tsx +++ b/web/components/headers/project-archived-issues.tsx @@ -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) - ) : ( - - {currentProjectDetails?.name.charAt(0)} + currentProjectDetails && ( + + ) } diff --git a/web/components/headers/project-draft-issues.tsx b/web/components/headers/project-draft-issues.tsx index 3fd0cb399..4f2929621 100644 --- a/web/components/headers/project-draft-issues.tsx +++ b/web/components/headers/project-draft-issues.tsx @@ -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) - ) : ( - - {currentProjectDetails?.name.charAt(0)} + currentProjectDetails && ( + + ) } diff --git a/web/components/headers/project-inbox.tsx b/web/components/headers/project-inbox.tsx index b89fbaaac..0e1bdcd1e 100644 --- a/web/components/headers/project-inbox.tsx +++ b/web/components/headers/project-inbox.tsx @@ -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) - ) : ( - - {currentProjectDetails?.name.charAt(0)} + currentProjectDetails && ( + + ) } diff --git a/web/components/headers/project-issue-details.tsx b/web/components/headers/project-issue-details.tsx index 2f6349e61..b9343a15c 100644 --- a/web/components/headers/project-issue-details.tsx +++ b/web/components/headers/project-issue-details.tsx @@ -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) - ) : ( - - {currentProjectDetails?.name.charAt(0)} + currentProjectDetails && ( + + ) } diff --git a/web/components/headers/project-issues.tsx b/web/components/headers/project-issues.tsx index 8e8807fdb..19eaf4f4f 100644 --- a/web/components/headers/project-issues.tsx +++ b/web/components/headers/project-issues.tsx @@ -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 ? ( - - {renderEmoji(currentProjectDetails.emoji)} - - ) : currentProjectDetails?.icon_prop ? ( -
- {renderEmoji(currentProjectDetails.icon_prop)} -
- ) : ( - - {currentProjectDetails?.name.charAt(0)} + currentProjectDetails && ( + + ) ) : ( diff --git a/web/components/headers/project-settings.tsx b/web/components/headers/project-settings.tsx index 87b2e507e..817d842b4 100644 --- a/web/components/headers/project-settings.tsx +++ b/web/components/headers/project-settings.tsx @@ -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 = 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) - ) : ( - - {currentProjectDetails?.name.charAt(0)} + currentProjectDetails && ( + + ) } diff --git a/web/components/headers/project-view-issues.tsx b/web/components/headers/project-view-issues.tsx index eea211431..4abc3edf9 100644 --- a/web/components/headers/project-view-issues.tsx +++ b/web/components/headers/project-view-issues.tsx @@ -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 ? ( - - {renderEmoji(currentProjectDetails.emoji)} - - ) : currentProjectDetails?.icon_prop ? ( -
- {renderEmoji(currentProjectDetails.icon_prop)} -
- ) : ( - - {currentProjectDetails?.name.charAt(0)} + currentProjectDetails && ( + + ) } diff --git a/web/components/headers/project-views.tsx b/web/components/headers/project-views.tsx index 3b4d7fb20..99533189a 100644 --- a/web/components/headers/project-views.tsx +++ b/web/components/headers/project-views.tsx @@ -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 ? ( - - {renderEmoji(currentProjectDetails.emoji)} - - ) : currentProjectDetails?.icon_prop ? ( -
- {renderEmoji(currentProjectDetails.icon_prop)} -
- ) : ( - - {currentProjectDetails?.name.charAt(0)} + currentProjectDetails && ( + + ) } diff --git a/web/components/issues/issue-layouts/filters/applied-filters/project.tsx b/web/components/issues/issue-layouts/filters/applied-filters/project.tsx index 24e8fd338..84e81b6e8 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/project.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/project.tsx @@ -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 = observer((props) => { return (
- {projectDetails.emoji ? ( - {renderEmoji(projectDetails.emoji)} - ) : projectDetails.icon_prop ? ( -
{renderEmoji(projectDetails.icon_prop)}
- ) : ( - - {projectDetails?.name.charAt(0)} - - )} + + + {projectDetails.name} {editable && (
{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 ( {({ open }) => (
- {project.emoji ? ( -
- {renderEmoji(project.emoji)} -
- ) : project.icon_prop ? ( -
- {renderEmoji(project.icon_prop)} -
- ) : ( -
- {project?.name.charAt(0)} -
- )} -
{project.name}
+ + + +
{projectDetails.name}
{project.assigned_issues > 0 && ( diff --git a/web/components/project/card.tsx b/web/components/project/card.tsx index c74e9ee75..08aec43fa 100644 --- a/web/components/project/card.tsx +++ b/web/components/project/card.tsx @@ -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 = observer((props) => {
-
- - {project.emoji - ? renderEmoji(project.emoji) - : project.icon_prop - ? renderEmoji(project.icon_prop) - : null} +
+ +
diff --git a/web/components/project/create-project-modal.tsx b/web/components/project/create-project-modal.tsx index 01cbb5888..7a66c3a30 100644 --- a/web/components/project/create-project-modal.tsx +++ b/web/components/project/create-project-modal.tsx @@ -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 = { + 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 = ({ onClose }) => { useEffect(() => { onClose(); @@ -42,19 +68,6 @@ const IsGuestCondition: FC = ({ 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 = observer((props) => { const { isOpen, onClose, setToFavorite = false, workspaceSlug } = props; // store @@ -66,7 +79,6 @@ export const CreateProjectModal: FC = 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 = observer((props) => { control, watch, setValue, - } = useForm({ - defaultValues: { - cover_image, - description: "", - emoji_and_icon: getRandomEmoji(), - identifier: "", - name: "", - network: 2, - project_lead: undefined, - }, + } = useForm({ + defaultValues, reValidateMode: "onChange", }); - const currentNetwork = NETWORK_CHOICES.find((n) => n.key === watch("network")); - if (currentWorkspaceRole && isOpen) if (currentWorkspaceRole < EUserWorkspaceRoles.MEMBER) return ; const handleClose = () => { onClose(); setIsChangeInIdentifierRequired(true); - reset(); + setTimeout(() => { + reset(); + }, 300); }; const handleAddToFavorites = (projectId: string) => { @@ -110,18 +114,11 @@ export const CreateProjectModal: FC = 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) => { // 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 = observer((props) => { captureProjectEvent({ eventName: PROJECT_CREATED, payload: { - ...payload, + ...formData, state: "FAILED", }, }); @@ -165,13 +162,13 @@ export const CreateProjectModal: FC = 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) => { 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 = observer((props) => { >
- {watch("cover_image") !== null && ( + {watch("cover_image") && ( Cover Image )} @@ -218,30 +215,50 @@ export const CreateProjectModal: FC = observer((props) => {
- { - setValue("cover_image", image); - }} + ( + + )} />
( - - {value ? renderEmoji(value) : "Icon"} -
+ + + + } + 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 = observer((props) => { /> )} /> - {errors?.name?.message} + + <>{errors?.name?.message} +
= observer((props) => { /> )} /> - {errors?.identifier?.message} + + <>{errors?.identifier?.message} +
= observer((props) => { ( -
- - {currentNetwork ? ( - <> - - {currentNetwork.label} - - ) : ( - Select Network - )} -
- } - placement="bottom-start" - noChevron - tabIndex={4} - > - {NETWORK_CHOICES.map((network) => ( - -
- -
-

{network.label}

-

{network.description}

-
+ render={({ field: { onChange, value } }) => { + const currentNetwork = NETWORK_CHOICES.find((n) => n.key === value); + + return ( +
+ + {currentNetwork ? ( + <> + + {currentNetwork.label} + + ) : ( + Select network + )}
- - ))} - -
- )} + } + placement="bottom-start" + noChevron + tabIndex={4} + > + {NETWORK_CHOICES.map((network) => ( + +
+ +
+

{network.label}

+

{network.description}

+
+
+
+ ))} + +
+ ); + }} /> ( -
- -
- )} + render={({ field: { value, onChange } }) => { + if (value === undefined || value === null || typeof value === "string") + return ( +
+ +
+ ); + else return <>; + }} />
@@ -396,7 +425,7 @@ export const CreateProjectModal: FC = observer((props) => { Cancel
diff --git a/web/components/project/form.tsx b/web/components/project/form.tsx index 25186e08e..1ef7ee226 100644 --- a/web/components/project/form.tsx +++ b/web/components/project/form.tsx @@ -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 = (props) => { } = useForm({ defaultValues: { ...project, - emoji_and_icon: project.emoji ?? project.icon_prop, workspace: (project.workspace as IWorkspace).id, }, }); @@ -55,7 +63,6 @@ export const ProjectDetailsForm: FC = (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 = (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 = (props) => {
-
- ( - - )} - /> -
+ ( + + + + } + 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} + /> + )} + />
{watch("name")} diff --git a/web/components/project/index.ts b/web/components/project/index.ts index 42e310edb..27f3eda33 100644 --- a/web/components/project/index.ts +++ b/web/components/project/index.ts @@ -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"; diff --git a/web/components/project/project-logo.tsx b/web/components/project/project-logo.tsx new file mode 100644 index 000000000..3d5887b28 --- /dev/null +++ b/web/components/project/project-logo.tsx @@ -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) => { + const { className, logo } = props; + + if (logo.in_use === "icon" && logo.icon) + return ( + + {logo.icon.name} + + ); + + if (logo.in_use === "emoji" && logo.emoji) + return ( + + {logo.emoji.value?.split("-").map((emoji) => String.fromCodePoint(parseInt(emoji, 10)))} + + ); + + return ; +}; diff --git a/web/components/project/sidebar-list-item.tsx b/web/components/project/sidebar-list-item.tsx index c86ba0dc2..11ed01cd3 100644 --- a/web/components/project/sidebar-list-item.tsx +++ b/web/components/project/sidebar-list-item.tsx @@ -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 = observer((props) => { const [leaveProjectModalOpen, setLeaveProjectModal] = useState(false); const [publishModalOpen, setPublishModal] = useState(false); const [isMenuActive, setIsMenuActive] = useState(false); + // refs + const actionSectionRef = useRef(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(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 = observer((props) => { {({ open }) => ( <>
{provided && !disableDrag && ( = observer((props) => { >
} - 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" diff --git a/web/helpers/emoji.helper.tsx b/web/helpers/emoji.helper.tsx index 5ff95027b..1fb746f51 100644 --- a/web/helpers/emoji.helper.tsx +++ b/web/helpers/emoji.helper.tsx @@ -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("-"); +}; diff --git a/web/helpers/project.helper.ts b/web/helpers/project.helper.ts index 8d78964ee..441c14a42 100644 --- a/web/helpers/project.helper.ts +++ b/web/helpers/project.helper.ts @@ -43,3 +43,6 @@ export const orderJoinedProjects = ( return updatedSortOrder; }; + +export const projectIdentifierSanitizer = (identifier: string): string => + identifier.replace(/[^ÇŞĞIİÖÜA-Za-z0-9]/g, ""); diff --git a/web/layouts/settings-layout/project/sidebar.tsx b/web/layouts/settings-layout/project/sidebar.tsx index 8cf2befc2..628c2a854 100644 --- a/web/layouts/settings-layout/project/sidebar.tsx +++ b/web/layouts/settings-layout/project/sidebar.tsx @@ -24,8 +24,8 @@ export const ProjectSettingsSidebar = () => {
SETTINGS - {[...Array(8)].map(() => ( - + {[...Array(8)].map((index) => ( + ))}
diff --git a/web/pages/_app.tsx b/web/pages/_app.tsx index bc8230256..48cd5a80c 100644 --- a/web/pages/_app.tsx +++ b/web/pages/_app.tsx @@ -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"; diff --git a/web/styles/emoji.css b/web/styles/emoji.css new file mode 100644 index 000000000..2fe3ddab3 --- /dev/null +++ b/web/styles/emoji.css @@ -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; +} diff --git a/yarn.lock b/yarn.lock index 9518afff6..c8cfcffd4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"