[WEB-5305] refactor: migrate emoji and logo components to propel and remove duplication (#8038)
This commit is contained in:
parent
4927ef8c71
commit
9b3bd11c03
46 changed files with 62 additions and 858 deletions
|
|
@ -1,12 +1,11 @@
|
|||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { ProjectIcon } from "@plane/propel/icons";
|
||||
// plane imports
|
||||
import type { ICustomSearchSelectOption } from "@plane/types";
|
||||
import { BreadcrumbNavigationSearchDropdown, Breadcrumbs } from "@plane/ui";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { SwitcherLabel } from "@/components/common/switcher-label";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useCallback, useMemo } from "react";
|
||||
import { AtSign, Briefcase, Calendar } from "lucide-react";
|
||||
// plane imports
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import {
|
||||
CycleGroupIcon,
|
||||
CycleIcon,
|
||||
|
|
@ -26,7 +27,7 @@ import type {
|
|||
IProject,
|
||||
TWorkItemFilterProperty,
|
||||
} from "@plane/types";
|
||||
import { Avatar, Logo } from "@plane/ui";
|
||||
import { Avatar } from "@plane/ui";
|
||||
import {
|
||||
getAssigneeFilterConfig,
|
||||
getCreatedAtFilterConfig,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ProjectIcon } from "@plane/propel/icons";
|
||||
// plane package imports
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { ProjectIcon } from "@plane/propel/icons";
|
||||
import { cn } from "@plane/utils";
|
||||
import { Logo } from "@/components/common/logo";
|
||||
// plane web hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { ProjectIcon } from "@plane/propel/icons";
|
||||
// plane package imports
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { ProjectIcon } from "@plane/propel/icons";
|
||||
import { CustomSearchSelect } from "@plane/ui";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ import { useParams } from "next/navigation";
|
|||
import useSWR from "swr";
|
||||
import { UserRound } from "lucide-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { ProjectIcon } from "@plane/propel/icons";
|
||||
// plane package imports
|
||||
import type { AnalyticsTableDataMap, WorkItemInsightColumns } from "@plane/types";
|
||||
// plane web components
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { getFileURL } from "@plane/utils";
|
||||
import { Logo } from "@/components/common/logo";
|
||||
// hooks
|
||||
import { useAnalytics } from "@/hooks/store/use-analytics";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import type { FC } from "react";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import type { ISvgIcons } from "@plane/propel/icons";
|
||||
import type { TLogoProps } from "@plane/types";
|
||||
import { getFileURL, truncateText } from "@plane/utils";
|
||||
import { Logo } from "@/components/common/logo";
|
||||
|
||||
type TSwitcherIconProps = {
|
||||
logo_props?: TLogoProps;
|
||||
|
|
|
|||
|
|
@ -4,12 +4,11 @@ import type { FC } from "react";
|
|||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { ChevronRightIcon } from "@plane/propel/icons";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
// icons
|
||||
import { Row } from "@plane/ui";
|
||||
// helpers
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
|
||||
type Props = {
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import { Check, Search } from "lucide-react";
|
|||
import { Combobox } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { ProjectIcon, ChevronDownIcon } from "@plane/propel/icons";
|
||||
import { ComboDropDown } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
// hooks
|
||||
import { useDropdown } from "@/hooks/use-dropdown";
|
||||
// plane web imports
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import { useRouter } from "next/navigation";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { PageIcon } from "@plane/propel/icons";
|
||||
// plane import
|
||||
import type { TActivityEntityData, TPageEntityData } from "@plane/types";
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { calculateTimeAgo, getFileURL, getPageName } from "@plane/utils";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { ListItem } from "@/components/core/list";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { useRouter } from "next/navigation";
|
||||
// plane types
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import type { TActivityEntityData, TProjectEntityData } from "@plane/types";
|
||||
import { calculateTimeAgo } from "@plane/utils";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { ListItem } from "@/components/core/list";
|
||||
import { MemberDropdown } from "@/components/dropdowns/member/dropdown";
|
||||
// helpers
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { CloseIcon } from "@plane/propel/icons";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import React, { useMemo, useState } from "react";
|
|||
import { sortBy } from "lodash-es";
|
||||
import { observer } from "mobx-react";
|
||||
// ui
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { FilterHeader, FilterOption } from "@/components/issues/issue-layouts/filters";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { clone, isNil, pull, uniq, concat } from "lodash-es";
|
|||
import scrollIntoView from "smooth-scroll-into-view-if-needed";
|
||||
// plane types
|
||||
import { EIconSize, ISSUE_PRIORITIES, STATE_GROUPS } from "@plane/constants";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import type { ISvgIcons } from "@plane/propel/icons";
|
||||
import { CycleGroupIcon, CycleIcon, ModuleIcon, PriorityIcon, StateGroupIcon } from "@plane/propel/icons";
|
||||
import type {
|
||||
|
|
@ -26,8 +27,6 @@ import { EIssuesStoreType } from "@plane/types";
|
|||
// plane ui
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { renderFormattedDate, getFileURL } from "@plane/utils";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
// helpers
|
||||
// store
|
||||
import { store } from "@/lib/store-context";
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { EmojiIconPicker, EmojiIconPickerTypes } from "@plane/ui";
|
||||
import { EmojiPicker, EmojiIconPickerTypes, Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
// store
|
||||
import type { TPageInstance } from "@/store/pages/base-page";
|
||||
|
||||
|
|
@ -27,7 +25,7 @@ export const PageEditorHeaderLogoPicker: React.FC<Props> = observer((props) => {
|
|||
"max-h-[56px] pointer-events-auto": isLogoSelected,
|
||||
})}
|
||||
>
|
||||
<EmojiIconPicker
|
||||
<EmojiPicker
|
||||
isOpen={isLogoPickerOpen}
|
||||
handleToggle={(val) => setIsLogoPickerOpen(val)}
|
||||
className="flex items-center justify-center"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { SmilePlus } from "lucide-react";
|
||||
// plane imports
|
||||
import { EmojiIconPicker, EmojiIconPickerTypes } from "@plane/ui";
|
||||
import { EmojiPicker, EmojiIconPickerTypes } from "@plane/propel/emoji-icon-picker";
|
||||
import { cn } from "@plane/utils";
|
||||
// store
|
||||
import type { TPageInstance } from "@/store/pages/base-page";
|
||||
|
|
@ -32,7 +32,7 @@ export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
|
|||
"opacity-100": isTitleEmpty,
|
||||
})}
|
||||
>
|
||||
<EmojiIconPicker
|
||||
<EmojiPicker
|
||||
isOpen={isLogoPickerOpen}
|
||||
handleToggle={(val) => setIsLogoPickerOpen(val)}
|
||||
className="flex items-center justify-center"
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
import type { FC } from "react";
|
||||
import { useRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { PageIcon } from "@plane/propel/icons";
|
||||
// plane imports
|
||||
import { getPageName } from "@plane/utils";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { ListItem } from "@/components/core/list";
|
||||
import { BlockItemAction } from "@/components/pages/list/block-item-action";
|
||||
// hooks
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ import { Globe2, Lock } from "lucide-react";
|
|||
import { ETabIndices, EPageAccess } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { EmojiPicker, EmojiIconPickerTypes, Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { PageIcon } from "@plane/propel/icons";
|
||||
import type { TPage } from "@plane/types";
|
||||
import { EmojiIconPicker, EmojiIconPickerTypes, Input } from "@plane/ui";
|
||||
import { convertHexEmojiToDecimal, getTabIndex } from "@plane/utils";
|
||||
import { Input } from "@plane/ui";
|
||||
import { getTabIndex } from "@plane/utils";
|
||||
// components
|
||||
import { AccessField } from "@/components/common/access-field";
|
||||
import { Logo } from "@/components/common/logo";
|
||||
// hooks
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ export const PageForm: React.FC<Props> = (props) => {
|
|||
<div className="space-y-5 p-5">
|
||||
<h3 className="text-xl font-medium text-custom-text-200">Create page</h3>
|
||||
<div className="flex items-start gap-2 h-9 w-full">
|
||||
<EmojiIconPicker
|
||||
<EmojiPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={(val: boolean) => setIsOpen(val)}
|
||||
className="flex items-center justify-center flex-shrink0"
|
||||
|
|
@ -86,8 +86,8 @@ export const PageForm: React.FC<Props> = (props) => {
|
|||
|
||||
if (val?.type === "emoji")
|
||||
logoValue = {
|
||||
value: convertHexEmojiToDecimal(val.value.unified),
|
||||
url: val.value.imageUrl,
|
||||
value: val.value,
|
||||
url: undefined,
|
||||
};
|
||||
else if (val?.type === "icon") logoValue = val.value;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import React from "react";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
// plane imports
|
||||
import type { TPartialProject } from "@/plane-web/types";
|
||||
// local imports
|
||||
|
|
|
|||
|
|
@ -13,15 +13,13 @@ import { Disclosure, Transition } from "@headlessui/react";
|
|||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
// types
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { ChevronDownIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import type { IUserProfileProjectSegregation } from "@plane/types";
|
||||
// plane ui
|
||||
import { Loader } from "@plane/ui";
|
||||
import { cn, renderFormattedDate, getFileURL } from "@plane/utils";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useAppTheme } from "@/hooks/store/use-app-theme";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { ArchiveRestoreIcon, Check, ExternalLink, LinkIcon, Lock, Settings, Tras
|
|||
import { EUserPermissions, EUserPermissionsLevel, IS_FAVORITE_MENU_OPEN } from "@plane/constants";
|
||||
import { useLocalStorage } from "@plane/hooks";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { setPromiseToast, setToast, TOAST_TYPE } from "@plane/propel/toast";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import type { IProject } from "@plane/types";
|
||||
|
|
@ -16,7 +17,6 @@ import type { TContextMenuItem } from "@plane/ui";
|
|||
import { Avatar, AvatarGroup, ContextMenu, FavoriteStar } from "@plane/ui";
|
||||
import { copyUrlToClipboard, cn, getFileURL, renderFormattedDate } from "@plane/utils";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
|
|
|
|||
|
|
@ -3,14 +3,13 @@ import { Controller, useFormContext } from "react-hook-form";
|
|||
// plane imports
|
||||
import { ETabIndices } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmojiPicker, EmojiIconPickerTypes } from "@plane/propel/emoji-icon-picker";
|
||||
import { EmojiPicker, EmojiIconPickerTypes, Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { CloseIcon } from "@plane/propel/icons";
|
||||
// plane types
|
||||
import type { IProject } from "@plane/types";
|
||||
// plane ui
|
||||
import { getFileURL, getTabIndex } from "@plane/utils";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { ImagePickerPopover } from "@/components/core/image-picker-popover";
|
||||
// plane web imports
|
||||
import { ProjectTemplateSelect } from "@/plane-web/components/projects/create/template-select";
|
||||
|
|
|
|||
|
|
@ -8,14 +8,12 @@ import { NETWORK_CHOICES, PROJECT_TRACKER_ELEMENTS, PROJECT_TRACKER_EVENTS } fro
|
|||
import { useTranslation } from "@plane/i18n";
|
||||
// plane imports
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { EmojiPicker } from "@plane/propel/emoji-icon-picker";
|
||||
import { EmojiPicker, EmojiIconPickerTypes, Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import type { IProject, IWorkspace } from "@plane/types";
|
||||
import { CustomSelect, Input, TextArea, EmojiIconPickerTypes } from "@plane/ui";
|
||||
import { CustomSelect, Input, TextArea } from "@plane/ui";
|
||||
import { renderFormattedDate, getFileURL } from "@plane/utils";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { ImagePickerPopover } from "@/components/core/image-picker-popover";
|
||||
import { TimezoneSelect } from "@/components/global";
|
||||
// helpers
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { Combobox } from "@headlessui/react";
|
|||
// plane ui
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { CloseIcon } from "@plane/propel/icons";
|
||||
import { Checkbox, EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
|
|
@ -14,7 +15,6 @@ import { cn } from "@plane/utils";
|
|||
import darkProjectAsset from "@/app/assets/empty-state/search/project-dark.webp?url";
|
||||
import lightProjectAsset from "@/app/assets/empty-state/search/project-light.webp?url";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { SimpleEmptyState } from "@/components/empty-state/simple-empty-state-root";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import Link from "next/link";
|
|||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { Row } from "@plane/ui";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
// plane web imports
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import { observer } from "mobx-react";
|
|||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { PROJECT_SETTINGS_CATEGORIES, PROJECT_SETTINGS_CATEGORY } from "@plane/constants";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { getUserRole } from "@plane/utils";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
// local imports
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { Controller, useForm } from "react-hook-form";
|
|||
import { ETabIndices, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { EmojiPicker, EmojiIconPickerTypes } from "@plane/propel/emoji-icon-picker";
|
||||
import { EmojiPicker, EmojiIconPickerTypes, Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { ViewsIcon } from "@plane/propel/icons";
|
||||
import type {
|
||||
IIssueDisplayFilterOptions,
|
||||
|
|
@ -20,7 +20,6 @@ import { EViewAccess, EIssuesStoreType } from "@plane/types";
|
|||
import { Input, TextArea } from "@plane/ui";
|
||||
import { getComputedDisplayFilters, getComputedDisplayProperties, getTabIndex } from "@plane/utils";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { DisplayFiltersSelection, FiltersDropdown } from "@/components/issues/issue-layouts/filters";
|
||||
import { WorkItemFiltersRow } from "@/components/work-item-filters/filters-row";
|
||||
// hooks
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import type { FC } from "react";
|
|||
import { useRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { ViewsIcon } from "@plane/propel/icons";
|
||||
// types
|
||||
import type { IProjectView } from "@plane/types";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { ListItem } from "@/components/core/list";
|
||||
// hooks
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { PageIcon } from "@plane/propel/icons";
|
||||
// plane imports
|
||||
import type { IFavorite, TLogoProps } from "@plane/types";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
// plane web constants
|
||||
import { FAVORITE_ITEM_ICONS, FAVORITE_ITEM_LINKS } from "@/plane-web/constants/sidebar-favorites";
|
||||
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ import { Disclosure, Transition } from "@headlessui/react";
|
|||
import { EUserPermissions, EUserPermissionsLevel, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { ArchiveIcon, ChevronRightIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { CustomMenu, DropIndicator, DragHandle, ControlLink } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { Logo } from "@/components/common/logo";
|
||||
import { LeaveProjectModal } from "@/components/project/leave-project-modal";
|
||||
import { PublishProjectModal } from "@/components/project/publish-project/modal";
|
||||
// hooks
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@ import { set } from "lodash-es";
|
|||
import { action, computed, makeObservable, observable, reaction, runInAction } from "mobx";
|
||||
// plane imports
|
||||
import { EPageAccess } from "@plane/constants";
|
||||
import type { TChangeHandlerProps } from "@plane/propel/emoji-icon-picker";
|
||||
import type { TDocumentPayload, TLogoProps, TNameDescriptionLoader, TPage } from "@plane/types";
|
||||
import type { TChangeHandlerProps } from "@plane/ui";
|
||||
import { convertHexEmojiToDecimal } from "@plane/utils";
|
||||
// plane web store
|
||||
import { ExtendedBasePage } from "@/plane-web/store/pages/extended-base-page";
|
||||
import type { RootStore } from "@/plane-web/store/root.store";
|
||||
|
|
@ -448,8 +447,8 @@ export class BasePage extends ExtendedBasePage implements TBasePage {
|
|||
let logoValue = {};
|
||||
if (value?.type === "emoji")
|
||||
logoValue = {
|
||||
value: convertHexEmojiToDecimal(value.value.unified),
|
||||
url: value.value.imageUrl,
|
||||
value: value.value,
|
||||
url: undefined,
|
||||
};
|
||||
else if (value?.type === "icon") logoValue = value.value;
|
||||
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@
|
|||
"@plane/hooks": "workspace:*",
|
||||
"@plane/types": "workspace:*",
|
||||
"@plane/ui": "workspace:*",
|
||||
"@plane/utils": "workspace:*",
|
||||
"@plane/propel": "workspace:*",
|
||||
"@plane/utils": "workspace:*",
|
||||
"@tiptap/core": "catalog:",
|
||||
"@tiptap/extension-blockquote": "^2.22.3",
|
||||
"@tiptap/extension-character-count": "^2.22.3",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// plane imports
|
||||
import { EmojiIconPicker, EmojiIconPickerTypes, Logo, TEmojiLogoProps } from "@plane/ui";
|
||||
import { cn, convertHexEmojiToDecimal } from "@plane/utils";
|
||||
import { EmojiPicker, EmojiIconPickerTypes, Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import type { TLogoProps } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
// types
|
||||
import { TCalloutBlockAttributes } from "./types";
|
||||
// utils
|
||||
|
|
@ -17,7 +18,7 @@ type Props = {
|
|||
export const CalloutBlockLogoSelector: React.FC<Props> = (props) => {
|
||||
const { blockAttributes, disabled, handleOpen, isOpen, updateAttributes } = props;
|
||||
|
||||
const logoValue: TEmojiLogoProps = {
|
||||
const logoValue: TLogoProps = {
|
||||
in_use: blockAttributes["data-logo-in-use"],
|
||||
icon: {
|
||||
color: blockAttributes["data-icon-color"],
|
||||
|
|
@ -31,7 +32,7 @@ export const CalloutBlockLogoSelector: React.FC<Props> = (props) => {
|
|||
|
||||
return (
|
||||
<div contentEditable={false}>
|
||||
<EmojiIconPicker
|
||||
<EmojiPicker
|
||||
closeOnSelect={false}
|
||||
isOpen={isOpen}
|
||||
handleToggle={handleOpen}
|
||||
|
|
@ -43,7 +44,7 @@ export const CalloutBlockLogoSelector: React.FC<Props> = (props) => {
|
|||
onChange={(val) => {
|
||||
// construct the new logo value based on the type of value
|
||||
let newLogoValue: Partial<TCalloutBlockAttributes> = {};
|
||||
let newLogoValueToStoreInLocalStorage: TEmojiLogoProps = {
|
||||
let newLogoValueToStoreInLocalStorage: TLogoProps = {
|
||||
in_use: "emoji",
|
||||
emoji: {
|
||||
value: DEFAULT_CALLOUT_BLOCK_ATTRIBUTES["data-emoji-unicode"],
|
||||
|
|
@ -51,15 +52,16 @@ export const CalloutBlockLogoSelector: React.FC<Props> = (props) => {
|
|||
},
|
||||
};
|
||||
if (val.type === "emoji") {
|
||||
// val.value is now a string in decimal format (e.g. "128512")
|
||||
newLogoValue = {
|
||||
"data-emoji-unicode": convertHexEmojiToDecimal(val.value.unified),
|
||||
"data-emoji-url": val.value.imageUrl,
|
||||
"data-emoji-unicode": val.value,
|
||||
"data-emoji-url": undefined,
|
||||
};
|
||||
newLogoValueToStoreInLocalStorage = {
|
||||
in_use: "emoji",
|
||||
emoji: {
|
||||
value: convertHexEmojiToDecimal(val.value.unified),
|
||||
url: val.value.imageUrl,
|
||||
value: val.value,
|
||||
url: undefined,
|
||||
},
|
||||
};
|
||||
} else if (val.type === "icon") {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// plane imports
|
||||
import type { TEmojiLogoProps } from "@plane/ui";
|
||||
import type { TLogoProps } from "@plane/types";
|
||||
import { sanitizeHTML } from "@plane/utils";
|
||||
// types
|
||||
import {
|
||||
|
|
@ -33,7 +33,7 @@ export const getStoredLogo = (): TStoredLogoValue => {
|
|||
if (typeof window !== "undefined") {
|
||||
const storedData = sanitizeHTML(localStorage.getItem("editor-calloutComponent-logo") ?? "");
|
||||
if (storedData) {
|
||||
let parsedData: TEmojiLogoProps;
|
||||
let parsedData: TLogoProps;
|
||||
try {
|
||||
parsedData = JSON.parse(storedData);
|
||||
} catch (error) {
|
||||
|
|
@ -65,7 +65,7 @@ export const getStoredLogo = (): TStoredLogoValue => {
|
|||
return fallBackValues;
|
||||
};
|
||||
// function to update the stored logo on local storage
|
||||
export const updateStoredLogo = (value: TEmojiLogoProps): void => {
|
||||
export const updateStoredLogo = (value: TLogoProps): void => {
|
||||
if (typeof window === "undefined") return;
|
||||
localStorage.setItem("editor-calloutComponent-logo", JSON.stringify(value));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
export * from "./emoji-picker";
|
||||
export * from "./helper";
|
||||
export * from "./logo";
|
||||
export * from "./lucide-icons";
|
||||
export * from "./material-icons";
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ import type { FC } from "react";
|
|||
// eslint-disable-next-line import/order
|
||||
import useFontFaceObserver from "use-font-face-observer";
|
||||
// plane imports
|
||||
import { getEmojiSize, LUCIDE_ICONS_LIST, stringToEmoji } from "@plane/propel/emoji-icon-picker";
|
||||
import type { TLogoProps } from "@plane/types";
|
||||
// local imports
|
||||
import { getEmojiSize, stringToEmoji } from "./helper";
|
||||
import { LUCIDE_ICONS_LIST } from "./lucide-icons";
|
||||
|
||||
type Props = {
|
||||
logo: TLogoProps;
|
||||
|
|
@ -47,7 +47,6 @@
|
|||
"@popperjs/core": "^2.11.8",
|
||||
"@radix-ui/react-scroll-area": "^1.2.3",
|
||||
"clsx": "^2.0.0",
|
||||
"emoji-picker-react": "^4.5.16",
|
||||
"lodash-es": "catalog:",
|
||||
"lucide-react": "catalog:",
|
||||
"react-color": "^2.19.3",
|
||||
|
|
|
|||
|
|
@ -1,102 +0,0 @@
|
|||
import { Placement } from "@popperjs/core";
|
||||
import { EmojiClickData, Theme } from "emoji-picker-react";
|
||||
|
||||
export enum EmojiIconPickerTypes {
|
||||
EMOJI = "emoji",
|
||||
ICON = "icon",
|
||||
}
|
||||
|
||||
export const TABS_LIST = [
|
||||
{
|
||||
key: EmojiIconPickerTypes.EMOJI,
|
||||
title: "Emojis",
|
||||
},
|
||||
{
|
||||
key: EmojiIconPickerTypes.ICON,
|
||||
title: "Icons",
|
||||
},
|
||||
];
|
||||
|
||||
export type TChangeHandlerProps =
|
||||
| {
|
||||
type: EmojiIconPickerTypes.EMOJI;
|
||||
value: EmojiClickData;
|
||||
}
|
||||
| {
|
||||
type: EmojiIconPickerTypes.ICON;
|
||||
value: {
|
||||
name: string;
|
||||
color: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TCustomEmojiPicker = {
|
||||
isOpen: boolean;
|
||||
handleToggle: (value: boolean) => void;
|
||||
buttonClassName?: string;
|
||||
className?: string;
|
||||
closeOnSelect?: boolean;
|
||||
defaultIconColor?: string;
|
||||
defaultOpen?: EmojiIconPickerTypes;
|
||||
disabled?: boolean;
|
||||
dropdownClassName?: string;
|
||||
label: React.ReactNode;
|
||||
onChange: (value: TChangeHandlerProps) => void;
|
||||
placement?: Placement;
|
||||
searchDisabled?: boolean;
|
||||
searchPlaceholder?: string;
|
||||
theme?: Theme;
|
||||
iconType?: "material" | "lucide";
|
||||
};
|
||||
|
||||
export const DEFAULT_COLORS = ["#95999f", "#6d7b8a", "#5e6ad2", "#02b5ed", "#02b55c", "#f2be02", "#e57a00", "#f38e82"];
|
||||
|
||||
export type TIconsListProps = {
|
||||
defaultColor: string;
|
||||
onChange: (val: { name: string; color: string }) => void;
|
||||
searchDisabled?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adjusts the given hex color to ensure it has enough contrast.
|
||||
* @param {string} hex - The hex color code input by the user.
|
||||
* @returns {string} - The adjusted hex color code.
|
||||
*/
|
||||
export const adjustColorForContrast = (hex: string): string => {
|
||||
// Ensure hex color is valid
|
||||
if (!/^#([0-9A-F]{3}){1,2}$/i.test(hex)) {
|
||||
throw new Error("Invalid hex color code");
|
||||
}
|
||||
|
||||
// Convert hex to RGB
|
||||
let r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
if (hex.length === 4) {
|
||||
r = parseInt(hex[1] + hex[1], 16);
|
||||
g = parseInt(hex[2] + hex[2], 16);
|
||||
b = parseInt(hex[3] + hex[3], 16);
|
||||
} else if (hex.length === 7) {
|
||||
r = parseInt(hex[1] + hex[2], 16);
|
||||
g = parseInt(hex[3] + hex[4], 16);
|
||||
b = parseInt(hex[5] + hex[6], 16);
|
||||
}
|
||||
|
||||
// Calculate luminance
|
||||
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
||||
|
||||
// If the color is too light, darken it
|
||||
if (luminance > 0.5) {
|
||||
r = Math.max(0, r - 50);
|
||||
g = Math.max(0, g - 50);
|
||||
b = Math.max(0, b - 50);
|
||||
}
|
||||
|
||||
// Convert RGB back to hex
|
||||
const toHex = (value: number): string => {
|
||||
const hex = value.toString(16);
|
||||
return hex.length === 1 ? "0" + hex : hex;
|
||||
};
|
||||
|
||||
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
||||
};
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
import { Popover, Tab } from "@headlessui/react";
|
||||
import EmojiPicker from "emoji-picker-react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { usePopper } from "react-popper";
|
||||
// plane helpers
|
||||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
// helpers
|
||||
import { cn } from "../utils";
|
||||
// hooks
|
||||
import { EmojiIconPickerTypes, TABS_LIST, TCustomEmojiPicker } from "./emoji-icon-helper";
|
||||
import { LucideIconsList } from "./lucide-icons-list";
|
||||
// helpers
|
||||
|
||||
export const EmojiIconPicker: React.FC<TCustomEmojiPicker> = (props) => {
|
||||
const {
|
||||
isOpen,
|
||||
handleToggle,
|
||||
buttonClassName,
|
||||
className,
|
||||
closeOnSelect = true,
|
||||
defaultIconColor = "#6d7b8a",
|
||||
defaultOpen = EmojiIconPickerTypes.EMOJI,
|
||||
disabled = false,
|
||||
dropdownClassName,
|
||||
label,
|
||||
onChange,
|
||||
placement = "bottom-start",
|
||||
searchDisabled = false,
|
||||
searchPlaceholder = "Search",
|
||||
theme,
|
||||
} = props;
|
||||
// refs
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
// popper-js
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement,
|
||||
modifiers: [
|
||||
{
|
||||
name: "preventOverflow",
|
||||
options: {
|
||||
padding: 20,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// close dropdown on outside click
|
||||
useOutsideClickDetector(containerRef, () => handleToggle(false));
|
||||
|
||||
return (
|
||||
<Popover as="div" className={cn("relative", className)}>
|
||||
<>
|
||||
<Popover.Button as={React.Fragment}>
|
||||
<button
|
||||
type="button"
|
||||
ref={setReferenceElement}
|
||||
className={cn("outline-none", buttonClassName)}
|
||||
disabled={disabled}
|
||||
onClick={() => handleToggle(!isOpen)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
</Popover.Button>
|
||||
{isOpen && (
|
||||
<Popover.Panel className="fixed z-10" static>
|
||||
<div
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
className={cn(
|
||||
"w-80 bg-custom-background-100 rounded-md border-[0.5px] border-custom-border-300 overflow-hidden",
|
||||
dropdownClassName
|
||||
)}
|
||||
>
|
||||
<Tab.Group
|
||||
ref={containerRef}
|
||||
as="div"
|
||||
className="h-full w-full flex flex-col overflow-hidden"
|
||||
defaultIndex={TABS_LIST.findIndex((tab) => tab.key === defaultOpen)}
|
||||
>
|
||||
<Tab.List as="div" className="grid grid-cols-2 gap-1 p-2">
|
||||
{TABS_LIST.map((tab) => (
|
||||
<Tab
|
||||
key={tab.key}
|
||||
className={({ selected }) =>
|
||||
cn("py-1 text-sm rounded border border-custom-border-200", {
|
||||
"bg-custom-background-80": selected,
|
||||
"hover:bg-custom-background-90 focus:bg-custom-background-90": !selected,
|
||||
})
|
||||
}
|
||||
>
|
||||
{tab.title}
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
<Tab.Panels as="div" className="h-full w-full overflow-y-auto">
|
||||
<Tab.Panel>
|
||||
<EmojiPicker
|
||||
onEmojiClick={(val) => {
|
||||
onChange({
|
||||
type: EmojiIconPickerTypes.EMOJI,
|
||||
value: val,
|
||||
});
|
||||
if (closeOnSelect) handleToggle(false);
|
||||
}}
|
||||
height="20rem"
|
||||
width="100%"
|
||||
theme={theme}
|
||||
searchDisabled={searchDisabled}
|
||||
searchPlaceholder={searchPlaceholder}
|
||||
previewConfig={{
|
||||
showPreview: false,
|
||||
}}
|
||||
lazyLoadEmojis
|
||||
/>
|
||||
</Tab.Panel>
|
||||
<Tab.Panel className="h-80 w-full relative overflow-hidden overflow-y-auto">
|
||||
<LucideIconsList
|
||||
defaultColor={defaultIconColor}
|
||||
onChange={(val) => {
|
||||
onChange({
|
||||
type: EmojiIconPickerTypes.ICON,
|
||||
value: val,
|
||||
});
|
||||
if (closeOnSelect) handleToggle(false);
|
||||
}}
|
||||
searchDisabled={searchDisabled}
|
||||
/>
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
)}
|
||||
</>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
import { Popover, Tab } from "@headlessui/react";
|
||||
import EmojiPicker from "emoji-picker-react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { usePopper } from "react-popper";
|
||||
// plane helpers
|
||||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
// components
|
||||
import { cn } from "../utils";
|
||||
import { EmojiIconPickerTypes, TABS_LIST, TCustomEmojiPicker } from "./emoji-icon-helper";
|
||||
import { IconsList } from "./icons-list";
|
||||
// helpers
|
||||
// hooks
|
||||
|
||||
export const CustomEmojiIconPicker: React.FC<TCustomEmojiPicker> = (props) => {
|
||||
const {
|
||||
isOpen,
|
||||
handleToggle,
|
||||
buttonClassName,
|
||||
className,
|
||||
closeOnSelect = true,
|
||||
defaultIconColor = "#6d7b8a",
|
||||
defaultOpen = EmojiIconPickerTypes.EMOJI,
|
||||
disabled = false,
|
||||
dropdownClassName,
|
||||
label,
|
||||
onChange,
|
||||
placement = "bottom-start",
|
||||
searchDisabled = false,
|
||||
searchPlaceholder = "Search",
|
||||
theme,
|
||||
} = props;
|
||||
// refs
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
// popper-js
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement,
|
||||
modifiers: [
|
||||
{
|
||||
name: "preventOverflow",
|
||||
options: {
|
||||
padding: 20,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// close dropdown on outside click
|
||||
useOutsideClickDetector(containerRef, () => handleToggle(false));
|
||||
|
||||
return (
|
||||
<Popover as="div" className={cn("relative", className)}>
|
||||
<>
|
||||
<Popover.Button as={React.Fragment}>
|
||||
<button
|
||||
type="button"
|
||||
ref={setReferenceElement}
|
||||
className={cn("outline-none", buttonClassName)}
|
||||
disabled={disabled}
|
||||
onClick={() => handleToggle(!isOpen)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
</Popover.Button>
|
||||
{isOpen && (
|
||||
<Popover.Panel className="fixed z-10" static>
|
||||
<div
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
className={cn(
|
||||
"w-80 bg-custom-background-100 rounded-md border-[0.5px] border-custom-border-300 overflow-hidden",
|
||||
dropdownClassName
|
||||
)}
|
||||
>
|
||||
<Tab.Group
|
||||
ref={containerRef}
|
||||
as="div"
|
||||
className="h-full w-full flex flex-col overflow-hidden"
|
||||
defaultIndex={TABS_LIST.findIndex((tab) => tab.key === defaultOpen)}
|
||||
>
|
||||
<Tab.List as="div" className="grid grid-cols-2 gap-1 p-2">
|
||||
{TABS_LIST.map((tab) => (
|
||||
<Tab
|
||||
key={tab.key}
|
||||
className={({ selected }) =>
|
||||
cn("py-1 text-sm rounded border border-custom-border-200", {
|
||||
"bg-custom-background-80": selected,
|
||||
"hover:bg-custom-background-90 focus:bg-custom-background-90": !selected,
|
||||
})
|
||||
}
|
||||
>
|
||||
{tab.title}
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
<Tab.Panels as="div" className="h-full w-full overflow-y-auto">
|
||||
<Tab.Panel>
|
||||
<EmojiPicker
|
||||
onEmojiClick={(val) => {
|
||||
onChange({
|
||||
type: EmojiIconPickerTypes.EMOJI,
|
||||
value: val,
|
||||
});
|
||||
if (closeOnSelect) handleToggle(false);
|
||||
}}
|
||||
height="20rem"
|
||||
width="100%"
|
||||
theme={theme}
|
||||
searchDisabled={searchDisabled}
|
||||
searchPlaceholder={searchPlaceholder}
|
||||
previewConfig={{
|
||||
showPreview: false,
|
||||
}}
|
||||
/>
|
||||
</Tab.Panel>
|
||||
<Tab.Panel className="h-80 w-full relative overflow-hidden overflow-y-auto">
|
||||
<IconsList
|
||||
defaultColor={defaultIconColor}
|
||||
onChange={(val) => {
|
||||
onChange({
|
||||
type: EmojiIconPickerTypes.ICON,
|
||||
value: val,
|
||||
});
|
||||
if (closeOnSelect) handleToggle(false);
|
||||
}}
|
||||
searchDisabled={searchDisabled}
|
||||
/>
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
)}
|
||||
</>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
export const emojiCodeToUnicode = (emoji: string) => {
|
||||
if (!emoji) return "";
|
||||
|
||||
// convert emoji code to unicode
|
||||
const uniCodeEmoji = emoji
|
||||
.toString()
|
||||
.split("-")
|
||||
.map((emoji) => parseInt(emoji, 10).toString(16))
|
||||
.join("-");
|
||||
|
||||
return uniCodeEmoji;
|
||||
};
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { Search } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
// icons
|
||||
import useFontFaceObserver from "use-font-face-observer";
|
||||
import { InfoIcon } from "@plane/propel/icons";
|
||||
import { MATERIAL_ICONS_LIST } from "..";
|
||||
import { Input } from "../form-fields";
|
||||
import { cn } from "../utils";
|
||||
// components
|
||||
// hooks
|
||||
// helpers
|
||||
import { DEFAULT_COLORS, TIconsListProps, adjustColorForContrast } from "./emoji-icon-helper";
|
||||
|
||||
export const IconsList: React.FC<TIconsListProps> = (props) => {
|
||||
const { defaultColor, onChange, searchDisabled = false } = props;
|
||||
// states
|
||||
const [activeColor, setActiveColor] = useState(defaultColor);
|
||||
const [showHexInput, setShowHexInput] = useState(false);
|
||||
const [hexValue, setHexValue] = useState("");
|
||||
const [isInputFocused, setIsInputFocused] = useState(false);
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (DEFAULT_COLORS.includes(defaultColor.toLowerCase())) setShowHexInput(false);
|
||||
else {
|
||||
setHexValue(defaultColor.slice(1, 7));
|
||||
setShowHexInput(true);
|
||||
}
|
||||
}, [defaultColor]);
|
||||
|
||||
const filteredArray = MATERIAL_ICONS_LIST.filter((icon) => icon.name.toLowerCase().includes(query.toLowerCase()));
|
||||
|
||||
const isMaterialSymbolsFontLoaded = useFontFaceObserver([
|
||||
{
|
||||
family: `Material Symbols Rounded`,
|
||||
style: `normal`,
|
||||
weight: `normal`,
|
||||
stretch: `condensed`,
|
||||
},
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col sticky top-0 bg-custom-background-100">
|
||||
{!searchDisabled && (
|
||||
<div className="flex items-center px-2 py-[15px] w-full ">
|
||||
<div
|
||||
className={`relative flex items-center gap-2 bg-custom-background-90 h-10 rounded-lg w-full px-[30px] border ${isInputFocused ? "border-custom-primary-100" : "border-transparent"}`}
|
||||
onFocus={() => setIsInputFocused(true)}
|
||||
onBlur={() => setIsInputFocused(false)}
|
||||
>
|
||||
<Search className="absolute left-2.5 bottom-3 h-3.5 w-3.5 text-custom-text-400" />
|
||||
<Input
|
||||
placeholder="Search"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
className="text-[1rem] border-none p-0 h-full w-full "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-cols-9 gap-2 items-center justify-items-center px-2.5 py-1 h-9">
|
||||
{showHexInput ? (
|
||||
<div className="col-span-8 flex items-center gap-1 justify-self-stretch ml-2">
|
||||
<span
|
||||
className="h-4 w-4 flex-shrink-0 rounded-full mr-1"
|
||||
style={{
|
||||
backgroundColor: `#${hexValue}`,
|
||||
}}
|
||||
/>
|
||||
<span className="text-xs text-custom-text-300 flex-shrink-0">HEX</span>
|
||||
<span className="text-xs text-custom-text-200 flex-shrink-0 -mr-1">#</span>
|
||||
<Input
|
||||
type="text"
|
||||
value={hexValue}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setHexValue(value);
|
||||
if (/^[0-9A-Fa-f]{6}$/.test(value)) setActiveColor(adjustColorForContrast(`#${value}`));
|
||||
}}
|
||||
className="flex-grow pl-0 text-xs text-custom-text-200"
|
||||
mode="true-transparent"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
DEFAULT_COLORS.map((curCol) => (
|
||||
<button
|
||||
key={curCol}
|
||||
type="button"
|
||||
className="grid place-items-center size-5"
|
||||
onClick={() => {
|
||||
setActiveColor(curCol);
|
||||
setHexValue(curCol.slice(1, 7));
|
||||
}}
|
||||
>
|
||||
<span className="h-4 w-4 cursor-pointer rounded-full" style={{ backgroundColor: curCol }} />
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className={cn("grid place-items-center h-4 w-4 rounded-full border border-transparent", {
|
||||
"border-custom-border-400": !showHexInput,
|
||||
})}
|
||||
onClick={() => {
|
||||
setShowHexInput((prevData) => !prevData);
|
||||
setHexValue(activeColor.slice(1, 7));
|
||||
}}
|
||||
>
|
||||
{showHexInput ? (
|
||||
<span className="conical-gradient h-4 w-4 rounded-full" />
|
||||
) : (
|
||||
<span className="text-custom-text-300 text-[0.6rem] grid place-items-center">#</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 w-full pl-4 pr-3 py-1 h-6">
|
||||
<InfoIcon className="h-3 w-3" />
|
||||
<p className="text-xs"> Colors will be adjusted to ensure sufficient contrast.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-8 gap-1 px-2.5 justify-items-center mt-2">
|
||||
{filteredArray.map((icon) => (
|
||||
<button
|
||||
key={icon.name}
|
||||
type="button"
|
||||
className="h-9 w-9 select-none text-lg grid place-items-center rounded hover:bg-custom-background-80"
|
||||
onClick={() => {
|
||||
onChange({
|
||||
name: icon.name,
|
||||
color: activeColor,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{isMaterialSymbolsFontLoaded ? (
|
||||
<span
|
||||
style={{ color: activeColor }}
|
||||
className="material-symbols-rounded !text-[1.25rem] !leading-[1.25rem]"
|
||||
>
|
||||
{icon.name}
|
||||
</span>
|
||||
) : (
|
||||
<span className="size-5 rounded animate-pulse bg-custom-background-80" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
export * from "./emoji-icon-picker-new";
|
||||
export * from "./emoji-icon-picker";
|
||||
export * from "./emoji-icon-helper";
|
||||
export * from "./logo";
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { Emoji } from "emoji-picker-react";
|
||||
import React, { FC } from "react";
|
||||
import useFontFaceObserver from "use-font-face-observer";
|
||||
// local imports
|
||||
import { LUCIDE_ICONS_LIST } from "..";
|
||||
import { emojiCodeToUnicode } from "./helpers";
|
||||
|
||||
export type TEmojiLogoProps = {
|
||||
in_use: "emoji" | "icon";
|
||||
emoji?: {
|
||||
value?: string;
|
||||
url?: string;
|
||||
};
|
||||
icon?: {
|
||||
name?: string;
|
||||
color?: string;
|
||||
};
|
||||
};
|
||||
|
||||
type Props = {
|
||||
logo: TEmojiLogoProps;
|
||||
size?: number;
|
||||
type?: "lucide" | "material";
|
||||
};
|
||||
|
||||
export const Logo: FC<Props> = (props) => {
|
||||
const { logo, size = 16, type = "material" } = props;
|
||||
|
||||
// destructuring the logo object
|
||||
const { in_use, emoji, icon } = logo;
|
||||
|
||||
// if no in_use value, return empty fragment
|
||||
if (!in_use) return <></>;
|
||||
|
||||
// derived values
|
||||
const value = in_use === "emoji" ? emoji?.value : icon?.name;
|
||||
const color = icon?.color;
|
||||
const lucideIcon = LUCIDE_ICONS_LIST.find((item) => item.name === value);
|
||||
|
||||
const isMaterialSymbolsFontLoaded = useFontFaceObserver([
|
||||
{
|
||||
family: `Material Symbols Rounded`,
|
||||
style: `normal`,
|
||||
weight: `normal`,
|
||||
stretch: `condensed`,
|
||||
},
|
||||
]);
|
||||
// if no value, return empty fragment
|
||||
if (!value) return <></>;
|
||||
|
||||
if (!isMaterialSymbolsFontLoaded) {
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
height: size,
|
||||
width: size,
|
||||
}}
|
||||
className="rounded animate-pulse bg-custom-background-80"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// emoji
|
||||
if (in_use === "emoji") {
|
||||
return <Emoji unified={emojiCodeToUnicode(value)} size={size} />;
|
||||
}
|
||||
|
||||
// icon
|
||||
if (in_use === "icon") {
|
||||
return (
|
||||
<>
|
||||
{type === "lucide" ? (
|
||||
<>
|
||||
{lucideIcon && (
|
||||
<lucideIcon.element
|
||||
style={{
|
||||
color: color,
|
||||
height: size,
|
||||
width: size,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<span
|
||||
className="material-symbols-rounded"
|
||||
style={{
|
||||
fontSize: size,
|
||||
color: color,
|
||||
scale: "115%",
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// if no value, return empty fragment
|
||||
return <></>;
|
||||
};
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
import { Search } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
// local imports
|
||||
import { InfoIcon } from "@plane/propel/icons";
|
||||
import { LUCIDE_ICONS_LIST } from "..";
|
||||
import { Input } from "../form-fields";
|
||||
import { cn } from "../utils";
|
||||
import { DEFAULT_COLORS, TIconsListProps, adjustColorForContrast } from "./emoji-icon-helper";
|
||||
|
||||
export const LucideIconsList: React.FC<TIconsListProps> = (props) => {
|
||||
const { defaultColor, onChange, searchDisabled = false } = props;
|
||||
// states
|
||||
const [activeColor, setActiveColor] = useState(defaultColor);
|
||||
const [showHexInput, setShowHexInput] = useState(false);
|
||||
const [hexValue, setHexValue] = useState("");
|
||||
const [isInputFocused, setIsInputFocused] = useState(false);
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (DEFAULT_COLORS.includes(defaultColor.toLowerCase())) setShowHexInput(false);
|
||||
else {
|
||||
setHexValue(defaultColor.slice(1, 7));
|
||||
setShowHexInput(true);
|
||||
}
|
||||
}, [defaultColor]);
|
||||
|
||||
const filteredArray = LUCIDE_ICONS_LIST.filter((icon) => icon.name.toLowerCase().includes(query.toLowerCase()));
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col sticky top-0 bg-custom-background-100">
|
||||
{!searchDisabled && (
|
||||
<div className="flex items-center px-2 py-[15px] w-full ">
|
||||
<div
|
||||
className={`relative flex items-center gap-2 bg-custom-background-90 h-10 rounded-lg w-full px-[30px] border ${isInputFocused ? "border-custom-primary-100" : "border-transparent"}`}
|
||||
onFocus={() => setIsInputFocused(true)}
|
||||
onBlur={() => setIsInputFocused(false)}
|
||||
>
|
||||
<Search className="absolute left-2.5 bottom-3 h-3.5 w-3.5 text-custom-text-400" />
|
||||
<Input
|
||||
placeholder="Search"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
className="text-[1rem] border-none p-0 h-full w-full "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-cols-9 gap-2 items-center justify-items-center px-2.5 py-1 h-9">
|
||||
{showHexInput ? (
|
||||
<div className="col-span-8 flex items-center gap-1 justify-self-stretch ml-2">
|
||||
<span
|
||||
className="h-4 w-4 flex-shrink-0 rounded-full mr-1"
|
||||
style={{
|
||||
backgroundColor: `#${hexValue}`,
|
||||
}}
|
||||
/>
|
||||
<span className="text-xs text-custom-text-300 flex-shrink-0">HEX</span>
|
||||
<span className="text-xs text-custom-text-200 flex-shrink-0 -mr-1">#</span>
|
||||
<Input
|
||||
type="text"
|
||||
value={hexValue}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setHexValue(value);
|
||||
if (/^[0-9A-Fa-f]{6}$/.test(value)) setActiveColor(adjustColorForContrast(`#${value}`));
|
||||
}}
|
||||
className="flex-grow pl-0 text-xs text-custom-text-200"
|
||||
mode="true-transparent"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
DEFAULT_COLORS.map((curCol) => (
|
||||
<button
|
||||
key={curCol}
|
||||
type="button"
|
||||
className="grid place-items-center size-5"
|
||||
onClick={() => {
|
||||
setActiveColor(curCol);
|
||||
setHexValue(curCol.slice(1, 7));
|
||||
}}
|
||||
>
|
||||
<span className="h-4 w-4 cursor-pointer rounded-full" style={{ backgroundColor: curCol }} />
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className={cn("grid place-items-center h-4 w-4 rounded-full border border-transparent", {
|
||||
"border-custom-border-400": !showHexInput,
|
||||
})}
|
||||
onClick={() => {
|
||||
setShowHexInput((prevData) => !prevData);
|
||||
setHexValue(activeColor.slice(1, 7));
|
||||
}}
|
||||
>
|
||||
{showHexInput ? (
|
||||
<span className="conical-gradient h-4 w-4 rounded-full" />
|
||||
) : (
|
||||
<span className="text-custom-text-300 text-[0.6rem] grid place-items-center">#</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 w-full pl-4 pr-3 py-1 h-6">
|
||||
<InfoIcon className="flex-shrink-0 h-3 w-3" />
|
||||
<p className="!text-xs"> Colors will be adjusted to ensure sufficient contrast.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-8 gap-1 px-2.5 justify-items-center mt-2">
|
||||
{filteredArray.map((icon) => (
|
||||
<button
|
||||
key={icon.name}
|
||||
type="button"
|
||||
className="h-9 w-9 select-none text-lg grid place-items-center rounded hover:bg-custom-background-80"
|
||||
onClick={() => {
|
||||
onChange({
|
||||
name: icon.name,
|
||||
color: activeColor,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<icon.element style={{ color: activeColor }} className="size-4" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -12,7 +12,6 @@ export * from "./drag-handle";
|
|||
export * from "./drop-indicator";
|
||||
export * from "./dropdown";
|
||||
export * from "./dropdowns";
|
||||
export * from "./emoji";
|
||||
export * from "./favorite-star";
|
||||
export * from "./form-fields";
|
||||
export * from "./header";
|
||||
|
|
|
|||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
|
|
@ -1385,9 +1385,6 @@ importers:
|
|||
clsx:
|
||||
specifier: ^2.0.0
|
||||
version: 2.1.1
|
||||
emoji-picker-react:
|
||||
specifier: ^4.5.16
|
||||
version: 4.12.2(react@18.3.1)
|
||||
lodash-es:
|
||||
specifier: 'catalog:'
|
||||
version: 4.17.21
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue