Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed

- **设置页开关改用宿主 `Switch` 组件(#108)。** 五个布尔设置(按套餐过滤模型、零数据保留、网页搜索、侧边栏额度、缓存图片卸载)原先是 `appearance:none` 的 checkbox 加伪元素画出的开关,现在是宿主 primitives 的 `Switch`(`<button role="switch" aria-checked>`),与宿主其他设置页开关同源,读屏读到的状态与画面不会再不一致。外观保持 40×24;底色与滑块改为 160ms 同步过渡,按下时轻微缩小。点开关行的标题文字不再切换开关,需点开关本身。

## [0.16.0] - 2026-10-30

### Changed
Expand Down
2 changes: 1 addition & 1 deletion docs/agent-reference/client-and-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ Task-specific reference moved from the former root `AGENTS.md`. All source and t
包含基础层隐藏项的显式覆盖。保存冲突或缺少原子写入能力时保留草稿。
显示全部会选中已加载的目录模型;目录失败不清空已存选择。
- **字号与校验说明**:页面及面板辅助文字至少 12px;重试次数提示与实际 0~50 次限制一致。
- **设置页动效与悬停(2026-10-29)**:可按的元素(`cc-btn`、`cc-segItem`、`cc-iconButton`、`cc-resetInline`)`:active` 时缩放(0.97,20~28px 图标按钮 0.95),160ms `cubic-bezier(.23,1,.32,1)`;折叠箭头、卡片入场、保存条、额度进度条、`cc-notice` 入场共用这条曲线,颜色与描边 150ms `ease`。只动 `transform`/`opacity`/颜色:额度条 `cc-usageBarFill` 与 `cc-miniMeterFill` 满宽,按比例 `translateX` 左移(保留圆角端),`cc-notice` 用 `@starting-style` 从上方 4px 淡入;保存条 48px、不过冲、出场快于入场。所有 `:hover` 集中在页尾的 `(hover:hover) and (pointer:fine)` 块,块内校验失败的描边排在普通悬停之后,以同等优先级压住它;整行开关(分组标题、账户卡片头)悬停只加深箭头,不铺底色。`prefers-reduced-motion` 去掉缩放与位移、保留淡入与颜色。分组、账户卡片展开收起刻意不加动画。开关 40×24,底色与滑块同步过渡。约束由 `tests/styles.test.ts` 末尾四条用例锁定。分组说明 `cc-groupDesc` 下直接接账户列表、提示块组或覆盖表时留 12px(设置行自带 12px 内边距,不另加)。
- **设置页动效与悬停(2026-10-29)**:可按的元素(`cc-btn`、`cc-segItem`、`cc-iconButton`、`cc-resetInline`)`:active` 时缩放(0.97,20~28px 图标按钮 0.95),160ms `cubic-bezier(.23,1,.32,1)`;折叠箭头、卡片入场、保存条、额度进度条、`cc-notice` 入场共用这条曲线,颜色与描边 150ms `ease`。只动 `transform`/`opacity`/颜色:额度条 `cc-usageBarFill` 与 `cc-miniMeterFill` 满宽,按比例 `translateX` 左移(保留圆角端),`cc-notice` 用 `@starting-style` 从上方 4px 淡入;保存条 48px、不过冲、出场快于入场。所有 `:hover` 集中在页尾的 `(hover:hover) and (pointer:fine)` 块,块内校验失败的描边排在普通悬停之后,以同等优先级压住它;整行开关(分组标题、账户卡片头)悬停只加深箭头,不铺底色。`prefers-reduced-motion` 去掉缩放与位移、保留淡入与颜色。分组、账户卡片展开收起刻意不加动画。布尔开关(`ToggleField`)用宿主 primitives 的 `Switch`(issue #108,`<button role="switch" aria-checked>`,不再是 `appearance:none` 的 checkbox 加伪元素);`.cc-switch` 只做三处调整:尺寸保持设计画布的 40×24、滑块 20px(原语为 36×20,两者行程都是 16px);底色与滑块统一为 160ms 强 ease-out 同步过渡;按压缩放 0.95。原语不收 `id`,所以开关行标题是纯文本、开关以 `aria-label` 取名,点标题不再切换。`Switch` 的签名与样式在 `0.2.1-rc.2`、`0.2.1-rc.4`、`0.2.2-alpha.2` 三版逐字一致(2026-10-30 核对),升级宿主时一并复核。约束由 `tests/styles.test.ts` 末尾四条用例锁定。分组说明 `cc-groupDesc` 下直接接账户列表、提示块组或覆盖表时留 12px(设置行自带 12px 内边距,不另加)。
- **右侧只有一条边界(2026-10-22)**:设置行控件、账户卡片、分组头动作(「刷新」)、「查看模型用量」都贴内容
右缘。为此「恢复默认」不在控件后面留列,而是放在标题行「已自定义」徽标后的 20px 圆形图标按钮(`OverrideTag`,
`cc-resetInline`;只清空存储值的草稿只显示按钮)——同日先后试过「每行末尾留 28px 复位空位」与「卡片、分组头
Expand Down
58 changes: 31 additions & 27 deletions lib/client.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 18 additions & 19 deletions src/client/page-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,23 +213,21 @@ export const PAGE_CSS = `
.cc-check:checked{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}
.cc-check:checked::after{content:'';position:absolute;top:3px;left:5px;width:3px;height:7px;border:solid var(--dsw-alias-label-primary-foreground,#fff);border-width:0 1.5px 1.5px 0;transform:rotate(45deg)}
.cc-checkName{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Modelled on the platform's own Switch primitive (ui-primitives
* Switch.module.css): a 40x24 capsule padding its track by 2px and sliding a
* 20px thumb across the 36px content box. Three things are load-bearing.
* (1) corner-shape:round on BOTH track and thumb — the theme sets a global
* superellipse corner shape, which squares a capsule's ends off around a round
* thumb. (2) No literal colours: brand-primary INVERTS between the modes, so a
* hardcoded white thumb vanishes on the checked track in dark mode; the brand
* foreground is what the platform paints on that fill. (3) border-l3 as the
* off track — border-l2 leaves it indistinguishable from the page. The track
* and thumb share one duration so the switch reads as one object. */
.cc-toggle{box-sizing:border-box;appearance:none;flex-shrink:0;width:40px;height:24px;margin:0;padding:2px;border:0;border-radius:12px;corner-shape:round;background:var(--dsw-alias-border-l3);cursor:pointer;position:relative;transition:background-color .12s ease}
.cc-toggle:checked{background:var(--dsw-alias-brand-primary)}
.cc-toggle::after{content:'';display:block;width:20px;height:20px;border-radius:50%;corner-shape:round;background:var(--dsw-alias-label-primary-foreground);transition:transform .12s ease}
.cc-toggle:checked::after{transform:translateX(16px)}
.cc-toggle:not(:checked)::after{background:var(--dsw-alias-switch-thumb,var(--dsw-alias-label-primary-foreground))}
.cc-toggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
.cc-toggle:disabled{cursor:default;opacity:.5}
/* The settings switches are the platform's Switch primitive (issue #108); its
* module CSS owns the colours, capsule shape, focus ring and disabled look.
* This layer only adjusts it. (1) Size stays the design canvas's 40x24 with a
* 20px thumb, one step up from the primitive's 36x20; the travel is 16px in
* both, so the primitive's own translateX still lands the thumb. The
* cc-rowControl> qualifier lifts these above the module's single-class rules
* whatever order the two sheets load in. (2) Motion joins the page's: the
* primitive slides the thumb over 120ms ease and snaps the track colour, so
* the thumb and track now share one 160ms strong ease-out and read as one
* object, and the switch scales to .95 on press like the other small
* pressables. The thumb is targeted as the button's only child because its
* module class name is hashed. */
.cc-rowControl>.cc-switch{width:40px;height:24px;transition:background-color .16s var(--cc-ease-out),transform .16s var(--cc-ease-out)}
.cc-rowControl>.cc-switch>span{width:20px;height:20px;transition:transform .16s var(--cc-ease-out)}
.cc-switch:active:not(:disabled){transform:scale(.95)}
.cc-accountMode{align-items:center;gap:8px;display:flex;margin:12px 16px 0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
.cc-accountList{flex-direction:column;gap:8px;display:flex}
.cc-accountList+.cc-panel{margin:8px 16px 16px}
Expand Down Expand Up @@ -426,9 +424,10 @@ export const PAGE_CSS = `
* changes, which carry meaning without motion. Card entrance keeps its fade
* (the transform is neutralised), and the fold is already instant. */
@media (prefers-reduced-motion:reduce){
.cc-foldChevron,.cc-toggle::after,.cc-usageBarFill,.cc-miniMeterFill{transition:none}
.cc-foldChevron,.cc-rowControl>.cc-switch>span,.cc-usageBarFill,.cc-miniMeterFill{transition:none}
.cc-btn,.cc-iconButton,.cc-resetInline,.cc-segItem{transition:background-color .15s ease,color .15s ease}
.cc-btn:active:not(:disabled),.cc-iconButton:active:not(:disabled),.cc-resetInline:active:not(:disabled),.cc-segItem:active:not(:disabled):not(.cc-segOn){transform:none}
.cc-rowControl>.cc-switch{transition:background-color .15s ease}
.cc-btn:active:not(:disabled),.cc-iconButton:active:not(:disabled),.cc-resetInline:active:not(:disabled),.cc-segItem:active:not(:disabled):not(.cc-segOn),.cc-switch:active:not(:disabled){transform:none}
.cc-notice{transition:opacity .2s ease}
.cc-group{transition:opacity .2s ease;transform:none!important}
.cc-saveBar{transition:opacity .16s ease,visibility 0s linear .16s}
Expand Down
31 changes: 17 additions & 14 deletions src/client/section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
*/

import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore, type ButtonHTMLAttributes, type ReactNode } from 'react'
import { Menu, StateDot } from '@deepseek-ai/dsh-client-ui-primitives'
import { Menu, StateDot, Switch } from '@deepseek-ai/dsh-client-ui-primitives'
import type { MenuEntry, StateDotState } from '@deepseek-ai/dsh-client-ui-primitives'
import type { Translate } from '@deepseek-ai/dsh-client-ui-slots'
import type { CommandCodeCredits } from '../adapter.ts'
Expand Down Expand Up @@ -487,8 +487,15 @@ function invalidCopy(reason: StagedField['invalidReason'], t: Translate<Settings
* One boolean field row rendered as a switch. The staged text is `'true'` /
* `'false'` / `''` (unset → `defaultChecked`); toggling stages the string the
* boolean field spec parses back into a real boolean on save.
*
* The control is the harness's own `Switch` primitive (issue #108), not a
* restyled checkbox: a `<button role="switch" aria-checked>` whose drawing keys
* off `aria-checked`, so what it shows cannot drift from what assistive tech
* reads, and it matches the host's other settings switches. It takes no `id`,
* so the row title is plain text and the switch carries the label as its
* accessible name instead of through `<label htmlFor>`.
*/
function ToggleField({
export function ToggleField({
id,
label,
hint,
Expand All @@ -513,21 +520,17 @@ function ToggleField({
return (
<SettingRow
title={label}
titleFor={id}
titleId={id}
tag={<OverrideTag label={label} state={state} disabled={disabled} t={t} onReset={onReset} />}
description={hint}
control={
<>
<input
id={id}
className="cc-toggle"
type="checkbox"
role="switch"
checked={checked}
disabled={disabled}
onChange={(event) => onEdit(event.target.checked ? 'true' : 'false')}
/>
</>
<Switch
className="cc-switch"
checked={checked}
label={label}
disabled={disabled}
onChange={(next) => onEdit(next ? 'true' : 'false')}
/>
}
/>
)
Expand Down
14 changes: 14 additions & 0 deletions tests/_primitives-stub.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,17 @@ export function StateDot({ state, size, className }) {
...(size === undefined ? {} : { 'data-cc-size': String(size) }),
})
}

/** Render the switch's button contract: role, state and accessible name. */
export function Switch({ checked, onChange, label, disabled, title, className }) {
return createElement('button', {
type: 'button',
role: 'switch',
'aria-checked': checked,
'aria-label': label,
title,
disabled: disabled === true,
className,
onClick: () => onChange(!checked),
})
}
Loading
Loading