@chakra-ui/react#PopoverArrow TypeScript Examples

The following examples show how to use @chakra-ui/react#PopoverArrow. 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: Popover.tsx    From calories-in with MIT License 6 votes vote down vote up
function Popover() {
  const selectRef = useRef<HTMLSelectElement>(null)

  return (
    <PopoverBase placement="left" initialFocusRef={selectRef}>
      {({ onClose }) => {
        return (
          <>
            <PopoverTrigger>
              <Trigger />
            </PopoverTrigger>

            <PopoverContent boxShadow="lg">
              <PopoverArrow />
              <PopoverCloseButton />
              <PopoverHeader>Filters</PopoverHeader>

              <PopoverBody>
                <Content selectRef={selectRef} />
              </PopoverBody>

              <PopoverFooter border="0">
                <Footer onClose={onClose} />
              </PopoverFooter>
            </PopoverContent>
          </>
        )
      }}
    </PopoverBase>
  )
}
Example #2
Source File: ColumnsHider.tsx    From ke with MIT License 6 votes vote down vote up
export function ColumnsHider<T>({ children, onChange, columns, visibleColumns }: ColumnHiderProps<T>): JSX.Element {
  const handleChange = useCallback(
    (values: ReactText[]) => {
      const newVisibleColumns = columns.filter(({ name }) => values.includes(String(name)))

      onChange(newVisibleColumns)
    },
    [onChange, columns]
  )

  return (
    <div>
      <Popover placement="right-start">
        <PopoverTrigger>
          <Button margin="10px 0">Отображаемые колонки</Button>
        </PopoverTrigger>
        <Portal>
          <PopoverContent>
            <PopoverArrow />
            <PopoverHeader>Выберите отображаемые колонки</PopoverHeader>
            <PopoverCloseButton />
            <PopoverBody maxHeight="400px" overflow="scroll">
              <CheckboxGroup value={visibleColumns.map(({ name }) => name)} onChange={handleChange}>
                {columns.map(({ name, header }) => (
                  <Checkbox value={name} display="block">
                    {header}
                  </Checkbox>
                ))}
              </CheckboxGroup>
            </PopoverBody>
          </PopoverContent>
        </Portal>
      </Popover>
      {children}
    </div>
  )
}
Example #3
Source File: PrivateApiRequirements.tsx    From bluebubbles-server with Apache License 2.0 5 votes vote down vote up
PrivateApiRequirements = (): JSX.Element => {
    const requirements: Array<RequirementsItem> = (useAppSelector(state => state.config.private_api_requirements) ?? []);
    const [showProgress, setShowProgress] = useBoolean();

    const refreshRequirements = () => {
        setShowProgress.on();
        getPrivateApiRequirements().then(requirements => {
            // I like longer spinning
            setTimeout(() => {
                setShowProgress.off();
            }, 1000);
            
            if (!requirements) return;
            store.dispatch(setConfig({ name: 'private_api_requirements', value: requirements }));
        });
    };

    return (
        <Box border='1px solid' borderColor={useColorModeValue('gray.200', 'gray.700')} borderRadius='xl' p={3} width='325px'>
            <Stack direction='row' align='center'>
                <Text fontSize='lg' fontWeight='bold'>Private API Requirements</Text>
                <Box
                    _hover={{ cursor: 'pointer' }}
                    animation={showProgress ? `${spin} infinite 1s linear` : undefined}
                    onClick={refreshRequirements}
                >
                    <BiRefresh />
                </Box>
            </Stack>
            <UnorderedList mt={2} ml={8}>
                {requirements.map(e => (
                    <ListItem key={e.name}>
                        <Stack direction='row' align='center'>
                            <Text fontSize='md'><strong>{e.name}</strong>:&nbsp;
                                <Box as='span' color={e.pass ? 'green' : 'red'}>{e.pass ? 'Pass' : 'Fail'}</Box>
                            </Text>
                            {(!e.pass) ? (
                                <Popover trigger='hover'>
                                    <PopoverTrigger>
                                        <Box ml={2} _hover={{ color: 'brand.primary', cursor: 'pointer' }}>
                                            <AiOutlineInfoCircle />
                                        </Box>
                                    </PopoverTrigger>
                                    <PopoverContent>
                                        <PopoverArrow />
                                        <PopoverCloseButton />
                                        <PopoverHeader>How to Fix</PopoverHeader>
                                        <PopoverBody>
                                            <Text>
                                                {e.solution}
                                            </Text>
                                        </PopoverBody>
                                    </PopoverContent>
                                </Popover>
                            ): null}
                        </Stack>
                    </ListItem>
                ))}
            </UnorderedList>
        </Box>
    );
}
Example #4
Source File: SharePopover.tsx    From ksana.in with Apache License 2.0 5 votes vote down vote up
SharePopover = ({ url }: SharePopoverProps) => {
  const [isLoadingShare, setLoadingShare] = useState<boolean>(false)
  const [showShare, setShowShare] = useState<boolean>(false)
  const [text, setText] = useState<string>('')
  const parsedUrl = encodeURIComponent(url)

  const handleShare = async (url: string) => {
    setLoadingShare(true)
    const d = await getMeta(url)
    setText(encodeURIComponent(d.description))
    setShowShare(true)
    setLoadingShare(false)
  }

  return (
    <Popover
      isOpen={showShare}
      onClose={() => {
        setShowShare(false)
      }}
      isLazy
      placement="bottom"
    >
      <PopoverTrigger>
        <IconButton
          onClick={() => {
            handleShare(url)
          }}
          aria-label="Share url"
          fontSize="20px"
          variant="ghost"
          borderRadius="md"
          isLoading={isLoadingShare}
          icon={<HiShare color="#ED8936" />}
        />
      </PopoverTrigger>
      <PopoverContent>
        <PopoverArrow />
        <PopoverCloseButton />
        <PopoverHeader>Bagikan tautan anda</PopoverHeader>
        <PopoverBody>
          <Stack direction="row" justifyContent="center">
            <Link isExternal href={`https://api.whatsapp.com/send?text=${text}+%0A+${parsedUrl}`}>
              <IconButton
                borderRadius="md"
                colorScheme="green"
                aria-label="Share whatsapp"
                icon={<FaWhatsapp />}
              />
            </Link>
            <Link
              isExternal
              href={`https://twitter.com/intent/tweet?text=${text}+%0A+${parsedUrl}`}
            >
              <IconButton
                borderRadius="md"
                colorScheme="twitter"
                aria-label="Share twitter"
                icon={<FaTwitter />}
              />
            </Link>
            <Link
              isExternal
              href={`https://www.facebook.com/sharer/sharer.php?quote=${text}&u=${parsedUrl}`}
            >
              <IconButton
                borderRadius="md"
                colorScheme="facebook"
                aria-label="Share Facebook"
                icon={<FaFacebook />}
              />
            </Link>
          </Stack>
        </PopoverBody>
      </PopoverContent>
    </Popover>
  )
}
Example #5
Source File: Hustlers.tsx    From dope-monorepo with GNU General Public License v3.0 5 votes vote down vote up
Hustlers = () => {
    const [hustlers, setHustlers] = React.useState<any>();

    useEffect(() => {
        if (!(window.ethereum as any)?.selectedAddress) return;

        fetch(`https://api.dopewars.gg/wallets/${ethers.utils.getAddress(
            (window.ethereum as any).selectedAddress,
          )}/hustlers`).then(res => res.json()).then(res => setHustlers(res));
    }, []);
    
    return (
        <div>
            {hustlers ? <SimpleGrid columns={2} spacing={5} paddingBottom="8">
                {
                    hustlers.map((hustler: any, i: number) =>
                        <VStack key={i}>
                            <Text paddingBottom="0px">
                                {hustler.id} {hustler.name ? " - " + hustler.name : ""}
                            </Text>
                            <object width="70%" type="image/svg+xml" data={hustler.svg} />
                            { localStorage.getItem(`gameSelectedHustler_${(window.ethereum as any).selectedAddress}`) !== hustler.id ? <Popover>
                                <PopoverTrigger>
                                    <Button variant="primary">
                                        Set as selected hustler
                                    </Button>
                                </PopoverTrigger>
                                <PopoverContent>
                                    <PopoverArrow />
                                    <PopoverCloseButton />
                                    <PopoverHeader>Are you sure?</PopoverHeader>
                                    <PopoverBody>The game needs to be reloaded in order to modify your selected hustler</PopoverBody>
                                    <PopoverFooter>
                                        <Button variant="primary" onClick={() => {
                                            localStorage.setItem(`gameSelectedHustler_${(window.ethereum as any).selectedAddress}`, hustler.id);
                                            window.location.reload();
                                        }}>
                                            Confirm
                                        </Button>
                                    </PopoverFooter>
                                </PopoverContent>
                            </Popover> : undefined }
                        </VStack>
                    )
                }
            </SimpleGrid> : <Center padding="4">
                    <Spinner size="lg"/>
                </Center>
            }
            <Center>
                <a href="/inventory">
                    <Button variant="primary">
                        Details
                    </Button>
                </a>
            </Center>
        </div>
    )
}
Example #6
Source File: ColorPicker.tsx    From dope-monorepo with GNU General Public License v3.0 5 votes vote down vote up
ColorPicker = ({ colors, selectedColor, changeCallback }: ColorPickerProps) => {
  const [color, setColor] = useState(selectedColor ?? colors[0]);

  const handleColorInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    handleColorChange(e.target.value);
  };

  const handleColorChange = (color: string) => {
    setColor(color);
    if (typeof changeCallback === 'function') changeCallback(color);
  };

  return (
    <>
      <Popover variant="picker">
        <PopoverTrigger>
          <Button
            aria-label={color}
            background={color}
            height="64px"
            width="64px"
            padding={0}
            minWidth="unset"
            borderRadius={3}
          ></Button>
        </PopoverTrigger>
        <PopoverContent width="196px">
          <PopoverArrow bg={color} />
          <PopoverCloseButton color="white" />
          <PopoverHeader
            height="100px"
            backgroundColor={color}
            borderTopLeftRadius={5}
            borderTopRightRadius={5}
            color="white"
          >
            <Center height="100%">{color}</Center>
          </PopoverHeader>
          <PopoverBody height="96px">
            <SimpleGrid columns={5} spacing={2}>
              {colors.map(c => (
                <Button
                  key={c}
                  aria-label={c}
                  background={c}
                  height="32px"
                  width="32px"
                  padding={0}
                  minWidth="unset"
                  borderRadius={3}
                  _hover={{ background: c }}
                  onClick={() => {
                    handleColorChange(c);
                  }}
                ></Button>
              ))}
            </SimpleGrid>
            <Input
              borderRadius={3}
              marginTop={3}
              placeholder="red.100"
              size="sm"
              value={color}
              onChange={handleColorInputChange}
            />
          </PopoverBody>
        </PopoverContent>
      </Popover>
    </>
  );
}
Example #7
Source File: story-timeline.tsx    From portfolio with MIT License 5 votes vote down vote up
StoryTimeline: React.FC<StoryTimelineProps> = ({
  icon = FiCheckCircle,
  boxProps = {},
  index,
  year,
  skipTrail = false,
  children,
  ...props
}) => {
  const [isOpen, setIsOpen] = React.useState(true);
  const open = () => setIsOpen(!isOpen);
  const close = () => setIsOpen(false);
  const color = useColorModeValue("gray.700", "gray.200");
  let place = index % 2 === 0 ? "right" : "left";

  return (
    <Flex minH={20} {...props}>
      <Flex flexDir="column" alignItems={"center"} minHeight={"8rem"} mr={4}>
        <Popover
          returnFocusOnClose={false}
          isOpen={isOpen}
          onClose={close}
          placement={place}
          closeOnBlur={false}
          // variant="responsive"
          width={["9.3rem", "13rem", "15rem", "100%"]}
        >
          <PopoverTrigger>
            <Box onClick={open} position="relative">
              <Circle
                size={12}
                bg={useColorModeValue("gray.600", "gray.500")}
                opacity={useColorModeValue(0.07, 0.15)}
                sx={{}}
              />
              {year ? (
                <Box
                  fontSize={15}
                  fontWeight={"bold"}
                  color={color}
                  pos="absolute"
                  left="0.5rem"
                  top="0.875rem"
                >
                  {year}
                </Box>
              ) : (
                <Box
                  as={icon}
                  size="1.25rem"
                  color={color}
                  pos="absolute"
                  left="0.875rem"
                  top="0.875rem"
                />
              )}
            </Box>
          </PopoverTrigger>
          <Box fontSize={15}>
            {!year && (
              <PopoverContent padding={["0.2rem", "0.2rem", "0.7rem"]}>
                <PopoverArrow />
                {/* <PopoverCloseButton /> */}
                <PopoverBody>
                  <Box overflow="scroll">{children}</Box>
                </PopoverBody>
              </PopoverContent>
            )}
          </Box>
        </Popover>
        {!skipTrail && <Box w="1px" flex={1} bg={color} />}
      </Flex>
    </Flex>
  );
}
Example #8
Source File: ConnectionSettings.tsx    From bluebubbles-server with Apache License 2.0 5 votes vote down vote up
ConnectionSettings = (): JSX.Element => {
    const proxyService: string = (useAppSelector(state => state.config.proxy_service) ?? '').toLowerCase().replace(' ', '-');

    return (
        <Stack direction='column' p={5}>
            <Flex flexDirection='row' justifyContent='flex-start' alignItems='center'>
                <Text fontSize='2xl'>Connection Settings</Text>
                <Popover trigger='hover'>
                    <PopoverTrigger>
                        <Box ml={2} _hover={{ color: 'brand.primary', cursor: 'pointer' }}>
                            <AiOutlineInfoCircle />
                        </Box>
                    </PopoverTrigger>
                    <PopoverContent>
                        <PopoverArrow />
                        <PopoverCloseButton />
                        <PopoverHeader>Information</PopoverHeader>
                        <PopoverBody>
                            <Text>
                                These settings will determine how your clients will connect to the server
                            </Text>
                        </PopoverBody>
                    </PopoverContent>
                </Popover>
            </Flex>
            <Divider orientation='horizontal' />
            <Spacer />
            <ProxyServiceField />
            <Spacer />
            {(proxyService === 'ngrok') ? (<NgrokRegionField />) : null}
            <Spacer />
            {(proxyService === 'ngrok') ? (<NgrokAuthTokenField />) : null}
            <Spacer />
            <Divider orientation='horizontal' />
            <ServerPasswordField />
            <LocalPortField />

            <Spacer />
            <Accordion allowMultiple>
                <AccordionItem>
                    <AccordionButton>
                        <Box flex='1' textAlign='left' width="15em">
                            Advanced Connection Settings
                        </Box>
                        <AccordionIcon />
                    </AccordionButton>
                    <AccordionPanel pb={4}>
                        <EncryptCommunicationsField />
                        <Box m={15} />
                        {(proxyService === 'dynamic-dns') ? (<UseHttpsField />) : null}
                    </AccordionPanel>
                </AccordionItem>
            </Accordion>
        </Stack>
    );
}
Example #9
Source File: PermissionRequirements.tsx    From bluebubbles-server with Apache License 2.0 5 votes vote down vote up
PermissionRequirements = (): JSX.Element => {
    const permissions: Array<RequirementsItem> = (useAppSelector(state => state.config.permissions) ?? []);
    const [showProgress, setShowProgress] = useBoolean();

    const refreshRequirements = () => {
        setShowProgress.on();
        checkPermissions().then(permissions => {
            // I like longer spinning
            setTimeout(() => {
                setShowProgress.off();
            }, 1000);

            if (!permissions) return;
            store.dispatch(setConfig({ name: 'permissions', value: permissions }));
        });
    };

    return (
        <Box border='1px solid' borderColor={useColorModeValue('gray.200', 'gray.700')} borderRadius='xl' p={3} width='325px'>
            <Stack direction='row' align='center'>
                <Text fontSize='lg' fontWeight='bold'>macOS Permissions</Text>
                <Box
                    _hover={{ cursor: 'pointer' }}
                    animation={showProgress ? `${spin} infinite 1s linear` : undefined}
                    onClick={refreshRequirements}
                >
                    <BiRefresh />
                </Box>
            </Stack>
            <UnorderedList mt={2} ml={8}>
                {permissions.map(e => (
                    <ListItem key={e.name}>
                        <Stack direction='row' align='center'>
                            <Text fontSize='md'><strong>{e.name}</strong>:&nbsp;
                                <Box as='span' color={e.pass ? 'green' : 'red'}>{e.pass ? 'Pass' : 'Fail'}</Box>
                            </Text>
                            {(e.pass) ? (
                                <Popover trigger='hover'>
                                    <PopoverTrigger>
                                        <Box ml={2} _hover={{ color: 'brand.primary', cursor: 'pointer' }}>
                                            <AiOutlineInfoCircle />
                                        </Box>
                                    </PopoverTrigger>
                                    <PopoverContent>
                                        <PopoverArrow />
                                        <PopoverCloseButton />
                                        <PopoverHeader>How to Fix</PopoverHeader>
                                        <PopoverBody>
                                            <Text>
                                                {e.solution}
                                            </Text>
                                        </PopoverBody>
                                    </PopoverContent>
                                </Popover>
                            ): null}
                        </Stack>
                    </ListItem>
                ))}
            </UnorderedList>
        </Box>
    );
}
Example #10
Source File: LogsLayout.tsx    From bluebubbles-server with Apache License 2.0 4 votes vote down vote up
LogsLayout = (): JSX.Element => {
    const dispatch = useAppDispatch();
    const [requiresConfirmation, confirm] = useState((): string | null => {
        return null;
    });
    const alertRef = useRef(null);
    let logs = useAppSelector(state => state.logStore.logs);
    const showDebug = useAppSelector(state => state.logStore.debug);

    // If we don't want to show debug logs, filter them out
    if (!showDebug) {
        logs = logs.filter(e => e.type !== 'debug');
    }

    const toggleDebugMode = (e: React.ChangeEvent<HTMLInputElement>) => {
        dispatch(setDebug(e.target.checked));
    };

    return (
        <Box p={3} borderRadius={10}>
            <Flex flexDirection="column">
                <Stack direction='column' p={5}>
                    <Flex flexDirection='row' justifyContent='flex-start' alignItems='center'>
                        <Text fontSize='2xl'>Controls</Text>
                        <Popover trigger='hover'>
                            <PopoverTrigger>
                                <Box ml={2} _hover={{ color: 'brand.primary', cursor: 'pointer' }}>
                                    <AiOutlineInfoCircle />
                                </Box>
                            </PopoverTrigger>
                            <PopoverContent>
                                <PopoverArrow />
                                <PopoverCloseButton />
                                <PopoverHeader>Information</PopoverHeader>
                                <PopoverBody>
                                    <Text>
                                        This page will allow you to perform debugging actions on your BlueBubbles server.
                                        As many of you know, software is not perfect, and there will always be edge cases
                                        depending on the environment. These controls allow us to get the information needed, or
                                        take the required actions to solve an issue. It also allows you to "see" into what
                                        the server is doing in the background.
                                    </Text>
                                </PopoverBody>
                            </PopoverContent>
                        </Popover>
                    </Flex>
                    <Divider orientation='horizontal' />
                    <Flex flexDirection="row" justifyContent="flex-start">
                        <Menu>
                            <MenuButton
                                as={Button}
                                rightIcon={<BsChevronDown />}
                                width="12em"
                                mr={5}
                            >
                                Manage
                            </MenuButton>
                            <MenuList>
                                <MenuItem icon={<VscDebugRestart />} onClick={() => confirm('restartServices')}>
                                    Restart Services
                                </MenuItem>
                                <MenuItem icon={<BsBootstrapReboot />} onClick={() => confirm('fullRestart')}>
                                    Full Restart
                                </MenuItem>
                                <MenuItem icon={<FiExternalLink />} onClick={() => openLogLocation()}>
                                    Open Log Location
                                </MenuItem>
                                <MenuItem icon={<GoFileSubmodule />} onClick={() => openAppLocation()}>
                                    Open App Location
                                </MenuItem>
                                <MenuItem icon={<AiOutlineClear />} onClick={() => clearLogs()}>
                                    Clear Logs
                                </MenuItem>
                            </MenuList>
                        </Menu>
                        <Menu>
                            <MenuButton
                                as={Button}
                                rightIcon={<BsChevronDown />}
                                width="12em"
                                mr={5}
                            >
                                Debug Actions
                            </MenuButton>
                            <MenuList>
                                <MenuItem icon={<BsTerminal />} onClick={() => confirm('restartViaTerminal')}>
                                    Restart via Terminal
                                </MenuItem>
                                <MenuItem icon={<AiOutlineClear />} onClick={() => confirm('clearEventCache')}>
                                    Clear Event Cache
                                </MenuItem>
                            </MenuList>
                        </Menu>
                        
                    </Flex>
                </Stack>
                <Stack direction='column' p={5}>
                    <Text fontSize='2xl'>Debug Logs</Text>
                    <Divider orientation='horizontal' />
                    <Spacer />
                    <Flex flexDirection='row' justifyContent='flex-start' alignItems='center'>
                        <Checkbox onChange={(e) => toggleDebugMode(e)} isChecked={showDebug}>Show Debug Logs</Checkbox>
                        <Popover trigger='hover'>
                            <PopoverTrigger>
                                <Box ml={2} _hover={{ color: 'brand.primary', cursor: 'pointer' }}>
                                    <AiOutlineInfoCircle />
                                </Box>
                            </PopoverTrigger>
                            <PopoverContent>
                                <PopoverArrow />
                                <PopoverCloseButton />
                                <PopoverHeader>Inforamation</PopoverHeader>
                                <PopoverBody>
                                    <Text>
                                        Enabling this option will show DEBUG level logs. Leaving
                                        this disabled will only INFO, WARN, and ERROR level logs.
                                    </Text>
                                </PopoverBody>
                            </PopoverContent>
                        </Popover>
                    </Flex>
                    <Spacer />
                    <LogsTable logs={logs} />
                </Stack>
            </Flex>

            <ConfirmationDialog
                modalRef={alertRef}
                onClose={() => confirm(null)}
                body={confirmationActions[requiresConfirmation as string]?.message}
                onAccept={() => {
                    if (hasKey(confirmationActions, requiresConfirmation as string)) {
                        if (confirmationActions[requiresConfirmation as string].shouldDispatch ?? false) {
                            dispatch(confirmationActions[requiresConfirmation as string].func() as AnyAction);
                        } else {
                            confirmationActions[requiresConfirmation as string].func();
                        }
                    }
                }}
                isOpen={requiresConfirmation !== null}
            />
        </Box>
    );
}
Example #11
Source File: WalkthroughLayout.tsx    From bluebubbles-server with Apache License 2.0 4 votes vote down vote up
WalkthroughLayout = ({...rest}): JSX.Element => {
    const [step, setStep] = useState(0);
    const [completedSteps, setCompletedSteps] = useState([] as Array<number>);
    const proxyService: string = useAppSelector(state => state.config.proxy_service ?? '');
    const password: string = useAppSelector(state => state.config.password ?? '');
    const bgColor = useBackground();
    
    // Links walkthrough steps and the values they rely on to be completed
    const steps: Array<StepItem> = [
        {
            component: IntroWalkthrough,
            dependencies: []
        },
        {
            component: PermissionsWalkthrough,
            dependencies: []
        },
        {
            component: NotificationsWalkthrough,
            dependencies: []
        },
        {
            component: ConnectionWalkthrough,
            dependencies: [proxyService, password]
        },
        {
            component: PrivateApiWalkthrough,
            dependencies: []
        },
        {
            component: ConfigurationsWalkthrough,
            dependencies: []
        }
    ];

    const CurrentStep = steps[step].component;
    const requiresDependencies = steps[step].dependencies.filter(e => e.length > 0).length !== steps[step].dependencies.length;
    const showNext = step < steps.length && !requiresDependencies;
    const showPrevious = step > 0;

    // Make sure we start at the top
    useEffect(() => {
        window.scrollTo(0, 0);
    }, []);

    const nextButton = (
        <Button
            disabled={!showNext}
            mt='20px'
            colorScheme='blue'
            onClick={() => {
                if (step === steps.length - 1) {
                    toggleTutorialCompleted(true);
                } else {
                    setStep(step + 1);
                }
                
            }}
        >
            {step === steps.length - 1 ? 'Finish' : 'Next'} &gt;
        </Button>
    );

    return (
        <Box p={3} {...rest}>
            <Box mb='80px'>
                <CurrentStep onComplete={() => {
                    setCompletedSteps([...completedSteps, step]);
                }}/>
            </Box>
            <Box position='fixed' bottom={0} left={0} width='100%' height='80px' bg={bgColor}>
                <Divider />
                <Flex justifyContent='space-between' alignItems='center' mx={5}>
                    <Button
                        disabled={!showPrevious}
                        mt='20px'
                        onClick={() => setStep(step - 1)}
                    >
                        &lt; Back
                    </Button>
                    <Stack width='70%'>
                        <Slider aria-label='slider-ex-6' isReadOnly={true} value={step * 20}>
                            <SliderMark value={0} mt='3' ml='-3.5' fontSize='sm'>
                                Intro
                            </SliderMark>
                            <SliderMark value={20} mt='3' ml='-9' fontSize='sm'>
                                Permissions
                            </SliderMark>
                            <SliderMark value={40} mt='3' ml='-10' fontSize='sm'>
                                Notifications
                            </SliderMark>
                            <SliderMark value={60} mt='3' ml='-9' fontSize='sm'>
                                Connection
                            </SliderMark>
                            <SliderMark value={80} mt='3' ml='-9' fontSize='sm'>
                                Private API
                            </SliderMark>
                            <SliderMark value={100} mt='3' ml='-5' fontSize='sm'>
                                Finish
                            </SliderMark>
                            <SliderTrack>
                                <SliderFilledTrack />
                            </SliderTrack>
                            <SliderThumb />
                        </Slider>
                    </Stack>
                    {/* Step 3 is the connection step */}
                    {(step === 3 && password.length === 0) ? (
                        <Popover autoFocus={false} defaultIsOpen={true}>
                            <PopoverTrigger>
                                {nextButton}
                            </PopoverTrigger>
                            <PopoverContent>
                                <PopoverArrow />
                                <PopoverCloseButton />
                                <PopoverHeader>Requirements</PopoverHeader>
                                <PopoverBody>
                                    <Text>Enter a password and save it (using the floppy disk button) to proceed</Text>
                                </PopoverBody>
                            </PopoverContent>
                        </Popover>
                    ) : nextButton}
                </Flex>
            </Box>
        </Box>
    );
}
Example #12
Source File: page-footer.tsx    From notebook with MIT License 4 votes vote down vote up
export function PageFooter() {
  return (
    <SimpleGrid
      flexDirection="column-reverse"
      gridTemplateColumns={["1fr", "1fr", "1fr 1fr", "1fr 1fr"]}
      borderTopWidth={2}
      mt="30px"
      borderTopColor="gray.900"
      pt="20px"
    >
      <Box d={["block", "block", "none", "none"]} mb="30px">
        <FooterSignup />
      </Box>
      <Box>
        <SimpleGrid columns={[1, 1, 2, 2]}>
          <Stack mb={["10px", "10px", 0, 0]}>
            <Text as="span">
              <ExternalFooterLink
                href={`mailto:${siteConfig.author.email}`}
                text="Contact us"
              />
            </Text>
            <Text as="span">
              <ExternalFooterLink
                href={siteConfig.repo.url}
                text="Contribute"
              />
            </Text>
            <Text as="span">
              <InternalFooterLink
                href="/projects"
                text="Open source projects"
              />
            </Text>
          </Stack>
          <Stack>
            <Text as="span">
              <Popover placement="top">
                <PopoverTrigger>
                  <Text
                    as="span"
                    _focus={{ outline: "none", boxShadow: "none" }}
                    fontWeight={500}
                    color="gray.500"
                    cursor="pointer"
                    _hover={{ color: "gray.600", textDecoration: "none" }}
                  >
                    Social Accounts
                  </Text>
                </PopoverTrigger>
                <Portal>
                  <PopoverContent>
                    <PopoverArrow />
                    <PopoverCloseButton />
                    <PopoverBody>
                      <Stack
                        as="footer"
                        isInline
                        spacing={[1, 2]}
                        justifyContent="center"
                        alignItems="center"
                      >
                        <ExternalSocialLink
                          href={siteConfig.author.github}
                          icon={<FaGithub />}
                          type="gray"
                          label="Github Account"
                        />
                        <ExternalSocialLink
                          href={siteConfig.author.dev}
                          icon={<FaDev />}
                          type="gray"
                          label="Dev Account"
                        />
                        <ExternalSocialLink
                          href={siteConfig.author.linkedin}
                          icon={<FaLinkedin />}
                          type="linkedin"
                          label="LinkedIn Account"
                        />
                        <ExternalSocialLink
                          href={siteConfig.author.twitter}
                          icon={<FaTwitter />}
                          type="twitter"
                          label="Twitter Account"
                        />
                        <ExternalSocialLink
                          href={siteConfig.author.quora}
                          icon={<FaQuora />}
                          type="red"
                          label="Quora Account"
                        />
                      </Stack>
                    </PopoverBody>
                  </PopoverContent>
                </Portal>
              </Popover>
            </Text>

            <Text as="span">
              <ExternalFooterLink
                href={`mailto:${siteConfig.author.email}`}
                text="Sponsor"
              />
            </Text>
            <Text as="span">
              <ExternalFooterLink
                href={"/#faqs"}
                isExternal={false}
                text="FAQs"
              />
            </Text>
          </Stack>
        </SimpleGrid>
        <Text mt="20px" color="gray.500">
          Made with ? by{" "}
          <ChakraLink
            _focus={{ boxShadow: "none", outline: "none" }}
            target="_blank"
            href={siteConfig.author.github}
            fontWeight={600}
            color={"gray.400"}
            bgClip="text"
            bgGradient="linear(to-l, #7928CA,#FF0080)"
            _hover={{
              bgGradient: "linear(to-r, red.500, yellow.500)"
            }}
          >
            Muhammad Ahmad
          </ChakraLink>{" "}
        </Text>
      </Box>
      <Box d={["none", "none", "block", "block"]}>
        <FooterSignup />
      </Box>
    </SimpleGrid>
  );
}
Example #13
Source File: HomeLayout.tsx    From bluebubbles-server with Apache License 2.0 4 votes vote down vote up
HomeLayout = (): JSX.Element => {
    const address = useAppSelector(state => state.config.server_address);
    const fcmClient = useAppSelector(state => state.config.fcm_client);
    const password = useAppSelector(state => state.config.password);
    const port = useAppSelector(state => state.config.socket_port);
    const qrCode = fcmClient ? buildQrData(password, address, fcmClient) : null;

    return (
        <Box p={3} borderRadius={10}>
            <Flex flexDirection="column">
                <Stack direction='column' p={5}>
                    <Flex flexDirection='row' justifyContent='flex-start' alignItems='center'>
                        <Text fontSize='2xl'>Connection Details</Text>
                        <Popover trigger='hover'>
                            <PopoverTrigger>
                                <Box ml={2} _hover={{ color: 'brand.primary', cursor: 'pointer' }}>
                                    <AiOutlineInfoCircle />
                                </Box>
                            </PopoverTrigger>
                            <PopoverContent>
                                <PopoverArrow />
                                <PopoverCloseButton />
                                <PopoverHeader>Information</PopoverHeader>
                                <PopoverBody>
                                    <Text>
                                        This page will detail your current connection details. This includes your&nbsp;
                                        server address and your local port.
                                    </Text>
                                    <br />
                                    <UnorderedList>
                                        <ListItem><strong>Server Address:</strong> This is the address that your clients will connect to</ListItem>
                                        <ListItem><strong>Local Port:</strong> This is the port that the HTTP server is running on, 
                                            and the port you will use when port forwarding&nbsp;
                                            for a dynamic DNS
                                        </ListItem>
                                    </UnorderedList>
                                </PopoverBody>
                            </PopoverContent>
                        </Popover>
                    </Flex>
                    <Divider orientation='horizontal' />
                    <Spacer />
                    <Flex flexDirection="row" justifyContent="space-between">
                        <Stack>
                            <Flex flexDirection="row" alignItems='center'>
                                <Text fontSize='md' fontWeight='bold' mr={2}>Server Address: </Text>
                                {(!address) ? (
                                    <SkeletonText noOfLines={1} />
                                ) : (
                                    <Text fontSize='md'>{address}</Text>
                                )}
                                <Tooltip label='Copy Address'>
                                    <IconButton
                                        ml={3}
                                        size='md'
                                        aria-label='Copy Address'
                                        icon={<BiCopy size='22px' />}
                                        onClick={() => copyToClipboard(address)}
                                    />
                                </Tooltip>
                                <Popover placement='bottom' isLazy={true}>
                                    <PopoverTrigger>
                                        <Box ml={2} _hover={{ color: 'brand.primary', cursor: 'pointer' }} >
                                            <Tooltip label='Show QR Code'>
                                                <IconButton
                                                    ml={1}
                                                    size='md'
                                                    aria-label='Show QR Code'
                                                    icon={<AiOutlineQrcode size='24px' />}
                                                />
                                            </Tooltip>
                                        </Box>
                                    </PopoverTrigger>
                                    <PopoverContent>
                                        <PopoverArrow />
                                        <PopoverCloseButton />
                                        <PopoverHeader>QR Code</PopoverHeader>
                                        <PopoverBody>
                                            <Flex justifyContent='center' flexDirection='column' alignItems='center'>
                                                <Text>
                                                    Your QR Code contains your server configuration so that clients can connect.
                                                    Your QR Code should remain <strong>private</strong> as it contains sensitive information!
                                                </Text>
                                                <Box border="5px solid" borderColor='white' mt={4} height='266px' width='266px' borderRadius='lg' mb={3}>
                                                    {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
                                                    {/* @ts-ignore: ts2876 */}
                                                    {(qrCode) ? <QRCode value={qrCode as string} /> : null}
                                                </Box>
                                            </Flex>
                                        </PopoverBody>
                                    </PopoverContent>
                                </Popover>
                            </Flex>
                            <Flex flexDirection="row">
                                <Text fontSize='md' fontWeight='bold' mr={2}>Local Port: </Text>
                                {(!port) ? (
                                    <SkeletonText noOfLines={1} />
                                ) : (
                                    <Text fontSize='md'>{port}</Text>
                                )}
                            </Flex>
                        </Stack>
                        <Divider orientation="vertical" />
                    </Flex>
                </Stack>
                <Stack direction='column' p={5}>
                    <Flex flexDirection='row' justifyContent='flex-start' alignItems='center'>
                        <Text fontSize='2xl'>iMessage Highlights</Text>
                        <Popover trigger='hover'>
                            <PopoverTrigger>
                                <Box ml={2} _hover={{ color: 'brand.primary', cursor: 'pointer' }}>
                                    <AiOutlineInfoCircle />
                                </Box>
                            </PopoverTrigger>
                            <PopoverContent>
                                <PopoverArrow />
                                <PopoverCloseButton />
                                <PopoverHeader>Information</PopoverHeader>
                                <PopoverBody>
                                    <Text>
                                        These are just some fun stats that I included to give you a quick "snapshot"
                                        of your iMessage history on the Mac Device. This does not include messages that
                                        are on Apple's servers, only what is local to this device.
                                    </Text>
                                </PopoverBody>
                            </PopoverContent>
                        </Popover>
                    </Flex>
                    <Divider orientation='horizontal' />
                    <Spacer />
                    { /* Delays are so older systems do not freeze when requesting data from the databases */ }
                    <SimpleGrid columns={3} spacing={5}>
                        <TotalMessagesStatBox />
                        <TopGroupStatBox delay={200} />
                        <BestFriendStatBox delay={400} />
                        <DailyMessagesStatBox delay={600} />
                        <TotalPicturesStatBox delay={800} />
                        <TotalVideosStatBox delay={1000} />
                    </SimpleGrid>
                </Stack>
            </Flex>
        </Box>
    );
}
Example #14
Source File: FcmLayout.tsx    From bluebubbles-server with Apache License 2.0 4 votes vote down vote up
FcmLayout = (): JSX.Element => {
    const dispatch = useAppDispatch();
    const alertRef = useRef(null);
    const confirmationActions: NodeJS.Dict<any> = {
        clearConfiguration: {
            message: (
                'Are you sure you want to clear your FCM Configuration?<br /><br />' +
                'Doing so will prevent notifications from being delivered until ' +
                'your configuration is re-loaded'
            ),
            func: async () => {
                const success = await clearFcmConfiguration();
                if (success) {
                    dispatch(setConfig({ name: 'fcm_client', 'value': null }));
                    dispatch(setConfig({ name: 'fcm_server', 'value': null }));
                }
            }
        }
    };

    const serverLoaded = (useAppSelector(state => state.config.fcm_server !== null) ?? false);
    const clientLoaded = (useAppSelector(state => state.config.fcm_client !== null) ?? false);
    const [isDragging, setDragging] = useBoolean();
    const [errors, setErrors] = useState([] as Array<ErrorItem>);
    const [requiresConfirmation, setRequiresConfirmation] = useState(null as string | null);
    const alertOpen = errors.length > 0;
    

    const onDrop = async (e: React.DragEvent<HTMLDivElement>) => {
        e.preventDefault();

        dragCounter = 0;
        setDragging.off();
        
        // I'm not sure why, but we need to copy the file data _before_ we read it using the file reader.
        // If we do not, the data transfer file list gets set to empty after reading the first file.
        const listCopy: Array<Blob> = [];
        for (let i = 0; i < e.dataTransfer.files.length; i++) {
            listCopy.push(e.dataTransfer.files.item(i) as Blob);
        }

        // Actually read the files
        const errors: Array<ErrorItem> = [];
        for (let i = 0; i < listCopy.length; i++) {
            try {
                const fileStr = await readFile(listCopy[i]);
                const validClient = isValidClientConfig(fileStr);
                const validServer = isValidServerConfig(fileStr);
                const jsonData = JSON.parse(fileStr);

                if (validClient) {
                    const test = isValidFirebaseUrl(jsonData);
                    if (test) {
                        await saveFcmClient(jsonData);
                        dispatch(setConfig({ name: 'fcm_client', 'value': jsonData }));
                    } else {
                        throw new Error(
                            'Your Firebase setup does not have a real-time database enabled. ' +
                            'Please enable the real-time database in your Firebase Console.'
                        );
                    }
                } else if (validServer) {
                    await saveFcmServer(jsonData);
                    dispatch(setConfig({ name: 'fcm_server', 'value': jsonData }));
                } else {
                    throw new Error('Invalid Google FCM File!');
                }
            } catch (ex: any) {
                errors.push({ id: String(i), message: ex?.message ?? String(ex) });
            }
        }

        if (errors.length > 0) {
            setErrors(errors);
        }
    };

    const onDragEnter = (e: React.DragEvent<HTMLDivElement>) => {
        e.preventDefault();
        if (dragCounter === 0) {
            setDragging.on();
        }

        dragCounter += 1;
    };

    const onDragOver = (e: React.DragEvent<HTMLDivElement>) => {
        e.stopPropagation();
        e.preventDefault();
    };

    const onDragLeave = () => {
        dragCounter -= 1;
        if (dragCounter === 0) {
            setDragging.off();
        }
    };

    const closeAlert = () => {
        setErrors([]);
    };

    const confirm = (confirmationType: string | null) => {
        setRequiresConfirmation(confirmationType);
    };

    return (
        <Box
            p={3}
            borderRadius={10}
            onDragEnter={(e) => onDragEnter(e)}
            onDragLeave={() => onDragLeave()}
            onDragOver={(e) => onDragOver(e)}
            onDrop={(e) => onDrop(e)}
        >
            <Stack direction='column' p={5}>
                <Text fontSize='2xl'>Controls</Text>
                <Divider orientation='horizontal' />
                <Flex flexDirection="row" justifyContent="flex-start">
                    <Menu>
                        <MenuButton
                            as={Button}
                            rightIcon={<BsChevronDown />}
                            width="12em"
                            mr={5}
                        >
                            Manage
                        </MenuButton>
                        <MenuList>
                            <MenuItem icon={<FiTrash />} onClick={() => confirm('clearConfiguration')}>
                                Clear Configuration
                            </MenuItem>
                        </MenuList>
                    </Menu>
                </Flex>
            </Stack>
            <Stack direction='column' p={5}>
                <Flex flexDirection='row' justifyContent='flex-start' alignItems='center'>
                    <Text fontSize='2xl'>Configuration</Text>
                    <Popover trigger='hover'>
                        <PopoverTrigger>
                            <Box ml={2} _hover={{ color: 'brand.primary', cursor: 'pointer' }}>
                                <AiOutlineInfoCircle />
                            </Box>
                        </PopoverTrigger>
                        <PopoverContent>
                            <PopoverArrow />
                            <PopoverCloseButton />
                            <PopoverHeader>Information</PopoverHeader>
                            <PopoverBody>
                                <Text>
                                    Drag and drop your JSON configuration files from your Google Firebase Console. If you
                                    do not have these configuration files. Please go to
                                    <span style={{ color: baseTheme.colors.brand.primary }}>
                                        <Link href='https://bluebubbles.app/install' color='brand.primary' target='_blank'> Our Website </Link>
                                    </span>
                                    to learn how.
                                </Text>
                                <Text>
                                    These configurations enable the BlueBubbles server to send notifications and other
                                    messages to all of the clients via Google FCM. Google Play Services is required
                                    for Android Devices.
                                </Text>
                            </PopoverBody>
                        </PopoverContent>
                    </Popover>
                </Flex>
                <Divider orientation='horizontal' />
                <Spacer />

                <SimpleGrid columns={2} spacing={5}>
                    <DropZone
                        text="Drag n' Drop Google Services JSON"
                        loadedText="Google Services JSON Successfully Loaded!"
                        isDragging={isDragging}
                        isLoaded={clientLoaded}
                    />
                    <DropZone
                        text="Drag n' Drop Admin SDK JSON"
                        loadedText="Admin SDK JSON Successfully Loaded!"
                        isDragging={isDragging}
                        isLoaded={serverLoaded}
                    />
                </SimpleGrid>
            </Stack>

            <ErrorDialog
                errors={errors}
                modalRef={alertRef}
                onClose={() => closeAlert()}
                isOpen={alertOpen}
            />

            <ConfirmationDialog
                modalRef={alertRef}
                onClose={() => confirm(null)}
                body={confirmationActions[requiresConfirmation as string]?.message}
                onAccept={() => {
                    if (hasKey(confirmationActions, requiresConfirmation as string)) {
                        confirmationActions[requiresConfirmation as string].func();
                    }
                }}
                isOpen={requiresConfirmation !== null}
            />
        </Box>
    );
}
Example #15
Source File: index.tsx    From engine with MIT License 4 votes vote down vote up
Header = () => (
  <AccordionItem>
    <Heading>
      <AccordionButton _expanded={{ bg: "gray.300", color: "white" }}>
        <Box flex="1" textAlign="left">
          Header
        </Box>
        <AccordionIcon />
      </AccordionButton>
    </Heading>
    <AccordionPanel pb={4}>
      <HStack mb="3">
        <Box w="70%">
          Fill values from a past runtime call, a unit test or auto determine
          values based on observe and update dependencies
        </Box>
        <Box w="30%">
          <Button size="sm" mr="3" mb="2" color="teal">
            Auto
          </Button>
          <Popover>
            <PopoverTrigger>
              <Button size="sm" mr="3" mb="2" color="purple">
                From call
              </Button>
            </PopoverTrigger>
            <PopoverContent>
              <PopoverArrow />
              <PopoverHeader>Runtime call history (5)</PopoverHeader>
              <PopoverCloseButton />
              <PopoverBody>
                <OrderedList>
                  <ListItem
                    cursor="pointer"
                    _hover={{
                      color: "teal.500",
                    }}
                  >
                    23/02/2022 14:15:10.123
                  </ListItem>
                  <ListItem
                    cursor="pointer"
                    _hover={{
                      color: "teal.500",
                    }}
                  >
                    23/02/2022 14:13:2.130
                  </ListItem>
                  <ListItem
                    cursor="pointer"
                    _hover={{
                      color: "teal.500",
                    }}
                  >
                    23/02/2022 14:12:41.500
                  </ListItem>
                  <ListItem
                    cursor="pointer"
                    _hover={{
                      color: "teal.500",
                    }}
                  >
                    23/02/2022 13:21:20.341
                  </ListItem>
                  <ListItem
                    cursor="pointer"
                    _hover={{
                      color: "teal.500",
                    }}
                  >
                    23/02/2022 12:40:19.983
                  </ListItem>
                </OrderedList>
              </PopoverBody>
            </PopoverContent>
          </Popover>
          <Popover>
            <PopoverTrigger>
              <Button size="sm" color="blue">
                From test
              </Button>
            </PopoverTrigger>
            <PopoverContent>
              <PopoverArrow />
              <PopoverHeader>Tests (100% coverage)</PopoverHeader>
              <PopoverCloseButton />
              <PopoverBody>
                <OrderedList>
                  <ListItem
                    cursor="pointer"
                    _hover={{
                      color: "teal.500",
                    }}
                  >
                    should ensure guards work (30% coverage)
                  </ListItem>
                  <ListItem
                    cursor="pointer"
                    _hover={{
                      color: "teal.500",
                    }}
                  >
                    should do computation when y is less (70% coverage)
                  </ListItem>
                  <ListItem
                    cursor="pointer"
                    _hover={{
                      color: "teal.500",
                    }}
                  >
                    should reject if y is greater than (64% coverage)
                  </ListItem>
                </OrderedList>
              </PopoverBody>
            </PopoverContent>
          </Popover>
        </Box>
      </HStack>

      <Divider mb="4" />

      <Box mb="4">
        <HStack>
          <InputGroup size="sm" w="70%">
            <InputLeftAddon children="pie" w="32" />
            <Input defaultValue="3.14" />
          </InputGroup>
        </HStack>
        <HStack>
          <InputGroup size="sm" w="70%">
            <InputLeftAddon children="http" w="32" />
            <Input defaultValue={`axios from "axios"`} />
          </InputGroup>
          <InputGroup size="sm" w="30%">
            <Input defaultValue="[mock]" bg="black.100" />
            <InputRightAddon children={<SettingsIcon />} cursor="pointer" />
          </InputGroup>
        </HStack>
        <HStack>
          <InputGroup size="sm" w="70%">
            <InputLeftAddon children="operation" w="32" />
            <Input defaultValue="prop.operation" />
          </InputGroup>
          <InputGroup size="sm" w="30%">
            <Input defaultValue="sum" />
            <InputRightAddon children={<SettingsIcon />} cursor="pointer" />
          </InputGroup>
        </HStack>
        <HStack>
          <InputGroup size="sm" w="70%">
            <InputLeftAddon children="foo" w="32" />
            <Input defaultValue="observe.foo.value" />
          </InputGroup>
          <InputGroup size="sm" w="30%">
            <Input defaultValue="123" bg="teal.800" />
            <InputRightAddon children={<SettingsIcon />} cursor="pointer" />
          </InputGroup>
        </HStack>
        <HStack>
          <InputGroup size="sm" w="70%">
            <InputLeftAddon children="bam" w="32" />
            <Input defaultValue="observe.bar.internal.something" />
          </InputGroup>
          <InputGroup size="sm" w="30%">
            <Input defaultValue="321" bg="teal.800" />
            <InputRightAddon children={<SettingsIcon />} cursor="pointer" />
          </InputGroup>
        </HStack>
        <HStack>
          <InputGroup size="sm" w="70%">
            <InputLeftAddon children="updateSome" w="32" />
            <Input defaultValue="update.a.value.somewhere" />
          </InputGroup>
          <InputGroup size="sm" w="30%">
            <Input defaultValue="444" bg="yellow.700" />
            <InputRightAddon children={<SettingsIcon />} cursor="pointer" />
          </InputGroup>
        </HStack>
        <HStack>
          <InputGroup size="sm" w="70%">
            <InputLeftAddon children="" w="32" />
            <Input placeholder="enter something..." />
          </InputGroup>
          <InputGroup size="sm" w="30%">
            <Input placeholder="sample value" />
            <InputRightAddon children={<SettingsIcon />} cursor="pointer" />
          </InputGroup>
        </HStack>
      </Box>

      <HStack>
        <Text w="70%" size="sm">
          Last run took 14ms
        </Text>
        <ButtonGroup variant="outline" spacing="4">
          <Button size="sm" colorScheme="black">
            Save as test
          </Button>
          <Button size="sm" colorScheme="green" variant="solid">
            Run
          </Button>
        </ButtonGroup>
      </HStack>
    </AccordionPanel>
  </AccordionItem>
)
Example #16
Source File: DevicesLayout.tsx    From bluebubbles-server with Apache License 2.0 4 votes vote down vote up
DevicesLayout = (): JSX.Element => {
    const [requiresConfirmation, confirm] = useState((): string | null => {
        return null;
    });
    const alertRef = useRef(null);
    const devices = useAppSelector(state => state.deviceStore.devices);
    const dispatch = useAppDispatch();
    
    useEffect(() => {
        refreshDevices(false);

        // Refresh devices every 60 seconds
        const refresher = setInterval(() => {
            refreshDevices(false);
        }, 60000);

        // Return a function to clear the interval on unmount
        return () => clearInterval(refresher);
    }, []);

    return (
        <Box p={3} borderRadius={10}>
            <Stack direction='column' p={5}>
                <Text fontSize='2xl'>Controls</Text>
                <Divider orientation='horizontal' />
                <Box>
                    <Menu>
                        <MenuButton
                            as={Button}
                            rightIcon={<BsChevronDown />}
                            width="12em"mr={5}
                        >
                            Manage
                        </MenuButton>
                        <MenuList>
                            <MenuItem icon={<BiRefresh />} onClick={() => refreshDevices()}>
                                Refresh Devices
                            </MenuItem>
                            <MenuItem icon={<FiTrash />} onClick={() => confirm('clearDevices')}>
                                Clear Devices
                            </MenuItem>
                        </MenuList>
                    </Menu>
                </Box>
            </Stack>
            <Stack direction='column' p={5}>
                <Flex flexDirection='row' justifyContent='flex-start' alignItems='center'>
                    <Text fontSize='2xl'>Devices</Text>
                    <Popover trigger='hover'>
                        <PopoverTrigger>
                            <Box ml={2} _hover={{ color: 'brand.primary', cursor: 'pointer' }}>
                                <AiOutlineInfoCircle />
                            </Box>
                        </PopoverTrigger>
                        <PopoverContent>
                            <PopoverArrow />
                            <PopoverCloseButton />
                            <PopoverHeader>Information</PopoverHeader>
                            <PopoverBody>
                                <Text>
                                    Here is where you'll find any devices that are registered with your BlueBubbles
                                    server to receive notifications and other messages. If you do not see your device
                                    here after setting up your app, please contact us for assistance.
                                </Text>
                            </PopoverBody>
                        </PopoverContent>
                    </Popover>
                </Flex>
                <Divider orientation='horizontal' />
                {(devices.length === 0) ? (
                    <Flex justifyContent="center" alignItems="center">
                        <section style={{marginTop: 20}}>
                            <Text fontSize="md">You have no devices registered with the server!</Text>
                        </section>
                    </Flex>
                ) : null}
                {(devices.length > 0) ? (
                    <DevicesTable devices={devices} />
                ) : null}
            </Stack>

            <ConfirmationDialog
                modalRef={alertRef}
                onClose={() => confirm(null)}
                body={confirmationActions[requiresConfirmation as string]?.message}
                onAccept={() => {
                    if (hasKey(confirmationActions, requiresConfirmation as string)) {
                        if (confirmationActions[requiresConfirmation as string].shouldDispatch ?? false) {
                            dispatch(confirmationActions[requiresConfirmation as string].func() as AnyAction);
                        } else {
                            confirmationActions[requiresConfirmation as string].func();
                        }
                    }
                }}
                isOpen={requiresConfirmation !== null}
            />
        </Box>
    );
}
Example #17
Source File: ContactsLayout.tsx    From bluebubbles-server with Apache License 2.0 4 votes vote down vote up
ContactsLayout = (): JSX.Element => {
    const [search, setSearch] = useState('' as string);
    const [isLoading, setIsLoading] = useBoolean(true);
    const [contacts, setContacts] = useState([] as any[]);
    const [permission, setPermission] = useState((): string | null => {
        return null;
    });
    const dialogRef = useRef(null);
    const inputFile = useRef(null);
    const [dialogOpen, setDialogOpen] = useBoolean();
    const alertRef = useRef(null);
    const [requiresConfirmation, confirm] = useState((): string | null => {
        return null;
    });

    let filteredContacts = contacts;
    if (search && search.length > 0) {
        filteredContacts = filteredContacts.filter((c) => buildIdentifier(c).includes(search.toLowerCase()));
    }

    const {
        currentPage,
        setCurrentPage,
        pagesCount,
        pages
    } = usePagination({
        pagesCount: Math.ceil(filteredContacts.length / perPage),
        initialState: { currentPage: 1 },
    });

    const refreshPermissionStatus = async (): Promise<void> => {
        setPermission(null);
        await waitMs(500);
        ipcRenderer.invoke('contact-permission-status').then((status: string) => {
            setPermission(status);
        }).catch(() => {
            setPermission('Unknown');
        });
    };

    const requestContactPermission = async (): Promise<void> => {
        setPermission(null);
        ipcRenderer.invoke('request-contact-permission').then((status: string) => {
            setPermission(status);
        }).catch(() => {
            setPermission('Unknown');
        });
    };

    const loadContacts = (showToast = false) => {
        ipcRenderer.invoke('get-contacts').then((contactList: any[]) => {
            setContacts(contactList.map((e: any) => {
                // Patch the ID as a string
                e.id = String(e.id);
                return e;
            }));
            setIsLoading.off();
        }).catch(() => {
            setIsLoading.off();
        });

        if (showToast) {
            showSuccessToast({
                id: 'contacts',
                description: 'Successfully refreshed Contacts!'
            });
        }
    };

    useEffect(() => {
        loadContacts();
        refreshPermissionStatus();
    }, []);

    const getEmptyContent = () => {
        const wrap = (child: JSX.Element) => {
            return (
                <section style={{marginTop: 20}}>
                    {child}
                </section>
            );
        };

        if (isLoading) {
            return wrap(<CircularProgress isIndeterminate />);
        }

        if (contacts.length === 0) {
            return wrap(<Text fontSize="md">BlueBubbles found no contacts in your Mac's Address Book!</Text>);
        }

        return null;
    };

    const filterContacts = () => {
        return filteredContacts.slice((currentPage - 1) * perPage, currentPage * perPage);
    };

    const onCreate = async (contact: ContactItem) => {
        const newContact = await createContact(
            contact.firstName,
            contact.lastName,
            {
                emails: contact.emails.map((e: NodeJS.Dict<any>) => e.address),
                phoneNumbers: contact.phoneNumbers.map((e: NodeJS.Dict<any>) => e.address)
            }
        );

        if (newContact) {
            // Patch the contact using a string ID & source type
            newContact.id = String(newContact.id);
            newContact.sourceType = 'db';

            // Patch the addresses
            (newContact as any).phoneNumbers = (newContact as any).addresses.filter((e: any) => e.type === 'phone');
            (newContact as any).emails = (newContact as any).addresses.filter((e: any) => e.type === 'email');

            setContacts([newContact, ...contacts]);
        }
    };

    const onUpdate = async (contact: NodeJS.Dict<any>) => {
        const cId = typeof(contact.id) === 'string' ? Number.parseInt(contact.id) : contact.id as number;
        const newContact = await updateContact(
            cId,
            {
                firstName: contact.firstName,
                lastName: contact.lastName,
                displayName: contact.displayName
            }
        );

        const copiedContacts = [...contacts];
        let updated = false;
        for (let i = 0; i < copiedContacts.length; i++) {
            if (copiedContacts[i].id === String(cId)) {
                copiedContacts[i].firstName = newContact.firstName;
                copiedContacts[i].lastName = newContact.lastName;
                copiedContacts[i].displayName = newContact.displayName;
                updated = true;
            }
        }

        if (updated) {
            setContacts(copiedContacts);
        }
    };

    const onDelete = async (contactId: number | string) => {
        await deleteContact(typeof(contactId) === 'string' ? Number.parseInt(contactId as string) : contactId);
        setContacts(contacts.filter((e: ContactItem) => {
            return e.id !== String(contactId);
        }));
    };

    const onAddAddress = async (contactId: number | string, address: string) => {
        const cId = typeof(contactId) === 'string' ? Number.parseInt(contactId as string) : contactId;
        const addr = await addAddressToContact(cId, address, address.includes('@') ? 'email' : 'phone');
        if (addr) {
            setContacts(contacts.map((e: ContactItem) => {
                if (e.id !== String(contactId)) return e;
                if (address.includes('@')) {
                    e.emails = [...e.emails, addr];
                } else {
                    e.phoneNumbers = [...e.phoneNumbers, addr];
                }

                return e;
            }));
        }
    };

    const onDeleteAddress = async (contactAddressId: number) => {
        await deleteContactAddress(contactAddressId);
        setContacts(contacts.map((e: ContactItem) => {
            e.emails = e.emails.filter((e: ContactAddress) => e.id !== contactAddressId);
            e.phoneNumbers = e.phoneNumbers.filter((e: ContactAddress) => e.id !== contactAddressId);
            return e;
        }));
    };

    const clearLocalContacts = async () => {
        // Delete the contacts, then filter out the DB items
        await deleteLocalContacts();
        setContacts(contacts.filter(e => e.sourceType !== 'db'));
    };

    const confirmationActions: ConfirmationItems = {
        clearLocalContacts: {
            message: (
                'Are you sure you want to clear/delete all local Contacts?<br /><br />' +
                'This will remove any Contacts added manually, via the API, or via the import process'
            ),
            func: clearLocalContacts
        }
    };

    return (
        <Box p={3} borderRadius={10}>
            <Stack direction='column' p={5}>
                <Text fontSize='2xl'>Controls</Text>
                <Divider orientation='horizontal' />
                <Box>
                    <Menu>
                        <MenuButton
                            as={Button}
                            rightIcon={<BsChevronDown />}
                            width="12em"mr={5}
                        >
                            Manage
                        </MenuButton>
                        <MenuList>
                            <MenuItem icon={<BsPersonPlus />} onClick={() => setDialogOpen.on()}>
                                Add Contact
                            </MenuItem>
                            <MenuItem icon={<BiRefresh />} onClick={() => loadContacts(true)}>
                                Refresh Contacts
                            </MenuItem>
                            <MenuItem
                                icon={<BiImport />}
                                onClick={() => {
                                    if (inputFile && inputFile.current) {
                                        (inputFile.current as HTMLElement).click();
                                    }
                                }}
                            >
                                Import VCF
                                <input
                                    type='file'
                                    id='file'
                                    ref={inputFile}
                                    accept=".vcf"
                                    style={{display: 'none'}}
                                    onChange={(e) => {
                                        const files = e?.target?.files ?? [];
                                        for (const i of files) {
                                            ipcRenderer.invoke('import-vcf', i.webkitRelativePath);
                                        }
                                    }}
                                />
                            </MenuItem>
                            <MenuDivider />
                            <MenuItem icon={<FiTrash />} onClick={() => confirm('clearLocalContacts')}>
                                Clear Local Contacts
                            </MenuItem>
                        </MenuList>
                    </Menu>
                    <Menu>
                        <MenuButton
                            as={Button}
                            rightIcon={<BsChevronDown />}
                            width="12em"
                            mr={5}
                        >
                            Permissions
                        </MenuButton>
                        <MenuList>
                            <MenuItem icon={<BiRefresh />} onClick={() => refreshPermissionStatus()}>
                                Refresh Permission Status
                            </MenuItem>
                            {(permission !== null && permission !== 'Authorized') ? (
                                <MenuItem icon={<BsUnlockFill />} onClick={() => requestContactPermission()}>
                                    Request Permission
                                </MenuItem>
                            ) : null}
                        </MenuList>
                    </Menu>
                    <Text as="span" verticalAlign="middle">
                        Status: <Text as="span" color={getPermissionColor(permission)}>
                            {permission ? permission : 'Checking...'}
                        </Text>
                    </Text>
                </Box>
            </Stack>
            <Stack direction='column' p={5}>
                <Flex flexDirection='row' justifyContent='flex-start' alignItems='center'>
                    <Text fontSize='2xl'>Contacts ({filteredContacts.length})</Text>
                    <Popover trigger='hover'>
                        <PopoverTrigger>
                            <Box ml={2} _hover={{ color: 'brand.primary', cursor: 'pointer' }}>
                                <AiOutlineInfoCircle />
                            </Box>
                        </PopoverTrigger>
                        <PopoverContent>
                            <PopoverArrow />
                            <PopoverCloseButton />
                            <PopoverHeader>Information</PopoverHeader>
                            <PopoverBody>
                                <Text>
                                    Here are the contacts on your macOS device that BlueBubbles knows about,
                                    and will serve to any clients that want to know about them. These include
                                    contacts from this Mac's Address Book, as well as contacts from uploads/imports
                                    or manual entry.
                                </Text>
                            </PopoverBody>
                        </PopoverContent>
                    </Popover>
                </Flex>
                <Divider orientation='horizontal' />
                <Flex flexDirection='row' justifyContent='flex-end' alignItems='center' pt={3}>
                    <InputGroup width="xxs">
                        <InputLeftElement pointerEvents='none'>
                            <AiOutlineSearch color='gray.300' />
                        </InputLeftElement>
                        <Input
                            placeholder='Search Contacts'
                            onChange={(e) => {
                                if (currentPage > 1) {
                                    setCurrentPage(1);
                                }

                                setSearch(e.target.value);
                            }}
                            value={search}
                        />
                    </InputGroup>
                </Flex>
                <Flex justifyContent="center" alignItems="center">
                    {getEmptyContent()}
                </Flex>
                {(contacts.length > 0) ? (
                    <ContactsTable
                        contacts={filterContacts()}
                        onCreate={onCreate}
                        onDelete={onDelete}
                        onUpdate={onUpdate}
                        onAddressAdd={onAddAddress}
                        onAddressDelete={onDeleteAddress}
                    />
                ) : null}
                <Pagination
                    pagesCount={pagesCount}
                    currentPage={currentPage}
                    onPageChange={setCurrentPage}
                >
                    <PaginationContainer
                        align="center"
                        justify="space-between"
                        w="full"
                        pt={2}
                    >
                        <PaginationPrevious minWidth={'75px'}>Previous</PaginationPrevious>
                        <Box ml={1}></Box>
                        <PaginationPageGroup flexWrap="wrap" justifyContent="center">
                            {pages.map((page: number) => (
                                <PaginationPage 
                                    key={`pagination_page_${page}`} 
                                    page={page}
                                    my={1}
                                    px={3}
                                    fontSize={14}
                                />
                            ))}
                        </PaginationPageGroup>
                        <Box ml={1}></Box>
                        <PaginationNext minWidth={'50px'}>Next</PaginationNext>
                    </PaginationContainer>
                </Pagination>
            </Stack>

            <ContactDialog
                modalRef={dialogRef}
                isOpen={dialogOpen}
                onCreate={onCreate}
                onDelete={onDelete}
                onAddressAdd={onAddAddress}
                onAddressDelete={onDeleteAddress}
                onClose={() => setDialogOpen.off()}
            />

            <ConfirmationDialog
                modalRef={alertRef}
                onClose={() => confirm(null)}
                body={confirmationActions[requiresConfirmation as string]?.message}
                onAccept={() => {
                    confirmationActions[requiresConfirmation as string].func();
                }}
                isOpen={requiresConfirmation !== null}
            />
        </Box>
    );
}
Example #18
Source File: ApiLayout.tsx    From bluebubbles-server with Apache License 2.0 4 votes vote down vote up
ApiLayout = (): JSX.Element => {
    const dialogRef = useRef(null);
    const [dialogOpen, setDialogOpen] = useBoolean();
    const webhooks = useAppSelector(state => state.webhookStore.webhooks);

    return (
        <Box p={3} borderRadius={10}>
            <Flex flexDirection="column">
                <Stack direction='column' p={5}>
                    <Flex flexDirection='row' justifyContent='flex-start' alignItems='center'>
                        <Text fontSize='2xl'>API</Text>
                        <Popover trigger='hover'>
                            <PopoverTrigger>
                                <Box ml={2} _hover={{ color: 'brand.primary', cursor: 'pointer' }}>
                                    <AiOutlineInfoCircle />
                                </Box>
                            </PopoverTrigger>
                            <PopoverContent>
                                <PopoverArrow />
                                <PopoverCloseButton />
                                <PopoverHeader>Information</PopoverHeader>
                                <PopoverBody>
                                    <Text>
                                        Learn how you can interact with the API to automate and orchestrate iMessage-related
                                        actions. Our REST API gives you access to the underlying iMessage API in a
                                        more succinct and easy to digest way. We also offer webhooks so you can receive
                                        callbacks from the server.
                                    </Text>
                                </PopoverBody>
                            </PopoverContent>
                        </Popover>
                    </Flex>
                    <Divider orientation='horizontal' />
                    <Text>
                        BlueBubbles offers a high-level REST API to interact with the server, as well as iMessage itself.
                        With the API, you'll be able to send messages, fetch messages, filter chats, and more! To see what
                        else you can do in the API, please see the documentation below:
                    </Text>
                    <Spacer />
                    <LinkBox as='article' maxW='sm' px='5' pb={5} pt={2} borderWidth='1px' rounded='xl'>
                        <Text color='gray'>
                            https://documenter.getpostman.com
                        </Text>
                        <Heading size='sm' mt={2}>
                            <LinkOverlay href='https://documenter.getpostman.com/view/765844/UV5RnfwM' target='_blank'>
                                Click to view API documentation
                            </LinkOverlay>
                        </Heading>
                    </LinkBox>
                    
                </Stack>
                <Stack direction='column' p={5}>
                    <Flex flexDirection='row' justifyContent='flex-start' alignItems='center'>
                        <Text fontSize='2xl'>Webhooks</Text>
                        <Popover trigger='hover'>
                            <PopoverTrigger>
                                <Box ml={2} _hover={{ color: 'brand.primary', cursor: 'pointer' }}>
                                    <AiOutlineInfoCircle />
                                </Box>
                            </PopoverTrigger>
                            <PopoverContent>
                                <PopoverArrow />
                                <PopoverCloseButton />
                                <PopoverHeader>Information</PopoverHeader>
                                <PopoverBody>
                                    <Text>
                                        Any webhooks registered here will receive a POST request whenever an iMessage event
                                        occurs. The body of the POST request will be a JSON payload containing the type of
                                        event and the event data.
                                    </Text>
                                </PopoverBody>
                            </PopoverContent>
                        </Popover>
                    </Flex>
                    <Divider orientation='horizontal' />
                    <Spacer />
                    <Box>
                        <Menu>
                            <MenuButton
                                as={Button}
                                rightIcon={<BsChevronDown />}
                                width="12em"
                            >
                                Manage
                            </MenuButton>
                            <MenuList>
                                <MenuItem icon={<AiOutlinePlus />} onClick={setDialogOpen.on}>
                                    Add Webhook
                                </MenuItem>
                            </MenuList>
                        </Menu>
                    </Box>
                    <Spacer />
                    <WebhooksTable webhooks={webhooks} />
                </Stack>
            </Flex>

            <AddWebhookDialog
                modalRef={dialogRef}
                isOpen={dialogOpen}
                onClose={() => setDialogOpen.off()}
            />
        </Box>
    );
}
Example #19
Source File: Header.tsx    From openchakra with MIT License 4 votes vote down vote up
Header = () => {
  const showLayout = useSelector(getShowLayout)
  const showCode = useSelector(getShowCode)
  const dispatch = useDispatch()

  return (
    <DarkMode>
      <Flex
        justifyContent="space-between"
        bg="#1a202c"
        as="header"
        height="3rem"
        px="1rem"
      >
        <Flex
          width="14rem"
          height="100%"
          backgroundColor="#1a202c"
          color="white"
          as="a"
          fontSize="xl"
          flexDirection="row"
          alignItems="center"
          aria-label="Chakra UI, Back to homepage"
        >
          <Box fontSize="2xl" as={AiFillThunderbolt} mr={1} color="teal.100" />{' '}
          <Box fontWeight="bold">open</Box>chakra
        </Flex>

        <Flex flexGrow={1} justifyContent="space-between" alignItems="center">
          <HStack spacing={4} justify="center" align="center">
            <Box>
              <HeaderMenu />
            </Box>
            <FormControl flexDirection="row" display="flex" alignItems="center">
              <Tooltip
                zIndex={100}
                hasArrow
                bg="yellow.100"
                aria-label="Builder mode help"
                label="Builder mode adds extra padding/borders"
              >
                <FormLabel
                  cursor="help"
                  color="gray.200"
                  fontSize="xs"
                  htmlFor="preview"
                  pb={0}
                  mb={0}
                  mr={2}
                  whiteSpace="nowrap"
                >
                  Builder mode
                </FormLabel>
              </Tooltip>
              <LightMode>
                <Switch
                  isChecked={showLayout}
                  colorScheme="teal"
                  size="sm"
                  onChange={() => dispatch.app.toggleBuilderMode()}
                  id="preview"
                />
              </LightMode>
            </FormControl>

            <FormControl display="flex" flexDirection="row" alignItems="center">
              <FormLabel
                color="gray.200"
                fontSize="xs"
                mr={2}
                mb={0}
                htmlFor="code"
                pb={0}
                whiteSpace="nowrap"
              >
                Code panel
              </FormLabel>
              <LightMode>
                <Switch
                  isChecked={showCode}
                  id="code"
                  colorScheme="teal"
                  onChange={() => dispatch.app.toggleCodePanel()}
                  size="sm"
                />
              </LightMode>
            </FormControl>
          </HStack>

          <Stack direction="row">
            <CodeSandboxButton />
            <Popover>
              {({ onClose }) => (
                <>
                  <PopoverTrigger>
                    <Button
                      ml={4}
                      rightIcon={<SmallCloseIcon path="" />}
                      size="xs"
                      variant="ghost"
                    >
                      Clear
                    </Button>
                  </PopoverTrigger>
                  <LightMode>
                    <PopoverContent zIndex={100} bg="white">
                      <PopoverArrow />
                      <PopoverCloseButton />
                      <PopoverHeader>Are you sure?</PopoverHeader>
                      <PopoverBody fontSize="sm">
                        Do you really want to remove all components on the
                        editor?
                      </PopoverBody>
                      <PopoverFooter display="flex" justifyContent="flex-end">
                        <Button
                          size="sm"
                          variant="ghost"
                          colorScheme="red"
                          rightIcon={<CheckIcon path="" />}
                          onClick={() => {
                            dispatch.components.reset()
                            if (onClose) {
                              onClose()
                            }
                          }}
                        >
                          Yes, clear
                        </Button>
                      </PopoverFooter>
                    </PopoverContent>
                  </LightMode>
                </>
              )}
            </Popover>
          </Stack>
        </Flex>

        <Stack
          justifyContent="flex-end"
          width="13rem"
          align="center"
          direction="row"
          spacing="2"
        >
          <Link isExternal href="https://github.com/premieroctet/openchakra">
            <Box as={DiGithubBadge} size={32} color="gray.200" />
          </Link>
          <Box lineHeight="shorter" color="white" fontSize="xs">
            by{' '}
            <Link isExternal href="https://premieroctet.com" color="teal.100">
              Premier Octet
            </Link>
          </Box>
        </Stack>
      </Flex>
    </DarkMode>
  )
}
Example #20
Source File: ColorPickerControl.tsx    From openchakra with MIT License 4 votes vote down vote up
ColorPickerControl = (props: ColorPickerPropType) => {
  const theme = useTheme()
  const themeColors: any = omit(theme.colors, [
    'transparent',
    'current',
    'black',
    'white',
  ])

  const { setValue, setValueFromEvent } = useForm()
  const value = usePropsSelector(props.name)

  let propsIconButton: any = { bg: value }
  if (value && themeColors[value]) {
    propsIconButton = { colorScheme: value }
  }

  return (
    <>
      <Popover placement="bottom">
        <PopoverTrigger>
          {props.gradient ? (
            <IconButton
              mr={2}
              boxShadow="md"
              border={props.gradientColor ? 'none' : '2px solid grey'}
              isRound
              aria-label="Color"
              size="xs"
              colorScheme={props.gradientColor}
              bg={props.gradientColor}
            />
          ) : (
            <IconButton
              mr={2}
              boxShadow="md"
              border={value ? 'none' : '2px solid grey'}
              isRound
              aria-label="Color"
              size="xs"
              {...propsIconButton}
            />
          )}
        </PopoverTrigger>
        <Portal>
          <PopoverContent width="200px">
            <PopoverArrow />
            <PopoverBody>
              {props.withFullColor ? (
                <Tabs size="sm" variant="soft-rounded" colorScheme="green">
                  <TabList>
                    <Tab>Theme</Tab>
                    <Tab>All</Tab>
                  </TabList>
                  <TabPanels mt={4}>
                    <TabPanel p={0}>
                      {props.gradient ? (
                        <HuesPickerControl
                          name={props.name}
                          themeColors={themeColors}
                          enableHues
                          setValue={setValue}
                          gradient={true}
                          index={props.index}
                          updateGradient={props.updateGradient}
                        />
                      ) : (
                        <HuesPickerControl
                          name={props.name}
                          themeColors={themeColors}
                          enableHues
                          setValue={setValue}
                          gradient={props.gradient}
                        />
                      )}
                    </TabPanel>

                    <TabPanel p={0}>
                      <Box position="relative" height="150px">
                        <ColorPicker
                          color={props.gradient ? props.gradientColor : value}
                          onChange={(color: any) => {
                            props.gradient
                              ? props.updateGradient!(
                                  `#${color.hex}`,
                                  props.index!,
                                )
                              : setValue(props.name, `#${color.hex}`)
                          }}
                        />
                        );
                      </Box>
                    </TabPanel>
                  </TabPanels>
                </Tabs>
              ) : props.gradient ? (
                <HuesPickerControl
                  name={props.name}
                  themeColors={themeColors}
                  enableHues
                  setValue={setValue}
                  gradient={true}
                  index={props.index}
                  updateGradient={props.updateGradient}
                />
              ) : (
                <HuesPickerControl
                  name={props.name}
                  themeColors={themeColors}
                  enableHues={false}
                  setValue={setValue}
                  gradient={props.gradient}
                />
              )}
            </PopoverBody>
          </PopoverContent>
        </Portal>
      </Popover>
      {props.gradient ? (
        <Input
          width="100px"
          size="sm"
          name={props.name}
          onChange={(e: ChangeEvent<HTMLInputElement>) => {
            props.gradient
              ? props.updateGradient!(e.target.value, props.index!)
              : setValue(props.name, e.target.value)
          }}
          value={props.gradientColor}
        />
      ) : (
        <Input
          width="100px"
          size="sm"
          name={props.name}
          onChange={setValueFromEvent}
          value={value}
        />
      )}
    </>
  )
}