import React, { useRef } from 'react'; import './Form.css'; /* 表单(移动端)— 规格 §32 结构:item(字段)> label(标签 + 必填星号)/ main(控件 + 错误提示)。 移动端与桌面端的关键差别是**标签位置**:窄屏默认置顶标签(labelPosition=top), 需要一组纵向对齐的字段时才用 left(定宽标签列)。 必填星号是装饰(aria-hidden),语义由控件的 aria-required / required 承担; 错误态由字段级 is-error + 控件 aria-invalid 双写,错误文案用 aria-describedby 关联。 本端只做「必填」的结构表达,不发请求、不做正则校验(业务规则在宿主,规格 §32.7)。 */ let seq = 0; export default function Form({ fields = [], labelPosition = 'top', borderless = false, disabled = false, submitText = '提交', onSubmit, onChange, children = null, }) { const idRef = useRef(null); if (idRef.current === null) { seq += 1; idRef.current = 'kole-m-form-' + seq; } const base = idRef.current; const cls = 'kole-m-form' + (labelPosition === 'left' ? ' kole-m-form--left' : '') + (borderless ? ' kole-m-form--borderless' : '') + (disabled ? ' is-disabled' : ''); return (
{ e.preventDefault(); if (disabled) return; if (onSubmit) onSubmit(fields); }} > {fields.map((f, i) => { const item = f || {}; const key = item.name || String(i); const labelId = `${base}-label-${key}`; const errorId = `${base}-error-${key}`; const hintId = `${base}-hint-${key}`; const describedBy = [item.error ? errorId : '', item.hint ? hintId : ''].filter(Boolean).join(' ') || undefined; return (
{item.required ? ( ) : null} {item.label} {item.type === 'select' ? ( /* 非输入型控件:整行可点(原生 button,键盘可达) */ ) : item.type === 'textarea' ? (