Website for showcasing all features of Tesseract Web.
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 

219 行
5.1 KiB

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