github.com/wfusion/gofusion@v1.1.14/common/infra/asynq/asynqmon/ui/src/components/GroupSelect.tsx (about) 1 import React from "react"; 2 import { makeStyles, useTheme } from "@material-ui/core/styles"; 3 import TextField from "@material-ui/core/TextField"; 4 import Autocomplete from "@material-ui/lab/Autocomplete"; 5 import useMediaQuery from "@material-ui/core/useMediaQuery"; 6 import ListSubheader from "@material-ui/core/ListSubheader"; 7 import { VariableSizeList, ListChildComponentProps } from "react-window"; 8 import { GroupInfo } from "../api"; 9 import { isDarkTheme } from "../theme"; 10 11 const useStyles = makeStyles((theme) => ({ 12 groupSelectOption: { 13 display: "flex", 14 justifyContent: "space-between", 15 width: "100%", 16 }, 17 groupSize: { 18 fontSize: "12px", 19 color: theme.palette.text.secondary, 20 background: isDarkTheme(theme) 21 ? "#303030" 22 : theme.palette.background.default, 23 textAlign: "center", 24 padding: "3px 6px", 25 borderRadius: "10px", 26 marginRight: "2px", 27 }, 28 inputRoot: { 29 borderRadius: 20, 30 paddingLeft: "12px !important", 31 }, 32 })); 33 34 interface Props { 35 selected: GroupInfo | null; 36 onSelect: (newVal: GroupInfo | null) => void; 37 groups: GroupInfo[]; 38 error: string; 39 } 40 41 export default function GroupSelect(props: Props) { 42 const classes = useStyles(); 43 const [inputValue, setInputValue] = React.useState(""); 44 45 return ( 46 <Autocomplete 47 id="task-group-selector" 48 value={props.selected} 49 onChange={(event: any, newValue: GroupInfo | null) => { 50 props.onSelect(newValue); 51 }} 52 inputValue={inputValue} 53 onInputChange={(event, newInputValue) => { 54 setInputValue(newInputValue); 55 }} 56 disableListWrap 57 ListboxComponent={ 58 ListboxComponent as React.ComponentType< 59 React.HTMLAttributes<HTMLElement> 60 > 61 } 62 options={props.groups} 63 getOptionLabel={(option: GroupInfo) => option.group} 64 style={{ width: 300 }} 65 renderOption={(option: GroupInfo) => ( 66 <div className={classes.groupSelectOption}> 67 <span>{option.group}</span> 68 <span className={classes.groupSize}>{option.size}</span> 69 </div> 70 )} 71 renderInput={(params) => ( 72 <TextField {...params} label="Select group" variant="outlined" /> 73 )} 74 classes={{ 75 inputRoot: classes.inputRoot, 76 }} 77 size="small" 78 /> 79 ); 80 } 81 82 // Virtualized list. 83 // Reference: https://v4.mui.com/components/autocomplete/#virtualization 84 85 const LISTBOX_PADDING = 8; // px 86 87 function renderRow(props: ListChildComponentProps) { 88 const { data, index, style } = props; 89 return React.cloneElement(data[index], { 90 style: { 91 ...style, 92 top: (style.top as number) + LISTBOX_PADDING, 93 }, 94 }); 95 } 96 97 const OuterElementContext = React.createContext({}); 98 99 const OuterElementType = React.forwardRef<HTMLDivElement>((props, ref) => { 100 const outerProps = React.useContext(OuterElementContext); 101 return <div ref={ref} {...props} {...outerProps} />; 102 }); 103 104 function useResetCache(data: any) { 105 const ref = React.useRef<VariableSizeList>(null); 106 React.useEffect(() => { 107 if (ref.current != null) { 108 ref.current.resetAfterIndex(0, true); 109 } 110 }, [data]); 111 return ref; 112 } 113 114 // Adapter for react-window 115 const ListboxComponent = React.forwardRef<HTMLDivElement>( 116 function ListboxComponent(props, ref) { 117 const { children, ...other } = props; 118 const itemData = React.Children.toArray(children); 119 const theme = useTheme(); 120 const smUp = useMediaQuery(theme.breakpoints.up("sm"), { noSsr: true }); 121 const itemCount = itemData.length; 122 const itemSize = smUp ? 36 : 48; 123 124 const getChildSize = (child: React.ReactNode) => { 125 if (React.isValidElement(child) && child.type === ListSubheader) { 126 return 48; 127 } 128 return itemSize; 129 }; 130 131 const getHeight = () => { 132 if (itemCount > 8) { 133 return 8 * itemSize; 134 } 135 return itemData.map(getChildSize).reduce((a, b) => a + b, 0); 136 }; 137 138 const gridRef = useResetCache(itemCount); 139 140 return ( 141 <div ref={ref}> 142 <OuterElementContext.Provider value={other}> 143 <VariableSizeList 144 itemData={itemData} 145 height={getHeight() + 2 * LISTBOX_PADDING} 146 width="100%" 147 ref={gridRef} 148 outerElementType={OuterElementType} 149 innerElementType="ul" 150 itemSize={(index) => getChildSize(itemData[index])} 151 overscanCount={5} 152 itemCount={itemCount} 153 > 154 {renderRow} 155 </VariableSizeList> 156 </OuterElementContext.Provider> 157 </div> 158 ); 159 } 160 );