refactor: Update bottom controls for improved styling and functionality
- Adjusted button styles across various components for consistency and better UX. - Enhanced layout of action controls to utilize whitespace more effectively. - Updated slider styles for a more modern appearance and improved usability. - Refined input fields and labels for better accessibility and readability. - Introduced new app surface styles for a cohesive design across the application. - Added tests for canvas cursor behavior to ensure correct cursor display during operations.
This commit is contained in:
@@ -23,11 +23,11 @@ export function GenerateActionControls({ settings, generation, dispatch, workflo
|
||||
const repair = precondition.ready ? undefined : precondition.repair;
|
||||
|
||||
return (
|
||||
<div className="flex max-w-[calc(100vw-2rem)] flex-wrap items-center justify-center gap-2 px-2">
|
||||
<div className="flex max-w-[calc(100vw-2rem)] flex-nowrap items-center justify-start gap-2 whitespace-nowrap px-2">
|
||||
<button
|
||||
type="button"
|
||||
disabled={!canGenerate}
|
||||
className="h-12 rounded-full bg-white px-7 text-base font-semibold !text-black transition hover:bg-white/90 focus:outline-none focus-visible:ring-2 focus-visible:ring-white/40 disabled:pointer-events-none disabled:opacity-35"
|
||||
className="h-9 rounded-lg bg-sky-300 px-5 text-xs font-semibold !text-slate-950 transition hover:bg-sky-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-sky-300/50 disabled:pointer-events-none disabled:opacity-35"
|
||||
title={job?.status === "failed" ? job.error : preconditionMessage ?? "Generate with ComfyUI"}
|
||||
onClick={() => {
|
||||
void generate();
|
||||
@@ -36,17 +36,17 @@ export function GenerateActionControls({ settings, generation, dispatch, workflo
|
||||
{busy && job?.kind === "generate" ? "Generating..." : "Generate"}
|
||||
</button>
|
||||
{preconditionMessage ? (
|
||||
<span className="flex max-w-80 items-center gap-2 rounded-full bg-amber-300/10 px-3 py-2 text-xs text-amber-100/75" role="status">
|
||||
<span className="flex max-w-80 items-center gap-2 rounded-md border border-amber-300/10 bg-amber-300/[0.07] px-2.5 py-1.5 text-xs text-amber-100/70" role="status">
|
||||
<span>{preconditionMessage}</span>
|
||||
{repair === "add-mask" ? (
|
||||
<button type="button" className="shrink-0 rounded-full bg-white px-3 py-1 font-semibold text-black" onClick={() => void workflow.prepareInpaintMask()}>Add mask</button>
|
||||
<button type="button" className="shrink-0 rounded-md bg-white px-2.5 py-1 font-semibold text-black" onClick={() => void workflow.prepareInpaintMask()}>Add mask</button>
|
||||
) : repair === "set-outpaint-padding" ? (
|
||||
<button type="button" className="shrink-0 rounded-full bg-white px-3 py-1 font-semibold text-black" onClick={() => dispatch(commandIds.toolSetGenerateSettings, { outpaint: { ...settings.outpaint, left: 128, right: 128 } })}>Add padding</button>
|
||||
<button type="button" className="shrink-0 rounded-md bg-white px-2.5 py-1 font-semibold text-black" onClick={() => dispatch(commandIds.toolSetGenerateSettings, { outpaint: { ...settings.outpaint, left: 128, right: 128 } })}>Add padding</button>
|
||||
) : null}
|
||||
</span>
|
||||
) : null}
|
||||
<GenerationJobStatus generation={generation} />
|
||||
{busy ? <button type="button" className="h-9 rounded-full bg-white/5 px-3 text-xs font-semibold text-white/70 hover:bg-white/10 hover:text-white" onClick={workflow.cancel}>Cancel</button> : null}
|
||||
{busy ? <button type="button" className="h-8 rounded-md bg-white/[0.05] px-2.5 text-xs font-semibold text-white/65 hover:bg-white/[0.09] hover:text-white" onClick={workflow.cancel}>Cancel</button> : null}
|
||||
{candidate ? (
|
||||
<>
|
||||
<CandidatePicker generation={generation} dispatch={dispatch} />
|
||||
@@ -66,19 +66,19 @@ export function GenerateActionControls({ settings, generation, dispatch, workflo
|
||||
|
||||
function CandidatePicker({ generation, dispatch }: { generation: GenerationState; dispatch: AppStore["dispatch"] }) {
|
||||
return (
|
||||
<div className="subtle-scrollbar flex max-w-[min(34rem,calc(100vw-2rem))] items-stretch gap-2 overflow-x-auto rounded-[1.25rem] bg-black/20 p-2 ring-1 ring-white/[0.08]" role="group" aria-label="Provisional generation candidates">
|
||||
<div className="subtle-scrollbar flex max-w-[min(34rem,calc(100vw-2rem))] items-stretch gap-1.5 overflow-x-auto border-l border-white/[0.08] pl-2" role="group" aria-label="Provisional generation candidates">
|
||||
{generation.candidates.map((candidate) => {
|
||||
const selected = candidate.id === (generation.selectedCandidateId ?? generation.candidates[0]?.id);
|
||||
return (
|
||||
<button
|
||||
key={candidate.id}
|
||||
type="button"
|
||||
className={`relative h-16 w-16 shrink-0 overflow-hidden rounded-xl ring-2 transition ${selected ? "ring-white" : "ring-white/10 hover:ring-white/40"}`}
|
||||
className={`relative h-16 w-16 shrink-0 overflow-hidden rounded-lg border transition ${selected ? "border-sky-300 shadow-[0_0_0_1px_rgba(125,211,252,0.35)]" : "border-white/10 hover:border-white/30"}`}
|
||||
title={`Provisional candidate · seed ${candidate.seed}`}
|
||||
onClick={() => dispatch(commandIds.generationSelectCandidate, { candidateId: candidate.id })}
|
||||
>
|
||||
<img src={candidate.source} alt="" className="h-full w-full object-cover" />
|
||||
{selected ? <span className="absolute inset-x-1 bottom-1 rounded-full bg-black/70 px-1 py-0.5 text-[0.6rem] font-semibold text-white">Reviewing</span> : null}
|
||||
{selected ? <span className="absolute inset-x-1 bottom-1 rounded bg-black/75 px-1 py-0.5 text-[0.58rem] font-semibold uppercase tracking-wide text-sky-100">Reviewing</span> : null}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -116,7 +116,7 @@ function CandidateControls({
|
||||
const disabled = busy;
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center justify-center gap-1 rounded-[1.25rem] bg-white/[0.04] px-2 py-2 ring-1 ring-white/[0.05]">
|
||||
<div className="flex flex-nowrap items-center justify-center gap-1 border-l border-white/[0.08] pl-2">
|
||||
<CandidatePreview candidate={candidate} />
|
||||
<span className="px-2 text-xs font-medium text-white/55" title={`${candidate.settings.model} · ${candidate.mode}`}><strong className="block font-semibold text-white/75">Provisional result</strong>Seed {candidate.seed}</span>
|
||||
<CandidateCompareControls compareMode={compareMode} disabled={disabled} dispatch={dispatch} />
|
||||
@@ -170,14 +170,14 @@ function CandidateControls({
|
||||
|
||||
function CandidateCompareControls({ compareMode, disabled, dispatch }: { compareMode: GenerationCompareMode; disabled: boolean; dispatch: AppStore["dispatch"] }) {
|
||||
return (
|
||||
<span className="flex items-center gap-1 rounded-full bg-black/20 p-1" aria-label="Compare candidate">
|
||||
<span className="flex items-center gap-0.5 border-l border-white/[0.08] pl-1" aria-label="Compare candidate">
|
||||
{generationCompareOptions.map((option) => {
|
||||
const active = compareMode === option.mode;
|
||||
return (
|
||||
<button
|
||||
key={option.mode}
|
||||
type="button"
|
||||
className={`h-7 rounded-full px-2 text-[0.7rem] font-semibold transition ${
|
||||
className={`h-7 rounded px-2 text-[0.68rem] font-semibold transition ${
|
||||
active ? "bg-white text-black" : "text-white/55 hover:bg-white/10 hover:text-white"
|
||||
} disabled:pointer-events-none disabled:opacity-35`}
|
||||
disabled={disabled}
|
||||
@@ -200,14 +200,14 @@ const generationCompareOptions: Array<{ mode: GenerationCompareMode; label: stri
|
||||
|
||||
function CandidatePreview({ candidate }: { candidate: GenerationCandidate }) {
|
||||
if (!candidate.inputImage) {
|
||||
return <img src={candidate.source} alt="" className="h-10 w-10 rounded-full bg-black/25 object-cover ring-1 ring-white/10" />;
|
||||
return <img src={candidate.source} alt="" className="h-10 w-10 rounded-md bg-black/25 object-cover ring-1 ring-white/10" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="flex items-center -space-x-2" title={candidate.maskImage ? "Input crop, normalized mask, generated candidate" : "Before and generated candidate"}>
|
||||
<img src={candidate.inputImage} alt="" className="h-10 w-10 rounded-full bg-black/25 object-cover ring-1 ring-white/10" />
|
||||
{candidate.maskImage ? <img src={candidate.maskImage} alt="" className="h-10 w-10 rounded-full bg-black/25 object-cover ring-1 ring-white/20" /> : null}
|
||||
<img src={candidate.source} alt="" className="h-10 w-10 rounded-full bg-black/25 object-cover ring-2 ring-white/40" />
|
||||
<img src={candidate.inputImage} alt="" className="h-10 w-10 rounded-md bg-black/25 object-cover ring-1 ring-white/10" />
|
||||
{candidate.maskImage ? <img src={candidate.maskImage} alt="" className="h-10 w-10 rounded-md bg-black/25 object-cover ring-1 ring-white/20" /> : null}
|
||||
<img src={candidate.source} alt="" className="h-10 w-10 rounded-md bg-black/25 object-cover ring-1 ring-sky-300/40" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -216,7 +216,7 @@ function CandidateButton({ label, title, disabled, busy, onClick }: { label: str
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="h-9 rounded-full bg-white/5 px-3 text-xs font-semibold text-white/70 transition hover:bg-white/10 hover:text-white disabled:pointer-events-none disabled:opacity-35"
|
||||
className="h-8 rounded-md bg-white/[0.05] px-2.5 text-xs font-semibold text-white/65 transition hover:bg-white/[0.09] hover:text-white disabled:pointer-events-none disabled:opacity-35"
|
||||
disabled={disabled}
|
||||
title={title}
|
||||
onClick={onClick}
|
||||
|
||||
Reference in New Issue
Block a user