feat: add edit mode toggle to show/hide drag handles

- Add edit mode button next to settings in header
- Edit button turns blue when active
- Drag handles fade in/out with edit mode toggle
- Add smooth 200ms transition animation
- Add i18n support for edit mode (en/zh)
- Maintain consistent spacing between header elements
This commit is contained in:
Jason
2025-10-19 22:12:12 +08:00
parent 43ed1c7533
commit 491bbff11d
5 changed files with 40 additions and 6 deletions

View File

@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { Plus, Settings } from "lucide-react";
import { Plus, Settings, Edit3 } from "lucide-react";
import type { Provider } from "@/types";
import { useProvidersQuery } from "@/lib/query";
import {
@@ -27,6 +27,7 @@ function App() {
const { t } = useTranslation();
const [activeApp, setActiveApp] = useState<AppType>("claude");
const [isEditMode, setIsEditMode] = useState(false);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [isAddOpen, setIsAddOpen] = useState(false);
const [isMcpOpen, setIsMcpOpen] = useState(false);
@@ -58,7 +59,7 @@ function App() {
if (event.appType === activeApp) {
await refetch();
}
},
}
);
} catch (error) {
console.error("[App] Failed to subscribe provider switch event", error);
@@ -112,7 +113,7 @@ function App() {
<div className="flex h-screen flex-col bg-gray-50 dark:bg-gray-950">
<header className="flex-shrink-0 border-b border-gray-200 bg-white px-6 py-4 dark:border-gray-800 dark:bg-gray-900">
<div className="flex flex-wrap items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="flex items-center gap-1.5">
<a
href="https://github.com/farion1231/cc-switch"
target="_blank"
@@ -125,9 +126,26 @@ function App() {
variant="ghost"
size="icon"
onClick={() => setIsSettingsOpen(true)}
title={t("common.settings")}
className="ml-2"
>
<Settings className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => setIsEditMode(!isEditMode)}
title={t(
isEditMode ? "header.exitEditMode" : "header.enterEditMode"
)}
className={
isEditMode
? "text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300"
: ""
}
>
<Edit3 className="h-4 w-4" />
</Button>
<UpdateBadge onClick={() => setIsSettingsOpen(true)} />
</div>
@@ -155,6 +173,7 @@ function App() {
currentProviderId={currentProviderId}
appType={activeApp}
isLoading={isLoading}
isEditMode={isEditMode}
onSwitch={switchProvider}
onEdit={setEditingProvider}
onDelete={setConfirmDelete}

View File

@@ -21,6 +21,7 @@ interface ProviderCardProps {
provider: Provider;
isCurrent: boolean;
appType: AppType;
isEditMode?: boolean;
onSwitch: (provider: Provider) => void;
onEdit: (provider: Provider) => void;
onDelete: (provider: Provider) => void;
@@ -59,6 +60,7 @@ export function ProviderCard({
provider,
isCurrent,
appType,
isEditMode = false,
onSwitch,
onEdit,
onDelete,
@@ -101,7 +103,10 @@ export function ProviderCard({
<button
type="button"
className={cn(
"mt-1 flex h-8 w-8 items-center justify-center rounded-md border border-transparent text-muted-foreground transition-colors hover:border-muted hover:text-foreground",
"mt-1 flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md border text-muted-foreground transition-all duration-200",
isEditMode
? "border-muted hover:border-primary hover:text-foreground opacity-100"
: "border-transparent opacity-0 pointer-events-none",
dragHandleProps?.isDragging && "border-primary text-primary",
)}
aria-label={t("provider.dragHandle")}

View File

@@ -16,6 +16,7 @@ interface ProviderListProps {
providers: Record<string, Provider>;
currentProviderId: string;
appType: AppType;
isEditMode?: boolean;
onSwitch: (provider: Provider) => void;
onEdit: (provider: Provider) => void;
onDelete: (provider: Provider) => void;
@@ -29,6 +30,7 @@ export function ProviderList({
providers,
currentProviderId,
appType,
isEditMode = false,
onSwitch,
onEdit,
onDelete,
@@ -76,6 +78,7 @@ export function ProviderList({
provider={provider}
isCurrent={provider.id === currentProviderId}
appType={appType}
isEditMode={isEditMode}
onSwitch={onSwitch}
onEdit={onEdit}
onDelete={onDelete}
@@ -93,6 +96,7 @@ interface SortableProviderCardProps {
provider: Provider;
isCurrent: boolean;
appType: AppType;
isEditMode: boolean;
onSwitch: (provider: Provider) => void;
onEdit: (provider: Provider) => void;
onDelete: (provider: Provider) => void;
@@ -104,6 +108,7 @@ function SortableProviderCard({
provider,
isCurrent,
appType,
isEditMode,
onSwitch,
onEdit,
onDelete,
@@ -130,6 +135,7 @@ function SortableProviderCard({
provider={provider}
isCurrent={isCurrent}
appType={appType}
isEditMode={isEditMode}
onSwitch={onSwitch}
onEdit={onEdit}
onDelete={onDelete}

View File

@@ -47,7 +47,9 @@
"toggleLightMode": "Switch to Light Mode",
"addProvider": "Add Provider",
"switchToChinese": "Switch to Chinese",
"switchToEnglish": "Switch to English"
"switchToEnglish": "Switch to English",
"enterEditMode": "Enter Edit Mode",
"exitEditMode": "Exit Edit Mode"
},
"provider": {
"noProviders": "No providers added yet",

View File

@@ -47,7 +47,9 @@
"toggleLightMode": "切换到亮色模式",
"addProvider": "添加供应商",
"switchToChinese": "切换到中文",
"switchToEnglish": "切换到英文"
"switchToEnglish": "切换到英文",
"enterEditMode": "进入编辑模式",
"exitEditMode": "退出编辑模式"
},
"provider": {
"noProviders": "还没有添加任何供应商",