feat(ui): add select primitive and readonly control states
This commit is contained in:
1 parent
fb7e2eb3da
commit
291cbee485
6 files changed
+133
-2
No files matched your search
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
Reference in new issue
Block a user