表单&表单项
基础使用
Loading...
function Demo() {const [formData, setFormData] = useState({name: 'Micheal Jackson',gender: 'male',birthday: new Date('August 29, 1958'),displayBirthday: 'August 29, 1958',industry: 'Entertainment',})const setField = (k, v) =>setFormData({...formData,[k]: v,})const genderOptions = [{ label: 'Male', value: 'male' },{ label: 'Female', value: 'female' },]const industryOptions = [{ label: 'IT', value: 'IT' },{ label: 'Medical', value: 'Medical' },{ label: 'Entertainment', value: 'Entertainment' },{ label: 'Transportation', value: 'Transportation' },]return (<CForm><CFormItem label="姓名"><CInput value={formData.name} onChange={v => setField('name', v)} /></CFormItem><CFormItem label="性别"><CRadioGroupvalue={formData.gender}options={genderOptions}onChange={v => setField('gender', v)}/></CFormItem><CFormItem label="生日"><CDatePickerformat="MMM DD, YYYY"value={formData.birthday}onChange={v => setField('birthday', v)}formattedValue={formData.displayBirthday}onFormattedValueChange={v => setField('displayBirthday', v)}/></CFormItem><CFormItem label="行业"><CSelectvalue={formData.industry}onChange={v => setField('industry', v)}options={industryOptions}/></CFormItem></CForm>)}
折叠/展开 代码
配置式
Loading...
function Demo() {const [formData, setFormData] = useState({name: 'Micheal Jackson',gender: 'male',birthday: new Date('August 29, 1958'),displayBirthday: 'August 29, 1958',industry: 'Entertainment',})const setField = (k, v) =>setFormData({...formData,[k]: v,})const genderOptions = [{ label: 'Male', value: 'male' },{ label: 'Female', value: 'female' },]const industryOptions = [{ label: 'IT', value: 'IT' },{ label: 'Medical', value: 'Medical' },{ label: 'Entertainment', value: 'Entertainment' },{ label: 'Transportation', value: 'Transportation' },]const items = [{label: '姓名',children: (<CInput value={formData.name} onChange={v => setField('name', v)} />),},{label: '性别',children: (<CRadioGroupvalue={formData.gender}options={genderOptions}onChange={v => setField('gender', v)}/>),},{label: '生日',children: (<CDatePickerformat="MMM DD, YYYY"value={formData.birthday}onChange={v => setField('birthday', v)}formattedValue={formData.displayBirthday}onFormattedValueChange={v => setField('displayBirthday', v)}/>),},{label: '行业',children: (<CSelectvalue={formData.industry}onChange={v => setField('industry', v)}options={industryOptions}/>),},]return <CForm items={items} />}
折叠/展开 代码
尺寸
Loading...
function Demo() {const [formData, setFormData] = useState({name: 'Micheal Jackson',gender: 'male',birthday: new Date('August 29, 1958'),displayBirthday: 'August 29, 1958',industry: 'Entertainment',})const setField = (k, v) =>setFormData({...formData,[k]: v,})const genderOptions = [{ label: 'Male', value: 'male' },{ label: 'Female', value: 'female' },]const industryOptions = [{ label: 'IT', value: 'IT' },{ label: 'Medical', value: 'Medical' },{ label: 'Entertainment', value: 'Entertainment' },{ label: 'Transportation', value: 'Transportation' },]const sizes = ['xs', 'sm', 'md', 'lg', 'xl']const [size, setSize] = useState('md')const items = [{label: '尺寸',col: 12,children: (<CRadioGroupvalue={size}onChange={setSize}options={sizes.map(s => ({ label: s, value: s }))}/>),},{label: 'Name',children: (<CInputvalue={formData.name}onChange={v => setField('name', v)}/>),},{label: 'Gender',children: (<CRadioGroupvalue={formData.gender}options={genderOptions}onChange={v => setField('gender', v)}/>),},{label: 'Birthday',children: (<CDatePickerformat="MMM DD, YYYY"value={formData.birthday}onChange={v => setField('birthday', v)}formattedValue={formData.displayBirthday}onFormattedValueChange={v => setField('displayBirthday', v)}/>),},{label: 'Industry',children: (<CSelectvalue={formData.industry}onChange={v => setField('industry', v)}options={industryOptions}/>),},]return (<CFormitems={items}size={size}/>)}
折叠/展开 代码
间隔尺寸
Loading...
function Demo() {const [formData, setFormData] = useState({name: 'Micheal Jackson',gender: 'male',birthday: new Date('August 29, 1958'),displayBirthday: 'August 29, 1958',industry: 'Entertainment',})const setField = (k, v) =>setFormData({...formData,[k]: v,})const genderOptions = [{ label: 'Male', value: 'male' },{ label: 'Female', value: 'female' },]const industryOptions = [{ label: 'IT', value: 'IT' },{ label: 'Medical', value: 'Medical' },{ label: 'Entertainment', value: 'Entertainment' },{ label: 'Transportation', value: 'Transportation' },]const sizes = ['xs', 'sm', 'md', 'lg', 'xl']const [gutterSize, setGutterSize] = useState('md')const items = [{label: '间隔尺寸',col: 12,children: (<CRadioGroupvalue={gutterSize}onChange={setGutterSize}options={sizes.map(s => ({ label: s, value: s }))}/>),},{label: 'Name',children: (<CInputvalue={formData.name}onChange={v => setField('name', v)}/>),},{label: 'Gender',children: (<CRadioGroupvalue={formData.gender}options={genderOptions}onChange={v => setField('gender', v)}/>),},{label: 'Birthday',children: (<CDatePickerformat="MMM DD, YYYY"value={formData.birthday}onChange={v => setField('birthday', v)}formattedValue={formData.displayBirthday}onFormattedValueChange={v => setField('displayBirthday', v)}/>),},{label: 'Industry',children: (<CSelectvalue={formData.industry}onChange={v => setField('industry', v)}options={industryOptions}/>),},]return (<CFormitems={items}gutterSize={gutterSize}/>)}
折叠/展开 代码
项宽
Loading...
function Demo() {const [formData, setFormData] = useState({name: 'Micheal Jackson',gender: 'male',birthday: new Date('August 29, 1958'),displayBirthday: 'August 29, 1958',industry: 'Entertainment',})const setField = (k, v) =>setFormData({...formData,[k]: v,})const genderOptions = [{ label: 'Male', value: 'male' },{ label: 'Female', value: 'female' },]const industryOptions = [{ label: 'IT', value: 'IT' },{ label: 'Medical', value: 'Medical' },{ label: 'Entertainment', value: 'Entertainment' },{ label: 'Transportation', value: 'Transportation' },]const [col, setCol] = useState(6)const items = [{label: '每项宽',children: (<CRadioGroupvalue={col}onChange={setCol}options={[6, 12].map(s => ({ label: s, value: s }))}/>),},{label: 'Name',children: (<CInputvalue={formData.name}onChange={v => setField('name', v)}/>),},{label: 'Gender',children: (<CRadioGroupvalue={formData.gender}options={genderOptions}onChange={v => setField('gender', v)}/>),},{label: 'Birthday',children: (<CDatePickerformat="MMM DD, YYYY"value={formData.birthday}onChange={v => setField('birthday', v)}formattedValue={formData.displayBirthday}onFormattedValueChange={v => setField('displayBirthday', v)}/>),},{label: 'Industry',children: (<CSelectvalue={formData.industry}onChange={v => setField('industry', v)}options={industryOptions}/>),},]return (<CFormitems={items}col={col}/>)}
折叠/展开 代码
项文字宽度
Loading...
function Demo() {const [formData, setFormData] = useState({name: 'Micheal Jackson',gender: 'male',birthday: new Date('August 29, 1958'),displayBirthday: 'August 29, 1958',industry: 'Entertainment',})const setField = (k, v) =>setFormData({...formData,[k]: v,})const genderOptions = [{ label: 'Male', value: 'male' },{ label: 'Female', value: 'female' },]const industryOptions = [{ label: 'IT', value: 'IT' },{ label: 'Medical', value: 'Medical' },{ label: 'Entertainment', value: 'Entertainment' },{ label: 'Transportation', value: 'Transportation' },]const [labelWidth, setLabelWidth] = useState('80px')const items = [{label: '文字宽度',col: 12,children: (<CRadioGroupvalue={labelWidth}onChange={setLabelWidth}options={[{ label: '80px', value: '80px' },{ label: '100px', value: '100px' },{ label: '120px', value: '120px' },]}/>),},{label: 'Name',children: (<CInputvalue={formData.name}onChange={v => setField('name', v)}/>),},{label: 'Gender',children: (<CRadioGroupvalue={formData.gender}options={genderOptions}onChange={v => setField('gender', v)}/>),},{label: 'Birthday',children: (<CDatePickerformat="MMM DD, YYYY"value={formData.birthday}onChange={v => setField('birthday', v)}formattedValue={formData.displayBirthday}onFormattedValueChange={v => setField('displayBirthday', v)}/>),},{label: 'Industry',children: (<CSelectvalue={formData.industry}onChange={v => setField('industry', v)}options={industryOptions}/>),},]return (<CFormitems={items}labelWidth={labelWidth}/>)}
折叠/展开 代码
文字与元素排列方式
Loading...
function Demo() {const [formData, setFormData] = useState({name: 'Micheal Jackson',gender: 'male',birthday: new Date('August 29, 1958'),displayBirthday: 'August 29, 1958',industry: 'Entertainment',})const setField = (k, v) =>setFormData({...formData,[k]: v,})const genderOptions = [{ label: 'Male', value: 'male' },{ label: 'Female', value: 'female' },]const industryOptions = [{ label: 'IT', value: 'IT' },{ label: 'Medical', value: 'Medical' },{ label: 'Entertainment', value: 'Entertainment' },{ label: 'Transportation', value: 'Transportation' },]const directions = ['row', 'row-reverse', 'column', 'column-reverse']const [labelDirection, setLabelDirection] = useState('row')const items = [{label: '文字与元素排列方式',col: 12,children: (<CRadioGroupvalue={labelDirection}onChange={setLabelDirection}options={directions.map(d => ({label: d,value: d,}))}/>),},{label: 'Name',children: (<CInputvalue={formData.name}onChange={v => setField('name', v)}/>),},{label: 'Gender',children: (<CRadioGroupvalue={formData.gender}options={genderOptions}onChange={v => setField('gender', v)}/>),},{label: 'Birthday',children: (<CDatePickerformat="MMM DD, YYYY"value={formData.birthday}onChange={v => setField('birthday', v)}formattedValue={formData.displayBirthday}onFormattedValueChange={v => setField('displayBirthday', v)}/>),},{label: 'Industry',children: (<CSelectvalue={formData.industry}onChange={v => setField('industry', v)}options={industryOptions}/>),},]return (<CFormitems={items}labelDirection={labelDirection}/>)}
折叠/展开 代码
文字对齐方式
Loading...
function Demo() {const [formData, setFormData] = useState({name: 'Micheal Jackson',gender: 'male',birthday: new Date('August 29, 1958'),displayBirthday: 'August 29, 1958',industry: 'Entertainment',})const setField = (k, v) =>setFormData({...formData,[k]: v,})const genderOptions = [{ label: 'Male', value: 'male' },{ label: 'Female', value: 'female' },]const industryOptions = [{ label: 'IT', value: 'IT' },{ label: 'Medical', value: 'Medical' },{ label: 'Entertainment', value: 'Entertainment' },{ label: 'Transportation', value: 'Transportation' },]const [labelAlign, setLabelAlign] = useState('left')const items = [{label: '文字对齐方式',children: (<CRadioGroupvalue={labelAlign}onChange={setLabelAlign}options={['left', 'center', 'right'].map(d => ({label: d,value: d,}))}/>),},{label: 'Name',children: (<CInputvalue={formData.name}onChange={v => setField('name', v)}/>),},{label: 'Gender',children: (<CRadioGroupvalue={formData.gender}options={genderOptions}onChange={v => setField('gender', v)}/>),},{label: 'Birthday',children: (<CDatePickerformat="MMM DD, YYYY"value={formData.birthday}onChange={v => setField('birthday', v)}formattedValue={formData.displayBirthday}onFormattedValueChange={v => setField('displayBirthday', v)}/>),},{label: 'Industry',children: (<CSelectvalue={formData.industry}onChange={v => setField('industry', v)}options={industryOptions}/>),},]return (<CFormitems={items}labelAlign={labelAlign}/>)}
折叠/展开 代码
表单验证
Casual UI 的表单验证,需要配合field
、rules
属性
rules
为验证函数数组,每个验证函数为一个函数,该函数接受当前表单项对应值,返回:
false | string | Promise<false | string>
,返回含义如下:
- 返回
false
则代表验证通过无错误 - 回
string
则代表有错误,并且返回值为具体的错误信息 - 返回
Promise
则代表异步验证,内容也是false
或者具体的string
类型错误信息
假设你想定义一个验证是否必填的验证规则,可以这样写:
const rule = v => (v ? false : '该项是必填的')
它的等价异步逻辑大概是这样:
const asyncRule = v =>
new Promise(resolve => {
setTimeout(() => {
resolve(v ? false : '该项是必填的')
}, 1000)
})
Loading...
function Demo() {const [formData, setFormData] = useState({name: '',gender: '',birthday: null,displayBirthday: '',industry: '',hobbies: [],})const [validating, setValidating] = useState(false)const setField = (k, v) =>setFormData({...formData,[k]: v,})const genderOptions = [{ label: 'Male', value: 'male' },{ label: 'Female', value: 'female' },]const industryOptions = [{ label: 'IT', value: 'IT' },{ label: 'Medical', value: 'Medical' },{ label: 'Entertainment', value: 'Entertainment' },{ label: 'Transportation', value: 'Transportation' },]const items = [{label: '姓名',field: 'name',rules: [v => (v ? false : '请输入姓名')],children: (<CInputvalue={formData.name}onChange={v => setField('name', v)}placeholder="请输入姓名"/>),},{label: '性别',field: 'gender',rules: [v => {return v === 'male' ? false : '只能选Male!'},],children: (<CRadioGroupvalue={formData.gender}options={genderOptions}onChange={v => setField('gender', v)}/>),},{label: '生日',field: 'birthday',rules: [v => (!v ? '请选择生日' : false)],children: (<CDatePickerformat="MMM DD, YYYY"value={formData.birthday}placeholder="请选择生日"onChange={v => setField('birthday', v)}formattedValue={formData.displayBirthday}onFormattedValueChange={v => setField('displayBirthday', v)}/>),},{label: '行业',field: 'industry',rules: [v =>v !== 'IT' && v !== 'Entertainment'? '只能选IT或者Entertainment!': false,],children: (<CSelectvalue={formData.industry}onChange={v => setField('industry', v)}options={industryOptions}/>),},{label: '爱好',field: 'hobbies',rules: [v => (v && v.length < 2 ? '至少选择两个爱好' : false)],children: (<CCheckboxGroupvalue={formData.hobbies}onChange={v => setField('hobbies', v)}options={['Reading', 'Writing', 'Singing', 'Dancing'].map(h => ({label: h,value: h,}))}/>),},]const formRef = useRef(null)return (<div className="c-pa-md"><CFormref={formRef}value={formData}items={items}validating={validating}onValidatingChange={setValidating}/><div className="c-mt-xl"><SpaceItems><CButtonoutlinedlabel="清除验证状态"onClick={() => formRef.current.clearAll()}/><CButton label="提交" onClick={() => formRef.current.validateAll()} /></SpaceItems></div></div>)}
折叠/展开 代码
异步验证
Loading...
function Demo() {const [formData, setFormData] = useState({name: '',gender: '',birthday: null,displayBirthday: '',industry: '',hobbies: [],})const [validating, setValidating] = useState(false)const setField = (k, v) =>setFormData({...formData,[k]: v,})const genderOptions = [{ label: 'Male', value: 'male' },{ label: 'Female', value: 'female' },]const industryOptions = [{ label: 'IT', value: 'IT' },{ label: 'Medical', value: 'Medical' },{ label: 'Entertainment', value: 'Entertainment' },{ label: 'Transportation', value: 'Transportation' },]const items = [{label: '姓名',field: 'name',rules: [v => (v ? false : '请输入姓名')],children: (<CInputvalue={formData.name}onChange={v => setField('name', v)}placeholder="请输入姓名"/>),},{label: '性别',field: 'gender',rules: [v => {return v === 'male' ? false : '只能选Male!'},],children: (<CRadioGroupvalue={formData.gender}options={genderOptions}onChange={v => setField('gender', v)}/>),},{label: '生日',field: 'birthday',rules: [v => (!v ? '请选择生日' : false)],children: (<CDatePickerformat="MMM DD, YYYY"value={formData.birthday}placeholder="请选择生日"onChange={v => setField('birthday', v)}formattedValue={formData.displayBirthday}onFormattedValueChange={v => setField('displayBirthday', v)}/>),},{label: '行业',field: 'industry',rules: [v =>v !== 'IT' && v !== 'Entertainment'? '只能选IT或者Entertainment!': false,],children: (<CSelectvalue={formData.industry}onChange={v => setField('industry', v)}options={industryOptions}/>),},{label: '爱好',field: 'hobbies',rules: [v =>new Promise(resolve => {setTimeout(() => {resolve(v.length >= 2 ? false : '至少选择两个爱好')}, 3000)}),],children: (<CCheckboxGroupvalue={formData.hobbies}onChange={v => setField('hobbies', v)}options={['Reading', 'Writing', 'Singing', 'Dancing'].map(h => ({label: h,value: h,}))}/>),},]const formRef = useRef(null)return (<div className="c-pa-md"><CFormref={formRef}value={formData}items={items}validating={validating}onValidatingChange={setValidating}/><div className="c-mt-xl"><SpaceItems><CButtonoutlinedlabel="清除验证状态"onClick={() => formRef.current.clearAll()}/><CButton label="提交" onClick={() => formRef.current.validateAll()} /></SpaceItems></div></div>)}
折叠/展开 代码
自定义项
每个表单项即可以是一个内置的表单组件,如:CInput
,CRadioGroup
,CCheckboxGroup
,CSelect
,CDatePicker
等,也可以是自定义的组件。
当需要自定义组件时,可以传递一个函数式组件,该函数接收一个对象作为参数,该对象具有如下属性:validateCurrent, clearCurrent, hasError
,含义如下:
validateCurrent
:验证当前项函数,调用即立刻触发验证当前项clearCurrent
:清除当前项函数,调用即立刻清除当前项的验证状态hasError
:当前项是否有错误,如果有错误,改值即为具体的错误信息,否则为false
比如,我们需要自定义一个输入框,可以这样定义:
const CustomInput = ({ validateCurrent, clearCurrent, hasError }) => (
<input
value={formData.name}
onFocus={clearCurrent}
onBlur={validateCurrent}
onChange={e => setField('name', e.target.value)}
style={{
borderColor: hasError ? 'red' : 'inherit',
}}
/>
)
Loading...
function Demo() {const [formData, setFormData] = useState({name: '',gender: '',birthday: null,displayBirthday: '',industry: '',hobbies: [],})const setField = (k, v) =>setFormData({...formData,[k]: v,})const genderOptions = [{ label: 'Male', value: 'male' },{ label: 'Female', value: 'female' },]const industryOptions = [{ label: 'IT', value: 'IT' },{ label: 'Medical', value: 'Medical' },{ label: 'Entertainment', value: 'Entertainment' },{ label: 'Transportation', value: 'Transportation' },]const items = [{label: '姓名',field: 'name',rules: [v => (v ? false : '请输入姓名')],children: ({ validateCurrent, clearCurrent, hasError }) => (<inputvalue={formData.name}onFocus={clearCurrent}onBlur={validateCurrent}onChange={e => setField('name', e.target.value)}style={{borderColor: hasError ? 'red' : 'inherit',}}/>),},{label: '性别',field: 'gender',rules: [v => {return v === 'male' ? false : '只能选Male!'},],children: (<CRadioGroupvalue={formData.gender}options={genderOptions}onChange={v => setField('gender', v)}/>),},{label: '生日',field: 'birthday',rules: [v => (!v ? '请选择生日' : false)],children: (<CDatePickerformat="MMM DD, YYYY"value={formData.birthday}placeholder="请选择生日"onChange={v => setField('birthday', v)}formattedValue={formData.displayBirthday}onFormattedValueChange={v => setField('displayBirthday', v)}/>),},{label: '行业',field: 'industry',rules: [v =>v !== 'IT' && v !== 'Entertainment'? '只能选IT或者Entertainment!': false,],children: (<CSelectvalue={formData.industry}onChange={v => setField('industry', v)}options={industryOptions}/>),},{label: '爱好',field: 'hobbies',rules: [v => (v && v.length < 2 ? '至少选择两个爱好' : false)],children: (<CCheckboxGroupvalue={formData.hobbies}onChange={v => setField('hobbies', v)}options={['Reading', 'Writing', 'Singing', 'Dancing'].map(h => ({label: h,value: h,}))}/>),},]const formRef = useRef(null)return (<div className="c-pa-md"><CForm ref={formRef} value={formData} items={items} /><div className="c-mt-xl"><SpaceItems><CButtonoutlinedlabel="清除验证状态"onClick={() => formRef.current.clearAll()}/><CButton label="提交" onClick={() => formRef.current.validateAll()} /></SpaceItems></div></div>)}
折叠/展开 代码
也可以使用useFormItemContext
Hook,获取validateCurrent
、clearCurrent
、hasError
这个时候的自定义组件可以这样写:
const CustomInput = ({ value, onChange }) => {
const { clearCurrent, validateCurrent, hasError } = useFormItemContext()
return (
<input
value={value}
onFocus={clearCurrent}
onBlur={validateCurrent}
onChange={e => onChange(e.target.value)}
style={{
borderColor: hasError ? 'red' : 'inherit',
}}
/>
)
}
下面是一个示例:
Loading...
function Demo() {// 自定义一个简单的输入框const CustomInput = ({ value, onChange }) => {const { clearCurrent, validateCurrent, hasError } = useFormItemContext()return (<inputvalue={value}onFocus={clearCurrent}onBlur={validateCurrent}onChange={e => onChange(e.target.value)}style={{borderColor: hasError ? 'red' : 'inherit',}}/>)}const [formData, setFormData] = useState({name: '',gender: '',birthday: null,displayBirthday: '',industry: '',hobbies: [],})const setField = (k, v) =>setFormData({...formData,[k]: v,})const genderOptions = [{ label: 'Male', value: 'male' },{ label: 'Female', value: 'female' },]const industryOptions = [{ label: 'IT', value: 'IT' },{ label: 'Medical', value: 'Medical' },{ label: 'Entertainment', value: 'Entertainment' },{ label: 'Transportation', value: 'Transportation' },]const items = [{label: '姓名',field: 'name',rules: [v => (v ? false : '请输入姓名')],children: (<CustomInputvalue={formData.name}onChange={v => setField('name', v)}/>),},{label: '性别',field: 'gender',rules: [v => {return v === 'male' ? false : '只能选Male!'},],children: (<CRadioGroupvalue={formData.gender}options={genderOptions}onChange={v => setField('gender', v)}/>),},{label: '生日',field: 'birthday',rules: [v => (!v ? '请选择生日' : false)],children: (<CDatePickerformat="MMM DD, YYYY"value={formData.birthday}placeholder="请选择生日"onChange={v => setField('birthday', v)}formattedValue={formData.displayBirthday}onFormattedValueChange={v => setField('displayBirthday', v)}/>),},{label: '行业',field: 'industry',rules: [v =>v !== 'IT' && v !== 'Entertainment'? '只能选IT或者Entertainment!': false,],children: (<CSelectvalue={formData.industry}onChange={v => setField('industry', v)}options={industryOptions}/>),},{label: '爱好',field: 'hobbies',rules: [v => (v && v.length < 2 ? '至少选择两个爱好' : false)],children: (<CCheckboxGroupvalue={formData.hobbies}onChange={v => setField('hobbies', v)}options={['Reading', 'Writing', 'Singing', 'Dancing'].map(h => ({label: h,value: h,}))}/>),},]const formRef = useRef(null)return (<div className="c-pa-md"><CForm ref={formRef} value={formData} items={items} /><div className="c-mt-xl"><SpaceItems><CButtonoutlinedlabel="清除验证状态"onClick={() => formRef.current.clearAll()}/><CButton label="提交" onClick={() => formRef.current.validateAll()} /></SpaceItems></div></div>)}
折叠/展开 代码
提示
表单项所有的与表单同名配置,可以覆盖整体配置,使某个表单项具有自身行为
通常用在需要自定义某项具有特殊表现时
CForm Props
名称 (*代表必须) | 描述 | 类型 | 默认值 |
---|
No Data
CFormItem Props
名称 (*代表必须) | 描述 | 类型 | 默认值 |
---|
No Data