mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
04c4f0a203
New context menu options are available in the directory preview to create and rename files and directories It's missing three pieces of functionality, none of which are a regression: - Editing or creating an entry does not update the focused index. Focus index right now is pretty dumb, it doesn't factor in the column sorting so if you change that, the selected item will change to whatever is now at that index. We should update this so we use the actual file name to determine which element to focus and let the table determine which index to then highlight given the current sorting algo - Open in native preview should not be an option on remote connections with the exception of WSL, where it should resolve the file in the Windows filesystem, rather than the WSL one - We should catch CRUD errors in the dir preview and display a popup
189 lines
5.6 KiB
TypeScript
189 lines
5.6 KiB
TypeScript
// Copyright 2024, Command Line Inc.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
import { Button } from "@/element/button";
|
|
import {
|
|
autoUpdate,
|
|
FloatingPortal,
|
|
Middleware,
|
|
offset as offsetMiddleware,
|
|
useClick,
|
|
useDismiss,
|
|
useFloating,
|
|
useInteractions,
|
|
type OffsetOptions,
|
|
type Placement,
|
|
} from "@floating-ui/react";
|
|
import clsx from "clsx";
|
|
import {
|
|
Children,
|
|
cloneElement,
|
|
forwardRef,
|
|
isValidElement,
|
|
JSXElementConstructor,
|
|
memo,
|
|
ReactElement,
|
|
ReactNode,
|
|
useState,
|
|
} from "react";
|
|
|
|
import "./popover.scss";
|
|
|
|
interface PopoverProps {
|
|
children: ReactNode;
|
|
className?: string;
|
|
placement?: Placement;
|
|
offset?: OffsetOptions;
|
|
onDismiss?: () => void;
|
|
middleware?: Middleware[];
|
|
}
|
|
|
|
const isPopoverButton = (
|
|
element: ReactElement
|
|
): element is ReactElement<PopoverButtonProps, JSXElementConstructor<PopoverButtonProps>> => {
|
|
return element.type === PopoverButton;
|
|
};
|
|
|
|
const isPopoverContent = (
|
|
element: ReactElement
|
|
): element is ReactElement<PopoverContentProps, JSXElementConstructor<PopoverContentProps>> => {
|
|
return element.type === PopoverContent;
|
|
};
|
|
|
|
const Popover = memo(
|
|
({ children, className, placement = "bottom-start", offset = 3, onDismiss, middleware }: PopoverProps) => {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
|
|
const handleOpenChange = (open: boolean) => {
|
|
setIsOpen(open);
|
|
if (!open && onDismiss) {
|
|
onDismiss();
|
|
}
|
|
};
|
|
|
|
if (offset === undefined) {
|
|
offset = 3;
|
|
}
|
|
|
|
middleware ??= [];
|
|
middleware.push(offsetMiddleware(offset));
|
|
|
|
const { refs, floatingStyles, context } = useFloating({
|
|
placement,
|
|
open: isOpen,
|
|
onOpenChange: handleOpenChange,
|
|
middleware: middleware,
|
|
whileElementsMounted: autoUpdate,
|
|
});
|
|
|
|
const click = useClick(context);
|
|
const dismiss = useDismiss(context);
|
|
const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss]);
|
|
|
|
const renderChildren = Children.map(children, (child) => {
|
|
if (isValidElement(child)) {
|
|
if (isPopoverButton(child)) {
|
|
return cloneElement(child as any, {
|
|
isActive: isOpen,
|
|
ref: refs.setReference,
|
|
getReferenceProps,
|
|
// Do not overwrite onClick
|
|
});
|
|
}
|
|
|
|
if (isPopoverContent(child)) {
|
|
return isOpen
|
|
? cloneElement(child as any, {
|
|
ref: refs.setFloating,
|
|
style: floatingStyles,
|
|
getFloatingProps,
|
|
})
|
|
: null;
|
|
}
|
|
}
|
|
return child;
|
|
});
|
|
|
|
return <div className={clsx("popover", className)}>{renderChildren}</div>;
|
|
}
|
|
);
|
|
|
|
interface PopoverButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
isActive?: boolean;
|
|
children: React.ReactNode;
|
|
getReferenceProps?: () => any;
|
|
as?: keyof JSX.IntrinsicElements | React.ComponentType<any>;
|
|
}
|
|
|
|
const PopoverButton = forwardRef<HTMLButtonElement | HTMLDivElement, PopoverButtonProps>(
|
|
(
|
|
{
|
|
isActive,
|
|
children,
|
|
onClick: userOnClick, // Destructured from props
|
|
getReferenceProps,
|
|
className,
|
|
as: Component = "button",
|
|
...props // The rest of the props, without onClick
|
|
},
|
|
ref
|
|
) => {
|
|
const referenceProps = getReferenceProps?.() || {};
|
|
const popoverOnClick = referenceProps.onClick;
|
|
|
|
// Remove onClick from referenceProps to prevent it from overwriting our combinedOnClick
|
|
const { onClick: refOnClick, ...restReferenceProps } = referenceProps;
|
|
|
|
const combinedOnClick = (event: React.MouseEvent) => {
|
|
if (userOnClick) {
|
|
userOnClick(event as any); // Our custom onClick logic
|
|
}
|
|
if (popoverOnClick) {
|
|
popoverOnClick(event); // Popover's onClick logic
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Button
|
|
ref={ref}
|
|
className={clsx("popover-button", className, { "is-active": isActive })}
|
|
{...props} // Spread the rest of the props
|
|
{...restReferenceProps} // Spread referenceProps without onClick
|
|
onClick={combinedOnClick} // Assign combined onClick after spreading
|
|
>
|
|
{children}
|
|
</Button>
|
|
);
|
|
}
|
|
);
|
|
|
|
interface PopoverContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
children: React.ReactNode;
|
|
getFloatingProps?: () => any;
|
|
}
|
|
|
|
const PopoverContent = forwardRef<HTMLDivElement, PopoverContentProps>(
|
|
({ children, className, getFloatingProps, style, ...props }, ref) => {
|
|
return (
|
|
<FloatingPortal>
|
|
<div
|
|
ref={ref}
|
|
className={clsx("popover-content", className)}
|
|
style={style}
|
|
{...getFloatingProps?.()}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</div>
|
|
</FloatingPortal>
|
|
);
|
|
}
|
|
);
|
|
|
|
Popover.displayName = "Popover";
|
|
PopoverButton.displayName = "PopoverButton";
|
|
PopoverContent.displayName = "PopoverContent";
|
|
|
|
export { Popover, PopoverButton, PopoverContent };
|
|
export type { PopoverButtonProps, PopoverContentProps };
|