react-use#useEnsuredForwardedRef TypeScript Examples

The following examples show how to use react-use#useEnsuredForwardedRef. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: FilesystemBrowserTextbox.tsx    From flood with GNU General Public License v3.0 4 votes vote down vote up
FilesystemBrowserTextbox = forwardRef<HTMLInputElement, FilesystemBrowserTextboxProps>(
  (
    {
      id,
      label,
      selectable,
      suggested,
      showBasePathToggle,
      showCompletedToggle,
      showSequentialToggle,
      onChange,
    }: FilesystemBrowserTextboxProps,
    ref,
  ) => {
    const [destination, setDestination] = useState<string>(
      suggested ||
        SettingStore.floodSettings.torrentDestinations?.[''] ||
        SettingStore.clientSettings?.directoryDefault ||
        '',
    );
    const [isDirectoryListOpen, setIsDirectoryListOpen] = useState<boolean>(false);

    const formRowRef = useRef<HTMLDivElement>(null);
    const textboxRef = useEnsuredForwardedRef(ref as MutableRefObject<HTMLInputElement>);

    const {i18n} = useLingui();

    useEffect(() => {
      const closeDirectoryList = (): void => {
        setIsDirectoryListOpen(false);
      };

      const handleDocumentClick = (e: Event): void => {
        if (!formRowRef.current?.contains(e.target as unknown as Node)) {
          closeDirectoryList();
        }
      };

      document.addEventListener('click', handleDocumentClick);
      window.addEventListener('resize', closeDirectoryList);

      return () => {
        document.removeEventListener('click', handleDocumentClick);
        window.removeEventListener('resize', closeDirectoryList);
      };
    }, []);

    const toggles: Array<ReactElement> = [];
    if (showBasePathToggle) {
      toggles.push(
        <Checkbox grow={false} id="isBasePath" key="isBasePath">
          <Trans id="torrents.destination.base_path" />
        </Checkbox>,
      );
    }
    if (showCompletedToggle) {
      toggles.push(
        <Checkbox grow={false} id="isCompleted" key="isCompleted">
          <Trans id="torrents.destination.completed" />
        </Checkbox>,
      );
    }
    if (showSequentialToggle) {
      // TODO: this is getting bloated. toggles can be moved to their own elements...
      toggles.push(
        <Checkbox grow={false} id="isSequential" key="isSequential">
          <Trans id="torrents.destination.sequential" />
        </Checkbox>,
      );
    }

    return (
      <FormRowGroup ref={formRowRef}>
        <FormRow>
          <Textbox
            autoComplete={isDirectoryListOpen ? 'off' : undefined}
            addonPlacement="after"
            defaultValue={destination}
            id={id}
            label={label}
            onChange={debounce(
              () => {
                if (textboxRef.current == null) {
                  return;
                }

                const newDestination = textboxRef.current.value;

                if (onChange) {
                  onChange(newDestination);
                }

                setDestination(newDestination);
              },
              100,
              {leading: true},
            )}
            onClick={(event) => event.nativeEvent.stopImmediatePropagation()}
            placeholder={i18n._('torrents.add.destination.placeholder')}
            ref={textboxRef}
          >
            <FormElementAddon
              onClick={() => {
                if (textboxRef.current != null) {
                  setDestination(textboxRef.current.value);
                }
                setIsDirectoryListOpen(!isDirectoryListOpen);
              }}
            >
              <Search />
            </FormElementAddon>
            <Portal>
              <ContextMenu
                isIn={isDirectoryListOpen}
                onClick={(event) => event.nativeEvent.stopImmediatePropagation()}
                overlayProps={{isInteractive: false}}
                padding={false}
                triggerRef={textboxRef}
              >
                <FilesystemBrowser
                  directory={destination}
                  selectable={selectable}
                  onItemSelection={(newDestination: string, shouldKeepOpen = true) => {
                    if (textboxRef.current != null) {
                      textboxRef.current.value = newDestination;
                    }

                    setIsDirectoryListOpen(shouldKeepOpen);
                    setDestination(newDestination);
                  }}
                  onYieldFocus={() => {
                    textboxRef.current?.focus();
                  }}
                />
              </ContextMenu>
            </Portal>
          </Textbox>
        </FormRow>
        {toggles.length > 0 ? <FormRow>{toggles}</FormRow> : null}
      </FormRowGroup>
    );
  },
)
Example #2
Source File: TableHeading.tsx    From flood with GNU General Public License v3.0 4 votes vote down vote up
TableHeading = observer(
  forwardRef<HTMLDivElement, TableHeadingProps>(
    ({onCellClick, onCellFocus, onWidthsChange}: TableHeadingProps, ref) => {
      const [isPointerDown, setIsPointerDown] = useState<boolean>(false);

      const focusedCell = useRef<TorrentListColumn>();
      const focusedCellWidth = useRef<number>();
      const lastPointerX = useRef<number>();
      const tableHeading = useEnsuredForwardedRef<HTMLDivElement>(ref as MutableRefObject<HTMLDivElement>);
      const resizeLine = useRef<HTMLDivElement>(null);

      const {i18n} = useLingui();

      const handlePointerMove = (event: PointerEvent) => {
        let widthDelta = 0;
        if (lastPointerX.current != null) {
          widthDelta = event.clientX - lastPointerX.current;
        }

        let nextCellWidth = 20;
        if (focusedCellWidth.current != null) {
          nextCellWidth = focusedCellWidth.current + widthDelta;
        }

        if (nextCellWidth > 20) {
          focusedCellWidth.current = nextCellWidth;
          lastPointerX.current = event.clientX;
          if (resizeLine.current != null && tableHeading.current != null) {
            resizeLine.current.style.transform = `translate(${Math.max(0, event.clientX)}px, ${
              tableHeading.current.getBoundingClientRect().top
            }px)`;
          }
        }
      };

      const handlePointerUp = () => {
        UIStore.removeGlobalStyle(pointerDownStyles);
        window.removeEventListener('pointerup', handlePointerUp);
        window.removeEventListener('pointermove', handlePointerMove);

        setIsPointerDown(false);
        lastPointerX.current = undefined;

        if (resizeLine.current != null) {
          resizeLine.current.style.opacity = '0';
        }

        if (focusedCell.current != null && focusedCellWidth.current != null) {
          onWidthsChange(focusedCell.current, focusedCellWidth.current);
        }

        focusedCell.current = undefined;
        focusedCellWidth.current = undefined;
      };

      return (
        <div className="table__row table__row--heading" role="row" ref={tableHeading}>
          {SettingStore.floodSettings.torrentListColumns.reduce((accumulator: Array<ReactElement>, {id, visible}) => {
            if (!visible) {
              return accumulator;
            }

            const labelID = TorrentListColumns[id];
            if (labelID == null) {
              return accumulator;
            }

            let handle = null;
            const width = SettingStore.floodSettings.torrentListColumnWidths[id] || 100;

            if (!isPointerDown) {
              handle = (
                <span
                  className="table__heading__handle"
                  onPointerDown={(event) => {
                    if (!isPointerDown && resizeLine.current != null && tableHeading.current != null) {
                      setIsPointerDown(true);

                      focusedCell.current = id;
                      focusedCellWidth.current = width;
                      lastPointerX.current = event.clientX;

                      window.addEventListener('pointerup', handlePointerUp);
                      window.addEventListener('pointermove', handlePointerMove);
                      UIStore.addGlobalStyle(pointerDownStyles);

                      resizeLine.current.style.transform = `translate(${Math.max(0, event.clientX)}px, ${
                        tableHeading.current.getBoundingClientRect().top
                      }px)`;
                      resizeLine.current.style.opacity = '1';
                    }
                  }}
                />
              );
            }

            const isSortActive = id === SettingStore.floodSettings.sortTorrents.property;
            const classes = classnames('table__cell table__heading', {
              'table__heading--is-sorted': isSortActive,
              [`table__heading--direction--${SettingStore.floodSettings.sortTorrents.direction}`]: isSortActive,
            });

            accumulator.push(
              <button
                className={classes}
                css={{
                  textAlign: 'left',
                  ':focus': {
                    outline: 'none',
                    WebkitTapHighlightColor: 'transparent',
                  },
                }}
                role="columnheader"
                aria-sort={
                  isSortActive
                    ? SettingStore.floodSettings.sortTorrents.direction === 'asc'
                      ? 'ascending'
                      : 'descending'
                    : 'none'
                }
                type="button"
                key={id}
                onClick={() => onCellClick(id)}
                onFocus={() => onCellFocus()}
                style={{
                  width: `${width}px`,
                }}
              >
                <span className="table__heading__label" title={i18n._(labelID)}>
                  <Trans id={labelID} />
                </span>
                {handle}
              </button>,
            );

            return accumulator;
          }, [])}
          <div className="table__cell table__heading table__heading--fill" />
          <div className="table__heading__resize-line" ref={resizeLine} />
        </div>
      );
    },
  ),
)