mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 02:20:38 +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
|
|
@ -24,6 +24,7 @@ import {
|
|||
DialogContent,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ToggleSwitch } from "@/components/ui/toggle-switch";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
|
|
@ -1208,21 +1209,10 @@ export function NewIssueDialog() {
|
|||
{assigneeAdapterType === "claude_local" && (
|
||||
<div className="flex items-center justify-between rounded-md border border-border px-2 py-1.5">
|
||||
<div className="text-xs text-muted-foreground">Enable Chrome (--chrome)</div>
|
||||
<button
|
||||
data-slot="toggle"
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
|
||||
assigneeChrome ? "bg-green-600" : "bg-muted"
|
||||
)}
|
||||
onClick={() => setAssigneeChrome((value) => !value)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
|
||||
assigneeChrome ? "translate-x-4.5" : "translate-x-0.5"
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
<ToggleSwitch
|
||||
checked={assigneeChrome}
|
||||
onCheckedChange={() => setAssigneeChrome((value) => !value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover
|
|||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { AlertCircle, Archive, ArchiveRestore, Check, ExternalLink, Github, Loader2, Plus, Trash2, X } from "lucide-react";
|
||||
import { ChoosePathButton } from "./PathInstructionsModal";
|
||||
import { ToggleSwitch } from "@/components/ui/toggle-switch";
|
||||
import { DraftInput } from "./agent-config-primitives";
|
||||
import { InlineEditor } from "./InlineEditor";
|
||||
|
||||
|
|
@ -886,26 +887,14 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa
|
|||
</div>
|
||||
</div>
|
||||
{onUpdate || onFieldUpdate ? (
|
||||
<button
|
||||
data-slot="toggle"
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
|
||||
executionWorkspacesEnabled ? "bg-green-600" : "bg-muted",
|
||||
)}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
<ToggleSwitch
|
||||
checked={executionWorkspacesEnabled}
|
||||
onCheckedChange={() =>
|
||||
commitField(
|
||||
"execution_workspace_enabled",
|
||||
updateExecutionWorkspacePolicy({ enabled: !executionWorkspacesEnabled })!,
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
|
||||
executionWorkspacesEnabled ? "translate-x-4.5" : "translate-x-0.5",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
/>
|
||||
) : (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{executionWorkspacesEnabled ? "Enabled" : "Disabled"}
|
||||
|
|
@ -925,14 +914,9 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa
|
|||
If disabled, new issues stay on the project's primary checkout unless someone opts in.
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
data-slot="toggle"
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
|
||||
executionWorkspaceDefaultMode === "isolated_workspace" ? "bg-green-600" : "bg-muted",
|
||||
)}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
<ToggleSwitch
|
||||
checked={executionWorkspaceDefaultMode === "isolated_workspace"}
|
||||
onCheckedChange={() =>
|
||||
commitField(
|
||||
"execution_workspace_default_mode",
|
||||
updateExecutionWorkspacePolicy({
|
||||
|
|
@ -942,16 +926,7 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa
|
|||
: "isolated_workspace",
|
||||
})!,
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
|
||||
executionWorkspaceDefaultMode === "isolated_workspace"
|
||||
? "translate-x-4.5"
|
||||
: "translate-x-0.5",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-border/60 pt-2">
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
TooltipTrigger,
|
||||
TooltipContent,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { ToggleSwitch } from "@/components/ui/toggle-switch";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
|
@ -111,23 +112,11 @@ export function ToggleField({
|
|||
<span className="text-xs text-muted-foreground">{label}</span>
|
||||
{hint && <HintIcon text={hint} />}
|
||||
</div>
|
||||
<button
|
||||
data-slot="toggle"
|
||||
<ToggleSwitch
|
||||
checked={checked}
|
||||
onCheckedChange={onChange}
|
||||
data-testid={toggleTestId}
|
||||
type="button"
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
|
||||
checked ? "bg-green-600" : "bg-muted"
|
||||
)}
|
||||
onClick={() => onChange(!checked)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
|
||||
checked ? "translate-x-4.5" : "translate-x-0.5"
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -162,21 +151,10 @@ export function ToggleWithNumber({
|
|||
<span className="text-xs text-muted-foreground">{label}</span>
|
||||
{hint && <HintIcon text={hint} />}
|
||||
</div>
|
||||
<button
|
||||
data-slot="toggle"
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors shrink-0",
|
||||
checked ? "bg-green-600" : "bg-muted"
|
||||
)}
|
||||
onClick={() => onCheckedChange(!checked)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
|
||||
checked ? "translate-x-4.5" : "translate-x-0.5"
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
<ToggleSwitch
|
||||
checked={checked}
|
||||
onCheckedChange={onCheckedChange}
|
||||
/>
|
||||
</div>
|
||||
{showNumber && (
|
||||
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
|
|
|
|||
59
ui/src/components/ui/toggle-switch.tsx
Normal file
59
ui/src/components/ui/toggle-switch.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import * as React from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export interface ToggleSwitchProps
|
||||
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onChange"> {
|
||||
checked: boolean;
|
||||
onCheckedChange: (checked: boolean) => void;
|
||||
size?: "default" | "lg";
|
||||
}
|
||||
|
||||
export const ToggleSwitch = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
ToggleSwitchProps
|
||||
>(
|
||||
(
|
||||
{ checked, onCheckedChange, size = "default", className, disabled, ...props },
|
||||
ref,
|
||||
) => {
|
||||
const isLg = size === "lg";
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={checked}
|
||||
data-slot="toggle"
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"relative inline-flex shrink-0 items-center rounded-full transition-colors",
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
"disabled:cursor-not-allowed disabled:opacity-50",
|
||||
// Track: larger on mobile (<640px), standard on desktop
|
||||
isLg ? "h-7 w-12 sm:h-6 sm:w-11" : "h-6 w-10 sm:h-5 sm:w-9",
|
||||
checked ? "bg-green-600" : "bg-muted",
|
||||
className,
|
||||
)}
|
||||
onClick={() => onCheckedChange(!checked)}
|
||||
{...props}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"pointer-events-none inline-block rounded-full bg-white shadow-sm transition-transform",
|
||||
// Thumb
|
||||
isLg ? "size-5.5 sm:size-5" : "size-4.5 sm:size-3.5",
|
||||
// Slide position
|
||||
checked
|
||||
? isLg
|
||||
? "translate-x-5 sm:translate-x-5"
|
||||
: "translate-x-5 sm:translate-x-4.5"
|
||||
: "translate-x-0.5",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ToggleSwitch.displayName = "ToggleSwitch";
|
||||
Loading…
Add table
Add a link
Reference in a new issue