Git support (#143)

This commit is contained in:
Gregory Schier
2025-02-07 07:59:48 -08:00
committed by GitHub
parent cffc7714c1
commit 1a7c27663a
111 changed files with 4264 additions and 372 deletions

View File

@@ -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"