react-transition-group#TransitionGroup TypeScript Examples

The following examples show how to use react-transition-group#TransitionGroup. 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: index.tsx    From polkabtc-ui with Apache License 2.0 6 votes vote down vote up
TransitionWrapper = ({
  location,
  children
}: Props): JSX.Element => (
  <TransitionGroup>
    <CSSTransition
      key={location.key}
      classNames='fade'
      timeout={300}>
      {children}
    </CSSTransition>
  </TransitionGroup>
)
Example #2
Source File: Layout.tsx    From mStable-apps with GNU Lesser General Public License v3.0 6 votes vote down vote up
Layout: FC = ({ children }) => {
  return (
    <ModalDataProvider>
      <ModalProvider rootComponent={TransitionGroup}>
        <Background />
        <BannerMessage />
        <AppBar />
        <Container>
          <Main>{children}</Main>
        </Container>
        <Footer />
        <Toasts />
        <StyledTooltip tip="" hideIcon />
        <ReactTooltip id="global" place="top" html={true} />
        <GlobalStyle />
      </ModalProvider>
    </ModalDataProvider>
  )
}
Example #3
Source File: Toasts.tsx    From mStable-apps with GNU Lesser General Public License v3.0 6 votes vote down vote up
Toasts: FC = () => {
  const notifications = useNotificationsState()
  const txs = useTransactionsState()

  return (
    <Container>
      <TransitionGroup>
        {Object.keys(txs)
          .filter(id => txs[id].status === TransactionStatus.Pending || txs[id].status === TransactionStatus.Sent)
          .sort((a, b) => txs[b].manifest.createdAt - txs[a].manifest.createdAt)
          .map(id => (
            <Animation timeout={TIMEOUT.default} classNames="item" key={id}>
              <div>
                <TransactionGasProvider id={id}>
                  <PendingTransaction id={id} />
                </TransactionGasProvider>
              </div>
            </Animation>
          ))}
        {notifications
          .filter(n => !(n.hideToast || n.read))
          .map(notification => (
            <Animation
              isQuest={notification.type === NotificationType.Quest}
              timeout={notification.type === NotificationType.Quest ? TIMEOUT.quest : TIMEOUT.default}
              classNames="item"
              key={notification.id}
            >
              <NotificationItem notification={notification} />
            </Animation>
          ))}
      </TransitionGroup>
    </Container>
  )
}
Example #4
Source File: MinimizedChats.tsx    From foodie with MIT License 6 votes vote down vote up
MinimizedChats: React.FC<IProps> = ({ users }) => {
    const dispatch = useDispatch();

    return (
        <div className="p-4 absolute bottom-0 right-0">
            <TransitionGroup component={null}>
                {users.map(chat => chat.minimized && (
                    <CSSTransition
                        timeout={500}
                        classNames="fade"
                        key={chat.username}
                    >
                        <div
                            key={chat.username}
                            onClick={() => dispatch(initiateChat(chat))}
                            title={chat.username}
                        >
                            <Avatar
                                url={chat.profilePicture?.url}
                                size="lg"
                                className="cursor-pointer shadow-md hover:border-2 hover:border-indigo-500 hover:opacity-90  mr-2 my-4 border border-indigo-700"
                            />
                        </div>
                    </CSSTransition>
                ))}
            </TransitionGroup>
        </div>
    );
}
Example #5
Source File: TransitionCard.tsx    From mStable-apps with GNU Lesser General Public License v3.0 6 votes vote down vote up
TransitionCard: FC<{
  components: Record<string, ReactElement>
  selection?: string
}> = ({ components, selection, children }) => {
  return (
    <Container>
      <Header showBorder={!!selection}>{children}</Header>
      <Content open={!!selection}>
        <TransitionGroup>
          {Object.keys(components)
            .filter(type => type === selection)
            .map(type => {
              const Comp = components[type]
              return (
                <CSSTransition timeout={{ enter: 500, exit: 200 }} classNames="item" key={type}>
                  {Comp}
                </CSSTransition>
              )
            })}
        </TransitionGroup>
      </Content>
    </Container>
  )
}
Example #6
Source File: index.tsx    From oasis-wallet-web with Apache License 2.0 6 votes vote down vote up
export function App() {
  useRouteRedirects()
  const { i18n } = useTranslation()
  const size = useContext(ResponsiveContext)

  return (
    <ModalProvider>
      <Helmet
        titleTemplate="%s - Oasis Wallet"
        defaultTitle="Oasis Wallet"
        htmlAttributes={{ lang: i18n.language }}
      >
        <meta name="description" content="A wallet for Oasis" />
      </Helmet>
      <Box direction="row-responsive" background="background-back" fill style={{ minHeight: '100vh' }}>
        <Navigation />
        <Box flex pad={{ top: size === 'small' ? '64px' : undefined }}>
          <AppMain>
            <FatalErrorHandler />
            <Toolbar />
            <TransitionGroup>
              <Switch>
                <TransitionRoute exact path="/" component={HomePage} />
                <TransitionRoute exact path="/create-wallet" component={CreateWalletPage} />
                <TransitionRoute path="/open-wallet" component={OpenWalletPage} />
                <TransitionRoute exact path="/account/:address/stake" component={AccountPage} />
                <TransitionRoute path="/account/:address" component={AccountPage} />
              </Switch>
            </TransitionGroup>
            <Footer />
          </AppMain>
        </Box>
      </Box>
    </ModalProvider>
  )
}
Example #7
Source File: Modals.tsx    From flood with GNU General Public License v3.0 6 votes vote down vote up
Modals: FC = observer(() => {
  const {id} = UIStore.activeModal || {};

  useKeyPressEvent('Escape', () => UIStore.setActiveModal(null));

  let modal;
  if (id != null) {
    modal = (
      <CSSTransition key={id} classNames="modal__animation" timeout={{enter: 500, exit: 500}}>
        <div className="modal">
          <div
            className="modal__overlay"
            role="none"
            onClick={() => {
              UIStore.setActiveModal(null);
            }}
          />
          {createModal(id)}
        </div>
      </CSSTransition>
    );
  }

  return <TransitionGroup>{modal}</TransitionGroup>;
})
Example #8
Source File: index.tsx    From oasis-wallet-web with Apache License 2.0 6 votes vote down vote up
export function OpenWalletPage(props: Props) {
  return (
    <TransitionGroup>
      <Switch>
        <TransitionRoute exact path="/open-wallet" component={SelectOpenMethod} />
        <TransitionRoute exact path="/open-wallet/mnemonic" component={FromMnemonic} />
        <TransitionRoute exact path="/open-wallet/private-key" component={FromPrivateKey} />
        <TransitionRoute exact path="/open-wallet/ledger" component={FromLedger} />
      </Switch>
    </TransitionGroup>
  )
}
Example #9
Source File: Alerts.tsx    From flood with GNU General Public License v3.0 6 votes vote down vote up
Alerts: FC = observer(() => {
  const {sortedAlerts} = AlertStore;

  return (
    <TransitionGroup>
      {sortedAlerts != null && sortedAlerts.length > 0 ? (
        <CSSTransition classNames="alerts__list" timeout={{enter: 250, exit: 250}}>
          <ul className="alerts__list" key="alerts-list">
            {sortedAlerts.map((alert) => (
              <Alert key={alert.id} id={alert.id} />
            ))}
          </ul>
        </CSSTransition>
      ) : null}
    </TransitionGroup>
  );
})
Example #10
Source File: App.transitions.tsx    From tezos-academy with MIT License 6 votes vote down vote up
AppTransitions = ({ pageKey, children, reverse }: { pageKey: any; children: any; reverse: boolean }) => {
  const { pathname } = useLocation()

  useEffect(() => {
    window.scrollTo(0, 0)
  }, [pathname])

  const animation = reverse ? 'slide-right' : 'slide-left'

  return (
    <TransitionGroup
      childFactory={childFactoryCreator({
        classNames: animation,
        timeout: 300,
      })}
    >
      <CSSTransition timeout={0} key={pageKey}>
        <AppWrapper>{children}</AppWrapper>
      </CSSTransition>
    </TransitionGroup>
  )
}
Example #11
Source File: index.tsx    From interactsh-web with MIT License 6 votes vote down vote up
AnimatedSwitch = withRouter(({ location }) => {
  window.scrollTo(0, 0);
  document.getElementsByTagName("html")[0].style.overflow = "visible";
  return (
    <TransitionGroup>
      <CSSTransition key={location.pathname} classNames="slide slide" timeout={10}>
        <Switch>
          <Route exact path="/" component={HomePage} />
          <Route exact path="/terms" component={TermsPage} />
        </Switch>
      </CSSTransition>
    </TransitionGroup>
  );
})
Example #12
Source File: index.tsx    From vagasExplorer with MIT License 6 votes vote down vote up
Routes: React.FC<ToggleTheme> = ({ toggleTheme }) => (
  <Route
    render={({ location }) => (
      <TransitionGroup>
        <CSSTransition key={location.key} timeout={300} classNames="fade">
          <Switch location={location}>
            <Route
              exact
              path="/"
              component={() => <Home toggleTheme={toggleTheme} />}
            />
            <Route
              exact
              path="/dashboard"
              component={() => <Dashboard toggleTheme={toggleTheme} />}
            />
            <Route
              path="/repository/:repository+"
              component={() => <Repository toggleTheme={toggleTheme} />}
            />
          </Switch>
        </CSSTransition>
      </TransitionGroup>
    )}
  />
)
Example #13
Source File: ToastContainer.tsx    From vvs-ui with GNU General Public License v3.0 6 votes vote down vote up
ToastContainer: React.FC<ToastContainerProps> = ({ toasts, onRemove, ttl = 6000, stackSpacing = 24 }) => {
  return (
    <StyledToastContainer>
      <TransitionGroup>
        {toasts.map((toast, index) => {
          const zIndex = (ZINDEX - index).toString()
          const top = TOP_POSITION + index * stackSpacing

          return (
            <Toast key={toast.id} toast={toast} onRemove={onRemove} ttl={ttl} style={{ top: `${top}px`, zIndex }} />
          )
        })}
      </TransitionGroup>
    </StyledToastContainer>
  )
}
Example #14
Source File: index.tsx    From hive with MIT License 6 votes vote down vote up
function Page({ page, index, common, dashboard }: Props) {
    const currentKey = index;
    const timeout = { enter: 300, exit: 200 };

    const wrapClass = cn(styles.wrap, { [styles.mobile]: common.isMobile });

    return (
        <TransitionGroup component="div" className={styles.mainTransition}>
            <CSSTransition key={currentKey} timeout={timeout} classNames="animated-fade" appear>
                <div className={wrapClass}>
                    <LoaderSpinner loading={dashboard.loading} />
                    <Header page={page} />
                    <div className={styles.content}>
                        <div className={styles.background} />
                        <div className={styles.internalContent}>
                            <Content />
                        </div>
                    </div>
                </div>
            </CSSTransition>
        </TransitionGroup>
    );
}
Example #15
Source File: index.tsx    From hive with MIT License 6 votes vote down vote up
render() {
        const { model, isDragging, connectDragSource, connectDropTarget } = this.props;

        const timeout = { enter: 500, exit: 500 };

        return (
            <TransitionGroup component={null}>
                <CSSTransition
                    timeout={timeout}
                    classNames={{
                        appear: styles.appear,
                        appearActive: styles.appearActive,
                        enter: styles.enter,
                        enterActive: styles.enterActive,
                        exit: styles.exit,
                        exitActive: styles.exitActive,
                    }}
                    appear
                >
                    {connectDropTarget(
                        <div>
                            <Module
                                service={this.service}
                                connectDragSource={connectDragSource}
                                isDragging={isDragging}
                                model={model}
                                onRemoveWidget={this.removeWidget}
                                onOpenDrawer={this.openDrawer}
                            />
                        </div>
                    )}
                </CSSTransition>
            </TransitionGroup>
        );
    }
Example #16
Source File: index.tsx    From hive with MIT License 6 votes vote down vote up
render() {
        const { location } = this.props;
        const currentKey = location.pathname || '/';
        const timeout = { enter: 300, exit: 200 };

        return (
            <Fragment>
                <WidthCalculator />
                <Tabs location={location} />
                <TransitionGroup component="div" className={styles.mainTransition}>
                    <CSSTransition
                        key={currentKey}
                        timeout={timeout}
                        classNames="animated-fade"
                        appear
                    >
                        <div className={styles.transition}>
                            <Switch location={location}>
                                <AuthorizedRoute path={constants.paths.admin} component={Admin} />
                                <AuthorizedRoute
                                    path={constants.paths.dashboard}
                                    component={Dashboard}
                                />
                            </Switch>
                        </div>
                    </CSSTransition>
                </TransitionGroup>
            </Fragment>
        );
    }
Example #17
Source File: confirmationModal.tsx    From atlas with GNU General Public License v3.0 6 votes vote down vote up
ConfirmationModalProvider: React.FC = ({ children }) => {
  const [modals, setModals] = useState<Record<string, React.FC>>({})

  const openModal = useCallback(
    (key: string, modal: React.FC) =>
      setModals((modals) => ({
        ...modals,
        [key]: modal,
      })),
    []
  )

  const closeModal = useCallback(
    (key: string) =>
      setModals((modals) => {
        const { [key]: _, ...filteredModals } = modals
        return filteredModals
      }),
    []
  )

  const contextValue = useMemo(() => ({ openModal, closeModal }), [closeModal, openModal])

  return (
    <ConfirmationModalContext.Provider value={contextValue}>
      {children}
      <TransitionGroup>
        {Object.keys(modals).map((key) => {
          const Component = modals[key]
          return <Component key={key} />
        })}
      </TransitionGroup>
    </ConfirmationModalContext.Provider>
  )
}
Example #18
Source File: Products.tsx    From next-shopping-cart with MIT License 5 votes vote down vote up
Products = ({ searchTerm, productsList, openModal }: Props) => {
  const { addProduct } = useContext<Init>(CartContext);

  const term = searchTerm;

  const searchingFor = (searchText: string) => {
    return (x) => {
      return (
        x.name.toLowerCase().includes(searchText.toLowerCase()) || !searchText
      );
    };
  };

  const productsData = productsList
    .filter(searchingFor(term))
    .map((product) => {
      return (
        <CSSTransition
          key={product.id}
          classNames="fadeIn"
          timeout={{
            enter: 300,
            exit: 500,
          }}
        >
          <ProductItem
            // key={product.id}
            price={product.price}
            name={product.name}
            image={product.image}
            id={parseInt(product.id, 10)}
            unit={product.unit}
            addToCart={addProduct}
            // productQuantity={props.productQuantity}
            openModal={openModal}
          />
        </CSSTransition>
      );
    });

  // Empty and Loading States
  let view;
  // if (productsData.length <= 0 && !term) {
  //   view = <LoadingProducts />;
  // } else
  if (productsData.length <= 0 && term) {
    view = <NoResults />;
  } else {
    view = (
      <TransitionGroup component="div" className={styles.products}>
        {productsData}
      </TransitionGroup>
    );
  }
  return <div className={styles.productsWrapper}>{view}</div>;
}
Example #19
Source File: index.tsx    From questdb.io with Apache License 2.0 5 votes vote down vote up
Subscribe: React.FunctionComponent<Props> = ({
  placeholder = "Email address",
  provider = "enterprise",
  submitButtonText = "SUBMIT",
  submitButtonVariant,
  className,
}) => {
  const [loading, setLoading] = useState(false)
  const [sent, setSent] = useState(false)

  const onSubmit = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault()

    setLoading(true)

    const target = event.target as HTMLFormElement
    const email = new FormData(target).get("email") as string

    try {
      if (provider === "newsletter") {
        await fetch(
          `${providers.newsletter}&EMAIL=${encodeURIComponent(email)}`,
          { method: "GET" },
        )
      } else {
        await fetch(providers[provider], {
          body: JSON.stringify({ email }),
          headers: { "Content-Type": "application/json" },
          method: "POST",
        })
      }
    } catch (e) {}

    setSent(true)
  }

  return (
    <form className={clsx(style.root, className)} onSubmit={onSubmit}>
      <TransitionGroup>
        <CSSTransition key={sent.toString()} timeout={200} classNames="item">
          {sent ? (
            <p className={style.success}>
              Thank you, we will be in touch soon!
            </p>
          ) : (
            <div className={style.inputs}>
              <Input
                className={style.input}
                name="email"
                type="email"
                title="Email address should be valid"
                placeholder={placeholder}
                required
                pattern={emailPattern}
              />

              <Button
                className={style.submit}
                variant={submitButtonVariant}
                type="submit"
              >
                {loading ? <span className={style.loader} /> : submitButtonText}
              </Button>
            </div>
          )}
        </CSSTransition>
      </TransitionGroup>
    </form>
  )
}
Example #20
Source File: ChannelCover.tsx    From atlas with GNU General Public License v3.0 5 votes vote down vote up
ChannelCover: React.FC<ChannelCoverProps> = ({
  assetUrl,
  hasCoverUploadFailed,
  editable,
  disabled,
  onCoverEditClick,
}) => {
  return (
    <CoverWrapper>
      <MediaWrapper>
        {editable && !disabled && (
          <EditableControls>
            <EditCoverDesktopOverlay onClick={onCoverEditClick}>
              <SvgActionImage />
              <EditButtonMessage variant="t200-strong">{`${
                assetUrl ? 'Edit ' : 'Add '
              } cover image`}</EditButtonMessage>
            </EditCoverDesktopOverlay>
            <EditCoverMobileButton onClick={onCoverEditClick} variant="tertiary">
              <SvgActionImageFile />
            </EditCoverMobileButton>
          </EditableControls>
        )}
        <Media>
          <TransitionGroup>
            <CSSTransition
              key={assetUrl ? 'cover' : 'pattern'}
              timeout={parseInt(transitions.timings.loading)}
              classNames={transitions.names.fade}
            >
              {assetUrl ? (
                <CoverImage src={assetUrl} />
              ) : hasCoverUploadFailed ? (
                <FailedUploadContainer>
                  <StyledSvgIllustrativeFileFailed />
                  <Text variant="t100" secondary>
                    Failed upload
                  </Text>
                </FailedUploadContainer>
              ) : (
                <StyledBackgroundPattern />
              )}
            </CSSTransition>
          </TransitionGroup>
        </Media>
      </MediaWrapper>
    </CoverWrapper>
  )
}
Example #21
Source File: snackbar.tsx    From atlas with GNU General Public License v3.0 5 votes vote down vote up
Snackbars: React.FC = () => {
  const { closeSnackbar, cancelSnackbarTimeout, restartSnackbarTimeout } = useSnackbar()
  const snackbars = useSnackbarStore((state) => state.snackbars)
  const { cookiesAccepted } = usePersonalDataStore((state) => ({
    cookiesAccepted: state.cookiesAccepted,
  }))
  const bottomNavOpen = useBottomNavStore((state) => state.open)

  useEffect(() => {
    if (snackbars.length > SNACKBARS_LIMIT) {
      setTimeout(() => {
        closeSnackbar(snackbars[0].id)
      }, 500)
    }
  }, [snackbars, closeSnackbar])

  return (
    <SnackbarsContainer cookiesBannerOpen={cookiesAccepted === undefined} bottomNavOpen={bottomNavOpen}>
      <TransitionGroup>
        {snackbars.map(({ id, iconType, onActionClick, onExit, ...snackbarProps }) => (
          <CSSTransition key={id} timeout={parseInt(cVar('animationTimingMedium', true)) * 2} classNames="snackbar">
            <Snackbar
              {...snackbarProps}
              onActionClick={() => {
                onActionClick?.()
                closeSnackbar(id)
              }}
              onMouseEnter={() => {
                cancelSnackbarTimeout(id)
              }}
              onMouseLeave={() => {
                restartSnackbarTimeout(id)
              }}
              icon={iconType && ICON_TYPE_TO_ICON[iconType]}
              onClick={() => {
                onExit?.()
                closeSnackbar(id)
              }}
            />
          </CSSTransition>
        ))}
      </TransitionGroup>
    </SnackbarsContainer>
  )
}
Example #22
Source File: MessagesList.tsx    From foodie with MIT License 5 votes vote down vote up
MessagesList: React.FC<IProps> = (props) => {
    const { messages, handleReadMessage, infiniteScrollRef } = props;

    return (
        <div>
            {messages.length === 0 ? (
                <div className="text-center p-4">
                    <p className="text-gray-400 italic">No Messages.</p>
                </div>
            ) : (
                <div className="max-h-80vh laptop:max-h-70vh overflow-y-scroll divide-y divide-gray-100 scrollbar">
                    <TransitionGroup component={null}>
                        <div ref={infiniteScrollRef as React.RefObject<HTMLDivElement>}>
                            {messages.map(message => (message.from && message.to) && (
                                <CSSTransition
                                    timeout={500}
                                    classNames="fade"
                                    key={message.id}
                                >
                                    <div
                                        className={`flex justify-start cursor-pointer hover:bg-gray-100 dark:hover:bg-indigo-1100 border border-transparent dark:hover:border-indigo-700 px-2 py-3 relative ${(!message.seen && !message.isOwnMessage) && 'bg-indigo-100 dark:bg-indigo-1100 hover:bg-indigo-200'}`}
                                        key={message.id}
                                        onClick={() => handleReadMessage(message.isOwnMessage ? message.to : message.from)}
                                    >
                                        {/* --- IMAGE--- */}
                                        <Avatar
                                            url={!message.isOwnMessage ? message.from.profilePicture?.url : message.to.profilePicture?.url}
                                            size="lg"
                                            className="flex-shrink-0 mr-4"
                                        />
                                        <div className="relative flex-grow">
                                            {/* --- USERNAME --- */}
                                            <h5 className={`${(!message.seen && !message.isOwnMessage) && 'font-bold text-gray-800 dark:text-white'} text-gray-500`}>
                                                {!message.isOwnMessage ? message.from.username : message.to.username}
                                            </h5>
                                            {/* -- MESSAGE--- */}
                                            <span className={`block max-w-16rem laptop:max-w-xs whitespace-nowrap overflow-hidden overflow-ellipsis ${(message.seen || message.isOwnMessage) ? 'text-gray-400' : 'text-indigo-600 dark:text-indigo-400 font-medium'} text-sm`}>
                                                {message.isOwnMessage && 'You:'} {message.text}
                                            </span>
                                            {/* --- DATE --- */}
                                            <span className="absolute right-4 top-0 text-1xs text-gray-400">
                                                {displayTime(message.createdAt)}
                                            </span>
                                            {/* ---- SEEN ---- */}
                                            {(message.isOwnMessage && message.seen) && (
                                                <Avatar
                                                    className="absolute right-4 bottom-0"
                                                    size="xs"
                                                    url={message.to.profilePicture?.url}
                                                />
                                            )}
                                            {/* --- BADGE ---- */}
                                            {(!message.isOwnMessage && !message.seen) && (
                                                <div className="absolute rounded-full  bottom-0 top-0 right-4 my-auto w-2 h-2 bg-red-600" />
                                            )}
                                        </div>
                                    </div>
                                </CSSTransition>
                            )
                            )}
                        </div>
                    </TransitionGroup>
                </div>
            )}
        </div>
    );
}
Example #23
Source File: CommentList.tsx    From foodie with MIT License 5 votes vote down vote up
CommentList: React.FC<IProps> = ({ comments, updateCommentCallback }) => {
    const didMount = useDidMount();
    const dispatch = useDispatch();
    const [replies, setReplies] = useState<IComment[]>(comments);
    const { isOpen, closeModal, openModal } = useModal();

    useEffect(() => {
        didMount && setReplies(comments);
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [comments]);

    const deleteSuccessCallback = (comment: IComment) => {
        if (didMount) {
            (updateCommentCallback) && updateCommentCallback(comment); // For updating the base/parent comment
            dispatch(setTargetComment(null));
            setReplies(oldComments => oldComments.filter((cmt) => cmt.id !== comment.id));
        }
    }

    return (
        <TransitionGroup component={null}>
            {replies.map(comment => (
                <CSSTransition
                    timeout={500}
                    classNames="fade"
                    key={comment.id}
                >
                    <CommentItem openDeleteModal={openModal} comment={comment} />
                </CSSTransition>
            ))}
            {/* ---- DELETE MODAL ---- */}
            <Suspense fallback={<LoadingOutlined className="text-gray-800 dark:text-white" />}>
                {isOpen && (
                    <DeleteCommentModal
                        isOpen={isOpen}
                        closeModal={closeModal}
                        deleteSuccessCallback={deleteSuccessCallback}
                    />
                )}
            </Suspense>
        </TransitionGroup>
    );
}
Example #24
Source File: AppWrapper.tsx    From flood with GNU General Public License v3.0 5 votes vote down vote up
AppWrapper: FC<AppWrapperProps> = observer(({children, className}: AppWrapperProps) => {
  const navigate = useNavigate();

  const [searchParams] = useSearchParams();

  useEffectOnce(() => {
    AuthActions.verify().then(
      ({initialUser}: {initialUser?: boolean}): void => {
        if (initialUser) {
          navigate('/register', {replace: true});
        } else {
          navigate('/overview', {replace: true});
        }
      },
      (): void => {
        navigate('/login', {replace: true});
      },
    );
  });

  if (searchParams.has('action')) {
    if (searchParams.get('action') === 'add-urls') {
      if (searchParams.has('url')) {
        UIStore.setActiveModal({
          id: 'add-torrents',
          tab: 'by-url',
          urls: [{id: 0, value: searchParams.get('url') as string}],
        });
      }
    }
  }

  let overlay: ReactNode = null;
  if (!AuthStore.isAuthenticating || (AuthStore.isAuthenticated && !UIStore.haveUIDependenciesResolved)) {
    overlay = <LoadingOverlay dependencies={UIStore.dependencies} />;
  }

  if (AuthStore.isAuthenticated && !ClientStatusStore.isConnected && ConfigStore.authMethod !== 'none') {
    overlay = (
      <div className="application__loading-overlay">
        <div className="application__entry-barrier">
          <LogoutButton className={css({position: 'absolute', left: '5px', top: '5px'})} />
          <ClientConnectionInterruption />
        </div>
      </div>
    );
  }

  return (
    <div className={classnames('application', className)}>
      <WindowTitle />
      <TransitionGroup>
        {overlay != null ? (
          <CSSTransition timeout={{enter: 1000, exit: 1000}} classNames="application__loading-overlay">
            {overlay}
          </CSSTransition>
        ) : null}
      </TransitionGroup>
      {children}
    </div>
  );
})
Example #25
Source File: NotificationList.tsx    From foodie with MIT License 5 votes vote down vote up
NotificationList: React.FC<IProps> = (props) => {
    const { toggleNotification, notifications, readNotification, infiniteScrollRef } = props;
    const history = useHistory();
    const handleNotificationClick = (link: string, id: string) => {
        readNotification(id);
        toggleNotification(false);
        history.push(link);
    };

    return (
        <div className="relative">
            {notifications.length === 0 ? (
                <div className="text-center p-4">
                    <p className="text-gray-400 italic">No new notifications</p>
                </div>
            ) : (
                <div
                    className="max-h-80vh laptop:max-h-70vh relative overflow-y-scroll divide-y divide-gray-100 scrollbar"
                >
                    <TransitionGroup component={null}>
                        <div ref={infiniteScrollRef as React.RefObject<HTMLDivElement>}>
                            {notifications.map((notif) => notif.initiator && (
                                <CSSTransition
                                    timeout={500}
                                    classNames="fade"
                                    key={notif.id}
                                >
                                    <div
                                        className={`border border-transparent dark:hover:border-indigo-700 ${notif.unread ? 'bg-indigo-100 dark:bg-indigo-1100 hover:bg-indigo-200' : 'bg-white dark:bg-indigo-1000 dark:hover:bg-indigo-1100'} p-4 hover:bg-gray-100 dark:hover:bg-indigo-1100 hover:opacity-95 divide-y divide-y-2 divide-gray-100`}
                                        key={notif.id}
                                        onClick={() => handleNotificationClick(notif.link, notif.id)}
                                    >
                                        <div className="relative">
                                            <div className="flex items-start">
                                                <Avatar
                                                    url={notif.initiator.profilePicture?.url}
                                                    size={window.screen.width < 1024 ? 'sm' : 'lg'}
                                                    className="mr-2 flex-shrink-0"
                                                />
                                                <div>
                                                    <span className="text-indigo-700 dark:text-indigo-400 font-medium break-all">
                                                        {notif.initiator.username}
                                                    </span>
                                                    &nbsp;
                                                <span className="text-gray-600 dark:text-gray-400 break-all">
                                                        {
                                                            notif.type === 'like' ? 'likes your post.'
                                                                : notif.type === 'comment' ? 'commented on your post.'
                                                                    : notif.type === 'comment-like' ? 'likes your comment.'
                                                                        : notif.type === 'follow' ? 'started following you.'
                                                                            : notif.type === 'reply' ? 'replied to your comment'
                                                                                : ''
                                                        }
                                                    </span>
                                                    <br />
                                                    <span className="text-gray-500 text-1xs block">
                                                        {displayTime(notif.createdAt)}
                                                    </span>
                                                </div>
                                            </div>
                                            {(notif.type === 'like' || notif.type === 'comment-like') ? (
                                                <LikeOutlined className="text-2xl text-indigo-700 dark:text-indigo-400 absolute right-4 top-0 bottom-0 my-auto" />
                                            ) : (notif.type === 'comment' || notif.type === 'reply') ? (
                                                <CommentOutlined className="text-2xl text-indigo-700 dark:text-indigo-400 absolute right-4 top-0 bottom-0 my-auto" />
                                            ) : (
                                                <UserAddOutlined className="text-2xl text-indigo-700 dark:text-indigo-400 absolute right-4 top-0 bottom-0 my-auto" />
                                            )}
                                        </div>
                                    </div>
                                </CSSTransition>
                            ))}
                        </div>
                    </TransitionGroup>
                </div>
            )}
        </div>
    );
}
Example #26
Source File: AuthTab.tsx    From flood with GNU General Public License v3.0 4 votes vote down vote up
AuthTab: FC = observer(() => {
  const formRef = useRef<Form>(null);
  const clientConnectionSettingsRef = useRef<ClientConnectionSettings | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [isUserListFetched, setIsUserListFetched] = useState<boolean>(false);
  const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
  const {i18n} = useLingui();

  useEffect(() => {
    if (AuthStore.currentUser.isAdmin) {
      AuthActions.fetchUsers().then(() => {
        setIsUserListFetched(true);
      });
    }
  }, []);

  if (!AuthStore.currentUser.isAdmin) {
    return (
      <Form>
        <ModalFormSectionHeader>
          <Trans id="auth.user.accounts" />
        </ModalFormSectionHeader>
        <FormRow>
          <FormError>
            <Trans id="auth.message.not.admin" />
          </FormError>
        </FormRow>
      </Form>
    );
  }

  const isLoading = !isUserListFetched && AuthStore.users.length === 0;
  const interactiveListClasses = classnames('interactive-list', {
    'interactive-list--loading': isLoading,
  });

  return (
    <Form
      onSubmit={() => {
        if (formRef.current == null || clientConnectionSettingsRef.current == null) {
          return;
        }

        const formData = formRef.current.getFormData() as Partial<AuthTabFormData>;

        if (formData.username == null || formData.username === '') {
          setError('auth.error.username.empty');
        } else if (formData.password == null || formData.password === '') {
          setError('auth.error.password.empty');
        } else {
          setIsSubmitting(true);

          const connectionSettings = clientConnectionSettingsRef.current;
          if (connectionSettings == null) {
            setError('connection.settings.error.empty');
            setIsSubmitting(false);
            return;
          }

          AuthActions.createUser({
            username: formData.username,
            password: formData.password,
            client: connectionSettings,
            level: formData.isAdmin === true ? AccessLevel.ADMINISTRATOR : AccessLevel.USER,
          })
            .then(
              () => {
                if (formRef.current != null) {
                  formRef.current.resetForm();
                }
                setError(null);
                setIsSubmitting(false);
              },
              () => {
                setError('general.error.unknown');
                setIsSubmitting(false);
              },
            )
            .then(AuthActions.fetchUsers);
        }
      }}
      ref={formRef}
    >
      <ModalFormSectionHeader>
        <Trans id="auth.user.accounts" />
      </ModalFormSectionHeader>
      <FormRow>
        <FormRowItem>
          <ul className={interactiveListClasses}>
            <TransitionGroup>
              {isLoading && (
                <CSSTransition classNames="interactive-list__loading-indicator" timeout={{enter: 250, exit: 250}}>
                  <div className="interactive-list__loading-indicator" key="loading-indicator">
                    <LoadingRing />
                  </div>
                </CSSTransition>
              )}
            </TransitionGroup>
            {AuthStore.users
              .slice()
              .sort((a: Credentials, b: Credentials) => a.username.localeCompare(b.username))
              .map((user: Credentials) => {
                const isCurrentUser = user.username === AuthStore.currentUser.username;
                let badge = null;
                let removeIcon = null;

                if (!isCurrentUser) {
                  removeIcon = (
                    <button
                      className="interactive-list__icon interactive-list__icon--action interactive-list__icon--action--warning"
                      type="button"
                      onClick={() => AuthActions.deleteUser(user.username).then(AuthActions.fetchUsers)}
                    >
                      <Close />
                    </button>
                  );
                } else {
                  badge = (
                    <span className="interactive-list__label__tag tag">
                      <Trans id="auth.current.user" />
                    </span>
                  );
                }

                const classes = classnames('interactive-list__item', {
                  'interactive-list__item--disabled': isCurrentUser,
                });

                return (
                  <li className={classes} key={user.username}>
                    <span className="interactive-list__label">
                      <div className="interactive-list__label__text">{user.username}</div>
                      {badge}
                    </span>
                    {removeIcon}
                  </li>
                );
              })}
          </ul>
        </FormRowItem>
      </FormRow>
      <ModalFormSectionHeader>
        <Trans id="auth.add.user" />
      </ModalFormSectionHeader>
      {error && (
        <FormRow>
          <FormError>{i18n._(error)}</FormError>
        </FormRow>
      )}
      <FormRow>
        <Textbox
          id="username"
          label={<Trans id="auth.username" />}
          placeholder={i18n._('auth.username')}
          autoComplete="username"
        />
        <Textbox
          id="password"
          label={<Trans id="auth.password" />}
          placeholder={i18n._('auth.password')}
          autoComplete="new-password"
        />
        <Checkbox grow={false} id="isAdmin" labelOffset matchTextboxHeight>
          <Trans id="auth.admin" />
        </Checkbox>
      </FormRow>
      <ClientConnectionSettingsForm
        onSettingsChange={(settings) => {
          clientConnectionSettingsRef.current = settings;
        }}
      />
      <p />
      <FormRow justify="end">
        <Button isLoading={isSubmitting} priority="primary" type="submit">
          <Trans id="button.add" />
        </Button>
      </FormRow>
    </Form>
  );
})
Example #27
Source File: Header.tsx    From next-shopping-cart with MIT License 4 votes vote down vote up
Header = ({ handleSearch, resetSearch, searchValue }: Props) => {
  const [showCart, flipState] = useState<boolean>(false);
  const [mobileSearch, setSearch] = useState<boolean>(false);

  const {
    cart,
    totalItems,
    totalAmount,
    removeProduct,
    bounce,
    bouceEnd,
  } = useContext<Init>(CartContext);

  const cartPreview = useRef<HTMLDivElement>();

  useEffect(() => {
    if (bounce) {
      const timer1 = setTimeout(() => bouceEnd(), 1000);
      // this will clear Timeout when component unmount like in willComponentUnmount
      return () => {
        clearTimeout(timer1);
      };
    }
  }, [bounce, bouceEnd]);

  const handleCart = () => {
    flipState((prevCart) => !prevCart);
  };

  const handleSubmit = (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault();
  };

  const handleMobileSearch = () => {
    setSearch(true);
  };

  const handleSearchNav = () => {
    setSearch(false);
    resetSearch();
  };

  useClickOutside(() => flipState(false), cartPreview, true);

  const cartItems = cart.map((product: ProductLocal) => {
    return (
      <CSSTransition
        key={product.id}
        classNames="fadeIn"
        timeout={{
          enter: 500,
          exit: 300,
        }}
      >
        <li className={styles.cartItem} key={product.name}>
          <img
            className={styles.productImage}
            src={product.image}
            alt="product"
          />
          <div className={styles.productInfo}>
            <p className={styles.productName}>{product.name}</p>
            <p className={styles.productPrice}>{product.price}</p>
          </div>
          <div className={styles.productTotal}>
            <p className={styles.quantity}>
              {product.quantity} {product.quantity > 1 ? "Nos." : "No."}{" "}
            </p>
            <p className={styles.amount}>{product.quantity * product.price}</p>
          </div>
          <button
            className={styles.productRemove}
            onClick={() => removeProduct(product.id)}
            type="button"
          >
            ×
          </button>
        </li>
      </CSSTransition>
    );
  });
  let view;
  if (cartItems.length <= 0) {
    view = <EmptyCart />;
  } else {
    view = (
      <TransitionGroup component="ul" className={styles.cartItems}>
        {cartItems}
      </TransitionGroup>
    );
  }

  let cartBox = null;

  if (showCart)
    cartBox = (
      <div
        className={`${styles.cartPreview} ${styles.active}`}
        ref={cartPreview}
      >
        <CartScrollBar>{view}</CartScrollBar>
        <div className={styles.actionBlock}>
          <button
            type="button"
            className={cart.length > 0 ? " " : styles.disabled}
          >
            PROCEED TO CHECKOUT
          </button>
        </div>
      </div>
    );

  return (
    <header className={styles.header}>
      <div className={styles.container}>
        <div className={styles.brand}>
          <img
            className={styles.logo}
            src="https://res.cloudinary.com/sivadass/image/upload/v1493547373/dummy-logo/Veggy.png"
            alt="Veggy Brand Logo"
          />
        </div>

        <div className={styles.search}>
          <button
            className={styles.mobileSearch}
            onClick={handleMobileSearch}
            type="button"
          >
            <img
              src="https://res.cloudinary.com/sivadass/image/upload/v1494756966/icons/search-green.png"
              alt="search"
            />
          </button>
          <form
            action="#"
            method="get"
            className={
              mobileSearch
                ? `${styles.searchForm} ${styles.active}`
                : `${styles.searchForm}`
            }
          >
            <button
              className={styles.backButton}
              onClick={handleSearchNav}
              type="button"
            >
              <img
                src="https://res.cloudinary.com/sivadass/image/upload/v1494756030/icons/back.png"
                alt="back"
              />
            </button>
            <input
              type="search"
              placeholder="Search for Vegetables and Fruits"
              className={styles.searchKeyword}
              value={searchValue}
              onChange={handleSearch}
            />
            <button
              className={styles.searchButton}
              type="submit"
              onClick={handleSubmit}
              aria-label="submit"
            />
          </form>
        </div>

        <div className={styles.cart}>
          <div className={styles.cartInfo}>
            <table>
              <tbody>
                <tr>
                  <td>No. of items</td>
                  <td>:</td>
                  <td>
                    <strong>{totalItems}</strong>
                  </td>
                </tr>
                <tr>
                  <td>Sub Total</td>
                  <td>:</td>
                  <td>
                    <strong>{totalAmount}</strong>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
          <button
            className={styles.cartIcon}
            onClick={handleCart}
            type="button"
          >
            <img
              className={bounce ? styles.tada : " "}
              src="https://res.cloudinary.com/sivadass/image/upload/v1493548928/icons/bag.png"
              alt="Cart"
            />
            {totalItems ? (
              <span className={styles.cartCount}>{totalItems}</span>
            ) : (
              ""
            )}
          </button>
          {cartBox}
        </div>
      </div>
    </header>
  );
}
Example #28
Source File: Alert.tsx    From gear-js with GNU General Public License v3.0 4 votes vote down vote up
AlertProvider = ({ children, template: Template, containerClassName }: Props) => {
  const root = useRef<HTMLDivElement | null>(null);

  const timers = useRef<AlertTimer>(new Map());
  const [alerts, setAlerts] = useState<AlertInstance[]>([]);

  const removeTimer = useCallback((alertId: string) => {
    const timerId = timers.current.get(alertId);

    if (timerId) {
      clearTimeout(timerId);
      timers.current.delete(alertId);
    }
  }, []);

  const remove = useCallback(
    (alertId: string) => {
      removeTimer(alertId);
      setAlerts((prevState) => prevState.filter((alert) => alert.id !== alertId));
    },
    [removeTimer],
  );

  const createTimer = useCallback(
    (alertId: string, timeout = 0) => {
      if (timeout > 0) {
        const timerId = setTimeout(() => remove(alertId), timeout);

        timers.current.set(alertId, timerId);
      }
    },
    [remove],
  );

  const show = useCallback(
    (content: ReactNode, options: AlertOptions): string => {
      const id = nanoid(6);

      createTimer(id, options.timeout);
      setAlerts((prevState) => [
        ...prevState,
        {
          id,
          content,
          options,
        },
      ]);

      return id;
    },
    [createTimer],
  );

  const update = useCallback(
    (alertId: string, content: ReactNode, options?: AlertOptions) => {
      removeTimer(alertId);

      setAlerts((prevState) =>
        prevState.map((alert) => {
          if (alert.id !== alertId) return alert;

          const updatedAlert = {
            id: alert.id,
            content,
            options: {
              ...alert.options,
              ...options,
            },
          };

          createTimer(updatedAlert.id, updatedAlert.options.timeout);

          return updatedAlert;
        }),
      );
    },
    [removeTimer, createTimer],
  );

  const getAlertTemplate = useCallback(
    (templateOptions: AlertOptions) => (content: ReactNode, options?: TemplateAlertOptions) =>
      show(content, {
        ...templateOptions,
        ...options,
      }),
    [show],
  );

  const alertContext = useMemo(
    () => ({
      update,
      remove,
      info: getAlertTemplate(DEFAULT_INFO_OPTIONS),
      error: getAlertTemplate(DEFAULT_ERROR_OPTIONS),
      success: getAlertTemplate(DEFAULT_SUCCESS_OPTIONS),
      loading: getAlertTemplate(DEFAULT_LOADING_OPTIONS),
    }),
    [update, remove, getAlertTemplate],
  );

  useEffect(() => {
    root.current = document.createElement('div');
    root.current.id = 'alert-root';
    containerClassName && root.current.classList.add(containerClassName);
    document.body.appendChild(root.current);
  }, []);

  return (
    <>
      <AlertContext.Provider value={alertContext}>{children}</AlertContext.Provider>
      {root.current &&
        createPortal(
          <TransitionGroup appear>
            {alerts.map((alert) => (
              <Transition key={alert.id}>
                <Template alert={alert} close={() => remove(alert.id)} />
              </Transition>
            ))}
          </TransitionGroup>,
          root.current,
        )}
    </>
  );
}
Example #29
Source File: enterprise.tsx    From questdb.io with Apache License 2.0 4 votes vote down vote up
Enterprise = () => {
  const title = "QuestDB Enterprise"
  const description =
    "The fastest open source time-series database for organizations, on premise or on the cloud."

  const { ref, width } = useResizeObserver<HTMLDivElement>()
  // An "item" is a quote
  // Index in the array of quotes of the item that is "focused"
  const [index, setIndex] = useState(0)
  // How many items we can show on the screen
  const viewportSize = Math.max(1, Math.floor((width ?? 0) / QUOTE_WIDTH))
  // How many items will actually be displayed (can be smaller than viewportSize)
  const viewportCount =
    viewportSize === 0 ? 0 : Math.ceil(quotes.length / viewportSize)
  // Page number
  const page = Math.floor(index / viewportSize)
  // The quotes to show
  const viewportQuotes = quotes.slice(
    page * viewportSize,
    (page + 1) * viewportSize,
  )
  const increaseIndex = useCallback(() => {
    setIndex((index) => Math.min(index + viewportSize, quotes.length - 1))
  }, [viewportSize])
  const decreaseIndex = useCallback(() => {
    setIndex((index) => Math.max(index - viewportSize, 0))
  }, [viewportSize])

  return (
    <Layout canonical="/enterprise" description={description} title={title}>
      <section className={seCss["section--inner"]}>
        <div className={seCss.section__header}>
          <h1
            className={clsx(
              seCss.section__title,
              seCss["section__title--accent"],
            )}
          >
            QuestDB Enterprise
          </h1>

          <p
            className={clsx(
              seCss.section__subtitle,
              seCss["section__subtitle--accent"],
              "text--center",
            )}
          >
            The fastest open source time-series database for organizations, on
            premise or on the cloud.
          </p>

          <Subscribe
            placeholder="Work Email"
            submitButtonText="Contact Us"
            provider="enterprise"
            className={style.subscribe}
          />

          <img
            alt="Artistic view of the console with sub-queries"
            className={ilCss.illustration}
            height={394}
            src="/img/pages/enterprise/banner.svg"
            width={900}
          />

          <p
            className={clsx(
              seCss.section__subtitle,
              seCss["section__subtitle--accent"],
              "text--center",
            )}
          >
            QuestDB Enterprise is the best way to run QuestDB on your own
            infrastructure at any scale. The software can be deployed on-premise
            or in the cloud on AWS, GCP or Azure.
          </p>

          <div className={clsx(clCss.cloud)}>
            <SvgImage
              image={<AwsLogo width="73" height="44" />}
              title="AWS logo"
            />

            <SvgImage
              image={<GoogleCloudLogo width="219" height="34" />}
              title="Google Cloud logo"
            />

            <SvgImage
              image={<AzureLogo width="116" height="34" />}
              title="Google Cloud logo"
            />
          </div>
        </div>
      </section>

      <section className={seCss["section--flex-wrap"]}>
        <div className={caCss.card}>
          <div className={caCss.card__image}>
            <img
              alt="Chat icon"
              height={52}
              src="/img/pages/enterprise/chat.svg"
              width={62}
            />
          </div>
          <h2 className={caCss.card__title}>Support</h2>
          <ul className={caCss.card__list}>
            <li className={clsx(prCss.property, caCss.card__item)}>
              Enterprise support: 24x7 technical support from high-quality
              engineers via phone, chat, and email
            </li>
            <li className={clsx(prCss.property, caCss.card__item)}>
              On demand training
            </li>
          </ul>
        </div>

        <div className={caCss.card}>
          <div className={caCss.card__image}>
            <img
              alt="Lock icon"
              height={58}
              src="/img/pages/enterprise/lock.svg"
              width={42}
            />
          </div>
          <h2 className={caCss.card__title}>Security and authentication</h2>
          <ul className={caCss.card__list}>
            <li className={clsx(prCss.property, caCss.card__item)}>
              Advanced security
            </li>
            <li className={clsx(prCss.property, caCss.card__item)}>
              Access control
            </li>
            <li className={clsx(prCss.property, caCss.card__item)}>
              Authentication
            </li>
          </ul>
        </div>

        <div className={caCss.card}>
          <div className={caCss.card__image}>
            <img
              alt="Cog icon"
              height={48}
              src="/img/pages/enterprise/cog.svg"
              width={45}
            />
          </div>
          <h2 className={caCss.card__title}>Management</h2>
          <ul className={caCss.card__list}>
            <li className={clsx(prCss.property, caCss.card__item)}>
              Automation
            </li>
            <li className={clsx(prCss.property, caCss.card__item)}>
              Database monitoring
            </li>
            <li className={clsx(prCss.property, caCss.card__item)}>
              Analytics and visualization
            </li>
          </ul>
        </div>

        <div className={caCss.card}>
          <div className={caCss.card__image}>
            <img
              alt="Rocket icon"
              height={56}
              src="/img/pages/enterprise/rocket.svg"
              width={56}
            />
          </div>
          <h2 className={caCss.card__title}>Unlimited scale</h2>
          <ul className={caCss.card__list}>
            <li className={clsx(prCss.property, caCss.card__item)}>
              High throughput replication
            </li>
            <li className={clsx(prCss.property, caCss.card__item)}>
              Horizontal scalability (high-performance clusters, sharding)
            </li>
          </ul>
        </div>
      </section>

      <section className={seCss["section--inner"]}>
        <div className={peCss.performance__left}>
          <h2 className={peCss.performance__title}>Superior performance</h2>
          <p className={peCss.performance__item}>
            <span className={peCss.performance__bullet} />
            Fast ingestion - O(1) complexity, heavy parallelization, out of
            order inserts
          </p>
          <p
            className={clsx(
              peCss.performance__item,
              peCss["performance__item--important"],
            )}
          >
            Downsize your instance, reduce hardware costs
          </p>
          <p className={peCss.performance__item}>
            <span className={peCss.performance__bullet} />
            SIMD accelerated SQL queries for lightning fast data retrieval
          </p>
          <p
            className={clsx(
              peCss.performance__item,
              peCss["performance__item--important"],
            )}
          >
            Real-time analytics, correlate events over time
          </p>
          <Button className={peCss.performance__cta} href="/case-study/toggle/">
            View case study
          </Button>
        </div>
        <div className={peCss.performance__right}>
          <img
            alt="Charts showing the performance improvments when using QuestDB"
            height={411}
            src="/img/pages/enterprise/performance.svg"
            width={661}
          />
        </div>
      </section>

      <section
        className={clsx(seCss["section--inner"], seCss["section--column"])}
      >
        <h2 className={quCss.title}>The word on QuestDB</h2>

        <div className={quCss.carousel} ref={ref}>
          <TransitionGroup component={null}>
            <CSSTransition key={page} timeout={200} classNames="item">
              <div className={quCss.carousel__group}>
                {viewportQuotes.map((Quote) => (
                  <Quote key={quotes.indexOf(Quote)} />
                ))}
              </div>
            </CSSTransition>
          </TransitionGroup>
        </div>

        <div className={quCss.controls}>
          <div
            className={clsx(
              quCss["controls__chevron-wrapper"],
              quCss["controls__chevron-wrapper--left"],
              {
                [quCss["controls__chevron-wrapper--hidden"]]: page === 0,
              },
            )}
            onClick={decreaseIndex}
          >
            <Chevron className={quCss.controls__chevron} side="left" />
          </div>

          <div className={quCss.controls__middle}>
            {Array(viewportCount)
              .fill(0)
              .map((_, idx) => (
                <Bullet
                  index={idx}
                  key={idx}
                  onClick={setIndex}
                  page={page}
                  viewportSize={viewportSize}
                />
              ))}
          </div>

          <div
            className={clsx(
              quCss["controls__chevron-wrapper"],
              quCss["controls__chevron-wrapper--right"],
              {
                [quCss["controls__chevron-wrapper--hidden"]]:
                  page === viewportCount - 1,
              },
            )}
            onClick={increaseIndex}
          >
            <Chevron className={quCss.controls__chevron} side="right" />
          </div>
        </div>
      </section>
    </Layout>
  )
}