fix(web,ui): fix hydration error from nested button descendants and MobX strict-mode warnings
Some checks failed
Deploy Plane Project Tracker / Deploy to cisystem (push) Failing after 5m16s
Some checks failed
Deploy Plane Project Tracker / Deploy to cisystem (push) Failing after 5m16s
- Replace nested <Button> with styled <div> in DropdownButton (BorderButton, BackgroundButton, TransparentButton) across all issue dropdowns - Support 'div' variant and element in AppSidebarItem, DragHandle, and CustomMenu triggers/items to avoid invalid HTML button nesting - Fix MobX strict-mode observable mutations in IssueRootStore autorun and BaseWorkspaceRootStore fetchWorkspaces with runInAction - Defer WorkItemFilterStore filter registration via queueMicrotask to eliminate cross-component render updates on WorkItemFiltersToggle - Remove redundant e.preventDefault() in Kanban card actions to prevent blocking native/synthetic event propagation
This commit is contained in:
parent
31110e92c5
commit
e67f0cf15d
18 changed files with 138 additions and 131 deletions
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import React from "react";
|
||||
// helpers
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { getButtonStyling } from "@plane/propel/button";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import { cn } from "@plane/utils";
|
||||
// types
|
||||
|
|
@ -76,10 +76,9 @@ function BorderButton(props: ButtonProps) {
|
|||
layout="stacked"
|
||||
disabled={!showTooltip || isMobile}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
<div
|
||||
className={cn(
|
||||
getButtonStyling("ghost", "sm"),
|
||||
"flex h-full w-full items-center justify-start gap-1.5 border-[0.5px] border-strong",
|
||||
{
|
||||
"bg-layer-transparent-active": isActive,
|
||||
|
|
@ -88,7 +87,7 @@ function BorderButton(props: ButtonProps) {
|
|||
)}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
|
@ -102,16 +101,15 @@ function BackgroundButton(props: ButtonProps) {
|
|||
layout="stacked"
|
||||
disabled={!showTooltip || isMobile}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
<div
|
||||
className={cn(
|
||||
getButtonStyling("ghost", "sm"),
|
||||
"flex h-full w-full items-center justify-between gap-1.5 bg-layer-3 hover:bg-layer-1-hover",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
|
@ -125,10 +123,9 @@ function TransparentButton(props: ButtonProps) {
|
|||
layout="stacked"
|
||||
disabled={!showTooltip || isMobile}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
<div
|
||||
className={cn(
|
||||
getButtonStyling("ghost", "sm"),
|
||||
"flex h-full w-full items-center justify-between gap-1.5",
|
||||
{
|
||||
"bg-layer-transparent-active": isActive,
|
||||
|
|
@ -137,7 +134,7 @@ function TransparentButton(props: ButtonProps) {
|
|||
)}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,9 +47,6 @@ export const SubIssuesListItemProperties = observer(function SubIssuesListItemPr
|
|||
// oxlint-disable-next-line unicorn/consistent-function-scoping
|
||||
const handleEventPropagation = (e: SyntheticEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation();
|
||||
if (e.type !== "focus") {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
const handleStartDate = (date: Date | null) => {
|
||||
|
|
|
|||
|
|
@ -87,7 +87,6 @@ const KanbanIssueDetailsBlock = observer(function KanbanIssueDetailsBlock(props:
|
|||
// oxlint-disable-next-line unicorn/consistent-function-scoping
|
||||
const handleEventPropagation = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
useOutsideClickDetector(menuActionRef, () => setIsMenuActive(false));
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ interface AppSidebarItemData {
|
|||
}
|
||||
|
||||
interface AppSidebarItemProps {
|
||||
variant?: "link" | "button";
|
||||
variant?: "link" | "button" | "div";
|
||||
item?: AppSidebarItemData;
|
||||
}
|
||||
|
||||
|
|
@ -47,6 +47,7 @@ interface AppSidebarButtonItemProps {
|
|||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
as?: "button" | "div";
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -107,7 +108,15 @@ function AppSidebarLinkItem({ href, children, className }: AppSidebarLinkItemPro
|
|||
);
|
||||
}
|
||||
|
||||
function AppSidebarButtonItem({ children, onClick, disabled = false, className }: AppSidebarButtonItemProps) {
|
||||
function AppSidebarButtonItem({ as: Component = "button", children, onClick, disabled = false, className }: AppSidebarButtonItemProps) {
|
||||
if (Component === "div") {
|
||||
return (
|
||||
<div className={cn(styles.base, className)} onClick={onClick}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={cn(styles.base, className)} onClick={onClick} disabled={disabled} type="button">
|
||||
{children}
|
||||
|
|
@ -142,6 +151,14 @@ function AppSidebarItem({ variant = "link", item }: AppSidebarItemProps) {
|
|||
return <AppSidebarLinkItem href={href}>{commonItems}</AppSidebarLinkItem>;
|
||||
}
|
||||
|
||||
if (variant === "div") {
|
||||
return (
|
||||
<AppSidebarButtonItem as="div" onClick={onClick} disabled={disabled}>
|
||||
{commonItems}
|
||||
</AppSidebarButtonItem>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AppSidebarButtonItem onClick={onClick} disabled={disabled}>
|
||||
{commonItems}
|
||||
|
|
|
|||
|
|
@ -172,8 +172,7 @@ export const ExtendedSidebarItem = observer(function ExtendedSidebarItem(props:
|
|||
>
|
||||
{!disableDrag && (
|
||||
<Tooltip label={t("drag_to_rearrange")} align="start" disabled={isDragging}>
|
||||
<button
|
||||
type="button"
|
||||
<div
|
||||
className={cn(
|
||||
"absolute top-1/2 -left-3 flex -translate-y-1/2 cursor-grab items-center justify-center rounded text-placeholder opacity-0 group-hover/project-item:opacity-100",
|
||||
{
|
||||
|
|
@ -183,8 +182,8 @@ export const ExtendedSidebarItem = observer(function ExtendedSidebarItem(props:
|
|||
)}
|
||||
ref={dragHandleRef}
|
||||
>
|
||||
<DragHandle className="bg-transparent" />
|
||||
</button>
|
||||
<DragHandle className="bg-transparent" as="div" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
<SidebarNavItem isActive={isActive}>
|
||||
|
|
|
|||
|
|
@ -191,8 +191,7 @@ export function FavoriteFolder(props: Props) {
|
|||
align="end"
|
||||
disabled={isDragging || isMobile}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
<div
|
||||
className={cn(
|
||||
"absolute top-1/2 -left-3 hidden -translate-y-1/2 cursor-grab items-center justify-center rounded-sm text-placeholder group-hover/project-item:flex",
|
||||
{
|
||||
|
|
@ -201,8 +200,8 @@ export function FavoriteFolder(props: Props) {
|
|||
}
|
||||
)}
|
||||
>
|
||||
<DragHandle className="bg-transparent" />
|
||||
</button>
|
||||
<DragHandle className="bg-transparent" as="div" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div className="grid size-5 flex-shrink-0 place-items-center">
|
||||
<FavoriteFolderIcon />
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export const HelpMenuRoot = observer(function HelpMenuRoot() {
|
|||
<CustomMenu
|
||||
customButton={
|
||||
<AppSidebarItem
|
||||
variant="button"
|
||||
variant="div"
|
||||
item={{
|
||||
icon: <HelpOutline className="size-5" />,
|
||||
isActive: isNeedHelpOpen,
|
||||
|
|
@ -59,23 +59,11 @@ export const HelpMenuRoot = observer(function HelpMenuRoot() {
|
|||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
<div className="my-1 border-t border-subtle" />
|
||||
<CustomMenu.MenuItem>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleShortcutsListModal(true)}
|
||||
className="justify-sbg-layer-211 flex w-full items-center hover:bg-layer-1"
|
||||
>
|
||||
<span className="text-11">{t("keyboard_shortcuts")}</span>
|
||||
</button>
|
||||
<CustomMenu.MenuItem onClick={() => toggleShortcutsListModal(true)}>
|
||||
<span className="text-11">{t("keyboard_shortcuts")}</span>
|
||||
</CustomMenu.MenuItem>
|
||||
<CustomMenu.MenuItem>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setProductUpdatesModalOpen(true)}
|
||||
className="justify-sbg-layer-211 flex w-full items-center hover:bg-layer-1"
|
||||
>
|
||||
<span className="text-11">{t("whats_new")}</span>
|
||||
</button>
|
||||
<CustomMenu.MenuItem onClick={() => setProductUpdatesModalOpen(true)}>
|
||||
<span className="text-11">{t("whats_new")}</span>
|
||||
</CustomMenu.MenuItem>
|
||||
<CustomMenu.MenuItem onClick={() => window.open("https://forum.plane.so", "_blank", "noopener,noreferrer")}>
|
||||
<div className="flex items-center gap-x-2 rounded-sm text-11">
|
||||
|
|
|
|||
|
|
@ -313,8 +313,7 @@ export const SidebarProjectsListItem = observer(function SidebarProjectsListItem
|
|||
align="end"
|
||||
disabled={isDragging || isMobile}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
<div
|
||||
className={cn(
|
||||
"absolute top-1/2 -left-3 hidden -translate-y-1/2 cursor-grab items-center justify-center rounded-sm text-placeholder group-hover/project-item:flex",
|
||||
{
|
||||
|
|
@ -325,8 +324,8 @@ export const SidebarProjectsListItem = observer(function SidebarProjectsListItem
|
|||
)}
|
||||
ref={dragHandleRef}
|
||||
>
|
||||
<DragHandle className="bg-transparent" />
|
||||
</button>
|
||||
<DragHandle className="bg-transparent" as="div" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export const UserMenuRoot = observer(function UserMenuRoot() {
|
|||
className="flex items-center"
|
||||
customButton={
|
||||
<AppSidebarItem
|
||||
variant="button"
|
||||
variant="div"
|
||||
item={{
|
||||
icon: (
|
||||
<Avatar
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ export const WorkspaceMenuRoot = observer(function WorkspaceMenuRoot(props: Work
|
|||
})}
|
||||
>
|
||||
<AppSidebarItem
|
||||
variant="button"
|
||||
variant="div"
|
||||
item={{
|
||||
icon: (
|
||||
<WorkspaceLogo
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { isEmpty } from "lodash-es";
|
||||
import { autorun, makeObservable, observable } from "mobx";
|
||||
import { autorun, makeObservable, observable, runInAction } from "mobx";
|
||||
// types
|
||||
import type { ICycle, IIssueLabel, IModule, IProject, IState, IUserLite, TIssueServiceType } from "@plane/types";
|
||||
import { EIssueServiceType } from "@plane/types";
|
||||
|
|
@ -200,26 +200,28 @@ export class IssueRootStore implements IIssueRootStore {
|
|||
this.rootStore = rootStore;
|
||||
|
||||
autorun(() => {
|
||||
if (rootStore?.user?.data?.id) this.currentUserId = rootStore?.user?.data?.id;
|
||||
if (this.workspaceSlug !== rootStore.router.workspaceSlug) this.workspaceSlug = rootStore.router.workspaceSlug;
|
||||
if (this.teamspaceId !== rootStore.router.teamspaceId) this.teamspaceId = rootStore.router.teamspaceId;
|
||||
if (this.projectId !== rootStore.router.projectId) this.projectId = rootStore.router.projectId;
|
||||
if (this.cycleId !== rootStore.router.cycleId) this.cycleId = rootStore.router.cycleId;
|
||||
if (this.moduleId !== rootStore.router.moduleId) this.moduleId = rootStore.router.moduleId;
|
||||
if (this.viewId !== rootStore.router.viewId) this.viewId = rootStore.router.viewId;
|
||||
if (this.globalViewId !== rootStore.router.globalViewId) this.globalViewId = rootStore.router.globalViewId;
|
||||
if (this.userId !== rootStore.router.userId) this.userId = rootStore.router.userId;
|
||||
if (!isEmpty(rootStore?.state?.stateMap)) this.stateMap = rootStore?.state?.stateMap;
|
||||
if (!isEmpty(rootStore?.state?.projectStates)) this.stateDetails = rootStore?.state?.projectStates;
|
||||
if (!isEmpty(rootStore?.state?.workspaceStates)) this.workspaceStateDetails = rootStore?.state?.workspaceStates;
|
||||
if (!isEmpty(rootStore?.label?.labelMap)) this.labelMap = rootStore?.label?.labelMap;
|
||||
if (!isEmpty(rootStore?.memberRoot?.workspace?.workspaceMemberMap))
|
||||
this.workSpaceMemberRolesMap = rootStore?.memberRoot?.workspace?.memberMap || undefined;
|
||||
if (!isEmpty(rootStore?.memberRoot?.memberMap)) this.memberMap = rootStore?.memberRoot?.memberMap || undefined;
|
||||
if (!isEmpty(rootStore?.projectRoot?.project?.projectMap))
|
||||
this.projectMap = rootStore?.projectRoot?.project?.projectMap;
|
||||
if (!isEmpty(rootStore?.module?.moduleMap)) this.moduleMap = rootStore?.module?.moduleMap;
|
||||
if (!isEmpty(rootStore?.cycle?.cycleMap)) this.cycleMap = rootStore?.cycle?.cycleMap;
|
||||
runInAction(() => {
|
||||
if (rootStore?.user?.data?.id) this.currentUserId = rootStore?.user?.data?.id;
|
||||
if (this.workspaceSlug !== rootStore.router.workspaceSlug) this.workspaceSlug = rootStore.router.workspaceSlug;
|
||||
if (this.teamspaceId !== rootStore.router.teamspaceId) this.teamspaceId = rootStore.router.teamspaceId;
|
||||
if (this.projectId !== rootStore.router.projectId) this.projectId = rootStore.router.projectId;
|
||||
if (this.cycleId !== rootStore.router.cycleId) this.cycleId = rootStore.router.cycleId;
|
||||
if (this.moduleId !== rootStore.router.moduleId) this.moduleId = rootStore.router.moduleId;
|
||||
if (this.viewId !== rootStore.router.viewId) this.viewId = rootStore.router.viewId;
|
||||
if (this.globalViewId !== rootStore.router.globalViewId) this.globalViewId = rootStore.router.globalViewId;
|
||||
if (this.userId !== rootStore.router.userId) this.userId = rootStore.router.userId;
|
||||
if (!isEmpty(rootStore?.state?.stateMap)) this.stateMap = rootStore?.state?.stateMap;
|
||||
if (!isEmpty(rootStore?.state?.projectStates)) this.stateDetails = rootStore?.state?.projectStates;
|
||||
if (!isEmpty(rootStore?.state?.workspaceStates)) this.workspaceStateDetails = rootStore?.state?.workspaceStates;
|
||||
if (!isEmpty(rootStore?.label?.labelMap)) this.labelMap = rootStore?.label?.labelMap;
|
||||
if (!isEmpty(rootStore?.memberRoot?.workspace?.workspaceMemberMap))
|
||||
this.workSpaceMemberRolesMap = rootStore?.memberRoot?.workspace?.memberMap || undefined;
|
||||
if (!isEmpty(rootStore?.memberRoot?.memberMap)) this.memberMap = rootStore?.memberRoot?.memberMap || undefined;
|
||||
if (!isEmpty(rootStore?.projectRoot?.project?.projectMap))
|
||||
this.projectMap = rootStore?.projectRoot?.project?.projectMap;
|
||||
if (!isEmpty(rootStore?.module?.moduleMap)) this.moduleMap = rootStore?.module?.moduleMap;
|
||||
if (!isEmpty(rootStore?.cycle?.cycleMap)) this.cycleMap = rootStore?.cycle?.cycleMap;
|
||||
});
|
||||
});
|
||||
|
||||
this.issues = new IssueStore();
|
||||
|
|
|
|||
|
|
@ -190,7 +190,9 @@ export class BaseWorkspaceRootStore implements IWorkspaceRootStore {
|
|||
});
|
||||
return workspaceResponse;
|
||||
} finally {
|
||||
this.loader = false;
|
||||
runInAction(() => {
|
||||
this.loader = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { action, makeObservable, observable } from "mobx";
|
||||
import { action, makeObservable, observable, runInAction } from "mobx";
|
||||
import { computedFn } from "mobx-utils";
|
||||
// plane imports
|
||||
import type { TExpressionOptions } from "@plane/constants";
|
||||
|
|
@ -49,6 +49,7 @@ export interface IWorkItemFilterStore {
|
|||
export class WorkItemFilterStore implements IWorkItemFilterStore {
|
||||
// observable
|
||||
filters: IWorkItemFilterStore["filters"];
|
||||
private _pendingFilters = new Map<TWorkItemFilterKey, IWorkItemFilterInstance>();
|
||||
|
||||
constructor() {
|
||||
this.filters = new Map<TWorkItemFilterKey, IWorkItemFilterInstance>();
|
||||
|
|
@ -69,9 +70,10 @@ export class WorkItemFilterStore implements IWorkItemFilterStore {
|
|||
* @param entityId - The entity id.
|
||||
* @returns The filter instance.
|
||||
*/
|
||||
getFilter: IWorkItemFilterStore["getFilter"] = computedFn((entityType, entityId) =>
|
||||
this.filters.get(this._getFilterKey(entityType, entityId))
|
||||
);
|
||||
getFilter: IWorkItemFilterStore["getFilter"] = computedFn((entityType, entityId) => {
|
||||
const key = this._getFilterKey(entityType, entityId);
|
||||
return this.filters.get(key) ?? this._pendingFilters.get(key);
|
||||
});
|
||||
|
||||
// ------------ actions ------------
|
||||
|
||||
|
|
@ -80,7 +82,8 @@ export class WorkItemFilterStore implements IWorkItemFilterStore {
|
|||
* If the instance already exists, updates its expression options to ensure they're current.
|
||||
*/
|
||||
getOrCreateFilter: IWorkItemFilterStore["getOrCreateFilter"] = action((params) => {
|
||||
const existingFilter = this.getFilter(params.entityType, params.entityId);
|
||||
const filterKey = this._getFilterKey(params.entityType, params.entityId);
|
||||
const existingFilter = this._pendingFilters.get(filterKey) ?? this.getFilter(params.entityType, params.entityId);
|
||||
if (existingFilter) {
|
||||
// Update expression options on existing filter to ensure they're current
|
||||
if (params.expressionOptions) {
|
||||
|
|
@ -99,8 +102,13 @@ export class WorkItemFilterStore implements IWorkItemFilterStore {
|
|||
|
||||
// create new filter instance
|
||||
const newFilter = this._initializeFilterInstance(params);
|
||||
const filterKey = this._getFilterKey(params.entityType, params.entityId);
|
||||
this.filters.set(filterKey, newFilter);
|
||||
this._pendingFilters.set(filterKey, newFilter);
|
||||
queueMicrotask(() => {
|
||||
runInAction(() => {
|
||||
this._pendingFilters.delete(filterKey);
|
||||
this.filters.set(filterKey, newFilter);
|
||||
});
|
||||
});
|
||||
|
||||
return newFilter;
|
||||
});
|
||||
|
|
@ -193,7 +201,9 @@ export class WorkItemFilterStore implements IWorkItemFilterStore {
|
|||
* @param entityId - The entity id.
|
||||
*/
|
||||
deleteFilter: IWorkItemFilterStore["deleteFilter"] = action((entityType, entityId) => {
|
||||
this.filters.delete(this._getFilterKey(entityType, entityId));
|
||||
const key = this._getFilterKey(entityType, entityId);
|
||||
this._pendingFilters.delete(key);
|
||||
this.filters.delete(key);
|
||||
});
|
||||
|
||||
// ------------ private helpers ------------
|
||||
|
|
|
|||
|
|
@ -12,23 +12,24 @@ import { cn } from "./utils";
|
|||
interface IDragHandle {
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
as?: "button" | "div";
|
||||
}
|
||||
|
||||
export const DragHandle = forwardRef(function DragHandle(
|
||||
props: IDragHandle,
|
||||
ref: React.ForwardedRef<HTMLButtonElement | null>
|
||||
ref: React.ForwardedRef<any>
|
||||
) {
|
||||
const { className, disabled = false } = props;
|
||||
const { className, disabled = false, as: Component = "button" } = props;
|
||||
|
||||
if (disabled) {
|
||||
return <div className="h-[18px] w-[14px]" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
<Component
|
||||
type={Component === "button" ? "button" : undefined}
|
||||
className={cn("flex flex-shrink-0 cursor-grab rounded-sm bg-surface-2 p-0.5 text-secondary", className)}
|
||||
onContextMenu={(e) => {
|
||||
onContextMenu={(e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
|
|
@ -36,7 +37,7 @@ export const DragHandle = forwardRef(function DragHandle(
|
|||
>
|
||||
<MoreVerticalOutline className="h-3.5 w-3.5 text-placeholder" />
|
||||
<MoreVerticalOutline className="-ml-5 h-3.5 w-3.5 text-placeholder" />
|
||||
</button>
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -246,18 +246,17 @@ function CustomMenu(props: ICustomMenuDropdownProps) {
|
|||
{({ open }) => (
|
||||
<>
|
||||
{customButton ? (
|
||||
<Menu.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
onClick={handleMenuButtonClick}
|
||||
className={customButtonClassName}
|
||||
tabIndex={customButtonTabIndex}
|
||||
disabled={disabled}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
{customButton}
|
||||
</button>
|
||||
// oxlint-disable-next-line jsx_a11y/click-events-have-key-events oxlint-disable-next-line jsx_a11y/no-static-element-interactions
|
||||
<Menu.Button
|
||||
as="div"
|
||||
ref={setReferenceElement}
|
||||
onClick={handleMenuButtonClick}
|
||||
className={customButtonClassName}
|
||||
tabIndex={customButtonTabIndex}
|
||||
disabled={disabled}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
{customButton}
|
||||
</Menu.Button>
|
||||
) : (
|
||||
<>
|
||||
|
|
@ -462,32 +461,33 @@ function SubMenu(props: ICustomSubMenuProps) {
|
|||
}
|
||||
|
||||
function MenuItem(props: ICustomMenuItemProps) {
|
||||
const { children, disabled = false, onClick, className } = props;
|
||||
const { children, disabled = false, onClick, className, as: Component = "button" } = props;
|
||||
const submenuContext = useSubMenu();
|
||||
|
||||
return (
|
||||
<Menu.Item as="div" disabled={disabled}>
|
||||
{({ active, close }) => (
|
||||
<button
|
||||
type="button"
|
||||
<Component
|
||||
type={Component === "button" ? "button" : undefined}
|
||||
className={cn(
|
||||
"w-full truncate rounded-sm px-1 py-1.5 text-left text-secondary select-none",
|
||||
{
|
||||
"bg-layer-transparent-hover": active && !disabled,
|
||||
"text-placeholder": disabled,
|
||||
"cursor-pointer": Component === "div" && !disabled,
|
||||
},
|
||||
className
|
||||
)}
|
||||
onClick={(e) => {
|
||||
onClick={(e: React.MouseEvent) => {
|
||||
close();
|
||||
onClick?.(e);
|
||||
// Close submenu if this item is inside a submenu
|
||||
submenuContext?.closeSubmenu();
|
||||
}}
|
||||
disabled={disabled}
|
||||
disabled={Component === "button" ? disabled : undefined}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
</Component>
|
||||
)}
|
||||
</Menu.Item>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -99,22 +99,20 @@ export function CustomSearchSelect(props: ICustomSearchSelectProps) {
|
|||
return (
|
||||
<>
|
||||
{customButton ? (
|
||||
<Combobox.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between gap-1 text-11",
|
||||
{
|
||||
"cursor-not-allowed text-secondary": disabled,
|
||||
"cursor-pointer hover:bg-layer-transparent-hover": !disabled,
|
||||
},
|
||||
customButtonClassName
|
||||
)}
|
||||
onClick={toggleDropdown}
|
||||
>
|
||||
{customButton}
|
||||
</button>
|
||||
<Combobox.Button
|
||||
as="div"
|
||||
ref={setReferenceElement}
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between gap-1 text-11",
|
||||
{
|
||||
"cursor-not-allowed text-secondary": disabled,
|
||||
"cursor-pointer hover:bg-layer-transparent-hover": !disabled,
|
||||
},
|
||||
customButtonClassName
|
||||
)}
|
||||
onClick={toggleDropdown}
|
||||
>
|
||||
{customButton}
|
||||
</Combobox.Button>
|
||||
) : (
|
||||
<Combobox.Button as={React.Fragment}>
|
||||
|
|
|
|||
|
|
@ -82,17 +82,15 @@ function CustomSelect(props: ICustomSelectProps) {
|
|||
>
|
||||
<>
|
||||
{customButton ? (
|
||||
<Combobox.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 rounded text-11 ${
|
||||
disabled ? "cursor-not-allowed text-secondary" : "cursor-pointer hover:bg-layer-transparent-hover"
|
||||
} ${customButtonClassName}`}
|
||||
onClick={toggleDropdown}
|
||||
>
|
||||
{customButton}
|
||||
</button>
|
||||
<Combobox.Button
|
||||
as="div"
|
||||
ref={setReferenceElement}
|
||||
className={`flex items-center justify-between gap-1 rounded text-11 ${
|
||||
disabled ? "cursor-not-allowed text-secondary" : "cursor-pointer hover:bg-layer-transparent-hover"
|
||||
} ${customButtonClassName}`}
|
||||
onClick={toggleDropdown}
|
||||
>
|
||||
{customButton}
|
||||
</Combobox.Button>
|
||||
) : (
|
||||
<Combobox.Button as={React.Fragment}>
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ export interface ICustomMenuItemProps {
|
|||
disabled?: boolean;
|
||||
onClick?: (args?: any) => void;
|
||||
className?: string;
|
||||
as?: "button" | "div";
|
||||
}
|
||||
|
||||
export interface ICustomSelectItemProps {
|
||||
|
|
|
|||
Loading…
Reference in a new issue