refactor(delete): deduplicate form toggle

This commit is contained in:
Elian Doran
2026-04-11 12:19:47 +03:00
parent 755e5fc416
commit 4e49c2458d
4 changed files with 13 additions and 141 deletions

View File

@@ -3,7 +3,7 @@ import "./delete_notes.css";
import { useRef, useState, useEffect } from "preact/hooks";
import { t } from "../../services/i18n.js";
import Modal from "../react/Modal.js";
import Toggle from "../react/Toggle.js";
import FormToggle from "../react/FormToggle.js";
import type { DeleteNotesPreview } from "@triliumnext/commons";
import server from "../../services/server.js";
import froca from "../../services/froca.js";
@@ -147,7 +147,7 @@ export default function DeleteNotesDialog() {
label={t("delete_notes.erase_notes_label")}
description={t("delete_notes.erase_notes_description")}
>
<Toggle
<FormToggle
disabled={opts.forceDeleteAllClones}
currentValue={eraseNotes}
onChange={setEraseNotes}
@@ -190,7 +190,7 @@ function DeleteAllClonesOption({ cloneInfo, deleteAllClones, setDeleteAllClones
label={t("delete_notes.clones_label")}
description={t("delete_notes.delete_clones_description", { count: totalCloneCount })}
>
<Toggle
<FormToggle
currentValue={deleteAllClones}
onChange={setDeleteAllClones}
/>

View File

@@ -7,17 +7,22 @@ import { ComponentChildren } from "preact";
interface FormToggleProps {
currentValue: boolean | null;
onChange(newValue: boolean): void;
switchOnName: string;
/** Label shown when toggle is off. If omitted along with switchOffName, no label is shown. */
switchOnName?: string;
switchOnTooltip?: string;
switchOffName: string;
/** Label shown when toggle is on. If omitted along with switchOnName, no label is shown. */
switchOffName?: string;
switchOffTooltip?: string;
helpPage?: string;
disabled?: boolean;
afterName?: ComponentChildren;
/** ID for the input element, useful for accessibility with external labels */
id?: string;
}
export default function FormToggle({ currentValue, helpPage, switchOnName, switchOnTooltip, switchOffName, switchOffTooltip, onChange, disabled, afterName }: FormToggleProps) {
export default function FormToggle({ currentValue, helpPage, switchOnName, switchOnTooltip, switchOffName, switchOffTooltip, onChange, disabled, afterName, id }: FormToggleProps) {
const [ disableTransition, setDisableTransition ] = useState(true);
const hasLabel = switchOnName || switchOffName;
useEffect(() => {
const timeout = setTimeout(() => {
@@ -28,7 +33,7 @@ export default function FormToggle({ currentValue, helpPage, switchOnName, switc
return (
<div className="switch-widget">
<span className="switch-name">{ currentValue ? switchOffName : switchOnName }</span>
{hasLabel && <span className="switch-name">{ currentValue ? switchOffName : switchOnName }</span>}
{ afterName }
<label>
@@ -37,6 +42,7 @@ export default function FormToggle({ currentValue, helpPage, switchOnName, switc
title={currentValue ? switchOffTooltip : switchOnTooltip }
>
<input
id={id}
className="switch-toggle"
type="checkbox"
checked={currentValue === true}

View File

@@ -1,85 +0,0 @@
.tn-toggle {
--toggle-track-width: 44px;
--toggle-track-height: 24px;
--toggle-off-track-background: var(--more-accented-background-color);
--toggle-on-track-background: var(--main-text-color);
--toggle-thumb-size: 18px;
--toggle-thumb-background: var(--main-background-color);
display: inline-flex;
align-items: center;
}
/* The track of the toggle switch */
.tn-toggle-track {
display: block;
position: relative;
width: var(--toggle-track-width);
height: var(--toggle-track-height);
border-radius: var(--toggle-track-height);
background-color: var(--toggle-off-track-background);
transition: background 200ms ease-in;
cursor: pointer;
}
.tn-toggle-track.disable-transitions,
.tn-toggle-track.disable-transitions::after {
transition: none !important;
}
.tn-toggle-track.on {
background: var(--toggle-on-track-background);
transition: background 100ms ease-out;
}
/* The thumb of the toggle switch */
.tn-toggle-track::after {
--offset: calc((var(--toggle-track-height) - var(--toggle-thumb-size)) / 2);
content: "";
position: absolute;
top: var(--offset);
inset-inline-start: var(--offset);
width: var(--toggle-thumb-size);
height: var(--toggle-thumb-size);
background-color: var(--toggle-thumb-background);
border-radius: 50%;
transition: transform 600ms cubic-bezier(0.22, 1, 0.36, 1);
}
.tn-toggle-track.on::after {
transform: translateX(calc(var(--toggle-track-width) - var(--toggle-thumb-size) - var(--offset) * 2));
transition: transform 200ms cubic-bezier(0.64, 0, 0.78, 0);
}
body[dir="rtl"] .tn-toggle-track.on::after {
transform: translateX(calc((var(--toggle-track-width) - var(--toggle-thumb-size) - var(--offset) * 2) * -1));
}
/* Hidden checkbox for accessibility */
.tn-toggle-input {
position: absolute;
top: 0;
inset-inline-start: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
/* Focus state */
.tn-toggle-track:has(.tn-toggle-input:focus-visible) {
outline: 2px solid var(--button-border-color);
outline-offset: 2px;
}
/* Disabled state */
.tn-toggle-track.disabled {
opacity: 0.5;
cursor: not-allowed;
}
.tn-toggle-track.disabled .tn-toggle-input {
cursor: not-allowed;
}

View File

@@ -1,49 +0,0 @@
import clsx from "clsx";
import "./Toggle.css";
import { useEffect, useState } from "preact/hooks";
interface ToggleProps {
currentValue: boolean;
onChange(newValue: boolean): void;
disabled?: boolean;
id?: string;
}
/**
* A simple toggle switch without labels. For use with OptionsRow or other
* contexts where the label is provided separately.
*/
export default function Toggle({ currentValue, onChange, disabled, id }: ToggleProps) {
const [disableTransition, setDisableTransition] = useState(true);
useEffect(() => {
const timeout = setTimeout(() => {
setDisableTransition(false);
}, 100);
return () => clearTimeout(timeout);
}, []);
return (
<label className="tn-toggle">
<div
className={clsx("tn-toggle-track", {
on: currentValue,
disabled,
"disable-transitions": disableTransition
})}
>
<input
id={id}
className="tn-toggle-input"
type="checkbox"
checked={currentValue}
onInput={(e) => {
onChange(!currentValue);
e.preventDefault();
}}
disabled={disabled}
/>
</div>
</label>
);
}