mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-26 03:11:12 +01:00
Git support (#143)
This commit is contained in:
@@ -36,17 +36,23 @@ import { HotKey } from './HotKey';
|
||||
import { Icon } from './Icon';
|
||||
import { Separator } from './Separator';
|
||||
import { HStack, VStack } from './Stacks';
|
||||
import { LoadingIcon } from './LoadingIcon';
|
||||
|
||||
export type DropdownItemSeparator = {
|
||||
type: 'separator';
|
||||
label?: string;
|
||||
label?: ReactNode;
|
||||
hidden?: boolean;
|
||||
};
|
||||
|
||||
export type DropdownItemContent = {
|
||||
type: 'content';
|
||||
label?: ReactNode;
|
||||
hidden?: boolean;
|
||||
};
|
||||
|
||||
export type DropdownItemDefault = {
|
||||
type?: 'default';
|
||||
label: ReactNode;
|
||||
keepOpen?: boolean;
|
||||
hotKeyAction?: HotkeyAction;
|
||||
hotKeyLabelOnly?: boolean;
|
||||
color?: 'default' | 'danger' | 'info' | 'warning' | 'notice';
|
||||
@@ -54,10 +60,11 @@ export type DropdownItemDefault = {
|
||||
hidden?: boolean;
|
||||
leftSlot?: ReactNode;
|
||||
rightSlot?: ReactNode;
|
||||
onSelect?: () => void;
|
||||
waitForOnSelect?: boolean;
|
||||
onSelect?: () => void | Promise<void>;
|
||||
};
|
||||
|
||||
export type DropdownItem = DropdownItemDefault | DropdownItemSeparator;
|
||||
export type DropdownItem = DropdownItemDefault | DropdownItemSeparator | DropdownItemContent;
|
||||
|
||||
export interface DropdownProps {
|
||||
children: ReactElement<HTMLAttributes<HTMLButtonElement>>;
|
||||
@@ -374,14 +381,20 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(i: DropdownItem) => {
|
||||
if (i.type !== 'separator' && !i.keepOpen) {
|
||||
handleClose();
|
||||
}
|
||||
async (item: DropdownItem) => {
|
||||
if (!('onSelect' in item) || !item.onSelect) return;
|
||||
setSelectedIndex(null);
|
||||
if (i.type !== 'separator' && typeof i.onSelect === 'function') {
|
||||
i.onSelect();
|
||||
|
||||
const promise = item.onSelect();
|
||||
if (item.waitForOnSelect) {
|
||||
try {
|
||||
await promise;
|
||||
} catch {
|
||||
// Nothing
|
||||
}
|
||||
}
|
||||
|
||||
handleClose();
|
||||
},
|
||||
[handleClose, setSelectedIndex],
|
||||
);
|
||||
@@ -391,10 +404,10 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
close: handleClose,
|
||||
prev: handlePrev,
|
||||
next: handleNext,
|
||||
select() {
|
||||
async select() {
|
||||
const item = items[selectedIndexRef.current ?? -1] ?? null;
|
||||
if (!item) return;
|
||||
handleSelect(item);
|
||||
await handleSelect(item);
|
||||
},
|
||||
};
|
||||
}, [handleClose, handleNext, handlePrev, handleSelect, items]);
|
||||
@@ -466,6 +479,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
{items.map(
|
||||
(item, i) =>
|
||||
item.type !== 'separator' &&
|
||||
item.type !== 'content' &&
|
||||
!item.hotKeyLabelOnly &&
|
||||
item.hotKeyAction && (
|
||||
<MenuItemHotKey
|
||||
@@ -519,7 +533,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
space={2}
|
||||
className="pb-0.5 px-1.5 mb-2 text-sm border border-border-subtle mx-2 rounded font-mono h-xs"
|
||||
>
|
||||
<Icon icon="search" size="xs" className="text-text-subtle" />
|
||||
<Icon icon="search" size="xs" />
|
||||
<div className="text">{filter}</div>
|
||||
</HStack>
|
||||
)}
|
||||
@@ -537,6 +551,13 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
</Separator>
|
||||
);
|
||||
}
|
||||
if (item.type === 'content') {
|
||||
return (
|
||||
<div key={i} className={classNames('my-1.5 mx-2 max-w-xs')}>
|
||||
{item.label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<MenuItem
|
||||
focused={i === selectedIndex}
|
||||
@@ -559,13 +580,19 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
interface MenuItemProps {
|
||||
className?: string;
|
||||
item: DropdownItemDefault;
|
||||
onSelect: (item: DropdownItemDefault) => void;
|
||||
onSelect: (item: DropdownItemDefault) => Promise<void>;
|
||||
onFocus: (item: DropdownItemDefault) => void;
|
||||
focused: boolean;
|
||||
}
|
||||
|
||||
function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: MenuItemProps) {
|
||||
const handleClick = useCallback(() => onSelect?.(item), [item, onSelect]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const handleClick = useCallback(async () => {
|
||||
if (item.waitForOnSelect) setIsLoading(true);
|
||||
await onSelect?.(item);
|
||||
if (item.waitForOnSelect) setIsLoading(false);
|
||||
}, [item, onSelect]);
|
||||
|
||||
const handleFocus = useCallback(
|
||||
(e: ReactFocusEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation(); // Don't trigger focus on any parents
|
||||
@@ -598,7 +625,11 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
|
||||
onClick={handleClick}
|
||||
justify="start"
|
||||
leftSlot={
|
||||
item.leftSlot && <div className="pr-2 flex justify-start opacity-70">{item.leftSlot}</div>
|
||||
(isLoading || item.leftSlot) && (
|
||||
<div className={classNames('pr-2 flex justify-start opacity-70')}>
|
||||
{isLoading ? <LoadingIcon /> : item.leftSlot}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
rightSlot={rightSlot && <div className="ml-auto pl-3">{rightSlot}</div>}
|
||||
innerClassName="!text-left"
|
||||
|
||||
Reference in New Issue
Block a user