feat(settings): 侧边卡片设置页 UI/UX 现代化——卡片底部设置条替代隐形齿轮 (#300)

* feat(settings): 侧边卡片设置页 UI/UX 现代化——卡片底部设置条替代隐形齿轮,协调双色启用态

- 二级设置入口:卡片右下 16px 幽灵齿轮(tertiary 色、像素钉位)改为
  卡片底缘全宽设置条(hairline 分隔 + 齿轮图标 + 「功能设置」文字
  标签),常态可发现;hover 换品牌淡底 + brand 字色引导。可见性条件
  与 aria-label 契约不变(仅父级启用且声明 settings)
- 常规行齿轮升级为带 border-l2 边框的 22px 芯片按钮,hover 同样
  accent 引导
- 启用卡片:品牌描边柔化为 color-mix(45%),勾选徽标改用
  state-success-primary(绿=已开启确认,brand=激活强调的协调双色)
- 节奏:section gap 16 / group padding 20 / 网格 minmax(180px) gap 12;
  计数徽章改为 accent-soft 淡底 pill;开关关态 thumb 降档 +
  track 底 layer-2
- 动效纪律:全部过渡 ≤160ms 且仅颜色属性;prefers-reduced-motion
  扩展覆盖设置条/行齿轮/弹窗行
- 清理:合并 .pluginEntries 重复定义;文档同步(AGENTS.md §5 +
  设计文档新增 §9 偏差记录)

* feat(settings): 按反馈迭代——卡片开关滑块、二级弹窗长列表滚动、卡片等高

- 勾选徽标 → 紧凑品牌开关滑块(30×16 track + 10px thumb,开=品牌填充
  反色拇指,关=中性且不渲染;纯视觉指示,卡片本身仍是 aria-pressed
  开关,语义契约不变)
- 二级弹窗长列表:popupRows 内部滚动 max-height min(52vh,440px) +
  细滚动条(--dsw-alias-scrollbar-* 令牌),终端 6 行场景弹窗壳体与
  Done 固定、仅行区滚动;行密度收紧(padding 12/14、gap 8)
- 卡片等高:.card min-height 106px + .cardMain flex:1 + .cardTop
  min-height 28px——带设置条/不带设置条/无图标卡片全部等高
- prefers-reduced-motion 覆盖 cardSwitch 过渡;文档同步(AGENTS.md
  §5 + 设计文档 §9 迭代记录)

* fix(settings): 二级设置弹窗行区取消横向滚动——box-sizing:border-box + overflow-x:hidden

popupRows 的 width:100% 配 content-box 会在 padding-right:4px 时把
内容盒撑宽 4px,overflow-y:auto 隐式启用的 overflow-x 因此出现横向
滚动条;改为 border-box 并显式 overflow-x:hidden(minifier 输出
overflow:hidden auto),行区始终填满弹窗宽度、无左右滑动。add-plugin
弹窗的 pluginEntries 同步加 overflow-x:hidden。
This commit is contained in:
Atlas Zeng
2026-08-21 21:39:59 +08:00
committed by GitHub
parent 6cea4bdd6f
commit 0fdf63b081
4 changed files with 177 additions and 81 deletions
+20 -16
View File
@@ -21,13 +21,15 @@
* one-line intro (the DSH section heading+intro recipe).
*
* A card's on/off state is its VISUAL STATE: enabled = highlighted (brand
* border + tinted fill + a circular check badge pinned to the card's far
* right), disabled = neutral and dimmed. Features that declare
* `settings.toggles` carry a gear corner button that opens a native Modal
* (wider than the primitive default) with the related settings as
* title/desc + custom-switch rows and a Done footer. The toggles
* themselves are custom switches: a real checkbox (native semantics and
* focus) driving a styled track/thumb.
* border + tinted fill + a compact switch knob at the card's far right),
* disabled = neutral and dimmed. Features that declare
* `settings.toggles` carry a labeled settings strip at the card's bottom
* edge that opens a native Modal (wider than the primitive default) with
* the related settings as title/desc + custom-switch rows and a Done
* footer; the popup body scrolls internally when a feature declares many
* rows (e.g. Terminal's six). The toggles themselves are custom
* switches: a real checkbox (native semantics and focus) driving a styled
* track/thumb.
*
* Writes ride the plugin's own fenced settings route (the host calls the
* settings seam in-process — the DSH settings RPC domain does not serve
@@ -40,7 +42,6 @@
*/
import { Fragment, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
import {
IconCheckOutline16,
IconChevronDownOutline14,
IconPlusOutline16,
IconSettingsOutline16,
@@ -793,8 +794,9 @@ export function SideCardSection({ store, service }: SideCardSectionProps) {
* One SMALL toggle card for the responsive inventory grid: the card's main
* area is the switch (click to flips, visual state IS the state), the icon
* sits in a rounded chip, the check badge pins to the far right, and a
* feature that declares related settings carries a gear corner button
* opening its settings popup.
* feature that declares related settings gets a labeled SETTINGS STRIP
* across the card's bottom edge (gear icon + text) opening its settings
* popup — discoverable at rest, not a hover-only ghost corner button.
*/
const renderCard = (props: {
title: string
@@ -802,13 +804,13 @@ export function SideCardSection({ store, service }: SideCardSectionProps) {
icon?: ReactNode
enabled: boolean
onToggle: (next: boolean) => void
/** A feature with declared related settings shows the gear corner button. */
/** A feature with declared related settings shows the settings strip. */
onOpenSettings?: () => void
}) => {
const hasSettings = props.onOpenSettings !== undefined
return (
<div
className={clsx(css.card, props.enabled && css.cardOn, hasSettings && css.cardWithGear)}
className={clsx(css.card, props.enabled && css.cardOn)}
>
<button
type="button"
@@ -823,8 +825,10 @@ export function SideCardSection({ store, service }: SideCardSectionProps) {
)}
<span className={css.cardTitle}>{props.title}</span>
{props.enabled && (
<span className={css.cardCheck}>
<IconCheckOutline16 size={12} />
<span className={css.cardSwitch} aria-hidden="true">
<span className={css.cardSwitchTrack}>
<span className={css.cardSwitchThumb} />
</span>
</span>
)}
</span>
@@ -833,12 +837,12 @@ export function SideCardSection({ store, service }: SideCardSectionProps) {
{hasSettings && (
<button
type="button"
className={css.cardGear}
className={css.cardSettings}
aria-label={`${props.title} ${t('settingsPopup')}`}
title={t('settingsPopup')}
onClick={props.onOpenSettings}
>
<IconSettingsOutline16 size={12} />
<span>{t('settingsPopup')}</span>
</button>
)}
</div>