Settings: Improve layout of a setting section and error

This commit is contained in:
Nuckyz 2025-07-16 18:15:52 -03:00
parent 828358bd2e
commit d0869c41cd
No known key found for this signature in database
GPG key ID: 440BF8296E1C4AD9
10 changed files with 77 additions and 33 deletions

View file

@ -116,7 +116,11 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
); );
}); });
return <Flex flexDirection="column" style={{ gap: 12, marginBottom: 16 }}>{options}</Flex>; return (
<div className="vc-plugins-settings">
{options}
</div>
);
} }
function renderMoreUsers(_label: string, count: number) { function renderMoreUsers(_label: string, count: number) {

View file

@ -16,11 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { wordsFromCamel, wordsToTitle } from "@utils/text"; import { Switch } from "@components/settings/Switch";
import { PluginOptionBoolean } from "@utils/types"; import { PluginOptionBoolean } from "@utils/types";
import { Forms, React, Switch, useState } from "@webpack/common"; import { React, useState } from "@webpack/common";
import { resolveError, SettingProps } from "./Common"; import { resolveError, SettingProps, SettingsSection } from "./Common";
export function BooleanSetting({ option, pluginSettings, definedSettings, id, onChange }: SettingProps<PluginOptionBoolean>) { export function BooleanSetting({ option, pluginSettings, definedSettings, id, onChange }: SettingProps<PluginOptionBoolean>) {
const def = pluginSettings[id] ?? option.default; const def = pluginSettings[id] ?? option.default;
@ -40,20 +40,9 @@ export function BooleanSetting({ option, pluginSettings, definedSettings, id, on
} }
return ( return (
<Forms.FormSection> <SettingsSection name={id} description={option.description} error={error} inlineSetting>
<Switch <Switch checked={state} onChange={handleChange} />
value={state} </SettingsSection>
onChange={handleChange}
note={option.description}
disabled={option.disabled?.call(definedSettings) ?? false}
{...option.componentProps}
hideBorder
style={{ marginBottom: "0.5em" }}
>
{wordsToTitle(wordsFromCamel(id))}
</Switch>
{error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}
</Forms.FormSection>
); );
} }

View file

@ -4,12 +4,15 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { Margins } from "@utils/margins"; import { classNameFactory } from "@api/Styles";
import { classes } from "@utils/misc";
import { wordsFromCamel, wordsToTitle } from "@utils/text"; import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { DefinedSettings, PluginOptionBase } from "@utils/types"; import { DefinedSettings, PluginOptionBase } from "@utils/types";
import { Forms } from "@webpack/common"; import { Text } from "@webpack/common";
import { PropsWithChildren } from "react"; import { PropsWithChildren } from "react";
export const cl = classNameFactory("vc-plugins-setting-");
interface SettingBaseProps<T> { interface SettingBaseProps<T> {
option: T; option: T;
onChange(newValue: any): void; onChange(newValue: any): void;
@ -34,15 +37,20 @@ interface SettingsSectionProps extends PropsWithChildren {
name: string; name: string;
description: string; description: string;
error: string | null; error: string | null;
inlineSetting?: boolean;
} }
export function SettingsSection({ name, description, error, children }: SettingsSectionProps) { export function SettingsSection({ name, description, error, inlineSetting, children }: SettingsSectionProps) {
return ( return (
<Forms.FormSection> <div className={cl("section")}>
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(name))}</Forms.FormTitle> <div className={classes(cl("content"), inlineSetting && cl("inline"))}>
<Forms.FormText className={Margins.bottom20} type="description">{description}</Forms.FormText> <div className={cl("label")}>
{children} {name && <Text className={cl("title")} variant="text-md/medium">{wordsToTitle(wordsFromCamel(name))}</Text>}
{error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>} {description && <Text className={cl("description")} variant="text-sm/normal">{description}</Text>}
</Forms.FormSection> </div>
{children}
</div>
{error && <Text className={cl("error")} variant="text-sm/normal">{error}</Text>}
</div>
); );
} }

View file

@ -53,9 +53,9 @@ export function NumberSetting({ option, pluginSettings, definedSettings, id, onC
<TextInput <TextInput
type="number" type="number"
pattern="-?[0-9]+" pattern="-?[0-9]+"
placeholder={option.placeholder ?? "Enter a number"}
value={state} value={state}
onChange={handleChange} onChange={handleChange}
placeholder={option.placeholder ?? "Enter a number"}
disabled={option.disabled?.call(definedSettings) ?? false} disabled={option.disabled?.call(definedSettings) ?? false}
{...option.componentProps} {...option.componentProps}
/> />

View file

@ -41,14 +41,14 @@ export function SelectSetting({ option, pluginSettings, definedSettings, onChang
return ( return (
<SettingsSection name={id} description={option.description} error={error}> <SettingsSection name={id} description={option.description} error={error}>
<Select <Select
isDisabled={option.disabled?.call(definedSettings) ?? false}
options={option.options}
placeholder={option.placeholder ?? "Select an option"} placeholder={option.placeholder ?? "Select an option"}
options={option.options}
maxVisibleItems={5} maxVisibleItems={5}
closeOnSelect={true} closeOnSelect={true}
select={handleChange} select={handleChange}
isSelected={v => v === state} isSelected={v => v === state}
serialize={v => String(v)} serialize={v => String(v)}
isDisabled={option.disabled?.call(definedSettings) ?? false}
{...option.componentProps} {...option.componentProps}
/> />
</SettingsSection> </SettingsSection>

View file

@ -39,7 +39,6 @@ export function SliderSetting({ option, pluginSettings, definedSettings, id, onC
return ( return (
<SettingsSection name={id} description={option.description} error={error}> <SettingsSection name={id} description={option.description} error={error}>
<Slider <Slider
disabled={option.disabled?.call(definedSettings) ?? false}
markers={option.markers} markers={option.markers}
minValue={option.markers[0]} minValue={option.markers[0]}
maxValue={option.markers[option.markers.length - 1]} maxValue={option.markers[option.markers.length - 1]}
@ -47,6 +46,7 @@ export function SliderSetting({ option, pluginSettings, definedSettings, id, onC
onValueChange={handleChange} onValueChange={handleChange}
onValueRender={(v: number) => String(v.toFixed(2))} onValueRender={(v: number) => String(v.toFixed(2))}
stickToMarkers={option.stickToMarkers ?? true} stickToMarkers={option.stickToMarkers ?? true}
disabled={option.disabled?.call(definedSettings) ?? false}
{...option.componentProps} {...option.componentProps}
/> />
</SettingsSection> </SettingsSection>

View file

@ -40,11 +40,11 @@ export function TextSetting({ option, pluginSettings, definedSettings, id, onCha
<SettingsSection name={id} description={option.description} error={error}> <SettingsSection name={id} description={option.description} error={error}>
<TextInput <TextInput
type="text" type="text"
placeholder={option.placeholder ?? "Enter a value"}
value={state} value={state}
onChange={handleChange} onChange={handleChange}
placeholder={option.placeholder ?? "Enter a value"}
disabled={option.disabled?.call(definedSettings) ?? false}
maxLength={null} maxLength={null}
disabled={option.disabled?.call(definedSettings) ?? false}
{...option.componentProps} {...option.componentProps}
/> />
</SettingsSection> </SettingsSection>

View file

@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import "./styles.css";
import { OptionType } from "@utils/types"; import { OptionType } from "@utils/types";
import { ComponentType } from "react"; import { ComponentType } from "react";

View file

@ -0,0 +1,35 @@
.vc-plugins-setting-section {
display: flex;
flex-direction: column;
gap: 0.5em;
}
.vc-plugins-setting-content {
display: flex;
flex-direction: column;
gap: 0.5em;
}
.vc-plugins-setting-inline {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.vc-plugins-setting-label {
display: flex;
flex-direction: column;
gap: 0.25em;
}
.vc-plugins-setting-title {
color: var(--header-primary);
}
.vc-plugins-setting-description {
color: var(--header-secondary);
}
.vc-plugins-setting-error {
color: var(--text-danger);
}

View file

@ -74,3 +74,9 @@
.vc-plugins-info-icon:not(:hover, :focus) { .vc-plugins-info-icon:not(:hover, :focus) {
color: var(--text-muted); color: var(--text-muted);
} }
.vc-plugins-settings {
display: flex;
flex-direction: column;
gap: 1.25em;
}