SupremeTechnopriest/react-idle-timer

View on GitHub
docs/src/components/Demo.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
import { useState, useRef, MutableRefObject, BaseSyntheticEvent } from 'react'
import { useTranslation } from 'next-i18next'
import {
  chakra,
  Box,
  Container,
  Flex,
  FlexProps,
  VStack,
  HStack,
  Grid,
  Button,
  Text,
  Icon,
  NumberInput,
  NumberInputField,
  NumberInputStepper,
  NumberIncrementStepper,
  NumberDecrementStepper,
  Input,
  InputGroup,
  InputRightElement,
  Tabs,
  TabList,
  Tab,
  TabPanels,
  TabPanel,
  FormControl,
  FormLabel,
  Switch,
  Spacer,
  useColorModeValue
} from '@chakra-ui/react'

import { BsInfoLg } from 'react-icons/bs'
import { Window } from '@components/Window'

import { debounceFn } from '@utils/debounce'

const descriptionDefault = 'Hover over a property or method to see a description.'

function postMessage (event: string, data: any, targets: MutableRefObject<HTMLIFrameElement>[]) {
  const deferred = []
  for (const target of targets) {
    if (target.current) {
      target.current.contentWindow.postMessage({ event, data })
    } else {
      deferred.push([event, data, [target]])
    }
  }
  if (deferred.length) {
    setTimeout(() => {
      for (const [event, data, targets] of deferred) {
        postMessage(event, data, targets)
      }
    }, 1000)
  }
}

interface IButtonWithEventProps {
  event: string
  description: string
  frames: MutableRefObject<HTMLIFrameElement>[]
  setDescription: (desc: string) => void
}

function ButtonWithEvent (props: IButtonWithEventProps) {
  return (
    <Button
      w='full'
      fontSize='14'
      _hover={{
        bg: 'red.400',
        color: 'white'
      }}
      onMouseOver={() => props.setDescription(props.description)}
      onMouseOut={() => props.setDescription(descriptionDefault)}
      onClick={() => postMessage(props.event, null, props.frames)}>
        {props.event}
    </Button>
  )
}

interface ISwitchWithEventProps {
  event: string
  description: string
  frames: MutableRefObject<HTMLIFrameElement>[]
  defaultChecked?: boolean
  onChange?: (checked: boolean) => void
  setDescription: (desc: string) => void
}

function SwitchWithEvent (props: ISwitchWithEventProps) {
  return (
    <FormControl
      display='flex'
      alignItems='center'
      justifyContent='space-between'
      role='group'
      cursor='pointer'
      onMouseOver={() => props.setDescription(props.description)}
      onMouseOut={() => props.setDescription(descriptionDefault)}
      onChange={(e: BaseSyntheticEvent) => {
        const checked = e.target.checked
        if (props.onChange) props.onChange(checked)
        postMessage(props.event, checked, props.frames)
      }}
    >
      <FormLabel
        htmlFor={props.event}
        mb='0'
        mr={3}
        cursor='pointer'
        _groupHover={{
          color: 'red.400'
        }}
      >
        {props.event}
      </FormLabel>
      <Switch
        id={props.event}
        defaultChecked={props.defaultChecked}
        colorScheme='red'
      />
    </FormControl>
  )
}

interface INumberInputWithEventProps extends FlexProps {
  event: string
  description: string
  frames: MutableRefObject<HTMLIFrameElement>[]
  defaultValue: number
  setDescription: (desc: string) => void
}

function NumberInputWithEvent (props: INumberInputWithEventProps) {
  const { event, frames, setDescription, defaultValue, ...rest } = props
  const onChange = debounceFn((value: number) => {
    postMessage(event, value, frames)
  }, 500)

  return (
    <Flex
      w='full'
      justify='space-between'
      align='center'
      role='group'
      onMouseOver={() => setDescription(props.description)}
      onMouseOut={() => setDescription(descriptionDefault)}
      {...rest}
    >
      <Text _groupHover={{ color: 'red.400' }}>{event}</Text>
      <NumberInput
        w={125}
        defaultValue={defaultValue}
        focusBorderColor='red.400'
        onChange={onChange}
      >
        <NumberInputField />
        <NumberInputStepper>
          <NumberIncrementStepper />
          <NumberDecrementStepper />
        </NumberInputStepper>
      </NumberInput>
    </Flex>
  )
}

interface IMessageInputProps extends FlexProps {
  event: string
  description: string
  frames: MutableRefObject<HTMLIFrameElement>[]
  setDescription: (desc: string) => void
}

function MessageInput (props: IMessageInputProps) {
  const [message, setMessage] = useState<string>('Hello')

  const onClick = () => {
    if (message) {
      postMessage(props.event, message, props.frames)
    }
  }

  return (
    <InputGroup size='md'>
      <Input
        pr='4.5rem'
        type='text'
        defaultValue={message}
        placeholder='Message Content'
        onChange={event => setMessage(event.target.value)}
        onMouseOver={() => props.setDescription(props.description)}
        onMouseOut={() => props.setDescription(descriptionDefault)}
      />
      <InputRightElement width='6rem'>
        <Button
          h='1.75rem'
          size='sm'
          _hover={{
            bg: 'red.400',
            color: 'white'
          }}
          onClick={onClick}
          onMouseOver={() => props.setDescription(props.description)}
          onMouseOut={() => props.setDescription(descriptionDefault)}
        >
          message
        </Button>
      </InputRightElement>
    </InputGroup>
  )
}

interface IControlPanelProps {
  frameA: MutableRefObject<HTMLIFrameElement>
  frameB: MutableRefObject<HTMLIFrameElement>
  crossTab: boolean
  setCrossTab: (enabled: boolean) => void
  setDescription: (desc: string) => void
}

function ControlPanel ({ frameA, frameB, crossTab, setCrossTab, setDescription }: IControlPanelProps) {
  const { t } = useTranslation('common')
  return (
    <Tabs isFitted variant='enclosed' colorScheme='red'>
      <TabList mb='1em'>
        <Tab>Props</Tab>
        <Tab>Methods</Tab>
      </TabList>
      <TabPanels>
        <TabPanel>
          <VStack>
            <NumberInputWithEvent event='timeout' defaultValue={2000} frames={[frameA, frameB]} setDescription={setDescription} description={t('props.timeout')} />
            <NumberInputWithEvent event='promptBeforeIdle' defaultValue={0} frames={[frameA, frameB]} setDescription={setDescription} description={t('props.promptBeforeIdle')} />
            <NumberInputWithEvent event='debounce' defaultValue={0} frames={[frameA, frameB]} setDescription={setDescription} description={t('props.debounce')} />
            <NumberInputWithEvent event='throttle' defaultValue={0} frames={[frameA, frameB]} setDescription={setDescription} description={t('props.throttle')} />
            <NumberInputWithEvent event='eventsThrottle' defaultValue={0} frames={[frameA, frameB]} setDescription={setDescription} description={t('props.eventsThrottle')} />
            <Spacer />
            <SwitchWithEvent event='startOnMount' frames={[frameA, frameB]} setDescription={setDescription} description={t('props.startOnMount')} />
            <SwitchWithEvent event='startManually' frames={[frameA, frameB]} setDescription={setDescription} defaultChecked description={t('props.startManually')} />
            <SwitchWithEvent event='disabled' frames={[frameA, frameB]} setDescription={setDescription} description={t('props.disabled')} />
            <SwitchWithEvent event='stopOnIdle' frames={[frameA, frameB]} setDescription={setDescription} description={t('props.stopOnIdle')} />
            <SwitchWithEvent event='crossTab' frames={[frameA, frameB]} setDescription={setDescription} description={t('props.crossTab')} onChange={setCrossTab} />
            {crossTab && (
              <>
                <SwitchWithEvent event='leaderElection' frames={[frameA, frameB]} setDescription={setDescription} description={t('props.leaderElection')} />
                <NumberInputWithEvent event='syncTimers' defaultValue={0} frames={[frameA, frameB]} setDescription={setDescription} description={t('props.syncTimers')} />
              </>
            )}
          </VStack>
        </TabPanel>
        <TabPanel>
          <VStack>
            <HStack width='full'>
              <VStack width='45%'>
                <ButtonWithEvent event='start' frames={[frameA, frameB]} setDescription={setDescription} description={t('methods.start')} />
                <ButtonWithEvent event='reset' frames={[frameA]} setDescription={setDescription} description={t('methods.reset')} />
                <ButtonWithEvent event='activate' frames={[frameA]} setDescription={setDescription} description={t('methods.activate')} />
                <ButtonWithEvent event='pause' frames={[frameA]} setDescription={setDescription} description={t('methods.pause')} />
                <ButtonWithEvent event='resume' frames={[frameA]} setDescription={setDescription} description={t('methods.resume')} />
                <ButtonWithEvent event='isIdle' frames={[frameA, frameB]} setDescription={setDescription} description={t('methods.isIdle')} />
                <ButtonWithEvent event='isPrompted' frames={[frameA, frameB]} setDescription={setDescription} description={t('methods.isPrompted')} />
                <ButtonWithEvent event='isLeader' frames={[frameA, frameB]} setDescription={setDescription} description={t('methods.isLeader')} />
                <ButtonWithEvent event='isLastActiveTab' frames={[frameA, frameB]} setDescription={setDescription} description={t('methods.isLastActiveTab')} />
                <Button w='full' disabled />
              </VStack>
              <VStack width='55%'>
                <ButtonWithEvent event='getTabId' frames={[frameA, frameB]} setDescription={setDescription} description={t('methods.getTabId')} />
                <ButtonWithEvent event='getRemainingTime' frames={[frameA, frameB]} setDescription={setDescription} description={t('methods.getRemainingTime')} />
                <ButtonWithEvent event='getElapsedTime' frames={[frameA, frameB]} setDescription={setDescription} description={t('methods.getElapsedTime')} />
                <ButtonWithEvent event='getTotalElapsedTime' frames={[frameA, frameB]} setDescription={setDescription} description={t('methods.getTotalElapsedTime')} />
                <ButtonWithEvent event='getLastActiveTime' frames={[frameA, frameB]} setDescription={setDescription} description={t('methods.getLastActiveTime')} />
                <ButtonWithEvent event='getLastIdleTime' frames={[frameA, frameB]} setDescription={setDescription} description={t('methods.getLastIdleTime')} />
                <ButtonWithEvent event='getActiveTime' frames={[frameA, frameB]} setDescription={setDescription} description={t('methods.getActiveTime')} />
                <ButtonWithEvent event='getTotalActiveTime' frames={[frameA, frameB]} setDescription={setDescription} description={t('methods.getTotalActiveTime')} />
                <ButtonWithEvent event='getIdleTime' frames={[frameA, frameB]} setDescription={setDescription} description={t('methods.getIdleTime')} />
                <ButtonWithEvent event='getTotalIdleTime' frames={[frameA, frameB]} setDescription={setDescription} description={t('methods.getTotalIdleTime')} />
              </VStack>
            </HStack>
            <MessageInput event='message' frames={[frameA]} setDescription={setDescription} description={t('methods.message')} />
            <SwitchWithEvent event='emitOnSelf' frames={[frameA]} setDescription={setDescription} description={t('methods.emitOnSelf')} />
          </VStack>
        </TabPanel>
      </TabPanels>
    </Tabs>
  )
}

export function Demo () {
  const { t } = useTranslation('common')
  const windowA = useRef<HTMLIFrameElement>()
  const windowB = useRef<HTMLIFrameElement>()
  const [crossTab, setCrossTab] = useState<boolean>(false)
  const [description, setDescription] = useState<string>(descriptionDefault)

  return (
    <Box
      as='section'
      py={20}
    >
      <Container maxW='1280px'>
        <Box maxW='760px' mx='auto' textAlign='center' mb='56px'>
          <chakra.h2 textStyle='heading' mb='5'>
            {t('homepage.demo.title')}
          </chakra.h2>
          <chakra.p opacity={0.7} fontSize='lg'>
            {t('homepage.demo.description')}
          </chakra.p>
        </Box>
        <Grid
          templateColumns={{ base: 'repeat(1, 1fr)', md: crossTab ? '350px 1fr 1fr' : '350px 1fr' }}
          gap={10}
          px={{ md: 12 }}
        >
          <ControlPanel
            frameA={windowA}
            frameB={windowB}
            crossTab={crossTab}
            setCrossTab={setCrossTab}
            setDescription={setDescription}
          />
          <Window ref={windowA} url='/demo?hideControls=true&startManually' />
          {crossTab && (
            <Window ref={windowB} url='/demo?hideControls=true&startManually' />
          )}
        </Grid>
        <Flex
          align='center'
          p={2}
          mt={8}
          mx={{ md: 12 }}
          display={{ base: 'none', md: 'flex' }}
          borderRadius={4}
          bg={useColorModeValue('gray.100', 'gray.700')}
          borderLeftColor='red.400'
          borderLeftStyle='solid'
          borderLeftWidth={2}
        >
          <Icon as={BsInfoLg} ml={2} mr={3} color='red.400' />
          <Text>{description}</Text>
        </Flex>
      </Container>
    </Box>
  )
}