mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-16 02:40:39 +09:00
Unify all toggle switches into a single responsive ToggleSwitch component
Replaces 12+ inline toggle button implementations across the app with a shared ToggleSwitch component that scales up on mobile for better touch targets. Default size is h-6/w-10 on mobile, h-5/w-9 on desktop; "lg" variant is h-7/w-12 on mobile, h-6/w-11 on desktop. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
3d685335eb
commit
dbb5f0c4a9
9 changed files with 128 additions and 226 deletions
|
|
@ -25,6 +25,7 @@ import { queryKeys } from "../lib/queryKeys";
|
|||
import { AgentConfigForm } from "../components/AgentConfigForm";
|
||||
import { PageTabBar } from "../components/PageTabBar";
|
||||
import { adapterLabels, roleLabels, help } from "../components/agent-config-primitives";
|
||||
import { ToggleSwitch } from "@/components/ui/toggle-switch";
|
||||
import { MarkdownEditor } from "../components/MarkdownEditor";
|
||||
import { assetsApi } from "../api/assets";
|
||||
import { getUIAdapter, buildTranscript, onAdapterChange } from "../adapters";
|
||||
|
|
@ -1627,30 +1628,16 @@ function ConfigurationTab({
|
|||
Lets this agent create or hire agents and implicitly assign tasks.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
data-slot="toggle"
|
||||
aria-checked={canCreateAgents}
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors shrink-0 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
canCreateAgents ? "bg-green-600" : "bg-muted",
|
||||
)}
|
||||
onClick={() =>
|
||||
<ToggleSwitch
|
||||
checked={canCreateAgents}
|
||||
onCheckedChange={() =>
|
||||
updatePermissions.mutate({
|
||||
canCreateAgents: !canCreateAgents,
|
||||
canAssignTasks: !canCreateAgents ? true : canAssignTasks,
|
||||
})
|
||||
}
|
||||
disabled={updatePermissions.isPending}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
|
||||
canCreateAgents ? "translate-x-4.5" : "translate-x-0.5",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-4 text-sm">
|
||||
<div className="space-y-1">
|
||||
|
|
@ -1659,30 +1646,16 @@ function ConfigurationTab({
|
|||
{taskAssignHint}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
data-slot="toggle"
|
||||
aria-checked={canAssignTasks}
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors shrink-0 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
canAssignTasks ? "bg-green-600" : "bg-muted",
|
||||
)}
|
||||
onClick={() =>
|
||||
<ToggleSwitch
|
||||
checked={canAssignTasks}
|
||||
onCheckedChange={() =>
|
||||
updatePermissions.mutate({
|
||||
canCreateAgents,
|
||||
canAssignTasks: !canAssignTasks,
|
||||
})
|
||||
}
|
||||
disabled={updatePermissions.isPending || taskAssignLocked}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
|
||||
canAssignTasks ? "translate-x-4.5" : "translate-x-0.5",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { FlaskConical } from "lucide-react";
|
|||
import { instanceSettingsApi } from "@/api/instanceSettings";
|
||||
import { useBreadcrumbs } from "../context/BreadcrumbContext";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { cn } from "../lib/utils";
|
||||
import { ToggleSwitch } from "@/components/ui/toggle-switch";
|
||||
|
||||
export function InstanceExperimentalSettings() {
|
||||
const { setBreadcrumbs } = useBreadcrumbs();
|
||||
|
|
@ -82,24 +82,12 @@ export function InstanceExperimentalSettings() {
|
|||
and existing issue runs.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
data-slot="toggle"
|
||||
aria-label="Toggle isolated workspaces experimental setting"
|
||||
<ToggleSwitch
|
||||
checked={enableIsolatedWorkspaces}
|
||||
onCheckedChange={() => toggleMutation.mutate({ enableIsolatedWorkspaces: !enableIsolatedWorkspaces })}
|
||||
disabled={toggleMutation.isPending}
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-60",
|
||||
enableIsolatedWorkspaces ? "bg-green-600" : "bg-muted",
|
||||
)}
|
||||
onClick={() => toggleMutation.mutate({ enableIsolatedWorkspaces: !enableIsolatedWorkspaces })}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
|
||||
enableIsolatedWorkspaces ? "translate-x-4.5" : "translate-x-0.5",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
aria-label="Toggle isolated workspaces experimental setting"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -112,26 +100,12 @@ export function InstanceExperimentalSettings() {
|
|||
automatically when backend changes or migrations make the current boot stale.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
data-slot="toggle"
|
||||
aria-label="Toggle guarded dev-server auto-restart"
|
||||
<ToggleSwitch
|
||||
checked={autoRestartDevServerWhenIdle}
|
||||
onCheckedChange={() => toggleMutation.mutate({ autoRestartDevServerWhenIdle: !autoRestartDevServerWhenIdle })}
|
||||
disabled={toggleMutation.isPending}
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-60",
|
||||
autoRestartDevServerWhenIdle ? "bg-green-600" : "bg-muted",
|
||||
)}
|
||||
onClick={() =>
|
||||
toggleMutation.mutate({ autoRestartDevServerWhenIdle: !autoRestartDevServerWhenIdle })
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
|
||||
autoRestartDevServerWhenIdle ? "translate-x-4.5" : "translate-x-0.5",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
aria-label="Toggle guarded dev-server auto-restart"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { instanceSettingsApi } from "@/api/instanceSettings";
|
|||
import { Button } from "../components/ui/button";
|
||||
import { useBreadcrumbs } from "../context/BreadcrumbContext";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { ToggleSwitch } from "@/components/ui/toggle-switch";
|
||||
import { cn } from "../lib/utils";
|
||||
|
||||
const FEEDBACK_TERMS_URL = import.meta.env.VITE_FEEDBACK_TERMS_URL?.trim() || "https://paperclip.ing/tos";
|
||||
|
|
@ -95,28 +96,12 @@ export function InstanceGeneralSettings() {
|
|||
default.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
data-slot="toggle"
|
||||
aria-label="Toggle username log censoring"
|
||||
<ToggleSwitch
|
||||
checked={censorUsernameInLogs}
|
||||
onCheckedChange={() => updateGeneralMutation.mutate({ censorUsernameInLogs: !censorUsernameInLogs })}
|
||||
disabled={updateGeneralMutation.isPending}
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-60",
|
||||
censorUsernameInLogs ? "bg-green-600" : "bg-muted",
|
||||
)}
|
||||
onClick={() =>
|
||||
updateGeneralMutation.mutate({
|
||||
censorUsernameInLogs: !censorUsernameInLogs,
|
||||
})
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
|
||||
censorUsernameInLogs ? "translate-x-4.5" : "translate-x-0.5",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
aria-label="Toggle username log censoring"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -129,24 +114,12 @@ export function InstanceGeneralSettings() {
|
|||
toggling panels. This is off by default.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
data-slot="toggle"
|
||||
aria-label="Toggle keyboard shortcuts"
|
||||
<ToggleSwitch
|
||||
checked={keyboardShortcuts}
|
||||
onCheckedChange={() => updateGeneralMutation.mutate({ keyboardShortcuts: !keyboardShortcuts })}
|
||||
disabled={updateGeneralMutation.isPending}
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-60",
|
||||
keyboardShortcuts ? "bg-green-600" : "bg-muted",
|
||||
)}
|
||||
onClick={() => updateGeneralMutation.mutate({ keyboardShortcuts: !keyboardShortcuts })}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
|
||||
keyboardShortcuts ? "translate-x-4.5" : "translate-x-0.5",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
aria-label="Toggle keyboard shortcuts"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { useToast } from "../context/ToastContext";
|
|||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { buildRoutineTriggerPatch } from "../lib/routine-trigger-patch";
|
||||
import { timeAgo } from "../lib/timeAgo";
|
||||
import { ToggleSwitch } from "@/components/ui/toggle-switch";
|
||||
import { EmptyState } from "../components/EmptyState";
|
||||
import { PageSkeleton } from "../components/PageSkeleton";
|
||||
import { AgentIcon } from "../components/AgentIconPicker";
|
||||
|
|
@ -710,24 +711,13 @@ export function RoutineDetail() {
|
|||
}}
|
||||
disabled={runRoutine.isPending}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
data-slot="toggle"
|
||||
aria-checked={automationEnabled}
|
||||
aria-label={automationEnabled ? "Pause automatic triggers" : "Enable automatic triggers"}
|
||||
<ToggleSwitch
|
||||
size="lg"
|
||||
checked={automationEnabled}
|
||||
onCheckedChange={() => updateRoutineStatus.mutate(automationEnabled ? "paused" : "active")}
|
||||
disabled={automationToggleDisabled}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
||||
automationEnabled ? "bg-emerald-500" : "bg-muted"
|
||||
} ${automationToggleDisabled ? "cursor-not-allowed opacity-50" : ""}`}
|
||||
onClick={() => updateRoutineStatus.mutate(automationEnabled ? "paused" : "active")}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-5 w-5 rounded-full bg-background shadow-sm transition-transform ${
|
||||
automationEnabled ? "translate-x-5" : "translate-x-0.5"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
aria-label={automationEnabled ? "Pause automatic triggers" : "Enable automatic triggers"}
|
||||
/>
|
||||
<span className={`min-w-[3.75rem] text-sm font-medium ${automationLabelClassName}`}>
|
||||
{automationLabel}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { useBreadcrumbs } from "../context/BreadcrumbContext";
|
|||
import { useToast } from "../context/ToastContext";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { getRecentAssigneeIds, sortAgentsByRecency, trackRecentAssignee } from "../lib/recent-assignees";
|
||||
import { ToggleSwitch } from "@/components/ui/toggle-switch";
|
||||
import { EmptyState } from "../components/EmptyState";
|
||||
import { PageSkeleton } from "../components/PageSkeleton";
|
||||
import { AgentIcon } from "../components/AgentIconPicker";
|
||||
|
|
@ -640,29 +641,18 @@ export function Routines() {
|
|||
</td>
|
||||
<td className="px-3 py-2.5" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
data-slot="toggle"
|
||||
aria-checked={enabled}
|
||||
aria-label={enabled ? `Disable ${routine.title}` : `Enable ${routine.title}`}
|
||||
disabled={isStatusPending || isArchived}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
||||
enabled ? "bg-foreground" : "bg-muted"
|
||||
} ${isStatusPending || isArchived ? "cursor-not-allowed opacity-50" : ""}`}
|
||||
onClick={() =>
|
||||
<ToggleSwitch
|
||||
size="lg"
|
||||
checked={enabled}
|
||||
onCheckedChange={() =>
|
||||
updateRoutineStatus.mutate({
|
||||
id: routine.id,
|
||||
status: nextRoutineStatus(routine.status, !enabled),
|
||||
})
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-5 w-5 rounded-full bg-background shadow-sm transition-transform ${
|
||||
enabled ? "translate-x-5" : "translate-x-0.5"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
disabled={isStatusPending || isArchived}
|
||||
aria-label={enabled ? `Disable ${routine.title}` : `Enable ${routine.title}`}
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{isArchived ? "Archived" : enabled ? "On" : "Off"}
|
||||
</span>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue