index.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. import { unit } from '@ant-design/cssinjs';
  2. import { resetComponent } from '../../style';
  3. import { genCollapseMotion, zoomIn } from '../../style/motion';
  4. import { genStyleHooks, mergeToken } from '../../theme/internal';
  5. import genFormValidateMotionStyle from './explain';
  6. const resetForm = token => ({
  7. legend: {
  8. display: 'block',
  9. width: '100%',
  10. marginBottom: token.marginLG,
  11. padding: 0,
  12. color: token.colorTextDescription,
  13. fontSize: token.fontSizeLG,
  14. lineHeight: 'inherit',
  15. border: 0,
  16. borderBottom: `${unit(token.lineWidth)} ${token.lineType} ${token.colorBorder}`
  17. },
  18. 'input[type="search"]': {
  19. boxSizing: 'border-box'
  20. },
  21. // Position radios and checkboxes better
  22. 'input[type="radio"], input[type="checkbox"]': {
  23. lineHeight: 'normal'
  24. },
  25. 'input[type="file"]': {
  26. display: 'block'
  27. },
  28. // Make range inputs behave like textual form controls
  29. 'input[type="range"]': {
  30. display: 'block',
  31. width: '100%'
  32. },
  33. // Make multiple select elements height not fixed
  34. 'select[multiple], select[size]': {
  35. height: 'auto'
  36. },
  37. // Focus for file, radio, and checkbox
  38. [`input[type='file']:focus,
  39. input[type='radio']:focus,
  40. input[type='checkbox']:focus`]: {
  41. outline: 0,
  42. boxShadow: `0 0 0 ${unit(token.controlOutlineWidth)} ${token.controlOutline}`
  43. },
  44. // Adjust output element
  45. output: {
  46. display: 'block',
  47. paddingTop: 15,
  48. color: token.colorText,
  49. fontSize: token.fontSize,
  50. lineHeight: token.lineHeight
  51. }
  52. });
  53. const genFormSize = (token, height) => {
  54. const {
  55. formItemCls
  56. } = token;
  57. return {
  58. [formItemCls]: {
  59. [`${formItemCls}-label > label`]: {
  60. height
  61. },
  62. [`${formItemCls}-control-input`]: {
  63. minHeight: height
  64. }
  65. }
  66. };
  67. };
  68. const genFormStyle = token => {
  69. const {
  70. componentCls
  71. } = token;
  72. return {
  73. [token.componentCls]: Object.assign(Object.assign(Object.assign({}, resetComponent(token)), resetForm(token)), {
  74. [`${componentCls}-text`]: {
  75. display: 'inline-block',
  76. paddingInlineEnd: token.paddingSM
  77. },
  78. // ================================================================
  79. // = Size =
  80. // ================================================================
  81. '&-small': Object.assign({}, genFormSize(token, token.controlHeightSM)),
  82. '&-large': Object.assign({}, genFormSize(token, token.controlHeightLG))
  83. })
  84. };
  85. };
  86. const genFormItemStyle = token => {
  87. const {
  88. formItemCls,
  89. iconCls,
  90. rootPrefixCls,
  91. antCls,
  92. labelRequiredMarkColor,
  93. labelColor,
  94. labelFontSize,
  95. labelHeight,
  96. labelColonMarginInlineStart,
  97. labelColonMarginInlineEnd,
  98. itemMarginBottom
  99. } = token;
  100. return {
  101. [formItemCls]: Object.assign(Object.assign({}, resetComponent(token)), {
  102. marginBottom: itemMarginBottom,
  103. verticalAlign: 'top',
  104. '&-with-help': {
  105. transition: 'none'
  106. },
  107. [`&-hidden,
  108. &-hidden${antCls}-row`]: {
  109. // https://github.com/ant-design/ant-design/issues/26141
  110. display: 'none'
  111. },
  112. '&-has-warning': {
  113. [`${formItemCls}-split`]: {
  114. color: token.colorError
  115. }
  116. },
  117. '&-has-error': {
  118. [`${formItemCls}-split`]: {
  119. color: token.colorWarning
  120. }
  121. },
  122. // ==============================================================
  123. // = Label =
  124. // ==============================================================
  125. [`${formItemCls}-label`]: {
  126. flexGrow: 0,
  127. overflow: 'hidden',
  128. whiteSpace: 'nowrap',
  129. textAlign: 'end',
  130. verticalAlign: 'middle',
  131. '&-left': {
  132. textAlign: 'start'
  133. },
  134. '&-wrap': {
  135. overflow: 'unset',
  136. lineHeight: token.lineHeight,
  137. whiteSpace: 'unset',
  138. '> label': {
  139. verticalAlign: 'middle',
  140. textWrap: 'balance'
  141. }
  142. },
  143. '> label': {
  144. position: 'relative',
  145. display: 'inline-flex',
  146. alignItems: 'center',
  147. maxWidth: '100%',
  148. height: labelHeight,
  149. color: labelColor,
  150. fontSize: labelFontSize,
  151. [`> ${iconCls}`]: {
  152. fontSize: token.fontSize,
  153. verticalAlign: 'top'
  154. },
  155. [`&${formItemCls}-required`]: {
  156. '&::before': {
  157. display: 'inline-block',
  158. marginInlineEnd: token.marginXXS,
  159. color: labelRequiredMarkColor,
  160. fontSize: token.fontSize,
  161. fontFamily: 'SimSun, sans-serif',
  162. lineHeight: 1,
  163. content: '"*"'
  164. },
  165. [`&${formItemCls}-required-mark-hidden, &${formItemCls}-required-mark-optional`]: {
  166. '&::before': {
  167. display: 'none'
  168. }
  169. }
  170. },
  171. // Optional mark
  172. [`${formItemCls}-optional`]: {
  173. display: 'inline-block',
  174. marginInlineStart: token.marginXXS,
  175. color: token.colorTextDescription,
  176. [`&${formItemCls}-required-mark-hidden`]: {
  177. display: 'none'
  178. }
  179. },
  180. // Optional mark
  181. [`${formItemCls}-tooltip`]: {
  182. color: token.colorTextDescription,
  183. cursor: 'help',
  184. writingMode: 'horizontal-tb',
  185. marginInlineStart: token.marginXXS
  186. },
  187. '&::after': {
  188. content: '":"',
  189. position: 'relative',
  190. marginBlock: 0,
  191. marginInlineStart: labelColonMarginInlineStart,
  192. marginInlineEnd: labelColonMarginInlineEnd
  193. },
  194. [`&${formItemCls}-no-colon::after`]: {
  195. content: '"\\a0"'
  196. }
  197. }
  198. },
  199. // ==============================================================
  200. // = Input =
  201. // ==============================================================
  202. [`${formItemCls}-control`]: {
  203. ['--ant-display']: 'flex',
  204. flexDirection: 'column',
  205. flexGrow: 1,
  206. [`&:first-child:not([class^="'${rootPrefixCls}-col-'"]):not([class*="' ${rootPrefixCls}-col-'"])`]: {
  207. width: '100%'
  208. },
  209. '&-input': {
  210. position: 'relative',
  211. display: 'flex',
  212. alignItems: 'center',
  213. minHeight: token.controlHeight,
  214. '&-content': {
  215. flex: 'auto',
  216. maxWidth: '100%',
  217. // Fix https://github.com/ant-design/ant-design/issues/54042
  218. // Remove impact of whitespaces
  219. [`&:has(> ${antCls}-switch:only-child, > ${antCls}-rate:only-child)`]: {
  220. display: 'flex',
  221. alignItems: 'center'
  222. }
  223. }
  224. }
  225. },
  226. // ==============================================================
  227. // = Explain =
  228. // ==============================================================
  229. [formItemCls]: {
  230. '&-additional': {
  231. display: 'flex',
  232. flexDirection: 'column'
  233. },
  234. '&-explain, &-extra': {
  235. clear: 'both',
  236. color: token.colorTextDescription,
  237. fontSize: token.fontSize,
  238. lineHeight: token.lineHeight
  239. },
  240. '&-explain-connected': {
  241. width: '100%'
  242. },
  243. '&-extra': {
  244. minHeight: token.controlHeightSM,
  245. transition: `color ${token.motionDurationMid} ${token.motionEaseOut}` // sync input color transition
  246. },
  247. '&-explain': {
  248. '&-error': {
  249. color: token.colorError
  250. },
  251. '&-warning': {
  252. color: token.colorWarning
  253. }
  254. }
  255. },
  256. [`&-with-help ${formItemCls}-explain`]: {
  257. height: 'auto',
  258. opacity: 1
  259. },
  260. // ==============================================================
  261. // = Feedback Icon =
  262. // ==============================================================
  263. [`${formItemCls}-feedback-icon`]: {
  264. fontSize: token.fontSize,
  265. textAlign: 'center',
  266. visibility: 'visible',
  267. animationName: zoomIn,
  268. animationDuration: token.motionDurationMid,
  269. animationTimingFunction: token.motionEaseOutBack,
  270. pointerEvents: 'none',
  271. '&-success': {
  272. color: token.colorSuccess
  273. },
  274. '&-error': {
  275. color: token.colorError
  276. },
  277. '&-warning': {
  278. color: token.colorWarning
  279. },
  280. '&-validating': {
  281. color: token.colorPrimary
  282. }
  283. }
  284. })
  285. };
  286. };
  287. const makeVerticalLayoutLabel = token => ({
  288. padding: token.verticalLabelPadding,
  289. margin: token.verticalLabelMargin,
  290. whiteSpace: 'initial',
  291. textAlign: 'start',
  292. '> label': {
  293. margin: 0,
  294. '&::after': {
  295. // https://github.com/ant-design/ant-design/issues/43538
  296. visibility: 'hidden'
  297. }
  298. }
  299. });
  300. const genHorizontalStyle = token => {
  301. const {
  302. antCls,
  303. formItemCls
  304. } = token;
  305. return {
  306. [`${formItemCls}-horizontal`]: {
  307. [`${formItemCls}-label`]: {
  308. flexGrow: 0
  309. },
  310. [`${formItemCls}-control`]: {
  311. flex: '1 1 0',
  312. // https://github.com/ant-design/ant-design/issues/32777
  313. // https://github.com/ant-design/ant-design/issues/33773
  314. minWidth: 0
  315. },
  316. // Do not change this to `ant-col-24`! `-24` match all the responsive rules
  317. // https://github.com/ant-design/ant-design/issues/32980
  318. // https://github.com/ant-design/ant-design/issues/34903
  319. // https://github.com/ant-design/ant-design/issues/44538
  320. [`${formItemCls}-label[class$='-24'], ${formItemCls}-label[class*='-24 ']`]: {
  321. [`& + ${formItemCls}-control`]: {
  322. minWidth: 'unset'
  323. }
  324. },
  325. [`${antCls}-col-24${formItemCls}-label,
  326. ${antCls}-col-xl-24${formItemCls}-label`]: makeVerticalLayoutLabel(token)
  327. }
  328. };
  329. };
  330. const genInlineStyle = token => {
  331. const {
  332. componentCls,
  333. formItemCls,
  334. inlineItemMarginBottom
  335. } = token;
  336. return {
  337. [`${componentCls}-inline`]: {
  338. display: 'flex',
  339. flexWrap: 'wrap',
  340. [`${formItemCls}-inline`]: {
  341. flex: 'none',
  342. marginInlineEnd: token.margin,
  343. marginBottom: inlineItemMarginBottom,
  344. '&-row': {
  345. flexWrap: 'nowrap'
  346. },
  347. [`> ${formItemCls}-label,
  348. > ${formItemCls}-control`]: {
  349. display: 'inline-block',
  350. verticalAlign: 'top'
  351. },
  352. [`> ${formItemCls}-label`]: {
  353. flex: 'none'
  354. },
  355. [`${componentCls}-text`]: {
  356. display: 'inline-block'
  357. },
  358. [`${formItemCls}-has-feedback`]: {
  359. display: 'inline-block'
  360. }
  361. }
  362. }
  363. };
  364. };
  365. const makeVerticalLayout = token => {
  366. const {
  367. componentCls,
  368. formItemCls,
  369. rootPrefixCls
  370. } = token;
  371. return {
  372. [`${formItemCls} ${formItemCls}-label`]: makeVerticalLayoutLabel(token),
  373. // ref: https://github.com/ant-design/ant-design/issues/45122
  374. [`${componentCls}:not(${componentCls}-inline)`]: {
  375. [formItemCls]: {
  376. flexWrap: 'wrap',
  377. [`${formItemCls}-label, ${formItemCls}-control`]: {
  378. // When developer pass `xs: { span }`,
  379. // It should follow the `xs` screen config
  380. // ref: https://github.com/ant-design/ant-design/issues/44386
  381. [`&:not([class*=" ${rootPrefixCls}-col-xs"])`]: {
  382. flex: '0 0 100%',
  383. maxWidth: '100%'
  384. }
  385. }
  386. }
  387. }
  388. };
  389. };
  390. const genVerticalStyle = token => {
  391. const {
  392. componentCls,
  393. formItemCls,
  394. antCls
  395. } = token;
  396. return {
  397. [`${formItemCls}-vertical`]: {
  398. [`${formItemCls}-row`]: {
  399. flexDirection: 'column'
  400. },
  401. [`${formItemCls}-label > label`]: {
  402. height: 'auto'
  403. },
  404. [`${formItemCls}-control`]: {
  405. width: '100%'
  406. },
  407. [`${formItemCls}-label,
  408. ${antCls}-col-24${formItemCls}-label,
  409. ${antCls}-col-xl-24${formItemCls}-label`]: makeVerticalLayoutLabel(token)
  410. },
  411. [`@media (max-width: ${unit(token.screenXSMax)})`]: [makeVerticalLayout(token), {
  412. [componentCls]: {
  413. [`${formItemCls}:not(${formItemCls}-horizontal)`]: {
  414. [`${antCls}-col-xs-24${formItemCls}-label`]: makeVerticalLayoutLabel(token)
  415. }
  416. }
  417. }],
  418. [`@media (max-width: ${unit(token.screenSMMax)})`]: {
  419. [componentCls]: {
  420. [`${formItemCls}:not(${formItemCls}-horizontal)`]: {
  421. [`${antCls}-col-sm-24${formItemCls}-label`]: makeVerticalLayoutLabel(token)
  422. }
  423. }
  424. },
  425. [`@media (max-width: ${unit(token.screenMDMax)})`]: {
  426. [componentCls]: {
  427. [`${formItemCls}:not(${formItemCls}-horizontal)`]: {
  428. [`${antCls}-col-md-24${formItemCls}-label`]: makeVerticalLayoutLabel(token)
  429. }
  430. }
  431. },
  432. [`@media (max-width: ${unit(token.screenLGMax)})`]: {
  433. [componentCls]: {
  434. [`${formItemCls}:not(${formItemCls}-horizontal)`]: {
  435. [`${antCls}-col-lg-24${formItemCls}-label`]: makeVerticalLayoutLabel(token)
  436. }
  437. }
  438. }
  439. };
  440. };
  441. // ============================== Export ==============================
  442. export const prepareComponentToken = token => ({
  443. labelRequiredMarkColor: token.colorError,
  444. labelColor: token.colorTextHeading,
  445. labelFontSize: token.fontSize,
  446. labelHeight: token.controlHeight,
  447. labelColonMarginInlineStart: token.marginXXS / 2,
  448. labelColonMarginInlineEnd: token.marginXS,
  449. itemMarginBottom: token.marginLG,
  450. verticalLabelPadding: `0 0 ${token.paddingXS}px`,
  451. verticalLabelMargin: 0,
  452. inlineItemMarginBottom: 0
  453. });
  454. export const prepareToken = (token, rootPrefixCls) => {
  455. const formToken = mergeToken(token, {
  456. formItemCls: `${token.componentCls}-item`,
  457. rootPrefixCls
  458. });
  459. return formToken;
  460. };
  461. export default genStyleHooks('Form', (token, {
  462. rootPrefixCls
  463. }) => {
  464. const formToken = prepareToken(token, rootPrefixCls);
  465. return [genFormStyle(formToken), genFormItemStyle(formToken), genFormValidateMotionStyle(formToken), genHorizontalStyle(formToken), genInlineStyle(formToken), genVerticalStyle(formToken), genCollapseMotion(formToken), zoomIn];
  466. }, prepareComponentToken, {
  467. // Let From style before the Grid
  468. // ref https://github.com/ant-design/ant-design/issues/44386
  469. order: -1000
  470. });