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.
 
 
 

263 line
6.8 KiB

  1. import * as React from 'react';
  2. import { TextControl } from '@tesseract-design/web-base';
  3. import clsx from 'clsx';
  4. import { PluginCreator } from 'tailwindcss/types/config';
  5. import { useFallbackId } from '@modal-sh/react-utils';
  6. const DateDropdownDerivedElementComponent = 'input';
  7. /**
  8. * Derived HTML element of the {@link DateDropdown} component.
  9. */
  10. export type DateDropdownDerivedElement = HTMLElementTagNameMap[
  11. typeof DateDropdownDerivedElementComponent
  12. ];
  13. /**
  14. * Props of the {@link DateDropdown} component.
  15. */
  16. export interface DateDropdownProps extends Omit<React.HTMLProps<DateDropdownDerivedElement>, 'size' | 'type' | 'label' | 'pattern'> {
  17. /**
  18. * Short textual description indicating the nature of the component's value.
  19. */
  20. label?: React.ReactNode,
  21. /**
  22. * Short textual description as guidelines for valid input values.
  23. */
  24. hint?: React.ReactNode,
  25. /**
  26. * Size of the component.
  27. */
  28. size?: TextControl.Size,
  29. /**
  30. * Should the component display a border?
  31. */
  32. border?: boolean,
  33. /**
  34. * Should the component occupy the whole width of its parent?
  35. */
  36. block?: boolean,
  37. /**
  38. * Style of the component.
  39. */
  40. variant?: TextControl.Variant,
  41. /**
  42. * Is the label hidden?
  43. */
  44. hiddenLabel?: boolean,
  45. /**
  46. * Visual length of the input.
  47. */
  48. length?: number,
  49. }
  50. export const dateDropdownPlugin: PluginCreator = ({ addComponents }) => {
  51. addComponents({
  52. '.date-dropdown': {
  53. '& > input::-webkit-calendar-picker-indicator': {
  54. 'background-image': 'none',
  55. 'position': 'absolute',
  56. 'bottom': '0',
  57. 'right': '0',
  58. 'height': '100%',
  59. 'padding': '0',
  60. 'aspect-ratio': '1 / 1',
  61. 'cursor': 'inherit',
  62. },
  63. '&[data-size="small"] > input::-webkit-calendar-picker-indicator': {
  64. 'width': '2.5rem',
  65. },
  66. '&[data-size="medium"] > input::-webkit-calendar-picker-indicator': {
  67. 'width': '3rem',
  68. },
  69. '&[data-size="large"] > input::-webkit-calendar-picker-indicator': {
  70. 'width': '4rem',
  71. },
  72. },
  73. });
  74. };
  75. /**
  76. * Component for inputting date values.
  77. */
  78. export const DateDropdown = React.forwardRef<
  79. DateDropdownDerivedElement,
  80. DateDropdownProps
  81. >((
  82. {
  83. label,
  84. hint,
  85. size = 'medium' as const,
  86. border = false,
  87. block = false,
  88. variant = 'default' as const,
  89. hiddenLabel = false,
  90. className,
  91. id: idProp,
  92. style,
  93. length,
  94. ...etcProps
  95. }: DateDropdownProps,
  96. forwardedRef,
  97. ) => {
  98. const labelId = React.useId();
  99. const id = useFallbackId(idProp);
  100. return (
  101. <div
  102. className={clsx(
  103. 'relative rounded ring-secondary/50 overflow-hidden group has-[:disabled]:opacity-50 date-dropdown',
  104. 'focus-within:ring-4',
  105. {
  106. 'block': block,
  107. 'inline-block align-middle': !block,
  108. },
  109. className,
  110. )}
  111. style={style}
  112. data-size={size}
  113. data-testid="base"
  114. >
  115. {label && (
  116. <>
  117. <label
  118. data-testid="label"
  119. id={labelId}
  120. htmlFor={id}
  121. className={clsx(
  122. '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',
  123. {
  124. 'sr-only': hiddenLabel,
  125. },
  126. {
  127. 'pr-10': size === 'small',
  128. 'pr-12': size === 'medium',
  129. 'pr-16': size === 'large',
  130. },
  131. )}
  132. >
  133. <span className="block w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
  134. {label}
  135. </span>
  136. </label>
  137. {' '}
  138. </>
  139. )}
  140. <DateDropdownDerivedElementComponent
  141. {...etcProps}
  142. size={length}
  143. ref={forwardedRef}
  144. id={id}
  145. aria-labelledby={labelId}
  146. type="date"
  147. data-testid="input"
  148. pattern="\d{4}-\d{2}-\d{2}"
  149. className={clsx(
  150. 'bg-negative rounded-inherit w-full peer block font-inherit tabular-nums cursor-pointer',
  151. 'focus:outline-0',
  152. 'disabled:cursor-not-allowed',
  153. {
  154. 'pl-4': variant === 'default',
  155. 'pl-1.5 pt-4': variant === 'alternate',
  156. },
  157. {
  158. 'pr-10 h-10 text-xxs': size === 'small',
  159. 'pr-12 h-12 text-xs': size === 'medium',
  160. 'pr-16 h-16': size === 'large',
  161. },
  162. )}
  163. />
  164. {hint && (
  165. <div
  166. data-testid="hint"
  167. className={clsx(
  168. 'absolute left-0 px-1 pointer-events-none text-xxs leading-none w-full bg-negative select-none',
  169. {
  170. 'bottom-0 pl-4 pb-1': variant === 'default',
  171. 'top-0.5': variant === 'alternate',
  172. },
  173. {
  174. 'pt-2': variant === 'alternate' && size === 'small',
  175. 'pt-3': variant === 'alternate' && size !== 'small',
  176. },
  177. {
  178. 'pr-10': size === 'small',
  179. 'pr-12': size === 'medium',
  180. 'pr-16': size === 'large',
  181. },
  182. )}
  183. >
  184. <div
  185. className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis"
  186. >
  187. {hint}
  188. </div>
  189. </div>
  190. )}
  191. <div
  192. data-testid="indicator"
  193. className={clsx(
  194. 'text-center flex items-center justify-center aspect-square absolute bottom-0 right-0 pointer-events-none select-none text-primary group-focus-within:text-secondary',
  195. {
  196. 'w-10': size === 'small',
  197. 'w-12': size === 'medium',
  198. 'w-16': size === 'large',
  199. },
  200. )}
  201. >
  202. <svg
  203. className="w-6 h-6 fill-none stroke-current stroke-2 linejoin-round linecap-round"
  204. viewBox="0 0 24 24"
  205. role="presentation"
  206. >
  207. <rect
  208. x="3"
  209. y="4"
  210. width="18"
  211. height="18"
  212. rx="2"
  213. ry="2"
  214. />
  215. <line
  216. x1="16"
  217. y1="2"
  218. x2="16"
  219. y2="6"
  220. />
  221. <line
  222. x1="8"
  223. y1="2"
  224. x2="8"
  225. y2="6"
  226. />
  227. <line
  228. x1="3"
  229. y1="10"
  230. x2="21"
  231. y2="10"
  232. />
  233. </svg>
  234. </div>
  235. {border && (
  236. <span
  237. data-testid="border"
  238. className="absolute z-[1] inset-0 rounded-inherit border-2 border-primary pointer-events-none group-focus-within:border-secondary"
  239. />
  240. )}
  241. </div>
  242. );
  243. });
  244. DateDropdown.displayName = 'DateDropdown';
  245. DateDropdown.defaultProps = {
  246. label: undefined,
  247. hint: undefined,
  248. size: 'medium',
  249. border: false,
  250. block: false,
  251. variant: 'default',
  252. hiddenLabel: false,
  253. length: undefined,
  254. };