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  );