feat(web): settings form polish — dialog layout, homepage button, no default rows, unified dropdown text (#39)

Co-authored-by: Alice <alice@prismshadow.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Yaowei Zheng
2026-07-23 01:07:45 +08:00
committed by GitHub
parent 6d56ce9ccc
commit d01d0faf7a
10 changed files with 363 additions and 183 deletions
@@ -43,6 +43,11 @@ export interface OptionMenuChoice<T extends string> {
const PANEL_WIDTH = 288; // w-72
/** Row-title text follows the control's size tier, so the dropdown reads exactly like an Input of the same tier (review: dropdown text = input text); descriptions stay text-xs secondary. */
const rowTextClass: Record<ControlSize, string> = { base: "text-base", sm: "text-xs" };
/** Descriptions keep one step below the row title at every tier, so the title/description hierarchy survives the sm tier's text-xs titles. */
const rowDescClass: Record<ControlSize, string> = { base: "text-xs", sm: "text-[11px]" };
export function OptionMenu<T extends string>({
options,
value,
@@ -143,7 +148,7 @@ export function OptionMenu<T extends string>({
>
<span className="flex items-center justify-between gap-2">
<span
className={`text-sm ${
className={`${rowTextClass[size]} ${
opt.value === value
? "font-medium text-gray-900 dark:text-gray-100"
: "text-gray-700 dark:text-gray-300"
@@ -170,7 +175,9 @@ export function OptionMenu<T extends string>({
</svg>
)}
</span>
<span className="mt-0.5 block text-xs text-gray-500 dark:text-gray-400">
<span
className={`mt-0.5 block ${rowDescClass[size]} text-gray-500 dark:text-gray-400`}
>
{opt.description}
</span>
</button>
+4 -1
View File
@@ -48,6 +48,9 @@ const CONTROL_CLASS =
"dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:hover:border-gray-600 " +
"dark:focus:border-gray-400 dark:focus:ring-gray-500/30";
/** Menu-row text follows the control's size tier, so the dropdown reads exactly like an Input of the same tier (review: dropdown text = input text). */
const menuTextClass: Record<ControlSize, string> = { base: "text-base", sm: "text-xs" };
export function Select({
label,
hint,
@@ -169,7 +172,7 @@ export function Select({
aria-selected={o.value === current}
disabled={o.disabled}
onClick={() => pick(o.value)}
className={`flex w-full items-center px-3 py-1.5 text-left text-sm transition-colors duration-150 disabled:opacity-50 ${
className={`flex w-full items-center px-3 py-1.5 text-left ${menuTextClass[size]} transition-colors duration-150 disabled:opacity-50 ${
o.value === current
? "bg-gray-100 font-medium text-gray-900 dark:bg-gray-800 dark:text-gray-100"
: "text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"
+44
View File
@@ -0,0 +1,44 @@
/**
* iOS-style toggle switch: a `button` with `role="switch"` + `aria-checked` (a native
* button is keyboard-operable out of the box — Space/Enter activate it — and is labelable
* content, so clicking an enclosing `<label>`'s text toggles it too). Sliding knob with a
* color/transform transition; the on-state follows the theme accent variable (same source
* as Button primary), the off-state is gray; dark-mode aware; disabled dims and blocks.
* Sized for compact (sm) form rows, matching the dialogs' controls.
*/
import type { ButtonHTMLAttributes } from "react";
export interface SwitchProps extends Omit<
ButtonHTMLAttributes<HTMLButtonElement>,
"onChange" | "type" | "role"
> {
checked: boolean;
onChange: (checked: boolean) => void;
}
export function Switch({ checked, onChange, disabled, className, ...rest }: SwitchProps) {
return (
<button
type="button"
role="switch"
aria-checked={checked}
disabled={disabled}
onClick={() => onChange(!checked)}
className={
"relative inline-flex h-5 w-9 shrink-0 items-center rounded-full " +
"transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-gray-400/30 " +
"disabled:cursor-not-allowed disabled:opacity-60 " +
(checked ? "bg-[var(--accent-bg)]" : "bg-gray-300 dark:bg-gray-600") +
` ${className ?? ""}`
}
{...rest}
>
<span
aria-hidden
className={`inline-block size-4 transform rounded-full bg-white shadow transition-transform duration-200 ${
checked ? "translate-x-[18px]" : "translate-x-0.5"
}`}
/>
</button>
);
}