* feat(app): open project folder from project context menu (#2487) * refactor(app): give file manager action a home --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
176 lines
5.8 KiB
TypeScript
176 lines
5.8 KiB
TypeScript
import { useMemo } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { type PressableStateCallbackType } from "react-native";
|
|
import { StyleSheet, withUnistyles } from "react-native-unistyles";
|
|
import { Archive, CircleCheck, Copy, MoreVertical, Pencil, Pin, PinOff } from "lucide-react-native";
|
|
import { isNative, isWeb } from "@/constants/platform";
|
|
import type { Theme } from "@/styles/theme";
|
|
import type { ShortcutKey } from "@/utils/format-shortcut";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import { Shortcut } from "@/components/ui/shortcut";
|
|
import { OpenInFileManagerMenuItem } from "@/workspace/open-in-file-manager/menu-item";
|
|
|
|
const foregroundColorMapping = (theme: Theme) => ({ color: theme.colors.foreground });
|
|
const foregroundMutedColorMapping = (theme: Theme) => ({
|
|
color: theme.colors.foregroundMuted,
|
|
});
|
|
|
|
const ThemedMoreVertical = withUnistyles(MoreVertical);
|
|
const ThemedCopy = withUnistyles(Copy);
|
|
const ThemedArchive = withUnistyles(Archive);
|
|
const ThemedPencil = withUnistyles(Pencil);
|
|
const ThemedCircleCheck = withUnistyles(CircleCheck);
|
|
const ThemedPin = withUnistyles(Pin);
|
|
const ThemedPinOff = withUnistyles(PinOff);
|
|
|
|
const copyLeadingIcon = <ThemedCopy size={14} uniProps={foregroundMutedColorMapping} />;
|
|
const renameLeadingIcon = <ThemedPencil size={14} uniProps={foregroundMutedColorMapping} />;
|
|
const markAsReadLeadingIcon = (
|
|
<ThemedCircleCheck size={14} uniProps={foregroundMutedColorMapping} />
|
|
);
|
|
const archiveLeadingIcon = <ThemedArchive size={14} uniProps={foregroundMutedColorMapping} />;
|
|
const pinLeadingIcon = <ThemedPin size={14} uniProps={foregroundMutedColorMapping} />;
|
|
const unpinLeadingIcon = <ThemedPinOff size={14} uniProps={foregroundMutedColorMapping} />;
|
|
|
|
function renderTriggerIcon({ hovered }: { hovered?: boolean }) {
|
|
return (
|
|
<ThemedMoreVertical
|
|
size={14}
|
|
uniProps={hovered ? foregroundColorMapping : foregroundMutedColorMapping}
|
|
/>
|
|
);
|
|
}
|
|
|
|
interface SidebarWorkspaceMenuProps {
|
|
workspaceKey: string;
|
|
onCopyPath?: () => void;
|
|
onCopyBranchName?: () => void;
|
|
onRename?: () => void;
|
|
onMarkAsRead?: () => void;
|
|
onArchive: () => void;
|
|
archiveLabel?: string;
|
|
archiveStatus?: "idle" | "pending" | "success";
|
|
archivePendingLabel?: string;
|
|
archiveShortcutKeys?: ShortcutKey[][] | null;
|
|
isPinned?: boolean;
|
|
onTogglePin?: () => void;
|
|
openInFileManagerPath?: string | null;
|
|
}
|
|
|
|
export function SidebarWorkspaceMenu({
|
|
workspaceKey,
|
|
onCopyPath,
|
|
onCopyBranchName,
|
|
onRename,
|
|
onMarkAsRead,
|
|
onArchive,
|
|
archiveLabel,
|
|
archiveStatus,
|
|
archivePendingLabel,
|
|
archiveShortcutKeys,
|
|
isPinned,
|
|
onTogglePin,
|
|
openInFileManagerPath,
|
|
}: SidebarWorkspaceMenuProps) {
|
|
const { t } = useTranslation();
|
|
const archiveTrailing = useMemo(
|
|
() => (archiveShortcutKeys && !isNative ? <Shortcut chord={archiveShortcutKeys} /> : null),
|
|
[archiveShortcutKeys],
|
|
);
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger
|
|
hitSlop={8}
|
|
style={triggerStyle}
|
|
accessibilityRole={isWeb ? undefined : "button"}
|
|
accessibilityLabel={t("sidebar.workspace.actions.menu")}
|
|
testID={`sidebar-workspace-kebab-${workspaceKey}`}
|
|
>
|
|
{renderTriggerIcon}
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" width={260}>
|
|
{onCopyPath ? (
|
|
<DropdownMenuItem
|
|
testID={`sidebar-workspace-menu-copy-path-${workspaceKey}`}
|
|
leading={copyLeadingIcon}
|
|
onSelect={onCopyPath}
|
|
>
|
|
{t("sidebar.workspace.actions.copyPath")}
|
|
</DropdownMenuItem>
|
|
) : null}
|
|
{onCopyBranchName ? (
|
|
<DropdownMenuItem
|
|
testID={`sidebar-workspace-menu-copy-branch-name-${workspaceKey}`}
|
|
leading={copyLeadingIcon}
|
|
onSelect={onCopyBranchName}
|
|
>
|
|
{t("sidebar.workspace.actions.copyBranchName")}
|
|
</DropdownMenuItem>
|
|
) : null}
|
|
{onRename ? (
|
|
<DropdownMenuItem
|
|
testID={`sidebar-workspace-menu-rename-${workspaceKey}`}
|
|
leading={renameLeadingIcon}
|
|
onSelect={onRename}
|
|
>
|
|
{t("sidebar.workspace.actions.rename")}
|
|
</DropdownMenuItem>
|
|
) : null}
|
|
{onMarkAsRead ? (
|
|
<DropdownMenuItem
|
|
testID={`sidebar-workspace-menu-mark-as-read-${workspaceKey}`}
|
|
leading={markAsReadLeadingIcon}
|
|
onSelect={onMarkAsRead}
|
|
>
|
|
Mark as read
|
|
</DropdownMenuItem>
|
|
) : null}
|
|
{onTogglePin ? (
|
|
<DropdownMenuItem
|
|
testID={`sidebar-workspace-menu-pin-${workspaceKey}`}
|
|
leading={isPinned ? unpinLeadingIcon : pinLeadingIcon}
|
|
onSelect={onTogglePin}
|
|
>
|
|
{isPinned ? t("sidebar.workspace.actions.unpin") : t("sidebar.workspace.actions.pin")}
|
|
</DropdownMenuItem>
|
|
) : null}
|
|
<OpenInFileManagerMenuItem
|
|
path={openInFileManagerPath}
|
|
testID={`sidebar-workspace-menu-open-folder-${workspaceKey}`}
|
|
/>
|
|
<DropdownMenuItem
|
|
testID={`sidebar-workspace-menu-archive-${workspaceKey}`}
|
|
leading={archiveLeadingIcon}
|
|
trailing={archiveTrailing}
|
|
status={archiveStatus}
|
|
pendingLabel={archivePendingLabel}
|
|
onSelect={onArchive}
|
|
>
|
|
{archiveLabel ?? t("sidebar.workspace.actions.archive")}
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
}
|
|
|
|
function triggerStyle({ hovered = false }: PressableStateCallbackType & { hovered?: boolean }) {
|
|
return [styles.trigger, hovered && styles.triggerHovered];
|
|
}
|
|
|
|
const styles = StyleSheet.create((theme) => ({
|
|
trigger: {
|
|
padding: 2,
|
|
borderRadius: 4,
|
|
marginLeft: 2,
|
|
},
|
|
triggerHovered: {
|
|
backgroundColor: theme.colors.surface2,
|
|
},
|
|
}));
|