forms.tsx 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import React from 'react';
  2. import { Field, FormikErrors, FormikTouched } from 'formik';
  3. import { nonEmptyStr } from '../../functions/misc';
  4. import { Popup, Icon } from 'semantic-ui-react';
  5. export type LabelledProps<FormValues> = {
  6. name?: keyof FormValues;
  7. label?: React.ReactNode;
  8. invisibleLabel?: boolean;
  9. placeholder?: string;
  10. tooltip?: React.ReactNode;
  11. textarea?: boolean;
  12. required?: boolean;
  13. className?: string;
  14. style?: React.CSSProperties;
  15. children?: React.ReactNode;
  16. errors: FormikErrors<FormValues>;
  17. touched: FormikTouched<FormValues>;
  18. isSubmitting: boolean;
  19. };
  20. export function LabelledField<FormValues> () {
  21. const LabelledFieldInner: React.FunctionComponent<LabelledProps<FormValues>> =
  22. (props: LabelledProps<FormValues>) => {
  23. const { name, label, invisibleLabel = false, tooltip, required = false, touched, errors, children, style } = props;
  24. const hasError = name && touched[name] && errors[name];
  25. const errorClass = hasError ? 'error' : '';
  26. const fieldWithError =
  27. <>
  28. <div>{children}</div>
  29. {name && hasError && <div className='ui pointing red label'>{errors[name]}</div>}
  30. </>;
  31. const renderLabel = () =>
  32. nonEmptyStr(label)
  33. ? <>
  34. {required && <b style={{ color: 'red' }} title='This field is required'>* </b>}
  35. {label}
  36. </>
  37. : null;
  38. return (label || invisibleLabel)
  39. ? <div style={style} className={`ui--Labelled field ${errorClass}`}>
  40. <label htmlFor={name as string}>
  41. {renderLabel()}
  42. {tooltip && <FieldTooltip>{tooltip}</FieldTooltip> }
  43. </label>
  44. <div className='ui--Labelled-content'>
  45. {fieldWithError}
  46. </div>
  47. </div>
  48. : <div style={style} className={`field ${errorClass}`}>
  49. {fieldWithError}
  50. </div>;
  51. };
  52. return LabelledFieldInner;
  53. }
  54. export function LabelledText<FormValues> () {
  55. const LF = LabelledField<FormValues>();
  56. return (props: LabelledProps<FormValues>) => {
  57. const { name, placeholder, textarea = false, className, style, ...otherProps } = props;
  58. const textareaProps = !textarea ? {} : {
  59. component: 'textarea',
  60. rows: 3
  61. };
  62. const fieldProps = {
  63. id: name,
  64. name,
  65. placeholder,
  66. className,
  67. style,
  68. disabled: otherProps.isSubmitting,
  69. ...textareaProps
  70. };
  71. return <LF name={name} {...otherProps} >
  72. <Field {...fieldProps} />
  73. </LF>;
  74. };
  75. }
  76. type FieldTooltipProps = {
  77. children: React.ReactNode;
  78. }
  79. export const FieldTooltip = (props: FieldTooltipProps) => {
  80. return <Popup
  81. trigger={<Icon name='question' circular size='small' style={{ marginLeft: '.25rem' }} />}
  82. content={props.children}
  83. position='right center'
  84. />;
  85. };