feat(ui): add select primitive and readonly control states

This commit is contained in:
xiaohan.yuan committed 2026-10-18 19:23:05 +08:00
1 parent fb7e2eb3da
commit 291cbee485
6 files changed
+133 -2

No files matched your search

+4 -2
View File
@@ -33,6 +33,7 @@ export function Input({
wrapperClassName, wrapperClassName,
className, className,
disabled, disabled,
readOnly,
...inputProps ...inputProps
}: InputProps): React.JSX.Element { }: InputProps): React.JSX.Element {
const wrapperClass = classNames( const wrapperClass = classNames(
@@ -45,9 +46,10 @@ export function Input({
) )
return ( return (
<div className={wrapperClass} data-disabled={disabled ? 'true' : undefined}> <div className={wrapperClass} data-disabled={disabled ? 'true' : undefined}
data-readonly={readOnly ? 'true' : undefined}>
{prefix ? <span className="yc-input__prefix">{prefix}</span> : null} {prefix ? <span className="yc-input__prefix">{prefix}</span> : null}
<input className={classNames('yc-input__inner', className)} disabled={disabled} {...inputProps} /> <input className={classNames('yc-input__inner', className)} disabled={disabled} readOnly={readOnly} {...inputProps} />
{suffix ? <span className="yc-input__suffix">{suffix}</span> : null} {suffix ? <span className="yc-input__suffix">{suffix}</span> : null}
</div> </div>
) )
+82
View File
@@ -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<SelectSize, 'small' | 'medium' | 'large'> = {
sm: 'small',
md: 'medium',
lg: 'large'
}
export interface SelectProps extends Omit<SourceSelectProps, 'size'> {
size?: SelectSize
}
function classNames(...values: Array<string | false | null | undefined>): string {
return values.filter(Boolean).join(' ')
}
/** 统一下拉:透传 source-select 的全部现有 props,仅替换 size 命名。 */
export function Select({ size = 'md', className, ...props }: SelectProps): React.JSX.Element {
return (
<SourceSelect
{...props}
className={classNames('yc-select', `yc-select--${size}`, className)}
size={SOURCE_SIZE[size]}
/>
)
}
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 (
<span className="yc-select-static source-select-host" data-settings-unavailable="true" aria-disabled="true" title={hint}>
<SourceSelect
className={classNames('yc-select', `yc-select--${size}`, 'yc-select--static', className)}
size={SOURCE_SIZE[size]}
value={value}
options={[{ value, label: value }]}
displayLabel={value}
disabled
onChange={noop}
ariaLabel={ariaLabel}
/>
</span>
)
}
+3
View File
@@ -16,6 +16,7 @@ export interface SwitchProps {
onCheckedChange?(checked: boolean): void onCheckedChange?(checked: boolean): void
disabled?: boolean disabled?: boolean
readOnly?: boolean readOnly?: boolean
tabIndex?: number
size?: SwitchSize size?: SwitchSize
'aria-label': string 'aria-label': string
className?: string className?: string
@@ -30,6 +31,7 @@ export function Switch({
onCheckedChange, onCheckedChange,
disabled = false, disabled = false,
readOnly = false, readOnly = false,
tabIndex,
size = 'md', size = 'md',
className, className,
'aria-label': ariaLabel 'aria-label': ariaLabel
@@ -54,6 +56,7 @@ export function Switch({
aria-readonly={readOnly || undefined} aria-readonly={readOnly || undefined}
checked={checked} checked={checked}
disabled={disabled} disabled={disabled}
tabIndex={tabIndex}
onChange={handleChange} onChange={handleChange}
/> />
<span className="yc-switch__track"> <span className="yc-switch__track">
+5
View File
@@ -38,6 +38,11 @@
outline: none; outline: none;
} }
.yc-input[data-readonly='true'],
.yc-input[data-readonly='true'] .yc-input__inner {
cursor: default;
}
.yc-input__inner::placeholder { .yc-input__inner::placeholder {
color: var(--yc-input-placeholder); color: var(--yc-input-placeholder);
} }
+32
View File
@@ -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);
}
+7
View File
@@ -160,6 +160,10 @@
--yc-select-bg-disabled: #f7f7f7; --yc-select-bg-disabled: #f7f7f7;
--yc-select-fg-disabled: rgba(0, 0, 0, 0.3); --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 */ /* Dialog */
--yc-dialog-bg: #ffffff; --yc-dialog-bg: #ffffff;
--yc-dialog-border: #dedede; --yc-dialog-border: #dedede;
@@ -274,6 +278,9 @@ html[data-theme='dark'] {
--yc-select-bg-disabled: #171717; --yc-select-bg-disabled: #171717;
--yc-select-fg-disabled: rgba(255, 255, 255, 0.3); --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-bg: #1f1f1f;
--yc-dialog-border: #3d3d3d; --yc-dialog-border: #3d3d3d;
--yc-dialog-overlay: rgba(0, 0, 0, 0.4); --yc-dialog-overlay: rgba(0, 0, 0, 0.4);