Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

MultiSelect.tsx 6.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import React, { useCallback, useMemo, useRef } from 'react';
  2. import { makeStyles } from 'tss-react/mui';
  3. import { IconCloseLarge } from '../../../icons/svg';
  4. import { withPixelLineHeight } from '../../../styles/functions.web';
  5. import { MultiSelectItem } from '../types';
  6. import ClickableIcon from './ClickableIcon';
  7. import Input from './Input';
  8. interface IProps {
  9. autoFocus?: boolean;
  10. disabled?: boolean;
  11. error?: boolean;
  12. errorDialog?: JSX.Element | null;
  13. filterValue?: string;
  14. isOpen?: boolean;
  15. items: MultiSelectItem[];
  16. noMatchesText?: string;
  17. onFilterChange?: (value: string) => void;
  18. onRemoved: (item: any) => void;
  19. onSelected: (item: any) => void;
  20. placeholder?: string;
  21. selectedItems?: MultiSelectItem[];
  22. }
  23. const MULTI_SELECT_HEIGHT = 200;
  24. const useStyles = makeStyles()(theme => {
  25. return {
  26. container: {
  27. position: 'relative'
  28. },
  29. items: {
  30. '&.found': {
  31. position: 'absolute',
  32. boxShadow: '0px 5px 10px rgba(0, 0, 0, 0.75)'
  33. },
  34. marginTop: theme.spacing(2),
  35. width: '100%',
  36. backgroundColor: theme.palette.ui01,
  37. border: `1px solid ${theme.palette.ui04}`,
  38. borderRadius: `${Number(theme.shape.borderRadius)}px`,
  39. ...withPixelLineHeight(theme.typography.bodyShortRegular),
  40. zIndex: 2,
  41. maxHeight: `${MULTI_SELECT_HEIGHT}px`,
  42. overflowY: 'auto',
  43. padding: '0'
  44. },
  45. listItem: {
  46. boxSizing: 'border-box',
  47. display: 'flex',
  48. padding: `${theme.spacing(2)} ${theme.spacing(3)}`,
  49. alignItems: 'center',
  50. '& .content': {
  51. // 38px because of the icon before the content
  52. inlineSize: 'calc(100% - 38px)',
  53. overflowWrap: 'break-word',
  54. marginLeft: theme.spacing(2),
  55. color: theme.palette.text01,
  56. '&.with-remove': {
  57. // 60px because of the icon before the content and the remove button
  58. inlineSize: 'calc(100% - 60px)',
  59. marginRight: theme.spacing(2),
  60. '&.without-before': {
  61. marginLeft: 0,
  62. inlineSize: 'calc(100% - 38px)'
  63. }
  64. },
  65. '&.without-before': {
  66. marginLeft: 0,
  67. inlineSize: '100%'
  68. }
  69. },
  70. '&.found': {
  71. cursor: 'pointer',
  72. padding: `10px ${theme.spacing(3)}`,
  73. '&:hover': {
  74. backgroundColor: theme.palette.ui02
  75. }
  76. },
  77. '&.disabled': {
  78. cursor: 'not-allowed',
  79. '&:hover': {
  80. backgroundColor: theme.palette.ui01
  81. },
  82. color: theme.palette.text03
  83. }
  84. },
  85. errorMessage: {
  86. position: 'absolute',
  87. marginTop: theme.spacing(2),
  88. width: '100%'
  89. }
  90. };
  91. });
  92. const MultiSelect = ({
  93. autoFocus,
  94. disabled,
  95. error,
  96. errorDialog,
  97. placeholder,
  98. items,
  99. filterValue,
  100. onFilterChange,
  101. isOpen,
  102. noMatchesText,
  103. onSelected,
  104. selectedItems,
  105. onRemoved
  106. }: IProps) => {
  107. const { classes } = useStyles();
  108. const inputRef = useRef();
  109. const selectItem = useCallback(item => () => onSelected(item), [ onSelected ]);
  110. const removeItem = useCallback(item => () => onRemoved(item), [ onRemoved ]);
  111. const foundItems = useMemo(() => (
  112. <div className = { `${classes.items} found` }>
  113. {
  114. items.length > 0
  115. ? items.map(item => (
  116. <div
  117. className = { `${classes.listItem} ${item.isDisabled ? 'disabled' : ''} found` }
  118. key = { item.value }
  119. onClick = { item.isDisabled ? undefined : selectItem(item) }>
  120. {item.elemBefore}
  121. <div className = { `content ${item.elemBefore ? '' : 'without-before'}` }>
  122. {item.content}
  123. {item.description && <p>{item.description}</p>}
  124. </div>
  125. </div>
  126. ))
  127. : <div className = { classes.listItem }>{noMatchesText}</div>
  128. }
  129. </div>
  130. ), [ items ]);
  131. const errorMessageDialog = useMemo(() =>
  132. error && <div className = { classes.errorMessage }>
  133. { errorDialog }
  134. </div>, [ error ]);
  135. return (
  136. <div className = { classes.container }>
  137. <Input
  138. autoFocus = { autoFocus }
  139. disabled = { disabled }
  140. onChange = { onFilterChange }
  141. placeholder = { placeholder }
  142. ref = { inputRef }
  143. value = { filterValue ?? '' } />
  144. {isOpen && foundItems}
  145. { errorMessageDialog }
  146. { selectedItems && selectedItems?.length > 0 && (
  147. <div className = { classes.items }>
  148. { selectedItems.map(item => (
  149. <div
  150. className = { `${classes.listItem} ${item.isDisabled ? 'disabled' : ''}` }
  151. key = { item.value }>
  152. {item.elemBefore}
  153. <div className = { `content with-remove ${item.elemBefore ? '' : 'without-before'}` }>
  154. <p>{item.content}</p>
  155. </div>
  156. <ClickableIcon
  157. accessibilityLabel = { 'multi-select-unselect' }
  158. icon = { IconCloseLarge }
  159. id = 'modal-header-close-button'
  160. onClick = { removeItem(item) } />
  161. </div>
  162. ))}
  163. </div>
  164. )}
  165. </div>
  166. );
  167. };
  168. export default MultiSelect;