feat: grey out/disable restore-default buttons at default value, extend to advanced toolbar sliders

- SliderComponent's reset icon now gets an is-disabled class + no
  onClick handler when the current value already equals defaultValue,
  with matching opacity/pointer-events CSS.
- Added the same restore-default affordance to the three native
  Setting-API sliders in the Advanced Toolbar tab (Toolbar row count,
  Bottom offset, Toolbar extra spacing) via a new addResettableSlider
  helper, using Obsidian's own ExtraButtonComponent.setDisabled() for
  the greyed-out state. Each slider's onChange keeps its existing
  save/updateStyles logic; the reset button now also drives the
  slider's own setValue() and toggles its own disabled state without
  a full section re-render, so dragging stays smooth.
- Button height/width are plain number inputs, not sliders, so left
  as-is per the request scope.

Worth checking: General tab spacing slider and the three Advanced
Toolbar sliders all start disabled at their defaults, enable once
dragged away, and clicking reset snaps the slider back and re-disables
the button.
This commit is contained in:
johnny1093 2026-07-11 10:36:22 -04:00
parent 15b3a52bb9
commit a5600d9565
3 changed files with 111 additions and 54 deletions

View file

@ -49,6 +49,12 @@
display: flex;
}
.cmdr-slider .clickable-icon.is-disabled {
opacity: 0.35;
cursor: default;
pointer-events: none;
}
.cmdr-menu-more-options {
color: var(--text-muted);
position: absolute;

View file

@ -1,29 +1,77 @@
import { Notice, Platform, setIcon, Setting } from "obsidian";
import {
ExtraButtonComponent,
Notice,
Platform,
setIcon,
Setting,
SliderComponent,
} from "obsidian";
import { Fragment, h } from "preact";
import { useEffect, useRef } from "preact/hooks";
import { DEFAULT_SETTINGS } from "src/constants";
import t from "src/l10n";
import CommanderPlugin from "src/main";
import { injectIcons, ObsidianIcon, updateStyles } from "src/util";
import ChooseIconModal from "../chooseIconModal";
function addResettableSlider(
containerEl: HTMLElement,
opts: {
name: string;
desc: string;
min: number;
max: number;
step: number;
value: number;
defaultValue: number;
onChange: (value: number) => Promise<void>;
}
): void {
let resetBtn: ExtraButtonComponent;
let slider: SliderComponent;
new Setting(containerEl)
.setName(opts.name)
.setDesc(opts.desc)
.addSlider((cb) => {
slider = cb;
cb.setLimits(opts.min, opts.max, opts.step)
.setValue(opts.value)
.setDynamicTooltip()
.onChange(async (value) => {
await opts.onChange(value);
resetBtn.setDisabled(value === opts.defaultValue);
});
})
.addExtraButton((bt) => {
resetBtn = bt;
bt.setIcon("reset")
.setTooltip(t("Restore default"))
.setDisabled(opts.value === opts.defaultValue)
.onClick(async () => {
slider.setValue(opts.defaultValue);
await opts.onChange(opts.defaultValue);
resetBtn.setDisabled(true);
});
});
}
function render(containerEl: HTMLElement, plugin: CommanderPlugin): void {
containerEl.empty();
new Setting(containerEl)
.setName("Toolbar row count")
.setDesc(
"Set how many rows the mobile toolbar should have. Set this to 0 to remove the toolbar."
)
.addSlider((cb) =>
cb
.setLimits(0, 5, 1)
.setValue(plugin.settings.advancedToolbar.rowCount)
.setDynamicTooltip()
.onChange(async (value) => {
plugin.settings.advancedToolbar.rowCount = value;
await plugin.saveSettings();
updateStyles(plugin.settings.advancedToolbar);
})
);
addResettableSlider(containerEl, {
name: "Toolbar row count",
desc: "Set how many rows the mobile toolbar should have. Set this to 0 to remove the toolbar.",
min: 0,
max: 5,
step: 1,
value: plugin.settings.advancedToolbar.rowCount,
defaultValue: DEFAULT_SETTINGS.advancedToolbar.rowCount,
onChange: async (value) => {
plugin.settings.advancedToolbar.rowCount = value;
await plugin.saveSettings();
updateStyles(plugin.settings.advancedToolbar);
},
});
new Setting(containerEl)
.setName("Column layout")
@ -51,22 +99,20 @@ function render(containerEl: HTMLElement, plugin: CommanderPlugin): void {
// })
// })
new Setting(containerEl)
.setName("Bottom offset")
.setDesc(
"Offset the toolbar from the bottom of the screen. This is useful if the toolbar is partially obscured by other UI elements."
)
.addSlider((cb) =>
cb
.setLimits(0, 32, 1)
.setValue(plugin.settings.advancedToolbar.heightOffset)
.setDynamicTooltip()
.onChange(async (value) => {
plugin.settings.advancedToolbar.heightOffset = value;
await plugin.saveSettings();
updateStyles(plugin.settings.advancedToolbar);
})
);
addResettableSlider(containerEl, {
name: "Bottom offset",
desc: "Offset the toolbar from the bottom of the screen. This is useful if the toolbar is partially obscured by other UI elements.",
min: 0,
max: 32,
step: 1,
value: plugin.settings.advancedToolbar.heightOffset,
defaultValue: DEFAULT_SETTINGS.advancedToolbar.heightOffset,
onChange: async (value) => {
plugin.settings.advancedToolbar.heightOffset = value;
await plugin.saveSettings();
updateStyles(plugin.settings.advancedToolbar);
},
});
if (Platform.isMobile) {
const description = createFragment();
@ -192,22 +238,20 @@ function render(containerEl: HTMLElement, plugin: CommanderPlugin): void {
}
})
);
new Setting(advancedEl)
.setName("Toolbar extra spacing")
.setDesc(
"Some themes need extra spacing in the toolbar. If your toolbar doesn't wrap properly, try increasing this value."
)
.addSlider((cb) =>
cb
.setLimits(0, 64, 1)
.setValue(plugin.settings.advancedToolbar.spacing)
.setDynamicTooltip()
.onChange(async (value) => {
plugin.settings.advancedToolbar.spacing = value;
await plugin.saveSettings();
updateStyles(plugin.settings.advancedToolbar);
})
);
addResettableSlider(advancedEl, {
name: "Toolbar extra spacing",
desc: "Some themes need extra spacing in the toolbar. If your toolbar doesn't wrap properly, try increasing this value.",
min: 0,
max: 64,
step: 1,
value: plugin.settings.advancedToolbar.spacing,
defaultValue: DEFAULT_SETTINGS.advancedToolbar.spacing,
onChange: async (value) => {
plugin.settings.advancedToolbar.spacing = value;
await plugin.saveSettings();
updateStyles(plugin.settings.advancedToolbar);
},
});
}
export default function AdvancedToolbarSettings({

View file

@ -111,11 +111,18 @@ export function SliderComponent({
aria-label={t("Restore default")}
icon="reset"
size={16}
className="clickable-icon"
onClick={(): void => {
setVal(defaultValue);
props.changeHandler(defaultValue);
}}
className={`clickable-icon${
val === defaultValue ? " is-disabled" : ""
}`}
aria-disabled={val === defaultValue}
onClick={
val === defaultValue
? undefined
: (): void => {
setVal(defaultValue);
props.changeHandler(defaultValue);
}
}
/>
)}
<ChangeableText