Design system.
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 

212 рядки
6.3 KiB

  1. import * as React from 'react';
  2. import { TextControl } from '@tesseract-design/web-base';
  3. import clsx from 'clsx';
  4. export type MultilineTextInputDerivedElement = HTMLTextAreaElement;
  5. export interface MultilineTextInputProps extends Omit<React.HTMLProps<MultilineTextInputDerivedElement>, 'size' | 'style' | 'label'> {
  6. /**
  7. * Short textual description indicating the nature of the component's value.
  8. */
  9. label?: React.ReactNode,
  10. /**
  11. * Short textual description as guidelines for valid input values.
  12. */
  13. hint?: React.ReactNode,
  14. /**
  15. * Size of the component.
  16. */
  17. size?: TextControl.Size,
  18. /**
  19. * Additional description, usually graphical, indicating the nature of the component's value.
  20. */
  21. indicator?: React.ReactNode,
  22. /**
  23. * Should the component display a border?
  24. */
  25. border?: boolean,
  26. /**
  27. * Should the component occupy the whole width of its parent?
  28. */
  29. block?: boolean,
  30. /**
  31. * Style of the component.
  32. */
  33. variant?: TextControl.Variant,
  34. /**
  35. * Is the label hidden?
  36. */
  37. hiddenLabel?: boolean,
  38. }
  39. /**
  40. * Component for inputting textual values.
  41. *
  42. * This component supports multiline input and adjusts its layout accordingly.
  43. */
  44. export const MultilineTextInput = React.forwardRef<MultilineTextInputDerivedElement, MultilineTextInputProps>(
  45. (
  46. {
  47. label,
  48. hint,
  49. indicator,
  50. size = 'medium' as const,
  51. border = false,
  52. block = false,
  53. variant = 'default' as const,
  54. hiddenLabel = false,
  55. className,
  56. ...etcProps
  57. }: MultilineTextInputProps,
  58. ref,
  59. ) => {
  60. const labelId = React.useId();
  61. return (
  62. <div
  63. className={clsx(
  64. 'relative rounded ring-secondary/50',
  65. 'focus-within:ring-4',
  66. {
  67. 'block': block,
  68. 'inline-block align-middle': !block,
  69. },
  70. className,
  71. )}
  72. >
  73. <textarea
  74. {...etcProps}
  75. ref={ref}
  76. aria-labelledby={labelId}
  77. data-testid="input"
  78. style={{
  79. height: 0,
  80. }}
  81. className={clsx(
  82. 'bg-negative rounded-inherit w-full peer block',
  83. 'focus:outline-0',
  84. 'disabled:opacity-50 disabled:cursor-not-allowed',
  85. {
  86. 'resize': !block,
  87. 'resize-y': block,
  88. },
  89. {
  90. 'text-xxs': size === 'small',
  91. 'text-xs': size === 'medium',
  92. },
  93. {
  94. 'pl-4': variant === 'default',
  95. 'pl-1.5': variant === 'alternate',
  96. },
  97. {
  98. 'pt-4': variant === 'alternate' && size === 'small',
  99. 'pt-5': variant === 'alternate' && size === 'medium',
  100. 'pt-8': variant === 'alternate' && size === 'large',
  101. },
  102. {
  103. 'py-2.5': variant === 'default' && size === 'small',
  104. 'py-3': variant === 'default' && size === 'medium',
  105. 'py-5': variant === 'default' && size === 'large',
  106. },
  107. {
  108. 'pr-4': variant === 'default' && !indicator,
  109. 'pr-1.5': variant === 'alternate' && !indicator,
  110. },
  111. {
  112. 'pr-10': indicator && size === 'small',
  113. 'pr-12': indicator && size === 'medium',
  114. 'pr-16': indicator && size === 'large',
  115. },
  116. {
  117. 'min-h-10': size === 'small',
  118. 'min-h-12': size === 'medium',
  119. 'min-h-16': size === 'large',
  120. },
  121. )}
  122. />
  123. {
  124. label && (
  125. <label
  126. data-testid="label"
  127. id={labelId}
  128. className={clsx(
  129. 'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative',
  130. {
  131. 'sr-only': hiddenLabel,
  132. },
  133. {
  134. 'pr-1': !indicator,
  135. },
  136. {
  137. 'pr-10': indicator && size === 'small',
  138. 'pr-12': indicator && size === 'medium',
  139. 'pr-16': indicator && size === 'large',
  140. },
  141. )}
  142. >
  143. <span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
  144. {label}
  145. </span>
  146. </label>
  147. )
  148. }
  149. {hint && (
  150. <div
  151. data-testid="hint"
  152. className={clsx(
  153. 'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative',
  154. {
  155. 'bottom-0 pl-4 pb-1': variant === 'default',
  156. 'top-0.5': variant === 'alternate',
  157. },
  158. {
  159. 'pt-2': variant === 'alternate' && size === 'small',
  160. 'pt-3': variant === 'alternate' && size !== 'small',
  161. },
  162. {
  163. 'pr-4': !indicator && variant === 'default',
  164. 'pr-1': !indicator && variant === 'alternate',
  165. },
  166. {
  167. 'pr-10': indicator && size === 'small',
  168. 'pr-12': indicator && size === 'medium',
  169. 'pr-16': indicator && size === 'large',
  170. },
  171. )}
  172. >
  173. <div
  174. className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
  175. >
  176. {hint}
  177. </div>
  178. </div>
  179. )}
  180. {indicator && (
  181. <div
  182. className={clsx(
  183. 'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none',
  184. {
  185. 'w-10': size === 'small',
  186. 'w-12': size === 'medium',
  187. 'w-16': size === 'large',
  188. },
  189. )}
  190. >
  191. {indicator}
  192. </div>
  193. )}
  194. {
  195. border && (
  196. <span
  197. data-testid="border"
  198. className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
  199. />
  200. )
  201. }
  202. </div>
  203. );
  204. }
  205. );
  206. MultilineTextInput.displayName = 'MultilineTextInput';