diff --git a/src/renderer/src/ui/Input.tsx b/src/renderer/src/ui/Input.tsx index 86c286e..ec8b8e7 100644 --- a/src/renderer/src/ui/Input.tsx +++ b/src/renderer/src/ui/Input.tsx @@ -33,6 +33,7 @@ export function Input({ wrapperClassName, className, disabled, + readOnly, ...inputProps }: InputProps): React.JSX.Element { const wrapperClass = classNames( @@ -45,9 +46,10 @@ export function Input({ ) return ( -
+
{prefix ? {prefix} : null} - + {suffix ? {suffix} : null}
) diff --git a/src/renderer/src/ui/Select.tsx b/src/renderer/src/ui/Select.tsx new file mode 100644 index 0000000..06890f3 --- /dev/null +++ b/src/renderer/src/ui/Select.tsx @@ -0,0 +1,82 @@ +/** + * YCode UI Select(P3)。 + * + * 外观壳与行为复用 `components/source-select/SourceSelect.tsx`(RecoveredSelect), + * 不重写闭合态标记、弹层与键盘行为;这里只做尺寸命名映射与只读入口。 + * + * 契约见 `docs/界面基础组件规范-2026-10-18.md` 的 Select 章节: + * 对外尺寸 sm|md|lg,内部映射 small|medium|large,几何沿用恢复样式。 + */ +import SourceSelect, { type SourceSelectProps } from '../components/source-select/SourceSelect' +import './select.css' + +export type SelectSize = 'sm' | 'md' | 'lg' + +const SOURCE_SIZE: Record = { + sm: 'small', + md: 'medium', + lg: 'large' +} + +export interface SelectProps extends Omit { + size?: SelectSize +} + +function classNames(...values: Array): string { + return values.filter(Boolean).join(' ') +} + +/** 统一下拉:透传 source-select 的全部现有 props,仅替换 size 命名。 */ +export function Select({ size = 'md', className, ...props }: SelectProps): React.JSX.Element { + return ( + + ) +} + +export interface StaticSelectProps { + /** 闭合态显示值(原样渲染,不做截断/格式化)。 */ + value: string + 'aria-label': string + /** 外层不可用提示文案,例如「暂未接入」。 */ + hint?: string + size?: SelectSize + /** 落到闭合态按钮上,保留页面级 `.xxx.wb-select` 布局覆盖的选择器命中。 */ + className?: string +} + +const noop = (): void => {} + +/** + * 只读/不可用的静态下拉:复用同一个 Select 闭合态壳,但停在闭合态。 + * + * - `disabled` 让 RecoveredSelect/Popover 都拒绝展开,同时把按钮移出 Tab 顺序; + * - 恢复样式的 `.wb-select--disabled` 会降低前景对比度,`select.css` 按 + * --yc-select-* token 把它还原成普通闭合态(不可用行要求保持原版外观); + * - 外层保留 data-settings-unavailable / aria-disabled / title 语义。 + */ +export function StaticSelect({ + value, + hint, + size = 'md', + className, + 'aria-label': ariaLabel +}: StaticSelectProps): React.JSX.Element { + return ( + + + + ) +} diff --git a/src/renderer/src/ui/Switch.tsx b/src/renderer/src/ui/Switch.tsx index 1e54371..71ed300 100644 --- a/src/renderer/src/ui/Switch.tsx +++ b/src/renderer/src/ui/Switch.tsx @@ -16,6 +16,7 @@ export interface SwitchProps { onCheckedChange?(checked: boolean): void disabled?: boolean readOnly?: boolean + tabIndex?: number size?: SwitchSize 'aria-label': string className?: string @@ -30,6 +31,7 @@ export function Switch({ onCheckedChange, disabled = false, readOnly = false, + tabIndex, size = 'md', className, 'aria-label': ariaLabel @@ -54,6 +56,7 @@ export function Switch({ aria-readonly={readOnly || undefined} checked={checked} disabled={disabled} + tabIndex={tabIndex} onChange={handleChange} /> diff --git a/src/renderer/src/ui/input.css b/src/renderer/src/ui/input.css index b1d8ce6..23eba63 100644 --- a/src/renderer/src/ui/input.css +++ b/src/renderer/src/ui/input.css @@ -38,6 +38,11 @@ outline: none; } +.yc-input[data-readonly='true'], +.yc-input[data-readonly='true'] .yc-input__inner { + cursor: default; +} + .yc-input__inner::placeholder { color: var(--yc-input-placeholder); } diff --git a/src/renderer/src/ui/select.css b/src/renderer/src/ui/select.css new file mode 100644 index 0000000..b296870 --- /dev/null +++ b/src/renderer/src/ui/select.css @@ -0,0 +1,32 @@ +/** + * YCode UI Select 样式(P3)。 + * + * 常规 `Select` 直接复用 source-select 的恢复样式(闭合态几何与状态一律不重写), + * 因此本文件不定义基础尺寸/边框/宽度,避免盖掉页面级 + * `.general-settings-panel .wb-select`、`.power-save-blocker-settings__select.wb-select` + * 这类同元素(0,2,0)覆盖。 + * + * 本文件只处理 `StaticSelect`:恢复样式用 `.wb-select--disabled` 表达不可用, + * 而设置页不可用行必须保持「普通闭合态」外观(不允许 disabled 降低前景对比度), + * 所以按 --yc-select-* token 把颜色与光标还原回闭合态。 + * 选择器权重 (0,3,0)/(0,4,0) 高于 `.source-select-host .wb-select--disabled` + * 的 (0,2,0)/(0,3,0),不依赖样式表先后顺序。 + */ + +.yc-select-static { + display: inline-flex; + min-width: 0; +} + +.source-select-host .yc-select--static.wb-select--disabled { + color: var(--yc-select-fg-static); + cursor: default; +} + +.source-select-host .yc-select--static.wb-select--disabled:hover { + border-color: var(--yc-select-border-hover); +} + +.source-select-host .yc-select--static.wb-select--disabled .wb-select__caret { + color: var(--yc-select-icon-static); +} diff --git a/src/renderer/src/ui/tokens.css b/src/renderer/src/ui/tokens.css index 5b3bdfb..09833fc 100644 --- a/src/renderer/src/ui/tokens.css +++ b/src/renderer/src/ui/tokens.css @@ -160,6 +160,10 @@ --yc-select-bg-disabled: #f7f7f7; --yc-select-fg-disabled: rgba(0, 0, 0, 0.3); + /* Select 静态只读闭合态:等价恢复样式 .wb-select 的非 disabled 外观,深色见下方覆盖 */ + --yc-select-fg-static: #000; + --yc-select-icon-static: rgba(0, 0, 0, 0.5); + /* Dialog */ --yc-dialog-bg: #ffffff; --yc-dialog-border: #dedede; @@ -274,6 +278,9 @@ html[data-theme='dark'] { --yc-select-bg-disabled: #171717; --yc-select-fg-disabled: rgba(255, 255, 255, 0.3); + --yc-select-fg-static: #fff; + --yc-select-icon-static: rgba(255, 255, 255, 0.5); + --yc-dialog-bg: #1f1f1f; --yc-dialog-border: #3d3d3d; --yc-dialog-overlay: rgba(0, 0, 0, 0.4);