Website for showcasing all features of Tesseract Web.
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.

205 lines
4.8 KiB

  1. import * as React from 'react';
  2. import * as TextControlBase from '@tesseract-design/web-base-textcontrol';
  3. export interface SelectOption {
  4. label: string,
  5. value?: string | number | readonly string[]
  6. children?: SelectOption[]
  7. }
  8. type RenderOptionsProps = {
  9. options: SelectOption[],
  10. optionComponent?: React.ElementType,
  11. optgroupComponent?: React.ElementType,
  12. level?: number,
  13. }
  14. const RenderOptions: React.VFC<RenderOptionsProps> = ({
  15. options,
  16. optionComponent: Option = 'option',
  17. optgroupComponent: Optgroup = 'optgroup',
  18. level = 0,
  19. }: RenderOptionsProps) => (
  20. <>
  21. {
  22. options.map((o) => {
  23. if (typeof o.value !== 'undefined') {
  24. return (
  25. <Option
  26. key={`${o.label}:${o.value.toString()}`}
  27. value={o.value}
  28. >
  29. {o.label}
  30. </Option>
  31. );
  32. }
  33. if (typeof o.children !== 'undefined') {
  34. if (level === 0) {
  35. return (
  36. <Optgroup
  37. key={o.label}
  38. label={o.label}
  39. >
  40. <RenderOptions
  41. options={o.children}
  42. optionComponent={Option}
  43. optgroupComponent={Optgroup}
  44. level={level + 1}
  45. />
  46. </Optgroup>
  47. );
  48. }
  49. return (
  50. <React.Fragment
  51. key={o.label}
  52. >
  53. <Option
  54. disabled
  55. >
  56. {o.label}
  57. </Option>
  58. <RenderOptions
  59. options={o.children}
  60. optionComponent={Option}
  61. optgroupComponent={Optgroup}
  62. level={level + 1}
  63. />
  64. </React.Fragment>
  65. );
  66. }
  67. return null;
  68. })
  69. }
  70. </>
  71. );
  72. export type DropdownSelectProps = Omit<React.HTMLProps<HTMLSelectElement>, 'size' | 'style' | 'children'> & {
  73. /**
  74. * Short textual description indicating the nature of the component's value.
  75. */
  76. label?: React.ReactNode,
  77. /**
  78. * Short textual description as guidelines for valid input values.
  79. */
  80. hint?: React.ReactNode,
  81. /**
  82. * Size of the component.
  83. */
  84. size?: TextControlBase.TextControlSize,
  85. /**
  86. * Should the component display a border?
  87. */
  88. border?: boolean,
  89. /**
  90. * Should the component occupy the whole width of its parent?
  91. */
  92. block?: boolean,
  93. /**
  94. * Style of the component.
  95. */
  96. style?: TextControlBase.TextControlStyle,
  97. /**
  98. * Is the label hidden?
  99. */
  100. hiddenLabel?: boolean,
  101. /**
  102. * Options available for the component's values.
  103. */
  104. options?: SelectOption[],
  105. }
  106. /**
  107. * Component for inputting textual values.
  108. *
  109. * This component supports multiline input and adjusts its layout accordingly.
  110. */
  111. export const DropdownSelect = React.forwardRef<HTMLSelectElement, DropdownSelectProps>(
  112. (
  113. {
  114. label = '',
  115. hint = '',
  116. size = TextControlBase.TextControlSize.MEDIUM,
  117. border = false,
  118. block = false,
  119. style = TextControlBase.TextControlStyle.DEFAULT,
  120. hiddenLabel = false,
  121. multiple: _multiple,
  122. className: _className,
  123. placeholder: _placeholder,
  124. as: _as,
  125. options = [],
  126. ...etcProps
  127. }: DropdownSelectProps,
  128. ref,
  129. ) => {
  130. const styleArgs = React.useMemo<TextControlBase.TextControlBaseArgs>(() => ({
  131. block,
  132. border,
  133. size,
  134. indicator: true,
  135. style,
  136. resizable: true,
  137. predefinedValues: true,
  138. }), [block, border, size, style]);
  139. return (
  140. <div
  141. className={TextControlBase.Root(styleArgs)}
  142. >
  143. <select
  144. {...etcProps}
  145. className={TextControlBase.Input(styleArgs)}
  146. ref={ref}
  147. aria-label={label}
  148. >
  149. <RenderOptions
  150. options={options}
  151. />
  152. </select>
  153. {border && (
  154. <span
  155. data-testid="border"
  156. />
  157. )}
  158. {label && !hiddenLabel && (
  159. <div
  160. className={TextControlBase.LabelWrapper(styleArgs)}
  161. data-testid="label"
  162. >
  163. {label}
  164. </div>
  165. )}
  166. {hint && (
  167. <div
  168. className={TextControlBase.HintWrapper(styleArgs)}
  169. data-testid="hint"
  170. >
  171. <div
  172. className={TextControlBase.Hint()}
  173. >
  174. {hint}
  175. </div>
  176. </div>
  177. )}
  178. <div
  179. className={TextControlBase.IndicatorWrapper(styleArgs)}
  180. >
  181. <svg
  182. className={TextControlBase.Indicator()}
  183. viewBox="0 0 24 24"
  184. role="presentation"
  185. >
  186. <polyline
  187. points="6 9 12 15 18 9"
  188. />
  189. </svg>
  190. </div>
  191. </div>
  192. );
  193. }
  194. );
  195. DropdownSelect.displayName = 'DropdownSelect';