react#ReactNodeArray TypeScript Examples

The following examples show how to use react#ReactNodeArray. 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: ContextMenu.tsx    From freedeck-configurator with GNU General Public License v3.0 6 votes vote down vote up
ContextMenu: React.FC<{
  children: ReactNode | ReactNodeArray;
  menuId: string;
}> = ({ children, menuId }) => {
  const childrenArray = isReactNodeArray(children) ? children : [children];
  const childrenWithDividers = childrenArray.reduce(
    (acc: ReactNodeArray, child, index) => {
      if (!acc.length) return [child];
      acc.push(<Divider key={index} />);
      acc.push(child);
      return acc;
    },
    []
  );
  return (
    <ContextMenuWrapper id={menuId}>
      <Wrapper>{childrenWithDividers}</Wrapper>
    </ContextMenuWrapper>
  );
}
Example #2
Source File: ContextMenu.tsx    From freedeck-configurator with GNU General Public License v3.0 5 votes vote down vote up
isReactNodeArray = (
  children: ReactNode | ReactNodeArray
): children is ReactNodeArray => {
  return !!(children as ReactNodeArray).length;
}
Example #3
Source File: DiskUsage.tsx    From flood with GNU General Public License v3.0 5 votes vote down vote up
DiskUsage: FC = observer(() => {
  const {i18n} = useLingui();

  const {disks} = DiskUsageStore;
  const {mountPoints} = SettingStore.floodSettings;

  if (disks == null || mountPoints == null) {
    return null;
  }

  const diskMap = disks.reduce(
    (disksByTarget: Record<string, Disk>, disk: Disk) => ({
      ...disksByTarget,
      [disk.target]: disk,
    }),
    {},
  );

  const diskNodes: ReactNodeArray = mountPoints
    .filter((target) => target in diskMap)
    .map((target) => diskMap[target])
    .map((d) => (
      <li key={d.target} className="sidebar-filter__item sidebar__diskusage" role="row">
        <Tooltip
          content={
            <ul className="diskusage__details-list" role="tooltip">
              <DiskUsageTooltipItem value={d.used} label={i18n._('status.diskusage.used')} />
              <DiskUsageTooltipItem value={d.avail} label={i18n._('status.diskusage.free')} />
              <DiskUsageTooltipItem value={d.size} label={i18n._('status.diskusage.total')} />
            </ul>
          }
          position="top"
          styles={css({
            cursor: 'default',
          })}
          wrapperClassName="diskusage__item"
        >
          <div className="diskusage__text-row">
            {d.target}
            <span>{Math.round((100 * d.used) / d.size)}%</span>
          </div>
          <ProgressBar percent={Math.round((100 * d.used) / d.size)} />
        </Tooltip>
      </li>
    ));

  if (diskNodes == null || diskNodes.length === 0) {
    return null;
  }

  const title = i18n._('status.diskusage.title');

  return (
    <ul aria-label={title} className="sidebar-filter sidebar__item" role="table">
      <li className="sidebar-filter__item sidebar-filter__item--heading">{title}</li>
      {diskNodes}
    </ul>
  );
})
Example #4
Source File: FilesystemBrowser.tsx    From flood with GNU General Public License v3.0 4 votes vote down vote up
FilesystemBrowser: FC<FilesystemBrowserProps> = memo(
  ({directory, selectable, onItemSelection, onYieldFocus}: FilesystemBrowserProps) => {
    const [cursor, setCursor] = useState<number | null>(null);
    const [errorResponse, setErrorResponse] = useState<{data?: NodeJS.ErrnoException} | null>(null);
    const [separator, setSeparator] = useState<string>(directory.includes('/') ? '/' : '\\');
    const [directories, setDirectories] = useState<string[] | null>(null);
    const [files, setFiles] = useState<string[] | null>(null);

    const listRef = useRef<HTMLUListElement>(null);

    const lastSegmentIndex = directory.lastIndexOf(separator) + 1;
    const currentDirectory = lastSegmentIndex > 0 ? directory.substr(0, lastSegmentIndex) : directory;
    const lastSegment = directory.substr(lastSegmentIndex);

    useEffect(() => {
      if (!currentDirectory) {
        return;
      }

      setCursor(null);
      setDirectories(null);
      setFiles(null);

      FloodActions.fetchDirectoryList(currentDirectory)
        .then(({files: fetchedFiles, directories: fetchedDirectories, separator: fetchedSeparator}) => {
          setDirectories(fetchedDirectories);
          setFiles(fetchedFiles);
          setSeparator(fetchedSeparator);
          setErrorResponse(null);
        })
        .catch(({response}) => {
          setErrorResponse(response);
        });
    }, [currentDirectory]);

    useEffect(() => {
      if (listRef.current != null && cursor != null) {
        const element = (listRef.current.children[cursor] as HTMLLIElement)?.children[0] as HTMLDivElement;
        if (element?.tabIndex === 0) {
          element.focus();
        } else {
          setCursor(
            Array.from(listRef.current.children).findIndex((e) => (e.children[0] as HTMLDivElement).tabIndex === 0),
          );
        }
      }
    }, [cursor]);

    useKey('ArrowUp', (e) => {
      e.preventDefault();
      setCursor((prevCursor) => {
        if (prevCursor == null || prevCursor - 1 < 0) {
          onYieldFocus?.();
          return null;
        }

        return prevCursor - 1;
      });
    });

    useKey('ArrowDown', (e) => {
      e.preventDefault();
      setCursor((prevCursor) => {
        if (prevCursor != null) {
          return prevCursor + 1;
        }
        return 0;
      });
    });

    let errorMessage: string | null = null;
    let listItems: ReactNodeArray = [];

    if ((directories == null && selectable === 'directories') || (files == null && selectable === 'files')) {
      errorMessage = 'filesystem.fetching';
    }

    if (errorResponse && errorResponse.data && errorResponse.data.code) {
      errorMessage = MESSAGES[errorResponse.data.code as keyof typeof MESSAGES] || 'filesystem.error.unknown';
    }

    if (!directory) {
      errorMessage = 'filesystem.error.no.input';
    } else {
      const parentDirectory = `${currentDirectory.split(separator).slice(0, -2).join(separator)}${separator}`;
      const parentDirectoryElement = (
        <li
          css={[
            listItemStyle,
            listItemSelectableStyle,
            {
              '@media (max-width: 720px)': headerStyle,
            },
          ]}
          key={parentDirectory}
        >
          <button type="button" onClick={() => onItemSelection?.(parentDirectory, true)}>
            <Arrow css={{transform: 'scale(0.75) rotate(180deg)'}} />
            ..
          </button>
        </li>
      );

      const isDirectorySelectable = selectable !== 'files';
      const directoryMatched = lastSegment ? termMatch(directories, (subDirectory) => subDirectory, lastSegment) : [];

      const inputDirectoryElement =
        !directoryMatched.includes(lastSegment) && selectable === 'directories' && lastSegment && !errorMessage
          ? (() => {
              const inputDestination = `${currentDirectory}${lastSegment}${separator}`;
              return [
                <li
                  css={[
                    listItemStyle,
                    listItemSelectableStyle,
                    {fontWeight: 'bold', '@media (max-width: 720px)': {display: 'none'}},
                  ]}
                  key={inputDestination}
                >
                  <button type="button" onClick={() => onItemSelection?.(inputDestination, false)}>
                    <FolderClosedOutlined />
                    <span css={{whiteSpace: 'pre-wrap'}}>{lastSegment}</span>
                    <em css={{fontWeight: 'lighter'}}>
                      {' - '}
                      <Trans id="filesystem.error.enoent" />
                    </em>
                  </button>
                </li>,
              ];
            })()
          : [];

      const directoryList: ReactNodeArray =
        (directories?.length &&
          sort(directories.slice())
            .desc((subDirectory) => directoryMatched.includes(subDirectory))
            .map((subDirectory) => {
              const destination = `${currentDirectory}${subDirectory}${separator}`;
              return (
                <li
                  css={[
                    listItemStyle,
                    isDirectorySelectable ? listItemSelectableStyle : undefined,
                    directoryMatched.includes(subDirectory) ? {fontWeight: 'bold'} : undefined,
                  ]}
                  key={destination}
                >
                  <button
                    type="button"
                    disabled={!isDirectorySelectable}
                    tabIndex={isDirectorySelectable ? 0 : -1}
                    onClick={isDirectorySelectable ? () => onItemSelection?.(destination, true) : undefined}
                  >
                    <FolderClosedSolid />
                    {subDirectory}
                  </button>
                </li>
              );
            })) ||
        [];

      const isFileSelectable = selectable !== 'directories';
      const fileMatched = lastSegment ? termMatch(files, (file) => file, lastSegment) : [];
      const fileList: ReactNodeArray =
        (files?.length &&
          sort(files.slice())
            .desc((file) => fileMatched.includes(file))
            .map((file) => {
              const destination = `${currentDirectory}${file}`;
              return (
                <li
                  css={[
                    listItemStyle,
                    isFileSelectable ? listItemSelectableStyle : undefined,
                    fileMatched.includes(file) ? {fontWeight: 'bold'} : undefined,
                  ]}
                  key={destination}
                >
                  <button
                    type="button"
                    disabled={!isFileSelectable}
                    tabIndex={isFileSelectable ? 0 : -1}
                    onClick={isFileSelectable ? () => onItemSelection?.(destination, false) : undefined}
                  >
                    <File />
                    {file}
                  </button>
                </li>
              );
            })) ||
        [];

      if (directoryList.length === 0 && fileList.length === 0 && !errorMessage) {
        errorMessage = 'filesystem.empty.directory';
      }

      listItems = [parentDirectoryElement, ...inputDirectoryElement, ...directoryList, ...fileList];
    }

    return (
      <div
        css={{
          color: foregroundColor,
          listStyle: 'none',
          padding: '3px 0px',
          '.icon': {
            fill: 'currentColor',
            height: '14px',
            width: '14px',
            marginRight: `${25 * (1 / 5)}px`,
            marginTop: '-3px',
            verticalAlign: 'middle',
          },
        }}
      >
        {currentDirectory && (
          <li
            css={[
              listItemStyle,
              headerStyle,
              itemPadding,
              {
                whiteSpace: 'pre-wrap',
                wordBreak: 'break-all',
                '.icon': {
                  transform: 'scale(0.9)',
                  marginTop: '-2px !important',
                },
                '@media (max-width: 720px)': {
                  display: 'none',
                },
              },
            ]}
          >
            <FolderOpenSolid />
            {currentDirectory}
          </li>
        )}
        <ul ref={listRef}>{listItems}</ul>
        {errorMessage && (
          <div css={[listItemStyle, itemPadding, {opacity: 1}]}>
            <em>
              <Trans id={errorMessage} />
            </em>
          </div>
        )}
      </div>
    );
  },
)
Example #5
Source File: Select.tsx    From flood with GNU General Public License v3.0 4 votes vote down vote up
Select: FC<SelectProps> = ({
  additionalClassNames,
  children,
  defaultID,
  disabled,
  label,
  labelOffset,
  persistentPlaceholder,
  priority,
  shrink,
  grow,
  matchTriggerWidth,
  width,
  id,
  menuAlign,
  onOpen,
  onClose,
  onSelect,
}: SelectProps) => {
  const menuRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);
  const triggerRef = useRef<HTMLButtonElement>(null);

  const [isOpen, setIsOpen] = useState<boolean>(false);
  const [selectedID, setSelectedID] = useState<string | number>(
    defaultID ??
      (
        (children as ReactNodeArray)?.find(
          (child) => (child as ReactElement<SelectItemProps>)?.props?.id != null,
        ) as ReactElement<SelectItemProps>
      )?.props.id ??
      '',
  );

  const classes = classnames('select form__element', additionalClassNames, {
    'form__element--disabled': disabled,
    'form__element--label-offset': labelOffset,
    'select--is-open': isOpen,
  });

  const selectedItem = Children.toArray(children).find((child, index) => {
    const item = child as ReactElement<SelectItemProps>;
    return (
      (persistentPlaceholder && item.props.isPlaceholder) ||
      (!selectedID && index === 0) ||
      item.props.id === selectedID
    );
  });

  useKey('Escape', (event) => {
    event.preventDefault();
    setIsOpen(false);
  });

  useEvent(
    'scroll',
    (event: Event) => {
      if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
        setIsOpen(false);
      }
    },
    window,
    {capture: true},
  );

  useEffect(() => {
    if (isOpen) {
      onOpen?.();
    } else {
      onClose?.();
    }
  }, [isOpen, onClose, onOpen]);

  useEffect(() => {
    onSelect?.(selectedID);

    if (inputRef.current != null) {
      dispatchChangeEvent(inputRef.current);
    }
  }, [onSelect, selectedID]);

  return (
    <FormRowItem shrink={shrink} grow={grow} width={width}>
      {label && (
        <label className="form__element__label" htmlFor={`${id}`}>
          {label}
        </label>
      )}
      <div className={classes}>
        <input
          className="input input--hidden"
          name={`${id}`}
          onChange={() => undefined}
          tabIndex={-1}
          ref={inputRef}
          type="text"
          value={selectedID}
        />
        <Button
          additionalClassNames="select__button"
          buttonRef={triggerRef}
          addonPlacement="after"
          onClick={() => {
            if (!disabled) {
              setIsOpen(!isOpen);
            }
          }}
          priority={priority}
          wrap={false}
        >
          <FormElementAddon className="select__indicator">
            <Chevron />
          </FormElementAddon>
          {selectedItem && cloneElement(selectedItem as ReactElement, {isTrigger: true})}
        </Button>
        <Portal>
          <ContextMenu
            onOverlayClick={() => {
              setIsOpen(!isOpen);
            }}
            isIn={isOpen}
            matchTriggerWidth={matchTriggerWidth}
            menuAlign={menuAlign}
            ref={menuRef}
            triggerRef={triggerRef}
          >
            {Children.toArray(children).reduce((accumulator: Array<ReactElement>, child) => {
              const item = child as ReactElement<SelectItemProps>;

              if (item.props.isPlaceholder) {
                return accumulator;
              }

              accumulator.push(
                cloneElement(child as ReactElement, {
                  onClick: (selection: string | number) => {
                    setIsOpen(false);
                    setSelectedID(selection);
                  },
                  isSelected: item.props.id === selectedID,
                }),
              );

              return accumulator;
            }, [])}
          </ContextMenu>
        </Portal>
      </div>
    </FormRowItem>
  );
}