react-intersection-observer#useInView JavaScript Examples

The following examples show how to use react-intersection-observer#useInView. 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: 10-react-apps-series-b.js    From make-react-apps-site with MIT License 6 votes vote down vote up
export default function ReactApps() {
  const [topRef, topInView] = useInView()
  const showFloatingButton = !topInView

  return (
    <>
      <SEO title="Make 10 React Apps - Practical React Learning" />

      <FloatingButton
        isShowing={showFloatingButton}
        url="/10-react-apps-series-b#pricing"
      />

      <div ref={topRef}>
        <AppsHero whichCourse="b" />
      </div>

      <WhatWellBuild whichCourse="b" />
      <Pricing whichCourse="b" />
      <LessonList whichCourse="b" />
      <WhoAmI />
      <FAQ />
      {/* <Bundle /> */}
      <Footer />
    </>
  )
}
Example #2
Source File: HomeContent.js    From ar-episode2 with MIT License 6 votes vote down vote up
HomeContent = () => {
  const animation = useAnimation()
  const [contentRef, inView] = useInView({
    triggerOnce: true,
    rootMargin: "-300px",
  })

  useEffect(() => {
    if (inView) {
      animation.start("visible")
    }
  }, [animation, inView])

  return (
    <HomeContentSection
      ref={contentRef}
      animate={animation}
      initial="hidden"
      variants={{
        visible: {
          opacity: 1,
          y: 0,
          transition: { duration: 0.8, ease: [0.6, 0.05, -0.01, 0.9] },
        },
        hidden: { opacity: 0, y: 72 },
      }}
    >
      <Container>
        <Content>
          Great stories don’t just happen— <br /> they need to be uncovered. And
          we dig deep to discover the great stories that lie just below the
          surface. Dirt under our fingernails and all.
        </Content>
      </Container>
    </HomeContentSection>
  )
}
Example #3
Source File: Span.js    From mui-storyblok with MIT License 6 votes vote down vote up
Span = ({
  align,
  color,
  height,
  width,
  rootClass,
  content,
  variant,
  key,
  storyblokClass,
  dataBlokC,
  dataBlokUid,
  transitionClass,
}) => {
  const { ref, inView } = useInView({ threshold: 0, triggerOnce: true });
  const defaultStyles = {
    height,
    width,
  };
  const styles = Storyblok.arrayToMuiStyles(rootClass, defaultStyles);

  return (
    <MuiTypography
      component="span"
      key={key}
      className={`${styles.root} ${storyblokClass} ${inView && transitionClass}`}
      variant={variant}
      align={align}
      color={color}
      data-blok-c={dataBlokC}
      data-blok-uid={dataBlokUid}
      inView={inView}
      ref={ref}
      style={{ opacity: inView ? 1 : 0 }}
    >
      {` ${content} `}
    </MuiTypography>
  );
}
Example #4
Source File: DecentralisedWebsites.js    From ensdomains-v2 with MIT License 6 votes vote down vote up
export default function DecentralisedWebsites(props) {
  const { t } = useTranslation()
  const [ref, inView] = useInView({
    /* Optional options */
    threshold: 0,
    rootMargin: "-200px",
  })

  return (
    <Container id="home-decentralised-websites">
      <AnchorContainer href={"#home-decentralised-websites"}>
        <H2>
          {t("home.decentralisedWebsites.title")}
          <Anchor />
        </H2>
      </AnchorContainer>

      <P>{t("home.decentralisedWebsites.text")}</P>
      <ImageAnimation ref={ref}>
        <ImageTransition>
          <motion.img src={website2} style={{ opacity: inView ? 0 : 1 }} />
          <motion.img src={blur1} style={{ opacity: inView ? 1 : 0 }} />
        </ImageTransition>
        <ImageTransition>
          <img src={website1} alt={t("website")} />
        </ImageTransition>
        <ImageTransition>
          <motion.img src={website3} animate={{ opacity: inView ? 0 : 1 }} />
          <motion.img src={blur2} animate={{ opacity: inView ? 1 : 0 }} />
        </ImageTransition>
      </ImageAnimation>
      <Button href="https://medium.com/the-ethereum-name-service/cloudflare-and-fleek-make-ens-ipfs-site-deployment-as-easy-as-ever-262c990a7514">
        {t("c.learnMore")}
      </Button>
    </Container>
  )
}
Example #5
Source File: StickyMobileSection.jsx    From covince with MIT License 6 votes vote down vote up
StickyMobileSection = props => {
  const options = useMemo(() => ({
    threshold: [1]
  }), [])
  const { ref, inView } = useInView(options)
  return (
    <>
      <div
        className={classNames(
          props.className,
          'sticky bottom-0 bg-white dark:bg-gray-700 rounded-t-lg transition-colors',
          'border-solid border-0 border-t border-transparent',
          { 'shadow-2xl dark:bg-gray-600 border-gray-200 dark:border-gray-400 z-30': !inView }
        )}
      >
        {props.children}
      </div>
      {/* "sentinel" element - https://developers.google.com/web/updates/2017/09/sticky-headers */}
      <div className='h-0' ref={ref} />
    </>
  )
}
Example #6
Source File: index.js    From make-react-apps-site with MIT License 6 votes vote down vote up
export default function ReactApps() {
  const [topRef, topInView] = useInView()
  const showFloatingButton = !topInView

  return (
    <>
      <SEO title="Make 10 React Apps - Practical React Learning" />

      <FloatingButton
        isShowing={showFloatingButton}
        url="/#pricing"
      />

      <div ref={topRef}>
        <AppsHero />
      </div>

      <WhatWellBuild />
      <Pricing />
      <LessonList />
      <WhoAmI />
      <FAQ />
      {/* <Bundle /> */}
      <Footer />
    </>
  )
}
Example #7
Source File: StickyActionButton.jsx    From covince with MIT License 6 votes vote down vote up
StickyActionButton = props => {
  const options = useMemo(() => ({
    threshold: [1]
  }), [])
  const { ref, inView } = useInView(options)
  return (
    <>
      <PillButton
        className={classNames('sticky z-30 bottom-6 mx-auto mt-6 transition-shadow', { 'shadow-xl': !inView })}
        onClick={props.onClick}
      >
        {props.children}
      </PillButton>
      {/* "sentinel" element - https://developers.google.com/web/updates/2017/09/sticky-headers */}
      <div className='h-6' ref={ref} />
    </>
  )
}
Example #8
Source File: homeAbout.js    From ar-episode2 with MIT License 5 votes vote down vote up
HomeAbout = ({ onCursor }) => {
  //Default state, using number for our id. Which ever the number/id is in the state. That will be opened.
  const [expanded, setExpanded] = useState(0)
  const animation = useAnimation()
  const [aboutRef, inView] = useInView({
    triggerOnce: true,
    // Giving our scrollwheel additional 300px before executing animation
    rootMargin: "-300px",
  })

  useEffect(() => {
    if (inView) {
      animation.start("visible")
    }
  }, [animation, inView])

  return (
    <HomeAboutSection
      ref={aboutRef}
      animate={animation}
      initial="hidden"
      variants={{
        visible: {
          opacity: 1,
          y: 0,
          transition: { duration: 0.8, ease: [0.6, 0.05, -0.01, 0.9] },
        },
        hidden: { opacity: 0, y: 72 },
      }}
    >
      <Container>
        <Flex alignTop>
          <About>
            <h2>
              Furrow is an integrated, full-service creative studio offering
              video production, creative development, and post-production
              services.
            </h2>
            <p>
              Everybody’s got a story. And we don’t stop until we’ve uncovered
              what makes yours worth telling. Whether it’s working directly with
              you, an agency partner, or putting the finishing touches on
              something special, we’re ready to dig in and get our hands
              dirty—are you?
            </p>
          </About>
          <Services>
            <h3>Services</h3>
            {accordionIds.map((details, index) => (
              <Accordion
                key={index}
                details={details}
                expanded={expanded}
                setExpanded={setExpanded}
                onCursor={onCursor}
              />
            ))}
          </Services>
        </Flex>
      </Container>
    </HomeAboutSection>
  )
}
Example #9
Source File: DetachedTextBlockEditor.jsx    From volto-slate with MIT License 5 votes vote down vote up
DetachedTextBlockEditor = (props) => {
  const {
    data,
    index,
    properties,
    onSelectBlock,
    onChangeBlock,
    block,
    selected,
    formTitle,
    formDescription,
  } = props;
  const { value } = data;

  const intl = useIntl();
  const placeholder =
    data.placeholder || formTitle || intl.formatMessage(messages.text);
  let instructions = data?.instructions?.data || data?.instructions;
  if (!instructions || instructions === '<p><br/></p>') {
    instructions = formDescription;
  }

  const { ref, inView } = useInView({
    threshold: 0,
    rootMargin: '0px 0px 200px 0px',
  });

  return (
    <div className="text-slate-editor-inner detached-slate-editor" ref={ref}>
      <SlateEditor
        index={index}
        readOnly={!inView}
        properties={properties}
        renderExtensions={[]}
        value={value}
        block={block /* is this needed? */}
        debug={DEBUG}
        onFocus={() => {
          if (!selected) {
            onSelectBlock(block);
          }
        }}
        onChange={(value, selection, editor) => {
          onChangeBlock(block, {
            ...data,
            value,
            plaintext: serializeNodesToText(value || []),
            // TODO: also add html serialized value
          });
        }}
        selected={selected}
        placeholder={placeholder}
        onKeyDown={handleKeyDetached}
      />
    </div>
  );
}
Example #10
Source File: Hero.js    From halo-lab with MIT License 5 votes vote down vote up
Hero = ({ animation }) => {
  // eslint-disable-next-line no-unused-vars
  const { quotes, ...infoAssets } = useHomeHeroAssets();
  const { ref, inView } = useInView({
    threshold: 0,
  });
  return (
    <section ref={ref} className={styles.container}>
      <div className={styles.wrapper}>
        <div className={styles.title}>
          <h1 className={styles.titleText}>
            Design-driven development of your web product for years
          </h1>
          <Title className={styles.titleSvg} />
        </div>
        <animated.div
          style={{
            transform: inView ? animation.xy.interpolate(trans1) : 'none',
          }}
          className={`${styles.circleWrapper} ${styles.circleSm1Pos}`}
        >
          <div className={`${styles.circle} ${styles.circleSm1}`} />
        </animated.div>
        <animated.div
          style={{
            transform: inView ? animation.xy.interpolate(trans2) : 'none',
          }}
          className={`${styles.circleWrapper} ${styles.circleSm2Pos}`}
        >
          <div className={`${styles.circle} ${styles.circleSm2}`} />
        </animated.div>
        <animated.div
          style={{
            transform: inView ? animation.xy.interpolate(trans3) : 'none',
          }}
          className={`${styles.circleWrapper} ${styles.circleMdPos}`}
        >
          <div className={`${styles.circle} ${styles.circleMd}`} />
        </animated.div>
      </div>

      <Info {...infoAssets} />
    </section>
  );
}
Example #11
Source File: IndexHeroFeaturedIn.jsx    From pooltogether-landing-site with MIT License 5 votes vote down vote up
IndexHeroFeaturedIn = () => {
  const controls = useAnimation()
  const [ref, inView] = useInView()

  useEffect(() => {
    if (inView) {
      controls.start('visible')
    }
  }, [controls, inView])

  const containerVariants = {
    visible: {
      transition: {
        staggerChildren: 0.12
      }
    },
    hidden: {}
  }

  return (
    <div id='featured-in' className='text-center pt-10'>
      <div className='pool-container mx-auto'>
        <h5 className='my-0 sm:mt-4 leading-tight'>Featured in:</h5>

        <motion.div
          className={classnames(
            'flex flex-col xs:flex-row xs:flex-wrap justify-start items-start',
            'mt-2 mb-4 px-4 xs:px-8 rounded-xl -mx-4 sm:-mx-12 lg:-mx-16'
          )}
          ref={ref}
          animate={controls}
          initial='hidden'
          variants={containerVariants}
        >
          <GridItemSupportedBy
            altBg
            title={'Binance Academy'}
            img={BinanceAcademySvg}
            url='https://academy.binance.com/en/articles/how-pool-together-turns-saving-money-into-a-game'
          />

          <GridItemSupportedBy
            altBg
            title={'Zapper'}
            img={ZapperFiSvg}
            url='https://learn.zapper.fi/articles/how-to-tranfer-eth-from-coinbase-to-defi'
          />

          <GridItemSupportedBy
            altBg
            title={'Ethereum.org'}
            img={EthereumPng}
            url='https://ethereum.org/en/dapps/'
          />

          <GridItemSupportedBy
            altBg
            title={'Bankless'}
            img={BanklessPng}
            url='https://shows.banklesshq.com/p/early-access-meet-the-nation-pooltogether'
          />

          <GridItemSupportedBy
            altBg
            title={'CoinDesk'}
            img={CoinDeskPng}
            url='https://www.coindesk.com/tag/pooltogether'
          />
        </motion.div>
      </div>
    </div>
  )
}
Example #12
Source File: GridItem.js    From mui-storyblok with MIT License 4 votes vote down vote up
GridItem = ({
  components,
  alignContent,
  alignItems,
  rootClass,
  direction,
  justify,
  lg,
  md,
  sm,
  wrap,
  spacing,
  xs,
  xl,
  content,
  dataBlokC,
  dataBlokUid,
  storyblokClass,
  transition,
  only,
  backgroundImageUrl,
}) => {
  let heroClass;
  const { ref, inView } = useInView({ threshold: 0.2, triggerOnce: true });
  const gridClass = window?.Storyblok?.inEditor ? {
    borderStyle: 'solid',
    borderColor: '#3889FF',
    borderWidth: '.1em',
  } : {};


  if (backgroundImageUrl) {
    heroClass = {
      ...{
        backgroundImage: `url(${backgroundImageUrl})`,
        backgroundPosition: 'center',
        backgroundRepeat: 'no-repeat',
        backgroundSize: 'cover',
        position: 'relative',
        padding: 0,
        margin: 0,
      },
    };
  }

  const styles = Storyblok.arrayToMuiStyles(rootClass, { ...heroClass });

  return (
    <Hidden only={only}>
      <Grid
        item
        container
        alignContent={alignContent}
        alignItems={alignItems}
        direction={direction}
        justify={justify}
        wrap={wrap}
        spacing={Number(spacing)}
        xs={sizeGrid(xs)}
        sm={sizeGrid(sm)}
        md={sizeGrid(md)}
        lg={sizeGrid(lg)}
        xl={sizeGrid(xl)}
        data-blok-c={dataBlokC}
        data-blok-uid={dataBlokUid}
        className={`${styles.root} ${storyblokClass} ${inView && transition}`}
        style={{ ...gridClass, opacity: inView ? 1 : 0 }}
        inView={inView}
        ref={ref}
      >
        {!content.length && <Box minHeight={200} width={{ xs: '100%' }} />}
        {content.length > 0
          && content.map((component, key) => (
            <Suspense fallback={<></>} key={key}>
              {renderComponentsWithBridge({ ...components }, {
                ...component,
                components,
                key,
              }, key)}
            </Suspense>
          ))
        }
      </Grid>
    </Hidden>
  );
}
Example #13
Source File: HomeFeatured.js    From ar-episode2 with MIT License 4 votes vote down vote up
HomeFeatured = ({ onCursor }) => {
  const [hovered, setHovered] = useState(false)
  const animation = useAnimation()
  const [featured, inView] = useInView({
    triggerOnce: true,
    rootMargin: "-300px",
  })

  useEffect(() => {
    if (inView) {
      animation.start("visible")
    }
  }, [animation, inView])

  return (
    <HomeFeaturedSection
      ref={featured}
      animate={animation}
      initial="hidden"
      variants={{
        visible: {
          opacity: 1,
          y: 0,
          transition: { duration: 0.8, ease: [0.6, 0.05, -0.01, 0.9] },
        },
        hidden: { opacity: 0, y: 72 },
      }}
    >
      <Container>
        <Link to="/">
          <FeaturedContent
            onHoverStart={() => setHovered(!hovered)}
            onHoverEnd={() => setHovered(!hovered)}
            onMouseEnter={() => onCursor("hovered")}
            onMouseLeave={onCursor}
          >
            <Flex spaceBetween>
              <h3>Featured Project</h3>
              <motion.div
                animate={{ opacity: hovered ? 1 : 0 }}
                transition={{ duration: 0.6, ease: [0.6, 0.05, -0.01, 0.9] }}
                className="meta"
              >
                <h4>PEI Seafood</h4>
                <h4>2019</h4>
              </motion.div>
            </Flex>
            <h2 className="featured-title">
              NOT <br /> HUMBLE
              <span className="arrow">
                <motion.svg
                  animate={{ x: hovered ? 48 : 0 }}
                  transition={{ duration: 0.6, ease: [0.6, 0.05, -0.01, 0.9] }}
                  xmlns="http://www.w3.org/2000/svg"
                  viewBox="0 0 101 57"
                >
                  <path
                    d="M33 34H0V24h81.429L66 7.884 73.548 0l19.877 20.763.027-.029L101 28.618 73.829 57l-7.548-7.884L80.753 34H33z"
                    fill="#FFF"
                    fillRule="evenodd"
                  ></path>
                </motion.svg>
              </span>
            </h2>
          </FeaturedContent>
          <FeaturedVideo>
            <video
              loop
              autoPlay
              src={require("../../assets/video/featured-video.mp4")}
            ></video>
          </FeaturedVideo>
        </Link>
      </Container>
      <Container>
        <FeaturedProjects>
          <Flex flexEnd>
            <button>
              <span>All Projects</span>
            </button>
          </Flex>
        </FeaturedProjects>
      </Container>
    </HomeFeaturedSection>
  )
}
Example #14
Source File: footer.js    From ar-episode2 with MIT License 4 votes vote down vote up
Footer = ({ setHamburgerPosition, onCursor }) => {
  const instagramRef = useRef(null)
  const instagramPosition = useElementPosition(instagramRef)

  const facebookRef = useRef(null)
  const facebookPosition = useElementPosition(facebookRef)

  const vimeoRef = useRef(null)
  const vimeoPosition = useElementPosition(vimeoRef)

  const animation = useAnimation()
  const [footerRef, inView] = useInView({
    triggerOnce: true,
    rootMargin: "-100px",
  })
  useEffect(() => {
    if (inView) {
      animation.start("visible")
    }
  }, [animation, inView])

  const menuHover = x => {
    onCursor("locked")
    setHamburgerPosition({ x: x })
  }

  return (
    <FooterNav
      ref={footerRef}
      animate={animation}
      initial="hidden"
      variants={{
        visible: {
          opacity: 1,
          y: 0,
          transition: { duration: 0.8, ease: [0.6, 0.05, -0.01, 0.9] },
        },
        hidden: { opacity: 0, y: 72 },
      }}
    >
      <Container>
        <Flex spaceBetween>
          <FooterContent>
            <p>902.315.1279</p>
            <p>[email protected]</p>
          </FooterContent>
          <FooterContent wider>
            <p>15 Camburhill Ct Unit C</p>
            <p>Charlottetown, PE C1E 0E2</p>
          </FooterContent>
          <FooterSocial>
            <a
              onMouseEnter={() => menuHover(instagramPosition.x)}
              onMouseLeave={onCursor}
              ref={instagramRef}
              href="/"
              target="_blank"
            >
              <Instagram />
            </a>
            <a
              onMouseEnter={() => menuHover(facebookPosition.x)}
              onMouseLeave={onCursor}
              ref={facebookRef}
              href="/"
              target="_blank"
            >
              <Facebook />
            </a>
            <a
              onMouseEnter={() => menuHover(vimeoPosition.x)}
              onMouseLeave={onCursor}
              ref={vimeoRef}
              href="/"
              target="_blank"
            >
              <Vimeo />
            </a>
          </FooterSocial>
        </Flex>
      </Container>
    </FooterNav>
  )
}
Example #15
Source File: IndexSupportedBy.jsx    From pooltogether-landing-site with MIT License 4 votes vote down vote up
IndexSupportedBy = () => {
  const controls = useAnimation()
  const [ref, inView] = useInView()

  useEffect(() => {
    if (inView) {
      controls.start('visible')
    }
  }, [controls, inView])

  const containerVariants = {
    visible: {
      transition: {
        staggerChildren: 0.12
      }
    },
    hidden: {}
  }

  return (
    <div id='backed-by' className='bg-darkened text-center pt-12 pb-6 sm:pt-20 sm:pb-16'>
      <div className='pool-container mx-auto'>
        <h3 className='text-sm xs:text-lg sm:text-xl my-0 sm:mb-12 leading-tight'>
          Protocol supported by
        </h3>

        <motion.div
          className={classnames(
            'flex flex-col xs:flex-row xs:flex-wrap justify-start items-start',
            'mt-2 mb-4 px-4 xs:px-8 rounded-xl -mx-4 sm:-mx-12 lg:-mx-16'
          )}
          ref={ref}
          animate={controls}
          initial='hidden'
          variants={containerVariants}
        >
          <GridItemSupportedBy
            title={'ConsenSys'}
            img={ConsensysSvg}
            url='https://www.consensys.com'
          />

          <GridItemSupportedBy
            title={'IDEO'}
            img={IdeoSvg}
            url='https://www.ideo.com'
            maxHeight={30}
          />

          <GridItemSupportedBy
            title={'DTC Capital'}
            img={DTCCapitalSvg}
            url='https://www.dtc.capital'
          />

          <GridItemSupportedBy title={'ParaFi'} img={ParaFiSvg} url='https://www.parafi.capital/' />

          <GridItemSupportedBy
            title={'MetaCartel Ventures'}
            img={MetaCartelSvg}
            url='https://metacartel.org'
          />

          <GridItemSupportedBy title={'The LAO'} img={TheLaoSvg} url='https://thelao.io' />

          <GridItemSupportedBy
            title={'Robot Ventures'}
            img={RobotVenturesSvg}
            url='https://twitter.com/robotventures'
          />
        </motion.div>

        {/* <div className='flex justify-center items-center sm:-mx-2'>
        <a
          href='https://makerdao.com'
          title='View the Maker site'
          target='_blank'
          rel='noopener noreferrer'
        >
          <img
            src={MakerDaoSvg}
            className='mt-4'
          />
        </a>
      </div> */}
      </div>
    </div>
  )
}
Example #16
Source File: IndexIntegrations.jsx    From pooltogether-landing-site with MIT License 4 votes vote down vote up
IndexIntegrations = () => {
  const controls = useAnimation()
  const [ref, inView] = useInView()

  useEffect(() => {
    if (inView) {
      controls.start('visible')
    }
  }, [controls, inView])

  const containerVariants = {
    visible: {
      transition: {
        staggerChildren: 0.2
      }
    },
    hidden: {}
  }

  return (
    <>
      <div className='bg-secondary'>
        <div className='pool-container mx-auto pt-12 pb-6 sm:pt-20 sm:pb-16'>
          <div className='lg:px-20'>
            <div className='flex items-center justify-between'>
              <h1 className='leading-10 sm:leading-tight'>
                <div className='text-flashy'>Protocol</div>{' '}
                <div className='block -mt-2'>Integrations</div>
              </h1>
            </div>

            <div className='flex flex-col py-4'>
              <p className='text-sm xs:text-xl sm:text-xl lg:text-3xl lg:max-w-3xl'>
                Try community-built interfaces and get inspired by what you can build on the
                PoolTogether protocol.
              </p>

              <motion.div
                className={classnames(
                  'flex flex-col sm:flex-row sm:flex-wrap',
                  'mt-8 mb-4 rounded-xl text-base lg:text-lg -mx-4 sm:-mx-4 lg:-mx-8'
                )}
                ref={ref}
                animate={controls}
                initial='hidden'
                variants={containerVariants}
              >
                {/* <GridItem
                title={'Argent'}
                description={`Use the Argent app to join the pool.`}
                img={ArgentSvg}
                url='https://www.argent.xyz/'
              /> */}

                <GridItem
                  altBg
                  title={'Dharma'}
                  description={`Deposit into PoolTogether from your US bank.`}
                  img={DharmaSvg}
                  url='https://www.dharma.io/'
                />

                <GridItem
                  altBg
                  title={'ZapperFi'}
                  description={`Join PoolTogether using this portal to DeFi.`}
                  img={ZapperFiSvg}
                  url='https://www.zapper.fi/#/dashboard'
                />

                <GridItem
                  altBg
                  title={'Zerion'}
                  description={`Access DeFi & view your PoolTogether deposits.`}
                  img={ZerionSvg}
                  url='https://zerion.io/'
                />

                <GridItem
                  altBg
                  title={'Twitter Bot'}
                  description={`Updates each time someone joins or wins!`}
                  img={BotSvg}
                  url='https://twitter.com/PoolTogetherBot'
                  attribution={`bot icon by Sophia Bai from the Noun Project`}
                />
                {/* 
              <GridItem
                title={'EBO'}
                description={`EBO Finance is a wallet app for joining the pool.`}
                img={EBOSvg}
                url='https://ebo.io/'
              /> */}
              </motion.div>
            </div>
          </div>

          <div className='bg-card rounded-xl mx-auto text-center p-4 xs:p-12 sm:pt-12 sm:pb-12 sm:mt-10'>
            <div className='flex flex-col items-center'>
              <h2 className='mt-4 mb-8 text-center'>Check out our developer documentation</h2>
              <p className='text-sm xs:text-lg sm:text-xl max-w-lg text-center'>
                Learn about the PoolTogether protocol and emerging use cases
              </p>

              <ButtonLink
                secondary
                textSize='2xl'
                href='https://docs.pooltogether.com'
                as='https://docs.pooltogether.com'
                className='my-8 w-3/4 sm:w-1/2'
              >
                Go to docs
              </ButtonLink>
            </div>
          </div>
        </div>
      </div>
    </>
  )
}
Example #17
Source File: BrandAssetsPage.jsx    From pooltogether-landing-site with MIT License 4 votes vote down vote up
BrandAssetsPage = (props) =>  {
  const controls = useAnimation()
  const [ref, inView] = useInView()

  useEffect(() => {
    if (inView) {
      controls.start('visible')
    }
  }, [controls, inView])

  return <>
    <div
      className='pool-container mx-auto flex flex-col text-base h-full z-10 relative mb-20'
    >
      <h4
        className='my-0'
      >
        Brand Assets
      </h4>

      <h2
        className='mb-6'
      >
        PoolTogether Logos &amp; Usage
      </h2>

      <p>
        Here is a handy zipped-up package of all the latest logos, wordmarks, and symbols to assist you in building PoolTogether integrations or linking to us:
      </p>

      <BoxLinkWithIcon
        isExternal
        href='https://github.com/pooltogether/pooltogether--brand-assets/blob/141936c859553a2a42ac96ed807551b85a4d56d9/pooltogether-brand-assets-v1.2.0.zip?raw=true'
        title='Download brand assets zip package'
        icon={'download'}
      >
        pooltogether-brand-assets-v1.2.0.zip
      </BoxLinkWithIcon>

      <br/>

      <h4
        className='my-5'
      >
        By Style
      </h4>

      <p>
        If you would rather download one or two specific styles you can find them on the <a href='https://github.com/pooltogether/pooltogether--brand-assets'>GitHub brand assets repository</a>.
      </p>

      
      <div className='my-5'>
        <p className='text-base my-0 text-accent-1'>
          Examples:
        </p>

        <motion.div
          className={classnames(
            'flex flex-col xs:flex-row xs:flex-wrap justify-start items-start',
            'mt-2 mb-4 px-4 xs:px-8 rounded-xl -mx-4 lg:-mx-8',
          )}
          ref={ref}
          animate={controls}
          initial='hidden'
          variants={{
            visible: {
              transition: {
                staggerChildren: 0.2
              }
            },
            hidden: {
            },
          }}
        >
          <GridItemBrandAssets
            title={`Purple Wordmark`}
            img={PoolTogetherPurpleWordmarkImg}
          />
          <GridItemBrandAssets
            title={`White Mark`}
            img={PoolTogetherWhiteMarkImg}
          />
          <GridItemBrandAssets
            title={`Trophy`}
            img={PoolTogetherTrophyImg}
          />
        </motion.div>


      </div>
      
      <h4
        className='my-5'
      >Usage</h4>

      <p>
        We would like you to use any of the the assets 'as is'. If you need a modified version of any of the logos feel free to <a
          href='mailto:[email protected]'
        >reach out to us</a> and we'll be happy to help.
      </p>

      <p>
        Please do not use any of the PoolTogether assets as the logo or in your logo for your app or brand. Thanks!
      </p>
    </div>
  </>

}
Example #18
Source File: Grid.js    From mui-storyblok with MIT License 4 votes vote down vote up
Grid = ({
  alignContent,
  alignItems,
  rootClass,
  direction,
  justify,
  wrap,
  spacing,
  content,
  components,
  style,
  dataBlokC,
  dataBlokUid,
  storyblokClass,
  height,
  backgroundImageUrl,
  transition,
  only,
  backgroundColor,
}) => {
  const { ref, inView } = useInView({ threshold: 0, triggerOnce: true });
  const gridClass = window?.Storyblok?.inEditor ? {
    borderStyle: 'solid',
    borderColor: '#FF2020',
    borderWidth: '.1em',
  } : {};
  let gridBackgroundColor = {};

  let heroClass = {
    ...style,
    height,
  };

  if (backgroundImageUrl) {
    heroClass = {
      ...heroClass,
      ...{
        backgroundImage: `url(${backgroundImageUrl})`,
        backgroundPosition: 'center',
        backgroundRepeat: 'no-repeat',
        backgroundSize: 'cover',
        position: 'relative',
        padding: 0,
        margin: 0,
      },
    };
  }

  if (backgroundColor && !backgroundImageUrl) {
    gridBackgroundColor = { backgroundColor: backgroundColor.color };
  }

  const styles = Storyblok.arrayToMuiStyles(rootClass, { ...heroClass, ...gridBackgroundColor });

  return (
    <Hidden only={only}>
      <Box width={{ xs: '100%' }}>
        <MuiGrid
          container
          alignContent={alignContent}
          alignItems={alignItems}
          direction={direction}
          justify={justify}
          wrap={wrap}
          spacing={Number(spacing)}
          data-blok-c={dataBlokC}
          data-blok-uid={dataBlokUid}
          className={`${styles.root} ${storyblokClass} ${inView && transition}`}
          style={{ ...gridClass, opacity: inView ? 1 : 0 }}
          inView={inView}
          ref={ref}
        >
          {!content.length && <Box minHeight={200} width={{ xs: '100%' }} />}
          {content.length > 0 && content.map((component, key) => (
            <Suspense fallback={<Box width={{ xs: '100%' }} key={key} />}>
              {renderComponentsWithBridge({ ...{ GridItem }, ...components }, {
                ...component,
                components,
                key,
              }, key)}
            </Suspense>
          ))}
        </MuiGrid>
      </Box>
    </Hidden>
  );
}
Example #19
Source File: index.jsx    From react-animated-numbers with MIT License 4 votes vote down vote up
AnimatedNumber = ({
  animateToNumber,
  fontStyle,
  configs,
  includeComma,
}) => {
  const { ref, inView } = useInView({ triggerOnce: true });
  const keyCount = React.useRef(0);
  const animteTonumberString = includeComma
    ? Math.abs(animateToNumber).toLocaleString()
    : String(Math.abs(animateToNumber));
  const animateToNumbersArr = Array.from(animteTonumberString, Number).map(
    (x, idx) => (isNaN(x) ? animteTonumberString[idx] : x)
  );

  const [numberHeight, setNumberHeight] = React.useState(0);

  const numberDivRef = React.useRef(null);

  const setConfig = (configs, number, index) => {
    if (typeof configs === "function") {
      return configs(number, index);
    }
    return configs
      ? configs[getRandomIntInclusive(0, configs.length - 1)]
      : undefined;
  };

  React.useEffect(() => {
    const height = numberDivRef.current.getClientRects()?.[0]?.height;
    if (height) {
      setNumberHeight(height);
    }
  }, [animateToNumber, fontStyle]);

  return (
    <>
      {numberHeight !== 0 && (
        <div
          ref={ref}
          style={{ display: "flex", flexDirection: "row" }}
          className="animated-container"
        >
          {inView && animateToNumber < 0 && <div style={fontStyle}>-</div>}
          {inView &&
            animateToNumbersArr.map((n, index) => {
              if (typeof n === "string") {
                return (
                  <div key={index} style={{ ...fontStyle }}>
                    {n}
                  </div>
                );
              }

              return (
                <div
                  key={index}
                  style={{
                    height: numberHeight,
                    overflow: "hidden",
                  }}
                >
                  <Spring
                    key={`${keyCount.current++}`}
                    from={{
                      transform: "translateY(0px)",
                    }}
                    to={{
                      transform: `translateY(${
                        -1 * (numberHeight * animateToNumbersArr[index]) -
                        numberHeight * 20
                      })`,
                    }}
                    config={setConfig(configs, n, index)}
                  >
                    {(props) =>
                      NUMBERS.map((number, i) => (
                        <animated.div
                          key={i}
                          style={{ ...fontStyle,...props }}
                        >
                          {number}
                        </animated.div>
                      ))
                    }
                  </Spring>
                </div>
              );
            })}
        </div>
      )}

      <div
        ref={numberDivRef}
        style={{ position: "absolute", top: -9999, ...fontStyle }}
      >
        {0}
      </div>
    </>
  );
}
Example #20
Source File: GridView.js    From gutenberg-forms with GNU General Public License v2.0 4 votes vote down vote up
GridView = memo(function GridView() {
    const isMounted = useIsMounted()
    const templates = useTemplatesStore((state) => state.templates)
    const appendTemplates = useTemplatesStore((state) => state.appendTemplates)
    const [serverError, setServerError] = useState('')
    const retryOnce = useRef(false)
    const [nothingFound, setNothingFound] = useState(false)
    const [loading, setLoading] = useState(false)
    const [loadMoreRef, inView] = useInView()
    const searchParamsRaw = useTemplatesStore((state) => state.searchParams)
    const currentType = useGlobalStore((state) => state.currentType)
    const resetTemplates = useTemplatesStore((state) => state.resetTemplates)
    const open = useGlobalStore((state) => state.open)
    const taxonomies = useTaxonomyStore((state) => state.taxonomies)
    const updateType = useTemplatesStore((state) => state.updateType)
    const updateTaxonomies = useTemplatesStore(
        (state) => state.updateTaxonomies,
    )

    // Store the next page in case we have pagination
    const nextPage = useRef(useTemplatesStore.getState().nextPage)
    const searchParams = useRef(useTemplatesStore.getState().searchParams)
    const taxonomyType =
        searchParams.current.type === 'pattern' ? 'patternType' : 'layoutType'
    const currentTax = searchParams.current.taxonomies[taxonomyType]
    const defaultOrAlt = useTestGroup('default-or-alt-sitetype', ['A', 'B'])

    // Subscribing to the store will keep these values updates synchronously
    useEffect(() => {
        return useTemplatesStore.subscribe(
            (state) => state.nextPage,
            (n) => (nextPage.current = n),
        )
    }, [])
    useEffect(() => {
        return useTemplatesStore.subscribe(
            (state) => state.searchParams,
            (s) => (searchParams.current = s),
        )
    }, [])

    // Fetch the templates then add them to the current state
    const fetchTemplates = useCallback(() => {
        if (!defaultOrAlt) {
            return
        }
        setServerError('')
        setNothingFound(false)
        const defaultError = __(
            'Unknown error occured. Check browser console or contact support.',
            'extendify',
        )
        const args = { offset: nextPage.current }
        // AB test the default or defaultAlt site type
        const defaultSiteType =
            defaultOrAlt === 'A' ? { slug: 'default' } : { slug: 'defaultAlt' }
        const siteType = searchParams.current.taxonomies?.siteType?.slug?.length
            ? searchParams.current.taxonomies.siteType
            : defaultSiteType
        // End AB test - otherwise use { slug: 'default' } when empty
        const params = cloneDeep(searchParams.current)
        params.taxonomies.siteType = siteType
        TemplatesApi.get(params, args)
            .then((response) => {
                if (!isMounted.current) return
                if (response?.error?.length) {
                    setServerError(response?.error)
                    return
                }
                if (response?.records?.length <= 0) {
                    setNothingFound(true)
                    return
                }
                if (
                    searchParamsRaw === searchParams.current &&
                    response?.records?.length
                ) {
                    useTemplatesStore.setState({
                        nextPage: response?.offset ?? '',
                    })
                    appendTemplates(response.records)
                    setLoading(false)
                }
            })
            .catch((error) => {
                if (!isMounted.current) return
                console.error(error)
                setServerError(defaultError)
            })
    }, [appendTemplates, isMounted, searchParamsRaw, defaultOrAlt])

    useEffect(() => {
        if (templates?.length === 0) {
            setLoading(true)
            return
        }
    }, [templates?.length, searchParamsRaw])

    useEffect(() => {
        // If there's a server error, retry the request
        // This is temporary until we upgrade the bckend and add
        // a tool like react query to handle this automatically
        if (!retryOnce.current && serverError.length) {
            retryOnce.current = true
            fetchTemplates()
        }
    }, [serverError, fetchTemplates])

    useEffect(() => {
        // This will check the URL for a pattern type and set that and remove it
        // TODO: possibly refactor this if we exapnd it to support layouts
        if (!open || !taxonomies?.patternType?.length) return
        const search = new URLSearchParams(window.location.search)
        if (!search.has('ext-patternType')) return
        const term = search.get('ext-patternType')
        // Delete it right away
        search.delete('ext-patternType')
        window.history.replaceState(
            null,
            null,
            window.location.pathname + '?' + search.toString(),
        )
        // Search the slug in patternTypes
        const tax = taxonomies.patternType.find((t) => t.slug === term)
        if (!tax) return
        updateTaxonomies({ patternType: tax })
        updateType('pattern')
    }, [open, taxonomies, updateType, updateTaxonomies])

    // This is the main driver for loading templates
    // This loads the initial batch of templates. But if we don't yet have taxonomies.
    // There's also an option to skip loading on first mount
    useEffect(() => {
        if (!Object.keys(searchParams.current?.taxonomies)?.length) {
            return
        }

        if (useTemplatesStore.getState().skipNextFetch) {
            // This is useful if the templates are fetched already and
            // the library moves to/from another state that re-renders
            // The point is to keep the logic close to the list. That may change someday
            useTemplatesStore.setState({
                skipNextFetch: false,
            })
            return
        }
        fetchTemplates()
        return () => resetTemplates()
    }, [fetchTemplates, searchParams, resetTemplates])

    // Fetches when the load more is in view
    useEffect(() => {
        nextPage.current && inView && fetchTemplates()
    }, [inView, fetchTemplates, templates])

    if (serverError.length && retryOnce.current) {
        return (
            <div className="text-left">
                <h2 className="text-left">{__('Server error', 'extendify')}</h2>
                <code
                    className="mb-4 block max-w-xl p-4"
                    style={{ minHeight: '10rem' }}>
                    {serverError}
                </code>
                <Button
                    isTertiary
                    onClick={() => {
                        retryOnce.current = false
                        fetchTemplates()
                    }}>
                    {__('Press here to reload')}
                </Button>
            </div>
        )
    }

    if (nothingFound) {
        return (
            <div className="-mt-2 flex h-full w-full items-center justify-center sm:mt-0">
                <h2 className="text-sm font-normal text-extendify-gray">
                    {sprintf(
                        searchParams.current.type === 'template'
                            ? __(
                                  'We couldn\'t find any layouts in the "%s" category.',
                                  'extendify',
                              )
                            : __(
                                  'We couldn\'t find any patterns in the "%s" category.',
                                  'extendify',
                              ),
                        currentTax?.title ?? currentTax.slug,
                    )}
                </h2>
            </div>
        )
    }

    return (
        <>
            {loading && (
                <div className="-mt-2 flex h-full w-full items-center justify-center sm:mt-0">
                    <Spinner />
                </div>
            )}

            <Grid type={currentType} templates={templates}>
                {templates.map((template) => {
                    return (
                        <ImportTemplateBlock
                            maxHeight={
                                currentType === 'template' ? 520 : 'none'
                            }
                            key={template.id}
                            template={template}
                        />
                    )
                })}
            </Grid>

            {nextPage.current && (
                <>
                    <div className="my-20">
                        <Spinner />
                    </div>
                    {/* This is a large div that, when in view, will trigger more patterns to load */}
                    <div
                        className="relative flex -translate-y-full transform flex-col items-end justify-end"
                        ref={loadMoreRef}
                        style={{
                            zIndex: -1,
                            marginBottom: '-100%',
                            height:
                                currentType === 'template' ? '150vh' : '75vh',
                        }}
                    />
                </>
            )}
        </>
    )
})
Example #21
Source File: DefaultTextBlockEditor.jsx    From volto-slate with MIT License 4 votes vote down vote up
DefaultTextBlockEditor = (props) => {
  const {
    block,
    blocksConfig,
    data,
    detached = false,
    index,
    onChangeBlock,
    onInsertBlock,
    onMutateBlock,
    onSelectBlock,
    pathname,
    properties,
    selected,
    uploadRequest,
    uploadContent,
    uploadedContent,
    defaultSelection,
    saveSlateBlockSelection,
    allowedBlocks,
    formTitle,
    formDescription,
  } = props;

  const { slate } = config.settings;
  const { textblockExtensions } = slate;
  const { value } = data;

  // const [addNewBlockOpened, setAddNewBlockOpened] = React.useState();
  const [showDropzone, setShowDropzone] = React.useState(false);
  const [uploading, setUploading] = React.useState(false);
  const [newImageId, setNewImageId] = React.useState(null);

  const prevReq = React.useRef(null);

  const withBlockProperties = React.useCallback(
    (editor) => {
      editor.getBlockProps = () => props;
      return editor;
    },
    [props],
  );

  const slateSettings = React.useMemo(
    () => ({
      ...config.settings.slate,
      persistentHelpers: [
        ...config.settings.slate.persistentHelpers,
        PersistentSlashMenu,
      ],
    }),
    [],
  );

  const onDrop = React.useCallback(
    (files) => {
      // TODO: need to fix setUploading, treat uploading indicator
      // inteligently, show progress report on uploading files
      setUploading(true);
      files.forEach((file) => {
        const [mime] = file.type.split('/');
        if (mime !== 'image') return;

        readAsDataURL(file).then((data) => {
          const fields = data.match(/^data:(.*);(.*),(.*)$/);
          uploadContent(
            getBaseUrl(pathname),
            {
              '@type': 'Image',
              title: file.name,
              image: {
                data: fields[3],
                encoding: fields[2],
                'content-type': fields[1],
                filename: file.name,
              },
            },
            block,
          );
        });
      });
      setShowDropzone(false);
    },
    [pathname, uploadContent, block],
  );

  const { loaded, loading } = uploadRequest;
  const imageId = uploadedContent['@id'];
  const prevLoaded = prevReq.current;

  React.useEffect(() => {
    if (loaded && !loading && !prevLoaded && newImageId !== imageId) {
      const url = flattenToAppURL(imageId);
      setNewImageId(imageId);

      createImageBlock(url, index, props);
    }
    prevReq.current = loaded;
  }, [props, loaded, loading, prevLoaded, imageId, newImageId, index]);

  const handleUpdate = React.useCallback(
    (editor) => {
      // defaultSelection is used for things such as "restoring" the selection
      // when joining blocks or moving the selection to block start on block
      // split
      if (defaultSelection) {
        const selection = parseDefaultSelection(editor, defaultSelection);
        if (selection) {
          setTimeout(() => {
            Transforms.select(editor, selection);
            saveSlateBlockSelection(block, null);
          }, 120);
          // TODO: use React sync render API
          // without setTimeout, the join is not correct. Slate uses internally
          // a 100ms throttle, so setting to a bigger value seems to help
        }
      }
    },
    [defaultSelection, block, saveSlateBlockSelection],
  );

  const onEditorChange = (value, editor) => {
    ReactDOM.unstable_batchedUpdates(() => {
      onChangeBlock(block, {
        ...data,
        value,
        plaintext: serializeNodesToText(value || []),
        // TODO: also add html serialized value
      });
      deconstructToVoltoBlocks(editor);
    });
  };

  // Get editing instructions from block settings or props
  let instructions = data?.instructions?.data || data?.instructions;
  if (!instructions || instructions === '<p><br/></p>') {
    instructions = formDescription;
  }

  const intl = useIntl();
  const placeholder =
    data.placeholder || formTitle || intl.formatMessage(messages.text);
  const schema = TextBlockSchema(data);

  const disableNewBlocks = data?.disableNewBlocks || detached;
  const { ref, inView } = useInView({
    threshold: 0,
    rootMargin: '0px 0px 200px 0px',
  });

  const handleFocus = React.useCallback(() => {
    if (!selected) {
      onSelectBlock(block);
    }
  }, [onSelectBlock, selected, block]);

  return (
    <div className="text-slate-editor-inner" ref={ref}>
      <>
        <Dropzone
          disableClick
          onDrop={onDrop}
          className="dropzone"
          onDragOver={() => setShowDropzone(true)}
          onDragLeave={() => setShowDropzone(false)}
        >
          {({ getRootProps, getInputProps }) => {
            return showDropzone ? (
              <div className="drop-indicator">
                {uploading ? (
                  <Dimmer active>
                    <Loader indeterminate>Uploading image</Loader>
                  </Dimmer>
                ) : (
                  <Message>
                    <center>
                      <img src={imageBlockSVG} alt="" />
                    </center>
                  </Message>
                )}
              </div>
            ) : (
              <>
                <SlateEditor
                  index={index}
                  readOnly={!inView}
                  properties={properties}
                  extensions={textblockExtensions}
                  renderExtensions={[withBlockProperties]}
                  value={value}
                  block={block /* is this needed? */}
                  defaultSelection={defaultSelection}
                  onUpdate={handleUpdate}
                  debug={DEBUG}
                  onFocus={handleFocus}
                  onChange={(value, editor) => onEditorChange(value, editor)}
                  onKeyDown={handleKey}
                  selected={selected}
                  placeholder={placeholder}
                  slateSettings={slateSettings}
                />
                {DEBUG ? <div>{block}</div> : ''}
              </>
            );
          }}
        </Dropzone>

        {selected && !data.plaintext?.trim() && !disableNewBlocks && (
          <BlockChooserButton
            data={data}
            block={block}
            onInsertBlock={(id, value) => {
              onSelectBlock(onInsertBlock(id, value));
            }}
            onMutateBlock={onMutateBlock}
            allowedBlocks={allowedBlocks}
            blocksConfig={blocksConfig}
            size="24px"
            className="block-add-button"
            properties={properties}
          />
        )}

        <SidebarPortal selected={selected}>
          <div id="slate-plugin-sidebar"></div>
          {instructions ? (
            <Segment attached>
              <div dangerouslySetInnerHTML={{ __html: instructions }} />
            </Segment>
          ) : (
            <>
              <ShortcutListing />
              <MarkdownIntroduction />
              <BlockDataForm
                schema={schema}
                title={schema.title}
                onChangeField={(id, value) => {
                  onChangeBlock(block, {
                    ...data,
                    [id]: value,
                  });
                }}
                formData={data}
                block={block}
              />
            </>
          )}
        </SidebarPortal>
      </>
    </div>
  );
}
Example #22
Source File: DistrictDashboard.js    From dashboard with MIT License 4 votes vote down vote up
function DistrictDashboard() {
  const todayDate = new Date();
  const params = useParams();
  const [isOpen, setIsOpen] = useState(false);
  const [timeseries, setTimeseries] = useState(false);
  const [filterDistrict, setFilterDistrict] = useState(ACTIVATED_DISTRICTS[0]);
  const [filterFacilityTypes, setFilterFacilityTypes] =
    useState(FACILITY_TYPES);
  const [content, setContent] = useState(
    CONTENT[params.content?.toUpperCase()] || CONTENT.CAPACITY
  );
  const [dates, datesOnChange] = useState([
    getNDateBefore(todayDate, 14),
    todayDate,
  ]);
  const [date, dateOnChange] = useState(todayDate);
  const [ref, inView] = useInView({
    threshold: 0,
  });

  const getDistrict = (name) => {
    const district = ACTIVATED_DISTRICTS.find(
      (district) => district.name.toLowerCase() === name?.toLowerCase()
    );

    return district === undefined ? ACTIVATED_DISTRICTS[0] : district;
  };

  useEffect(() => {
    setFilterDistrict(getDistrict(params.district));
  }, [params.district]);

  useEffect(() => {
    setContent(CONTENT[params.content?.toUpperCase()] || CONTENT.CAPACITY);
  }, [params.content]);

  useEffect(() => {
    window.history.replaceState(
      null,
      "Care Dashboard",
      `/district/${filterDistrict.name.toLowerCase()}/${Object.entries(CONTENT)
        .find((a) => a[1] === content)[0]
        .toLowerCase()}`
    );
  }, [content, filterDistrict]);

  const renderContent = () => {
    switch (content) {
      case CONTENT.CAPACITY:
        return !timeseries ? (
          <Capacity
            filterDistrict={filterDistrict}
            filterFacilityTypes={filterFacilityTypes}
            date={date}
          />
        ) : (
          <CapacityTimeseries
            filterDistrict={filterDistrict}
            filterFacilityTypes={filterFacilityTypes}
            dates={dates}
          />
        );
      case CONTENT.PATIENT:
        return !timeseries ? (
          <Patient
            filterDistrict={filterDistrict}
            filterFacilityTypes={filterFacilityTypes}
            date={date}
          />
        ) : (
          <PatientTimeseries
            filterDistrict={filterDistrict}
            filterFacilityTypes={filterFacilityTypes}
            dates={dates}
          />
        );
      case CONTENT.TESTS:
        return !timeseries ? (
          <Tests
            filterDistrict={filterDistrict}
            filterFacilityTypes={filterFacilityTypes}
            date={date}
          />
        ) : (
          <TestsTimeseries
            filterDistrict={filterDistrict}
            filterFacilityTypes={filterFacilityTypes}
            dates={dates}
          />
        );
      case CONTENT.TRIAGE:
        return !timeseries ? (
          <Triage
            filterDistrict={filterDistrict}
            filterFacilityTypes={filterFacilityTypes}
            date={date}
          />
        ) : (
          <TriageTimeseries
            filterDistrict={filterDistrict}
            filterFacilityTypes={filterFacilityTypes}
            dates={dates}
          />
        );
      case CONTENT.LSG:
        return !timeseries ? (
          <Lsg filterDistrict={filterDistrict} date={date} />
        ) : (
          <div>Work in Progress</div>
        );
      case CONTENT.OXYGEN:
        return !timeseries ? (
          <OxygenMonitor
            filterDistrict={filterDistrict}
            filterFacilityTypes={filterFacilityTypes}
            date={date}
          />
        ) : (
          <div>Work in Progress</div>
        );
      case CONTENT.MAP:
        return !timeseries ? (
          <DistrictMap
            filterDistrict={filterDistrict}
            filterFacilityTypes={filterFacilityTypes}
            date={date}
          />
        ) : (
          <div>Work in Progress</div>
        );
      default:
        return <div />;
    }
  };

  function ConditionalFilter({ floating }) {
    return (
      <Filter
        floating={floating}
        timeseries={timeseries}
        setTimeseries={setTimeseries}
        date={date}
        dateOnChange={dateOnChange}
        dates={dates}
        datesOnChange={datesOnChange}
        maxDate={todayDate}
        filterFacilityTypes={filterFacilityTypes}
        setFilterFacilityTypes={setFilterFacilityTypes}
        content={content}
      />
    );
  }
  const transitions = useTransition(content, null, {
    from: { opacity: 0 },
    enter: { opacity: 1 },
    leave: { opacity: 1 },
  });
  return (
    <div className="overflow-hidden md:overflow-auto">
      <PageTitle>District Dashboard</PageTitle>
      <div className="flex flex-col items-center justify-between mb-2 px-4 py-2 bg-primary-500 rounded-lg shadow-md md:flex-row">
        <p className="text-white font-semibold">{filterDistrict.name}</p>
        <div className="md:flex md:space-x-2">
          <div className="flex flex-wrap justify-center dark:text-gray-700 dark:bg-gray-900 bg-white rounded-lg space-x-1 space-y-1 md:space-x-0 md:space-y-0">
            {Object.keys(CONTENT).map((k, i) => {
              let t = "shadow-xs ";
              if (i === 0) {
                t += "md:rounded-r-none";
              } else if (i === Object.keys(CONTENT).length - 1) {
                t += "md:rounded-l-none";
              } else {
                t += "md:rounded-l-none md:rounded-r-none";
              }
              return (
                <Button
                  layout="link"
                  onClick={() => setContent(CONTENT[k])}
                  className={t}
                  disabled={content === CONTENT[k]}
                  key={i}
                >
                  <span className="capitalize">{k.toLowerCase()}</span>
                </Button>
              );
            })}
          </div>
          <div className="relative mt-2 dark:bg-gray-900 bg-white rounded-lg md:mt-0">
            <Button
              layout="link"
              onClick={() => setIsOpen(!isOpen)}
              aria-label="Select district"
              aria-haspopup="true"
              disabled={false}
              iconRight={ChevronDown}
              className="w-full shadow-xs"
            >
              {filterDistrict.name}
            </Button>
            <Dropdown
              isOpen={isOpen}
              align="right"
              onClose={() => setIsOpen(false)}
              className="z-40"
            >
              {ACTIVATED_DISTRICTS.map((d, i) => (
                <DropdownItem
                  key={i}
                  onClick={() => {
                    setFilterDistrict(d);
                    setIsOpen(false);
                  }}
                >
                  <span>{d.name}</span>
                </DropdownItem>
              ))}
            </Dropdown>
          </div>
        </div>
      </div>
      <div ref={ref}>
        <ConditionalFilter floating={false} />
      </div>
      {!inView && <ConditionalFilter floating />}
      <Suspense fallback={<ThemedSuspense />}>
        <SWRConfig
          value={{
            suspense: true,
            loadingTimeout: 10_000,
            refreshInterval: 300_000,
            onError: (error, key) => {
              // eslint-disable-next-line no-console
              console.error(error, key);
            },
          }}
        >
          {transitions.map(({ key, props }) => (
            <animated.div key={key} style={props}>
              {renderContent()}
            </animated.div>
          ))}
        </SWRConfig>
      </Suspense>
    </div>
  );
}