Design system.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

219 line
5.8 KiB

  1. import * as React from 'react';
  2. import { TextControl } from '@tesseract-design/web-base';
  3. import clsx from 'clsx';
  4. export type WeekInputDerivedElement = HTMLInputElement;
  5. export interface WeekInputProps extends Omit<React.HTMLProps<WeekInputDerivedElement>, 'size' | 'type' | 'label' | 'pattern'> {
  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. export const WeekInput = React.forwardRef<
  43. WeekInputDerivedElement,
  44. WeekInputProps
  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. id: idProp,
  57. style,
  58. ...etcProps
  59. }: WeekInputProps,
  60. forwardedRef,
  61. ) => {
  62. const labelId = React.useId();
  63. const defaultId = React.useId();
  64. const id = idProp ?? defaultId;
  65. // TODO render enhanced version
  66. return (
  67. <div
  68. className={clsx(
  69. 'relative rounded ring-secondary/50 overflow-hidden group has-[:disabled]:opacity-50',
  70. 'focus-within:ring-4',
  71. {
  72. 'block': block,
  73. 'inline-block align-middle': !block,
  74. },
  75. className,
  76. )}
  77. style={style}
  78. data-testid="base"
  79. >
  80. {label && (
  81. <>
  82. <label
  83. data-testid="label"
  84. id={labelId}
  85. htmlFor={id}
  86. className={clsx(
  87. 'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold group-focus-within:text-secondary text-primary leading-none bg-negative select-none',
  88. {
  89. 'sr-only': hiddenLabel,
  90. },
  91. {
  92. 'pr-1': !indicator,
  93. },
  94. {
  95. 'pr-10': indicator && size === 'small',
  96. 'pr-12': indicator && size === 'medium',
  97. 'pr-16': indicator && size === 'large',
  98. },
  99. )}
  100. >
  101. <span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
  102. {label}
  103. </span>
  104. </label>
  105. {' '}
  106. </>
  107. )}
  108. <input
  109. {...etcProps}
  110. ref={forwardedRef}
  111. id={id}
  112. aria-labelledby={labelId}
  113. type="week"
  114. data-testid="input"
  115. pattern="\d{4}-W\d{,2}"
  116. className={clsx(
  117. 'bg-negative rounded-inherit w-full peer block font-inherit tabular-nums',
  118. 'focus:outline-0',
  119. 'disabled:opacity-50 disabled:cursor-not-allowed',
  120. {
  121. 'text-xxs': size === 'small',
  122. 'text-xs': size === 'medium',
  123. },
  124. {
  125. 'pl-4': variant === 'default',
  126. 'pl-1.5': variant === 'alternate',
  127. },
  128. {
  129. 'pt-4': variant === 'alternate',
  130. },
  131. {
  132. 'pr-4': variant === 'default' && !indicator,
  133. 'pr-1.5': variant === 'alternate' && !indicator,
  134. },
  135. {
  136. 'pr-10': indicator && size === 'small',
  137. 'pr-12': indicator && size === 'medium',
  138. 'pr-16': indicator && size === 'large',
  139. },
  140. {
  141. 'h-10': size === 'small',
  142. 'h-12': size === 'medium',
  143. 'h-16': size === 'large',
  144. },
  145. )}
  146. />
  147. {hint && (
  148. <div
  149. data-testid="hint"
  150. className={clsx(
  151. 'absolute left-0 px-1 pointer-events-none text-xxs leading-none w-full bg-negative select-none',
  152. {
  153. 'bottom-0 pl-4 pb-1': variant === 'default',
  154. 'top-0.5': variant === 'alternate',
  155. },
  156. {
  157. 'pt-2': variant === 'alternate' && size === 'small',
  158. 'pt-3': variant === 'alternate' && size !== 'small',
  159. },
  160. {
  161. 'pr-4': !indicator && variant === 'default',
  162. 'pr-1': !indicator && variant === 'alternate',
  163. },
  164. {
  165. 'pr-10': indicator && size === 'small',
  166. 'pr-12': indicator && size === 'medium',
  167. 'pr-16': indicator && size === 'large',
  168. },
  169. )}
  170. >
  171. <div
  172. className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
  173. >
  174. {hint}
  175. </div>
  176. </div>
  177. )}
  178. {indicator && (
  179. <div
  180. className={clsx(
  181. 'text-center flex items-center justify-center aspect-square absolute bottom-0 right-0 pointer-events-none select-none',
  182. {
  183. 'w-10': size === 'small',
  184. 'w-12': size === 'medium',
  185. 'w-16': size === 'large',
  186. },
  187. )}
  188. >
  189. {indicator}
  190. </div>
  191. )}
  192. {border && (
  193. <span
  194. data-testid="border"
  195. className="absolute z-[1] inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary"
  196. />
  197. )}
  198. </div>
  199. );
  200. });
  201. WeekInput.displayName = 'WeekInput';
  202. WeekInput.defaultProps = {
  203. label: undefined,
  204. hint: undefined,
  205. size: 'medium',
  206. indicator: undefined,
  207. border: false,
  208. block: false,
  209. variant: 'default',
  210. hiddenLabel: false,
  211. };