onChange(val)}
options={options}
className="border-none p-0"
- customButton={
+ menuButton={
{value && value.length > 3
@@ -63,7 +63,7 @@ export const ProjectSelect = observer(function ProjectSelect(props: Props) {
}
- customButtonClassName="border-none p-0 bg-transparent hover:bg-transparent w-auto h-auto"
+ menuButtonWrapperClassName="h-auto w-auto border-none bg-transparent p-0 hover:bg-transparent"
multiple
/>
);
diff --git a/plane-src/apps/web/core/components/automation/auto-close-automation.tsx b/plane-src/apps/web/core/components/automation/auto-close-automation.tsx
index 48c7203..9857511 100644
--- a/plane-src/apps/web/core/components/automation/auto-close-automation.tsx
+++ b/plane-src/apps/web/core/components/automation/auto-close-automation.tsx
@@ -13,7 +13,7 @@ import { PROJECT_AUTOMATION_MONTHS, EUserPermissions, EUserPermissionsLevel, EIc
import { useTranslation } from "@plane/i18n";
import { StateGroupIcon, StatePropertyIcon } from "@plane/propel/icons";
import type { IProject } from "@plane/types";
-import { CustomSelect, CustomSearchSelect, ToggleSwitch, Loader } from "@plane/ui";
+import { CustomSelect, Loader, SearchSelectionDropdown, ToggleSwitch } from "@plane/ui";
import { SelectMonthModal } from "@/components/automation";
import { SettingsControlItem } from "@/components/settings/control-item";
// hooks
@@ -151,7 +151,7 @@ export const AutoCloseAutomation = observer(function AutoCloseAutomation(props:
{t("project_settings.automations.auto-close.auto_close_status")}
-
diff --git a/plane-src/apps/web/core/components/exporter/export-form.tsx b/plane-src/apps/web/core/components/exporter/export-form.tsx
index 8fde52e..04bb6f8 100644
--- a/plane-src/apps/web/core/components/exporter/export-form.tsx
+++ b/plane-src/apps/web/core/components/exporter/export-form.tsx
@@ -21,7 +21,7 @@ import { TOAST_TYPE, setToast } from "@plane/propel/toast";
// import { Tooltip } from "@plane/propel/tooltip";
// import { EIssuesStoreType } from "@plane/types";
import type { TWorkItemFilterExpression } from "@plane/types";
-import { CustomSearchSelect, CustomSelect } from "@plane/ui";
+import { CustomSelect, SearchSelectionDropdown } from "@plane/ui";
// import { WorkspaceLevelWorkItemFiltersHOC } from "@/components/work-item-filters/filters-hoc/workspace-level";
// import { WorkItemFiltersRow } from "@/components/work-item-filters/filters-row";
import { useProject } from "@/hooks/store/use-project";
@@ -155,7 +155,7 @@ export const ExportForm = observer(function ExportForm(props: Props) {
name="project"
disabled={!isMember && (!hasProjects || !canPerformAnyCreateAction)}
render={({ field: { value, onChange } }) => (
- onChange(val)}
options={options}
diff --git a/plane-src/apps/web/core/components/exporter/export-modal.tsx b/plane-src/apps/web/core/components/exporter/export-modal.tsx
index bf840de..ad0eaa0 100644
--- a/plane-src/apps/web/core/components/exporter/export-modal.tsx
+++ b/plane-src/apps/web/core/components/exporter/export-modal.tsx
@@ -14,7 +14,7 @@ import { Button } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IUser, IImporterService } from "@plane/types";
// ui
-import { Checkbox, CustomSearchSelect, EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
+import { Checkbox, EModalPosition, EModalWidth, ModalCore, SearchSelectionDropdown } from "@plane/ui";
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useUser } from "@/hooks/store/user";
@@ -122,7 +122,7 @@ export const Exporter = observer(function Exporter(props: Props) {
-
onChange(val)}
options={options}
diff --git a/plane-src/apps/web/core/components/global/timezone-select.tsx b/plane-src/apps/web/core/components/global/timezone-select.tsx
index a4d692a..44fa2c4 100644
--- a/plane-src/apps/web/core/components/global/timezone-select.tsx
+++ b/plane-src/apps/web/core/components/global/timezone-select.tsx
@@ -6,7 +6,7 @@
import { observer } from "mobx-react";
// plane imports
-import { CustomSearchSelect } from "@plane/ui";
+import { SearchSelectionDropdown } from "@plane/ui";
import { cn } from "@plane/utils";
// hooks
import useTimezone from "@/hooks/use-timezone";
@@ -39,7 +39,7 @@ export const TimezoneSelect = observer(function TimezoneSelect(props: TTimezoneS
return (
-
{
diff --git a/plane-src/apps/web/core/components/navigation/project-header.tsx b/plane-src/apps/web/core/components/navigation/project-header.tsx
index 134c8d3..f7534c8 100644
--- a/plane-src/apps/web/core/components/navigation/project-header.tsx
+++ b/plane-src/apps/web/core/components/navigation/project-header.tsx
@@ -9,7 +9,7 @@ import { observer } from "mobx-react";
// plane imports
import { ProjectIcon } from "@plane/propel/icons";
import type { ICustomSearchSelectOption } from "@plane/types";
-import { CustomSearchSelect } from "@plane/ui";
+import { SearchSelectionDropdown } from "@plane/ui";
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user";
@@ -100,13 +100,13 @@ export const ProjectHeader = observer(function ProjectHeader(props: TProjectHead
if (!currentProjectDetails) return null;
return (
- : null}
+ menuButton={currentProjectDetails ? : null}
className="h-full rounded"
- customButtonClassName="group flex items-center gap-0.5 rounded-sm hover:bg-surface-2 outline-none cursor-pointer h-full"
+ menuButtonWrapperClassName="group flex h-full cursor-pointer items-center gap-0.5 rounded-sm outline-none hover:bg-surface-2"
/>
);
});
diff --git a/plane-src/apps/web/core/components/project/member-select.tsx b/plane-src/apps/web/core/components/project/member-select.tsx
index 2a9b67f..c299d3a 100644
--- a/plane-src/apps/web/core/components/project/member-select.tsx
+++ b/plane-src/apps/web/core/components/project/member-select.tsx
@@ -11,7 +11,7 @@ import { Ban } from "lucide-react";
import { useTranslation } from "@plane/i18n";
import { EUserProjectRoles } from "@plane/types";
// plane ui
-import { Avatar, CustomSearchSelect } from "@plane/ui";
+import { Avatar, SearchSelectionDropdown } from "@plane/ui";
// helpers
import { getFileURL } from "@plane/utils";
// hooks
@@ -62,7 +62,7 @@ export const MemberSelect = observer(function MemberSelect(props: Props) {
const selectedOption = projectId ? getProjectMemberDetails(value, projectId.toString()) : null;
return (
-
diff --git a/plane-src/apps/web/core/components/project/send-project-invitation-modal.tsx b/plane-src/apps/web/core/components/project/send-project-invitation-modal.tsx
index 2a06726..f87e842 100644
--- a/plane-src/apps/web/core/components/project/send-project-invitation-modal.tsx
+++ b/plane-src/apps/web/core/components/project/send-project-invitation-modal.tsx
@@ -13,7 +13,7 @@ import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button";
import { PlusIcon, CloseIcon, ChevronDownIcon } from "@plane/propel/icons";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
-import { Avatar, CustomSelect, CustomSearchSelect, EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
+import { Avatar, CustomSelect, EModalPosition, EModalWidth, ModalCore, SearchSelectionDropdown } from "@plane/ui";
// helpers
import { getFileURL } from "@plane/utils";
// hooks
@@ -193,10 +193,10 @@ export const SendProjectInvitationModal = observer(function SendProjectInvitatio
render={({ field: { value, onChange } }) => {
const selectedMember = getWorkspaceMemberDetails(value);
return (
-
+ menuButton={
+ <>
{value && value !== "" ? (
)}
-
+ >
}
+ menuButtonWrapperClassName="shadow-sm flex w-full items-center justify-between gap-1 rounded-md border border-subtle px-3 py-2 text-left text-13 text-secondary duration-300 hover:bg-layer-1 hover:text-primary focus:outline-none"
onChange={(val: string) => {
onChange(val);
// Update the role to the workspace role when member ID changes
diff --git a/plane-src/packages/ui/src/breadcrumbs/navigation-search-dropdown.tsx b/plane-src/packages/ui/src/breadcrumbs/navigation-search-dropdown.tsx
index 598064e..a4c687c 100644
--- a/plane-src/packages/ui/src/breadcrumbs/navigation-search-dropdown.tsx
+++ b/plane-src/packages/ui/src/breadcrumbs/navigation-search-dropdown.tsx
@@ -5,9 +5,8 @@
*/
import * as React from "react";
-import { useState } from "react";
import type { ICustomSearchSelectOption } from "@plane/types";
-import { CustomSearchSelect } from "../dropdowns";
+import { SearchSelectionDropdown } from "../dropdowns";
import { cn } from "../utils";
import { Breadcrumbs } from "./breadcrumbs";
@@ -42,18 +41,10 @@ export function BreadcrumbNavigationSearchDropdown(props: TBreadcrumbNavigationS
rotateChevronWhenLast = true,
showLastChevron = true,
} = props;
- // state
- const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const shouldOpenOnItemClick = openOnLabelClick || !handleOnClick;
return (
- {
- setIsDropdownOpen(true);
- }}
- onClose={() => {
- setIsDropdownOpen(false);
- }}
+ {
@@ -61,7 +52,7 @@ export function BreadcrumbNavigationSearchDropdown(props: TBreadcrumbNavigationS
onChange?.(value);
}
}}
- customButton={
+ menuButton={({ open }) => (
<>
{
@@ -92,27 +83,26 @@ export function BreadcrumbNavigationSearchDropdown(props: TBreadcrumbNavigationS
{(!isLast || showLastChevron) && (
)}
>
- }
+ )}
disabled={navigationDisabled}
className="h-full rounded-sm"
- customButtonClassName={cn(
- "group flex h-full cursor-pointer items-center gap-0.5 rounded-sm outline-none hover:bg-surface-2",
- {
- "bg-surface-2": isDropdownOpen,
- }
- )}
+ menuButtonWrapperClassName={({ open }) =>
+ cn("group flex h-full cursor-pointer items-center gap-0.5 rounded-sm outline-none hover:bg-surface-2", {
+ "bg-surface-2": open,
+ })
+ }
/>
);
}
diff --git a/plane-src/packages/ui/src/dropdowns/index.ts b/plane-src/packages/ui/src/dropdowns/index.ts
index 7498cf5..fc1800a 100644
--- a/plane-src/packages/ui/src/dropdowns/index.ts
+++ b/plane-src/packages/ui/src/dropdowns/index.ts
@@ -6,6 +6,7 @@
export * from "./context-menu";
export * from "./action-dropdown";
+export * from "./search-selection-dropdown";
export * from "./custom-menu";
export * from "./custom-select";
export * from "./custom-search-select";
diff --git a/plane-src/packages/ui/src/dropdowns/search-selection-dropdown.tsx b/plane-src/packages/ui/src/dropdowns/search-selection-dropdown.tsx
new file mode 100644
index 0000000..c966d74
--- /dev/null
+++ b/plane-src/packages/ui/src/dropdowns/search-selection-dropdown.tsx
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
+import type { ReactNode } from "react";
+import { useMemo, useState } from "react";
+import type { ICustomSearchSelectOption } from "@plane/types";
+import { CustomSearchSelect } from "./custom-search-select";
+import type { IDropdownProps } from "./helper";
+
+export type TSearchSelectionDropdownOption = ICustomSearchSelectOption & {
+ shouldRender?: boolean;
+};
+
+type TSearchSelectionDropdownBaseProps = Omit & {
+ footerOption?: ReactNode;
+ menuButton?: ReactNode | ((props: { open: boolean }) => ReactNode);
+ menuButtonWrapperClassName?: string | ((props: { open: boolean }) => string);
+ noResultsMessage?: string;
+ onChange: (value: any) => void;
+ onClose?: () => void;
+ options?: TSearchSelectionDropdownOption[];
+};
+
+type TSingleValueProps = {
+ multiple?: false;
+ value: any;
+};
+
+type TMultipleValuesProps = {
+ multiple: true;
+ value: any[] | null;
+};
+
+type Props = TSearchSelectionDropdownBaseProps & (TSingleValueProps | TMultipleValuesProps);
+
+export function SearchSelectionDropdown(props: Props) {
+ const {
+ defaultOpen = false,
+ menuButton,
+ menuButtonWrapperClassName,
+ onOpen,
+ onClose,
+ options,
+ ...rest
+ } = props;
+
+ const [isOpen, setIsOpen] = useState(defaultOpen);
+
+ const renderedOptions = useMemo(
+ () => options?.filter((option) => option.shouldRender !== false),
+ [options]
+ );
+
+ const resolvedMenuButton = typeof menuButton === "function" ? menuButton({ open: isOpen }) : menuButton;
+ const resolvedMenuButtonWrapperClassName =
+ typeof menuButtonWrapperClassName === "function"
+ ? menuButtonWrapperClassName({ open: isOpen })
+ : menuButtonWrapperClassName;
+
+ return (
+ {
+ setIsOpen(false);
+ onClose?.();
+ }}
+ onOpen={() => {
+ setIsOpen(true);
+ onOpen?.();
+ }}
+ options={renderedOptions}
+ />
+ );
+}